labeling
Concept Overview
Section titled “Concept Overview”The triple-barrier method (AFML Chapter 3) replaces fixed-horizon labeling with a path-dependent approach. Instead of asking “did the price go up in 10 days?”, it asks “which barrier did the price hit first — a profit-taking ceiling, a stop-loss floor, or a maximum holding horizon?”
This matters because fixed-horizon labels create artifacts: a trade that hits +5% then reverses to -1% at the horizon gets labeled as a loss. Triple-barrier labels capture the actual trade outcome under realistic exit rules.
Meta-labeling is a two-stage extension: a primary model predicts direction (side), while a secondary model learns when to act on that signal. The secondary model’s label is binary (1 = the primary model was correct, 0 = it wasn’t). This separation lets you combine a simple directional model with a sophisticated sizing/filtering model.
Barrier widths are scaled by a volatility target (typically EWMA of returns), making them adaptive across regimes. Events are sourced from structural filters like CUSUM rather than calendar time.
When to Use
Section titled “When to Use”Use this module immediately after event detection (CUSUM/z-score filters) and volatility estimation. It sits at the start of the ML pipeline: raw price events go in, labeled training examples come out.
Prerequisites: A price series with timestamps, filtered event timestamps, and a volatility target series.
Alternatives: Fixed-horizon labeling (simpler but regime-blind), or trend-scanning labels for continuous-valued targets instead of classification.
Mathematical Foundations
Section titled “Mathematical Foundations”Triple-Barrier Event Time
Section titled “Triple-Barrier Event Time”
Labeling Rule
Section titled “Labeling Rule”
Target Volatility Scaling
Section titled “Target Volatility Scaling”
Key Parameters
Section titled “Key Parameters”| Parameter | Type | Description | Default |
|---|---|---|---|
pt | f64 | Profit-taking barrier multiplier (× volatility target) | 1.0 |
sl | f64 | Stop-loss barrier multiplier (× volatility target) | 1.0 |
min_ret | f64 | Minimum return threshold; events with smaller absolute returns are labeled 0 | 0.0 |
vertical_barrier_times | Option<Vec> | Maximum holding period timestamps; events expire if neither profit nor stop barrier is hit | None |
side_prediction | Option<Vec<f64>> | Primary model side forecasts (+1/−1) for meta-labeling mode | None |
Usage Examples
Section titled “Usage Examples”Python
Section titled “Python”Triple-barrier labels from price series
Section titled “Triple-barrier labels from price series”from openquant._core import labeling, filters
# 1) Detect events with CUSUM filtertimestamps = ["2024-01-01T09:30:00", "2024-01-01T09:31:00", ...]close = [100.0, 100.1, 99.9, 100.2, 100.05, 100.3, ...]event_ts = filters.cusum_filter_timestamps(close, timestamps, 0.02)
# 2) Estimate target volatility (use your own EWMA or rolling std)target_ts = event_tstarget_vals = [0.02] * len(event_ts) # simplified constant target
# 3) Compute triple-barrier labelslabels = labeling.triple_barrier_labels( close_timestamps=timestamps, close_prices=close, t_events=event_ts, target_timestamps=target_ts, target_values=target_vals, pt=1.0, sl=1.0, min_ret=0.005,)# Each label: (event_ts, return, target, label_int, touch_ts)Meta-labeling: learn when to act on a primary signal
Section titled “Meta-labeling: learn when to act on a primary signal”from openquant._core import labeling
# Primary model gives side predictions (+1 or -1) at each eventside_prediction = [1.0, -1.0, 1.0, 1.0, -1.0, ...]
meta_labels = labeling.meta_labels( close_timestamps=timestamps, close_prices=close, t_events=event_ts, target_timestamps=target_ts, target_values=target_vals, side_prediction=side_prediction, pt=1.0, sl=1.0, min_ret=0.005,)# Train a secondary classifier on meta_labels to filter false signalsEnd-to-end: Event Filter -> Vertical Barrier -> Triple Barrier Labels
Section titled “End-to-end: Event Filter -> Vertical Barrier -> Triple Barrier Labels”use chrono::NaiveDateTime;use openquant::filters::{cusum_filter_timestamps, Threshold};use openquant::labeling::{add_vertical_barrier, get_events, get_bins};use openquant::util::volatility::get_daily_vol;
// 1) price series and timestampslet close: Vec<(NaiveDateTime, f64)> = /* load bars */ vec![];let prices: Vec<f64> = close.iter().map(|(_, p)| *p).collect();let ts: Vec<NaiveDateTime> = close.iter().map(|(t, _)| *t).collect();
// 2) detect candidate events via CUSUM filterlet events = cusum_filter_timestamps(&prices, &ts, Threshold::Scalar(0.02));
// 3) estimate target volatility and add max-holding horizonlet target = get_daily_vol(&close, 100);let vbars = add_vertical_barrier(&events, &close, 1, 0, 0, 0);
// 4) compute barrier touches and labelslet ev = get_events(&close, &events, (1.0, 1.0), &target, 0.005, 3, Some(&vbars), None);let bins = get_bins(&ev, &close);assert!(!bins.is_empty());Meta-Labeling Workflow with Side Signal
Section titled “Meta-Labeling Workflow with Side Signal”use chrono::NaiveDateTime;use openquant::labeling::{get_events, get_bins};
let close: Vec<(NaiveDateTime, f64)> = /* bars */ vec![];let events: Vec<NaiveDateTime> = /* primary event timestamps */ vec![];let target: Vec<(NaiveDateTime, f64)> = /* vol target */ vec![];let vbars: Vec<(NaiveDateTime, NaiveDateTime)> = /* horizon */ vec![];
// Primary model side forecast (+1 / -1)let side: Vec<(NaiveDateTime, f64)> = events.iter().map(|t| (*t, 1.0)).collect();
let meta_events = get_events( &close, &events, (1.0, 1.0), &target, 0.005, 3, Some(&vbars), Some(&side),);let meta_bins = get_bins(&meta_events, &close);// Use meta_bins to train a second-stage filter (take/skip decision)assert!(!meta_bins.is_empty());Common Pitfalls
Section titled “Common Pitfalls”- Setting symmetric barriers (pt=sl=1) when the strategy has asymmetric payoff — calibrate each barrier width independently.
- Using calendar-time vertical barriers with information-driven bars — the holding period should match bar frequency, not wall time.
- Ignoring class imbalance after labeling: if 80% of events hit the vertical barrier, the model learns to predict ‘no movement’ and the labels need recalibration.
- Forgetting that meta-labeling requires aligned timestamps between the primary model’s side predictions and the event set — off-by-one joins silently corrupt labels.
API Reference
Section titled “API Reference”Python API
Section titled “Python API”labeling.add_vertical_barrierlabeling.triple_barrier_eventslabeling.triple_barrier_labelslabeling.meta_labelslabeling.get_eventslabeling.get_binslabeling.drop_labels
Rust API
Section titled “Rust API”add_vertical_barrierget_eventsget_binsdrop_labelsEvent
Implementation Notes
Section titled “Implementation Notes”- Label stability is dominated by event quality and volatility-target quality; calibrate these before tuning ML models.
- Always audit class balance and average holding time after labeling; both drive downstream model behavior.
- In meta-labeling, side alignment and timestamp joins are a frequent hidden bug source.