If you’ve ever wanted to track live trades, monitor bid/ask changes, or build a tool that reacts faster than a bar can form, OnMarketData() is where the action begins.

Unlike OnBarUpdate(), which reacts to completed bars, OnMarketData() responds to every change in Level I market data — including bid, ask, and last price updates. That means it can give you real-time visibility into what the market is doing right now — not a second later.

This makes it a must-know method for scalpers, DOM traders, or anyone developing indicators and strategies that rely on speed and precision. But it also comes with some important limitations and gotchas (especially around performance and TickReplay) that can trip you up if you’re not careful.

In this post, we’ll break down how OnMarketData() works, what kind of data it provides, how to use it correctly, and when you might want to explore its more powerful (but heavier) cousin, OnMarketDepth().

Let’s dive into the tick-by-tick world of NinjaScript. 👇

👉 Save this official NinjaTrader Help Guide link:

  1. OnMarketData() – NinjaTrader Help Guide
  2. MarketDataEventArgs – NinjaTrader Help Guide
  3. OnMarketDepth() – NinjaTrader Help Guide
  4. MarketDepthEventArgs – NinjaTrader Help Guide
  5. Tick Replay – NinjaTrader Help Guide

If you’re new here, make sure you check out the earlier posts too:

🤔 What Is OnMarketData()?

OnMarketData() is an event-driven method in NinjaScript that fires every time Level I market data changes. That means whenever there’s a new bid, ask, or last traded price, this method is called — instantly and in the correct sequence.

Unlike OnBarUpdate(), which is based on bar closures or ticks per bar, OnMarketData() is tied directly to raw exchange data. It’s faster and more granular, making it ideal for:

  • Real-time bid/ask spread tracking
  • Tape reading and uptick/downtick logic
  • Building ultra-responsive tools or alerts
  • Monitoring order flow shifts as they happen

🧠 Important:

  • This method is not called during backtests unless you explicitly enable TickReplay
  • Even with TickReplay, only last trade data (MarketDataType.Last) is replayed — bid/ask updates are not streamed historically

Here’s what a basic usage pattern looks like:

protected override void OnMarketData(MarketDataEventArgs marketData)
{
    if (marketData.MarketDataType == MarketDataType.Bid)
        Print("New bid price: " + marketData.Price);
}

🧩 Market Data Types: What Can Trigger OnMarketData()?

Every time OnMarketData() is triggered, it includes a MarketDataType — telling you what kind of data changed. Most scripts focus on Bid, Ask, and Last, but there’s more under the hood.

Here’s a breakdown of the available types:

🔹 Common Types You’ll Use Most

  • MarketDataType.Bid – Best bid price (highest price buyers are offering)
  • MarketDataType.Ask – Best ask price (lowest price sellers are offering)
  • MarketDataType.Last – Price and volume of the most recent trade

These three types are used constantly in real-time strategies. Want to track spread? Use Bid and Ask. Want to log trade size and direction? Use Last.

🔸 Additional (Feed-Dependent) Types

Depending on your data provider, you may also have access to:

  • MarketDataType.DailyHigh
  • MarketDataType.DailyLow
  • MarketDataType.DailyVolume
  • MarketDataType.LastClose (previous session’s close)
  • MarketDataType.Opening
  • MarketDataType.Settlement
  • MarketDataType.OpenInterest (IQFeed, Kinetick only)

These are less frequently used in real-time logic, but may be helpful for overlaying session-level reference data in custom indicators.

🧠 Tip:
Not all market data feeds support every MarketDataType. If you reference an unsupported type, you might get nothing — no error, no data — just silence. Always use conditional checks.

if (marketData.MarketDataType == MarketDataType.OpenInterest)
{
    Print("Open interest: " + marketData.Price);
}

🔍 Inside the MarketDataEventArgs

When OnMarketData() is called, it passes in a MarketDataEventArgs object — this is your data packet, and it contains everything you need to understand the market change.

Here’s a breakdown of what’s inside:

Property Description
MarketDataType Identifies what changed: Bid, Ask, Last, etc.
Price The price of the market update
Volume Volume associated with the update (non-zero mostly for Last)
Time Timestamp of the event, as a DateTime
Instrument The instrument that the data belongs to
IsReset Used internally for UI reset detection — usually not needed in indicators
ToString() Quick snapshot of the full object — useful for logging/debugging

