You got your first SMA indicator up and running. However, it’s still providing raw values and lacks user customization options. Users usually love to control everything about the product they bought. In this article, I will guide you in more detail on how to upgrade your SMA indicator.
User interface upgrades
A significant part of creating a useful indicator involves designing its user interface and ensuring a smooth user experience. This includes making the indicator visually appealing and easy to configure. At ninZa.co, we carefully craft our software indicator with the customer at the center. Therefore, we set high standards for the development team which you will learn about shortly.
Adding user-adjustable inputs
One indicator must be easy to customize the inputs. Let’s say you give your friend the new SMA indicator. He prefers to change the period to 100. What options does he have? Would you direct him to this NinjaScript tutorial series to modify the code himself?
Besides, if you allow others to change your source code, meaning they could steal it. I’m not talking about this SMA, it’s widely available. But what if it’s your proprietary algorithm?
That approach is cumbersome and doesn't align with the typical way software is shared or sold. Instead, you enable the SMA indicator to allow your friends to change the period from the user interface (UI). To achieve this, we need to move the period
parameter to the UI.
First, re-define the variable with public
keyword:
public int Period { get; set; }
Since it’s publicly accessible, I use the Pascal-case. It’s a good practice in programming to separate public and private variables with casing styles. You can learn more about these styles in this article.
Besides, you may notice that we have an extra get/set
pair:
get
– Allows you to retrieve the value of Period
.
set
– Allows you to assign a new value to Period
.
Now, let's set a default value for it. Navigate to the OnStateChange()
function where you added the SMA plot, assign a default value of period here:
Period = 14;
AddPlot(Brushes.Chocolate, "Hello World SMA");
Because C# executes code sequentially, accessing Period
before it is initialized in OnStateChange()
would result in an error. At that moment, Period
is not yet declared. Note that you will need to move the Period
declaration line up above the OnStateChange()
function:
public int Period { get; set; } // <- move to here
protected override void OnStateChange()
{
// ...
}
Now, you need to replace the old period
with the new one Period
(Pascal-case):
protected override void OnBarUpdate()
{
if (CurrentBar < Period) return;
for (int bar = 0; bar < Period; bar++) {
sumPrice = sumPrice + Close[bar];
}
double avgValue = sumPrice / Period;
HelloWorldSMA[0] = avgValue;
sumPrice = 0.0;
}
Press F5 to compile the code. Go to your chart, open the indicator dialog. Under the “Misc” section, you will see your Period
parameter available there with a default value of 14:

