import { BaseStrategy } from './base.js'; /** * Martingale Strategy * * Logic: * - If one side is ~70%+ (configurable), bet the opposite side. * - On loss, double the bet size (Martingale). * - On win, reset to base bet size. * - Max consecutive losses cap to prevent blowup. */ export class MartingaleStrategy extends BaseStrategy { constructor(config = {}) { super('martingale', { threshold: config.threshold || 70, // Trigger when one side >= this % baseBet: config.baseBet || 1, // Base bet in dollars maxDoublings: config.maxDoublings || 5, // Max consecutive losses before stopping cooldownMs: config.cooldownMs || 60000, // Min time between trades (1 min) ...config }); this.consecutiveLosses = 0; this.currentBetSize = this.config.baseBet; this.lastTradeTime = 0; this.lastTradeTicker = null; } evaluate(state) { if (!state || !this.enabled) return null; const now = Date.now(); // Cooldown — don't spam trades if (now - this.lastTradeTime < this.config.cooldownMs) return null; // Don't trade same ticker twice if (state.ticker === this.lastTradeTicker) return null; // Check if Martingale limit reached if (this.consecutiveLosses >= this.config.maxDoublings) { return null; // Paused — too many consecutive losses } const { yesPct, noPct } = state; const threshold = this.config.threshold; let signal = null; // If "Yes" is at 70%+, bet "No" (the underdog) if (yesPct >= threshold) { signal = { strategy: this.name, side: 'no', price: noPct, size: this.currentBetSize, reason: `Yes at ${yesPct}% (≥${threshold}%), betting No at ${noPct}¢`, ticker: state.ticker }; } // If "No" is at 70%+, bet "Yes" (the underdog) else if (noPct >= threshold) { signal = { strategy: this.name, side: 'yes', price: yesPct, size: this.currentBetSize, reason: `No at ${noPct}% (≥${threshold}%), betting Yes at ${yesPct}¢`, ticker: state.ticker }; } if (signal) { this.lastTradeTime = now; this.lastTradeTicker = state.ticker; } return signal; } onSettlement(result, trade) { if (!trade || trade.strategy !== this.name) return; const won = (trade.side === 'yes' && result === 'yes') || (trade.side === 'no' && result === 'no'); if (won) { console.log(`[Martingale] WIN — resetting to base bet $${this.config.baseBet}`); this.consecutiveLosses = 0; this.currentBetSize = this.config.baseBet; } else { this.consecutiveLosses++; this.currentBetSize = this.config.baseBet * Math.pow(2, this.consecutiveLosses); console.log(`[Martingale] LOSS #${this.consecutiveLosses} — next bet: $${this.currentBetSize}`); if (this.consecutiveLosses >= this.config.maxDoublings) { console.log(`[Martingale] MAX LOSSES REACHED. Strategy paused.`); } } } toJSON() { return { ...super.toJSON(), consecutiveLosses: this.consecutiveLosses, currentBetSize: this.currentBetSize, paused: this.consecutiveLosses >= this.config.maxDoublings }; } }