Kalshi Fifa Soccer Trader

Other

Trade Kalshi soccer markets using EA FC OVR rating disparity and a bivariate Poisson model. Finds edge on match winner, total goals (over/under), and goal spread markets. Use when the user wants to trade soccer markets on Kalshi, automate soccer bets using FIFA ratings, or find mispriced soccer outcomes.

Install

openclaw skills install kalshi-fifa-soccer-trader

Kalshi FIFA Soccer Trader

Trade soccer markets on Kalshi using EA FC OVR rating disparity as the signal, executed via DFlow on Solana.

🚨 Framework, not a production trading system. Read DISCLAIMER.md before connecting a wallet with real funds.

This is a template. The default signal is EA FC OVR disparity fed through a bivariate Poisson goal model. Remix it: swap in Elo ratings, add in-play context, or filter by tournament. The skill handles market discovery, model scoring, de-vigging, and trade execution. Your alpha is the input.

Defaults: dry-run mode, max $5 USDC per trade, minimum 6% EV edge required. Pass --live to enable real trades; set SIMMER_SOCCER_MAX_POSITION_USD and SIMMER_SOCCER_ENTRY_EDGE to adjust.

Powered by DFlow. Kalshi trades execute via DFlow's Solana-based prediction market infrastructure. KYC through Proof is required for buys.

When to Use This Skill

Use when the user wants to:

  • Trade Kalshi soccer match winner, total goals, or goal spread markets
  • Use EA FC OVR ratings to find pricing edges against the market
  • Automate position management across multiple upcoming soccer matches
  • Backtest or dry-run the Poisson goal model against live Kalshi prices

Strategy

EA FC OVR disparity → bivariate Poisson → probability grid → de-vig vs Kalshi prices → EV-positive trades.

Three market types handled:

  • Match winner — P(home win) / P(draw) / P(away win) vs the YES/NO price
  • Total goals — P(total goals > line) vs the over market price
  • Goal spread — P(home wins by ≥ N goals) vs the spread market price

Model in brief:

  1. Look up both teams' EA FC OVR in ratings.json (top ~200 clubs + all national teams)
  2. Convert OVR disparity → expected goals per team (λ_home, λ_away) via log-linear model with home advantage
  3. Build a 10×10 joint Poisson goal grid
  4. Sum grid cells to get probabilities for each market type
  5. De-vig the Kalshi YES/NO prices to implied probability
  6. Edge = model probability − implied probability
  7. Trade when edge > threshold and safeguards pass

Setup

  1. Install the Simmer SDK

    pip install simmer-sdk
    
  2. Set your Simmer API key

    export SIMMER_API_KEY=...   # simmer.markets/dashboard → SDK tab
    
  3. Set your Solana private key (live trading only)

    export SOLANA_PRIVATE_KEY=<base58-encoded-secret-key>
    
  4. Complete KYC (required for buys on Kalshi)

    • Verify at dflow.net/proof
    • Check status: curl "https://api.simmer.markets/api/proof/status?wallet=YOUR_SOLANA_ADDRESS"
  5. Fund wallet

    • SOL on Solana mainnet for gas (~0.05 SOL)
    • USDC on Solana mainnet for capital
  6. Import soccer markets (first run)

    python soccer_trader.py --import-markets
    
  7. Dry run first — always

    python soccer_trader.py
    

Configuration

SettingEnv varDefaultDescription
Entry edgeSIMMER_SOCCER_ENTRY_EDGE0.06Min EV edge to trade (6%)
Exit thresholdSIMMER_SOCCER_EXIT_THRESHOLD0.90Sell when implied prob reaches this
Max positionSIMMER_SOCCER_MAX_POSITION_USD5.00Max USDC per trade
Max trades/runSIMMER_SOCCER_MAX_TRADES_PER_RUN5Cap per scan cycle
Market typesSIMMER_SOCCER_MARKET_TYPESwinner,totals,spreadWhich market types to trade (comma-sep)
Min liquiditySIMMER_SOCCER_MIN_LIQUIDITY500Skip markets with liquidity below this USD
Slippage maxSIMMER_SOCCER_SLIPPAGE_MAX0.10Skip if estimated slippage > 10%
Smart sizing %SIMMER_SOCCER_SIZING_PCT0.04% of balance per trade (with --smart-sizing)
Home advantageSIMMER_SOCCER_HOME_BOOST0.12Log-scale home goal boost (0 = neutral venue)

Quick Commands

# Status + open positions
python scripts/status.py

# Positions only
python scripts/status.py --positions

# Full P&L
python scripts/status.py --pnl

Running the Skill

# Dry run (default) — shows edge but no trades
python soccer_trader.py

# Live trading
python soccer_trader.py --live

# Smart position sizing from portfolio balance
python soccer_trader.py --live --smart-sizing

# Only trade match winner markets
python soccer_trader.py --live --market-types winner

# Only totals and spread
python soccer_trader.py --live --market-types totals,spread

# Import new Kalshi soccer markets
python soccer_trader.py --import-markets

# Show current model config and ratings loaded
python soccer_trader.py --config

# Quiet — only print on trades/errors (ideal for cron)
python soccer_trader.py --live --smart-sizing --quiet

# Manage open positions (exit anything above threshold)
python soccer_trader.py --live --manage-positions

How It Works