Try changing the value of the parameter and clicking OK. The SMA plot on the chart will update accordingly. You can add the built-in SMA indicator with the same period to verify it works well.
Bonus: adding the following line above the Period
declaration, compiling the code, and then checking the indicator dialog. This attribute is related to UI organization and does not affect the indicator's calculations. Further research into the Display
attribute is encouraged: https://developer.ninjatrader.com/docs/desktop/displayattribute
[Display(Name = "SMA Period", GroupName = "Parameters")]
public int Period { get; set; }
Changing plot colors dynamically
Initially, the SMA plot line has a single color. Even though its style can be changed in the indicator dialog, it remains a static color. You might want the plot's color to change dynamically based on market conditions.
Here are three common scenarios:
- If the current price is greater than the previous price, it’s an uptrend → paint green.
- If the current price is less than the previous price, it’s a downtrend → paint red.
- If the current price equals the previous price, it's sideways → paint yellow.
For sideways color
We just need to compare the Close[0]
with Close[1]
. Add this line at the bottom of the OnBarUpdate()
function.
if (Close[0].ApproxCompare(Close[1]) == 0) {
PlotBrushes[0][0] = Brushes.Yellow;
}
Close
prices are stored as double
values. Using the standard equality operator (==
) for comparison with double
values can lead to precision errors. Therefore, we use a built-in function ApproxCompare()
. If the result equals 0, meaning the Close[0]
and Close[1]
are equal.
To change the color of the plot, we use the built-in PlotBrushes[0][0]
property. From left to right, the first number is the plot index, it’s 0 because currently we have only one plot in our indicator. The second number is the bars ago value. You can learn more about it here.
For uptrend color
Continue with the previous code, we will add some checks for the uptrend status:
if (Close[0].ApproxCompare(Close[1]) == 0) {
PlotBrushes[0][0] = Brushes.Yellow;
} else {
if (IsRising(HelloWorldSMA) {
PlotBrushes[0][0] = Brushes.Green;
}
}
NinjaScript has a built-in function IsRising()
to evaluate a rising condition. It returns true when the current value is greater than the value of 1 bar ago.
For downtrend color
If the function IsRising()
fails, it means that the plot is going down. We just need to update the plot color:
if (Close[0].ApproxCompare(Close[1]) == 0) {
PlotBrushes[0][0] = Brushes.Yellow;
} else {
if (IsRising(HelloWorldSMA) {
PlotBrushes[0][0] = Brushes.Green;
} else {
PlotBrushes[0][0] = Brushes.Red;
}
}
Now compile and check your chart. You will see the SMA plot color updated with its uptrend/downtrend state.

Bonus: Go to the SetDefaults
state and change the value of IsOverlay
to true. The next time you add the indicator to the chart, it will populate in the same panel with the bars, not in a separate panel.

Background color
The plot color is good but it’s still hard to see. We will paint the background with the same color as the plot. We use the BackBrush
property of the current panel:
if (Close[0].ApproxCompare(Close[1]) == 0) {
PlotBrushes[0][0] = Brushes.Yellow;
BackBrush = Brushes.Yellow;
} else {
if (IsRising(HelloWorldSMA) {
PlotBrushes[0][0] = Brushes.Green;
BackBrush = Brushes.Green;
} else {
PlotBrushes[0][0] = Brushes.Red;
BackBrush = Brushes.Red;
}
}
Compile, then we will get this result:

This works, but the visual result is not ideal. We need to change to a different color for better visuals. Therefore, we need to define additional variables to manage the colors. Let’s add 1 variable for color:
[XmlIgnore]
[Display(Name = "Plot: Uptrend", GroupName = "Parameters")]
public Brush PlotUptrend { get; set; }
[Browsable(false)]
public string PlotUptrendSerialize {
get { return Serialize.BrushToString(PlotUptrend); }
set { PlotUptrend = Serialize.StringToBrush(value); }
}
Brush
is a common data type in NinjaScript for representing colors and styles. It’s a complex object. NinjaScript does not directly support serializing Brush
objects in XML files, which are used to store indicator configurations. Therefore, we use a workaround:
- The
[XmlIgnore]
attribute prevents the XML serializer from including PlotUptrend
during the serialization process.
- We create
PlotUptrendSerialize
to store and retrieve the brush color as a string.
- The
get
accessor converts PlotUptrend
to a string using Serialize.BrushToString(PlotUptrend)
.
- The
set
accessor converts the string back into a Brush
object using Serialize.StringToBrush(value)
.
[Browsable(false)]
ensures that PlotUptrendSerialize
isn’t displayed in UI editors, since it’s only used for serialization.
We need five more similar variable sets for the other colors:
- PlotDowntrend
- PlotSideways
- BackgroundUptrend
- BackgroundDowntrend
- BackgroundSideways
The declaration will look like this:
[XmlIgnore]
[Display(Name = "Plot: Uptrend", GroupName = "Parameters")]
public Brush PlotUptrend { get; set; }
[Browsable(false)]
public string PlotUptrendSerialize {
get { return Serialize.BrushToString(PlotUptrend); }
set { PlotUptrend = Serialize.StringToBrush(value); }
}
[XmlIgnore]
[Display(Name = "Plot: Downtrend", GroupName = "Parameters")]
public Brush PlotDowntrend { get; set; }
[Browsable(false)]
public string PlotDowntrendSerialize {
get { return Serialize.BrushToString(PlotDowntrend); }
set { PlotDowntrend = Serialize.StringToBrush(value); }
}
[XmlIgnore]
[Display(Name = "Plot: Sideways", GroupName = "Parameters")]
public Brush PlotSideways { get; set; }
[Browsable(false)]
public string PlotSidewaysSerialize {
get { return Serialize.BrushToString(PlotSideways); }
set { PlotSideways = Serialize.StringToBrush(value); }
}
[XmlIgnore]
[Display(Name = "Background: Uptrend", GroupName = "Parameters")]
public Brush BackgroundUptrend { get; set; }
[Browsable(false)]
public string BackgroundUptrendSerialize {
get { return Serialize.BrushToString(BackgroundUptrend); }
set { BackgroundUptrend = Serialize.StringToBrush(value); }
}
[XmlIgnore]
[Display(Name = "Background: Downtrend", GroupName = "Parameters")]
public Brush BackgroundDowntrend { get; set; }
[Browsable(false)]
public string BackgroundDowntrendSerialize {
get { return Serialize.BrushToString(BackgroundDowntrend); }
set { BackgroundDowntrend = Serialize.StringToBrush(value); }
}
[XmlIgnore]
[Display(Name = "Background: Sideways", GroupName = "Parameters")]
public Brush BackgroundSideways { get; set; }
[Browsable(false)]
public string BackgroundSidewaysSerialize {
get { return Serialize.BrushToString(BackgroundSideways); }
set { BackgroundSideways = Serialize.StringToBrush(value); }
}
Next, let's configure default values for these colors. Go to the SetDefaults
state and add more code:
PlotUptrend = Brushes.DodgerBlue;
PlotDowntrend = Brushes.HotPink;
PlotSideways = Brushes.Gold;
BackgroundUptrend = Brushes.LightBlue;
BackgroundDowntrend = Brushes.LightPink;
BackgroundSideways = Brushes.LightGoldenrodYellow;
…and update the color-setting code block:
if (Close[0].ApproxCompare(Close[1]) == 0) {
PlotBrushes[0][0] = PlotSideways;
BackBrush = BackgroundSideways;
} else {
if (IsRising(HelloWorldSMA)) {
PlotBrushes[0][0] = PlotUptrend;
BackBrush = BackgroundUptrend;
} else {
PlotBrushes[0][0] = PlotDowntrend;
BackBrush = BackgroundDowntrend;
}
}
Compile the code and refresh the chart (F5). You will see the updated colors on the chart. Color has changed. Open up the indicator dialog, you will see 6 new parameters there:


You can now customize these color values as desired.
Algorithm upgrades
Using other input series
The current implementation using the closing price for calculations. However, users may want to use other price data (Open, High, Low), you can do that with simple changes:
sumPrice = sumPrice + Input[bar];
You only need to change Close
to Input
. This built-in variable represents the current input value settings.
Compile the code and open the indicator's properties window. Under the “Data Series” section, click “Edit input…” at “Input series”.

You can select your favorite input series from the dropdown menu. Try changing it and see how it affects your chart.

Apply new MA algorithm
Some traders prefer SMA, while others prefer EMA. We will add an EMA plot to provide users with an alternative moving average.
First, add one new plot to the chart:
AddPlot(Brushes.LimeGreen, "Hello World EMA");
Then, declare a variable for our new EMA and its multiplier:
public Series<double> HelloWorldEMA {
get { return Values[1]; } // <-- Note that is's Values[1], not Values[0]
}
double emaMultiplier;
Initialize emaMultiplier
in the OnStateChange()
function, within the State.Configure
block:
//...
else if (State == State.Configure)
{
emaMultiplier = 2.0 / (Period + 1);
}
Initialize EMA values before enough bars are available:
if (CurrentBar < Period)
{
HelloWorldEMA[0] = Input[0];
return;
}
Implement the algorithm:
HelloWorldEMA[0] = Input[0] * emaMultiplier + HelloWorldEMA[1] * (1 - emaMultiplier);
Compile the code and refresh your chart. You will see a new green EMA plot. Add a built-in EMA to the chart with the same period to verify it’s correct.
Code tidies up
The code now includes both EMA and SMA calculations. However, the code is becoming less organized with the addition of these two basic algorithms. To improve readability and maintainability, we will refactor the code to use a single plot and allow users to select their preferred MA algorithm. Users who need both SMA and EMA can add the indicator to the chart twice.
First, we define a new enumeration for SMA and EMA. Declare this outside the namespace
block:
public enum HelloWorld_MAType { SMA, EMA }
namespace NinjaTrader.NinjaScript.Indicators
{
// ...
}
Declare a new input property to store the user’s selection of the MA type:
[Display(Name = "MA Type", Order = 10, GroupName = "Parameters")]
public HelloWorld_MAType MAType { get; set; }
Since we are using a single plot, we can consolidate the period parameter:
[Display(Name = "MA Period", Order = 0, GroupName = "Parameters")]
public int MAPeriod { get; set; }
…and use a single plot declaration, remove the HelloWorldEMA
series:
public Series<double> HelloWorldMA {
get { return Values[0]; }
}
Remember to update the AddPlot()
function and set default value for MAType
in function OnStateChange()
:
MAType = HelloWorld_MAType.SMA;
AddPlot(Brushes.Chocolate, "Hello World MA");
AddPlot(Brushes.LimeGreen, "Hello World EMA"); // <-- Remove
Next, we define a separate function at the same class level as OnBarUpdate()
to encapsulate the SMA calculation logic. This function will use the new MAPeriod
input parameter:
double CalculateSMA()
{
double sumPrice = 0.0;
for (int bar = 0; bar < MAPeriod; bar++) {
sumPrice = sumPrice + Input[bar];
}
double avgValue = sumPrice / MAPeriod;
return avgValue;
}
You can now remove the previous declaration of sumPrice
, as it is now defined within the CalculateSMA()
function. Also, remove all the SMA calculations from OnBarUpdate()
. Then, call the CalculateSMA()
function to get the SMA value:
HelloWorldMA[0] = CalculateSMA();
Similarly, for EMA:
double CalculateEMA()
{
return Input[0] * emaMultiplier + HelloWorldMA[1] * (1 - emaMultiplier);
}
…and update the OnBarUpdate()
function accordingly:
protected override void OnBarUpdate()
{
if (CurrentBar == 0)
{
if (MAType == HelloWorld_MAType.EMA)
{
HelloWorldMA[0] = Input[0];
}
return;
}
if (MAType == HelloWorld_MAType.EMA)
{
HelloWorldMA[0] = CalculateEMA();
// styling here
// ...............
}
if (CurrentBar < MAPeriod) return;
if (MAType == HelloWorld_MAType.SMA)
{
HelloWorldMA[0] = CalculateSMA();
// styling here
// ................
}
}
We apply the same changes to the styling code. Note that Close
is changed to Input
to use the user-selected data series:
void PaintPlotAndBackground()
{
if (Input[0].ApproxCompare(Input[1]) == 0) {
PlotBrushes[0][0] = PlotSideways;
BackBrush = BackgroundSideways;
} else {
if (IsRising(HelloWorldMA)) {
PlotBrushes[0][0] = PlotUptrend;
BackBrush = BackgroundUptrend;
} else {
PlotBrushes[0][0] = PlotDowntrend;
BackBrush = BackgroundDowntrend;
}
}
}
Replace the styling code in OnBarUpdate()
with a call to the new function PaintPlotAndBackground()
.
protected override void OnBarUpdate()
{
if (CurrentBar == 0)
{
if (MAType == HelloWorld_MAType.EMA)
{
HelloWorldMA[0] = Input[0];
}
return;
}
if (MAType == HelloWorld_MAType.EMA)
{
HelloWorldMA[0] = CalculateEMA();
PaintPlotAndBackground();
}
if (CurrentBar < MAPeriod) return;
if (MAType == HelloWorld_MAType.SMA)
{
HelloWorldMA[0] = CalculateSMA();
PaintPlotAndBackground();
}
}
Bonus: The Display
attribute has a property Order
, which controls the order in which parameters are displayed in the indicator’s properties window. Parameters are displayed from top to bottom, corresponding to their order values (from smallest to largest).
[Display(Name = "MA Period", Order = 1, GroupName = "Parameters")]
public int MAPeriod { get; set; }

Users can now choose between two moving averages algorithms. You can use a similar approach to add more moving averages options. This is left as an exercise for you.
Next
Great job! You've successfully enhanced your SMA indicator by introducing customizations, implementing an EMA plot, and refining your code.
In the next article, we'll explore additional customization options, including alerts, markers, and programming signals. Alerts and markers will notify users about chart updates, while programming signals will empower them to develop strategies and custom scripts with your indicator at base.
Appendix
NinjaScript for Beginner