In the last post, we walked through our plan for the Better Moving Average (BMA) ā an indicator that lets you switch between multiple moving average types using a single dropdown. Now itās time to start building.
This post focuses on laying the foundation ā getting our files created, our workspace organized, and the core logic wired up to support future customization. Weāre not optimizing yet. Weāre just getting the engine in place: selecting a moving average type and correctly plotting it.
If youāve been following along from the earlier parts, this is where things start to click.
š Your BMA Roadmap (So Far)
If youāre just jumping in, this post is part of an ongoing series where we build a customizable moving average indicator from the ground up. Be sure to catch up on the earlier parts:
- Learn NinjaScript: Letās Make a Better Moving Average (BMA Part 1)
- Learn NinjaScript: Planning the Better Moving Average (BMA Part 2)
š Download the Source Files
Want to skip the typing and jump straight into the code? You can download the completed .cs
files here:
- š§ Download the Add-On File (mdtLearnAddon.cs)
- š Download the Indicator File (mdtLearnBetterMovingAverage.cs)
ā These are for educational and personal use only ā please donāt redistribute, resell, or repackage. You can read more in the code header.
šļø Letās Get Our Workspace and Files Ready
Before we dive into any logic, we need a clean foundation.
I keep a separate workspace just for building and testing new indicators and add-ons like this. It keeps my charts lean, helps avoid conflicts from other scripts, and lets me iterate quickly without clutter. I’ll show you my layout a little later, but letās start with the file setup first.
ā Step 1: Create the Indicator File
Weāre going to build a new NinjaScript indicator ā and for this project, I recommend keeping it clean and isolated in its own folder. Youāll find the steps in the screenshots Iāve included, but here’s the general idea:
- Open the NinjaScript Editor
- Right-click the Indicators folder
- Select New Folder
- Enter the folder name:
mdtLearnInd
- Right-click the new
mdtLearnInd
folder - Select New Indicator
- Enter
mdtLearnBetterMovingAverage
as the name - Click Generate
- Once the new file opens, press
Ctrl + S
to save






This will give us a neat location to store other related learning indicators if we keep expanding the project.
ā Step 2: Create the Add-On File
Weāll repeat a similar process for the add-on:
- Right-click the AddOns folder
- Select New AddOn
- Enter
mdtLearnAddon
as the name - Click Generate
- Press
Ctrl + S
to save the file



