Optimisation System — Dependency Map
This document maps every dependency in the parameter optimisation pipeline: what triggers it, how the grid is built, how the best config is selected, and how (and how far) it propagates back to live trading. Read alongside Trading System — Dependency Map and Backtest System — Dependency Map.
1. System Overview
Admin UI ──► POST /api/ohlc/optimise (mi-casa backend, admin-only)
│
▼
optimisationController.triggerOptimisation
│ (proxies as SSE to ohlc-service)
▼
POST /optimise (ohlc-service)
│
optimisationService.runOptimisation
│
┌───────┴────────────────────┐
│ │
saved_patterns mode walk_forward mode
backtestService walkForwardScanner → walkForwardBacktestService
(50 × runBacktest) (50 × runBacktest on scanned patterns)
│
▼
score + select best run
│
▼
persist to optimisation_runs (ohlc-service DB)
│
▼
SSE result stream → Admin UI
─── Later ───────────────────────────────
Admin UI ──► POST /api/ohlc/optimise/apply (mi-casa backend)
│
optimisationController.applyBestConfig
│ (fetches best run from ohlc-service)
▼
patternTradingConfigService.upsertSystemConfig
│
pattern_trading_configs (mi-casa DB)
2. Prerequisites for Optimisation to Run
| # | Condition | Source |
|---|---|---|
| 1 | OHLC_SERVICE_URL and OHLC_SERVICE_API_KEY set | optimisationController.ts |
| 2 | Caller authenticated with admin role | optimisationRoutes.ts |
| 3 | Valid symbol, timeframe in request | postSchema (Zod) |
| 4 | ohlc_patterns data exists (saved_patterns mode) | ohlcPatternRepository |
| 5 | ohlc_candles data exists for the full date range (walk_forward mode) | ohlcCandleRepository.getFrom |
| 6 | optimisation_runs table exists in ohlc-service DB | migration |
3. The Parameter Grid
buildGrid(optimiseEntryStrategy?) produces 50 configs (baseline + 49 LHS-style combinations):
| Parameter | Values tested |
|---|---|
slMultiplier | 0.8, 1.0, 1.2, 1.5 |
tp1Multiplier | 0.8, 1.0, 1.2 |
tp2Multiplier | 0.8, 1.0, 1.2 |
tp3Multiplier | 0.8, 1.0, 1.2 |
tpTargetMode | tp1, tp1_tp2, tp1_tp2_tp3 |
partialExitEnabled | false, true (with tp1_tp2_tp3 only) |
partialExitTp1Percent | 33%, 50% (when partial exit enabled) |
entryLagCandles | 0, 1, 2 — forced to 0 when entryStrategy is swing_confirmation, prz_touch, or candle_close |
minSignalScore | 0, 0.3, 0.5 |
entryStrategy | swing_confirmation, prz_touch, candle_close (only when optimiseEntryStrategy=true) |
Grid index 0 is always the baseline: { slMult=1, tp1-3Mult=1, tpTargetMode=tp1_tp2_tp3, partialExitEnabled=false, entryLag=0, minScore=0 }.
ABCD optimisation runs use the same emerging ABC / first-PRZ-touch path as the backtest runner; they do not wait for a confirmed D swing.
4. Scoring and Best-Run Selection
After all 50 backtests complete:
Step 1 — R:R filter
Runs that pass avgWinPips / avgLossPips >= 1.0 (or no losses and at least one win) and clear a sample-size floor are marked as passing. The sample-size floor is dynamic: max(5, ceil(maxCompletedTrades × 0.35)).
Step 2 — Composite score
compositeScore =
(totalPnlPips / maxPnlPips) × 0.35 + // absolute pips, relative to best pnl in the run
min(pipsPerTrade / maxPipsPerTrade, 1.0) × 0.2 +
min(completedTrades / maxCompletedTrades, 1.0) × 0.15 +
min(RR/2, 1.0) × 0.15 + // risk-reward ratio (capped at 2:1 → score 1.0)
min(PF/3, 1.0) × 0.1 + // profit factor (capped at 3.0 → score 1.0)
winRate × 0.05 // still matters, but no longer dominates
Step 3 — Best selection
- If any
passingruns: pick highestcompositeScorefrompassing. - If no
passingruns (best-effort): pick highestcompositeScoreacross all runs. isBestEffort: truein the result signals this fallback was used.
Step 4 — Persistence
All 50 runs are inserted into optimisation_runs with isBest=true for the winner.
5. Result Shape (OptimisationResult)
{
runGroupId: string // UUID grouping all 50 runs
bestConfig: TradingConfig
bestRun: OptimisationRun
allRuns: OptimisationRun[]
isBestEffort: boolean // true = no run passed 1:1 R:R; best-effort fallback used
}OptimisationRun mirrors BacktestResult statistics plus tradingConfig (JSONB), compositeScore, isBest, runGroupId.
6. Retrieving Results
GET /api/ohlc/optimise/results?symbol=&timeframe=&patternType=&limit=
Returns an array of run groups, each with:
runGroupId,createdAt,totalRuns,bestRun
Query strategy in optimisationRunRepository.getLatestGroups:
- Group by
run_group_id, get newest groups first (by earliestcreated_atin group). - For each group: fetch
isBest=truerun first; fall back to highestcompositeScore.
7. Applying the Best Config (POST /api/ohlc/optimise/apply)
applyBestConfig(runGroupId)
→ fetch bestRun from ohlc-service /optimise/results?runGroupId=...
→ ohlcConfigToInput(bestRun.tradingConfig) → Partial<NewPatternTradingConfig>
→ patternTradingConfigService.upsertSystemConfig(bestRun.patternType, input)
→ stored in pattern_trading_configs (mi-casa DB)
What gets applied vs. what is lost
| Config field | Applied to patternTradingConfigs? | Read by auto-trading engine? |
|---|---|---|
slMultiplier | ✅ | ✅ (via getMergedConfig) |
tp1-3Multiplier | ✅ | ✅ |
minSignalScore | ✅ | ✅ |
entryLagCandles | ✅ | ✅ |
entryStrategy | ✅ | ✅ |
tpTargetMode | ❌ (dropped — see Bug A below) | ⚠️ read from account, not config |
partialExitEnabled | ❌ (dropped) | ⚠️ read from account, not config |
partialExitTp1Percent | ❌ (dropped) | ⚠️ read from account, not config |
⚠️ Bug A:
OhlcTradingConfiginterface inoptimisationController.tsonly declarestpCount(legacy) and nottpTargetMode/partialExitEnabled. Since the optimisation JSONB storestpTargetMode(nottpCount), the mapping producestpCount: undefined→tpCountis never set in the apply call → the DB retains its previous value.
⚠️ Bug B (architectural):
tpCountandpartialCloseinpatternTradingConfigsare never read by the auto-trading engine. The engine readstpTargetModeandpartialExitEnabledfrompaperTradingAccounts. Storing these inpatternTradingConfigshas no effect on live trades. The optimisation’s TP-strategy recommendation must be applied manually to the account settings to take effect.
Fallback when patternType is ‘all’
When optimisation runs across all pattern types, bestRun.patternType = 'all'. upsertSystemConfig('all', ...) creates/updates the patternType = 'all' system config. This config sits at step 8 of the getMergedConfig 8-step fallback chain — it’s the lowest-priority catch-all for all patterns that have no more specific config.
8. Config Propagation to Live Trading
After applyBestConfig:
pattern_trading_configs (patternType, userId=null) — system default
│
▼
patternTradingConfigService.getMergedConfig(userId, patternType, symbol, timeframe)
│ (8-step fallback: user-scoped → user-global → sys-scoped → sys-global)
▼
autoTradingEngine.evaluatePatternEvent
│ uses from config: slMultiplier, tp1-3Multiplier, entryStrategy,
│ minSignalScore (via portfolioStrategy, separate),
│ entryLagCandles
│
│ uses from ACCOUNT (not config): tpTargetMode, partialExitEnabled,
│ partialExitTp1Percent, maxOpenTrades,
│ maxEntryDeviationPips, lotSize, currency
▼
paperTradeRepository.create(...)
See Trading System — Dependency Map §3–§6 for the full live trade path.
9. API Reference
| Method | Path | Auth | Description |
|---|---|---|---|
| POST | /api/ohlc/optimise | JWT + admin | Trigger optimisation (SSE stream) |
| GET | /api/ohlc/optimise/results | JWT + admin | List past optimisation groups |
| POST | /api/ohlc/optimise/apply | JWT + admin | Apply best run’s config as system default |
10. Database
optimisation_runs (ohlc-service DB)
| Column | Type | Notes |
|---|---|---|
id | uuid PK | pre-generated |
run_group_id | uuid | groups all 50 runs from one optimisation |
symbol, timeframe, pattern_type | varchar | pattern_type = 'all' when no filter |
trading_config | jsonb | full TradingConfig object (canonical fields) |
completed_trades, wins, losses | integer | |
win_rate, avg_win_pips, avg_loss_pips | numeric | |
profit_factor, risk_reward_ratio | numeric nullable | null when no losses |
total_pnl_pips, max_drawdown_pips, max_drawdown_pct | numeric | |
composite_score | numeric | 0..1 weighted score |
is_best | boolean | true for the winning run in the group |
triggered_by_user_id | varchar nullable | who triggered it |
created_at | timestamptz |
pattern_trading_configs (mi-casa DB)
Receives updates from applyBestConfig. See Trading System — Dependency Map §5 for full column list and getMergedConfig fallback chain.
11. Known Bugs / Limitations
| ID | Summary | Severity |
| ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------ | -------------------- | ------ |
| #458 | OhlcTradingConfig missing tpTargetMode → applied config always gets wrong tpCount | High |
| #459 | tpCount / partialClose in pattern_trading_configs never read by engine — TP strategy from optimisation has no effect on live trading | High (architectural) |
| #460 | minSignalScore backtest filter passes null-signalScore patterns — backtest over-optimistic vs. live (see Backtest System) | Medium |
| #461 | Backtest entryPrice in output is lag=0 open even when entryLagCandles > 0 (see Backtest System) | Medium |