WeatherEdge AI is a portfolio-grade AI/ML backend for weather-related prediction markets. It discovers weather markets, parses market questions into structured weather targets, fetches weather forecasts, estimates market resolution probabilities, compares those probabilities with market prices, supports paper-trading research, and evaluates predictions through backtesting and calibration.
Paper trading is the required first execution mode and remains the default for local development, tests, demos, and strategy validation. Live trading may be added later in this backend only after safety controls exist, including explicit live-mode configuration, credential isolation, position limits, audit logging, kill-switch behavior, and tests that prove paper mode cannot place real orders.
The current settings make that boundary explicit with TRADING_MODE=paper and LIVE_TRADING_ENABLED=false by default. No live execution adapter or order-placement endpoint is implemented.
Probability modeling includes the default transparent baseline_precip_v1 and an optional fixed-coefficient logistic_precip_v1 model. The logistic model is selectable for research comparisons but is not presented as trained or performance-proven.
The project is designed to demonstrate clean backend architecture, testable data pipelines, probability modeling, expected value analysis, and responsible AI/ML engineering.
The portfolio story is: build a reproducible backend that can discover, score, backtest, and paper trade weather markets first; then extend the same architecture toward live trading with clear safety gates and operational controls.
WeatherEdge AI is organized around a persisted research workflow:
Market
-> ParsedMarket
-> WeatherForecastSnapshot
-> Prediction
-> EVRecommendation
-> PaperTrade
Market
-> MarketPriceSnapshot
-> ResolvedOutcome
-> Backtest replay metrics
The API modules expose the workflow, while small adapter modules isolate public market data, weather forecasts, geocoding, probability modeling, strategy logic, and observed-outcome resolution. Raw provider payloads are preserved alongside normalized records so predictions and backtests can be traced back to their source inputs.
- Python 3.12+
- FastAPI
- Pydantic and pydantic-settings
- SQLAlchemy ORM
- PostgreSQL
- psycopg2-binary
- httpx
- pandas
- pytest
- Docker Compose
From Windows PowerShell:
cd C:\weatherEdgeAI\backend
python -m venv .venv
.\.venv\Scripts\activate
python -m pip install --upgrade pip
pip install -r requirements.txtCopy the environment template before running the app:
cd C:\weatherEdgeAI
Copy-Item .env.example .envStart the local PostgreSQL container:
cd C:\weatherEdgeAI
docker compose up -dCheck container status:
cd C:\weatherEdgeAI
docker compose psView database logs:
cd C:\weatherEdgeAI
docker compose logs postgresStop the database while keeping the persisted Docker volume:
cd C:\weatherEdgeAI
docker compose stopRemove the container and network while keeping the named data volume:
cd C:\weatherEdgeAI
docker compose downThe Compose service runs PostgreSQL 16 in a container named weatheredge-postgres and exposes it at localhost:5432. The default local connection string is:
postgresql+psycopg2://weatheredge:weatheredge@localhost:5432/weatheredge
These commands are for local development only. They operate on the Docker
PostgreSQL container named weatheredge-postgres.
Open a SQL shell:
cd C:\weatherEdgeAI
docker exec -it weatheredge-postgres psql -U weatheredge -d weatheredgeShow paper-trading research table counts:
cd C:\weatherEdgeAI
docker exec -it weatheredge-postgres psql -U weatheredge -d weatheredge -c "SELECT 'paper_trades' AS table_name, COUNT(*) FROM paper_trades UNION ALL SELECT 'paper_runner_runs', COUNT(*) FROM paper_runner_runs UNION ALL SELECT 'resolved_outcomes', COUNT(*) FROM resolved_outcomes;"Clear simulated paper trades and reset their local IDs:
cd C:\weatherEdgeAI
docker exec -it weatheredge-postgres psql -U weatheredge -d weatheredge -c "TRUNCATE TABLE paper_trades RESTART IDENTITY;"Clear simulated paper trades and public paper-run history:
cd C:\weatherEdgeAI
docker exec -it weatheredge-postgres psql -U weatheredge -d weatheredge -c "TRUNCATE TABLE paper_trades, paper_runner_runs RESTART IDENTITY;"Clear local evaluation records and simulated paper-trading history while keeping markets, parsed targets, forecasts, predictions, and EV recommendations:
cd C:\weatherEdgeAI
docker exec -it weatheredge-postgres psql -U weatheredge -d weatheredge -c "TRUNCATE TABLE paper_trades, paper_runner_runs, resolved_outcomes RESTART IDENTITY;"Fully reset the local database volume. This deletes all local PostgreSQL data, including discovered markets, snapshots, predictions, recommendations, paper trades, outcomes, and runner history:
cd C:\weatherEdgeAI
docker compose down -v
docker compose up -d
cd C:\weatherEdgeAI\backend
.\.venv\Scripts\alembic.exe upgrade headCreate or update the local PostgreSQL schema with Alembic before starting the API:
cd C:\weatherEdgeAI\backend
alembic upgrade headAlembic reads DATABASE_URL through the same application settings used by FastAPI. To create a new schema revision after ORM changes:
cd C:\weatherEdgeAI\backend
alembic revision --autogenerate -m "describe schema change"cd C:\weatherEdgeAI\backend
uvicorn app.main:app --reloadHealth check:
GET http://127.0.0.1:8000/health
Expected response:
{
"status": "ok",
"service": "WeatherEdge AI"
}The first frontend pass is a React dashboard backed by GET /dashboard/summary.
It uses the Vite dev-server proxy at /api, so the FastAPI backend should be running on
http://127.0.0.1:8000.
cd C:\weatherEdgeAI\frontend
npm install
npm run devOpen:
http://127.0.0.1:5173
Build the frontend:
cd C:\weatherEdgeAI\frontend
npm run buildcd C:\weatherEdgeAI\backend
pytestFor the fastest no-service deterministic demo:
cd C:\weatherEdgeAI\backend
.\.venv\Scripts\python.exe scripts\quick_demo.pyThis runs the paper workflow, seed backtest, and dashboard summary in-process against a temporary in-memory SQLite database. It does not require Docker, PostgreSQL, frontend setup, or network access.
After PostgreSQL is running and migrations are applied:
cd C:\weatherEdgeAI\backend
.\.venv\Scripts\python.exe scripts\demo_workflow.pyFor a quick smoke run without PostgreSQL:
cd C:\weatherEdgeAI\backend
.\.venv\Scripts\python.exe scripts\demo_workflow.py --sqlite-memoryExpected smoke-demo shape:
discovery: discovered=2, created=2, updated=0, price_snapshots_created=2
parsed: location_name='New York City', operator='>', threshold_value=1.0, threshold_unit='inch'
forecast: forecast_source='demo_fixture', forecast_precip_total=1.6, forecast_precip_unit='inch'
prediction: model_version='baseline_precip_v1', p_yes=0.75, p_no=0.25
strategy: recommendation='PAPER_BUY_YES', market_price_yes=0.44, edge_yes=0.31
paper_trade: side='YES', entry_price=0.44, quantity=10.0, status='OPEN'
market_detail: latest_price_snapshot_id=1, latest_forecast_snapshot_id=1, latest_prediction_id=1, latest_ev_recommendation_id=1, latest_paper_trade_id=1, next_action='monitor_paper_trade'
Prediction runs default to the transparent baseline model:
baseline_precip_v1
The optional comparison model is:
logistic_precip_v1
The logistic model uses fixed, hand-selected coefficients over forecast-vs-threshold features. It is useful for research comparison, but it is not trained or performance-proven yet.
After a market has been discovered, parsed, and given a forecast snapshot, run the default model through the API:
Invoke-RestMethod `
-Method Post `
-Uri http://127.0.0.1:8000/predictions/run/1Run the same prediction step with the logistic model:
Invoke-RestMethod `
-Method Post `
-Uri "http://127.0.0.1:8000/predictions/run/1?model_version=logistic_precip_v1"Then evaluate strategy as usual. The latest prediction for that market is used:
Invoke-RestMethod `
-Method Post `
-Uri http://127.0.0.1:8000/strategy/evaluate/1For public paper-runner research, keep the default baseline:
cd C:\weatherEdgeAI\backend
.\.venv\Scripts\python.exe scripts\paper_market_runner.py --rehearsal --model-version baseline_precip_v1Run the same guarded no-trade pass with the logistic model:
cd C:\weatherEdgeAI\backend
.\.venv\Scripts\python.exe scripts\paper_market_runner.py --rehearsal --model-version logistic_precip_v1Backtests and evidence reports filter by stored model_version, so compare model
versions by running each model, resolving outcomes when target windows complete,
and then using the same evaluation window with each version:
Invoke-RestMethod `
-Method Post `
-Uri http://127.0.0.1:8000/backtests/run `
-ContentType "application/json" `
-Body '{"start_date":"2026-05-01","end_date":"2026-05-10","model_version":"logistic_precip_v1"}'After PostgreSQL is running and migrations are applied, run a guarded one-shot public-market paper pass:
cd C:\weatherEdgeAI\backend
.\.venv\Scripts\python.exe scripts\paper_market_runner.py --max-trades 3 --quantity 1 --min-liquidity 100 --max-spread 0.15For a no-trade rehearsal that still discovers markets and evaluates signals:
cd C:\weatherEdgeAI\backend
.\.venv\Scripts\python.exe scripts\paper_market_runner.py --rehearsalInterval precipitation contracts such as between 190-200mm are off by default
for the public paper runner. To include them in a manual research pass:
cd C:\weatherEdgeAI\backend
.\.venv\Scripts\python.exe scripts\paper_market_runner.py --dry-run --allow-interval-contractsFor a bounded overnight paper run:
cd C:\weatherEdgeAI\backend
.\.venv\Scripts\python.exe scripts\paper_market_runner.py --interval-minutes 30 --max-hours 10 --max-trades 3 --quantity 1 --min-liquidity 100 --max-spread 0.15The runner now requires fresh prices by default. To explicitly replay stored binary
prices after a public refresh failure for research purposes, add
--allow-stale-price-fallback.
Before a longer paper run, run the readiness smoke checks:
cd C:\weatherEdgeAI\backend
.\.venv\Scripts\python.exe scripts\pre_run_smoke.pyAfter a bounded run, resolve eligible completed markets, settle matching open paper trades, generate an evidence report, and write timestamped JSON research logs:
cd C:\weatherEdgeAI\backend
.\.venv\Scripts\python.exe scripts\research_maintenance.py --start-date 2026-05-01 --end-date 2026-05-10 --limit 100The runner is paper-only. It uses public market data, Open-Meteo forecasts, stored model/EV logic, and simulated paper trades. It does not call authenticated trading APIs, place real orders, sign transactions, or create live execution records.
Each runner pass now persists a paper_runner_runs record with the run configuration, counts, skip reasons, errors, and final status so automated paper-market validation remains inspectable after the process exits.
GET /paper-runner/diagnostics summarizes recent public paper-run validation across runs, including skip-reason categories, source price status counts, unsupported public price reasons, and recent workflow/provider errors.
POST /backtests/resolved-outcomes/resolve-weather-batch resolves eligible completed parsed precipitation markets from observed weather data and can settle matching open paper trades.
GET /backtests/resolved-outcomes/eligibility-preview previews which parsed markets are ready for observed-outcome resolution without calling providers or writing records.
GET /evaluation/evidence-report combines paper-runner counts, backtest metrics, baseline comparisons, sample-size gates, unresolved trade counts, and interpretation limits for multi-day paper-run review.
The evidence report includes paper-trade lifecycle counts for recommended buy signals, recommended-but-not-traded signals, open trades, resolved trades, manually closed trades, unresolved trades, and unresolved trades past the target weather window.
It also reports market-implied comparison coverage so a reviewer can see how much of the evaluated sample had linked market YES prices.
The paper runner also applies configurable freshness guards for price snapshots, forecast snapshots, and started or elapsed target windows so stale or partial-window inputs are skipped before simulated trades are created. Public refresh failures do not fall back to stored prices unless --allow-stale-price-fallback is set explicitly.
Conservative paper portfolio limits are enabled by default for local research: 5 open simulated trades, 25 total simulated exposure, 5 per market, and 10 per parsed location. These are intentionally small paper-mode limits, not live-trading risk controls.
Every created paper trade stores a compact signal_snapshot_json so the entry can be explained later from the parsed target, forecast, model probability, market price, edge, liquidity, spread, recommendation reason, and runner config.
Optional paper entry slippage is available through PAPER_RUNNER_ENTRY_SLIPPAGE_RATE and defaults to 0.0; when enabled, paper trades preserve the quoted price in the signal snapshot and use the slipped fill as entry_price.
Run the deterministic seed-fixture backtest through the API after the server is running:
Invoke-RestMethod `
-Method Post `
-Uri http://127.0.0.1:8000/backtests/run `
-ContentType "application/json" `
-Body '{"start_date":"2026-05-01","end_date":"2026-05-10","model_version":"baseline_precip_v1","seed_fixtures":true}'AGENTS.md: operating instructions for Codex and future contributors.docs/ROADMAP.md: current project plan and priorities.docs/LOCAL_DEMO.md: copy-paste local demo workflow.docs/DATA_SOURCES.md: market, forecast, geocoding, and observed-outcome provider boundaries.docs/TRADING_MODES.md: paper, live, and read-only mode rules.docs/LIVE_TRADING_SAFETY.md: controls required before live execution.docs/BACKTESTING_SPEC.md: replay, metrics, and evidence requirements.docs/MODEL_TRAINING_WORKFLOW.md: baseline-read-to-trained-logistic workflow.docs/PAPER_RUN_EVALUATION.md: multi-day paper-run setup, outcome resolution, and interpretation runbook.
V1 starts with precipitation threshold markets, such as:
- Will New York City get more than 1 inch of rain tomorrow?
- Will Chicago receive at least 0.5 inches of rain tomorrow?
The current backend includes SQLAlchemy persistence models, Pydantic response schemas, mock and public-style market discovery, a precipitation market parser, forecast snapshots, a baseline precipitation probability model, EV strategy evaluation, simulated paper trades, resolved outcomes, and backtest replay metrics.
Market discovery now uses the public Polymarket-style Gamma API by default through POST /markets/discover. Public discovery searches Gamma public-search by keyword first, with default keywords biased toward V1 precipitation and weather-measurement terms instead of the broad weather query that can over-collect space-weather event-count markets. It deduplicates event results, expands child markets, skips inactive or closed child markets, and falls back to the active events listing when search returns no candidates. For local demos or tests without network access, pass "source": "mock" in the request body.
Market detail reads include a computed workflow_status object that shows completed pipeline steps and the next recommended backend action, such as create_forecast, run_prediction, or evaluate_strategy. The same detail response exposes the latest parsed market, price snapshot, forecast snapshot, prediction, EV recommendation, and paper trade when present so the paper-trading chain can be inspected from one endpoint.
GET /dashboard/summary provides a frontend-ready summary with recent market workflow rows, compact latest signal values, paper-buy opportunities, open paper trades, recent runner history, and a compact backtest/calibration summary. It does not refresh external data or create execution records.
The frontend/ app is a Vite + React paper-trading workspace. It shows the market pipeline, latest parsed target, forecast, model, price, EV, paper-trade status, backtest metrics, calibration buckets, paper opportunities, open and historical paper trades, resolved outcome logs, and recent public paper-runner history. Its run console can trigger the deterministic POST /demo/paper-workflow flow or a guarded public POST /paper-runner/run-once pass with dry-run, trade cap, quantity, liquidity, and spread controls. Public runs remain paper-only, and the UI does not expose live-trading controls.
Discovered markets and price refresh attempts store source_diagnostics so public data integration gaps are visible from API reads. Polymarket-sourced price refreshes use fresh Gamma market payloads, optionally enrich them with public CLOB market information, and can combine fresh token price maps with stored Gamma market context when the price response omits outcome/token metadata. Manual or fixture-backed markets can still refresh from stored payloads. Diagnostics identify supported or missing metadata, binary prices, top-of-book data, CLOB-style liquidity/volume fields, status, and resolution fields without enabling authenticated trading.
Market parsing does not create demo price snapshots. Strategy evaluation depends on price records from market discovery or the explicit price refresh endpoint so recommendations can be traced back to source payloads.
Forecast snapshots now use Open-Meteo through POST /weather/forecast/{parsed_market_id}. Parsed markets need latitude and longitude; the V1 parse route resolves New York City, NYC, New York, Chicago, London, and Hong Kong through a deterministic fixture geocoder by default. A broader Open-Meteo geocoding provider is implemented behind the same adapter and can be enabled for manual runs with GEOCODING_PROVIDER=open_meteo.
Interval/range precipitation contracts remain opt-in for paper-runner processing through PAPER_RUNNER_ALLOW_INTERVAL_CONTRACTS=true, --allow-interval-contracts, or the allow_interval_contracts API field.
Backtesting now supports persisted predictions evaluated against one selected resolved outcome per market, plus a deterministic seed-fixture replay. Reports include prediction count, resolved outcome count, win rate, Brier score, log loss, calibration buckets, sample-size notes, sample-size gates, baseline comparisons, coverage diagnostics, EV recommendation count, paper-trade count, gross paper PnL, fee and slippage costs, net settlement PnL, paper ROI, and max drawdown.
POST /backtests/walk-forward runs the same persisted-record replay across rolling date windows and returns per-window backtest responses plus aggregate weighted Brier score, log loss, win rate, paper PnL totals, and sparse/overlapping-window interpretation limits.
Resolved outcomes now settle open simulated paper trades for the same market by default using binary side payouts. This remains paper-only and does not create live execution records.
Observed weather outcomes can be resolved from Open-Meteo archive data for parsed precipitation markets. The resolver can also use a credential-gated NOAA/NCEI CDO daily PRCP client when NOAA_CDO_TOKEN is configured. NOAA is optional, mocked in tests, and not required for local demos.
Manual NOAA outcome resolution uses the same resolved-outcome endpoint:
{
"market_id": 1,
"resolution_provider": "noaa_cdo_daily"
}See docs/API_WORKFLOWS.md and docs/DATA_SOURCES.md for setup, failure modes, and current NOAA station-selection limitations.
GET /healthGET /dashboard/summaryPOST /demo/paper-workflowGET /marketsPOST /marketsGET /markets/{market_id}POST /markets/discoverPOST /markets/{market_id}/price-snapshots/refreshPOST /markets/{market_id}/parsePOST /weather/forecast/{parsed_market_id}GET /weather/forecast/{parsed_market_id}/latestPOST /predictions/run/{market_id}GET /predictions/{market_id}GET /predictions/{market_id}/latestPOST /strategy/evaluate/{market_id}GET /strategy/opportunitiesPOST /paper-tradesGET /paper-tradesPOST /paper-trades/{paper_trade_id}/closePOST /paper-runner/run-onceGET /paper-runner/runsGET /paper-runner/runs/{run_id}GET /paper-runner/diagnosticsPOST /backtests/runPOST /backtests/walk-forwardPOST /backtests/resolved-outcomesPOST /backtests/resolved-outcomes/resolve-weatherGET /backtests/resolved-outcomes/eligibility-previewPOST /backtests/resolved-outcomes/resolve-weather-batchGET /backtests/resolved-outcomesGET /evaluation/evidence-report