A Polymarket weather prediction trading bot — fetches real-world forecasts, finds mispriced temperature markets, and trades the edge using the Kelly Criterion.
Starts in paper trading mode by default. No real money moves until you explicitly turn it on.
- Every 30 minutes Oculie fetches weather forecasts from NOAA NWS, Open-Meteo, and Visual Crossing, builds a consensus probability distribution over tomorrow's temperature
- It scans Polymarket's Gamma API for active temperature markets (e.g. "Will the high in Seoul be 18°C or higher on April 22?")
- For each market it computes edge = forecast probability − market-implied price
- If edge ≥ threshold (default 3%), it sizes a position using the Kelly Criterion (capped at 25% of full Kelly) and executes the trade
- Open positions are polled every 5 minutes; a 30% adverse-move triggers an early exit
- Results are logged to SQLite, visible in a React dashboard, and optionally sent to Telegram/WhatsApp
Scheduler (APScheduler, every 30 min)
│
├── MarketScanner ← Polymarket Gamma API — active temperature markets
│
├── Consensus Engine
│ ├── NOAA NWS (US cities — free, no auth)
│ ├── Open-Meteo (international — free, 31-member ensemble)
│ ├── Visual Crossing (optional 4th source, API key required)
│ └── wttr.in (fallback)
│
├── TradeEngine ← edge calc → Kelly sizing → entry guard checks
├── CLOBClient ← py-clob-client → live Polymarket order placement
├── Resolver ← polls open positions; 30% stop-loss early exit
└── Reporter ← Telegram + WhatsApp alerts; Telegram command polling
Dashboard
├── FastAPI backend (HTTP Basic Auth on write endpoints)
└── React 18 + Vite + Tailwind SPA
See docs/ARCHITECTURE.md for a full breakdown of each component.
| City | Country | Forecast source | Primary model |
|---|---|---|---|
| New York City | US | NOAA NWS | GFS Seamless + HRRR |
| Chicago | US | NOAA NWS | GFS Seamless + HRRR |
| Miami | US | NOAA NWS | GFS Seamless + HRRR |
| Dallas | US | NOAA NWS | GFS Seamless + HRRR |
| Seattle | US | NOAA NWS | GFS Seamless + HRRR |
| Atlanta | US | NOAA NWS | GFS Seamless + HRRR |
| London | UK | Open-Meteo | UKMO Seamless |
| Seoul | South Korea | Open-Meteo | KMA Seamless |
| Shanghai | China | Open-Meteo | CMA GRAPES Global |
| Hong Kong | China | Open-Meteo | CMA GRAPES Global |
| Tokyo | Japan | Open-Meteo | JMA Seamless |
| Source | Coverage | Auth required | Notes |
|---|---|---|---|
| NOAA NWS | US cities | None | Free government API; GFS + HRRR ensemble |
| Open-Meteo | International | None | Free; 31-member GFS ensemble for probability |
| Visual Crossing | Global | API key (optional) | 4th consensus source; improves accuracy |
| wttr.in | Fallback | None | Public API; used when other sources fail |
Baseline (before any fixes): 64 trades, 0% win rate, -$937.55 P&L
The original system entered same-day markets with zero forecast advantage, sized NO-side positions without Bayesian dampening, and used raw (overconfident) Kelly probabilities.
| Phase | Key changes | Trades | Win rate | P&L |
|---|---|---|---|---|
| Baseline | — | 64 | 0% | -$937.55 |
| Phase A | Same-day filter · NO dampening · Kelly prob fix · price floor | 31 | 96.8% | +$1,450.00 |
| Phase B | Fractional Kelly (0.25×) · 4h forecast drift guard · $100 liquidity floor | 30 | 100% | +$1,500.00 |
Validated on NOAA historical data (April 2–6, 2026) with 0.50 baseline simulated Polymarket prices. Live performance will vary.
Full validation methodology: docs/VALIDATION_PHASE_A.md · docs/VALIDATION_PHASE_B.md
- Python 3.11+ with
uv:curl -LsSf https://astral.sh/uv/install.sh | sh - Node.js 18+
git clone https://github.com/your-username/oculie.git
cd oculie
cp .env.example .env # fill in API keys (optional for paper mode)
make install # installs Python deps, Node deps, builds frontend, initialises DBmake dev # starts bot + API (:8000) + dashboard (:5173) with hot-reloadOr use the standalone launcher:
bash start.sh # starts everything, opens browser, streams logsThe dashboard opens at http://localhost:5173. Press Ctrl+C to stop.
Paper mode is on by default — no real trades until you disable it in Settings.
Copy .env.example → .env and fill in what you need:
| Variable | Required | Description |
|---|---|---|
POLYMARKET_PRIVATE_KEY |
Live trading only | Ethereum private key (MetaMask/EOA) |
POLYMARKET_FUNDER |
Live trading only | Wallet address holding USDC |
POLYMARKET_API_KEY / SECRET / PASSPHRASE |
Live trading only | Derived from private key if blank |
POLYMARKET_SIGNATURE_TYPE |
Optional | 0=EOA (default), 1=Magic wallet, 2=Browser proxy |
TELEGRAM_BOT_TOKEN + TELEGRAM_CHAT_ID |
Optional | Trade alerts and bot commands |
WHATSAPP_PHONE_ID / ACCESS_TOKEN / RECIPIENT |
Optional | WhatsApp alerts |
DASHBOARD_USERNAME + DASHBOARD_PASSWORD |
Recommended | HTTP Basic Auth on write endpoints |
DASHBOARD_HOST + DASHBOARD_PORT |
Optional | Defaults to 127.0.0.1:8000 |
PAPER_MODE |
Default true |
Set false only after reviewing paper results |
VISUALCROSSING_API_KEY |
Optional | Enables Visual Crossing as 4th forecast source |
NOAA_CDO_TOKEN |
Backtesting only | Required for scripts/backtest_v2.py |
Weather data from NOAA NWS and Open-Meteo is free and requires no API key.
| Screen | What it shows |
|---|---|
| Overview | Bot status, wallet balance, P&L, open positions, countdown to next scan |
| Markets | Each city: forecast probability vs market price vs edge |
| Trade History | Full trade log with filters, win/loss stats, cumulative P&L chart |
| Settings | Sliders for threshold, bet limits, scan interval; paper mode toggle |
| Alerts | Notification log; test alert button |
US cities — NOAA point forecasts are fit to a normal distribution with horizon-scaled uncertainty (σ = 3°F for day 0–1, σ = 5°F for day 2+). Probabilities are computed by integrating the normal CDF across 5°F buckets matching each market's temperature range.
International cities — Open-Meteo's ensemble API returns 31 independent model runs. The fraction of members in each 1°C bucket gives a data-driven empirical distribution. Falls back to a point-mass at the deterministic p50 if ensemble data is unavailable.
All adjustable via the Settings screen:
| Limit | Default |
|---|---|
| Entry threshold (min edge) | 3% |
| Max bet per trade | $50 |
| Max total open exposure | $200 |
| Account floor (auto-pause) | $100 |
| Kelly multiplier | 0.25× (fractional Kelly) |
| Scan interval | 30 min |
make install # first-time setup
make dev # bot + backend + frontend (with hot-reload)
make test # run pytest suite
make logs # tail -f data/bot.log
make simulate # synthetic paper trading simulation
# Backtest on real NOAA historical data
uv run python scripts/backtest_v2.py --start 2024-01-01 --end 2024-12-31
uv run python scripts/backtest_v2.py --city nyc --start 2024-06-01 --end 2024-08-31
# Lint / format
uv run ruff check .
uv run ruff format .uv sync --extra telegramOnce TELEGRAM_BOT_TOKEN and TELEGRAM_CHAT_ID are set, message the bot directly:
| Command | Effect |
|---|---|
force-cycle |
Run a full scan immediately |
pause |
Skip upcoming scan cycles |
resume |
Resume after a pause |
paper-on / paper-off |
Toggle paper mode |
set-threshold <value> |
e.g. set-threshold 0.05 |
set-max-bet <value> |
e.g. set-max-bet 25 |
kill-switch |
Halt all trading |
Runs the bot and API as persistent background services with auto-restart:
make start # install and load launchd services
make stop # unload services
make status # show PID, exit code, last 20 log linesSee deploy/README.md for details.
cp .env.example .env && nano .env
docker compose -f deploy/docker-compose.yml up -d
docker compose -f deploy/docker-compose.yml logs -fnginx proxies /api/* to the FastAPI backend and serves the React SPA as static files.
- Run in paper mode for at least 48 hours and review Trade History
- Add Polymarket API credentials to
.env - Settings → toggle Paper Trading OFF → confirm (blocked until credentials are set)
- Monitor the first 24 hours via dashboard and Telegram alerts
- Never commit
.env— it's gitignored and contains your private keys - Use
.env.exampleas your template; it has no real values - The repo includes a pre-commit hook that blocks accidental
.envcommits - Paper mode is the default — live trading requires an explicit opt-in
MIT — see LICENSE