The Ten Stages of the CloddsBot Risk Engine Pre-Trade Pipeline
CloddsBot validates every trade through a unified RiskEngine that executes ten ordered checks—from kill switches to Kelly sizing—before any order reaches the exchange.
The RiskEngine serves as the gatekeeper for all trading activity in the CloddsBot system, implemented in src/risk/engine.ts. This defensive layer ensures that no position violates firm-wide risk limits or user-specific constraints. Below is the complete technical breakdown of each validation stage as defined in the source code and documented in src/skills/bundled/risk/SKILL.md.
Stage 1: Global Kill Switch
The pipeline begins with a system-level halt check using the SafetyManager. If the global kill switch is active, the engine rejects the trade immediately without further processing.
In src/risk/engine.ts (lines 5‑6), the engine queries the safety manager to determine if the entire system is disabled. This serves as the first line of defense against catastrophic market events or operational emergencies.
Stage 2: Circuit Breaker Validation
Next, the engine calls the execution-level CircuitBreaker.canTrade() method (lines 6‑7 in engine.ts). A tripped circuit breaker—triggered by excessive volatility or consecutive losses—blocks the trade at this stage.
This check operates independently from the global kill switch, allowing granular control over specific trading venues or strategies while maintaining system-wide safety.
Stage 3: Maximum Order Size Enforcement
The engine enforces the user-defined maxOrderSize parameter through the enforceMaxOrderSize function (lines 14‑16). Any order exceeding the configured size limit is rejected outright, preventing accidental fat-finger errors or oversized positions.
Stage 4: Exposure Limit Compliance
Per-user exposure checks occur via enforceExposureLimits (lines 36‑38), validating against maxTotalExposure and maxPositionValue thresholds. If the proposed trade would push the user's portfolio beyond either ceiling, the engine blocks execution.
This stage aggregates current positions from the getPositions and getPositionValues dependencies to calculate real-time exposure impact.
Stages 5‑7: SafetyManager Validations
The engine batches three critical risk checks through SafetyManager.validateTrade (lines 60‑62):
- Daily Loss Limit: Rejects trades that would exceed the configured daily loss ceiling.
- Maximum Drawdown: Blocks orders that would push the account beyond the allowed percentage drawdown from peak equity.
- Concentration Limit: Prevents HHI-style concentration breaches that would over-weight the portfolio in a single asset or category.
All three validations share the same invocation point but evaluate distinct risk metrics from the safety context.
Stage 8: Value‑at‑Risk (VaR) Calculation
The VaR Limit stage (lines 88‑99) calculates portfolio Value‑at‑Risk using the VaRCalculator. If the computed VaR exceeds the user-provided varLimit (typically set at 95% confidence), the trade is rejected.
This quantifies the potential loss in dollar terms under normal market conditions, adding a statistical safety layer beyond static exposure limits.
Stage 9: Volatility Regime Detection
The VolatilityDetector analyzes current market conditions (lines 19‑28). If the detector flags an extreme regime via shouldHalt, the trade is disallowed. In elevated—but not extreme—volatility, the engine may approve the trade but append a warning flag for downstream logging.
The detector typically uses a configurable lookbackWindow (default 30 periods) to determine regime state.
Stage 10: Kelly Sizing Recommendation
The final stage invokes the optional DynamicKellyCalculator (lines 41‑53) to compute a Kelly-optimal position size based on the trade's estimatedEdge and confidence. Unlike previous stages, this step never blocks the trade; instead, it may reduce the requested size through a volatility-regime multiplier.
The adjusted size appears in the adjustedSize field of the validation response, allowing the execution layer to submit a smaller, mathematically optimal order.
Implementation Example
The following TypeScript demonstrates how to instantiate the risk engine with all ten stages configured:
import { createRiskEngine } from 'clodds/risk';
import { getGlobalCircuitBreaker } from 'clodds/execution';
const riskEngine = createRiskEngine(
{
varLimit: 500,
varConfidence: 0.95,
volatilityConfig: { lookbackWindow: 30, haltOnExtreme: true },
},
{
riskContext: { db: {/* … */} },
safetyManager: {/* implements canTrade() & validateTrade() */},
circuitBreaker: getGlobalCircuitBreaker(),
kellyCalculator: {/* implements calculate() */},
getPositions: () => [],
getPositionValues: () => [],
}
);
const decision = riskEngine.validateTrade({
userId: 'user-123',
platform: 'polymarket',
marketId: 'abc-xyz',
side: 'buy',
size: 200,
price: 0.65,
estimatedEdge: 0.04,
confidence: 0.8,
category: 'politics',
});
if (decision.approved) {
console.log('Trade passes all 10 checks. Adjusted size =', decision.adjustedSize);
} else {
console.log('Trade blocked at stage:', decision.reason);
}
Summary
- Stages 1‑2 (
SafetyManager,CircuitBreaker) provide system-wide emergency stops. - Stages 3‑4 (
enforceMaxOrderSize,enforceExposureLimits) enforce hard position limits. - Stages 5‑7 (
validateTrade) check daily loss, drawdown, and concentration via the safety manager. - Stage 8 (
VaRCalculator) applies statistical risk metrics. - Stage 9 (
VolatilityDetector) halts trading during extreme market regimes. - Stage 10 (
DynamicKellyCalculator) optimizes sizing without blocking trades.
Each stage in src/risk/engine.ts can independently reject the trade, creating a defense-in-depth architecture that prioritizes capital preservation over execution speed.
Frequently Asked Questions
What happens if the volatility regime is extreme but all other checks pass?
The trade is blocked at Stage 9. According to the VolatilityDetector implementation in engine.ts lines 19‑28, when shouldHalt returns true for an extreme regime, the engine returns a rejection decision regardless of VaR or exposure limit compliance. This prevents trading during market discontinuities.
Can the Kelly sizing stage override previous rejection decisions?
No. The DynamicKellyCalculator runs only if the trade passes stages 1‑9. As implemented in engine.ts lines 41‑53, this stage only modifies the adjustedSize field downward; it cannot resurrect a rejected trade or increase the requested size beyond what earlier stages approved.
Where are the specific risk limits configured?
User-defined limits like maxOrderSize, maxTotalExposure, and varLimit are passed as configuration parameters to createRiskEngine (shown in the implementation example above). System-wide defaults and safety thresholds reside in the SafetyManager implementation, while circuit breaker parameters are defined in src/execution/circuit-breaker.ts.
How does the engine handle partial position closures?
The engine evaluates exposure limits using getPositions and getPositionValues (passed as dependencies), which return the current portfolio state. For closing trades, the logic in enforceExposureLimits (lines 36‑38) recognizes the delta effect and only blocks trades that would increase net exposure beyond limits, not those reducing it.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →