Parameter Optimisation (WFO Grid Search)
Date: 2025-06-05
Commit: 9647715
Overview
Implemented a full Walk-Forward Optimisation (WFO) parameter search system, allowing users to find the best strategy parameters by testing them against historical in-sample windows and validating on out-of-sample windows.
Architecture
Backend
| File | Purpose |
|---|---|
services/backend/src/db/migrations/0031_backtest_optimisations.sql | New backtest_optimisations table + 4 new columns on backtest_results |
src/routes/optimisations/optimisations-types.ts | TypeScript types: OptimisationRequestInput, WFOConfig, WFOWindow, WindowMetrics, StoredOptimisationRow, etc. |
src/routes/optimisations/optimisations-schema.ts | Zod request validation schema |
src/routes/optimisations/optimisations-repository.ts | DB operations: insert, find, list, update progress/results, count completed backtests |
src/routes/optimisations/optimisations-service.ts | Business logic: WFO window generation, param combo generation, metric scoring, backtest spawning, IS→OOS aggregation |
src/routes/optimisations/optimisations-handlers.ts | Hono route handlers for list, get, create |
src/routes/optimisations/optimisations-router.ts | Router wiring with auth middleware |
New API routes:
GET /api/optimisations— list all optimisations for userGET /api/optimisations/:id— get single optimisation with live progressPOST /api/optimisations— create and start a new optimisation runGET /api/bots/:id/optimisations— list optimisations for a specific bot
Database Changes
New table: backtest_optimisations
id, user_id, bot_id, symbol, exchange, strategy,
start_date, end_date, initial_balance, asset_class,
search_method, objective_metric, param_ranges (JSONB), wfo_config (JSONB),
status, total_combinations, completed_combinations,
best_params (JSONB), best_oos_metrics (JSONB), robustness_score,
oos_equity_curve (JSONB), all_results (JSONB), error_message
New columns on backtest_results:
optimisation_run_id UUID— links a backtest to its parent optimisationwfo_window_id TEXT— e.g.w0_is,w1_ooswfo_phase TEXT—'is'or'oos'param_combo_hash TEXT— deterministic hash of parameter combination
WFO Algorithm
- Param grid generation — Cartesian product of
{min, max, steps}ranges per param - Window generation — Rolling or anchored IS/OOS windows over the date range
- IS spawning — One backtest per (combo × window) using
BACKTEST_WORKFLOW - Pull-based aggregation — When
GET /api/optimisations/:idis called and all IS backtests are done, the service:- Finds the best-scoring param combo per window (by objective metric)
- Identifies globally best params (most frequently best across windows)
- Spawns OOS backtests for the winner of each window
- Robustness score — Fraction of OOS windows that were profitable
Frontend
| File | Purpose |
|---|---|
services/frontend/src/routes/bots/BotOptimiseTab.tsx | 3-step wizard to configure and launch an optimisation |
services/frontend/src/routes/optimisations/OptimisationResultPage.tsx | Full results page with polling, equity curve, heatmap, window table |
services/frontend/src/lib/api-client/index.ts | Added OptimisationRun type, createOptimisation, getOptimisation, listOptimisations, listBotOptimisations |
services/frontend/src/router.tsx | Added /optimisation/:id route |
services/frontend/src/routes/bots/BotDetailPage.tsx | Added optimise tab to tab bar |
3-Step Wizard (BotOptimiseTab)
- Step 1: Date range picker + parameter rows (auto-classified as tunable/structural based on type, with FIXED params hidden). Each enabled param gets
min/max/stepsinputs. Live combination counter. - Step 2: WFO mode (rolling/anchored), IS/OOS window day counts, quick presets, estimated windows and total backtests preview.
- Step 3: Objective metric selector (Calmar, Sharpe, Profit Factor, Expectancy), search method (grid/random), optional random sample count, summary table. Submit triggers
createOptimisation()→ navigates to/optimisation/:id.
Optimisation Result Page
- Live progress bar (5s polling)
- Stats: robustness %, total combinations, best OOS metric, best OOS profit
- Stitched OOS equity curve (lightweight-charts area series)
- Best parameters table with % change vs. current values
- WFO window results table (IS score vs OOS score per window)
- Interactive parameter heatmap (2D grid of any two params, colored by score)
- "Apply Best Params" CTA → navigate to bot strategy tab
- Linked backtests accordion
Key Decisions
-
Pull-based aggregation: OOS backtests are spawned lazily when the client polls
GET /api/optimisations/:id, rather than via a Workflow event. This avoids complexity of additional Workflow triggers and keeps the implementation in a single service. -
Reuse
BACKTEST_WORKFLOW: Each param combination/window spawns the existing backtest workflow — no new Workflow binding needed. -
Batch spawning in groups of 50 to avoid overwhelming the Cloudflare Workflow API rate limits.
-
Auto-classification of parameters:
ALWAYS_FIXED_PARAMS(credential/symbol/exchange etc.) are hidden; non-numeric params are disabled; numeric params default to enabled.
Migration Applied
Migration 0031_backtest_optimisations.sql was run manually against the Neon DB using individual sql.query() calls (neon serverless doesn't support multi-statement strings).