CtrlK
BlogDocsLog inGet started
Tessl Logo

risk-analysis

Risk measurement and stress testing — VaR/CVaR/max drawdown calculation, Monte Carlo simulation, extreme-value tail-risk analysis, and historical scenario stress testing.

62

Quality

73%

Does it follow best practices?

Run evals on this skill

Adds up to 20 points to the overall score

View guide

SecuritybySnyk

Passed

No findings from the security scan

Fix and improve this skill with Tessl

tessl review fix ./agent/src/skills/risk-analysis/SKILL.md
SKILL.md
Quality
Evals
Security

Risk Measurement and Stress Testing

Overview

Systematic risk-measurement methodology covering VaR/CVaR calculation, Monte Carlo simulation, stress-test design, and tail-risk analysis. It provides risk evaluation for backtest results and risk-control constraints for asset allocation.

Risk Measurement Methods

1. VaR (Value at Risk)

Definition: the maximum expected loss over a given horizon at a specified confidence level.

Three Calculation Methods

MethodFormula / StepsAdvantagesDisadvantages
Historical simulationSort historical returns and take the quantileNo distribution assumptionDepends on historical samples
Parametric (normal)VaR = μ - z_α × σEasy to computeAssumes a normal distribution
Monte CarloSimulate N paths and take the quantileFlexibleComputationally intensive

Historical Simulation Implementation

import numpy as np
import pandas as pd

def historical_var(returns: pd.Series, confidence: float = 0.95, horizon: int = 1) -> float:
    """
    Args:
        returns: Daily return series
        confidence: Confidence level, commonly 0.95 or 0.99
        horizon: Holding period in days, default 1
    Returns:
        VaR value (positive means loss)
    """
    sorted_returns = returns.sort_values()
    index = int((1 - confidence) * len(sorted_returns))
    var_1d = -sorted_returns.iloc[index]
    return var_1d * np.sqrt(horizon)  # square-root-of-time rule

Parametric Implementation

from scipy.stats import norm

def parametric_var(returns: pd.Series, confidence: float = 0.95, horizon: int = 1) -> float:
    mu = returns.mean()
    sigma = returns.std()
    z = norm.ppf(1 - confidence)
    var_1d = -(mu + z * sigma)
    return var_1d * np.sqrt(horizon)

2. CVaR / ES (Conditional VaR / Expected Shortfall)

Definition: the average loss beyond the VaR threshold, more conservative than VaR.

def historical_cvar(returns: pd.Series, confidence: float = 0.95) -> float:
    """CVaR = the mean of all losses beyond VaR."""
    var = historical_var(returns, confidence)
    tail_losses = returns[returns < -var]
    return -tail_losses.mean() if len(tail_losses) > 0 else var

VaR vs CVaR comparison:

MetricVaR(95%)CVaR(95%)Meaning
Typical value-2.1%-3.4%CVaR is usually 1.3-1.8x VaR
SubadditivityNot satisfiedSatisfiedCVaR can be used for portfolio risk decomposition
RegulationBasel IIBasel IIIRegulatory trend is shifting toward CVaR

3. Maximum Drawdown Analysis

def max_drawdown_analysis(equity: pd.Series) -> dict:
    """
    Args:
        equity: Net-value series
    Returns:
        dict: max_drawdown, peak_date, trough_date, recovery_date, duration
    """
    peak = equity.cummax()
    drawdown = (equity - peak) / peak
    max_dd = drawdown.min()
    trough_idx = drawdown.idxmin()
    peak_idx = equity[:trough_idx].idxmax()

    # Recovery date
    recovery = equity[trough_idx:][equity[trough_idx:] >= equity[peak_idx]]
    recovery_date = recovery.index[0] if len(recovery) > 0 else None

    return {
        'max_drawdown': max_dd,
        'peak_date': peak_idx,
        'trough_date': trough_idx,
        'recovery_date': recovery_date,
        'underwater_days': (trough_idx - peak_idx).days,
        'recovery_days': (recovery_date - trough_idx).days if recovery_date else None
    }

4. Monte Carlo Simulation

Geometric Brownian Motion (GBM)

def monte_carlo_gbm(S0: float, mu: float, sigma: float,
                     T: int = 252, n_paths: int = 10000) -> np.ndarray:
    """
    Args:
        S0: Initial price
        mu: Annualized return
        sigma: Annualized volatility
        T: Number of simulation days
        n_paths: Number of paths
    Returns:
        Price matrix of shape (n_paths, T)
    """
    dt = 1 / 252
    Z = np.random.standard_normal((n_paths, T))
    log_returns = (mu - 0.5 * sigma**2) * dt + sigma * np.sqrt(dt) * Z
    prices = S0 * np.exp(np.cumsum(log_returns, axis=1))
    return prices

Simulation Result Analysis

def analyze_mc_results(paths: np.ndarray, confidence: float = 0.95) -> dict:
    final_prices = paths[:, -1]
    returns = final_prices / paths[:, 0] - 1

    return {
        'mean_return': np.mean(returns),
        'median_return': np.median(returns),
        'std_return': np.std(returns),
        'var': -np.percentile(returns, (1 - confidence) * 100),
        'cvar': -np.mean(returns[returns < -np.percentile(returns, (1-confidence)*100)]),
        'prob_loss': np.mean(returns < 0),
        'worst_5pct': np.percentile(returns, 5),
        'best_5pct': np.percentile(returns, 95),
    }

Stress-Testing Framework

Historical Scenario Stress Tests

ScenarioPeriodChina A-share DrawdownUS Equity DrawdownBTC Drawdown10Y Government Bonds
2008 financial crisis2008.01-2008.10-65%-50%N/Ayield ↓ 100bp
2015 China equity crash2015.06-2015.08-45%-10%-20%yield ↓ 50bp
2018 trade war2018.01-2018.12-25%-20%-80%yield ↓ 30bp
2020 COVID shock2020.01-2020.03-15%-35%-50%yield ↓ 80bp
2022 hiking cycle2022.01-2022.10-20%-25%-65%yield ↑ 200bp

Hypothetical Scenario Design

STRESS_SCENARIOS = {
    'rate_shock_up_100bp': {
        'equity': -0.10,    # equities down 10%
        'bond_10y': -0.08,  # 10-year bonds down 8%
        'bond_2y': -0.02,   # short bonds down 2%
        'gold': +0.05,      # gold up 5%
        'btc': -0.15,       # BTC down 15%
    },
    'credit_crisis': {
        'equity': -0.25,
        'bond_10y': +0.05,  # government bonds act as a safe haven
        'credit_bond': -0.15,
        'gold': +0.10,
        'btc': -0.30,
    },
    'liquidity_dry_up': {
        'equity': -0.20,
        'bond_10y': -0.05,  # when liquidity is poor, everything falls
        'gold': -0.05,
        'btc': -0.40,
        'cash': 0.0,
    },
    'geopolitical_conflict': {
        'equity': -0.15,
        'bond_10y': +0.03,
        'gold': +0.15,
        'oil': +0.30,
        'btc': -0.20,
    },
}

Stress-Test Implementation Steps

  1. Select a scenario: either historical or hypothetical
  2. Apply shocks: multiply scenario shocks by the current positions
  3. Compute portfolio loss: portfolio_loss = Σ(weight_i × shock_i × position_i)
  4. Assess adequacy: compare loss vs risk budget and whether stop-loss thresholds are triggered

Tail-Risk Analysis (Extreme Value Theory, EVT)

POT Method (Peaks Over Threshold)

from scipy.stats import genpareto

def fit_gpd_tail(returns: pd.Series, threshold_pct: float = 5.0) -> dict:
    """
    Fit the tail with a generalized Pareto distribution.
    Args:
        returns: Daily returns
        threshold_pct: Threshold percentile (take the worst X%)
    """
    threshold = np.percentile(returns, threshold_pct)
    exceedances = threshold - returns[returns < threshold]  # make positive

    # Fit GPD
    shape, loc, scale = genpareto.fit(exceedances)

    return {
        'threshold': threshold,
        'n_exceedances': len(exceedances),
        'shape_xi': shape,      # ξ>0 fat tail, ξ=0 exponential tail, ξ<0 bounded tail
        'scale_sigma': scale,
        'tail_type': 'fat tail (dangerous)' if shape > 0 else 'thin tail (safer)',
    }

Tail-Risk Metrics

MetricCalculationMeaning
Kurtosisreturns.kurtosis()>3 indicates fat tails; China A-shares are often in the 4-8 range
Skewnessreturns.skew()<0 means left-skewed (large drops are more common than large rallies)
Tail ratioworst 5% / best 5%>1 means larger downside risk
Hill estimatorTail indexα<2 implies extremely fat tails

Analysis Framework

Input Requirements

Required:
- Return series (daily or higher frequency) or net-value series
- Portfolio weights (if it is a portfolio)

Optional:
- Benchmark returns (for relative risk analysis)
- Risk budget / constraint settings

Analysis Steps

  1. Data preprocessing: compute returns, check missing values, and handle outliers
  2. Descriptive statistics: mean / volatility / skewness / kurtosis / maximum drawdown
  3. VaR/CVaR calculation: compare three methods at both 95% and 99% confidence levels
  4. Monte Carlo simulation: 10,000 paths, output distribution statistics and VaR
  5. Stress testing: at least 3 historical scenarios + 2 hypothetical scenarios
  6. Tail analysis: fit GPD and determine tail type
  7. Risk-control recommendations: provide concrete recommendations based on the results

Output Format

## Risk Analysis Report

### Core Risk Metrics
| Metric | Value |
|------|-----|
| Daily volatility | 1.85% |
| Annualized volatility | 29.3% |
| Maximum drawdown | -32.5% (2024.09.15 → 2024.11.20) |
| VaR(95%, 1D) | -2.8% |
| CVaR(95%, 1D) | -4.2% |
| Skewness | -0.45 |
| Kurtosis | 5.2 (fat tail) |

### Stress-Test Results
| Scenario | Portfolio Loss | Stop Triggered |
|------|---------|----------|
| 2020 COVID replay | -18.5% | No |
| Rates +100bp | -12.3% | No |
| Liquidity dry-up | -28.7% | Yes |

### Monte Carlo Simulation (252 days, 10000 paths)
| Statistic | Value |
|------|-----|
| Expected return | +8.2% |
| Loss probability | 35% |
| Worst 5% scenario | -22.4% |

### Risk-Control Recommendations
1. Recommend setting a portfolio stop-loss at -15%
2. Tail risk is elevated; consider allocating 5% to gold as a hedge
3. Correlations rise in stressed markets, so diversification benefits will be discounted

Notes

  1. VaR is not the maximum loss: VaR only says "with 95% probability, losses will not exceed X"; the remaining 5% can be far worse
  2. Normality assumption is dangerous: financial returns are almost always fat-tailed, so parametric VaR underestimates risk
  3. History does not equal the future: historical simulation fails when structural breaks occur (for example, the first negative oil price)
  4. Correlation is unstable: correlation matrices observed in normal markets can collapse in crises (correlations trend toward 1)
  5. Monte Carlo seed: set a random seed for reproducibility, and use at least 10,000 paths for stability
  6. Holding-period scaling: the square-root-of-time rule only applies under i.i.d. returns; it becomes inaccurate under autocorrelation
  7. Risk in backtests: metrics.csv already includes max_drawdown and sharpe; this skill provides deeper analysis
Repository
HKUDS/Vibe-Trading
Last updated
First committed

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.