Skip to main content

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​

FilePurpose
services/backend/src/db/migrations/0031_backtest_optimisations.sqlNew backtest_optimisations table + 4 new columns on backtest_results
src/routes/optimisations/optimisations-types.tsTypeScript types: OptimisationRequestInput, WFOConfig, WFOWindow, WindowMetrics, StoredOptimisationRow, etc.
src/routes/optimisations/optimisations-schema.tsZod request validation schema
src/routes/optimisations/optimisations-repository.tsDB operations: insert, find, list, update progress/results, count completed backtests
src/routes/optimisations/optimisations-service.tsBusiness logic: WFO window generation, param combo generation, metric scoring, backtest spawning, IS→OOS aggregation
src/routes/optimisations/optimisations-handlers.tsHono route handlers for list, get, create
src/routes/optimisations/optimisations-router.tsRouter wiring with auth middleware

New API routes:

  • GET /api/optimisations — list all optimisations for user
  • GET /api/optimisations/:id — get single optimisation with live progress
  • POST /api/optimisations — create and start a new optimisation run
  • GET /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 optimisation
  • wfo_window_id TEXT — e.g. w0_is, w1_oos
  • wfo_phase TEXT — 'is' or 'oos'
  • param_combo_hash TEXT — deterministic hash of parameter combination

WFO Algorithm​

  1. Param grid generation — Cartesian product of {min, max, steps} ranges per param
  2. Window generation — Rolling or anchored IS/OOS windows over the date range
  3. IS spawning — One backtest per (combo × window) using BACKTEST_WORKFLOW
  4. Pull-based aggregation — When GET /api/optimisations/:id is 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
  5. Robustness score — Fraction of OOS windows that were profitable

Frontend​

FilePurpose
services/frontend/src/routes/bots/BotOptimiseTab.tsx3-step wizard to configure and launch an optimisation
services/frontend/src/routes/optimisations/OptimisationResultPage.tsxFull results page with polling, equity curve, heatmap, window table
services/frontend/src/lib/api-client/index.tsAdded OptimisationRun type, createOptimisation, getOptimisation, listOptimisations, listBotOptimisations
services/frontend/src/router.tsxAdded /optimisation/:id route
services/frontend/src/routes/bots/BotDetailPage.tsxAdded 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/steps inputs. 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​

  1. 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.

  2. Reuse BACKTEST_WORKFLOW: Each param combination/window spawns the existing backtest workflow — no new Workflow binding needed.

  3. Batch spawning in groups of 50 to avoid overwhelming the Cloudflare Workflow API rate limits.

  4. 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).