Install
openclaw skills install kalshi-fifa-soccer-traderTrade 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.
openclaw skills install kalshi-fifa-soccer-traderTrade 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
--liveto enable real trades; setSIMMER_SOCCER_MAX_POSITION_USDandSIMMER_SOCCER_ENTRY_EDGEto adjust.
Powered by DFlow. Kalshi trades execute via DFlow's Solana-based prediction market infrastructure. KYC through Proof is required for buys.
Use when the user wants to:
EA FC OVR disparity → bivariate Poisson → probability grid → de-vig vs Kalshi prices → EV-positive trades.
Three market types handled:
Model in brief:
ratings.json (top ~200 clubs + all national teams)Install the Simmer SDK
pip install simmer-sdk
Set your Simmer API key
export SIMMER_API_KEY=... # simmer.markets/dashboard → SDK tab
Set your Solana private key (live trading only)
export SOLANA_PRIVATE_KEY=<base58-encoded-secret-key>
Complete KYC (required for buys on Kalshi)
curl "https://api.simmer.markets/api/proof/status?wallet=YOUR_SOLANA_ADDRESS"Fund wallet
Import soccer markets (first run)
python soccer_trader.py --import-markets
Dry run first — always
python soccer_trader.py
| Setting | Env var | Default | Description |
|---|---|---|---|
| Entry edge | SIMMER_SOCCER_ENTRY_EDGE | 0.06 | Min EV edge to trade (6%) |
| Exit threshold | SIMMER_SOCCER_EXIT_THRESHOLD | 0.90 | Sell when implied prob reaches this |
| Max position | SIMMER_SOCCER_MAX_POSITION_USD | 5.00 | Max USDC per trade |
| Max trades/run | SIMMER_SOCCER_MAX_TRADES_PER_RUN | 5 | Cap per scan cycle |
| Market types | SIMMER_SOCCER_MARKET_TYPES | winner,totals,spread | Which market types to trade (comma-sep) |
| Min liquidity | SIMMER_SOCCER_MIN_LIQUIDITY | 500 | Skip markets with liquidity below this USD |
| Slippage max | SIMMER_SOCCER_SLIPPAGE_MAX | 0.10 | Skip if estimated slippage > 10% |
| Smart sizing % | SIMMER_SOCCER_SIZING_PCT | 0.04 | % of balance per trade (with --smart-sizing) |
| Home advantage | SIMMER_SOCCER_HOME_BOOST | 0.12 | Log-scale home goal boost (0 = neutral venue) |
# Status + open positions
python scripts/status.py
# Positions only
python scripts/status.py --positions
# Full P&L
python scripts/status.py --pnl
# 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
Each cycle:
import_source=kalshi + soccer keywords)ratings.json (fuzzy match on team aliases)signal_data (OVRs, λs, model prob, edge, market type) for backtestingTeam OVR ratings live in ratings.json. The skill uses a three-layer hierarchy to compute each team's effective OVR:
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.jsontop11_avg_ovr — average OVR of the best 11 players (fallback if no player data)ovr — composite team OVR including bench (last resort)Fields per team:
| Field | Description |
|---|---|
ovr | Composite team OVR (bench included) |
att / mid / def | Positional ratings |
top11_avg_ovr | Average OVR of the best 11 players |
players | List of {name, ovr, position} — used by lineup intel |
aliases | Fuzzy-match list for Kalshi market title parsing |
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).ratings.json player data by position grouplineup_intel.py — no changes needed in trading logicInjury 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.pyonce before a tournament to seed accurate squad data, thenfetch_lineups.pybefore 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.
Disable with --no-safeguards (not recommended).
"Team not found in ratings: [name]"
ratings.jsonratings.json and add the name to its aliases list"No soccer markets found"
python soccer_trader.py --import-markets to discover and import Kalshi soccer markets"KYC verification required"
"SOLANA_PRIVATE_KEY not set"
--live. Dry-run works without it."Insufficient SOL for transaction fees"
Kalshi maintenance window