👇 Example Use

protected override void OnMarketData(MarketDataEventArgs e)
{
    if (e.MarketDataType == MarketDataType.Last)
    {
        Print($"Trade @ {e.Price} | Volume: {e.Volume} | Time: {e.Time}");
    }
}

🧠 Tip: The Instrument property is useful when you’re working with multiple instruments or custom logic that dynamically changes what you’re analyzing — but for most indicators tied to a single chart instrument, you won’t need to reference it directly.

⏪ TickReplay Behavior and Important Execution Notes

OnMarketData() is powerful — but by default, it won’t run on historical data. If you’re loading a chart or running a backtest, nothing will fire unless you turn on TickReplay.

But here’s where it gets tricky.

✅ What TickReplay Does

  • It replays historical tick-by-tick Last trade data, allowing OnMarketData() to work as if it were real-time.
  • It lets you see trade-by-trade price and volume for historical sessions.

❌ What TickReplay Doesn’t Do

  • It does not replay Bid or Ask events as they happened.
  • Instead, it snapshots the bid/ask price at the time of each trade (Last) — meaning no intratick bid/ask changes, and no bid/ask volume.
  • If you need historical bid/ask data, you’ll have to use additional AddDataSeries() calls and handle it in OnBarUpdate().

🧠 Important: TickReplay must be enabled in two places:

  1. The chart or strategy analyzer UI (Right-click > Data Series > Enable “Tick Replay”)
  2. Your script must be compatible — usually by adding tick-sensitive logic.

⚠️ Other Must-Know Execution Details

These don’t get talked about enough — but they matter:

  • 🕒 OnMarketData() is always called after OnBarUpdate()
    This can affect sequencing if you’re comparing bar-based and tick-based data.
  • 💡 OnMarketData() does not depend on Calculate.OnEachTick or OnPriceChange. It runs regardless of your Calculate setting.
  • 🧹 Don’t leave an unused OnMarketData() method in your script.
    If it’s there — even if empty — NinjaTrader still attaches a market data stream to your script and consumes CPU.

🔒 Bonus: OnMarketData() vs. OnMarketDepth() (Level I vs. Level II)

If OnMarketData() gives you the top-of-book, then OnMarketDepth() opens the door to the entire order book. But it’s not just “more data” — it’s a different type of data, and it comes with different responsibilities.

Here’s a side-by-side breakdown:

Feature OnMarketData() (Level I) OnMarketDepth() (Level II)
Data Type Top-of-book only (Bid, Ask, Last) Full depth ladder (multiple levels)
Events Price/volume updates Add, Update, Remove operations
Book Depth Only 1 level Full depth across price levels
Market Maker Info ❌ Not available ✅ MarketMaker string (if available)
Historical Access ✅ With TickReplay (Last only) ❌ Not supported historically
Performance Moderate High CPU usage (use with care)
Typical Use Case Spread tracking, upticks DOM tools, liquidity heatmaps

🧠 How OnMarketDepth() Works

When used, it fires every time any level in the order book updates, and passes in a MarketDepthEventArgs object with details like:

  • MarketDataType.Bid or MarketDataType.Ask
  • OperationAdd, Update, or Remove
  • Position → 0-based index in the depth ladder
  • Price, Volume, Time, and MarketMaker

Here’s a simple example that prints changes to the ask side:

protected override void OnMarketDepth(MarketDepthEventArgs depth)
{
    if (depth.MarketDataType == MarketDataType.Ask && depth.Operation == Operation.Update)
        Print($"Ask Level {depth.Position} updated: {depth.Price} @ {depth.Volume}");
}

🧠 Tip: Like OnMarketData(), do not leave OnMarketDepth() in your script unless you’re actively using it — or it will still attach a full depth stream and consume resources.

🎯 Real-World Use Cases for OnMarketData()

Now that you understand how OnMarketData() works, let’s look at how you can actually use it in live scripts. These aren’t just theory — they’re patterns you’ll see in scalping indicators, execution filters, and custom DOM tools.

🟡 1. Spread Tracking (Bid/Ask Difference)

