Skip to content

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 returns
  • 1.0 - 2.0 — Good
  • 2.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):

gross = qc.gross_exposure(positions)
print(f"Gross Exposure: ${gross}")  # $65,000 total

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(...)