From f3910603fb8cb942932f62838d34f6ee8c738f2a Mon Sep 17 00:00:00 2001 From: multipleof4 Date: Sun, 15 Mar 2026 20:44:02 -0700 Subject: [PATCH] Feat: Add Sniper Reversal strategy --- lib/strategies/sniper-reversal.js | 62 +++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 lib/strategies/sniper-reversal.js diff --git a/lib/strategies/sniper-reversal.js b/lib/strategies/sniper-reversal.js new file mode 100644 index 0000000..5a659d2 --- /dev/null +++ b/lib/strategies/sniper-reversal.js @@ -0,0 +1,62 @@ +import { BaseStrategy } from './base.js'; + +export class SniperReversalStrategy extends BaseStrategy { + constructor(config = {}) { + super('sniper-reversal', { + triggerPct: config.triggerPct || 95, // Bet against a 95% favorite + minsLeft: config.minsLeft || 3, // Only in the last 3 minutes + betSize: config.betSize || 1, // Cheap lotto tickets + cooldownMs: config.cooldownMs || 60000, + ...config + }); + + this.lastTradeTime = 0; + this.lastTradeTicker = null; + } + + evaluate(state) { + if (!state || !this.enabled || !state.closeTime) return null; + + const now = Date.now(); + if (now - this.lastTradeTime < this.config.cooldownMs) return null; + if (state.ticker === this.lastTradeTicker) return null; + + const timeLeftMs = new Date(state.closeTime).getTime() - now; + const minsLeft = timeLeftMs / 60000; + + // Only strike in the final minutes + if (minsLeft > this.config.minsLeft || minsLeft <= 0) return null; + + const { yesPct, noPct } = state; + const trigger = this.config.triggerPct; + + let signal = null; + + if (yesPct >= trigger && noPct > 0) { + signal = { + strategy: this.name, + side: 'no', + price: noPct, + size: this.config.betSize, + reason: `Buzzer beater No at ${noPct}¢ (Yes is ${yesPct}%) with ${minsLeft.toFixed(1)}m left`, + ticker: state.ticker + }; + } else if (noPct >= trigger && yesPct > 0) { + signal = { + strategy: this.name, + side: 'yes', + price: yesPct, + size: this.config.betSize, + reason: `Buzzer beater Yes at ${yesPct}¢ (No is ${noPct}%) with ${minsLeft.toFixed(1)}m left`, + ticker: state.ticker + }; + } + + if (signal) { + this.lastTradeTime = now; + this.lastTradeTicker = state.ticker; + } + + return signal; + } +}