Skip to main content

get_weights_by_time_decay

Function get_weights_by_time_decay 

Source
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

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]);