How the CloddsBot Unified Risk Engine Detects Volatility Regimes and Triggers Circuit Breakers

The unified risk engine monitors rolling P&L standard deviations to classify markets into low, normal, high, or extreme volatility regimes, then halts trading or applies size multipliers based on these classifications, while a separate circuit breaker subsystem monitors configurable trip conditions including volatility thresholds to prevent further trades when markets become unstable.

The CloddsBot trading bot implements a unified risk engine that combines real-time volatility detection with a configurable circuit breaker mechanism to protect capital during turbulent market conditions. This system analyzes trade P&L percentages through a rolling window approach to identify regime changes, automatically reducing position sizes or stopping trading entirely when volatility exceeds safe thresholds. According to the source code in alsk1992/CloddsBot, the engine coordinates between the Volatility Detector (src/risk/volatility.ts) and the Circuit Breaker (src/risk/circuit-breaker.ts) to enforce risk limits before every trade execution.

Detecting Volatility Regimes via Rolling Window Analysis

The Volatility Detector subsystem maintains a rolling window of trade P&L percentages to calculate dynamic standard deviations and classify market conditions into distinct regimes.

Feeding P&L Observations to the Detector

Every completed trade propagates its profit-and-loss percentage to the volatility subsystem through the engine’s recordPnL method. In src/risk/engine.ts, this function forwards the observation to the detector’s internal buffer:

// src/risk/engine.ts → recordPnL
function recordPnL(record: PnLRecord): void {
  varCalc.addObservation(record);
  volDetector.addObservation(record.pnlPct);   // ← feed to volatility detector
}

The addObservation method in src/risk/volatility.ts maintains the rolling window by pushing new values and evicting old ones once the buffer reaches the configured lookbackWindow size (defaulting to 30 observations).

Computing Baseline Volatility

Before the detector can classify regimes, it must establish a baseline standard deviation. The createVolatilityDetector function initializes an empty window array and computes baselineStdDev only after the window fills completely:

// src/risk/volatility.ts → addObservation
window.push(pnlPct);
while (window.length > cfg.lookbackWindow) window.shift();
if (baselineStdDev === null && window.length >= cfg.lookbackWindow) {
  baselineStdDev = computeStdDev(window);
}

This baseline serves as the reference point for determining whether current market volatility represents a departure from historical norms.

Classifying Market Regimes

On each call to detect(), the engine calculates fresh mean, standard deviation (σ), and ATR metrics. The classifyRegime function compares current σ against the baseline (or absolute thresholds when no baseline exists) to categorize conditions into four states:

// src/risk/volatility.ts → classifyRegime
const ratio = threshold > 1e-12 ? stdDev / threshold : (stdDev > 0 ? cfg.extremeThreshold + 1 : 0);
if (ratio <= cfg.lowThreshold) return 'low';
if (ratio <= cfg.highThreshold) return 'normal';
if (ratio <= cfg.extremeThreshold) return 'high';
return 'extreme';

The regimes—low, normal, high, and extreme—trigger different risk responses based on configurable threshold ratios.

Applying Size Multipliers and Trading Halts

Each regime maps to a configurable size multiplier that adjusts position sizing (for example, 1.2× for low volatility, 0.5× for high, and 0.25× for extreme). Additionally, the detector returns a shouldHalt boolean when the regime is classified as extreme and the haltOnExtreme configuration is enabled:

// src/risk/volatility.ts → detect()
return {
  regime,
  sizeMultiplier: cfg.regimeMultipliers[regime],
  shouldHalt: regime === 'extreme' && cfg.haltOnExtreme,
  …
};

During trade validation, the engine retrieves these values via volDetector.detect() and either rejects the trade with an "Extreme volatility" message or applies the multiplier to reduce position size.

Triggering Circuit Breakers on Volatility Conditions

While the volatility detector manages regime-based sizing, the Circuit Breaker (src/risk/circuit-breaker.ts) provides hard stops when specific risk thresholds are breached.

