Trend-scanning labels

q.label.trend_scanning fits forward linear regressions over several candidate bar horizons. For each event it keeps the slope with the largest absolute t-value and assigns a directional label when that statistic exceeds min_t_value.

A horizon of 5 means five bars after the event and therefore fits six prices including the event itself.

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.75).where(volatility.gt(0)))
events = events[::5]
trends = q.label.trend_scanning(
    close,
    events=events,
    horizons=range(5, 21),
    min_t_value=2.0,
)
trends.tail()
end_time horizon slope t_value return label
event_time
2026-05-06 2026-06-04 20 0.001713 8.549745 0.031697 1
2026-05-19 2026-06-02 9 0.003638 13.396100 0.035217 1
2026-06-01 2026-06-10 7 -0.006540 -6.147072 -0.043650 -1
2026-06-15 2026-06-26 8 -0.003661 -5.524626 -0.031745 -1
2026-07-06 2026-07-23 13 -0.000783 -2.041907 -0.017437 -1

Selected horizons

The chosen horizon, slope, t_value, and realized return make the label auditable. end_time is the outcome boundary needed for leakage-aware splitting.

trends.groupby("label", observed=True).agg(
    observations=("label", "size"),
    median_horizon=("horizon", "median"),
    median_t_value=("t_value", "median"),
    average_return=("return", "mean"),
)
observations median_horizon median_t_value average_return
label
-1 46 13.0 -6.382815 -0.048026
0 1 6.0 1.269346 0.003524
1 78 16.0 7.181948 0.040679
Back to top