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