Where most of your trading logic happens.

In our last articles, we explored NinjaScript structure, variables and properties, and how NinjaTrader manages a script’s lifecycle with OnStateChange().

Now it’s time to dive into the true “heartbeat” of NinjaScript development: OnBarUpdate() — where most calculations, decisions, and signals happen.

👉 Save this official NinjaTrader Help Guide link:

🔹 What is OnBarUpdate()?

OnBarUpdate() is automatically called by NinjaTrader every time your data updates.

This is where your script:

  • Calculates indicator values
  • Checks trading conditions
  • Updates plots and visuals
  • Triggers entries, exits, and signals

Think of it as the “heartbeat” that processes each new data event.

Whether it triggers once per bar or on every tick depends on your Calculate setting.

🕰️ When Does OnBarUpdate Fire?

NinjaTrader connects to your data provider (like NinjaTrader Brokerage, CQG, Rithmic, etc.) and receives a continuous flow of market data for each instrument you have loaded.

This market data arrives as:

  • Ticks (each individual trade)
  • Price Changes (only when the price changes from an incoming tick compared to the prior tick)
  • Completed Bars (aggregated after a bar fully forms)

Each time NinjaTrader detects an event based on how you told it to process data (Calculate mode), it triggers OnBarUpdate().

Your script doesn’t control the raw data flow — but you do control when your code runs based on which type of data updates you choose to react to.

In other words:

  • If you want your logic to fire once per completed bar, use OnBarClose.
  • If you want to react immediately when price moves, use OnPriceChange.
  • If you need extreme precision for every tick, use OnEachTick.

👉 Choosing the right trigger keeps your script efficient and focused.

Calculate Mode When It Fires Plain English
OnBarClose After a bar fully closes One calculation per completed bar
OnPriceChange Every price change Only when price actually moves
OnEachTick Every tick Every single incoming tick

Pro Tip:
When choosing between OnPriceChange and OnEachTick, prefer OnPriceChange unless you truly need tick-by-tick detail. OnEachTick can be CPU-heavy — best used for order flow or volume-based indicators and strategies.

🛠️ BarsInProgress (BIP), CurrentBar, CurrentBars[], and BarsAgo Explained

Understanding how NinjaTrader tracks bars and data series is important for avoiding common bugs!

📌 What is BarsInProgress (BIP)?

In multi-series scripts (multiple timeframes or instruments), BarsInProgress identifies which series triggered OnBarUpdate.

  • BarsInProgress == 0 → Primary data series
  • BarsInProgress == 1 → First added series (e.g., 5-min)
  • BarsInProgress == 2 → Second added series (e.g., Daily)

How series are numbered:
It’s based on the order you add them in State.Configure.

🔥 Real Example:

protected override void OnStateChange()
{
    if (State == State.Configure)
    {
        AddDataSeries(BarsPeriodType.Minute, 5); // BIP 1
        AddDataSeries(BarsPeriodType.Day, 1);    // BIP 2
    }
}

protected override void OnBarUpdate()
{
    if (BarsInProgress == 0)
    {
        // Main chart logic
    }
    else if (BarsInProgress == 1)
    {
        // 5-minute series logic
    }
    else if (BarsInProgress == 2)
    {
        // Daily series logic
    }
}

📌 What is CurrentBar?

CurrentBar is NinjaTrader’s way of keeping track of how many bars have been processed on the active data series.

  • It starts at 0 for the very first bar (the oldest bar on the left side of your chart).
  • As new bars form to the right, CurrentBar increases by 1.
  • Each bar gets its own unique index — 0, 1, 2, 3… — moving left ➔ right across the chart.

🧠 Think of it like a bar counter that starts at zero and adds one every time a new candle appears.

Example on Chart CurrentBar Value
First historical bar (leftmost) 0
Second bar 1
Third bar 2
... ...
Most recent forming bar Highest number

📌 What is CurrentBars?

CurrentBars (with an “s”) tracks how many bars have been processed for each data series in your script — not just the main chart.

It is an array indexed by BarsInProgress.

✅ This matters when you’re working with multiple series (like different timeframes or instruments) using AddDataSeries().

🛡️ Plain English:

  • CurrentBars[0] = Number of bars on your main chart series
  • CurrentBars[1] = Number of bars on your first added series
  • CurrentBars[2] = Number of bars on your second added series
    …and so on.

🔎 Key Difference:

Concept Meaning
CurrentBar Refers to the current bar of the active BarsInProgress
CurrentBars[idx] Lets you manually check the bar count of any series (by index)

✅ Practical Example:

if (CurrentBars[0] < 20 || CurrentBars[1] < 10)
    return;

This protects your script — it checks that both your main chart and your secondary data series have enough bars loaded before trying to access their historical data.

📌 What is BarsAgo?

BarsAgo is how you reference previous bars relative to the bar NinjaTrader is currently processing.

It counts backward in time — starting from the most recent bar. This is the opposite of CurrentBar, which counts forward from the oldest bar!

