CUSUM event filter

q.label.cusum_filter emits event timestamps when cumulative positive or negative price changes cross a threshold. A volatility-scaled threshold reduces routine sampling during quiet periods and increases it during volatile periods.

The filter uses only changes observed through each event timestamp. It discovers candidate events; it does not assign future-aware outcomes.

import pandas as pd
import plotly.graph_objects as go

import qrt as q

close = q.data.datasets.load("spy")["close"]
close = close.loc[close.index.max() - pd.DateOffset(years=3) :]
volatility = close.pct_change(fill_method=None).ewm(span=20, min_periods=20).std()
threshold = volatility.mul(0.5).where(volatility.gt(0))
events = q.label.cusum_filter(close, threshold)
pd.DataFrame({"close": close.reindex(events), "threshold": threshold.reindex(events)}).tail()
close threshold
datetime
2026-07-15 754.809998 0.003828
2026-07-16 750.719971 0.003775
2026-07-17 743.289978 0.003922
2026-07-21 748.280029 0.003796
2026-07-23 738.179993 0.003923

Inspect detected events

Markers show the observations selected for downstream labeling. events is an index drawn directly from the price index, so it can be passed to fixed_horizon, triple_barrier, or trend_scanning.

figure = go.Figure()
figure.add_trace(go.Scatter(x=close.index, y=close, name="SPY close"))
figure.add_trace(go.Scatter(x=events, y=close.reindex(events), name="CUSUM event", mode="markers", marker={"size": 5, "color": "#d04a35"}))
figure.update_layout(title="SPY volatility-scaled CUSUM events", yaxis_title="Close", hovermode="x unified")
figure.show(renderer="notebook_connected")
Back to top