A beginner’s guide to how indicators work in NinjaTrader 8.

If you’re new to coding indicators in NinjaTrader 8, it can feel like a lot at once. NinjaScript has a specific structure that might seem overwhelming at first, but once you understand the layout, everything starts to make sense. Whether you’re building a simple moving average or a multi-layered custom tool, every indicator starts with the same foundation. In this post, I’ll walk you through how to open the editor, generate your first custom indicator, and explain what all the auto-generated code actually does.

By the end, you’ll have a better idea of what each section of the script is for—and where your logic will eventually go.

🧭 Getting Started: How to Open the NinjaScript Editor

Before we dive into the structure of an indicator, let’s start with how to access the editor, where you’ll write your code:

  1. Open NinjaTrader 8
  2. From the Control Center, click on the “New” menu
  3. Select “NinjaScript Editor”
  4. When the new window appears, in the right-hand pane (called “NinjaScript Explorer”), right-click on the “Indicator” folder
  5. Choose “New Indicator” — this opens a new window
  6. If you immediately click “Generate”, it will create a default indicator called MyCustomIndicator
  7. If you click “Next” instead, you’ll be able to enter a custom name before generating your file (i.e., input a custom indicator name and then click generate)

Once generated, NinjaTrader will create a new script with the full boilerplate structure, ready for you to edit.

Here’s what that default template looks like:

#region Using declarations
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Xml.Serialization;
using NinjaTrader.Cbi;
using NinjaTrader.Gui;
using NinjaTrader.Gui.Chart;
using NinjaTrader.Gui.SuperDom;
using NinjaTrader.Gui.Tools;
using NinjaTrader.Data;
using NinjaTrader.NinjaScript;
using NinjaTrader.Core.FloatingPoint;
using NinjaTrader.NinjaScript.DrawingTools;
#endregion

//This namespace holds Indicators in this folder and is required. Do not change it. 
namespace NinjaTrader.NinjaScript.Indicators
{
	public class MyCustomIndicator : Indicator
	{
		protected override void OnStateChange()
		{
			if (State == State.SetDefaults)
			{
				Description									= @"Enter the description for your new custom Indicator here.";
				Name										= "MyCustomIndicator";
				Calculate									= Calculate.OnBarClose;
				IsOverlay									= false;
				DisplayInDataBox							= true;
				DrawOnPricePanel							= true;
				DrawHorizontalGridLines						= true;
				DrawVerticalGridLines						= true;
				PaintPriceMarkers							= true;
				ScaleJustification							= NinjaTrader.Gui.Chart.ScaleJustification.Right;
				//Disable this property if your indicator requires custom values that cumulate with each new market data event. 
				//See Help Guide for additional information.
				IsSuspendedWhileInactive					= true;
			}
			else if (State == State.Configure)
			{
			}
		}

		protected override void OnBarUpdate()
		{
			//Add your custom indicator logic here.
		}
	}
}

#region NinjaScript generated code. Neither change nor remove.

namespace NinjaTrader.NinjaScript.Indicators
{
	public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase
	{
		private MyCustomIndicator[] cacheMyCustomIndicator;
		public MyCustomIndicator MyCustomIndicator()
		{
			return MyCustomIndicator(Input);
		}

		public MyCustomIndicator MyCustomIndicator(ISeries<double> input)
		{
			if (cacheMyCustomIndicator != null)
				for (int idx = 0; idx < cacheMyCustomIndicator.Length; idx++)
					if (cacheMyCustomIndicator[idx] != null &&  cacheMyCustomIndicator[idx].EqualsInput(input))
						return cacheMyCustomIndicator[idx];
			return CacheIndicator<MyCustomIndicator>(new MyCustomIndicator(), input, ref cacheMyCustomIndicator);
		}
	}
}

namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns
{
	public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase
	{
		public Indicators.MyCustomIndicator MyCustomIndicator()
		{
			return indicator.MyCustomIndicator(Input);
		}

		public Indicators.MyCustomIndicator MyCustomIndicator(ISeries<double> input )
		{
			return indicator.MyCustomIndicator(input);
		}
	}
}

namespace NinjaTrader.NinjaScript.Strategies
{
	public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase
	{
		public Indicators.MyCustomIndicator MyCustomIndicator()
		{
			return indicator.MyCustomIndicator(Input);
		}

		public Indicators.MyCustomIndicator MyCustomIndicator(ISeries<double> input )
		{
			return indicator.MyCustomIndicator(input);
		}
	}
}

#endregion

🔍 This auto-generated structure includes everything you need to get started—OnStateChange() for setup, OnBarUpdate() for live logic, and supporting code behind the scenes. You can now start editing and testing right inside NinjaTrader.

🔍 What the Auto-Generated Code Does

When NinjaTrader generates a new indicator, it fills in a lot of code for you. That code sets up your script so it works properly in the platform, even before you add any custom logic. Here’s a breakdown of what it all means and how the pieces fit together.

đź“– Using Declarations

At the very top of your indicator script, you’ll see a list of using statements. These tell the compiler which parts of the .NET framework and NinjaTrader platform your script will access. Think of them as “import” statements: they bring in tools, functions, and classes so your code can use them.

