diff --git a/lib/strategies/bull-dip-buyer.js b/lib/strategies/bull-dip-buyer.js index 30df40e..33b30fa 100644 --- a/lib/strategies/bull-dip-buyer.js +++ b/lib/strategies/bull-dip-buyer.js @@ -6,12 +6,11 @@ export class BullDipBuyer extends BaseStrategy { maxYesPrice: config.maxYesPrice || 45, minYesPrice: config.minYesPrice || 15, betSize: config.betSize || 1, - slippage: config.slippage || 3, // willing to pay up to 3¢ above target - cooldownMs: config.cooldownMs || 20000, // 20s for fast markets + slippage: config.slippage || 3, + cooldownMs: config.cooldownMs || 20000, ...config }); - // Separate tracking for paper vs live this._lastTrade = { paper: { time: 0, ticker: null }, live: { time: 0, ticker: null } @@ -29,14 +28,29 @@ export class BullDipBuyer extends BaseStrategy { const { yesPct } = state; if (yesPct <= this.config.maxYesPrice && yesPct >= this.config.minYesPrice) { - const maxPrice = Math.min(yesPct + this.config.slippage, 95); + // For live trading, require orderbook data — refuse to trade blind + if (caller === 'live') { + const bestAsk = this._getBestYesAsk(state); + if (bestAsk == null) { + return null; + } + // Verify the actual ask price is within our dip range + slippage + if (bestAsk > this.config.maxYesPrice + this.config.slippage) { + return null; + } + // Don't let slippage push us above 50¢ — that's not a dip + if (bestAsk > 50) { + return null; + } + } + + const maxPrice = Math.min(yesPct + this.config.slippage, this.config.maxYesPrice + this.config.slippage, 50); - // Check orderbook for real liquidity if available const bestAsk = this._getBestYesAsk(state); let reason = `Bullish dip buy: Yes @ ${yesPct}¢`; if (bestAsk != null) { if (bestAsk > maxPrice) { - return null; // best ask too expensive even with slippage + return null; } reason = `Bullish dip buy: Yes @ ${yesPct}¢ (ask: ${bestAsk}¢)`; } @@ -60,16 +74,9 @@ export class BullDipBuyer extends BaseStrategy { } _getBestYesAsk(state) { - // Best yes ask = lowest price someone is selling yes at - // In Kalshi terms: best ask on yes side, OR 100 - best bid on no side const ob = state.orderbook; if (!ob) return null; - // Yes side: sorted descending by price, ask = lowest offer - if (ob.yes?.length) { - // orderbook yes levels are bids (people wanting to buy yes) - // The ask is 100 - best_no_bid - } if (ob.no?.length) { const bestNoBid = ob.no[0]?.[0]; if (bestNoBid != null) return 100 - bestNoBid;