Triple-Barrier Labeling
SkillCommerce & financeLabel trades using profit-target, stop-loss, and time barriers with volatility-adaptive thresholds. Use when creating supervised labels for financial time series.
Available today. Use it from your connected AI after setup.
No other account needed.
Add ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
Then ask your AI: use the Triple-Barrier Labeling skill
What this skill tells your AI
The instructions your AI receives, as published by ml4t/skills in features/triple-barrier/SKILL.md and read by ahel’s review.
Fixed return thresholds ignore volatility - a 2% move is noise in crypto but a signal in treasuries. Triple-barrier labels adapt to the asset's current regime.
The Problem
Naive binary labels (return > 0) are noisy and ignore position management. A trade that gains 5% then gives back 8% is labeled "winning" if you only check the endpoint. Triple-barrier labeling mirrors real trading: you exit when you hit a profit target, a stop loss, or time runs out.
The Pattern
WRONG
import numpy as np
# Fixed threshold ignores volatility regime
labels = np.where(fwd_returns > 0.02, 1, np.where(fwd_returns < -0.01, -1, 0))
CORRECT
import numpy as np
def triple_barrier_labels(
prices: np.ndarray,
upper_mult: float = 2.0,
lower_mult: float = 1.5,
atr_period: int = 14,
max_holding: int = 10,
) -> np.ndarray:
"""Label each bar: +1 profit hit, -1 stop hit, 0 time expiry."""
# Volatility-adaptive barriers via a TRAILING mean of absolute price changes.
# mode="same" would centre the window and let atr[i] see bars after i.
abs_changes = np.abs(np.diff(prices, prepend=prices[0]))
atr = np.convolve(abs_changes, np.ones(atr_period) / atr_period)[: len(prices)]
# NaN, not 0: the final max_holding bars have no full horizon, and labeling
# them "time expiry" would teach the model that censoring means no move.
labels = np.full(len(prices), np.nan)
for i in range(len(prices) - max_holding):
upper = prices[i] + atr[i] * upper_mult
lower = prices[i] - atr[i] * lower_mult
labels[i] = 0.0 # time expiry unless a barrier is touched first
for j in range(1, max_holding + 1):
if prices[i + j] >= upper:
labels[i] = 1; break
elif prices[i + j] <= lower:
labels[i] = -1; break
return labels # drop the NaN tail before training
Barrier Calibration
| Symptom | Cause | Fix |
|---|---|---|
| 90%+ stops hit | Barriers too tight | Widen lower_mult |
| 90%+ time expiry | Barriers too wide | Tighten multipliers or shorten max_holding |
| Label imbalance >3:1 | Asymmetric barriers | Adjust upper/lower ratio |
The ATR multiplier controls barrier width relative to current volatility. Typical ranges: upper 1.5-3.0x, lower 1.0-2.0x. De Prado's original uses EWMA daily vol; ATR is a practical alternative that captures intraday range.
MFE/MAE diagnostics: Plot Maximum Favorable Excursion (best unrealized P&L) and Maximum Adverse Excursion (worst drawdown) for each trade to calibrate barriers empirically - barriers should sit at natural break points in the MFE/MAE distributions.
Guardrails
- Purging required: CV must purge
max_holding_periodbars around test boundaries to prevent leakage - Label overlap: labels with overlapping holding periods are not IID - effective sample size is ~N/H where H is holding period. Use sample uniqueness weighting or sequential bootstrap
- Class balance: check label distribution - use class weights if imbalanced beyond 3:1
- ATR lookback: must use only past data;
atr[i]must not include bari+1 - Tie-breaking: when both barriers are crossed in the same bar, define a resolution rule (e.g., stop-loss takes priority)
Production Implementation
ml4t-engineer provides a validated, vectorized implementation:
from ml4t.engineer.config import LabelingConfig
from ml4t.engineer.labeling import atr_triple_barrier_labels
config = LabelingConfig.atr_barrier(
atr_tp_multiple=2.0,
atr_sl_multiple=1.5,
atr_period=14,
max_holding_period=10,
)
labels = atr_triple_barrier_labels(
df,
config=config,
price_col="close",
timestamp_col="timestamp",
)
# Returns: label, label_time, label_bars, label_return
Checklist
- Barriers are volatility-adaptive (ATR or realized vol), not fixed thresholds
-
max_holding_periodmatches CV purge window (label_horizon) - Label distribution checked - no single class >80%
- ATR computed from past data only (no lookahead)
- Short-side labels handled correctly if strategy is long/short
Signals
- GitHub stars
- 20
- Forks
- 11
- Last commit
- Sep 2026
Advanced
- Item type
- skill
- Key
ml4t-triple-barrier- Source
- github.com/ml4t/skills