#region Using declarations
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media;
using System.Xml.Serialization;
using NinjaTrader.Cbi;
using NinjaTrader.Gui;
using NinjaTrader.Gui.Chart;
using NinjaTrader.Gui.SuperDom;
using NinjaTrader.Gui.Tools;
using NinjaTrader.Data;
using NinjaTrader.NinjaScript;
using NinjaTrader.Core.FloatingPoint;
using NinjaTrader.NinjaScript.DrawingTools;
#endregion

Many of these are system-level namespaces from C# (like System.Linq or System.Windows.Media), while others are specific to NinjaTrader—such as NinjaTrader.Gui.Chart (for chart objects) or NinjaTrader.NinjaScript (for indicators and strategies). You typically don’t modify this section, but it’s good to know it’s here because without these references, features like drawing on the chart or accessing market data wouldn’t work.

đź“„ The Main Class

Every NinjaScript indicator is built as a class. A class is a container for your logic—it defines the behavior and characteristics of your custom tool. This class must inherit from NinjaTrader’s built-in Indicator class so it can plug into the platform and behave like other indicators.

The class is wrapped inside a namespace, which is a way to group and organize code. NinjaTrader uses specific namespaces to organize different types of scripts (e.g., indicators, strategies, drawing tools).

namespace NinjaTrader.NinjaScript.Indicators
{
	public class MyCustomIndicator : Indicator
	{
		// Main logic lives here
	}
}

In this example:

  • namespace NinjaTrader.NinjaScript.Indicators tells the platform that this code defines an indicator.
  • public class MyCustomIndicator : Indicator defines your indicator and allows it to inherit NinjaTrader’s core functionality.

The class name (MyCustomIndicator) should always match the filename of your script.

🔄 The OnStateChange() Method

This method is called by NinjaTrader during different phases of your indicator’s lifecycle—before any bars are processed. It’s where you configure how your indicator behaves and what it needs to do when it starts running.

protected override void OnStateChange()
{
	if (State == State.SetDefaults)
	{
		Description = @"Enter the description for your new custom Indicator here.";
		Name = "MyCustomIndicator";
		Calculate = Calculate.OnBarClose;
		IsOverlay = false;
		DisplayInDataBox = true;
		DrawOnPricePanel = true;
		DrawHorizontalGridLines = true;
		DrawVerticalGridLines = true;
		PaintPriceMarkers = true;
		ScaleJustification = NinjaTrader.Gui.Chart.ScaleJustification.Right;
		IsSuspendedWhileInactive = true;
	}
	else if (State == State.Configure)
	{
		// Reserved for data series or setup logic
	}
}
  • SetDefaults is where you define visual settings, default names, and property defaults.
  • Configure is where you can set up custom data series or dependencies (more on the rest of the “States” in a later post).

đź“Š The OnBarUpdate() Method

This is where your logic runs during live chart updates. NinjaTrader calls this method on ever bar close, every change in price, or every tick (depending on your settings—more on those later). It’s where you’ll write the core logic for calculations, alerts, and signals.

protected override void OnBarUpdate()
{
	// Add your custom indicator logic here.
}

This method gives you access to bar data, indicator values, and anything else that changes as the chart updates.

đź“Ś Behind the Scenes: Auto-Generated Code

At the bottom of the file, you’ll see a region labeled NinjaScript generated code. While this code is usually hidden behind a region block and is not something you typically edit, it plays an important role.

This section includes wrapper functions that allow your custom indicator to be called from other indicators, strategies, and Market Analyzer columns. One of the key pieces looks like this:

public MyCustomIndicator MyCustomIndicator(ISeries<double> input)
{
	// Lookup or create instance logic here
}

This is what enables you to use your indicator elsewhere in NinjaTrader code. If you add custom properties to your indicator—such as a user-defined period—NinjaTrader will automatically update this section to include method overloads that support those inputs as parameters.

For example (more detail to follow in a later post):

private MyCustomIndicator ind; // This allows you to reference "ind" throughout your code

protected override void OnStateChange()
{
	if (State == State.DataLoaded)
	{
		ind = MyCustomIndicator(Input); // Initialize your indicator reference
	}
}

protected override void OnBarUpdate()
{
	double val = ind[0]; // Access the most recent value of the indicator
}

This behind-the-scenes section makes your indicator reusable, shareable, and compatible with other parts of the NinjaTrader platform.

🔄 What’s Next?

Now that you understand how a NinjaTrader indicator is structured, you’re ready to build on it. In the next article, we’ll cover variables and properties—how to create them, use them, and expose them as inputs for your indicators.

You’ll learn how to:

  • Declare internal variables
  • Expose user-configurable settings
  • Use [NinjaScriptProperty] and [Range] attributes to show and control inputs in the UI

Let me know if there’s a concept you want covered sooner—this series is designed for traders, not programmers.

🎉 Prop Trading Discounts

đź’Ą45% off at Bulenox.com with the code MDT45

Categorized in:

Learn NinjaScript,

Tagged in: