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:

📎 Download the Source Files

Want to skip the typing and jump straight into the code? You can download the completed .cs files here:

✅ 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:

  1. Open the NinjaScript Editor
  2. Right-click the Indicators folder
  3. Select New Folder
  4. Enter the folder name: mdtLearnInd
  5. Right-click the new mdtLearnInd folder
  6. Select New Indicator
  7. Enter mdtLearnBetterMovingAverage as the name
  8. Click Generate
  9. 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:

  1. Right-click the AddOns folder
  2. Select New AddOn
  3. Enter mdtLearnAddon as the name
  4. Click Generate
  5. 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:

  1. 🧭 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.
  2. 🛠️ 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, and slow
  • T3 needs four: period, tCount, vFactor
  • Most others just need input and period

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 when MaType 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:

  1. Grab the original list of all properties using TypeDescriptor.GetProperties().
  2. Copy them into a new collection we can modify.
  3. 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

  1. Open a Chart for your preferred instrument (e.g., ES 06-25).
  2. Right-click the chart and choose Indicators.
  3. Scroll to find Better Moving Averages (under the mdtLearnInd folder if you organized it that way).
  4. 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 selecting KAMA 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 to OnEachTick, remember the indicator forces it to OnPriceChange (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