World Cup match-winner maker bot for the Algo Traders Club — a readable, forkable Kalshi bot with non-negotiable risk controls and DeepSeek-powered news context.
Fork of Kalshinator. DeepKick is a community fork of Kalshinator, the Algo Traders Club's canonical Kalshi reference bot. Same battle-tested
kalshi/,risk/,db/, andengine/layers — the fork point isstrategies/anddata/, retargeted for 2026 World Cup match-winner markets. If you are new to the club stack, start with Kalshinator to learn the base; use DeepKick when you want the World Cup strategy and DeepSeek defaults out of the box.
DeepKick is a World Cup–focused fork of the Kalshinator reference bot. It scans Kalshi Sports match-winner markets before kickoff, compares public team-strength (Elo-style) ratings to market prices, and posts resting maker limits when the modeled edge exceeds 5%. Optional DeepSeek via OpenRouter can nudge the binary Elo probability within a hard ±5 percentage-point cap — it never replaces the quantitative model.
DeepKick is not a live in-game latency bot (pro desks have feeds ~30 seconds faster than public APIs), an edge guarantee, or investment advice. It defaults to demo mode and DRY_RUN=true so you can learn the full lifecycle before risking real capital.
Tournament window: 2026 FIFA World Cup — June 11 through July 19 (US/Mexico/Canada, 48 teams, 104 matches).
Requirements: Python 3.12+, uv, Kalshi demo API credentials (Kalshi docs).
# 1. Install dependencies
uv sync --dev
# 2. Configure environment
cp .env.example .env
# Edit .env — at minimum set:
# KALSHI_API_KEY_ID
# KALSHI_PRIVATE_KEY_PATH (path to your demo .pem file)
# OpenRouter key is optional (pure-Elo fallback runs without it)
# 3. Sanity-check credentials
uv run scripts/check_balance.py
# 4. Run one dry-run cycle (writes to data/deepkick.db)
uv run scripts/run_once.py
# 5. Start the backend (terminal 1)
uv run uvicorn deepkick.main:app --reload
# 6. Start the dashboard (terminal 2)
uv run streamlit run src/deepkick/dashboard/app.py
# Optional: open the static research/results dashboard instead
uv run streamlit run src/deepkick/dashboard/results_app.py
# Optional: refresh Kalshi fixture snapshot for the results dashboard
KALSHI_ENVIRONMENT=prod uv run scripts/snapshot_fixtures.pySee docs/dashboard.md for what each dashboard shows and how to read the backtest results.
Verify the API:
curl http://127.0.0.1:8000/health
curl http://127.0.0.1:8000/statusThe research is explicit: reacting to live win-probability from public feeds is dead on arrival for a small bot. DeepKick targets soft pre-game lines on group-stage and knockout match-winner markets, modeled from public team-strength ratings. That is the teachable, defensible edge — not millisecond reaction.
Single uvicorn process runs FastAPI, the APScheduler polling loop, and the SQLite writer. Streamlit is a separate read-mostly client.
flowchart TB
subgraph Uvicorn["uvicorn process"]
FastAPI["FastAPI\nmain.py"]
Scheduler["APScheduler"]
Loop["engine/loop.py\npoll -> evaluate -> risk -> execute -> log"]
Kalshi["kalshi/client.py\nmarket data + orders"]
Ratings["data/ratings.py\nElo seed / future live feed"]
Strategy["strategies/worldcup.py\nElo + bounded DeepSeek context"]
Risk["risk/\nKelly sizing + circuit breakers"]
Repo["db/repository.py\nsingle SQLite writer"]
Scheduler --> Loop
Loop --> Kalshi
Loop --> Ratings
Loop --> Strategy
Strategy -->|TradeSignals| Loop
Loop -->|pre-trade checks| Risk
Risk -->|approved orders only| Kalshi
Loop --> Repo
FastAPI --> Repo
FastAPI --> Kalshi
end
DB[("data/deepkick.db")]
Dashboard["Streamlit dashboard\napi_client.py only"]
Repo --> DB
Dashboard -->|HTTP| FastAPI
Cycle pipeline: poll → evaluate → risk → execute → log/persist
Every cycle writes a Cycle row; every order attempt (including dry-run) writes an Order row; every balance check writes a BalanceSnapshot row. The dashboard reads history through FastAPI — it never opens the .db file directly.
| Directory | Fork? | Role |
|---|---|---|
strategies/, data/ |
Yes — fork points | World Cup logic and ratings feeds |
kalshi/, llm/, risk/, db/, engine/ |
No | Infrastructure — do not bypass risk |
worldcup (default) fetches Kalshi's KXWCGAME series directly via WORLDCUP_SERIES_TICKERS, rather than scanning every Sports series. Real match-winner tickers look like KXWCGAME-26JUN25TURUSA-USA (YES = USA beats Turkiye). The strategy intentionally skips companion TIE contracts and tournament-outright series such as KXMENWORLDCUP, because those require different models.
- Loads team Elo seeds from
data/worldcup_elo_seed.json(v1 static fallback). - Parses the
KXWCGAMEticker into team codes and skips non-team outcomes such asTIE. - Computes neutral-ground win probability from the Elo gap.
- Optionally asks OpenRouter (default model:
deepseek/deepseek-chat) for a compact injury/lineup context check, then clamps any probability move to ±5 percentage points — not a full probability forecast. - Emits a maker limit one tick inside the spread when edge > 5%.
flowchart LR
Market["Kalshi binary market\nYES/NO"] --> Parse["Parse YES team\nfrom ticker"]
Ratings["Elo ratings"] --> Baseline["Binary Elo baseline\np(YES)"]
Parse --> Baseline
Baseline --> Candidate["Best Elo candidate\nedge > MIN_EDGE"]
Candidate --> LLM{"OpenRouter key set?"}
LLM -->|No| Signal["TradeSignal\npure Elo"]
LLM -->|Yes| DeepSeek["DeepSeek context check\nstructured JSON"]
DeepSeek --> Haircut["Apply confidence haircut"]
Haircut --> Clamp["Clamp final p(YES)\nto baseline +/- cap"]
Clamp --> Reprice["Recompute YES/NO edge"]
Reprice --> Signal
Signal --> Risk["risk/\nlimits + sizing"]
Elo owns the probability. DeepSeek only receives the already-binary YES probability for the specific Kalshi contract being evaluated, then returns structured JSON through the same OpenRouter/Pydantic path used elsewhere. The final YES probability is clamped to baseline ± LLM_MAX_PROBABILITY_ADJUSTMENT (default ±5pp), and DeepSeek's self-reported confidence is haircut before it can reduce signal confidence. This is deliberate: LLMs are overconfident on prediction markets, so DeepKick treats narrative skill as context, not sizing authority. See docs/deepseek-sentiment-layer.md for the full design note.
Next obvious fork step: wire a live ratings feed in data/ratings.py (api-sports.io free tier, ClubElo export, etc.).
-
Fork or clone this repository.
-
Create
src/deepkick/strategies/your_strategy.pyimplementing theStrategyABC:from deepkick.strategies import register_strategy from deepkick.strategies.base import Strategy, StrategyResult @register_strategy("your_strategy") class YourStrategy(Strategy): name = "your_strategy" async def evaluate(self, markets): # Your logic here — return StrategyResult(signals=[...]) ...
-
Register the module in
strategies/__init__.py(import it so@register_strategyruns). -
Point
.envat your strategy:ACTIVE_STRATEGY=your_strategy -
Repoint the market filter:
MARKET_CATEGORY_FILTER=CRYPTO(or whatever category you trade) -
Run
uv run scripts/run_once.py— your signals still pass throughrisk/before any order is placed.
See strategies/safe_compounder.py and strategies/worldcup.py for reference patterns.
All settings flow through .env — see .env.example. Highlights:
| Variable | Default | Notes |
|---|---|---|
DRY_RUN |
true |
Hot-reloaded — no restart needed for /status badge |
KALSHI_ENVIRONMENT |
demo |
Hot-reloaded |
ACTIVE_STRATEGY |
worldcup |
or safe_compounder, llm_directional, or your fork |
MARKET_CATEGORY_FILTER |
Sports |
World Cup markets live here; Kalshi's category filter is case-sensitive |
WORLDCUP_SERIES_TICKERS |
KXWCGAME |
Comma-separated World Cup game series to fetch directly |
EXCLUDED_MARKET_CATEGORIES |
ENTERTAINMENT,MENTIONS |
Sports is not excluded — required for this fork |
OPENROUTER_MODEL |
deepseek/deepseek-chat |
Config string only — swap without code changes |
LLM_MAX_PROBABILITY_ADJUSTMENT |
0.05 |
Hard cap on how far DeepSeek can move the Elo baseline |
DATABASE_PATH |
./data/deepkick.db |
Auto-created on first run |
| Method | Path | Purpose |
|---|---|---|
| GET | /health |
Liveness |
| GET | /status |
Strategy, dry-run, circuit breakers |
| GET | /balance, /positions |
Kalshi pass-through |
| GET | /balance/history, /cycles, /orders |
SQLite history |
| POST | /cycle/run, /cycle/pause, /cycle/resume |
Manual control |
No authentication in v1 — put behind a reverse proxy before exposing publicly.
uv run pytest # all mocked HTTP — safe default
uv run pytest -m live # opt-in live API tests (demo credentials)
uv run scripts/backtest.py # pure-Elo historical seed backtest; no credentialsRead-only Kalshi diagnostics:
KALSHI_ENVIRONMENT=prod uv run scripts/list_series.py "world cup"
KALSHI_ENVIRONMENT=prod uv run scripts/inspect_markets.py KXWCGAME KXMENWORLDCUPThese are documented gaps, not oversights — reasonable territory for your fork:
- Live team-strength ratings feed (static JSON seed ships in v1)
- WebSocket order book streaming
- Multi-model LLM consensus / ensemble voting
- Cross-venue arbitrage (Polymarket, PMXT, etc.)
- Historical backtesting framework
- Additional World Cup series models (BTTS, group qualification, tournament outrights)
- Database migrations / Postgres swap
- Telegram / Discord control UI
- Live in-game reaction (deliberately out of scope)
DeepKick is an educational reference implementation maintained by the Algo Traders Club. It is not investment advice. Past performance of any bundled strategy is not a guarantee of future results. The bot defaults to demo mode and DRY_RUN=true for a reason — understand the code and test thoroughly before trading live capital.
MIT — see LICENSE. Copyright Algo Traders Club.
- Algo Traders Club
- Kalshi API docs
- Product requirements: docs/deepkick-prd.md
- Live testing notes: docs/live-testing.md
- Backtest seed provenance: data/backtest/README.md
