Risk Metrics¶
qlcore provides essential risk metrics for evaluating trading performance and managing risk.
Performance Metrics¶
Sharpe Ratio¶
Risk-adjusted return relative to a risk-free rate:
import qlcore as qc
returns = [0.02, -0.01, 0.03, -0.02, 0.015, 0.01, -0.005]
sharpe = qc.sharpe_ratio(
returns,
risk_free_rate=0.0, # Risk-free rate (annualized)
periods_per_year=356, # Trading days (default)
)
print(f"Sharpe Ratio: {sharpe:.2f}")
Interpreting Sharpe
< 1.0— Subpar risk-adjusted returns1.0 - 2.0— Good2.0 - 3.0— Very good> 3.0— Excellent (verify it's not overfitting)
Sortino Ratio¶
Like Sharpe, but only penalizes downside volatility:
sortino = qc.sortino_ratio(
returns,
risk_free_rate=0.0,
periods_per_year=356,
)
print(f"Sortino Ratio: {sortino:.2f}")
Sortino is often preferred because it doesn't penalize upside volatility.
Drawdown Analysis¶
Maximum Drawdown¶
Largest peak-to-trough decline:
# From returns
mdd = qc.max_drawdown(returns)
print(f"Max Drawdown: {mdd:.2%}") # e.g., -15.3%
# From equity curve
equity = [10000, 10200, 10050, 9800, 10100, 9600, 10300]
mdd = qc.max_drawdown(equity)
Value at Risk (VaR)¶
Historical VaR¶
Estimate potential loss at a confidence level:
# 95% VaR
var_95 = qc.historical_var(returns, confidence=0.95)
print(f"VaR (95%): {var_95:.2%}")
# 99% VaR
var_99 = qc.historical_var(returns, confidence=0.99)
print(f"VaR (99%): {var_99:.2%}")
Interpretation
VaR(95%) = -2.1% means: "There's a 5% chance of losing more than 2.1% in a single period."
Exposure Metrics¶
Net Exposure¶
Directional exposure (longs minus shorts):
from decimal import Decimal
positions = {
"BTC-USDT-PERP": Decimal("45000"), # Long $45k
"ETH-USDT-PERP": Decimal("-20000"), # Short $20k
}
net = qc.net_exposure(positions)
print(f"Net Exposure: ${net}") # $25,000 net long
Gross Exposure¶
Total absolute exposure (longs plus shorts):
Liquidation Prices¶
Isolated Margin¶
Calculate liquidation price for isolated positions:
from decimal import Decimal
liq_price = qc.calculate_isolated_liquidation_price(
entry_price=Decimal("45000"),
position_side=qc.PositionSide.LONG,
leverage=10,
maintenance_margin_rate=Decimal("0.005"), # 0.5%
)
print(f"Liquidation Price: ${liq_price}")
Cross Margin¶
Calculate liquidation price using account balance:
liq_price = qc.calculate_cross_liquidation_price(
entry_price=Decimal("45000"),
position_side=qc.PositionSide.LONG,
position_size=Decimal("1.0"),
account_balance=Decimal("5000"),
maintenance_margin_rate=Decimal("0.005"),
)
print(f"Liquidation Price: ${liq_price}")
Complete Risk Report Example¶
import qlcore as qc
# Sample returns (daily)
returns = [0.02, -0.01, 0.03, -0.02, 0.015, 0.01, -0.005,
0.025, -0.015, 0.02, -0.01, 0.018]
# Metrics
sharpe = qc.sharpe_ratio(returns, risk_free_rate=0.0)
sortino = qc.sortino_ratio(returns, risk_free_rate=0.0)
mdd = qc.max_drawdown(returns)
var_95 = qc.historical_var(returns, confidence=0.95)
var_99 = qc.historical_var(returns, confidence=0.99)
# Report
print("=" * 40)
print("Risk Report")
print("=" * 40)
print(f"Sharpe Ratio: {sharpe:>10.2f}")
print(f"Sortino Ratio: {sortino:>10.2f}")
print(f"Max Drawdown: {mdd:>10.2%}")
print(f"VaR (95%): {var_95:>10.2%}")
print(f"VaR (99%): {var_99:>10.2%}")
print("=" * 40)
Using Namespace¶
import qlcore as qc
# Access via risk namespace
qc.risk.sharpe_ratio(returns)
qc.risk.sortino_ratio(returns)
qc.risk.max_drawdown(returns)
qc.risk.historical_var(returns, 0.95)
qc.risk.net_exposure(positions)
qc.risk.gross_exposure(positions)
qc.risk.calculate_isolated_liquidation_price(...)
qc.risk.calculate_cross_liquidation_price(...)