Skip to content

markowitz.backtest.result

markowitz.backtest.result

Container for walk-forward backtest output.

BacktestResult(returns_gross: pd.DataFrame, returns_net: pd.DataFrame, weights: dict[str, pd.DataFrame], turnover: pd.DataFrame, rebalance_dates: list[pd.Timestamp] = list()) dataclass

Aggregated output of a :class:WalkForward run.

Attributes:

Name Type Description
returns_gross DataFrame

Per-period gross returns, one column per strategy.

returns_net DataFrame

Per-period net-of-cost returns.

weights dict[str, DataFrame]

Mapping strategy_name -> DataFrame of post-rebalance weights.

turnover DataFrame

Per-period turnover, one column per strategy.

rebalance_dates list[Timestamp]

Sorted list of dates on which weights were recomputed.

summary(*, ann: int = 12, gamma: float = 5.0) -> pd.DataFrame

Standard performance grid (Sharpe, Sortino, MDD, Calmar, CEQ, TO).

Source code in src/markowitz/backtest/result.py
def summary(self, *, ann: int = 12, gamma: float = 5.0) -> pd.DataFrame:
    """Standard performance grid (Sharpe, Sortino, MDD, Calmar, CEQ, TO)."""
    rows: list[dict[str, float]] = []
    for col in self.returns_net.columns:
        r = self.returns_net[col].dropna()
        rows.append(
            {
                "Sharpe": _stats.sharpe_ratio(r, ann=ann),
                "Sortino": _stats.sortino_ratio(r, ann=ann),
                "MaxDD": _stats.max_drawdown(r),
                "Calmar": _stats.calmar(r, ann=ann),
                "CEQ": _stats.ceq(r, gamma=gamma),
                "Turnover": float(self.turnover[col].mean()) if col in self.turnover else 0.0,
            }
        )
    return pd.DataFrame(rows, index=list(self.returns_net.columns))