Each cycle:

  1. Fetch active soccer markets from Simmer API (filtered by import_source=kalshi + soccer keywords)
  2. Parse team names and market type from each market's title/question
  3. Look up EA FC OVR for each team from ratings.json (fuzzy match on team aliases)
  4. Compute λ_home and λ_away from OVR disparity
  5. Build 10×10 Poisson goal grid
  6. Derive model probability for the market's YES outcome
  7. De-vig Kalshi YES/NO prices to implied fair probability
  8. Edge = model_prob − implied_prob
  9. Safeguards: check time-to-resolution, slippage, flip-flop, liquidity
  10. Buy YES if edge > entry threshold (or NO if negative edge is large enough)
  11. Tag every trade with signal_data (OVRs, λs, model prob, edge, market type) for backtesting

Ratings

Team OVR ratings live in ratings.json. The skill uses a three-layer hierarchy to compute each team's effective OVR:

  1. Formation-aware (lineup_intel.py) — picks the best XI for a given formation (default 4-3-3) by position group, then adjusts for confirmed absences from lineup_cache.json
  2. top11_avg_ovr — average OVR of the best 11 players (fallback if no player data)
  3. ovr — composite team OVR including bench (last resort)

Fields per team:

FieldDescription
ovrComposite team OVR (bench included)
att / mid / defPositional ratings
top11_avg_ovrAverage OVR of the best 11 players
playersList of {name, ovr, position} — used by lineup intel
aliasesFuzzy-match list for Kalshi market title parsing

Lineup Intelligence

lineup_intel.py adds formation-aware OVR and injury signal detection on top of the static ratings.

# Step 1 (run once, or to refresh): load official WC 2026 squads from Wikipedia
python3 scripts/fetch_wc_squads.py              # all 48 WC teams
python3 scripts/fetch_wc_squads.py Spain France # specific teams only
python3 scripts/fetch_wc_squads.py --dry-run Spain  # preview without writing

# Step 2 (optional): augment with recent match lineup data from TheSportsDB
python3 scripts/fetch_lineups.py Sweden Tunisia
python3 scripts/fetch_lineups.py --all          # all teams in ratings.json
python3 scripts/fetch_lineups.py --demo Spain "Cape Verde"   # prediction demo

# Cached output: lineup_cache.json

What it does:

  • fetch_wc_squads.py — fetches all 48 official WC 2026 squad lists from Wikipedia (no API key, no rate limits). Populates lineup_cache.json with the full 26-man roster per team. Run this first so injury signals only fire for players genuinely excluded from the squad.
  • fetch_lineups.py — pulls last 5 matches per team from TheSportsDB and merges confirmed match starters into the existing squad cache (Wikipedia squad data is preserved, not replaced).
  • Builds a formation XI (default 4-3-3) from ratings.json player data by position group
  • Flags top-5 players not seen in the official squad as possible injury/exclusion
  • Warns when lineup data is stale (> 90 days since last match)
  • OVR is transparently adjusted in lineup_intel.py — no changes needed in trading logic

Injury flag example:

❓ V. Gyöker... (OVR 82) — not confirmed in last 1 lineup(s); possible injury/rest
⚠  Most recent data is 174d old (Tunisia 3-1 Uganda)

Recommended workflow: run fetch_wc_squads.py once before a tournament to seed accurate squad data, then fetch_lineups.py before individual matches for recent form signals. TheSportsDB free tier returns only ~3 starters per match — use it as a supplement to the Wikipedia squad, not a replacement.

Refresh from SoFIFA (Chrome must be closed due to profile lock):

# 1. Quit Chrome (Cmd+Q on macOS)
# 2. Scrape EA FC 26 ratings for all 48 WC 2026 teams
node scripts/scrape_sofifa.mjs

# 3. Merge scraped data into ratings.json
python3 scripts/merge_sofifa.py

# 4. Re-open Chrome

Add a missing team manually:

{
  "name": "Team Name",
  "ovr": 83,
  "att": 83, "mid": 82, "def": 81,
  "top11_avg_ovr": 84,
  "aliases": ["alternate name", "abbreviation", "country code"]
}

If a team can't be matched, the market is skipped with a warning. Add the team name (or an alias) to ratings.json to include it.

Safeguards

  • Time decay: Skip markets resolving in < 3 hours (spreads/winner) or < 1 hour (totals — goals can come late)
  • Slippage: Skip if estimated market slippage > 10%
  • Flip-flop: Skip if you've reversed direction on this market recently
  • Liquidity: Skip if market liquidity < min threshold
  • Both teams unknown: Skip if either team not found in ratings

Disable with --no-safeguards (not recommended).

Troubleshooting

"Team not found in ratings: [name]"

  • The market title's team name didn't fuzzy-match any entry in ratings.json
  • Add the team or alias: edit ratings.json and add the name to its aliases list

"No soccer markets found"

  • Run python soccer_trader.py --import-markets to discover and import Kalshi soccer markets
  • Check that Kalshi has active soccer markets (tournament may be between matches)

"KYC verification required"

"SOLANA_PRIVATE_KEY not set"

  • Only needed for --live. Dry-run works without it.

"Insufficient SOL for transaction fees"

  • Fund Solana wallet with at least 0.05 SOL

Kalshi maintenance window

  • Kalshi's clearinghouse has weekly maintenance Thursdays 3:00–5:00 AM ET — trades during this window will fail