import type { Round } from "./abi.js"; import { config } from "./config.js"; export type Side = "bull" | "bear" | null; export function outcome(r: Round): "bull" | "bear" | "tie" | "pending" { if (!r.oracleCalled || r.closePrice === 0n) return "pending"; if (r.closePrice === r.lockPrice) return "tie"; return r.closePrice > r.lockPrice ? "bull" : "bear"; } function momentumScore(history: Round[]): { bull: number; bear: number } { const outcomes = history.map(outcome).filter((o) => o === "bull" || o === "bear"); if (outcomes.length < 3) return { bull: 0.5, bear: 0.5 }; let bull = 0, bear = 0; const decay = 0.95; for (let i = outcomes.length - 1, w = 1; i >= 0 && i >= outcomes.length - config.lookbackRounds; i--, w *= decay) { if (outcomes[i] === "bull") bull += w; else bear += w; } const sum = bull + bear || 1; return { bull: bull / sum, bear: bear / sum }; } function streakReversal(history: Round[]): Side { const outcomes = history.map(outcome).filter((o) => o === "bull" || o === "bear"); if (outcomes.length < config.streakReversalThreshold) return null; const last = outcomes[outcomes.length - 1]; let streak = 0; for (let i = outcomes.length - 1; i >= 0 && outcomes[i] === last; i--) streak++; if (streak >= config.streakReversalThreshold) return last === "bull" ? "bear" : "bull"; return null; } export function decide( history: Round[], lastBetSide: Side, lastBetWon: boolean | null, roundsSinceBet: number ): { side: Side; edge: number } { if (history.length < 2) return { side: null, edge: 0 }; const pending = history.filter((r) => outcome(r) === "pending"); if (pending.length > 0) return { side: null, edge: 0 }; const closed = history.filter((r) => r.oracleCalled && r.closePrice !== 0n); if (closed.length < 2) return { side: null, edge: 0 }; if (lastBetWon === false && config.cooldownRoundsAfterLoss > 0 && roundsSinceBet < config.cooldownRoundsAfterLoss) return { side: null, edge: 0 }; const rev = streakReversal(closed); if (rev !== null) { const edge = 0.52 + Math.min(0.08, closed.length * 0.002); return { side: rev, edge }; } const { bull, bear } = momentumScore(closed); const rawEdge = Math.max(bull, bear); if (rawEdge < config.minEdgeScore) return { side: null, edge: rawEdge }; const side: Side = bull >= bear ? "bull" : "bear"; const edge = side === "bull" ? bull : bear; return { side, edge }; }