QuantJourney Backtester

QuantJourney Backtester

Share product feedback

Thank you.

Your note is now in the QuantJourney inbox.

backtester/portfolio/calc/rolling_stats.py

rolling_stats.py:
Rolling evidence with optional NumPy/Numba kernels.

This file generates rolling mean, volatility, Sharpe, drawdown, beta, alpha, Calmar and correlation series. It keeps a pandas API while using NumPy arrays internally for the heavier rolling calculations.

Import from backtester.portfolio.calc import rolling_stats

When To Read This

  • 01
    You want to explain why the backtester can run fast local research loops.
  • 02
    You need rolling beta, rolling Sharpe or rolling max drawdown in a report or validation page.
  • 03
    You are deciding where to add another windowed statistic.

File Anatomy

  • Environment switch: QJ_USE_NUMBA enables cached numba kernels when numba is available.
  • Internal kernels: NumPy fallback implementations for Sharpe, max drawdown and beta.
  • Public API: pandas DataFrame functions that preserve index and columns.
  • Composite metrics: rolling alpha and rolling Calmar reuse beta and drawdown kernels.

Data Contract

Inputs

  • returns: DataFrame of simple returns for rolling risk and beta calculations.
  • prices or cumulative NAV: DataFrame for rolling max drawdown.
  • benchmark: Series aligned to returns index for beta and alpha.

Outputs

  • DataFrames with the same index and columns as the input return matrix.
  • Rolling correlation DataFrame from pandas rolling corr for pairwise review.
  • NaN warmup rows before each window has enough observations.

Invariants

  • Public functions return pandas objects even when calculations are performed on NumPy arrays.
  • Benchmark series is reindexed to the returns index before beta and alpha calculation.
  • Numba acceleration is opt-in via QJ_USE_NUMBA, so local installs remain simple.

Public API And Key Internals

rolling_mean

function
rolling_mean(df, window)

Thin wrapper around pandas rolling mean for aligned DataFrame inputs.

Returns

pd.DataFrame.

rolling_volatility

function
rolling_volatility(returns, window)

Rolling standard deviation per column.

Returns

pd.DataFrame.

rolling_sharpe_ratio

function
rolling_sharpe_ratio(returns, *, risk_free_rate=0.02, window, days_per_year=252)

Windowed Sharpe ratio using NumPy or numba kernel.

Returns

pd.DataFrame.

rolling_max_drawdown

function
rolling_max_drawdown(prices, window)

Rolling max drawdown over a price or NAV matrix.

Returns

pd.DataFrame.

rolling_beta / rolling_alpha

function
rolling_beta(returns, benchmark, window)

Windowed beta and alpha versus an aligned benchmark.

Returns

pd.DataFrame.

rolling_calmar_ratio / rolling_correlation

function
rolling_calmar_ratio(returns, *, window=252, days_per_year=252)

Windowed Calmar ratio and pairwise rolling correlation.

Returns

pd.DataFrame.

Implementation Notes

  • The file is a concrete example of DataFrame/NumPy-first design: inputs stay labeled, inner loops run on arrays, outputs regain labels.
  • The numba kernels are cached and optional. If numba is unavailable, the NumPy fallback keeps behavior available.
  • Rolling drawdown expects a path-like matrix, not raw returns. Rolling Calmar constructs cumulative returns first.

Code Walkthrough

Use rolling evidence in a validation report

A single returns matrix can produce rolling Sharpe, beta and Calmar evidence for multiple strategy variants.

rolling_stats_usage.py Python
from backtester.portfolio.calc import rolling_stats

rolling_sharpe = rolling_stats.rolling_sharpe_ratio(
    returns=strategy_returns,
    window=252,
    risk_free_rate=0.02,
)
rolling_beta = rolling_stats.rolling_beta(
    returns=strategy_returns,
    benchmark=benchmark_returns,
    window=252,
)
rolling_calmar = rolling_stats.rolling_calmar_ratio(
    strategy_returns,
    window=252,
)

Key implementation: optional acceleration boundary

The function always returns a labeled DataFrame; only the internal kernel changes.

backtester/portfolio/calc/rolling_stats.py Python
def rolling_sharpe_ratio(
    returns: pd.DataFrame,
    *,
    risk_free_rate: float = 0.02,
    window: int,
    days_per_year: int = 252,
) -> pd.DataFrame:
    rf_daily = risk_free_rate / float(days_per_year)
    a = returns.to_numpy(dtype=np.float64)

    if USE_NUMBA:
        out = _rolling_sharpe_numba(a, window, rf_daily)
    else:
        out = _rolling_sharpe_numpy(a, window, rf_daily)

    return pd.DataFrame(out, index=returns.index, columns=returns.columns)

Internal kernel shape

The kernel loops across columns and windows; labels are restored at the public API boundary.

backtester/portfolio/calc/rolling_stats.py Python
def _rolling_beta_numpy(
    a: np.ndarray,
    bench: np.ndarray,
    window: int,
) -> np.ndarray:
    n, m = a.shape
    out = np.full((n, m), np.nan, dtype=np.float64)
    for j in range(m):
        for i in range(window - 1, n):
            start = i - window + 1
            w_a = a[start : i + 1, j]
            w_b = bench[start : i + 1]
            mask = ~(np.isnan(w_a) | np.isnan(w_b))
            w_a = w_a[mask]
            w_b = w_b[mask]
            if len(w_a) >= 2 and np.var(w_b, ddof=1) > 0.0:
                out[i, j] = np.cov(w_a, w_b, ddof=1)[0, 1] / np.var(w_b, ddof=1)
    return out