Insufficient Evidence and Regime Dependence
“How many trades do I need?” is the question everybody asks, and it does not have a number for an answer. What it has is a better question underneath it: how many genuinely independent things did I observe, and did they include more than one kind of market?
Why there is no single number
Section titled “Why there is no single number”You will see 30 trades quoted, and 100, and 200, and 1,000. All of them are wrong as universal answers, because the amount of evidence a given number of trades represents depends on three things none of the rules mention.
How variable the outcomes are. A rule whose trades cluster between +1% and +3% tells you something after twenty. A rule whose trades range from −40% to +180% tells you very little after two hundred, because a handful of extreme outcomes dominate every average you compute.
How independent the trades are. Ten positions entered on the same day on the same market-wide signal are close to one observation. A backtest that reports 400 trades on 40 correlated symbols over ten years may contain a few dozen genuinely distinct episodes.
How many things you tried before this one. A first-guess rule with 50 trades is much stronger evidence than the best of 3,600 specifications with 50 trades each. The data-snooping lesson is the other half of this one.
One bull market is one observation
Section titled “One bull market is one observation”This is the error that survives every other correction.
A backtest that spans a long, largely rising market has tested your rules against one market. It does not matter that it covers fifteen years and 4,000 bars and 800 trades. There was one broad environment, and every trade is an observation drawn from it.
The consequence is that you have learned “these rules did well in that environment”, and you have learned nothing about what happens in a different one — which is precisely the question that matters, because the environment will change and you will not be told when.
Splitting the evidence by regime
Section titled “Splitting the evidence by regime”A backtest report gives you one number per metric for the whole period. That number hides the thing you most need: whether the rule was tested in more than one kind of market, and how many observations landed in each kind.
Complete runnable AFL
// regime-split-evidence.afl// Part 30 - Insufficient Evidence and Regime Dependence//// PURPOSE// A backtest report gives you one number per metric for the whole period.// That number hides the thing you most need to know: whether the rule was// tested in more than one kind of market, and how many observations landed// in each kind. This exploration splits every signal into four market// regimes defined by a reference index, and reports the count and the mean// forward outcome in each.//// The usual finding is not that one regime is better. It is that three of// the four cells contain too few observations to support any statement at// all - which is itself the result.//// HOW TO RUN// Analysis -> Apply to: your universe. Periodicity: Daily.// Range: All quotations. Press EXPLORE. One row per symbol.//// ============================ ASSUMPTIONS =============================// Regime defined by a REFERENCE INDEX, not by the traded symbol, so// that every symbol is classified the same way on the same// date. Set the ticker in the parameter below; it must exist// in the database or every regime column will be empty.// Trend axis index close above / below its own TrendPeriod-bar simple// moving average, both known at that bar's close.// Volatility axis a 20-bar average true range of the index, as a percentage// of the index close, above / below its own VolPeriod-bar// average. Also known at that bar's close.// Outcome plain percentage change of the traded symbol's Close over// Horizon bars. No costs, no slippage, no position sizing,// no stops. This measures association, not tradability.// Independence forward windows overlap, so consecutive observations are// correlated and the effective sample is smaller than the// count printed. Do not treat the counts as independent// trials.// Alignment Foreign() aligns the index to the traded symbol's bars.// If your data has holes, consider Settings -> General ->// "Pad and align to reference symbol".// ======================================================================
SetBarsRequired( -2, -2 ); // -2 is sbrAll: the running totals need every bar
RefSymbol = ParamStr( "Reference index symbol", "^GSPC" );TrendPeriod = Param( "Index trend MA (bars)", 200, 50, 300, 10 );VolPeriod = Param( "Index volatility baseline (bars)", 250, 50, 500, 10 );SignalMA = Param( "Signal MA (bars)", 50, 5, 200, 5 );Horizon = Param( "Forward window (bars)", 20, 1, 120, 1 );MinTurnover = Param( "Min 50-bar turnover", 1000000, 0, 50000000, 250000 );
// ---------------------------------------------------------------------// 1. The reference index, read field by field. Foreign() returns one field per// call, so the true range is assembled by hand rather than with ATR(),// which would operate on the traded symbol's own price arrays.// ---------------------------------------------------------------------IndexClose = Foreign( RefSymbol, "C" );IndexHigh = Foreign( RefSymbol, "H" );IndexLow = Foreign( RefSymbol, "L" );
IndexPrevClose = Ref( IndexClose, -1 );IndexTrueRange = Max( IndexHigh, IndexPrevClose ) - Min( IndexLow, IndexPrevClose );IndexAtrPct = 100 * SafeDivide( MA( IndexTrueRange, 20 ), IndexClose, Null );
HaveIndex = NOT IsNull( IndexClose ) AND IndexClose > 0;
TrendUp = HaveIndex AND IndexClose > MA( IndexClose, TrendPeriod );VolHigh = HaveIndex AND IndexAtrPct > MA( IndexAtrPct, VolPeriod );
// ---------------------------------------------------------------------// 2. The signal being examined, and the outcome that followed it.// Ref() with a POSITIVE shift reads bars that had not printed yet. That is// correct here, because this is a measurement of what followed, and it must// never appear in a Buy expression.// ---------------------------------------------------------------------Turnover = MA( Close * Volume, 50 );Signal = Cross( Close, MA( Close, SignalMA ) ) AND Turnover >= MinTurnover AND Volume > 0;
FwdReturn = 100 * SafeDivide( Ref( Close, Horizon ) - Close, Close, Null );
LastBarIndex = LastValue( BarIndex() );HasForwardWindow = BarIndex() <= LastBarIndex - Horizon;
Measurable = Status( "barinrange" ) AND HaveIndex AND HasForwardWindow AND NOT IsNull( FwdReturn );
Observation = Measurable AND Signal;
// ---------------------------------------------------------------------// 3. Four regimes. Every observation lands in exactly one of them.// ---------------------------------------------------------------------RegimeUpQuiet = Observation AND TrendUp AND NOT VolHigh;RegimeUpWild = Observation AND TrendUp AND VolHigh;RegimeDownQuiet = Observation AND NOT TrendUp AND NOT VolHigh;RegimeDownWild = Observation AND NOT TrendUp AND VolHigh;
CountUpQuiet = Cum( RegimeUpQuiet );CountUpWild = Cum( RegimeUpWild );CountDownQuiet = Cum( RegimeDownQuiet );CountDownWild = Cum( RegimeDownWild );
// Nz() is deliberate: FwdReturn is Null outside the measurable window, and one// Null entering a running total destroys every value after it.SumUpQuiet = Cum( IIf( RegimeUpQuiet, Nz( FwdReturn ), 0 ) );SumUpWild = Cum( IIf( RegimeUpWild, Nz( FwdReturn ), 0 ) );SumDownQuiet = Cum( IIf( RegimeDownQuiet, Nz( FwdReturn ), 0 ) );SumDownWild = Cum( IIf( RegimeDownWild, Nz( FwdReturn ), 0 ) );
MeanUpQuiet = SafeDivide( SumUpQuiet, CountUpQuiet, Null );MeanUpWild = SafeDivide( SumUpWild, CountUpWild, Null );MeanDownQuiet = SafeDivide( SumDownQuiet, CountDownQuiet, Null );MeanDownWild = SafeDivide( SumDownWild, CountDownWild, Null );
TotalObs = CountUpQuiet + CountUpWild + CountDownQuiet + CountDownWild;TotalSum = SumUpQuiet + SumUpWild + SumDownQuiet + SumDownWild;MeanAll = SafeDivide( TotalSum, TotalObs, Null );
// How lopsided is the evidence? The share of all observations that fell into// the single most populated regime. Close to 100 means the rule has effectively// been tested once, in one kind of market.Largest = Max( Max( CountUpQuiet, CountUpWild ), Max( CountDownQuiet, CountDownWild ) );ConcentrationP = 100 * SafeDivide( Largest, TotalObs, Null );
// How much of the test period each regime occupied, regardless of signals.BarsMeasurable = Cum( Measurable );BarsUpQuiet = Cum( Measurable AND TrendUp AND NOT VolHigh );BarsUpWild = Cum( Measurable AND TrendUp AND VolHigh );BarsDownQuiet = Cum( Measurable AND NOT TrendUp AND NOT VolHigh );BarsDownWild = Cum( Measurable AND NOT TrendUp AND VolHigh );
// ---------------------------------------------------------------------// 4. One row per symbol, written on the last bar of the range.// ---------------------------------------------------------------------Filter = Status( "lastbarinrange" ) AND TotalObs > 0;
AddColumn( TotalObs, "Signals", 1.0 );AddColumn( MeanAll, "Mean fwd % all", 1.2 );
AddColumn( CountUpQuiet, "N up/quiet", 1.0 );AddColumn( MeanUpQuiet, "Fwd % up/quiet", 1.2 );AddColumn( CountUpWild, "N up/wild", 1.0 );AddColumn( MeanUpWild, "Fwd % up/wild", 1.2 );AddColumn( CountDownQuiet, "N down/quiet", 1.0 );AddColumn( MeanDownQuiet, "Fwd % down/quiet", 1.2 );AddColumn( CountDownWild, "N down/wild", 1.0 );AddColumn( MeanDownWild, "Fwd % down/wild", 1.2 );
AddColumn( ConcentrationP, "% of signals in biggest regime", 1.1 );
AddColumn( BarsMeasurable, "Bars measured", 1.0 );AddColumn( 100 * SafeDivide( BarsUpQuiet, BarsMeasurable, Null ), "% bars up/quiet", 1.1 );AddColumn( 100 * SafeDivide( BarsUpWild, BarsMeasurable, Null ), "% bars up/wild", 1.1 );AddColumn( 100 * SafeDivide( BarsDownQuiet, BarsMeasurable, Null ), "% bars down/quiet", 1.1 );AddColumn( 100 * SafeDivide( BarsDownWild, BarsMeasurable, Null ), "% bars down/wild", 1.1 );
// COUNT and AVERAGE rows. The AVERAGE row weights every symbol equally: a// symbol with four signals counts as much as one with four hundred. To pool// properly, export the table and weight each mean by its own signal count.AddSummaryRows( 2 | 16, 1.2 );It classifies every signal into four regimes on two axes — index trending up or down, index volatility high or low — and reports the count and mean forward outcome in each.
Why the regime is defined by a reference index
Section titled “Why the regime is defined by a reference index”Fragment — not a complete formula
IndexClose = Foreign( RefSymbol, "C" );TrendUp = HaveIndex AND IndexClose > MA( IndexClose, TrendPeriod );Not by the traded symbol. If each symbol were classified by its own trend, “up regime” would mean something different for every row and the cells would not be comparable. A single reference index classifies every symbol the same way on the same date, which is what makes the split meaningful.
Two implementation notes worth borrowing.
Foreign() returns one field per call, so the index’s true range has to be assembled by hand
rather than by calling ATR() — ATR() would operate on the traded symbol’s price arrays, which
would be a subtle and completely silent bug.
And Foreign() aligns the index to the traded symbol’s bars. If your data has holes, Settings →
General → “Pad and align to reference symbol” is the documented fix; without it, missing bars
in one series shift the alignment.
The number to read first
Section titled “The number to read first”Fragment — not a complete formula
ConcentrationP = 100 * SafeDivide( Largest, TotalObs, Null );The share of all observations that fell into the single most populated regime.
Close to 100 means the rule has effectively been tested once, in one kind of market — regardless of how many trades the report shows. That single figure is more informative about the strength of your evidence than the trade count, the win rate or the Sharpe ratio.
Note also the two Nz() calls in the running sums. FwdReturn is Null outside the measurable
window, and a single Null entering a Cum() destroys every value after it — which would silently
empty the entire column rather than producing an obviously wrong number.
Testing across regimes deliberately
Section titled “Testing across regimes deliberately”The exploration is diagnostic. Three ways to act on what it tells you, in increasing order of effort:
Split the date range and re-run. Take three or four disjoint multi-year spans covering visibly different conditions and run the same backtest on each. Compare results. This is crude, it uses no new machinery, and it is often decisive.
Test on a different universe or a different market. A rule that describes something general should not stop working when you change the instruments. A rule that only works on one universe is telling you about that universe.
Walk-forward. Part 32’s walk-forward methodology tests the process rather than a single parameter set, across many consecutive out-of-sample windows. It is the strongest of the three and the most work.
Confidence intervals, in plain language
Section titled “Confidence intervals, in plain language”You do not need the formulas to use the idea, and the idea is what matters.
Suppose your system’s average trade is +0.8%, and the individual trades vary widely — a standard deviation of, say, 9%. The average of a sample is itself uncertain, and roughly speaking that uncertainty shrinks with the square root of the sample size. With 100 trades, the uncertainty in your estimate of the mean is around 0.9% — larger than the mean itself.
The plain-language conclusion: your best estimate of the average trade is 0.8%, and the data is consistent with the true value being anywhere from clearly negative to clearly positive. You have not established that the system makes money.
Three cautions before you reach for a calculator, and they matter more than the arithmetic:
- The trades are not independent, so the effective sample is smaller than the count and the real uncertainty is larger than any standard formula will tell you.
- The distribution is not symmetric. Trading returns have long tails, and interval formulas built on symmetry understate the extremes.
- The sample was selected by your search process. An interval computed on the best of 3,600 specifications does not mean what it would mean on a pre-registered one.
What to say when the evidence is weak
Section titled “What to say when the evidence is weak”This is a skill, and it is the one that separates research from marketing.
Say what you measured. “Over this universe, this period, with these costs, the system produced X.” Every qualifier belongs in the sentence.
Say how many independent observations you think you have, and why that number differs from the trade count.
Say which regimes were represented, with counts, and which were not.
Say what would change your mind. If you cannot name a result that would make you abandon the idea, you were not testing it.
Say “I do not know” when that is the answer. Weak evidence is a legitimate finding. A great deal of published technical-analysis material exists because somebody could not bring themselves to report one.
There is no sufficient number of trades, because the amount of evidence depends on how variable the outcomes are, how independent they are, and how many specifications you tried before this one. One bull market is one observation no matter how many bars it contains. Splitting signals by an index-defined regime shows you how lopsided the evidence is, and the concentration figure — the share of observations in the largest regime — tells you more about the strength of your test than any performance metric. Confidence intervals are worth understanding directionally and not worth computing precisely, because the assumptions they rest on are all violated here. And “the evidence is weak” is a result you should be able to write down and publish to yourself without flinching.
Check your understanding
Sources for this lesson
6 verified · checked 2026-09-01
- 01AFL Function Reference — Foreignamibroker.com/guide/afl/foreign.html2026-08-31
- 02AFL Function Reference — SetBarsRequiredamibroker.com/guide/afl/setbarsrequired.html2026-08-31
- 03AFL Function Reference — SafeDivideamibroker.com/guide/afl/safedivide.html2026-08-31
- 04AFL Function Reference — AddSummaryRowsamibroker.com/guide/afl/addsummaryrows.html2026-08-31
- 05AmiBroker User's Guide — Settings window§ Pad and align to reference symbolamibroker.com/guide/w_settings.html2026-09-01
- 06AmiBroker User's Guide — Walk-forward testingamibroker.com/guide/h_walkforward.html2026-08-31
Every technical claim on this page was checked against the official AmiBroker documentation on the date shown. Where the course disagrees with folklore, the source is how you can tell which one to trust.