Plain English:

  • BarsAgo = 0 → The current bar being processed
  • BarsAgo = 1 → One bar ago
  • BarsAgo = 2 → Two bars ago
  • …and so on.

Once you understand that BarsAgo counts backwards from the current bar, it becomes easy to pull historical values.

Here’s a simple table showing how you access different past bar prices using BarsAgo indexing:

Example Meaning
Close[0] Current bar's close price
Close[1] Previous bar's close price
High[2] High from two bars ago

Important with Different Calculate Modes

  • OnBarClose: BarsAgo references the most recently finalized closed bars.
  • OnPriceChange / OnEachTick: BarsAgo 0 keeps updating mid-bar as price changes (i.e., references the bar that is currently forming).

🛡️ Waiting for Enough Bars

When your script loads, NinjaTrader starts processing bars immediately — but that doesn’t mean you always have enough bars available for safe calculations.

If you try to reference a bar (for example, Close[20]) before 21 bars have formed, your script will throw an out of range error and stop calculating.

✅ That’s why you must always check how many bars are available before doing anything that looks back — like calculating moving averages, using BarsAgo references, or accessing historical prices.

The number of available bars can vary depending on:

  • How much historical data NinjaTrader loads
  • How many bars your strategy needs to function
  • Whether you’re processing the primary or a secondary data series

Plain English:
If you’re going to look back 20 bars, you need at least 21 bars total: bar 0 (current) + 20 bars back.

Basic Single-Series Check

if (CurrentBar < 20)
    return;

Multi-Series Check

if (CurrentBars[0] < 20 || CurrentBars[1] < 20)
    return;

✅ Checks that both series are ready before calculating.

Dynamic Maximum Period Check

Instead of hardcoding, dynamically set the longest period you need.

private int maxPeriod;

protected override void OnStateChange()
{
    if (State == State.DataLoaded)
    {
        maxPeriod = Math.Max(FastPeriod, SlowPeriod);
    }
}

protected override void OnBarUpdate()
{
    if (CurrentBar < maxPeriod) // if CurrentBar is less than maxPeriod, do not process any further
        return;
        
    // Safely process your codde here after there are enough bars
}

🔥 Handling Different Behavior Based on Calculate

Before we jump into examples, let’s first review how NinjaTrader processes incoming market data.

You control when your script processes price updates using the Calculate setting.

The three main modes:

Calculate Mode What It Means Example (Price Starts at 4300)
OnBarClose Logic runs once after the full bar is complete If price moves from 4300 ➡️ 4310 ➡️ 4295 ➡️ 4305, only after 4305 closes the bar does OnBarUpdate fire
OnPriceChange Logic runs whenever the close price changes As soon as price ticks from 4300 to 4300.25, OnBarUpdate fires
OnEachTick Logic runs for every market tick (price or volume update) Every tick/trade triggers OnBarUpdate, even if price doesn't change

Plain English:

  • OnBarClose: Waits patiently for the whole candle to finish.
  • OnPriceChange: Wakes up every time the last traded price changes.
  • OnEachTick: Never sleeps—reacts to all incoming ticks (i.e., trades).

🖌️ Example Flow: Updating a Plot Called MyPlot

Let’s say you are updating a plot value called MyPlot inside OnBarUpdate.

Here’s how it behaves across different Calculate modes:

  • OnBarClose: You update MyPlot once per completed bar.
  • OnPriceChange: You update MyPlot every time the close price changes mid-bar.
  • OnEachTick: You update MyPlot every tick, even if price doesn’t change (e.g., you receive 10 ticks/trades at the same price).

Basic Example Logic:

The below is a very simple example of how we can update MyPlot to a value of 1 if the current close is greater than a 20 period SMA and a value of -1 if the close is less than the 20 period SMA.

if (Close[0] > SMA(20)[0])
    MyPlot[0] = 1; // Bullish
else if (Close[0] < SMA(20)[0])
    MyPlot[0] = -1; // Bearish

🔄 How to Handle MyPlot Updates Properly

In OnBarClose mode, this is easy — because the bar is finished and won’t change again. But in OnPriceChange and OnEachTick modes, price keeps changing during the current bar — and whether you need to reset your plots or variables depends on what type of logic you’re coding.

If your logic updates values based on a simple calculation that always runs every tick or price change (like a moving average), you likely don’t need to manually reset anything — because each update replaces the previous value cleanly.

If your logic is conditional (like signaling a Long or Short entry), you should reset your value before reapplying your conditions — or else an old signal could “stick” and still show even when it’s no longer valid! Note: there are scenarios where a trader would want to “see” those historical, invalid signals, but we won’t get into that now.

Practical Example:

Logic Type Reset Needed? Why?
Moving Average No Always recalculating fresh values
Signal Generation (Long/Short) Yes Conditions can change, and old signals can "stick"

🔁 Resetting Logic for OnPriceChange or OnEachTick Calculations

✅ If your indicator sets conditional signals, you should always clear your signal value first, then apply your fresh logic (there are scenarios where you would not want to clear your old signals, but we will not get into that now).

Otherwise, old/stale values could “stick” from ticks ago — showing false signals that aren’t valid anymore.

Example without reset ❌

// If bullish setup, mark signal
if (Close[0] > SMA(20)[0])
    MyPlot[0] = 1;

✅ Problem: If the condition was true 3 ticks ago but now it’s not, the old signal remains because nothing clears it.

Example with reset ✅

// Reset before re-evaluating
MyPlot.Reset();

if (Close[0] > SMA(20)[0])
    MyPlot[0] = 1;
else if (Close[0] < SMA(20)[0])
    MyPlot[0] = -1;

An alternative way to “reset” a Series<int> that tracks signals:

// Reset before re-evaluating
MyIntSeries0] = 0;

if (Close[0] > SMA(20)[0])
    MyIntSeries[0] = 1;
else if (Close[0] < SMA(20)[0])
    MyIntSeries[0] = -1;

✅ Now: Each tick or price change, we clean the value first and only set a new signal if it’s valid right now.

📚 Bonus Tip: Know about NinjaTrader’s Reset() Method

NNinjaTrader’s built-in Reset() method is a special function used on Series<T> objects.

What Reset() Actually Does:

  • Reset() sets an internal marker that tells NinjaTrader this bar’s value is not valid for calculations or plotting.
  • When you call something like MyPlot.Reset(), the value itself (like 0.0 for a Series<double>) still exists in memory — but NinjaTrader knows not to treat it as a valid datapoint.
  • This helps you prevent old or invalid signals from sticking when working with fast-updating data.

Here’s what different Series types will look like after you call Reset():

Series Type Value After Reset()
Series false
Series 0.0
Series DateTime.MinValue
Series 0
Series 0
Series 0
Series null

Practical Tip:
Use IsValidDataPoint(int barsAgo) to check if the current bar’s value is valid after using Reset(), especially if you are chaining logic that expects only valid values.

Real-World Example:
When resetting a plot in OnEachTick or OnPriceChange mode, Reset() ensures you don’t accidentally carry over a stale signal from a previous tick.

⚠️ Common Beginner Mistakes

Even experienced traders run into coding traps when starting with NinjaScript. Here are some frequent issues beginners hit when working with OnBarUpdate() — and how to avoid them:


❌ Not Checking CurrentBar Before Accessing Past Bars

If you try to access Close[10] but only have 5 bars loaded, your script will throw an error and crash.

✅ Always protect your code:

if (CurrentBar < 10)
    return;

// Safe to reference Close[10]

❌ Confusing BarsAgo vs CurrentBar

  • BarsAgo: Counts backward from the most recent bar (right to left).
  • CurrentBar: Counts forward from the oldest bar (left to right).

✅ Tip: If you’re looking back a few bars to check previous highs/lows, use BarsAgo.


❌ Forgetting to Reset Values in OnPriceChange or OnEachTick

When coding with Calculate.OnPriceChange or Calculate.OnEachTick, failing to reset your plots or internal flags can cause old/stale signals to “stick” when market conditions change mid-bar.

✅ Always reset at the start of OnBarUpdate:

MyPlot.Reset();

❌ Not Handling BarsInProgress in Multi-Series Scripts

If you add multiple data series (like 5-min and 1-hour) but don’t check BarsInProgress, your logic could run at the wrong times or based on the wrong instrument.

✅ Always check:

if (BarsInProgress != 0)
    return; // Only run primary series logic

❌ Using OnEachTick Unnecessarily

Many new coders think OnEachTick is “more accurate” — but it can overwhelm your strategy or indicator with thousands of updates per minute.

✅ Use OnPriceChange unless you specifically need true tick-by-tick updates (e.g., volume analysis or order flow indicators).


Pro Tip:
Always slow down, use clear condition checks, and write defensive code — especially when your script depends on fast live market updates!


🎯 Final Thoughts

Learning how NinjaTrader processes market data through OnBarUpdate() is a huge step forward in your NinjaScript journey. This is the core of where most indicators and strategies come to life.

You now have a solid understanding of:

  • When and why OnBarUpdate() fires
  • How different Calculate modes affect the timing and behavior of your code
  • How NinjaTrader organizes and manages multiple data streams using BarsInProgress
  • The differences between CurrentBar, CurrentBars[], and BarsAgo
  • Why it’s essential to wait for enough bars before running logic
  • How to properly reset values in live market conditions when using OnPriceChange or OnEachTick

These aren’t just technical details — they’re the foundation for writing accurate, stable, and reliable scripts.

✅ If you take the time to master these concepts now, you’ll avoid common frustrations later when you start layering in indicators, entries, exits, filters, and multiple timeframes.

Keep practicing, re-read sections when you need to, and build the habit of thinking through how your script reacts to live market behavior — not just what it looks like on a static chart.

You’re now equipped to move beyond simple examples and start crafting real, practical trading logic inside NinjaTrader!

🎉 Prop Trading Discounts

💥91% off at Bulenox.com with the code MDT91

Categorized in:

Learn NinjaScript,

Tagged in: