Fixed-horizon labels

q.label.fixed_horizon measures the return at one future horizon. Returns above the positive threshold receive label 1, returns below the negative threshold receive -1, and observations inside the neutral band receive 0.

The threshold may be scalar or event-specific. This example labels volatility-scaled CUSUM events after 10 trading observations.

import pandas as pd

import qrt as q

close = q.data.datasets.load("spy")["close"]
close = close.loc[close.index.max() - pd.DateOffset(years=5) :]
volatility = close.pct_change(fill_method=None).ewm(span=20, min_periods=20).std()
events = q.label.cusum_filter(close, volatility.mul(0.5).where(volatility.gt(0)))
labels = q.label.fixed_horizon(
    close,
    10,
    events=events,
    threshold=volatility.mul(0.5),
)
labels.tail()
end_time return threshold label
event_time
2026-06-29 2026-07-14 0.014615 0.005370 1
2026-06-30 2026-07-15 0.010766 0.005229 1
2026-07-06 2026-07-20 -0.012232 0.004683 -1
2026-07-07 2026-07-21 0.000762 0.004551 0
2026-07-09 2026-07-23 -0.017999 0.004327 -1

Inspect class balance

The output retains end_time, realized return, and the event-time threshold. Keep event_time and end_time when constructing validation folds so overlapping outcomes can be purged.

labels.groupby("label", observed=True).agg(
    observations=("label", "size"),
    average_return=("return", "mean"),
    average_threshold=("threshold", "mean"),
)
observations average_return average_threshold
label
-1 253 -0.028999 0.004834
0 92 -0.000474 0.005226
1 438 0.026861 0.004982

Visualize the labels

The marker direction and color encode the class at each event. Shaded intervals show the future window used to compute each outcome; overlapping bands make dependence between nearby labels visible.

chart_start = close.index.max() - pd.DateOffset(years=1)
figure = q.plot.labels(
    close.loc[chart_start:],
    labels.loc[chart_start:],
    title="SPY fixed-horizon labels (latest year)",
)
figure.show(renderer="notebook_connected")
Back to top