How to store, manage, and expose values inside your indicators
In the last article, we broke down the basic structure of a NinjaTrader indicator. Now it’s time to start making your indicators do real work.
When you build custom indicators, you’ll quickly realize that not every value is hardcoded. Sometimes you need to store information privately inside your code. Other times, you want the user to adjust settings directly from the indicator properties window—without touching your script.

That’s where variables and properties come in.
In this article, we’ll cover:
- The difference between variables and properties
- Common types of variables in NinjaScript
- How to declare internal variables
- How to expose public properties to users
- How NinjaTrader makes properties visible using attributes
By the end, you’ll know how to make your indicators more flexible, customizable, and professional (this is only the beginning, so hang in there for more).
🔁 What Are Variables and Properties?
In NinjaScript (built on C#), variables and properties let you store and control information inside your script.
- Variables: Internal values used for tracking, calculating, and managing logic.
- Properties: Publicly accessible values that users can modify in the indicator properties UI.
Private vs. Public
When you declare a variable or property, you must choose an access level:
- Private: Only the code inside your indicator can access it. Use this for internal tracking.
- Public: Accessible by other scripts (like strategies) and visible in NinjaTrader’s UI if properly decorated. Use this when you want users or other scripts to modify or read the value.
Example:
private int internalCounter; // Private - only used inside the indicator
[NinjaScriptProperty]
[Display(Name = "User Adjustable Period", GroupName = "Parameters", Order = 1)]
public int UserPeriod { get; set; } = 14; // Public - user can set this in UI
Choosing between private and public is important: only expose what needs to be changed or shared.
Common C# Variable Types in NinjaScript
Before we start declaring variables inside our indicators, it’s helpful to get familiar with the common types you’ll work with. Most of what you’ll use in NinjaScript are standard C# types, but there are a few NinjaTrader-specific types too. Here’s a quick overview you can reference as you build your indicators:
Type | Description | Example |
---|---|---|
int | Whole numbers | Number of bars to look back (e.g., 14) |
double | Decimal numbers | Price levels, moving averages |
bool | True or false | Toggle options like "Show Signal" |
string | Text | Display messages or labels |
Brush | Colors | Customizing plot colors |
Series | Specialized NinjaScript series of values | Price series, calculated indicators |
const | Constant value, cannot be changed | A fixed threshold level |
🛑 What is a const
?
A const
(short for “constant”) is a value that never changes once your indicator is running. You define it once, and it stays the same every time your code executes.
Example:
private const double MaxThreshold = 100.0;
In this case, MaxThreshold
will always be 100.0
—it’s locked in. You use const
values when you need something reliable that doesn’t depend on user input or market conditions.
📦 Declaring Variables
To declare a private variable at the class level:
private int myPeriod = 14; // Internal use only
private double myMaxValue = 0.0; // Internal use only
To declare a constant value (unchanging):
private const double MaxThreshold = 100.0;
If you want to track dynamic information like a running value per bar, you can use NinjaScript’s Series<T>
.
In Series<T>
, the T
stands for the type of data you want to store. For example, Series<double>
stores decimal numbers (like price levels), and Series<int>
would store whole numbers.
You declare the Series<T>
the same as other types:
private Series<double> mySeries;
You’ll initialize Series<T>
inside OnStateChange
:
else if (State == State.DataLoaded)
{
mySeries = new Series<double>(this);
}
Series<T>
automatically manages a history of values bar-by-bar, so you can refer to past calculations easily. We’ll cover Series<T>
more in depth in a future post.
🧭 Creating Public Properties
To allow the user to modify a setting from the UI, expose a public property using attributes.
[NinjaScriptProperty]
[Range(1, int.MaxValue)]
[Display(Name = "My Period", Description = "Period for custom calculation", GroupName = "Parameters", Order = 1)]
public int MyPeriod
{
get { return myPeriod; }
set { myPeriod = value; }
}
🔍 Explaining the Attributes
Before we dive into the details, it’s important to understand an additional concept: the difference between manually backing a property with a private variable versus using an automatic property.
You might see two different ways to create a property:
Option 1: Manual Property with Backing Variable
private int myPeriod;
[NinjaScriptProperty]
[Range(1, int.MaxValue)]
[Display(Name = "My Period", Description = "Period for custom calculation", GroupName = "Parameters", Order = 1)]
public int MyPeriod
{
get { return myPeriod; }
set { myPeriod = value; }
}
This structure gives you more control. You can later add validation, trigger custom logic when the value changes, or track when properties update internally.
Option 2: Auto-Property (Short Version)
[NinjaScriptProperty]
[Range(1, int.MaxValue)]
[Display(Name = "My Period", Description = "Period for custom calculation", GroupName = "Parameters", Order = 1)]
public int MyPeriod { get; set; }
This approach is fine for simple properties where you don’t need special behavior. It’s cleaner and faster to write but sacrifices flexibility.
When to use each:
- Use the manual pattern (backing variable) when you think you might add internal logic later.
- Use the auto-property when the value is simple and doesn’t need special handling.
📑 Understanding Property Attributes
When you create public properties in NinjaScript, you can attach special “attributes” that control how NinjaTrader displays and validates those properties. Here are the most important ones you’ll use:
[NinjaScriptProperty]
The [NinjaScriptProperty]
attribute tells NinjaTrader that this property should be treated as an input when your indicator or strategy is referenced by another script. It ensures that the value can be passed in externally when your indicator is used inside another indicator, strategy, or add-on.
⚡ Important: [NinjaScriptProperty]
does not control whether the property appears in the indicator’s settings window. That visibility is controlled separately by the [Display(...)]
attribute.
You should use [NinjaScriptProperty]
for core input values that impact logic and behavior — anything you would reasonably want to configure externally or programmatically. You typically do not use [NinjaScriptProperty]
for purely cosmetic settings (like plot colors, line styles, fonts, etc.), because there’s no need to pass those values externally when calling the indicator.
Eample: Why [NinjaScriptProperty]
Matters
If you don’t use [NinjaScriptProperty]
, your property won’t show up as an input when another script tries to reference your indicator.
✅ Correct (Accessible from Strategies and Other Indicators):
[NinjaScriptProperty]
[Display(Name = "Lookback Period", GroupName = "Parameters", Order = 1)]
public int Lookback { get; set; } = 20;
This allows another script to call:
MyCustomIndicator myInd = MyCustomIndicator(myInputSeries, lookback: 20);
🚫 Incorrect (Missing [NinjaScriptProperty]
, Not Settable Externally):
[Display(Name = "Lookback Period", GroupName = "Parameters", Order = 1)]<br>public int Lookback { get; set; } = 20;<code><br></code>
If you leave out [NinjaScriptProperty]
, the Lookback
value won’t be passed in during a script call — it will always use the internal default instead unless manually changed later, for example:
MyCustomIndicator myInd = MyCustomIndicator(myInputSeries);
[Range(min, max)]
Defines a range of acceptable values for the property. This prevents users from accidentally setting a value that doesn’t make sense (like zero or negative periods).
Example:
[Range(1, 200)]
public int Lookback { get; set; } = 20;
This restricts user input to values between 1 and 200.
[Display(Name, Description, GroupName, Order)]
The [Display]
attribute fine-tunes how your property appears in the NinjaTrader user interface. You can control multiple aspects:
- Name: This sets the friendly label the user sees next to the input field.
- Description: This adds a helpful tooltip that shows when the user hovers over the setting.
- GroupName: This organizes the property under a labeled section, like “Parameters” or “Settings” in the indicator panel.
- Order: This determines the position the property appears within its group.
Example:
[Display(Name = "Threshold", Description = "Signal trigger level", GroupName = "Settings", Order = 2)]
This would place the setting under the “Settings” group, show it second within that group, and display “Threshold” as the label with an explanatory tooltip when hovered.
Using these attributes helps you create clean, user-friendly, and professional-looking indicators.
⚙ Setting Default Values
Defaults are set inside OnStateChange
during the State.SetDefaults
phase:
if (State == State.SetDefaults)
{
Name = "MyCustomIndicator";
MyPeriod = 14; // Default shown in UI
}
If you don’t set a default here, users could see an empty or incorrect value when loading your indicator.
🧪 Example: Full Property Setup
Let’s extend our MyCustomIndicator
with a full start-to-finish example:
private int myPeriod;
private Series<double> mySeries;
private const double MaxThreshold = 100.0;
[NinjaScriptProperty]
[Range(1, 100)]
[Display(Name = "My Period", Description = "Lookback period.", GroupName = "Parameters", Order = 1)]
public int MyPeriod
{
get { return myPeriod; }
set { myPeriod = value; }
}
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Name = "MyCustomIndicator";
MyPeriod = 14;
}
else if (State == State.DataLoaded)
{
mySeries = new Series<double>(this);
}
}
protected override void OnBarUpdate()
{
double average = SMA(MyPeriod)[0];
mySeries[0] = average;
if (Close[0] > average && average < MaxThreshold)
Draw.Dot(this, "updot" + CurrentBar, true, 0, Low[0] - 1 * TickSize, Brushes.Green);
else
Draw.Dot(this, "downdot" + CurrentBar, true, 0, High[0] + 1 * TickSize, Brushes.Red);
}
In this example:
- The user controls “My Period” through the settings.
Series<double>
stores the average at each bar.MaxThreshold
ensures we aren’t drawing signals when the SMA gets too high.
🧰 Best Practices for Variables and Properties
- Always back public properties with private variables.
- Use
[Range]
to restrict invalid inputs. - Set safe defaults in
State.SetDefaults
. - Group inputs logically using
GroupName
. - Only expose properties the user should control.
- Use
const
for fixed, unchanging internal values. - Be thoughtful about when to use
[NinjaScriptProperty]
—don’t expose visual-only settings.
Example of clean grouped properties:
[NinjaScriptProperty]
[Display(Name = "Enable Signals", GroupName = "Settings", Order = 1)]
public bool EnableSignals { get; set; } = true;
[NinjaScriptProperty]
[Range(1, 50)]
[Display(Name = "Lookback Period", GroupName = "Settings", Order = 2)]
public int LookbackPeriod { get; set; } = 10;
🧩 What’s Next?
Now that you understand how to manage variables, properties, constants, and series, you’re ready to start building real visual outputs.
In the next article, we’ll cover Plots and Data Series—how to visually display your indicator values on the chart.
Let’s turn that data into something you (and your users) can actually see!ee!
🎉 Prop Trading Discounts
💥91% off at Bulenox.com with the code MDT91