Problem
The current TradingLoop.run() immediately enters a live event polling loop regardless of game status. For scheduled (not yet started) games, this means:
- Shipp's
getLiveEvents() returns empty — the game hasn't started, there are no events
- The loop just sleeps and retries, doing nothing until tipoff
- Pre-game markets are actively traded on Kalshi but the bot ignores them entirely
This is a missed opportunity. Pre-game markets often have the most liquidity, and edges can exist before games start.
Proposed Solution
Split the trading loop into two distinct phases: pre-match and live.
Phase 1: Pre-match (game status = scheduled)
Instead of polling Shipp for nonexistent live events, gather context from other sources:
Data sources for pre-match context:
- Shipp metadata (already available): team rosters from
home_team_players / away_team_players stored in games.metadata
- News headlines: recent team news via RSS or news APIs (injuries, trades, lineup changes)
- Odds/lines: opening lines from sportsbooks as a baseline reference
- Team stats: season record, recent form (last 5/10 games), home/away splits
- Head-to-head history: recent matchup results between the two teams
- Injury reports: official injury reports (out/doubtful/questionable)
Architecture:
interface PrematchDataSource {
name: string
gather(game: Game, sport: string): Promise<string>
}
A pluggable system where data sources are registered and called in parallel during pre-match. Each returns a text block that gets concatenated into the full pre-match context. This makes it easy to add new sources without modifying the core loop.
Built-in sources to start:
ShippMetadataSource — extracts rosters/venue from stored game metadata
NewsHeadlineSource — fetches recent headlines via Google News RSS
KalshiMarketSource — includes current market prices as implied probabilities
Future sources (community contributions):
ESPNStatsSource, NBAStatsSource — team/player statistics
OddsAPISource — consensus lines from sportsbooks
InjuryReportSource — official injury designations
WeatherSource — for outdoor sports (NFL, MLB, Soccer)
Phase 2: Live (game status = live)
Once the game starts, switch to the existing Shipp live event polling loop. No changes needed here.
AI Adapter Changes
Add a estimatePrematch() method alongside the existing estimateProbability():
- Different system prompt tuned for pre-match analysis (base rates, team strength, home/away advantage)
- Accepts a free-text
prematchContext string instead of ShippEvent[]
- Same structured output:
{ yesProbability, confidence, reasoning }
Risk Management Considerations
Pre-match trades may warrant different risk parameters:
- Higher minimum edge threshold (markets are more efficient pre-game)
- Lower confidence acceptance (less information available)
- Optional
strategy field to distinguish pre-match vs live orders in the DB (e.g., value-bet-prematch vs value-bet)
Flow
Game status = scheduled
├── Gather pre-match context (rosters, news, stats, market prices)
├── For each Kalshi market:
│ ├── AI estimates probability from pre-match context
│ ├── Compute edge vs market price
│ ├── Risk check → place trade if edge exists
│ └── Log analysis + reasoning
├── Log stats summary
└── Poll for game status change (scheduled → live)
└── When live → switch to existing live event loop
Game Lifecycle
scheduled ──[pre-match analysis]──→ waiting ──[game starts]──→ live ──[live loop]──→ completed
Context
Currently the only data flowing into the AI is Shipp's live event stream. By adding pre-match context gathering, the bot can:
- Trade earlier (hours before tipoff when markets open)
- Use a broader information set than just in-game events
- Potentially find larger edges pre-game when markets haven't fully adjusted to news
Related
Problem
The current
TradingLoop.run()immediately enters a live event polling loop regardless of game status. For scheduled (not yet started) games, this means:getLiveEvents()returns empty — the game hasn't started, there are no eventsThis is a missed opportunity. Pre-game markets often have the most liquidity, and edges can exist before games start.
Proposed Solution
Split the trading loop into two distinct phases: pre-match and live.
Phase 1: Pre-match (game status =
scheduled)Instead of polling Shipp for nonexistent live events, gather context from other sources:
Data sources for pre-match context:
home_team_players/away_team_playersstored ingames.metadataArchitecture:
A pluggable system where data sources are registered and called in parallel during pre-match. Each returns a text block that gets concatenated into the full pre-match context. This makes it easy to add new sources without modifying the core loop.
Built-in sources to start:
ShippMetadataSource— extracts rosters/venue from stored game metadataNewsHeadlineSource— fetches recent headlines via Google News RSSKalshiMarketSource— includes current market prices as implied probabilitiesFuture sources (community contributions):
ESPNStatsSource,NBAStatsSource— team/player statisticsOddsAPISource— consensus lines from sportsbooksInjuryReportSource— official injury designationsWeatherSource— for outdoor sports (NFL, MLB, Soccer)Phase 2: Live (game status =
live)Once the game starts, switch to the existing Shipp live event polling loop. No changes needed here.
AI Adapter Changes
Add a
estimatePrematch()method alongside the existingestimateProbability():prematchContextstring instead ofShippEvent[]{ yesProbability, confidence, reasoning }Risk Management Considerations
Pre-match trades may warrant different risk parameters:
strategyfield to distinguish pre-match vs live orders in the DB (e.g.,value-bet-prematchvsvalue-bet)Flow
Game Lifecycle
Context
Currently the only data flowing into the AI is Shipp's live event stream. By adding pre-match context gathering, the bot can:
Related