Research notes from a real NIFTY 15-minute ML build. No profit claims — just the

labeling mechanics that decide whether your model learns signal or noise.


Why Labeling Matters More Than the Model


Most beginners obsess over XGBoost hyperparameters. In practice, **the label defines the

game**. A model can only be as good as the question you ask it. Triple Barrier labeling is

the standard way to ask a *path-aware* question instead of a naive "price up or down."


The Three Barriers


For each entry bar at close `c`, with ATR `a`:


  • **Stop Loss (SL):** `c − SL_mult × ATR`
  • **Take Profit (TP):** `c + TP_mult × ATR`
  • **Vertical (time):** `c + horizon` bars

  • The label is decided by **which barrier is touched first**:

  • TP hit first → `+1` (win)
  • SL hit first → `−1` (loss)
  • Time expires, neither hit → `0` (no-trade)

  • Three Variants We Tested


    | Variant | SL | TP | Distribution (+1/−1/0) |

    |||||

    | `label_atr` | 0.6 ATR | 1.8 ATR (TP1), 3.0 ATR (TP2) | 18.6% / 65.8% / 15.7% |

    | `label_sr` | min(range_low, 0.6 ATR) | max(range_high, 1.8 ATR) | 37.2% / 37.7% / 25.1% |

    | `label_blended` | min(0.6 ATR, range_low) | max(1.8 ATR, range_high) | 23.3% / 41.3% / 35.4% |


    `label_atr` produces the most imbalanced set (only 18.6% winners) — brutal but clean.

    `label_sr` uses the actual range bounds, giving a more balanced 37/37/25 split.


    The Max-Hold Trap


    A subtle bug: labels used `MAX_HOLD_BARS = 6` but the backtest used `max_hold = 8`. A trade

    that would hit TP in bar 7-8 gets labeled "no-hit" (0) by the labeler yet taken as a trade by

    the backtester. **Always pass `max_hold` explicitly** so label and backtest agree.


    Don't Fake the Label With Future Data


    The #1 leakage source: using the *future* high/low of the selected option contract as a

    feature, or same-row future contract data. The barrier must be computed **only** from the

    entry bar's ATR and future path — never feed the future path back as an input.


    Why Most Bars Are "No-Trade"


    With tight 0.6/1.8 ATR barriers, ~65% of bars in `label_atr` are losses and only ~16% are

    no-trade. That severe imbalance means your classifier spends most of its effort learning

    "don't trade," which is correct but makes win-rate optimization tricky. Use

    `scale_pos_weight = min(neg/pos, 8.0)` to cap the imbalance fix.


    The Lesson


    Triple Barrier turns "will price go up?" into "will price reach my target before my stop,

    within my time budget?" That is the question that actually makes or loses money. Get the

    barriers and the hold-window right before you tune a single tree.


    *Research only. Not investment advice.*





    More From Shakti Tiwari


  • 🌐 **Websites:** [shaktitiwari.github.io/shakti-tiwari-nse](https://shaktitiwari.github.io/shakti-tiwari-nse) · [OptionTradingWithAI.in](https://optiontradingwithai.in)
  • 📚 **Books:** *Build Your Own AI* · *Option Trading with AI* (on [Amazon India](https://www.amazon.in/)) — practical guides from a Nifty options trader and ML practitioner.
  • 💬 **Community:** [Join the Discord](https://discord.gg/shaktitiwari) for live discussion, code, and research.
  • 💻 **Code:** [GitHub/shaktitiwari](https://github.com/shaktitiwari) — open research, models, and tools.
  • Educational only — not SEBI-registered investment advice. NISM-Series-XII certified; not a SEBI-registered Research Analyst. Content is educational only.

    Home | About