Overview
The DrawdownTracker class manages bankroll state and enforces progressive risk reduction as losses accumulate. The system uses a 4-level framework (green → yellow → red → critical) based on drawdown from the high-water mark, with additional cold-streak detection to catch model degradation early.
Drawdown Levels
The tracker defines four distinct risk zones:
| Level | Drawdown Range | Alpha Multiplier | Min EV Override | Trading Status |
|---|
| Green | < yellowPct | 1.0 | None | Full sizing |
| Yellow | ≥ yellowPct | 0.5 | 0.10 | Half sizing |
| Red | ≥ redPct | 0 | N/A | Suspended |
| Critical | ≥ criticalPct | 0 | N/A | Suspended |
Default thresholds: yellowPct = 0.10 (10%), redPct = 0.15 (15%), criticalPct = 0.20 (20%)
Drawdown Calculation
Drawdown is computed as the percentage decline from the high-water mark:
drawdown = (highWaterMark - bankroll) / highWaterMark
- High-water mark: The maximum bankroll value ever achieved
- Bankroll: Current account balance after all trades
- Drawdown: Always a value between 0 (at peak) and 1 (total loss)
Level Determination Logic
getLevel() {
const drawdown = (this._highWaterMark - this._bankroll) / this._highWaterMark
if (drawdown >= this._criticalPct) return 'critical'
if (drawdown >= this._redPct) return 'red'
if (drawdown >= this._yellowPct) return 'yellow'
// Green zone, but cold streak may force yellow
if (this._forcedYellow) return 'yellow'
return 'green'
}
Source: src/risk/drawdown-tracker.js:81
Even in the green zone (drawdown < 10%), the cold-streak circuit breaker can force yellow mode if the model exhibits consecutive high-confidence misses.
Cold-Streak Circuit Breaker
The tracker monitors consecutive high-confidence prediction misses as an early warning signal for model degradation:
Triggering Conditions
- Confidence threshold: Prediction confidence ≥
minConfidenceForStreak (default: 0.70)
- Consecutive misses: Streak reaches
consecutiveMissThreshold (default: 3)
- Result: Force yellow mode even if bankroll drawdown is in green zone
Reset Conditions
The cold streak resets to zero and yellow mode is released when:
- Any prediction is correct (regardless of confidence level)
Implementation
recordOutcome(correct, confidence) {
if (correct) {
this._coldStreak = 0
this._forcedYellow = false
return
}
if (confidence >= this._minConfidenceForStreak) {
this._coldStreak++
if (this._coldStreak >= this._consecutiveMissThreshold) {
this._forcedYellow = true
}
}
}
Source: src/risk/drawdown-tracker.js:60
The cold-streak mechanism is independent of whether predictions were traded. It tracks prediction accuracy, not trade outcomes, to detect model issues before they cause significant losses.
Bankroll Updates
Trade Recording
The tracker updates bankroll after each completed trade:
recordTrade(betAmount, won) {
this._tradeCount++
if (won) {
this._winCount++
this._bankroll += betAmount * 0.97 // 3% Polymarket fee
} else {
this._bankroll -= betAmount
}
if (this._bankroll > this._highWaterMark) {
this._highWaterMark = this._bankroll
}
}
Source: src/risk/drawdown-tracker.js:36
Payout Logic
- Wins: Add
betAmount × 0.97 (after 3% Polymarket fee)
- Losses: Deduct full
betAmount
- High-water mark: Updated only when bankroll reaches a new peak
The high-water mark is never decreased. This ensures drawdown percentages are always calculated from the absolute peak, not a moving baseline.
Position Sizing Adjustments
The getAdjustments() method returns parameters that modify position sizing behavior:
getAdjustments() {
const level = this.getLevel()
switch (level) {
case 'green':
return { alphaMultiplier: 1.0, minEVOverride: null, suspend: false }
case 'yellow':
return { alphaMultiplier: 0.5, minEVOverride: 0.10, suspend: false }
case 'red':
return { alphaMultiplier: 0, minEVOverride: null, suspend: true }
case 'critical':
return { alphaMultiplier: 0, minEVOverride: null, suspend: true }
}
}
Source: src/risk/drawdown-tracker.js:106
Parameter Descriptions
| Parameter | Type | Description |
|---|
alphaMultiplier | number | Multiplied by the Brier-tier alpha (see Position Sizing) |
minEVOverride | number | null | Overrides the minimum expected value threshold for yellow mode |
suspend | boolean | If true, all trading is halted |
Effect on Position Sizing
The alphaMultiplier is applied in the PositionSizer calculation:
let alpha = this.getAlpha(brierScore, predictionCount)
if (adjustments?.alphaMultiplier) {
alpha *= adjustments.alphaMultiplier
}
Example:
- Brier Tier 3 → base alpha = 0.25
- Yellow mode → alphaMultiplier = 0.5
- Effective alpha = 0.25 × 0.5 = 0.125 (half the normal sizing)
State Snapshot
The getState() method returns a comprehensive view of current risk status:
{
bankroll: number, // Current bankroll in USD
initialBankroll: number, // Starting bankroll
highWaterMark: number, // Maximum bankroll ever achieved
drawdownPct: number, // Current drawdown (0-1)
level: string, // 'green' | 'yellow' | 'red' | 'critical'
coldStreak: number, // Consecutive high-confidence misses
forcedYellow: boolean, // True if cold streak forced yellow mode
tradeCount: number, // Total trades executed
winCount: number, // Total winning trades
pnl: number // Profit/loss from initial bankroll
}
Example Scenarios
Scenario 1: Normal Drawdown Progression
Initial: bankroll = $10,000, HWM = $10,000, level = green
Trade 1: Win $485 → bankroll = $10,485, HWM = $10,485, level = green
Trade 2: Lose $500 → bankroll = $9,985, HWM = $10,485
drawdown = (10,485 - 9,985) / 10,485 = 4.77% → level = green
Trade 3: Lose $600 → bankroll = $9,385, HWM = $10,485
drawdown = (10,485 - 9,385) / 10,485 = 10.49% → level = yellow
alphaMultiplier = 0.5 (half sizing activated)
Scenario 2: Cold-Streak Circuit Breaker
State: bankroll = $10,200, HWM = $10,200, level = green
Outcome 1: Wrong, confidence = 0.75 → coldStreak = 1
Outcome 2: Wrong, confidence = 0.82 → coldStreak = 2
Outcome 3: Wrong, confidence = 0.71 → coldStreak = 3
coldStreak >= threshold (3) → forcedYellow = true
level = yellow (despite 0% drawdown)
Outcome 4: Correct → coldStreak = 0, forcedYellow = false
level = green (restored)
Scenario 3: Red Zone Suspension
State: bankroll = $8,900, HWM = $10,485
drawdown = (10,485 - 8,900) / 10,485 = 15.12% → level = red
suspend = true (no trading allowed)
(Trading halts until manual intervention or system reset)
Configuration Parameters
Drawdown behavior is controlled by config.risk:
{
bankroll: 10000, // Initial bankroll in USD
drawdown: {
yellowPct: 0.10, // 10% drawdown triggers yellow
redPct: 0.15, // 15% drawdown triggers red
criticalPct: 0.20 // 20% drawdown triggers critical
},
coldStreak: {
consecutiveMissThreshold: 3, // Misses before forcing yellow
minConfidenceForStreak: 0.70 // Confidence threshold for streak
}
}
Integration with Position Sizer
The drawdown tracker provides adjustments to the PositionSizer via the adjustments parameter:
const adjustments = drawdownTracker.getAdjustments()
const result = positionSizer.calculateBet({
p,
q,
bankroll,
brierScore,
predictionCount,
adjustments // Contains alphaMultiplier, minEVOverride, suspend
})
If adjustments.suspend === true, the calling code should skip trade execution entirely.