QuantJourney Backtester

QuantJourney Backtester

Share product feedback

Thank you.

Your note is now in the QuantJourney inbox.

backtester/portfolio/calc/risk.py

risk.py:
Column-wise risk metrics from the same return matrix.

This file turns the return contract into reportable risk: volatility, drawdowns, historical VaR/CVaR, downside metrics and risk-adjusted ratios. It is intentionally functional, so the same calculations can be reused in reports, notebooks and strategy pages.

Import from backtester.portfolio.calc import risk

When To Read This

  • 01
    You need to know exactly how Sharpe, Sortino, Calmar, Omega or drawdown are computed.
  • 02
    You are adding a report metric and want it to match the Python API.
  • 03
    You are comparing strategy columns and benchmark columns with the same risk definition.

File Anatomy

  • Path risk: drawdown paths, max drawdown, drawdown duration and conditional drawdown at risk.
  • Distribution risk: volatility, historical VaR, CVaR and expected shortfall.
  • Risk-adjusted ratios: Sharpe, Sortino, Calmar, Omega, serenity, gain-to-pain and smart ratios.
  • Compatibility hooks: selected metrics delegate to backtester.portfolio._compat wrappers.

Data Contract

Inputs

  • returns: simple period returns, DataFrame with dates x instruments or strategy columns.
  • benchmark_returns: Series for information ratio when benchmark-relative risk is required.
  • risk_free_rate, target_return, confidence and days_per_year parameters.

Outputs

  • pd.DataFrame paths for volatility and drawdowns.
  • pd.Series per return column for most scalar metrics.
  • float for aggregate conditional drawdown at risk.

Invariants

  • Drawdowns are computed from cumulative simple-return NAV: (1 + returns).cumprod().
  • VaR/CVaR are historical quantile calculations, not parametric model estimates.
  • Zero denominators are converted to NaN where a ratio would otherwise be misleading.

Public API And Key Internals

compute_drawdowns

function
compute_drawdowns(returns)

Builds underwater paths from cumulative NAV.

Returns

pd.DataFrame with negative or zero drawdown values.

compute_max_drawdown

function
compute_max_drawdown(returns)

Returns the minimum drawdown per column.

Returns

pd.Series.

compute_var / compute_cvar / compute_expected_shortfall

function
compute_cvar(returns, confidence=0.95)

Computes historical tail threshold and average loss beyond that threshold.

Returns

pd.Series.

sharpe_ratio / sortino_ratio / information_ratio

function
sharpe_ratio(returns, *, risk_free_rate=0.0, days_per_year=252, annualize=True)

Risk-adjusted return metrics with daily risk-free conversion.

Returns

pd.Series.

smart_sharpe_ratio / smart_sortino_ratio / smart_calmar_ratio

function
smart_sharpe_ratio(returns, *, risk_free_rate=0.0, days_per_year=252, trim_frac=0.02)

Trimmed variants intended to reduce sensitivity to extreme observations.

Returns

pd.Series.

sampled_volatility

function
sampled_volatility(returns, *, freq_vol="M", freq_return=None, days_per_year=252)

Resamples volatility observations for monthly or quarterly review tables.

Returns

pd.DataFrame.

Implementation Notes

  • The API is column-wise: a single call can evaluate strategy, benchmark and variants side by side.
  • Calmar and smart Calmar import annualized returns from returns.py, so report CAGR and Calmar stay consistent.
  • The module includes both common metrics and less common pain metrics because PDF tear-sheets need more than Sharpe.

Code Walkthrough

Build a risk packet for a report section

The same returns DataFrame feeds every metric. That makes comparison tables mechanically consistent.

risk_usage.py Python
from backtester.portfolio.calc import risk

risk_packet = {
    "max_drawdown": risk.compute_max_drawdown(strategy_returns),
    "var_95": risk.compute_var(strategy_returns, confidence=0.95),
    "cvar_95": risk.compute_cvar(strategy_returns, confidence=0.95),
    "sharpe": risk.sharpe_ratio(strategy_returns, risk_free_rate=0.02),
    "sortino": risk.sortino_ratio(strategy_returns, risk_free_rate=0.02),
    "omega": risk.omega_ratio(strategy_returns),
}

Key implementation: drawdown path and max drawdown

This is the base path-risk calculation reused by other risk functions.

backtester/portfolio/calc/risk.py Python
def compute_drawdowns(returns: pd.DataFrame) -> pd.DataFrame:
    nav = (1 + returns).cumprod()
    peak = nav.cummax()
    return (nav - peak) / peak


def compute_max_drawdown(returns: pd.DataFrame) -> pd.Series:
    dd = compute_drawdowns(returns)
    return dd.min()

Key implementation: trimmed smart Sharpe

The smart variant trims stacked returns before estimating mean and standard deviation.

backtester/portfolio/calc/risk.py Python
ex = excess.stack().sort_values()
n = len(ex)
k = int(n * trim_frac)
ex_t = ex.iloc[k : n - k] if n - 2 * k > 0 else ex
ex_df = ex_t.unstack()
mu = ex_df.mean()
sd = ex_df.std().replace(0.0, np.nan)
return mu / sd * np.sqrt(days_per_year)