sample_weights
Concept Overview
Section titled “Concept Overview”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.
When to Use
Section titled “When to Use”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).
Mathematical Foundations
Section titled “Mathematical Foundations”Uniqueness Weight
Section titled “Uniqueness Weight”
Time Decay
Section titled “Time Decay”
Key Parameters
Section titled “Key Parameters”| Parameter | Type | Description | Default |
|---|---|---|---|
delta | f64 | Time-decay exponent; 0 = uniform, 1 = linear decay, >1 = aggressive recency bias | 1.0 |
Usage Examples
Section titled “Usage Examples”Python
Section titled “Python”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 attributionw_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])Compute event weights
Section titled “Compute event weights”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));Common Pitfalls
Section titled “Common Pitfalls”- 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.
API Reference
Section titled “API Reference”Python API
Section titled “Python API”sample_weights.get_weights_by_returnsample_weights.get_weights_by_time_decay
Rust API
Section titled “Rust API”get_weights_by_returnget_weights_by_time_decay
Risk Notes and Caveats
Section titled “Risk Notes and Caveats”- Pair with sequential bootstrap for robust label sampling.
- Time-decay controls recency bias explicitly.