Skip to content
GeneratedAssembled automatically from moduleDocs.ts. No human has reviewed this page.

sample_weights

In AFML’s event-driven framework (Chapter 4), labels are derived from overlapping price paths. When two events overlap in time, their labels share information — the price observations that determine event A’s outcome also influence event B’s outcome. Treating these labels as independent samples inflates effective sample size and biases model training.

Uniqueness-based weighting addresses this by computing how unique each sample is at each time step. If a bar contributes to 3 concurrent events, each event gets 1/3 credit for that bar. The total weight of a sample is the sum of its per-bar uniqueness scores. Samples that overlap with many others get down-weighted; isolated samples get full weight.

Return-attribution weighting weights samples by their absolute return, giving more training influence to economically significant events.

Time-decay weighting applies a power-law decay so recent observations contribute more than older ones, useful when the data-generating process evolves over time.

These weights should be passed as sample_weight to your classifier or loss function.

Apply sample weights after labeling and before model training. They correct for the non-IID structure caused by overlapping triple-barrier labels.

Prerequisites: Labeled events from the labeling module, with event start/end times.

Alternatives: Equal weights (ignores overlap, biases toward dense clusters), or sequential bootstrap (sampling-based approach instead of weighting).

wi=tIt,ijIt,jw_i=\sum_t\frac{I_{t,i}}{\sum_j I_{t,j}}

wi=(iT)δw_i=(\frac{i}{T})^\delta

ParameterTypeDescriptionDefault
deltaf64Time-decay exponent; 0 = uniform, 1 = linear decay, >1 = aggressive recency bias1.0

Compute sample weights for overlapping labels

Section titled “Compute sample weights for overlapping labels”
from openquant._core import sample_weights
# Both functions weight EVENTS, not raw returns: an event is
# (t_in, t_out, label) and the return is attributed over the close series
# between those two timestamps. Timestamps parse as "%Y-%m-%d %H:%M:%S".
close_timestamps = [f"2024-01-02 09:3{i}:00" for i in range(8)]
close_prices = [100.0, 100.1, 99.9, 100.2, 100.05, 100.3, 99.7, 100.1]
events = [
(close_timestamps[0], close_timestamps[3], 1.0),
(close_timestamps[2], close_timestamps[5], -1.0),
(close_timestamps[4], close_timestamps[7], 1.0),
]
# Weight by uniqueness-adjusted return attribution
w_return = sample_weights.get_weights_by_return(events, close_timestamps, close_prices)
# Weight by time decay (oldest event decayed to 0.5 of the newest)
w_decay = sample_weights.get_weights_by_time_decay(events, close_timestamps, close_prices, 0.5)
# Each is a list of (event_timestamp, weight) pairs:
# model.fit(X, y, sample_weight=[w for _, w in w_return])
use chrono::{Duration, NaiveDateTime};
use openquant::sample_weights::get_weights_by_time_decay;
let t0 = NaiveDateTime::parse_from_str("2024-01-02 00:00:00", "%Y-%m-%d %H:%M:%S")?;
// Weighting is driven by triple-barrier events (t_in, t_out, label) — the label
// lifetimes — plus the close series they span. It is not a function of returns.
let triple_barrier_events: Vec<(NaiveDateTime, NaiveDateTime, f64)> = (0..20)
.map(|i| (t0 + Duration::days(i), t0 + Duration::days(i + 2), 1.0))
.collect();
let close: Vec<(NaiveDateTime, f64)> =
(0..25).map(|i| (t0 + Duration::days(i), 100.0 + i as f64 * 0.1)).collect();
// decay = 0.5: the oldest observation keeps half the weight of the newest.
// decay <= 0 erases the oldest observations entirely.
let weights = get_weights_by_time_decay(&triple_barrier_events, &close, 0.5)?;
println!("{} weights; newest = {:.4}", weights.len(), weights.last().map(|w| w.1).unwrap_or(0.0));
  • Training without any overlap correction — highly overlapping labels effectively duplicate data and overfit the dense-event regime.
  • Using uniqueness weights without the indicator matrix from the sampling module — the weights require knowledge of which bars each event spans.
  • Combining time-decay and uniqueness weights incorrectly — multiply them element-wise, don’t add.
  • sample_weights.get_weights_by_return
  • sample_weights.get_weights_by_time_decay
  • get_weights_by_return
  • get_weights_by_time_decay
  • Pair with sequential bootstrap for robust label sampling.
  • Time-decay controls recency bias explicitly.