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

#ConditionSource
1OHLC_SERVICE_URL and OHLC_SERVICE_API_KEY setoptimisationController.ts
2Caller authenticated with admin roleoptimisationRoutes.ts
3Valid symbol, timeframe in requestpostSchema (Zod)
4ohlc_patterns data exists (saved_patterns mode)ohlcPatternRepository
5ohlc_candles data exists for the full date range (walk_forward mode)ohlcCandleRepository.getFrom
6optimisation_runs table exists in ohlc-service DBmigration

3. The Parameter Grid

buildGrid(optimiseEntryStrategy?) produces 50 configs (baseline + 49 LHS-style combinations):

ParameterValues tested
slMultiplier0.8, 1.0, 1.2, 1.5
tp1Multiplier0.8, 1.0, 1.2
tp2Multiplier0.8, 1.0, 1.2
tp3Multiplier0.8, 1.0, 1.2
tpTargetModetp1, tp1_tp2, tp1_tp2_tp3
partialExitEnabledfalse, true (with tp1_tp2_tp3 only)
partialExitTp1Percent33%, 50% (when partial exit enabled)
entryLagCandles0, 1, 2 — forced to 0 when entryStrategy is swing_confirmation, prz_touch, or candle_close
minSignalScore0, 0.3, 0.5
entryStrategyswing_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 passing runs: pick highest compositeScore from passing.
  • If no passing runs (best-effort): pick highest compositeScore across all runs.
  • isBestEffort: true in 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:

  1. Group by run_group_id, get newest groups first (by earliest created_at in group).
  2. For each group: fetch isBest=true run first; fall back to highest compositeScore.

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 fieldApplied 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: OhlcTradingConfig interface in optimisationController.ts only declares tpCount (legacy) and not tpTargetMode / partialExitEnabled. Since the optimisation JSONB stores tpTargetMode (not tpCount), the mapping produces tpCount: undefinedtpCount is never set in the apply call → the DB retains its previous value.

⚠️ Bug B (architectural): tpCount and partialClose in patternTradingConfigs are never read by the auto-trading engine. The engine reads tpTargetMode and partialExitEnabled from paperTradingAccounts. Storing these in patternTradingConfigs has 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

MethodPathAuthDescription
POST/api/ohlc/optimiseJWT + adminTrigger optimisation (SSE stream)
GET/api/ohlc/optimise/resultsJWT + adminList past optimisation groups
POST/api/ohlc/optimise/applyJWT + adminApply best run’s config as system default

10. Database

optimisation_runs (ohlc-service DB)

ColumnTypeNotes
iduuid PKpre-generated
run_group_iduuidgroups all 50 runs from one optimisation
symbol, timeframe, pattern_typevarcharpattern_type = 'all' when no filter
trading_configjsonbfull TradingConfig object (canonical fields)
completed_trades, wins, lossesinteger
win_rate, avg_win_pips, avg_loss_pipsnumeric
profit_factor, risk_reward_rationumeric nullablenull when no losses
total_pnl_pips, max_drawdown_pips, max_drawdown_pctnumeric
composite_scorenumeric0..1 weighted score
is_bestbooleantrue for the winning run in the group
triggered_by_user_idvarchar nullablewho triggered it
created_attimestamptz

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 |