Pre-Trade Gate Checks

Early in the validateTrade execution sequence, the engine queries the circuit breaker’s state before proceeding with other validations:

// src/risk/engine.ts → validateTrade
if (deps.circuitBreaker) {
  const canTrade = deps.circuitBreaker.canTrade();
  const cbState = deps.circuitBreaker.getState();
  checks.push({
    name: 'circuit_breaker',
    passed: canTrade,
    message: canTrade
      ? 'Circuit breaker OK'
      : `Circuit breaker tripped: ${cbState.tripReason}`,
  });
  if (!canTrade) { … return rejected … }
}

If canTrade() returns false, the trade aborts immediately with the specific trip reason logged for audit purposes.

Evaluating Volatility Trip Conditions

The circuit breaker evaluates multiple condition types, including volatility-based trips. The checkVolatilityCondition function fetches the current market volatility percentage from the feature engineering service (src/services/feature-engineering/index.ts) and compares it against the configured maxVolatilityPct:

// src/risk/circuit-breaker.ts → checkVolatilityCondition
const volatilityPct = getVolatilityPct(features);
if (volatilityPct !== null && volatilityPct > condition.maxVolatilityPct) {
  return { tripped: true, details: { volatilityPct, … } };
}

This allows the breaker to trip based on external market data independently of the internal P&L-based regime detector.

Trip Execution and Event Emission

When any condition returns tripped: true, the breaker invokes tripBreaker(event), which atomically sets the state, timestamps the incident, logs the warning, and emits a notification:

// src/risk/circuit-breaker.ts → tripBreaker
state.tripped = true;
state.trippedAt = event.timestamp;
state.tripEvent = event;
logger.warn({ condition: event.condition.type, details: event.details }, 'Circuit breaker tripped');
emitter.emit('tripped', event);

This creates an immutable record of why trading halted, accessible via the dashboard and programmatic interfaces.

Automatic Reset and Cooldown

For transient volatility events, the circuit breaker supports automatic recovery. When autoReset is enabled and cooldownMs is greater than zero, tripBreaker schedules a timer to call resetBreaker(false) after the cooldown period expires:

// src/risk/circuit-breaker.ts → tripBreaker (auto‑reset)
if (cfg.autoReset && cfg.cooldownMs > 0) {
  state.resetAt = new Date(Date.now() + cfg.cooldownMs);
  setTimeout(() => { if (state.tripped && cfg.autoReset) resetBreaker(false); }, cfg.cooldownMs);
}

This mechanism prevents indefinite trading halts while ensuring sufficient cooling-off time after volatile market moves.

Engine Integration and Trade Validation Flow

The unified risk engine orchestrates seven sequential safety checks during validateTrade. The volatility regime and circuit breaker evaluations occupy specific positions in this pipeline:

  1. Kill-switch validation (safety manager)
  2. Circuit-breaker check (deps.circuitBreaker.canTrade())
  3. Order-size and exposure limits
  4. Daily loss, drawdown, and concentration validations
  5. Value-at-Risk (VaR) limit verification
  6. Volatility regime check (volDetector.detect()) with potential abort or size adjustment
  7. Kelly sizing final adjustment

If the volatility detector’s shouldHalt flag is true or the circuit breaker has tripped, the engine returns a rejected decision with detailed diagnostic messages. Otherwise, it applies the sizeMultiplier from the current regime—retrieved via volDetector.getRegime()—to the Kelly-adjusted or raw position size before approving the trade.

Configuration and Implementation Examples

Creating a Risk Engine with Custom Volatility Thresholds

import { createRiskEngine } from './risk/engine';
import { createVolatilityDetector } from './risk/volatility';

