Development of VaR (Value at Risk) System for Crypto Portfolio
Value at Risk (VaR) is a statistical estimate of maximum portfolio loss over a specific time horizon with given probability. "VaR 95% on 1 day = $5,000" means: with 95% probability daily loss will not exceed $5,000 (and with 5% probability it will).
VaR Calculation Methods
Historical Simulation VaR — most common and intuitive:
import numpy as np
import pandas as pd
from scipy import stats
class HistoricalVaR:
def __init__(self, confidence_level=0.95, lookback_days=252):
self.confidence = confidence_level
self.lookback = lookback_days
def calculate(self, portfolio_value, positions, price_history):
"""
portfolio_value: current portfolio value
positions: {symbol: qty}
price_history: DataFrame with prices for lookback_days
"""
# Calculate historical portfolio returns
portfolio_returns = []
for i in range(1, len(price_history)):
daily_pnl = 0
for symbol, qty in positions.items():
if symbol in price_history.columns:
prev_price = price_history[symbol].iloc[i-1]
curr_price = price_history[symbol].iloc[i]
daily_pnl += qty * (curr_price - prev_price)
portfolio_returns.append(daily_pnl / portfolio_value)
portfolio_returns = np.array(portfolio_returns)
# VaR = percentile of losses
var_pct = np.percentile(portfolio_returns, (1 - self.confidence) * 100)
var_usd = abs(var_pct) * portfolio_value
return {
'var_pct': var_pct,
'var_usd': var_usd,
'confidence': self.confidence,
'horizon_days': 1
}
Parametric (Variance-Covariance) VaR:
def parametric_var(positions, prices, cov_matrix, confidence=0.95, horizon=1):
weights = np.array([positions[s] * prices[s] for s in positions.keys()])
portfolio_value = weights.sum()
weights_pct = weights / portfolio_value
# Portfolio variance
portfolio_variance = weights_pct @ cov_matrix @ weights_pct
portfolio_std = np.sqrt(portfolio_variance * horizon)
# Normal distribution: z-score for confidence level
z_score = stats.norm.ppf(1 - confidence)
var_pct = z_score * portfolio_std
var_usd = abs(var_pct) * portfolio_value
return var_usd
Monte Carlo VaR — most accurate for crypto portfolios (accounts for fat tails):
def monte_carlo_var(portfolio_value, returns_history, n_simulations=10000,
confidence=0.95, horizon=1):
mean = returns_history.mean()
std = returns_history.std()
# Simulate N scenarios
simulated_returns = np.random.normal(mean, std, (n_simulations, horizon))
simulated_pnl = portfolio_value * simulated_returns.sum(axis=1)
var = np.percentile(simulated_pnl, (1 - confidence) * 100)
return abs(var)
CVaR (Conditional Value at Risk / Expected Shortfall)
CVaR is average loss in worst (1 - confidence)% scenarios. Better than VaR for describing tail risk:
def calculate_cvar(returns, portfolio_value, confidence=0.95):
var_threshold = np.percentile(returns, (1 - confidence) * 100)
tail_returns = returns[returns <= var_threshold]
cvar_pct = tail_returns.mean()
return abs(cvar_pct) * portfolio_value
Fat Tails Problem in Crypto
Crypto market has significantly heavier "tails" of return distribution compared to normal distribution. Parametric VaR based on normal distribution significantly underestimates risk.
Solutions:
Student's t-distribution: better describes fat tails. Add degrees of freedom parameter:
from scipy.stats import t
def t_distribution_var(mean, std, df, confidence=0.95):
t_score = t.ppf(1 - confidence, df)
return abs(mean + std * t_score)
Historical distribution + EVT (Extreme Value Theory): for tails use generalized Pareto distribution.
GARCH-VaR: accounts for volatility clustering — high volatility periods follow high volatility.
Backtesting VaR (Kupiec Test)
Check: how often did actual losses exceed predicted VaR?
def kupiec_test(var_predictions, actual_returns, confidence=0.95):
"""
If VaR 95% — violations should occur in 5% of cases
"""
violations = actual_returns < -var_predictions
n_violations = violations.sum()
n_total = len(actual_returns)
expected_violations = n_total * (1 - confidence)
# Binomial test
p_value = stats.binom_test(n_violations, n_total, 1 - confidence)
return {
'n_violations': n_violations,
'expected_violations': expected_violations,
'violation_rate': n_violations / n_total,
'p_value': p_value,
'model_valid': p_value > 0.05
}
Multi-horizon VaR
VaR scales with horizon by square root of time (for normal distribution):
VaR(n days) = VaR(1 day) × √n
For crypto portfolio calculate VaR for several horizons:
| Horizon | Application |
|---|---|
| 1 day | Daily risk monitoring |
| 7 days | Weekly report |
| 30 days | Stress testing |
Component VaR
Marginal VaR: contribution of each position to overall portfolio VaR. Identifies positions adding most risk.
def component_var(positions, cov_matrix, portfolio_var):
"""Contribution of each position to overall VaR"""
weights = np.array(list(positions.values()))
weights_pct = weights / weights.sum()
# Marginal contribution
marginal = cov_matrix @ weights_pct / portfolio_var
component = weights_pct * marginal
return dict(zip(positions.keys(), component))
Dashboard and Reporting
Realtime VaR monitor: recalculation on each position change or weekly with rolling covariance.
Daily VaR Report:
- Current 1-day VaR (95% and 99%)
- CVaR (Expected Shortfall)
- VaR breakdown by assets
- Backtesting: violations for last 30 days
Alerts: if current VaR exceeds limit (e.g., VaR 99% > 5% of capital) → Telegram alert.
We develop VaR system with historical, parametric and Monte Carlo methods, fat-tail correction, backtesting module (Kupiec test) and daily reporting.







