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.
from backtester.portfolio.calc import rolling_stats When To Read This
- 01You want to explain why the backtester can run fast local research loops.
- 02You need rolling beta, rolling Sharpe or rolling max drawdown in a report or validation page.
- 03You 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
functionrolling_mean(df, window) Thin wrapper around pandas rolling mean for aligned DataFrame inputs.
Returns
pd.DataFrame.
rolling_volatility
functionrolling_volatility(returns, window) Rolling standard deviation per column.
Returns
pd.DataFrame.
rolling_sharpe_ratio
functionrolling_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
functionrolling_max_drawdown(prices, window) Rolling max drawdown over a price or NAV matrix.
Returns
pd.DataFrame.
rolling_beta / rolling_alpha
functionrolling_beta(returns, benchmark, window) Windowed beta and alpha versus an aligned benchmark.
Returns
pd.DataFrame.
rolling_calmar_ratio / rolling_correlation
functionrolling_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.
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.
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.
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