Skip to main content

Backtest Position Sizing Fix: Enforce Exposure Cap

Problem​

Backtest position sizing could produce very large notionals when using risk-based sizing with tight stop losses because no exposure/leverage cap was enforced.

Example:

  • Risk amount = balance * riskPerTrade
  • Size = riskAmount / (entryPrice * stopLossPct)
  • Notional = size * entryPrice = riskAmount / stopLossPct

With small stop percentages this can exceed account balance by large multiples.

Fix Implemented​

Added a hard exposure cap to the sizing engine:

  • New config field: maxLeverage
  • Default: 1 (no leverage)
  • Applied to both:
    • engine-computed size
    • strategy-provided size overrides (signal.size)

Capping rule:

  • maxNotional = balance * maxLeverage
  • maxSize = maxNotional / entryPrice
  • finalSize = min(requestedSize, maxSize)

Files Changed​

  • services/backend/src/lib/backtest-engine/runner.ts
  • services/backend/src/lib/backtest-engine/types.ts
  • shared/types.ts

Runtime Metadata​

When opening a position, signals now also include:

  • position.maxLeverage
  • position.cappedByExposureLimit

This makes it auditable when a trade size was reduced by the cap.

Validation​

  • pnpm typecheck passed.
  • pnpm lint passed with existing pre-existing warnings only.

Deployments​

  • Dev backend version: 555d882a-5b1b-41d9-ba90-f3418ec8eb76
  • Production backend version: 5d4b860e-cae8-4b03-81b6-293b593b7ad2

Impact​

  • Existing completed backtests are unchanged (historical records).
  • New backtests/reruns now enforce capped exposure by default.