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.
Use Triple-Barrier Labeling in Claude, ChatGPT or Ahel Desktop
Free. Sign in, add Triple-Barrier Labeling and connect your AI. About a minute.
Also: Claude Code · Cursor · Codex
Then ask your AI: use the Triple-Barrier Labeling skill
Details
Instructions available. Your AI can read the instructions. Execution depends on the setup they require.
Account requirements not reviewed. Check the skill instructions before use; Ahel provides instructions and does not run this skill.
No other account needed.
Add Ahel to your AI once: Claude, ChatGPT, Cursor, Claude Code or Codex. Then ask it to use this.
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
- 22
- Forks
- 11
- Last commit
- Oct 2026
Advanced
- Item type
- skill
- Key
ml4t-triple-barrier- Source
- github.com/ml4t/skills
Related picks
Skill · wshobson
The pick for Pythonpython-pro
Skill · jeffallan
The pick for Pythonrseng-notebooks
Skill · fdiblen
The pick for Notebooksexecute
Skill · brycewang-stanford
The pick for Notebookspandas-dataframe-analyzer
Skill · a5c-ai
The pick for Pandasxlsx
Skill · anthropics
The pick for Pandas