What's the Right Answer? Labeling and Sample Weights for Indian Equities
Before a model can learn anything, you have to tell it what "right" looks like. That's labeling, and it's the step quants are sloppiest about — usually a one-liner: y = sign(return over next N days).
This post is about why that one-liner quietly corrupts your training set, how the triple-barrier method fixes it, and why the very act of fixing it creates a second problem — overlapping, redundant labels — that Chapter 4 of López de Prado's Advances in Financial Machine Learning then has to clean up.
The two chapters are a matched pair: Chapter 3 decides what each label is, Chapter 4 decides how much each label should count. We measured both on ten years of daily data (June 2016 – June 2026) from the Kite historical API.
Part 1 — Labeling: the endpoint lies
The broken default
The standard "fixed-horizon" label looks at the return over a fixed window — say 10 days — and stamps +1 if it's up, −1 if it's down. It has two quiet defects.
First, a fixed threshold ignores volatility. A 1% move in HDFC Bank and a 1% move in APAR Industries are not the same event — over our sample APAR ran at 42.5% annualised volatility versus HDFC's 22.8%. One fixed threshold across both means your "+1" class is a soup of real signals and pure noise, and the model can't tell which is which.
Second, and worse: it only looks at the endpoint. Picture a trade that craters 8% on day 2 and limps back to +1% by day 10. The endpoint says "winner, +1." Reality says you were stopped out for a brutal loss on day 2 and never saw day 10. Labeling by the endpoint teaches the model that a trade "worked" when in practice it blew up. It's like grading a road trip by your final GPS coordinate while ignoring that you crashed halfway.
The fix: three barriers
The triple-barrier method labels the way a trader actually experiences a position. You set three barriers and watch which one the price touches first:
- an upper barrier — a profit-take,
- a lower barrier — a stop-loss,
- a vertical barrier — a maximum holding period.
Touch the upper first → +1. Touch the lower first → −1. Run out of time → 0 (or the sign of the small leftover return). The magic word is first: the label is now path-dependent, so the 8%-drawdown trade is correctly labeled −1 because it hit the stop before anything else.
And the volatility defect is fixed in the same stroke: the up/down barriers are set as multiples of each instrument's own recent volatility (we used an EWMA estimate, barriers at 2× daily vol). In calm regimes the barriers sit tight; in turbulent ones they widen. A "+1" now means the same thing — "a move large relative to this stock's current normal" — across every instrument.
How often does the path actually flip the verdict?
We labeled all four series both ways (10-day horizon) and counted the disagreements:
| Instrument | Ann. vol | Labels where path disagrees with endpoint | "False winners" (endpoint +1, path −1) |
|---|---|---|---|
| HDFC Bank | 22.8% | 9.7% | 4.6% |
| NIFTY 50 | 16.2% | 10.6% | 6.1% |
| Smallcap 250 | 19.3% | 10.9% | 6.9% |
| APAR Industries | 42.5% | 7.2% | 3.4% |
Read the last column. On the NIFTY 50, 6 out of every 100 labels the naive method calls a "winner" are actually losers — the position would have been stopped out before the horizon. Roughly one in ten labels overall flips once you respect the path. You are training on that many mislabeled examples for free, simply by using the endpoint.
A subtle point hides in the APAR row: it has the highest volatility but the fewest flips. That's the volatility-scaling working as intended — because its barriers are set wide (2× a big vol), the path rarely hits a barrier before the vertical limit, so it behaves more like a slow fixed-horizon label. Scaling makes the method behave sensibly across wildly different instruments instead of drowning the high-vol names in stop-outs.
Meta-labeling, in one paragraph
Chapter 3's second big idea splits one hard question into two easy ones. A primary model (even your existing strategy — a momentum rule, a fundamental signal) decides the side: long or short. Then a secondary model decides only whether that call is worth acting on, and how big — it predicts whether the primary model is right this time. The analogy: the primary model is the analyst shouting "go long!"; the meta-model is the PM deciding whether to take the bet and at what size. You tune the primary for recall (catch everything), and let the meta-model supply precision (kill the false alarms). It's the cleanest way to bolt machine learning onto a strategy you already trust without throwing the strategy away.
Part 2 — Sample Weights: your labels are not independent
The fix to Chapter 3 breaks an assumption
Here's the catch. The moment you adopt triple-barrier labels, you've created a new problem. Monday's label depends on the price path over the next ~10 days. Tuesday's label depends on its next ~10 days. Those two windows overlap by nine days — the two labels are mostly the same outcome, viewed a day apart. Your dataset is no longer a pile of independent observations; it's a pile of heavily overlapping near-copies.
Every ML algorithm — and every accuracy score it reports — assumes your samples are independent. They aren't. It's like running a survey of 1,000 people where 900 just copied their neighbour: you report n = 1,000 and feel confident, but you really have about 100 opinions, and whatever the copycats said dominates the result far beyond its real worth.
Measuring the redundancy: uniqueness
Chapter 4 makes this precise. At each bar, concurrency counts how many label windows are sitting on top of it. Flip that around and you get a label's uniqueness — how much genuinely new information it carries, averaged over its life. A uniqueness of 1.0 means a fully independent sample; 0.2 means it's effectively one-fifth of an observation, five-way redundant.
Measured on our four series at a 10-day vertical barrier:
| Instrument | Average label uniqueness | Effective redundancy |
|---|---|---|
| HDFC Bank | 0.147 | ~6.8× |
| NIFTY 50 | 0.156 | ~6.4× |
| Smallcap 250 | 0.166 | ~6.0× |
| APAR Industries | 0.142 | ~7.1× |
Every one of these series carries 6–7× redundancy. Your "2,400-row" training set is really worth a few hundred independent observations. Treat the rows as independent and you'll be wildly overconfident, and the busy overlapping stretches will dominate the fit.
And the redundancy is a direct consequence of a Chapter 3 choice — the vertical barrier. Widen the holding period and the overlap explodes; uniqueness falls from ~0.20 at a 5-day barrier toward ~0.12 at 40 days (left chart). The single most important knob for sample independence is the holding-period cap you set back in labeling. Chapters 3 and 4 are the same decision viewed twice.
The three fixes, all built on uniqueness
Sample weights — down-weight the redundant samples and up-weight the unique ones, so the model stops counting the copycats nine times over. You can stack a return-attribution term on top: a label that resolved into a big, decisive move earns more say than one that drifted, because the loud events carry more signal.
Sequential bootstrap — this is for bagging models like random forests. Naive bootstrap resampling from overlapping labels fills every bag with near-duplicates, so your trees come out nearly identical and the ensemble's diversity collapses into a hall of mirrors. Sequential bootstrap fixes the draw itself: each pick prefers samples that don't overlap with what's already been drawn, producing bags that are far closer to independent — more diverse trees, more honest out-of-bag error.
Time decay — optionally let weights fade with age so recent regimes matter more than ancient ones. It stacks on top of the uniqueness weights.
How does it look-like

Where this sits in the pipeline
The stack we're building now has a clear spine:
dollar bars (Ch 2) → FFD features (Ch 5) → triple-barrier labels (Ch 3) → uniqueness-based sample weights + sequential bootstrap (Ch 4) → model
Two things tie it back to the earlier notes. The dollar-bar work makes labels land on activity rather than the clock — busy periods produce many closely spaced bars whose label windows overlap hard, which is exactly where uniqueness weighting earns its keep. And the two big tuning knobs of the whole project now stand out clearly: d* from the fractional-differentiation post, and the vertical barrier here. The first decides how much memory your features keep; the second decides how independent your labels are. Everything downstream inherits both.
The recurring lesson, three chapters running: the damage isn't done by weak models, it's done by default transformations applied without thinking — time bars, first-differencing, endpoint labels, equal sample weights. The fix is never a fancier model. It's refusing to lie to the one you have.