Quant Memo
Core

Rolling and Expanding Windows

A rolling window recomputes a statistic over the last N observations as new data arrives; an expanding window recomputes it over everything seen so far. Picking the wrong one silently changes what your backtest is measuring.

Prerequisites: NumPy Arrays and Dtypes

A 20-day moving average needs to answer the same question every single day: "what happened over the last 20 days?" As one new day arrives, the oldest day falls off the back. That sliding behavior is a rolling window. An expanding window never drops anything — day 500's average is computed over all 500 days that came before it, not just the last 20.

Both show up constantly in quant code: rolling windows compute moving averages, rolling volatility, and rolling Sharpe ratios; expanding windows compute running statistics like a strategy's cumulative max drawdown or an out-of-sample model that should only ever "know" the past.

Rolling = fixed-size window that slides forward, always the last N points. Expanding = growing window that starts small and accumulates everything up to today. The choice is not cosmetic — it decides whether your statistic reacts to recent regime changes or averages over the entire history.

What each one computes

For a rolling window of size NN, the value at time tt is a function of observations tN+1t-N+1 through tt only. Slide forward one step, and the window's leading edge drops the oldest point while gaining one new point. For an expanding window, the value at time tt is a function of every observation from the start of the series through tt — the window only ever grows.

rolling (N=4) window slides, always width 4 expanding window only grows, includes everything so far
The rolling window keeps a constant width as it moves; the expanding window's left edge never moves.

Worked example: rolling mean by hand

Daily returns (in %) for a stock: 1, -2, 3, 0, 4, -1. Compute a 3-day rolling mean.

  1. Day 3 (first full window): average of days 1-3 = (12+3)/3=0.67(1 - 2 + 3)/3 = 0.67.
  2. Day 4: window slides to days 2-4 = (2+3+0)/3=0.33(-2 + 3 + 0)/3 = 0.33. Day 1 has dropped out entirely.
  3. Day 5: window is days 3-5 = (3+0+4)/3=2.33(3 + 0 + 4)/3 = 2.33.
  4. Day 6: window is days 4-6 = (0+41)/3=1.0(0 + 4 - 1)/3 = 1.0.

Compare to the expanding mean at the same points: day 4 would average all four days (12+3+0)/4=0.51-2+3+0)/4 = 0.5, and day 6 would average all six (12+3+0+41)/6=0.831-2+3+0+4-1)/6 = 0.83. The expanding value moves much less per step because each new point is diluted by a growing denominator — that damping is the whole difference in behavior between the two.

In pandas this is df["ret"].rolling(3).mean() versus df["ret"].expanding().mean(). Both return NaN until enough data exists to fill the first window — the rolling version needs its first N1N-1 rows to warm up, the expanding version only needs min_periods (default 1).

What this means in practice

Rolling windows are the right tool when you want a statistic to track current conditions — a 20-day realized volatility should reflect this month's market, not 2019's. Expanding windows are the right tool when leaking future information would be a bug: an expanding Sharpe ratio computed only from data available up to each date is a legitimate point-in-time metric, while a rolling Sharpe computed with a centered window secretly uses future returns.

The most common bug is an off-by-one in what counts as "available." A rolling window with center=True in pandas uses future observations to fill the middle of the window — fine for smoothing a chart, disastrous for a backtest, because it means day tt's statistic depends on days after tt. Always default to a trailing (non-centered) window in anything that feeds a trading decision.

Related concepts

Practice in interviews

Further reading

  • McKinney, Python for Data Analysis (ch. 11, 'Time Series')
ShareTwitterLinkedIn