Portfolio Performance Measurement › Rolling Performance Analysis

Michael BrenndoerferDecember 16, 20254 min read
Quantitative FinanceAll chapters
Part 9 of 15Portfolio Theory and Investment Analysis
Chapter 49Portfolio Performance Measurement › Rolling Performance Analysis
Reading section 7 of 11 · Rolling Performance Analysis · 4 minSection 7 of 11 · 4 min

Book contents

Section 7 of 11 · 4 min read

Portfolio Performance Measurement › Rolling Performance AnalysisOne section · Sections in this chapter7 / 11 ↓
Reading tools Definitions & inline links

Choose your expertise level to adjust how many terms are explained. Beginners see more tooltips, experts see fewer to maintain reading flow. Hover over underlined terms for instant definitions.

Article links

Make inline references clickable

Static performance metrics over the full period can mask time-varying behavior. A manager might have excellent performance in one regime but poor performance in another. Rolling analysis captures this dynamic by computing metrics over moving windows.

In[29]:
Code
def rolling_sharpe(returns, risk_free_rate, window, periods_per_year=252):
    """Calculate rolling Sharpe ratio."""
    rolling_mean = pd.Series(returns).rolling(window).mean()
    rolling_std = pd.Series(returns).rolling(window).std()
    rolling_sr = (
        (rolling_mean - risk_free_rate)
        / rolling_std
        * np.sqrt(periods_per_year)
    )
    return rolling_sr.values


def rolling_max_drawdown(returns, window):
    """Calculate rolling maximum drawdown."""
    cumulative = (1 + pd.Series(returns)).cumprod()
    rolling_max = cumulative.rolling(window, min_periods=1).max()
    drawdown = (rolling_max - cumulative) / rolling_max
    rolling_max_dd = drawdown.rolling(window).max()
    return rolling_max_dd.values
In[30]:
Code
# Calculate rolling metrics with 252-day (1 year) window
window = 252

df["rolling_sharpe_portfolio"] = rolling_sharpe(
    df["portfolio"].values, daily_rf, window
)
df["rolling_sharpe_benchmark"] = rolling_sharpe(
    df["benchmark"].values, daily_rf, window
)
df["rolling_maxdd_portfolio"] = rolling_max_drawdown(
    df["portfolio"].values, window
)
df["rolling_maxdd_benchmark"] = rolling_max_drawdown(
    df["benchmark"].values, window
)
Out[31]:
Visualization
Time series of rolling 1-year Sharpe ratio from 2020 to 2024 for portfolio and benchmark. Both lines fluctuate around zero, with a horizontal dashed line at zero for reference.
Rolling 1-year Sharpe ratio for portfolio and benchmark. The metric fluctuates significantly over time, revealing periods of outperformance and underperformance that full-period statistics smooth over.
Time series of rolling 1-year maximum drawdown (as negative percentages) from 2020 to 2024 for portfolio and benchmark. Deeper troughs indicate periods of elevated tail risk.
Rolling 1-year maximum drawdown for portfolio and benchmark. This view highlights the changing tail risk profile of the strategies over the evaluation period.

Rolling analysis reveals that performance metrics are not constants; they fluctuate with market conditions. A manager with an impressive full-period Sharpe ratio might have experienced extended periods of negative Sharpe, which rolling analysis would reveal.

Out[32]:
Visualization
Histogram of rolling 1-year Sharpe ratios visually grouped into performance zones: negative, 0–0.5, 0.5–1.0, and above 1.0. A dashed vertical line marks the full-period Sharpe ratio. An annotation box shows the median Sharpe ratio and percentage of periods below zero.
Histogram of rolling 1-year Sharpe ratios for the portfolio. The distribution reveals significant variability around the median, with a substantial portion of periods showing negative risk-adjusted returns despite the positive full-period Sharpe ratio (dashed line).