Learn NinjaScript: Counting Candles Since a Condition Was Met
“How many bars since the last cross?” “How many bars has price been above the SMA?” Bars since a condition was met is one of the most-reused small patterns in NinjaScript — a single int field that increments every bar and resets to zero whenever the trigger fires.
This post covers the pattern, the corner cases, and the difference between rolling your own counter and reaching for NinjaTrader’s built-in BarsSinceExit or BarsSinceEntry.
⚙️ The Core Pattern
Counter increments on every bar. On the trigger bar it resets to zero. Two lines of logic:
private int barsSinceCross;
protected override void OnBarUpdate()
{
if (CurrentBar < Period + 1) return;
if (CrossAbove(Close, SMA(Period), 1))
barsSinceCross = 0;
else
barsSinceCross++;
// Use barsSinceCross however you need it:
// "only enter if at least 3 bars since the cross" etc.
}
The order matters. Reset first, then increment on non-trigger bars. If you flip the order, the trigger bar itself reads back as 1 instead of 0, which is usually wrong.
🔁 Using a Series<int> Instead of a Scalar
A scalar only holds the current bar’s value. If a strategy wants to check “what was the counter three bars ago,” scalar doesn’t help. Series<int> (Week 10) gives you bar-indexed history for free:
private Series<int> sBarsSinceCross;
protected override void OnStateChange()
{
// ... SetDefaults ...
if (State == State.DataLoaded)
sBarsSinceCross = new Series<int>(this, MaximumBarsLookBack.Infinite);
}
protected override void OnBarUpdate()
{
if (CurrentBar < Period + 1) { sBarsSinceCross[0] = 0; return; }
if (CrossAbove(Close, SMA(Period), 1))
sBarsSinceCross[0] = 0;
else
sBarsSinceCross[0] = sBarsSinceCross[1] + 1;
}
[Browsable(false)][XmlIgnore]
public Series<int> BarsSinceCross
{
get { Update(); return sBarsSinceCross; }
}
Notice the increment pattern: sBarsSinceCross[0] = sBarsSinceCross[1] + 1. The previous bar’s value plus one. That’s how you express “increment from the last stored value” with a Series<int>.
🎯 Condition-Met vs Trigger-Fired
Two subtly different questions. “How many bars has the condition been continuously true?” is different from “how many bars since the trigger last fired?” The counter resets in different places:
- Trigger-fired pattern: reset when the trigger fires, increment every other bar. Counts “how long since.” Common for cooldowns, re-entry delays, signal staleness.
- Condition-met pattern: reset when the condition is false, increment when it’s true. Counts “how long has this been the case.” Common for “has price been above SMA for at least 5 bars.”
Both are two-line patterns. The difference is which way the reset and increment point.
🆚 Built-in BarsSinceXxx Comparisons
NinjaScript has Bars.BarsSinceSession (bars since the session started) and, in strategies, BarsSinceEntryExecution and BarsSinceExitExecution (bars since the last entry or exit fill). These are built-in scalars computed from NinjaTrader’s own session and order tracking.
Use the built-ins when they match. For session-relative logic, Bars.BarsSinceSession is cleaner than rolling your own. For entry-relative logic in a strategy, BarsSinceEntryExecution is the idiomatic choice. For custom conditions — “bars since my crossover signal” — you need your own counter because there’s no built-in for it.
🛠️ The Anchor Example — MySma With a BarsSinceCross Counter
Extending MySma with a public BarsSinceCross output that a strategy can read:
private Series<int> sBarsSinceCross;
public override void OnStateChange()
{
// ... SetDefaults / AddPlot as before ...
if (State == State.DataLoaded)
sBarsSinceCross = new Series<int>(this, MaximumBarsLookBack.Infinite);
}
protected override void OnBarUpdate()
{
if (CurrentBar < Period + 1)
{
sBarsSinceCross[0] = 0;
return;
}
Values[0][0] = SMA(Period)[0];
if (CrossAbove(Close, SMA(Period), 1))
sBarsSinceCross[0] = 0;
else
sBarsSinceCross[0] = sBarsSinceCross[1] + 1;
}
[Browsable(false)][XmlIgnore]
public Series<int> BarsSinceCross
{
get { Update(); return sBarsSinceCross; }
}
A strategy calling mySma.BarsSinceCross[0] gets the count on the current bar; [5] gives the count five bars ago. “Only enter if at least 3 bars since the cross” becomes if (mySma.BarsSinceCross[0] >= 3).
📝 Pitfalls Checklist
- Forgetting to increment on non-trigger bars. Counter stays at zero forever. A check of “bars since” always reads zero and the calling logic never waits for anything.
- Incrementing before checking the trigger. Trigger bar ends up reading as 1 instead of 0. Subtle off-by-one that breaks “on the trigger bar itself” logic.
- Scalar when you needed a Series. A strategy that reads historical counter values needs
Series<int>. A scalar only gives the current bar. - Reset semantics unclear — does the trigger bar itself count as bar 0 or is bar 0 the one AFTER the trigger? Pick a definition and document it. The examples above treat the trigger bar as bar 0.
- Confusing with strategy built-ins.
BarsSinceEntryExecutionis strategy-only and tied to orders. Rolling your own counter for a custom condition isn’t the same thing and doesn’t share state with order tracking. - Not guarding the Series<int> startup.
sBarsSinceCross[1]throws ifCurrentBar < 1. Seed the Series at[0] = 0on early bars before using the increment pattern.
🎉 Prop Trading Discounts
💥89% off at Bulenox.com with the code MDT89






