Conviction Scoring
Conviction scoring is how TraderTape ranks signals when more fire than you can take. On a busy day the scanner might generate 15 entry signals across the NIFTY 100, but you only have 10 free position slots. Which 10 do you take? That's what conviction scoring decides.
This document explains the philosophy, the format, and the research that produced the current scoring functions.
The problem
A naive scanner generates signals in alphabetical order. If your capital fills the first 10 in that order, you've effectively picked the 10 stocks whose ticker happens to start with "A" or "B". That's worse than random โ it's biased toward arbitrary lexicographic ordering.
A slightly better approach is first match, first served by signal generation order. Still arbitrary.
A real conviction score asks: of the signals that fired today, which ones are statistically more likely to win? Then it ranks by that probability and takes the top N.
The right scoring function depends entirely on the strategy. A momentum strategy values signals where momentum is strong but not yet exhausted. A mean-reversion strategy values signals where the dip is deep but the trend is intact. The scoring needs to encode the strategy's edge profile.
What we learned: the original V1 scoring was almost perfectly inverted
The first version of V1 used textbook scoring rules. RSI 40-55 got the highest weight. Volatility calm (low ATR) got the highest weight. Trades right at SMA20 got the highest weight. Trades fully aligned with the trend got the highest weight.
When we ran the data, every single one of these was wrong:
| Dimension | Old (textbook) said best | Data said best |
|---|---|---|
| RSI(14) | 40-55 | 60-65 (0.91% avg P&L) |
| Vol regime | vol_calm | vol_spike (2.31% avg, 61% WR) |
| SMA20 distance | 0-1% above | 2-3% above (1.07% avg P&L) |
| Trend alignment | fully_aligned | fully_against (1.20% avg P&L) |
The story behind each:
-
RSI 60-65 wins because momentum signals work best when momentum is strong but the trade hasn't yet attracted enough buyers to push RSI > 70. RSI 40-55 is "not overbought" but it's also "not particularly trending". Strong moves spend most of their time at RSI 60-65, then briefly touch 70+ at the top.
-
Vol spikes win because they create snap-back entries. A stock that moves 4% on a single bar then settles is the kind of move momentum strategies are built to catch. Calm markets have less to capture.
-
2-3% above SMA20 wins because "right at SMA20" is just touching support, while "2-3% above" is the move already confirming. A stock that has bounced from SMA20 by 2-3% has demonstrated the bounce is real.
-
fully_against wins because deeply discounted entries have more upside. Stocks below their longer MAs but above SMA20 (the V1 entry filter) are buyable but unloved โ they have room to run.
After inverting the scoring (what's now called the rsi_dominant variant), V1's profit factor went from 1.35 to 1.56 and CAGR from 25% to 30% on the same backtest period.
The lesson: textbook scoring is often wrong. Always validate against real data before trusting a scoring function.
The format
A conviction scoring spec is a list of dimensions, each with score buckets:
{
"conviction_scoring": [
{
"indicator": "rsi14",
"ranges": [
{"min": 60, "max": 65, "score": 40},
{"min": 0, "max": 35, "score": 35},
{"min": 35, "max": 40, "score": 30},
{"min": 55, "max": 60, "score": 25},
{"min": 40, "max": 55, "score": 22},
{"min": 65, "max": 70, "score": 3}
]
},
{
"indicator": "vol_regime",
"values": [
{"value": "vol_spike", "score": 18},
{"value": "vol_elevated", "score": 12},
{"value": "vol_normal", "score": 8},
{"value": "vol_calm", "score": 5}
]
},
{
"indicator": "sma20_dist_pct",
"ranges": [
{"min": 2, "max": 3, "score": 15},
{"min": 3, "max": 5, "score": 12},
{"min": 1, "max": 2, "score": 10},
{"min": 0, "max": 1, "score": 8}
]
},
{
"indicator": "trend_alignment",
"values": [
{"value": "fully_against", "score": 10},
{"value": "partial", "score": 7},
{"value": "fully_aligned", "score": 5}
]
}
]
}
Each dimension has either ranges (for numeric indicators) or values (for categorical). The score for that dimension is the matching bucket's value (first match wins for ranges, exact match for values).
The total conviction score is the sum across all dimensions, normalized to 0-100.
How signals get ranked
For each entry signal:
- Compute every indicator value at the signal moment
- Walk each scoring dimension and find the matching bucket
- Sum the scores
- Normalize by the maximum possible total (so the top score is always 100)
The result is a number between 0 and 100. Signals with higher conviction get filled first by the capital allocator.
Using conviction in capital allocation
The capital allocator processes new entry signals in descending conviction order:
sorted_signals = sort(new_signals, key=lambda s: s.conviction, descending=True)
for signal in sorted_signals:
if portfolio has free capital AND sector cap not breached AND position cap not breached:
place entry GTT
decrement capital
else:
mark signal as no_capital
This means the top-conviction signal always gets filled first. If you have 5 free slots and 12 signals, you get the 5 highest-conviction ones, filtered by sector caps.
Validating a scoring function
The right way to validate a scoring function is quintile analysis:
- Run a backtest
- Sort all entry signals by conviction
- Group into 5 quintiles (Q1 = lowest 20%, Q5 = highest 20%)
- For each quintile, compute win rate and average P&L
A working scoring function shows a monotonic curve: Q1 has the worst metrics, Q5 has the best, with Q2 < Q3 < Q4 in between.
The current V1 rsi_dominant scoring achieves:
| Quintile | Win Rate | Avg P&L |
|---|---|---|
| Q1 (lowest) | 38.0% | +0.24% |
| Q2 | 41.5% | +0.51% |
| Q3 | 44.8% | +0.78% |
| Q4 | 47.2% | +0.99% |
| Q5 (highest) | 50.6% | +1.16% |
That's a clean monotonic curve. Q5 trades earn nearly 5x what Q1 trades earn on average. The capital allocator's preference for Q5 captures that gap.
If your scoring function shows a flat curve (all quintiles look the same), the scoring is uncorrelated with outcome โ you might as well rank randomly. If it's inverted (Q5 worse than Q1), the scoring is actively harmful โ you're using your scarce capital on the worst signals. Either rebuild it or invert the dimensions.
Universe sensitivity
The V1 scoring was developed and validated on NIFTY 100. Cross-validation showed it generalizes to NIFTY 150 (NIFTY 100 + MIDCAP 50) but not to NIFTY 50 alone or MIDCAP 50 alone:
| Universe | Q5โQ1 spread | Works? |
|---|---|---|
| NIFTY 100 | +4.3pp WR | โ |
| NIFTY 150 | +3.4pp WR | โ (diluted) |
| NIFTY 50 only | -0.2pp WR | โ โ too efficient |
| MIDCAP 50 only | -2.6pp WR | โ โ actively harmful |
The reason: scoring needs enough daily signal competition to be useful. With a 100-stock universe you typically get 10-15 signals per day vs 10 capital slots, so ranking matters. With a 50-stock universe you get 5-7 signals โ not enough for ranking to make a difference. With midcap, the indicator dynamics are different from large-cap, and the scoring function (calibrated on large-cap data) doesn't transfer.
The takeaway: scoring functions are universe-specific. If you change the universe, re-validate the scoring.
Strategy-specific scoring
V3 Dip Buyer's scoring is inverted from V1's because the strategy is fundamentally different:
| Dimension | V1 (momentum) | V3 (dip buyer) |
|---|---|---|
| RSI | 60-65 best | 30-40 best (the dip is deeper) |
| Vol regime | vol_spike best | vol_calm best (the dip has stabilized) |
| SMA20 distance | 2-3% above | 1-3% below (the price is at support) |
| Trend alignment | fully_against | fully_aligned (uptrend is intact) |
A dip buyer wants the opposite of what a momentum trader wants on most dimensions. That's the whole point โ they're catching different parts of the move. Conviction scoring needs to reflect that.
Pitfalls
Don't trust theoretical scoring. Backtest it. Run quintile analysis. If the curve isn't monotonic, your function is noise.
Don't over-fit dimensions. Adding more dimensions increases the chance of fitting noise. The rule of thumb is 3-5 dimensions max for a 6-year backtest.
Don't normalize away the spread. If you normalize all dimensions to 0-100 individually then add them, you're treating them as equally weighted. They're not โ RSI typically explains far more variance than vol regime. The TraderTape format lets each dimension contribute its raw score (e.g. 40 max for RSI vs 18 max for vol regime), preserving the natural weighting.
Don't validate on the same data you trained on. If you tweak the scoring buckets until the backtest looks good, you're over-fitting. Use walk-forward: tune on years X-Y, validate on Y+1 to today.
Customizing scoring for your strategy
The visual editor exposes conviction scoring in a dedicated tab. You can:
- Add / remove dimensions
- Add / edit score buckets within each dimension
- Reorder buckets (matters for
rangessince first match wins) - Disable scoring entirely (signals get a uniform score)
After editing, always run a backtest with quintile analysis to verify the new function produces a monotonic curve. The strategy editor surfaces this analysis automatically when you save.
Where the analysis lives
Quintile analysis runs as part of every backtest. Open any saved backtest run from the Backtest history page and look for the conviction analysis panel โ it shows the win rate and avg P&L per quintile, plus the spread between Q5 and Q1.
Next
- Strategy DSL โ the full DSL syntax
- Built-in Models โ how each model uses conviction
- Backtesting Guide โ quintile analysis in the backtest UI