Thatās it! You now have a clean, dedicated location for shared functionality that other indicators (like our BMA) can pull from.
š§© Letās Build the Add-On
With our file structure in place and our clean folders created, itās time to move into the logic that will drive this entire project.
The add-on is where we define what moving averages we support and how we calculate them ā outside of any single indicator. This approach keeps things modular, reusable, and clean. Instead of rewriting logic in every new indicator we build, weāll centralize it once here.
Weāll break this part down into three sections:
1ļøā£ File Header: Making the Code Clearly Personal-Use Only
Letās talk first about the license block at the top of the file. This is important ā not because itās flashy, but because it legally defines how the code may and may not be used.
Hereās the one I use (for our Learn NinjaScript series):
// MyDailyTake.com
//
// Copyright Ā© 2025 MyDailyTake.com
// All rights reserved.
//
// This software is made available for educational and personal use only.
// You are permitted to view and modify the code solely for your own private use.
//
// You may NOT:
// - Redistribute, copy, publish, or share this code in any form, modified or unmodified
// - Use this code in any commercial, institutional, or proprietary context
// - Include this code in any open source, paid, or public-facing software or package
//
// No license is granted for distribution, commercial use, or public disclosure.
//
// For questions or permissions, contact: https://mydailytake.com
š This block explicitly restricts:
- Commercial repackaging
- Redistribution (paid or free)
- Use in vendor products or rebrands
- Uploading to open-source or public GitHub repositories
This code is for personal learning and tinkering ā not for people trying to white-label or monetize it.
If you’re building for your own charts and want to tweak things ā go for it. But if you’re dreaming of selling indicators or building a product, this isn’t a foundation youāre allowed to build on.
Why does this matter? Because too many āfreeā codebases end up getting reskinned and sold to unsuspecting traders. This header makes sure that doesnāt happen with our work.
2ļøā£ Enum Setup: A Smarter Way to Let Users Choose an MA
This is where things start getting interesting.
Rather than hard-coding a bunch of logic with if
or switch
scattered through the indicator itself, we create a central enum that defines each moving average type we support.
Hereās what that looks like:
namespace mdtLearnEnums
{
public enum LearnMovingAverageType
{
DEMA, // Period
EMA, // Period
HMA, // Period
KAMA, // Fast, Period, Slow
MAMA, // FastLimit, SlowLimit
SMA, // Period
T3, // Period, TCount, VFactor
TMA, // Period
TEMA, // Period
TRIX, // Period, SignalPeriod
VMA, // Period, VolatilityPeriod
VWMA, // Period
WMA, // Period
ZLEMA, // Period
}
}
This enum gives us two key advantages:
- š§ Clarity in the UI ā The indicator will show a dropdown with readable options (e.g., “EMA”, “VWMA”, etc.), and the user can select what they want.
- š ļø Simplified logic later ā Instead of dozens of separate properties or methods, weāll just pass this enum into one helper method and let it figure out what to do.
Each entry in this enum also acts like a self-documenting piece of code ā especially if we annotate which parameters it uses. As you’ll see later, some MAs like SMA
or EMA
only need one period, while others like T3
, KAMA
, or MAMA
need multiple inputs.
We’re setting the foundation for dynamic property filtering and robust code reuse.
3ļøā£ Partial Class: Centralizing MA Logic Once and For All
The partial class
inside the add-on extends NinjaTraderās built-in Indicator
class and defines a single method:
public ISeries<double> LearnGetMovingAverage(
LearnMovingAverageType maType,
ISeries<double> input,
int period,
int signalPeriod,
int volatilityPeriod,
int tCount,
double vFactor,
int fast,
int slow,
double fastLimit,
double slowLimit)
This method returns the correct ISeries<double>
for whichever moving average the user has selected via the LearnMovingAverageType
enum.
š¹ Why this method exists
We want a single, reusable place to define all MA selection logic ā so indicators donāt have to replicate logic every time they need a moving average. This:
- Encapsulates all moving average routing logic into one location
- Enforces consistency and reuse across indicators
- Keeps the indicator clean and focused on display/plotting, not calculation
š¹ Why we use ISeries<double>
ISeries<double>
is NinjaTraderās standard interface for time series data. Every NT8 indicator returns values as a series (not just a single double), so this lets us plug any output into our plotting logic later with no extra handling.
š¹ The switch statement
We use a switch(maType)
block to route to the correct built-in (or custom) NinjaTrader MA method:
switch(maType)
{
case LearnMovingAverageType.DEMA:
return DEMA(input, period);
case LearnMovingAverageType.KAMA:
return KAMA(input, fast, period, slow);
case LearnMovingAverageType.MAMA:
return MAMA(input, fastLimit, slowLimit);
case LearnMovingAverageType.TRIX:
return TRIX(input, period, signalPeriod);
// Other cases...
default:
return SMA(input, period);
}
Each case maps to the correct NT8 method based on its required parameters. For example:
KAMA
needs three:fast
,period
, andslow
T3
needs four:period
,tCount
,vFactor
- Most others just need
input
andperiod
The method assumes youāve validated those inputs at the UI/property level ā so you wonāt get runtime errors due to invalid values like zero or negative periods. We delegate validation to the [Range(...)]
attributes inside the indicator.
š¹ Why not use fewer parameters?
This method is currently verbose on purpose ā it keeps each MAās logic explicit and helps ensure you never miss a required input for a more complex calculation. But this won’t scale well if we add more types or configurations.
Thatās why weāve left this inline comment:
// NOTE: The method LearnGetMovingAverage currently takes many parameters to accommodate
// different moving average types. As this expands, consider grouping these into a
// single class (e.g., LearnMAParams) for improved maintainability and readability.
Eventually, weāll refactor this to pass a single LearnMAParams
object that contains all properties. That will:
- Eliminate the risk of mixing up parameter order
- Make testing and extension easier
- Reduce boilerplate in every indicator that calls this method
But for now, keeping the logic visible and unwrapped gives us more control as we prototype and finalize design choices.
š Full Add-On Code So Far
#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;
#endregion
// MyDailyTake.com
//
// Copyright Ā© 2025 MyDailyTake.com
// All rights reserved.
//
// This software is made available for educational and personal use only.
// You are permitted to view and modify the code solely for your own private use.
//
// You may NOT:
// - Redistribute, copy, publish, or share this code in any form, modified or unmodified
// - Use this code in any commercial, institutional, or proprietary context
// - Include this code in any open source, paid, or public-facing software or package
//
// No license is granted for distribution, commercial use, or public disclosure.
//
// For questions or permissions, contact: https://mydailytake.com
namespace mdtLearnEnums
{
public enum LearnMovingAverageType
{
DEMA, // Period
EMA, // Period
HMA, // Period
KAMA, // Fast, Period, Slow
MAMA, // FastLimit, SlowLimit
SMA, // Period
T3, // Period, TCount, VFactor
TMA, // Period
TEMA, // Period
TRIX, // Period, SignalPeriod
VMA, // Period, VolatilityPeriod
VWMA, // Period
WMA, // Period
ZLEMA, // Period
}
}
// NOTE: The method LearnGetMovingAverage currently takes many parameters to accommodate
// different moving average types. As this expands, consider grouping these into a
// single class (e.g., LearnMAParams) for improved maintainability and readability.
namespace NinjaTrader.NinjaScript.Indicators
{
public partial class Indicator
{
/// <summary>
/// Returns the selected moving average based on the enum type provided.
/// </summary>
/// <param name="maType">The moving average type to use</param>
/// <param name="input">The input series</param>
/// <param name="period">Main period (used in most MAs)</param>
/// <param name="signalPeriod">Used in TRIX</param>
/// <param name="volatilityPeriod">Used in VMA</param>
/// <param name="tCount">Used in T3</param>
/// <param name="vFactor">Used in T3</param>
/// <param name="fast">Used in KAMA</param>
/// <param name="slow">Used in KAMA</param>
/// <param name="fastLimit">Used in MAMA</param>
/// <param name="slowLimit">Used in MAMA</param>
/// <returns>An ISeries<double> representing the selected moving average</returns>
public ISeries<double> LearnGetMovingAverage(
mdtLearnEnums.LearnMovingAverageType maType,
ISeries<double> input,
int period,
int signalPeriod,
int volatilityPeriod,
int tCount,
double vFactor,
int fast,
int slow,
double fastLimit,
double slowLimit)
{
// Input protection (e.g., period > 0) should be enforced through UI-level property attributes.
// This method assumes all inputs have already been validated by the calling indicator.
switch(maType)
{
case mdtLearnEnums.LearnMovingAverageType.DEMA:
return DEMA(input, period);
case mdtLearnEnums.LearnMovingAverageType.EMA:
return EMA(input, period);
case mdtLearnEnums.LearnMovingAverageType.HMA:
return HMA(input, period);
case mdtLearnEnums.LearnMovingAverageType.KAMA:
return KAMA(input, fast, period, slow);
case mdtLearnEnums.LearnMovingAverageType.MAMA:
return MAMA(input, fastLimit, slowLimit);
case mdtLearnEnums.LearnMovingAverageType.SMA:
return SMA(input, period);
case mdtLearnEnums.LearnMovingAverageType.T3:
return T3(input, period, tCount, vFactor);
case mdtLearnEnums.LearnMovingAverageType.TMA:
return TMA(input, period);
case mdtLearnEnums.LearnMovingAverageType.TEMA:
return TEMA(input, period);
case mdtLearnEnums.LearnMovingAverageType.TRIX:
return TRIX(input, period, signalPeriod);
case mdtLearnEnums.LearnMovingAverageType.VMA:
return VMA(input, period, volatilityPeriod);
case mdtLearnEnums.LearnMovingAverageType.VWMA:
return VWMA(input, period);
case mdtLearnEnums.LearnMovingAverageType.WMA:
return WMA(input, period);
case mdtLearnEnums.LearnMovingAverageType.ZLEMA:
return ZLEMA(input, period);
default:
return SMA(input, period);
}
}
}
}
š§ Building the Indicator Logic
Now that weāve got our add-on structured and returning the correct ISeries<double>
for each MA type, itās time to turn our attention to the indicator itself. This is where everything comes together. Weāll walk through each major part of the indicator step by stepānot just explaining what it does, but why weāve written it this way and how it all connects.
š 1. Header and Licensing
As with all of our Learn NinjaScript
series, this script is strictly educational and for personal use only. Thatās not just a suggestionāitās clearly stated in the licensing block at the top of the script:
// MyDailyTake.com
//
// Copyright Ā© 2025 MyDailyTake.com
// All rights reserved.
//
// This software is made available for educational and personal use only.
// You are permitted to view and modify the code solely for your own private use.
//
// You may NOT:
// - Redistribute, copy, publish, or share this code in any form, modified or unmodified
// - Use this code in any commercial, institutional, or proprietary context
// - Include this code in any open source, paid, or public-facing software or package
//
// No license is granted for distribution, commercial use, or public disclosure.
//
// For questions or permissions, contact: https://mydailytake.com
š” This is a clear and enforceable restriction. By placing it in the .cs
file, we make sure it travels with the code and discourages unauthorized repackaging or resale. This is not for vendors, and itās not for resaleāitās for learning and building your own understanding of NinjaScript.
š§¾ 2. Versioning and Identity
Weāve defined a few internal string fields to help track this indicator’s name, version, and purpose. These are used both in the indicatorās display name and in the description shown in the UI.
public string indVersion = "v1.0";
public string indName = "Better Moving Averages";
public string indDescription = "Select from 14+ moving average types, all in one customizable indicator.";
public override string DisplayName { get { return string.Format("{0} {1}", indName, indVersion); } }
if (State == State.SetDefaults)
{
Description = indDescription;
Name = indName;
}
This is useful for both you as the developer and for anyone testing or using your indicatorsāespecially as you start maintaining multiple versions or making enhancements. You could even consider expanding this later with changelogs or author fields if youāre maintaining a suite of indicators.
āļø 3. Moving Average Inputs (with Smart UI Refreshing)
This indicator supports 14+ different moving average typesāeach with its own quirks and parameter requirements. To make that manageable, we define a full set of configurable inputs using [NinjaScriptProperty]
attributes. These properties are grouped under āMoving Average Inputsā and mapped to the expected parameters for each MA type.
Example:
[NinjaScriptProperty]
[Range(1, int.MaxValue)]
[Display(Order = 2, GroupName = "Moving Average Inputs", Name = "Period")]
public int MaPeriod { get; set; }
We include every possible parameter even if only one or two are needed for the selected MA. This may seem excessive at first, but it makes the indicator flexible. Rather than building separate versions for T3, KAMA, MAMA, etc., we centralize everything into a single, unified indicator.
But hereās the important part: how do we only show the inputs that matter for the currently selected MA type?
Thatās where [RefreshProperties(RefreshProperties.All)]
comes in:
[NinjaScriptProperty]
[RefreshProperties(RefreshProperties.All)]
[Display(Order = 1, GroupName = "Moving Average Inputs", Name = "Type")]
public LearnMovingAverageType MaType { get; set; }
This attribute tells NinjaTrader to refresh all the visible properties anytime the MaType
is changed in the UI. Without it, you could switch from, say, KAMA to T3āand the Fast/Slow parameters would still show, even though theyāre no longer needed.
By combining this refresh behavior with our ICustomTypeDescriptor
implementation, we dynamically remove any unused inputs from the UI. That makes the property grid much cleaner and easier to useāand it helps prevent configuration mistakes.
š So in summary:
- We define all possible inputs up front.
- We use
RefreshProperties.All
to trigger a UI refresh whenMaType
changes. - We use
ICustomTypeDescriptor
to remove properties that arenāt needed for the selected MA.
Together, these techniques make this a powerful, configurable, and user-friendly indicator.
š§ 4. Filtering Parameters with ICustomTypeDescriptor
When you offer a flexible indicator like thisāwhere users can choose from over a dozen moving average typesāthereās a real risk of creating a cluttered, confusing input window. Weāve already handled one piece of that with [RefreshProperties(RefreshProperties.All)]
in the previous section. Now itās time to talk about the real engine behind our dynamic UI: ICustomTypeDescriptor
.
š” What Is ICustomTypeDescriptor
?
ICustomTypeDescriptor
is a powerful interface that allows you to intercept and modify the properties shown in the NinjaTrader property grid. When implemented on your indicator class, NinjaTrader calls into your code to ask, āHey, what properties should I show the user?ā You can answer differently based on whatever internal state you wantālike which moving average type is selected.
This lets us filter out irrelevant inputs and only show what matters for the selected MA.
š§± Where Does It Go?
ICustomTypeDescriptor
is implemented directly on the indicator class:
public class mdtLearnBetterMovingAverage : Indicator, ICustomTypeDescriptor
NinjaTrader will automatically call the methods inside this interface when the property panel is built. Itās important that this interface is declared on the class that hosts the properties (in this case, our indicator).
š How Does It Work?
We override the following method:
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
Inside this method, we:
- Grab the original list of all properties using
TypeDescriptor.GetProperties()
. - Copy them into a new collection we can modify.
- Call a helper method
ModifyProperties()
that removes the ones we donāt need.
Hereās a simplified look:
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
PropertyDescriptorCollection original = TypeDescriptor.GetProperties(GetType(), attributes);
PropertyDescriptor[] propsArray = new PropertyDescriptor[original.Count];
original.CopyTo(propsArray, 0);
PropertyDescriptorCollection filtered = new PropertyDescriptorCollection(propsArray);
ModifyProperties(filtered);
return filtered;
}
And inside ModifyProperties()
:
if (MaType != LearnMovingAverageType.KAMA)
{
col.Remove(col.Find("MaFast", true));
col.Remove(col.Find("MaSlow", true));
}
We repeat this logic for all the special-case MAs like T3, MAMA, VMA, and TRIX.
š§¼ Why It Matters
If we didnāt use ICustomTypeDescriptor
, weād be stuck showing every possible input for every possible MA typeāall the time. That would overwhelm the user and open the door to incorrect or nonsensical configurations.
By using ICustomTypeDescriptor
:
ā
We give the user a clean, contextual UI.
ā
We reduce the chance of configuration mistakes.
ā
We make the indicator easier to use and understand.
š 5. Adding the Plot
In State.SetDefaults
, we define the visual appearance of our indicator on the chart. We add a single line plot that will display the selected moving average:
AddPlot(new Stroke(Brushes.DimGray, DashStyleHelper.Solid, 3f), PlotStyle.Line, "Moving Average");
You can always add background coloring or bar overrides later, but keeping it clean and simple here is the best way to start.
š 6. Calculating the Moving Average (DataLoaded)
When NinjaTrader enters State.DataLoaded
, itās safe to start referencing other indicators and series. Thatās where we actually retrieve the correct MA from our add-on logic:
movingAverage = LearnGetMovingAverage(MaType, Input, MaPeriod, MaSignalPeriod, MaVolatilityPeriod, MaTCount, MaVFactor, MaFast, MaSlow, MaFastLimit, MaSlowLimit);
Although users can technically select On each tick as the calculation mode in NinjaTrader, this particular indicator doesnāt require tick-level data to function properly. It only needs price updates on bar close or price change to compute moving averages.
So, in the State.DataLoaded
section, we include this line:
if(Calculate == Calculate.OnEachTick)
Calculate = Calculate.OnPriceChange;
This is a subtle optimizationāit prevents recalculation on every tick unless you explicitly want that level of sensitivity.
š 7. Updating Values (OnBarUpdate)
The core logic is straightforward: pull the current value from the selected moving average and assign it to the plot:
if (CurrentBar < 0 || !movingAverage.IsValidDataPoint(0))
return;
MovingAverage[0] = movingAverage[0];
This ensures we only plot valid values and donāt throw exceptions on startup or invalid bars.
š¤ 8. Plot Output and Public Access
We expose the series publicly so it can be accessed from strategies or other indicators:
[Browsable(false)][XmlIgnore] public Series<double> MovingAverage { get { return Values[0]; } }
š¦ 9. Full Code (For Reference)
You can find the entire code below to compare against your own build. Donāt copy/paste blindlyāuse it to check your work and understand the flow. This indicator pulls together everything weāve covered so far and sets the stage for advanced customization in future BMA parts.
#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;
using mdtLearnEnums;
#endregion
// MyDailyTake.com
//
// Copyright Ā© 2025 MyDailyTake.com
// All rights reserved.
//
// This software is made available for educational and personal use only.
// You are permitted to view and modify the code solely for your own private use.
//
// You may NOT:
// - Redistribute, copy, publish, or share this code in any form, modified or unmodified
// - Use this code in any commercial, institutional, or proprietary context
// - Include this code in any open source, paid, or public-facing software or package
//
// No license is granted for distribution, commercial use, or public disclosure.
//
// For questions or permissions, contact: https://mydailytake.com
namespace NinjaTrader.NinjaScript.Indicators.mdtLearnInd
{
public class mdtLearnBetterMovingAverage : Indicator, ICustomTypeDescriptor
{
#region Versioning
public string indVersion = "v1.0";
public string indName = "Better Moving Averages";
public string indDescription = "Select from 14+ moving average types, all in one customizable indicator.";
public override string DisplayName { get { return string.Format("{0} {1}", indName, indVersion); } }
#endregion
#region Moving Average Inputs
[NinjaScriptProperty]
[RefreshProperties(RefreshProperties.All)]
[Display(Order = 01, GroupName = "Moving Average Inputs", Name = "Type", Description = "")]
public LearnMovingAverageType MaType { get; set; }
[NinjaScriptProperty]
[Range(1, int.MaxValue)]
[Display(Order = 02, GroupName = "Moving Average Inputs", Name = "Period", Description = "")]
public int MaPeriod { get; set; }
[NinjaScriptProperty]
[Range(1, int.MaxValue)]
[Display(Order = 03, GroupName = "Moving Average Inputs", Name = "Signal", Description = "")]
public int MaSignalPeriod { get; set; }
[NinjaScriptProperty]
[Range(1, int.MaxValue)]
[Display(Order = 04, GroupName = "Moving Average Inputs", Name = "Volatility", Description = "")]
public int MaVolatilityPeriod { get; set; }
[NinjaScriptProperty]
[Range(1, int.MaxValue)]
[Display(Order = 05, GroupName = "Moving Average Inputs", Name = "TCount", Description = "")]
public int MaTCount { get; set; }
[NinjaScriptProperty]
[Range(0, double.MaxValue)]
[Display(Order = 06, GroupName = "Moving Average Inputs", Name = "VFactor", Description = "")]
public double MaVFactor { get; set; }
[NinjaScriptProperty]
[Range(1, int.MaxValue)]
[Display(Order = 07, GroupName = "Moving Average Inputs", Name = "Fast", Description = "")]
public int MaFast { get; set; }
[NinjaScriptProperty]
[Range(1, int.MaxValue)]
[Display(Order = 08, GroupName = "Moving Average Inputs", Name = "Slow", Description = "")]
public int MaSlow { get; set; }
[NinjaScriptProperty]
[Range(0, double.MaxValue)]
[Display(Order = 09, GroupName = "Moving Average Inputs", Name = "Fast Limit", Description = "")]
public double MaFastLimit { get; set; }
[NinjaScriptProperty]
[Range(0, double.MaxValue)]
[Display(Order = 10, GroupName = "Moving Average Inputs", Name = "Slow Limit", Description = "")]
public double MaSlowLimit { get; set; }
#endregion
#region Variables
private ISeries<double> movingAverage;
#endregion
#region OnStateChange
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
#region Settings
Description = indDescription;
Name = indName;
Calculate = Calculate.OnBarClose;
IsOverlay = true;
DisplayInDataBox = true;
DrawOnPricePanel = true;
DrawHorizontalGridLines = true;
DrawVerticalGridLines = true;
PaintPriceMarkers = true;
ScaleJustification = NinjaTrader.Gui.Chart.ScaleJustification.Right;
IsSuspendedWhileInactive = true;
#endregion
#region Defaults
MaType = LearnMovingAverageType.EMA;
MaPeriod = 14;
MaSignalPeriod = 9;
MaVolatilityPeriod = 10;
MaTCount = 5;
MaVFactor = 0.7;
MaFast = 12;
MaSlow = 26;
MaFastLimit = 0.5;
MaSlowLimit = 0.5;
#endregion
#region Plots
AddPlot(new Stroke(Brushes.DimGray, DashStyleHelper.Solid, 3f), PlotStyle.Line, "Moving Average");
#endregion
}
else if (State == State.Configure)
{
}
else if (State == State.DataLoaded)
{
if(Calculate == Calculate.OnEachTick)
Calculate = Calculate.OnPriceChange;
movingAverage = LearnGetMovingAverage(MaType, Input, MaPeriod, MaSignalPeriod, MaVolatilityPeriod, MaTCount, MaVFactor, MaFast, MaSlow, MaFastLimit, MaSlowLimit);
}
}
#endregion
protected override void OnBarUpdate()
{
#region Pre-Process
if (CurrentBar < 0
|| !movingAverage.IsValidDataPoint(0))
return;
#endregion
MovingAverage[0] = movingAverage[0];
}
#region Plots / Public
[Browsable(false)][XmlIgnore] public Series<double> MovingAverage { get { return Values[0]; } }
#endregion
#region TypeDescriptor
private void ModifyProperties(PropertyDescriptorCollection col)
{
if (MaType != LearnMovingAverageType.T3)
{
col.Remove(col.Find("MaTCount", true));
col.Remove(col.Find("MaVFactor", true));
}
if (MaType != LearnMovingAverageType.MAMA)
{
col.Remove(col.Find("MaFastLimit", true));
col.Remove(col.Find("MaSlowLimit", true));
}
if (MaType != LearnMovingAverageType.VMA)
{
col.Remove(col.Find("MaVolatilityPeriod", true));
}
if (MaType != LearnMovingAverageType.KAMA)
{
col.Remove(col.Find("MaFast", true));
col.Remove(col.Find("MaSlow", true));
}
if (MaType != LearnMovingAverageType.TRIX)
{
col.Remove(col.Find("MaSignalPeriod", true));
}
}
#region Misc Helpers
public AttributeCollection GetAttributes()
{
return TypeDescriptor.GetAttributes(GetType());
}
public string GetClassName()
{
return TypeDescriptor.GetClassName(GetType());
}
public string GetComponentName()
{
return TypeDescriptor.GetComponentName(GetType());
}
public TypeConverter GetConverter()
{
return TypeDescriptor.GetConverter(GetType());
}
public EventDescriptor GetDefaultEvent()
{
return TypeDescriptor.GetDefaultEvent(GetType());
}
public PropertyDescriptor GetDefaultProperty()
{
return TypeDescriptor.GetDefaultProperty(GetType());
}
public object GetEditor(Type editorBaseType)
{
return TypeDescriptor.GetEditor(GetType(), editorBaseType);
}
public EventDescriptorCollection GetEvents(Attribute[] attributes)
{
return TypeDescriptor.GetEvents(GetType(), attributes);
}
public EventDescriptorCollection GetEvents()
{
return TypeDescriptor.GetEvents(GetType());
}
public PropertyDescriptorCollection GetProperties(Attribute[] attributes)
{
PropertyDescriptorCollection orig = TypeDescriptor.GetProperties(GetType(), attributes);
PropertyDescriptor[] arr = new PropertyDescriptor[orig.Count];
orig.CopyTo(arr, 0);
PropertyDescriptorCollection col = new PropertyDescriptorCollection(arr);
ModifyProperties(col);
return col;
}
public PropertyDescriptorCollection GetProperties()
{
return TypeDescriptor.GetProperties(GetType());
}
public object GetPropertyOwner(PropertyDescriptor pd)
{
return this;
}
#endregion
#endregion
}
}
#region NinjaScript generated code. Neither change nor remove.
namespace NinjaTrader.NinjaScript.Indicators
{
public partial class Indicator : NinjaTrader.Gui.NinjaScript.IndicatorRenderBase
{
private mdtLearnInd.mdtLearnBetterMovingAverage[] cachemdtLearnBetterMovingAverage;
public mdtLearnInd.mdtLearnBetterMovingAverage mdtLearnBetterMovingAverage(LearnMovingAverageType maType, int maPeriod, int maSignalPeriod, int maVolatilityPeriod, int maTCount, double maVFactor, int maFast, int maSlow, double maFastLimit, double maSlowLimit)
{
return mdtLearnBetterMovingAverage(Input, maType, maPeriod, maSignalPeriod, maVolatilityPeriod, maTCount, maVFactor, maFast, maSlow, maFastLimit, maSlowLimit);
}
public mdtLearnInd.mdtLearnBetterMovingAverage mdtLearnBetterMovingAverage(ISeries<double> input, LearnMovingAverageType maType, int maPeriod, int maSignalPeriod, int maVolatilityPeriod, int maTCount, double maVFactor, int maFast, int maSlow, double maFastLimit, double maSlowLimit)
{
if (cachemdtLearnBetterMovingAverage != null)
for (int idx = 0; idx < cachemdtLearnBetterMovingAverage.Length; idx++)
if (cachemdtLearnBetterMovingAverage[idx] != null && cachemdtLearnBetterMovingAverage[idx].MaType == maType && cachemdtLearnBetterMovingAverage[idx].MaPeriod == maPeriod && cachemdtLearnBetterMovingAverage[idx].MaSignalPeriod == maSignalPeriod && cachemdtLearnBetterMovingAverage[idx].MaVolatilityPeriod == maVolatilityPeriod && cachemdtLearnBetterMovingAverage[idx].MaTCount == maTCount && cachemdtLearnBetterMovingAverage[idx].MaVFactor == maVFactor && cachemdtLearnBetterMovingAverage[idx].MaFast == maFast && cachemdtLearnBetterMovingAverage[idx].MaSlow == maSlow && cachemdtLearnBetterMovingAverage[idx].MaFastLimit == maFastLimit && cachemdtLearnBetterMovingAverage[idx].MaSlowLimit == maSlowLimit && cachemdtLearnBetterMovingAverage[idx].EqualsInput(input))
return cachemdtLearnBetterMovingAverage[idx];
return CacheIndicator<mdtLearnInd.mdtLearnBetterMovingAverage>(new mdtLearnInd.mdtLearnBetterMovingAverage(){ MaType = maType, MaPeriod = maPeriod, MaSignalPeriod = maSignalPeriod, MaVolatilityPeriod = maVolatilityPeriod, MaTCount = maTCount, MaVFactor = maVFactor, MaFast = maFast, MaSlow = maSlow, MaFastLimit = maFastLimit, MaSlowLimit = maSlowLimit }, input, ref cachemdtLearnBetterMovingAverage);
}
}
}
namespace NinjaTrader.NinjaScript.MarketAnalyzerColumns
{
public partial class MarketAnalyzerColumn : MarketAnalyzerColumnBase
{
public Indicators.mdtLearnInd.mdtLearnBetterMovingAverage mdtLearnBetterMovingAverage(LearnMovingAverageType maType, int maPeriod, int maSignalPeriod, int maVolatilityPeriod, int maTCount, double maVFactor, int maFast, int maSlow, double maFastLimit, double maSlowLimit)
{
return indicator.mdtLearnBetterMovingAverage(Input, maType, maPeriod, maSignalPeriod, maVolatilityPeriod, maTCount, maVFactor, maFast, maSlow, maFastLimit, maSlowLimit);
}
public Indicators.mdtLearnInd.mdtLearnBetterMovingAverage mdtLearnBetterMovingAverage(ISeries<double> input , LearnMovingAverageType maType, int maPeriod, int maSignalPeriod, int maVolatilityPeriod, int maTCount, double maVFactor, int maFast, int maSlow, double maFastLimit, double maSlowLimit)
{
return indicator.mdtLearnBetterMovingAverage(input, maType, maPeriod, maSignalPeriod, maVolatilityPeriod, maTCount, maVFactor, maFast, maSlow, maFastLimit, maSlowLimit);
}
}
}
namespace NinjaTrader.NinjaScript.Strategies
{
public partial class Strategy : NinjaTrader.Gui.NinjaScript.StrategyRenderBase
{
public Indicators.mdtLearnInd.mdtLearnBetterMovingAverage mdtLearnBetterMovingAverage(LearnMovingAverageType maType, int maPeriod, int maSignalPeriod, int maVolatilityPeriod, int maTCount, double maVFactor, int maFast, int maSlow, double maFastLimit, double maSlowLimit)
{
return indicator.mdtLearnBetterMovingAverage(Input, maType, maPeriod, maSignalPeriod, maVolatilityPeriod, maTCount, maVFactor, maFast, maSlow, maFastLimit, maSlowLimit);
}
public Indicators.mdtLearnInd.mdtLearnBetterMovingAverage mdtLearnBetterMovingAverage(ISeries<double> input , LearnMovingAverageType maType, int maPeriod, int maSignalPeriod, int maVolatilityPeriod, int maTCount, double maVFactor, int maFast, int maSlow, double maFastLimit, double maSlowLimit)
{
return indicator.mdtLearnBetterMovingAverage(input, maType, maPeriod, maSignalPeriod, maVolatilityPeriod, maTCount, maVFactor, maFast, maSlow, maFastLimit, maSlowLimit);
}
}
}
#endregion
š We’ll continue expanding this in future posts with visual overlays, dynamic input updates, bar coloring, and additional smoothing options.
š§Ŗ How to Use the Indicator in NinjaTrader
Now that the code is complete and everythingās wired up, itās time to put it to the test in a live chart. This part is often overlooked, but itās where the practical value of your work really shines. Here’s how to get started using your new Better Moving Average indicator:
ā Applying It to a Chart
- Open a Chart for your preferred instrument (e.g., ES 06-25).
- Right-click the chart and choose Indicators.
- Scroll to find
Better Moving Averages
(under themdtLearnInd
folder if you organized it that way). - Add it to the chart and click OK.
You should now see a dark gray line plotted as your selected moving average.
šļø Exploring the Inputs
Open the indicator settings again. You’ll see the full list of parameters under the “Moving Average Inputs” group. Hereās whatās special:
- When you change the MA Type, the available inputs will change in real time.
- For example, selecting
TRIX
will show a Signal field, while selectingKAMA
will expose Fast and Slow periods.
That filtering is handled by the ICustomTypeDescriptor
, which modifies the property grid on the fly to only show relevant settings. This not only prevents confusion, but also reduces input errors for types that donāt use certain fields.
š Troubleshooting Tips
If you donāt see a line plotted:
- Make sure your
MaPeriod
is a valid number (greater than 1). - Confirm that data is loaded for your instrument and timeframe.
- Open the NinjaScript Output window to check for any errors thrown during loading.
- If you changed the
Calculate
mode toOnEachTick
, remember the indicator forces it toOnPriceChange
(since tick-level precision is unnecessary here).
š Final Thoughts
This post brought our Better Moving Average project from concept into execution. We now have:
- š A structured file setup with separate folders for indicators and add-ons
- āļø A flexible, modular
LearnGetMovingAverage()
function that returns 14+ different MA types - š§ Dynamic parameter filtering using
ICustomTypeDescriptor
, so your indicator UI only shows the inputs that actually apply - š„ļø A clean and efficient implementation that defaults to
OnPriceChange
to reduce unnecessary processing
While we havenāt added any visual filtering like bar or background coloring just yet, weāve laid the groundwork. You can now easily reference the calculated moving average through MovingAverage[0]
, which gives you a clean signal to use however you want ā including future enhancements like:
- Color bars when price is above or below the MA
- Highlight background during slope shifts
- Trigger alerts on MA crosses
You now have a foundational tool thatās fully functional and designed with expandability in mind.
š Prop Trading Discounts
š„91% off at Bulenox.com with the code MDT91