diff --git a/lib/strategies/martingale-alpha.js b/lib/strategies/martingale-alpha.js deleted file mode 100644 index d60eba0..0000000 --- a/lib/strategies/martingale-alpha.js +++ /dev/null @@ -1,126 +0,0 @@ -import { BaseStrategy } from './base.js'; -import crypto from 'crypto'; - -/** - * Martingale Alpha Strategy - * - * When odds are between 40-60% for both sides (a ~coin-flip market): - * - Use crypto.randomInt to pick yes/no - * - Round 1: bet $1, Round 2: bet $2, Round 3: bet $4 - * - If any round wins, reset to round 1 - * - If all 3 lose, reset to round 1 anyway (cap losses at $7 per cycle) - * - * Probability of losing 3 consecutive 50/50s = 12.5% - * Probability of winning at least 1 of 3 = 87.5% - */ -export class MartingaleAlphaStrategy extends BaseStrategy { - constructor(config = {}) { - super('martingale-alpha', { - minPct: config.minPct || 40, - maxPct: config.maxPct || 60, - baseBet: config.baseBet || 1, - maxRounds: config.maxRounds || 3, - slippage: config.slippage || 3, - cooldownMs: config.cooldownMs || 20000, - ...config - }); - - this.round = 0; - this.currentBetSize = this.config.baseBet; - this.cycleWins = 0; - this.cycleLosses = 0; - this.totalCycles = 0; - - this._lastTrade = { - paper: { time: 0, ticker: null }, - live: { time: 0, ticker: null } - }; - } - - evaluate(state, caller = 'paper') { - if (!state || !this.enabled) return null; - - const track = this._lastTrade[caller] || this._lastTrade.paper; - const now = Date.now(); - if (now - track.time < this.config.cooldownMs) return null; - if (state.ticker === track.ticker) return null; - - const { yesPct, noPct } = state; - const { minPct, maxPct } = this.config; - - if (yesPct < minPct || yesPct > maxPct) return null; - if (noPct < minPct || noPct > maxPct) return null; - - const flip = crypto.randomInt(0, 2); - const side = flip === 0 ? 'yes' : 'no'; - const price = side === 'yes' ? yesPct : noPct; - const maxPrice = Math.min(price + this.config.slippage, 95); - - const roundIndex = this.round; - const betSize = this.config.baseBet * Math.pow(2, roundIndex); - - const signal = { - strategy: this.name, - side, - price, - maxPrice, - size: betSize, - reason: `R${roundIndex + 1}/${this.config.maxRounds} coin-flip ${side.toUpperCase()} @ ${price}ยข ($${betSize}) | Market: ${yesPct}/${noPct}`, - ticker: state.ticker - }; - - track.time = now; - track.ticker = state.ticker; - this.currentBetSize = betSize; - - return signal; - } - - onSettlement(result, trade) { - if (!trade || trade.strategy !== this.name) return; - - const won = trade.side === result; - - if (won) { - console.log(`[MartingaleAlpha] WIN on round ${this.round + 1} โ€” cycle complete, resetting`); - this.cycleWins++; - this.totalCycles++; - this._resetCycle(); - } else { - this.round++; - if (this.round >= this.config.maxRounds) { - console.log(`[MartingaleAlpha] LOST all ${this.config.maxRounds} rounds โ€” cycle failed, resetting`); - this.cycleLosses++; - this.totalCycles++; - this._resetCycle(); - } else { - const nextBet = this.config.baseBet * Math.pow(2, this.round); - console.log(`[MartingaleAlpha] LOSS round ${this.round}/${this.config.maxRounds} โ€” next bet: $${nextBet}`); - this.currentBetSize = nextBet; - } - } - } - - _resetCycle() { - this.round = 0; - this.currentBetSize = this.config.baseBet; - // Reset both callers' ticker locks so new cycle can trade same market - this._lastTrade.paper.ticker = null; - this._lastTrade.live.ticker = null; - } - - toJSON() { - return { - ...super.toJSON(), - round: this.round + 1, - maxRounds: this.config.maxRounds, - currentBetSize: this.currentBetSize, - cycleWins: this.cycleWins, - cycleLosses: this.cycleLosses, - totalCycles: this.totalCycles, - cycleWinRate: this.totalCycles > 0 - ? parseFloat(((this.cycleWins / this.totalCycles) * 100).toFixed(1)) - : 0 - }; - } -}