Skip to content

MllRiskManager

Centralized Maximum Loss Limit (MLL) risk calculations, intraday extreme tracking, and immutable breach recording for challenges.

High-Level Design

MllRiskManager is a stateless calculation engine with one piece of owned state: the intraday_tracking table. It does not initiate any actions itself -- it is always called by other managers who supply the account value and react to the results.

Core Concept: MLL Thresholds

Each challenge plan defines a capital amount and an MLL threshold. Two derived balances drive all status logic:

minBalance   = capital - mllThreshold     (breach line)
alertBalance = capital - mllThreshold * 0.9 (warning buffer, ~10% above breach)

Both are computed from static CHALLENGE_CONFIGURATIONS -- no stored state involved.

MLL Status Classification

Given an account value, the manager classifies it into one of three states:

                    accountValue
    ───────────────────┼──────────────────────
         breached      │    at-risk    │  safe
    ──────────────────►│◄─────────────►│◄──────
                  minBalance      alertBalance
  • safe: Above alertBalance
  • at-risk: Between alertBalance and minBalance (warning zone)
  • breached: At or below minBalance (challenge fails)

Non-ACTIVE challenges always return "safe".

Data Flow: Who Calls What

MllRiskManager is invoked from four breach detection sites, each covering a different timing window:

┌──────────────────────────────┐
│  1. BalanceManager.applyPnL  │──► recordMllBreach()
│     (on realized PnL)        │    Balance-only check, no unrealized PnL.
└──────────────────────────────┘    Catches breaches at trade close time.

┌──────────────────────────────────────────┐
│  2. OrderExecutionManager.closePosition  │──► recordMllBreach()
│     (after position close)               │    Checks full account value
└──────────────────────────────────────────┘    (balance + unrealized PnL).

┌──────────────────────────────────────────────┐
│  3. AccountHealthManager                     │──► recordMllBreach()
│     (periodic + on-demand checks)            │    Hierarchical search drills
│                                              │    from 1h → 1m → per-second
└──────────────────────────────────────────────┘    to find exact breach moment.

┌──────────────────────────────────────────┐
│  4. ChallengeManager.passChallenge       │──► recordMllBreach()
│     (final guard before passing)         │    Prevents passing if account
└──────────────────────────────────────────┘    value is at or below MLL.

For display purposes, BalanceManager calls getMllRiskInfo() when building portfolio state, providing the risk dashboard data (status, distances, percentages).

Intraday Extreme Tracking

MllRiskManager owns the intraday_tracking SQLite table, which stores per-day peak and bottom account values for the current challenge account.

Write path: AccountValueTrackerManager receives hourly extremes from AccountValueAggregationTracker DO callbacks and forwards them here via updateIntradayExtremes().

Read path: ChallengeManager reads intraday low via getIntradayLow() for challenge status display.

Lifecycle:

  • Hourly: UPSERT with conditional update -- only overwrites if the new value is more extreme (lower bottom or higher peak). Uses CAST(... AS REAL) for numeric comparison of string-stored BigNumbers.
  • Daily (UTC midnight): clearIntradayTracking() deletes records older than today, called from the daily extremes callback.

Immutable Breach Recording

When any detection site finds accountValue <= minBalance, it calls recordMllBreach(accountValue, timestamp). This records the breach in the challenge table:

  • First-write-wins: If mll_breached_at is already set, subsequent calls are no-ops (returns false).
  • Dual update: Writes to SQLite and updates the in-memory challenge object for immediate consistency.
  • Forensic accuracy: The first breach timestamp is preserved regardless of how many detection sites fire. AccountHealthManager's hierarchical search provides per-second precision by drilling through 1h → 1m → per-second prices.

Multiple detection sites may race to record the same breach -- the immutability guarantee ensures exactly one timestamp is recorded.

See Also