Measure the real-time spread between bid and ask to detect tight market conditions or widening liquidity gaps.

double currentBid = 0;
double currentAsk = 0;

protected override void OnMarketData(MarketDataEventArgs e)
{
    if (e.MarketDataType == MarketDataType.Bid)
        currentBid = e.Price;
    else if (e.MarketDataType == MarketDataType.Ask)
        currentAsk = e.Price;

    if (currentBid > 0 && currentAsk > 0)
        Print($"Spread: {currentAsk - currentBid}");
}

🔵 2. Tape Reading (Upticks vs Downticks)

Use Last trade data to detect buying or selling pressure based on tick direction.

double lastPrice = 0;

protected override void OnMarketData(MarketDataEventArgs e)
{
    if (e.MarketDataType == MarketDataType.Last)
    {
        string dir = e.Price > lastPrice ? "" : e.Price < lastPrice ? "" : "-";
        Print($"{e.Time}: {dir} {e.Price} @ {e.Volume}");
        lastPrice = e.Price;
    }
}

🔴 3. High Volume Alerts

Trigger a sound, chart marker, or alert when a large trade comes through:

protected override void OnMarketData(MarketDataEventArgs e)
{
    if (e.MarketDataType == MarketDataType.Last && e.Volume >= 100)
    {
        Print($"🔔 Large trade detected: {e.Price} @ {e.Volume}");
        Alert("BigTrade", Priority.High, "Large Trade Hit!", NinjaTrader.Core.Globals.InstallDir + @"\sounds\Alert3.wav", 0, Brushes.Red, Brushes.White);
    }
}

These are just the beginning — you can also build live bid/ask ladders, track delta, or trigger entries based on microstructure shifts.

⚠️ Pitfalls and Performance Traps

OnMarketData() gives you raw, real-time power — but that also means you can accidentally crash your script or flood your system if you’re not careful. Here are the most common mistakes traders make when working with this method:

1. Leaving the Method in Your Script Unused

Even if your OnMarketData() method is empty, NinjaTrader will still attach a market data stream to your script — wasting CPU resources.

🧹 Solution: Only include OnMarketData() if you're actively using it. Remove it otherwise.

⚠️ 2. Logging or Drawing on Every Tick

Printing to the Output window or drawing to the chart on every bid, ask, or trade update will kill your performance — especially during volatile sessions.

🔇 Solution: Add filters or throttling logic.
For example, only log when the price changes by a tick or more, or once every 500ms.

🔄 3. Forgetting That You Must Track Your Own History

OnMarketData() doesn’t store anything for you. If you want to compare current price to the previous tick, you must manage that with your own variables.

💡 Solution: Use local variables like lastBid, lastVolume, etc., and update them as needed.

🧠 4. Confusing Calculate.OnEachTick with OnMarketData() Triggers

OnMarketData() is completely independent of your Calculate setting. It fires based on market data — not bar logic.

Clarification: You can use OnBarClose, OnPriceChange, or OnEachTickOnMarketData() will still fire when level 1 data changes.

🕒 5. Assuming Execution Order

You might think OnMarketData() always fires before or after OnBarUpdate() — but it’s not so predictable.

📌 Fact: NinjaTrader guarantees that OnMarketData() is called after OnBarUpdate(), but never the other way around.

🏁 Final Thoughts

OnMarketData() is one of the most powerful tools in your NinjaScript toolkit — but it’s also one of the easiest to misuse.

If you’re building something that needs to respond instantly to price changes, track the bid/ask spread, or monitor trade-by-trade volume, then this is exactly where you should be working. It’s the foundation for building tape readers, scalping filters, DOM-style visualizations, and alert systems that react before a bar even forms.

But power comes with responsibility:

  • Don’t leave it in your script unless you’re actually using it.
  • Use filters to avoid flooding your CPU and Output window.
  • Understand the limitations of TickReplay if you’re relying on historical data.

And if you’re ever wondering whether you should use OnMarketData(), ask yourself:

"Am I trying to act on what just happened — or what the bar says after it’s done?"

If it’s the first — welcome to tick logic. You’re in the right place. ⚡

🎉 Prop Trading Discounts

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

Categorized in:

Learn NinjaScript,

Tagged in: