pub fn get_weights_by_time_decay(
triple_barrier_events: &[(NaiveDateTime, NaiveDateTime, f64)],
close: &[(NaiveDateTime, f64)],
decay: f64,
) -> Result<Vec<(NaiveDateTime, f64)>, SampleWeightsError>Expand description
Sample weights by time decay (AFML §4.7, Snippet 4.11).
Each event’s average uniqueness is the mean of 1 / c_t over the bars of close in its
span (c_t = number of events containing bar t, floored at 1); an event covering no bar
has uniqueness 0. With x_i the cumulative uniqueness in start order and X its total, the
weight is max(0, a + b x_i) with b = (1 - decay) / X for decay >= 0,
b = 1 / ((decay + 1) X) for decay < 0, and a = 1 - b X, so the newest event has
weight 1. AFML’s domain is decay in (-1, 1]: 1 is no decay, 0 < decay < 1 decays
linearly toward decay, 0 toward 0, and -1 < decay < 0 zeroes the oldest -decay
fraction of cumulative uniqueness. The oldest weight approaches decay rather than
equalling it, because the line is anchored at x = 0. The
weights are not rescaled and do not include return attribution; close prices are not
used, only its timestamps.
Returns one (start, weight) pair per event, in input order. Cumulative uniqueness is
accumulated in start order; events that share a start keep their input order (a stable
sort), so each takes its own cumulative position and the later-listed one counts as newer.
§Errors
SampleWeightsError::InvalidDecayifdecayis outside(-1, 1]orNaN.SampleWeightsError::EndBeforeStartif any event’sendprecedes itsstart.
use chrono::{Duration, NaiveDate};
use openquant::sample_weights::get_weights_by_time_decay;
let t0 = NaiveDate::from_ymd_opt(2024, 1, 2).unwrap().and_hms_opt(9, 30, 0).unwrap();
let close: Vec<_> = (0..4).map(|i| (t0 + Duration::minutes(i), 100.0)).collect();
// Four non-overlapping one-bar events: x = 1, 2, 3, 4 and X = 4.
let events: Vec<_> = close.iter().map(|(t, _)| (*t, *t, 1.0)).collect();
// decay = 0: b = 1/4, a = 0.
let w = get_weights_by_time_decay(&events, &close, 0.0).unwrap();
let got: Vec<f64> = w.iter().map(|(_, w)| *w).collect();
assert_eq!(got, vec![0.25, 0.5, 0.75, 1.0]);
// decay = -0.5: b = 1/2, a = -1; the oldest half is clipped to 0.
let w = get_weights_by_time_decay(&events, &close, -0.5).unwrap();
let got: Vec<f64> = w.iter().map(|(_, w)| *w).collect();
assert_eq!(got, vec![0.0, 0.0, 0.5, 1.0]);