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:
- OnMarketData() – NinjaTrader Help Guide
- MarketDataEventArgs – NinjaTrader Help Guide
- OnMarketDepth() – NinjaTrader Help Guide
- MarketDepthEventArgs – NinjaTrader Help Guide
- Tick Replay – NinjaTrader Help Guide
📚 Related NinjaScript Lessons So Far
If you’re new here, make sure you check out the earlier posts too:
- Understanding the NinjaTrader Indicator Structure
- Variables and Properties in NinjaScript
- OnStateChange() Lifecycle Explained
- OnBarUpdate() Fully Explained
- Learn how to use Series<T>
- Learn NinjaScript Plots
- Learn NinjaScript Lines
- How to use NinjaScript Brushes
- Creating and Using Methods & Functions
- Add-On Your Way to Cleaner Code
- Using Lists, Dictionaries, and Arrays
- Creating and Using Classes Inside an Indicator
- Drawing on the Chart with Draw Objects
- Alerts, PlaySound, and Rearm Control
- Create and Use Custom Enums
- Booleans, If Statements, and Conditions
- Loops (For, While, Foreach)
- Try/Catch and Basic Error Handling
- How to Debug Your Indicator
🤔 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, allowingOnMarketData()
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
orAsk
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 inOnBarUpdate()
.
🧠 Important: TickReplay must be enabled in two places:
- The chart or strategy analyzer UI (Right-click > Data Series > Enable “Tick Replay”)
- 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 afterOnBarUpdate()
This can affect sequencing if you’re comparing bar-based and tick-based data. - 💡
OnMarketData()
does not depend onCalculate.OnEachTick
orOnPriceChange
. It runs regardless of yourCalculate
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
orMarketDataType.Ask
Operation
→Add
,Update
, orRemove
Position
→ 0-based index in the depth ladderPrice
,Volume
,Time
, andMarketMaker
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 likelastBid
,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 useOnBarClose
,OnPriceChange
, orOnEachTick
—OnMarketData()
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 thatOnMarketData()
is called afterOnBarUpdate()
, 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