// Example config – more conservative regime thresholds
const riskEngine = createRiskEngine(
  {
    varLimit: 100_000,
    volatilityConfig: {
      lowThreshold: 0.4,
      highThreshold: 1.2,
      extremeThreshold: 2.5,
      regimeMultipliers: { low: 1.3, normal: 1.0, high: 0.6, extreme: 0.25 },
      haltOnExtreme: true,
    },
  },
  {
    riskContext,
    safetyManager,
    circuitBreaker: createCircuitBreaker(CONSERVATIVE_CONFIG),
    kellyCalculator,
    getPositions,
    getPositionValues,
  }
);

Simulating Extreme Volatility Conditions

// Simulate extreme volatility by feeding high‑variance P&L observations
for (let i = 0; i < 40; i++) {
  riskEngine.recordPnL({ pnlPct: Math.random() * 5 - 2.5 }); // large swings
}

// Now request a trade – the engine will reject it due to the halted regime
const decision = riskEngine.validateTrade({
  userId: 'u123',
  platform: 'polymarket',
  marketId: '0xabc',
  side: 'buy',
  size: 500,
  price: 0.6,
  outcome: 'YES',
});
console.log(decision.approved); // false
console.log(decision.reason);   // "Extreme volatility — trading halted"

Accessing Circuit Breaker State via Dashboard

const dashboard = riskEngine.getDashboard();
console.log(dashboard.circuitBreakerState?.tripped); // true/false
if (dashboard.circuitBreakerState?.tripped) {
  console.log('Trip reason:', dashboard.circuitBreakerState.tripEvent?.condition.type);
}

Summary

  • The Volatility Detector in src/risk/volatility.ts maintains a 30-period rolling window of P&L percentages to calculate standard deviations and classify markets into low, normal, high, or extreme regimes.
  • Each volatility regime applies a configurable size multiplier to reduce position exposure during turbulent periods, with an optional trading halt when haltOnExtreme is enabled for extreme regimes.
  • The Circuit Breaker in src/risk/circuit-breaker.ts provides independent trip conditions based on external volatility metrics (maxVolatilityPct) and other risk factors, creating a hard stop via tripBreaker().
  • Both subsystems integrate in src/risk/engine.ts during the validateTrade sequence, with the circuit breaker acting as a pre-trade gate and the volatility detector applying dynamic sizing adjustments.
  • Automatic reset functionality allows the circuit breaker to resume trading after a configurable cooldownMs period, while the dashboard provides real-time visibility into both the current regime and breaker state.

Frequently Asked Questions

How does the volatility detector calculate the baseline standard deviation?

The detector stores P&L observations in a rolling window array capped at the lookbackWindow configuration (default 30). Once the window fills completely, it computes the initial baselineStdDev using the computeStdDev function on the full window. Subsequent regime classifications compare current volatility against this baseline to determine if market conditions have shifted relative to recent history.

What happens when the circuit breaker trips due to volatility?

When the checkVolatilityCondition function detects that getVolatilityPct exceeds the configured maxVolatilityPct, it returns a trip signal. The tripBreaker method then sets state.tripped to true, records the timestamp and trip reason, logs a warning with the volatility percentage, and emits a tripped event. All subsequent calls to canTrade() return false, rejecting new orders until the breaker resets.

Can the circuit breaker automatically resume trading after a volatility spike?

Yes. If the configuration specifies autoReset: true and a positive cooldownMs value, the breaker automatically schedules a timer upon tripping. When the cooldown duration elapses, resetBreaker(false) clears the tripped state without manual intervention, allowing the canTrade() check to pass again for new trade requests.

How does the engine combine volatility regime adjustments with Kelly sizing?

During validateTrade, the engine first calculates the Kelly-optimal position size (if enabled), then applies the sizeMultiplier from the current volatility regime as a final scaling factor. For example, a 1,000 unit Kelly-optimal position in a high-volatility regime (0.5× multiplier) would execute as 500 units. This sequential application ensures that market turbulence reduces exposure even when the underlying edge calculation suggests a larger position.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →