# How CloddsBot's Risk Management Engine Calculates VaR, CVaR, and Kelly Sizing

> Discover how CloddsBot calculates VaR, CVaR, and Kelly sizing for effective risk management. Learn about rolling-window VaR, portfolio limits, and optimal position sizing.

- Repository: [AL/CloddsBot](https://github.com/alsk1992/CloddsBot)
- Tags: deep-dive
- Published: 2026-09-11

---

**CloddsBot evaluates trade risk in three sequential stages: computing rolling-window VaR and CVaR in [`src/risk/var.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/risk/var.ts), enforcing portfolio-level VaR limits in [`src/risk/engine.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/risk/engine.ts), and applying the Kelly criterion with confidence scaling via [`src/utils/kelly.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/utils/kelly.ts) to determine optimal position sizes.**

The alsk1992/CloddsBot repository implements a quantitative risk management subsystem that protects capital through statistical tail-risk measurement and mathematical bet sizing. The engine processes each trade through a structured pipeline that quantifies potential losses using both historical and parametric methods, then optimizes allocations using the Kelly formula adjusted for forecast confidence.

## Stage 1: VaR and CVaR Calculation in [`src/risk/var.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/risk/var.ts)

The foundation of CloddsBot's risk assessment lies in the `createVaRCalculator` factory function. This module maintains a rolling window of P&L observations to calculate Value at Risk (VaR) and Conditional Value at Risk (CVaR), also known as Expected Shortfall.

### Rolling Window Statistics

By default, the calculator initializes a bounded array (`window`) with a `windowSize` of 100 observations. As trades complete, the `addObservation()` method appends records containing `pnlUsd`, `pnlPct`, and timestamps (lines 85-92). The `computeStats` helper (lines 94-99) computes the arithmetic mean and standard deviation (σ) of these windowed values, providing the statistical moments required for parametric VaR.

### Historical and Parametric VaR

The `calculateAt` routine (lines 47-88) generates two distinct VaR estimates at a configurable confidence level (default 0.95):

- **Historical VaR**: The implementation sorts the window array and selects the loss at the `(1 - confidence)` percentile using `sorted[Math.floor((1 - confidence) * len)]` (lines 64-68). This represents the actual dollar loss that was exceeded in only 5% of historical observations.
- **Parametric VaR**: Assuming normally distributed returns, the `normInv` function (lines 101-145) calculates the z-score for the confidence level via the inverse CDF. The parametric VaR equals `-(mean - z * stdDev)` (lines 70-71), estimating tail risk based on the portfolio's volatility characteristics.

### CVaR (Expected Shortfall) Implementation

Conditional Value at Risk measures the average severity of losses beyond the VaR threshold. The calculator identifies all tail losses up to the VaR index, averages them using `tailLosses.reduce(...) / tailLosses.length`, and negates the result (lines 73-76). This figure represents the expected loss given that a tail event has occurred.

All metrics return within a `VaRResult` object (lines 22-37) containing `historicalVaR`, `parametricVaR`, `cvar`, sample size, and statistical moments.

## Stage 2: Portfolio VaR Limit Enforcement in [`src/risk/engine.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/risk/engine.ts)

After calculating risk metrics, the **Risk Engine** validates trades against configured limits. The `createRiskEngine` function initializes with a `varLimit` parameter in `RiskConfig`. Before approving any trade, the engine compares the portfolio's current VaR against this limit (lines 162-166). If the calculated tail risk exceeds the threshold, the engine immediately rejects the trade, preventing accumulation of correlated risk exposure during volatile periods.

## Stage 3: Kelly Criterion Sizing in [`src/utils/kelly.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/utils/kelly.ts)

Once VaR checks pass, the engine calculates optimal position sizes using the Kelly criterion to maximize logarithmic wealth growth while avoiding ruin.

### The Core Formula and Input Validation

The `calculateKelly` function (lines 16-66) implements the standard formula **f* = (b × p - q) / b**, where:
- **b** = net odds (`odds - 1`)
- **p** = win probability (`winProb`)
- **q** = loss probability (`1 - p`)

Input validation (lines 20-26) rejects impossible probabilities or odds ≤ 1, ensuring mathematical validity.

### Expected Value Filtering

The calculator verifies positive expected value before proceeding: `winProb * b - loseProb > 0` (lines 36-40). If the EV calculation yields zero or negative results—indicating a losing proposition—the function returns zero for all position sizes, preventing the risk engine from approving -EV trades.

### Confidence Scaling and Bet Size Calculation

To account for uncertainty in probability estimates, the raw Kelly fraction undergoes confidence adjustment via the optional `confidence` parameter (lines 44-46), multiplying f* by a scaling factor (typically 0.0 to 1.0).

The final output includes:
- **Full Kelly**: `bankroll × min(adjustedFraction, 1)` (capped at 100% of capital)
- **Half Kelly**: 50% of the full Kelly amount
- **Quarter Kelly**: 25% of the full Kelly amount
- **Recommended Size**: Defaults to half-Kelly of the confidence-adjusted fraction, hard-capped at 25% of total bankroll (lines 52-55)

## Practical Implementation Examples

### Creating a VaR Calculator

```typescript
import { createVaRCalculator } from './src/risk/var';

// Initialize with 99% confidence and 120-trade window
const varCalc = createVaRCalculator({ windowSize: 120, confidenceLevel: 0.99 });

// Feed P&L observations as trades complete
varCalc.addObservation({ pnlUsd: -12.4, pnlPct: -0.012, timestamp: new Date() });
varCalc.addObservation({ pnlUsd: 8.3, pnlPct: 0.008, timestamp: new Date() });

// Retrieve risk metrics
const result = varCalc.calculate();
console.log('Historical VaR ≈ $' + result.historicalVaR);
console.log('Parametric VaR ≈ $' + result.parametricVaR);
console.log('CVaR (Expected Shortfall) ≈ $' + result.cvar);

```

### Computing Kelly Position Sizes

```typescript
import { calculateKelly } from './src/utils/kelly';

const kelly = calculateKelly({
  winProb: 0.62,      // 62% probability of winning
  odds: 2.5,          // 2.5:1 payout (net odds = 1.5)
  bankroll: 10_000,   // $10,000 bankroll
  confidence: 0.8,    // 80% confidence in the edge estimate
});

console.log('Kelly fraction ≈ ' + (kelly.kellyFraction * 100).toFixed(1) + '%');
console.log('Full-Kelly bet ≈ $' + kelly.fullKelly.toFixed(2));
console.log('Recommended (half-Kelly) bet ≈ $' + kelly.recommendedSize.toFixed(2));

```

### Integrating with the Risk Engine

```typescript
import { createRiskEngine } from './src/risk/engine';

const engine = createRiskEngine(
  { varLimit: 5_000, varConfidence: 0.95, initialBankroll: 20_000 },
  {
    riskContext: {/* portfolio context */},
    kellyCalculator: {/* DynamicKellyCalculator instance */},
  },
);

// Record completed trade P&L
engine.recordPnL({ pnlUsd: -150, pnlPct: -0.0075, timestamp: new Date() });

// Validate new trade request
const decision = engine.validateTrade({
  userId: 'u123',
  platform: 'polymarket',
  marketId: 'm456',
  side: 'buy',
  size: 1_000,
  price: 0.55,
  estimatedEdge: 0.06,
  confidence: 0.85,
});

if (decision.approved) {
  console.log('Approved size:', decision.adjustedSize);
} else {
  console.warn('Rejected:', decision.reason);
}

```

## Summary

- **Three-stage validation**: CloddsBot's risk engine first quantifies tail risk via VaR/CVaR, enforces portfolio limits, then applies Kelly sizing for capital allocation.
- **Dual VaR methodology**: The system calculates both historical percentile losses and parametric normal-distribution estimates to cross-validate risk figures.
- **Expected Shortfall**: CVaR averages losses beyond the VaR threshold, providing insight into the severity of tail events rather than just their probability.
- **Defensive Kelly implementation**: The formula includes EV filtering to block negative-expectation bets, confidence scaling for model uncertainty, and hard caps at 25% of bankroll for the recommended size.
- **Volatility regime adjustment**: Final position sizes are multiplied by `volSnapshot.sizeMultiplier` (lines 343-351 in [`engine.ts`](https://github.com/alsk1992/CloddsBot/blob/main/engine.ts)) to reduce exposure during high-volatility periods.

## Frequently Asked Questions

### What confidence level does CloddsBot use for VaR calculations?

The default confidence level is **0.95 (95%)**, meaning the VaR represents the loss level that is not expected to be exceeded 95% of the time. This is configurable during calculator instantiation via the `confidenceLevel` parameter or overridden per-call in `calculateAt`.

### How does the Kelly calculator handle negative expected value scenarios?

The `calculateKelly` function explicitly checks if `winProb * b - loseProb` is positive (lines 36-40). If the expected value is zero or negative, the function returns a zero-size result for all bet types (`fullKelly`, `halfKelly`, `recommendedSize`), preventing the risk engine from approving mathematically losing propositions.

### What is the maximum position size the Kelly algorithm can recommend?

While the raw Kelly fraction `f*` can theoretically exceed 1.0, the implementation caps the **full Kelly** at 100% of bankroll (`min(adjustedFraction, 1)`). The **recommended size** defaults to half-Kelly and is further constrained to a maximum of 25% of total bankroll (lines 52-55), ensuring no single position can cause catastrophic drawdown.

### How does the risk engine adjust Kelly sizing for market volatility?

After retrieving the base Kelly position size, the engine applies a volatility regime multiplier via `volSnapshot.sizeMultiplier` (lines 343-351 in [`src/risk/engine.ts`](https://github.com/alsk1992/CloddsBot/blob/main/src/risk/engine.ts)). This scaler reduces the recommended position size during high-volatility environments and increases it during stable periods, dynamically adapting the Kelly output to current market conditions before final approval.