Building Custom NinjaTrader Strategies with NinjaScript
· Strategy Guides · By the NocNoe Team
Learn how to build custom NinjaTrader strategies with NinjaScript. Step-by-step guide covering the lifecycle, entry logic, risk management, and debugging.
Every futures trader hits the same ceiling. You know your plan. You see the setups. But you can't execute them fast enough, consistently enough, or without emotion creeping in. That's exactly where NinjaScript changes the game.
NinjaScript is the C#-based programming language built into NinjaTrader 8. It lets you turn any trading idea — from a simple moving average crossover to a multi-timeframe momentum filter — into a fully automated strategy that enters, manages, and exits trades without you touching the keyboard.
This guide walks you through building custom NinjaTrader strategies with NinjaScript from scratch. No prior coding experience required. By the end, you'll understand the core architecture, write your first strategy, add risk management, and know how to debug and test before going live.
What Is NinjaScript and Why Does It Matter?
NinjaScript is NinjaTrader's proprietary scripting language. Under the hood, it's C# — one of the most widely used programming languages in the world. That means you get access to the full .NET framework, strong typing, and a massive ecosystem of learning resources.
But NinjaScript isn't just C# with a new name. It adds a layer of trading-specific methods and properties that make strategy development remarkably efficient. Over 100 built-in indicators. Native order management. Real-time and historical data access. Backtesting integration. All of it compiled and running natively inside NinjaTrader for maximum performance.
If you've been using the Strategy Builder's drag-and-drop interface, NinjaScript is the next level. The Builder is great for prototyping simple ideas, but the moment you need dynamic parameters, multi-timeframe analysis, or custom position management, you need code. NinjaScript gives you that control.
Setting Up Your Development Environment
Before writing a single line of code, you need two things open inside NinjaTrader 8:
- NinjaScript Editor — Go to the Control Center, click New → NinjaScript Editor. This is your workspace. It includes syntax highlighting, auto-completion, and a built-in compiler. Every time you press F5, NinjaTrader compiles all custom scripts simultaneously.
- NinjaScript Output Window — Go to New → NinjaScript Output. This is your debug console. Every
Print()statement in your code shows up here. Keep it visible at all times during development.
To create a new strategy, expand the Strategies folder in the NinjaScript Explorer panel (left side of the editor), right-click, and select New Strategy. The Strategy Wizard walks you through naming, default properties, and generates the boilerplate code. Give your strategy a descriptive name — something like MyEMACrossStrategy — and click Finish.
NinjaTrader generates a class that extends Strategy with two pre-built methods: OnStateChange() and OnBarUpdate(). These are the foundation of every NinjaScript strategy.
The NinjaScript Lifecycle: OnStateChange Explained
Every NinjaScript object — whether it's an indicator or a strategy — goes through a defined lifecycle. The OnStateChange() method fires each time your script transitions between states. Understanding this lifecycle is the single most important concept in NinjaScript development.
Here are the states that matter most for strategy builders:
- State.SetDefaults — Runs when your strategy first loads, even just for displaying it in a UI list. Set your default property values here: name, description,
Calculatemode, and any user-facing parameters. Keep this lean — no heavy logic. - State.Configure — Runs after the user clicks OK to apply the strategy. Add secondary data series here with
AddDataSeries(). This is where you set up multi-timeframe or multi-instrument configurations. - State.DataLoaded — All bars, instruments, and trading hours are now available. Instantiate your indicators here (SMA, EMA, ATR, etc.) and initialize any custom
Series<T>variables. Never create indicators inSetDefaults— the data doesn't exist yet. - State.Historical — Your strategy is processing historical bars. Use this to set flags if you want different behavior during backtesting vs. live trading.
- State.Realtime — Live data is flowing. Your strategy is now making real decisions. This is where you might enable alerts or switch to full-size position logic.
- State.Terminated — The strategy is shutting down. Clean up any resources, close file handles, dispose of objects.
A common pattern is setting a boolean flag during State.Historical and checking it in OnBarUpdate() to prevent certain logic from firing on old data:
private bool isRealtime = false;
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Name = "MyStrategy";
Calculate = Calculate.OnBarClose;
}
else if (State == State.DataLoaded)
{
// Initialize indicators here
}
else if (State == State.Realtime)
{
isRealtime = true;
}
}
Writing Your First Strategy: OnBarUpdate
OnBarUpdate() is where your trading logic lives. NinjaTrader calls this method every time a bar updates — the frequency depends on your Calculate setting (OnBarClose, OnEachTick, or OnPriceChange).
Here's a simple EMA crossover strategy that enters long when the fast EMA crosses above the slow EMA, and exits when it crosses below:
private EMA fastEMA;
private EMA slowEMA;
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Name = "EMACrossStrategy";
Calculate = Calculate.OnBarClose;
FastPeriod = 9;
SlowPeriod = 21;
}
else if (State == State.DataLoaded)
{
fastEMA = EMA(Close, FastPeriod);
slowEMA = EMA(Close, SlowPeriod);
}
}
protected override void OnBarUpdate()
{
if (CurrentBar < SlowPeriod)
return;
if (CrossAbove(fastEMA, slowEMA, 1))
EnterLong();
if (CrossBelow(fastEMA, slowEMA, 1))
ExitLong();
}
[NinjaScriptProperty]
public int FastPeriod { get; set; }
[NinjaScriptProperty]
public int SlowPeriod { get; set; }
</code>
Key takeaways from this example:
- Guard clause —
if (CurrentBar < SlowPeriod) return;prevents errors when there aren't enough bars to calculate the indicator. Always include this. - CrossAbove / CrossBelow — Built-in helper methods that check if one series crossed another within a specified number of bars. The
1means "within the last 1 bar." - EnterLong / ExitLong — Managed order methods. NinjaTrader handles the order submission, fill tracking, and position management for you.
- [NinjaScriptProperty] — Exposes
FastPeriodandSlowPeriodas user-editable parameters in the strategy settings window. No recompiling needed to change values.
Adding Risk Management: Stops, Targets, and Position Sizing
A strategy without risk management is a liability. NinjaScript makes it straightforward to add protective orders. The simplest approach uses SetStopLoss() and SetProfitTarget() inside OnStateChange() at the DataLoaded state:
else if (State == State.DataLoaded)
{
SetStopLoss(CalculationMode.Ticks, 20);
SetProfitTarget(CalculationMode.Ticks, 40);
}
This sets a fixed 20-tick stop loss and 40-tick profit target on every trade. But real-world strategies need dynamic risk management. Here's how to use the ATR (Average True Range) for volatility-adjusted stops:
private ATR atr;
// In State.DataLoaded:
atr = ATR(14);
// In OnBarUpdate, after entry:
if (CrossAbove(fastEMA, slowEMA, 1))
{
EnterLong();
SetStopLoss(CalculationMode.Price, Close[0] - (atr[0] * 2));
}
For a broader look at risk management principles for automated trading, including drawdown limits and daily loss caps, check our dedicated guide.
Multi-Timeframe Strategies
One of NinjaScript's most powerful features is multi-timeframe analysis. You can add secondary data series in State.Configure and reference them in OnBarUpdate() using the BarsInProgress property.
protected override void OnStateChange()
{
if (State == State.Configure)
{
AddDataSeries(BarsPeriodType.Minute, 15);
}
else if (State == State.DataLoaded)
{
// Index 0 = primary series, Index 1 = 15-min series
fastEMA = EMA(Closes[0], 9);
trendSMA = SMA(Closes[1], 50);
}
}
protected override void OnBarUpdate()
{
if (BarsInProgress != 0) return;
if (CurrentBars[0] < 9 || CurrentBars[1] < 50) return;
bool higherTFBullish = trendSMA[0] > trendSMA[1];
if (CrossAbove(fastEMA, slowEMA, 1) && higherTFBullish)
EnterLong();
}
The BarsInProgress filter is critical. Without it, your logic executes on every data series update — the 15-minute bar would trigger the same entry logic as your primary chart, leading to unexpected behavior. Always filter for BarsInProgress == 0 (your primary series) before executing trade logic.
Multi-timeframe confirmation is a staple of professional algorithmic trading strategies. It is meant to reduce false signals by aligning entries with the higher timeframe trend.
Debugging and Testing Your Strategy
Writing code is half the battle. The other half is making sure it works. NinjaScript gives you several debugging tools:
Print() Statements
The simplest and most effective debugging tool. Sprinkle Print() calls throughout your logic to trace execution flow:
Print(Time[0] + " | Close: " + Close[0] + " | FastEMA: " + fastEMA[0]);
Output appears in the NinjaScript Output window. This tells you exactly what values your strategy sees at each bar.
TraceOrders
Set TraceOrders = true; in State.SetDefaults to get detailed order-level logging. NinjaTrader prints every order submission, modification, fill, and cancellation. Essential when orders aren't behaving as expected.
Strategy Analyzer
Once your strategy compiles, use the Strategy Analyzer (Control Center → New → Strategy Analyzer) to backtest it against historical data. Review total net profit, max drawdown, profit factor, and the trade-by-trade log. If the numbers don't make sense, go back to Print() statements.
Visual Studio Integration
For advanced debugging, enable Debug Mode in the NinjaScript Editor (right-click → Debug Mode), then attach Visual Studio to the NinjaTrader process. You get breakpoints, variable inspection, and call stack analysis — the full IDE experience.
Common NinjaScript Mistakes (and How to Avoid Them)
After reviewing hundreds of forum posts and debugging sessions, these are the mistakes that trip up most NinjaScript developers:
- Instantiating indicators in SetDefaults — Data series don't exist yet. Always create indicators in
State.DataLoaded. Doing it earlier causes null reference exceptions. - Missing the CurrentBar guard — Accessing
Close[20]when only 10 bars have loaded crashes your strategy. Always checkif (CurrentBar < requiredBars) return;at the top ofOnBarUpdate(). - Ignoring BarsInProgress — In multi-series strategies,
OnBarUpdate()fires for every data series. Without filtering, your entry logic runs on the wrong timeframe. Always checkBarsInProgress. - Heavy logic in SetDefaults — This state fires every time NinjaTrader refreshes a UI list. If you put API calls or file I/O here, you'll slow down the entire platform.
- Not using TraceOrders — When orders don't fill or positions don't close, traders often rewrite their logic. The real problem is usually order state management. Turn on
TraceOrdersfirst. - Compiling with duplicate methods — NinjaTrader compiles all custom scripts at once. One broken file blocks everything. If you see CS0111 errors, check for duplicate
OnBarUpdate()orOnStateChange()definitions across all your scripts.
From Code to Live Futures Trading
Building a custom NinjaScript strategy is the foundation. But going from a backtest to live execution requires more: walk-forward testing, out-of-sample validation, and robust risk management that accounts for slippage, commissions, and real market conditions.
At NocNoe, we've built over 10 automated strategies through a rigorous DEV → QA → UAT → PROD pipeline. Every strategy is backtested on historical data before release. Our Pro tier gives you access to these strategies running on NinjaTrader — no coding required.
But if you want to build your own, NinjaScript is the way. Start with a simple crossover. Add a stop loss. Backtest it. Break it. Fix it. That cycle — code, test, iterate — is how every professional algo trader operates.
Whether you code your own strategies or use pre-built ones, the key is removing emotion from execution. Algorithms don't hesitate. They don't revenge trade. They follow the rules, every single time.
Ready to see what automated futures trading looks like in practice? Explore NocNoe's strategy library.
Hypothetical performance results have many inherent limitations, some of which are described below. No representation is being made that any account will or is likely to achieve profits or losses similar to those shown; in fact, there are frequently sharp differences between hypothetical performance results and the actual results subsequently achieved by any particular trading program. One of the limitations of hypothetical performance results is that they are generally prepared with the benefit of hindsight. In addition, hypothetical trading does not involve financial risk, and no hypothetical trading record can completely account for the impact of financial risk of actual trading. For example, the ability to withstand losses or to adhere to a particular trading program in spite of trading losses are material points which can also adversely affect actual trading results. There are numerous other factors related to the markets in general or to the implementation of any specific trading program which cannot be fully accounted for in the preparation of hypothetical performance results and all which can adversely affect trading results.
Risk Disclosure: Futures and forex trading contains substantial risk and is not for every investor. An investor could potentially lose all or more than the initial investment. Risk capital is money that can be lost without jeopardizing ones' financial security or life style. Only risk capital should be used for trading and only those with sufficient risk capital should consider trading. Past performance is not necessarily indicative of future results.
NinjaTrader® is a registered trademark of NinjaTrader Group, LLC. No NinjaTrader company has any affiliation with the owner, developer, or provider of the products or services described herein, or any interest, ownership or otherwise, in any such product or service, or endorses, recommends or approves any such product or service.