In Part 3, we laid the groundwork for our Better Moving Average (BMA) indicator — setting up the file structure, building inputs for multiple moving average types, and wiring up the core logic. But identifying the average is only half the story.
In this post, we’re diving into trend detection. 📉📈
You’ll learn how to:
- Add a new enum to control how trend is calculated
- Use decorated enum descriptions in the NinjaTrader UI dropdown
- Store trend direction in a
Series<int>
- Add flexible logic for detecting rising, falling, or flat states
- Update your pre-checks to ensure your logic runs safely
By the end of this post, you’ll have a robust framework that not only computes a moving average — but also knows what the trend is doing right now. Let’s get to it.
📚 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)
- Learn NinjaScript: File Setup and Core Logic (BMA Part 3)
📎 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.
🧩 What’s New in the Add-On File
In Part 3 of our Better Moving Average (BMA) series, we started using the add-on file (mdtLearnAddon.cs
) as a central place to store shared tools, reusable logic, and helper types. This post builds on that by introducing a few important additions that make our indicator more dynamic and polished.
Let’s walk through the two major enhancements we made to the add-on file this round:
📚 1. New Enums: LearnBmaTrendStyle
To support configurable trend detection logic in our indicator, we introduced a new enum called LearnBmaTrendStyle
. This enum defines the different logic styles the user can select from when determining if the moving average is trending up, down, or flat.
Here’s what it looks like:
public enum LearnBmaTrendStyle
{
[Description("None")]
None,
[Description("Rising / Falling / Neutral")]
RisingFallingNeutral,
[Description("Rising / Falling / Last")]
RisingFallingLast,
[Description("Cross Above / Below")]
CloseAboveBelow,
[Description("Low Above / High Below")]
LowAboveHighBelow
}
Each value corresponds to a unique method of evaluating trend direction — from simple comparisons of current vs. previous values to more nuanced price location logic (like close above the MA, or low above the MA). These will be plugged directly into the indicator and visual logic later.
📝 Why it lives in the add-on: Keeping this enum in our mdtLearnAddon.cs
file (alongside other shared types) ensures we can reuse this in future indicators, strategies, or even chart marker tools without redefining it each time.
🎨 2. Decorated Enums: EnumDescriptionConverter
We also added a new utility class called EnumDescriptionConverter
. This lets us format our enum dropdowns in the NinjaTrader UI with clean, friendly names instead of the raw enum text like RisingFallingLast
.
For example, if you’re editing a property like this:
[Display(Name = "Trend Logic")]
public LearnBmaTrendStyle TrendStyleLogic { get; set; }
By default, the dropdown would show this:
RisingFallingNeutral
RisingFallingLast
CloseAboveBelow
Not ideal.
Instead, we now apply the EnumDescriptionConverter
like this:
[TypeConverter(typeof(mdtLearnEnums.EnumDescriptionConverter))]
[PropertyEditor("NinjaTrader.Gui.Tools.StringStandardValuesEditorKey")]
[Display(Name = "Logic")]
public LearnBmaTrendStyle TrendStyleLogic { get; set; }
And now it looks like this in the UI:
- “Rising / Falling / Neutral”
- “Rising / Falling / Last”
- “Cross Above / Below”
This is made possible by our new EnumDescriptionConverter
, which scans for [Description()]
attributes on each enum value and swaps them in for display.
🔍 Behind the Scenes: EnumDescriptionConverter
Code
Here’s the class itself. It lives in mdtLearnEnums
inside the same add-on file, so it’s globally accessible throughout our project.
public class EnumDescriptionConverter : EnumConverter
{
private Type enumType;
public EnumDescriptionConverter(Type type) : base(type)
{
enumType = type;
}
public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
{
foreach (var fi in enumType.GetFields())
{
var attr = (DescriptionAttribute)Attribute.GetCustomAttribute(fi, typeof(DescriptionAttribute));
if (attr != null && (string)value == attr.Description)
return Enum.Parse(enumType, fi.Name);
}
return Enum.Parse(enumType, (string)value);
}
public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destType)
{
var fi = enumType.GetField(value.ToString());
var attr = (DescriptionAttribute)Attribute.GetCustomAttribute(fi, typeof(DescriptionAttribute));
return attr != null ? attr.Description : value.ToString();
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
var values = Enum.GetValues(enumType).Cast<object>().Select(v =>
{
var fi = enumType.GetField(v.ToString());
var attr = (DescriptionAttribute)Attribute.GetCustomAttribute(fi, typeof(DescriptionAttribute));
return attr != null ? attr.Description : v.ToString();
}).ToList();
return new StandardValuesCollection(values);
}
}
This isn’t something you’ll touch often — but once it’s there, it improves every single property grid where you use [Description]
on an enum.
📦 Wrapping Up the Add-On Updates
These changes set us up for success as we expand the Better Moving Average indicator with more visual logic and state management. By organizing the enums and helper classes in the add-on, we’re keeping the indicator clean and focused — just the way we like it.
📄 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
}
public enum LearnBmaTrendStyle
{
[Description("None")]
None,
[Description("Rising / Falling / Neutral")]
RisingFallingNeutral,
[Description("Rising / Falling / Last")]
RisingFallingLast,
[Description("Cross Above / Below")]
CloseAboveBelow,
[Description("Low Above / High Below")]
LowAboveHighBelow
}
#region EnumDescriptionConverter
public class EnumDescriptionConverter : EnumConverter
{
private Type enumType;
public EnumDescriptionConverter(Type type) : base(type)
{
enumType = type;
}
public override bool CanConvertFrom(ITypeDescriptorContext context, Type srcType)
{
return srcType == typeof(string);
}
public override bool CanConvertTo(ITypeDescriptorContext context, Type destType)
{
return destType == typeof(string);
}
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
foreach (System.Reflection.FieldInfo fi in enumType.GetFields())
{
DescriptionAttribute dna = (DescriptionAttribute)Attribute.GetCustomAttribute(fi, typeof(DescriptionAttribute));
if (dna != null && (string)value == dna.Description)
return Enum.Parse(enumType, fi.Name);
}
return Enum.Parse(enumType, (string)value);
}
public override object ConvertTo(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value, Type destType)
{
System.Reflection.FieldInfo fi = enumType.GetField(value.ToString());
DescriptionAttribute dna = (DescriptionAttribute) Attribute.GetCustomAttribute(fi, typeof(DescriptionAttribute));
if(dna != null)
return dna.Description;
else
return value.ToString();
}
public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
{
List<string> values = new List<string>();
foreach (var v in Enum.GetValues(enumType))
{
System.Reflection.FieldInfo fi = enumType.GetField(v.ToString());
DescriptionAttribute dna = (DescriptionAttribute)Attribute.GetCustomAttribute(fi, typeof(DescriptionAttribute));
if (dna != null)
values.Add(dna.Description);
else
values.Add(v.ToString());
}
return new StandardValuesCollection(values);
}
}
#endregion
}
// 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);
}
}
}
}
📈 What’s New in the Indicator File
With the add-on file updated to support our trend logic, it was time to hook everything up in the indicator itself. This part was all about giving users control over how we define the trend — and reacting to that definition on every bar.
Let’s walk through what changed.
🎛️ 1. New Property: TrendStyleLogic
The first step was exposing a new user-selectable property:
[NinjaScriptProperty]
[TypeConverter(typeof(mdtLearnEnums.EnumDescriptionConverter))]
[PropertyEditor("NinjaTrader.Gui.Tools.StringStandardValuesEditorKey")]
[Display(Order = 01, GroupName = "Trend Logic Inputs Inputs", Name = "Logic", Description = "")]
public LearnBmaTrendStyle TrendStyleLogic { get; set; }
This property tells the indicator how to define trend — whether it’s based on rising/falling values, crossovers, or price position. The [TypeConverter]
and [PropertyEditor]
ensure the dropdown displays nicely, thanks to our EnumDescriptionConverter
.
The default is set in OnStateChange()
:
TrendStyleLogic = LearnBmaTrendStyle.RisingFallingNeutral;
🧠 2. A New Series for Trend Values
Next, we added a Series<int>
to store the current trend state of each bar:
private Series<int> maTrend;
This allows us to track and use the trend value at any bar index — past or present. It’s initialized in State.DataLoaded
like this:
maTrend = new Series<int>(this);
Each bar is assigned a new value during OnBarUpdate()
using a helper method:
maTrend[0] = GetTrendDirection();
And that GetTrendDirection()
method is where the magic happens…
🧪 3. GetTrendDirection()
and Logic Variants
Here’s the core method:
private int GetTrendDirection()
{
switch (TrendStyleLogic)
{
case LearnBmaTrendStyle.RisingFallingNeutral: return TrendRisingFallingNeutral();
case LearnBmaTrendStyle.RisingFallingLast: return TrendRisingFallingLast();
case LearnBmaTrendStyle.CloseAboveBelow: return TrendCloseAboveBelow();
case LearnBmaTrendStyle.LowAboveHighBelow: return TrendLowAboveHighBelow();
default: return 0;
}
}
Each of those TrendXYZ()
methods checks for rising, falling, or neutral based on a different logic style. For example:
private int TrendRisingFallingNeutral()
{
if (movingAverage[0] > movingAverage[1]) return 1;
if (movingAverage[0] < movingAverage[1]) return -1;
return 0;
}
Or:
private int TrendLowAboveHighBelow()
{
if (Low[0] > movingAverage[0]) return 1;
if (High[0] < movingAverage[0]) return -1;
return 0;
}
They all return 1
(uptrend), -1
(downtrend), or 0
(neutral).
The RisingFallingLast
style is a bit different — it returns the last known direction if we’re flat:
private int TrendRisingFallingLast()
{
if (movingAverage[0] > movingAverage[1]) return 1;
if (movingAverage[0] < movingAverage[1]) return -1;
return maTrend[1];
}
This makes the trend “sticky” instead of flipping back to neutral during sideways moves.
🧼 4. Updated Pre-Processing Checks
Finally, we made our OnBarUpdate()
more robust by adding checks to avoid calculating trend too early or with bad data:
if (CurrentBar < 1
|| MaPeriod <= 0
|| !movingAverage.IsValidDataPoint(0)
|| !movingAverage.IsValidDataPoint(1))
return;
This helps prevent false trends, runtime errors, and wasted computation.
📄 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;
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 Trend Logic Inputs
[NinjaScriptProperty]
[TypeConverter(typeof(mdtLearnEnums.EnumDescriptionConverter))]
[PropertyEditor("NinjaTrader.Gui.Tools.StringStandardValuesEditorKey")]
[Display(Order = 01, GroupName = "Trend Logic Inputs Inputs", Name = "Logic", Description = "")]
public LearnBmaTrendStyle TrendStyleLogic { get; set; }
#endregion
#region Variables
private ISeries<double> movingAverage;
private Series<int> maTrend;
#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;
TrendStyleLogic = LearnBmaTrendStyle.RisingFallingNeutral;
#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;
maTrend = new Series<int>(this);
movingAverage = LearnGetMovingAverage(MaType, Input, MaPeriod, MaSignalPeriod, MaVolatilityPeriod, MaTCount, MaVFactor, MaFast, MaSlow, MaFastLimit, MaSlowLimit);
}
}
#endregion
protected override void OnBarUpdate()
{
#region Pre-Process
if (CurrentBar < 1
|| MaPeriod <= 0
|| !movingAverage.IsValidDataPoint(0)
|| !movingAverage.IsValidDataPoint(1))
return;
#endregion
maTrend[0] = GetTrendDirection();
MovingAverage[0] = movingAverage[0];
}
#region Trend Logic
private int GetTrendDirection()
{
switch(TrendStyleLogic)
{
case LearnBmaTrendStyle.RisingFallingNeutral:
return TrendRisingFallingNeutral();
case LearnBmaTrendStyle.RisingFallingLast:
return TrendRisingFallingLast();
case LearnBmaTrendStyle.CloseAboveBelow:
return TrendCloseAboveBelow();
case LearnBmaTrendStyle.LowAboveHighBelow:
return TrendLowAboveHighBelow();
default:
return 0;
}
}
private int TrendRisingFallingNeutral()
{
if(movingAverage[0] > movingAverage[1]) return 1;
if(movingAverage[0] < movingAverage[1]) return -1;
return 0;
}
private int TrendRisingFallingLast()
{
if(movingAverage[0] > movingAverage[1]) return 1;
if(movingAverage[0] < movingAverage[1]) return -1;
return maTrend[1];
}
private int TrendCloseAboveBelow()
{
if(Input[0] > movingAverage[0]) return 1;
if(Input[0] < movingAverage[0]) return -1;
return 0;
}
private int TrendLowAboveHighBelow()
{
if(Low[0] > movingAverage[0]) return 1;
if(High[0] < movingAverage[0]) return -1;
return 0;
}
#endregion
#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, LearnBmaTrendStyle trendStyleLogic)
{
return mdtLearnBetterMovingAverage(Input, maType, maPeriod, maSignalPeriod, maVolatilityPeriod, maTCount, maVFactor, maFast, maSlow, maFastLimit, maSlowLimit, trendStyleLogic);
}
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, LearnBmaTrendStyle trendStyleLogic)
{
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].TrendStyleLogic == trendStyleLogic && 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, TrendStyleLogic = trendStyleLogic }, 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, LearnBmaTrendStyle trendStyleLogic)
{
return indicator.mdtLearnBetterMovingAverage(Input, maType, maPeriod, maSignalPeriod, maVolatilityPeriod, maTCount, maVFactor, maFast, maSlow, maFastLimit, maSlowLimit, trendStyleLogic);
}
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, LearnBmaTrendStyle trendStyleLogic)
{
return indicator.mdtLearnBetterMovingAverage(input, maType, maPeriod, maSignalPeriod, maVolatilityPeriod, maTCount, maVFactor, maFast, maSlow, maFastLimit, maSlowLimit, trendStyleLogic);
}
}
}
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, LearnBmaTrendStyle trendStyleLogic)
{
return indicator.mdtLearnBetterMovingAverage(Input, maType, maPeriod, maSignalPeriod, maVolatilityPeriod, maTCount, maVFactor, maFast, maSlow, maFastLimit, maSlowLimit, trendStyleLogic);
}
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, LearnBmaTrendStyle trendStyleLogic)
{
return indicator.mdtLearnBetterMovingAverage(input, maType, maPeriod, maSignalPeriod, maVolatilityPeriod, maTCount, maVFactor, maFast, maSlow, maFastLimit, maSlowLimit, trendStyleLogic);
}
}
}
#endregion
🧠 Final Thoughts
This was a big step in bringing the Better Moving Average to life. We moved beyond file setup and started adding the logic that makes the indicator functional and flexible.
We introduced new enum-based trend logic, created a reusable EnumDescriptionConverter
, and added a Series<int>
to store and track the trend state bar by bar. These aren’t just minor additions — they’re foundational pieces that give us full control over how we define and respond to trend direction in future posts.
If you’ve been following along from Part 1, you now have:
- A custom indicator that pulls from 14+ moving average types
- Configurable logic for how trends are detected
- Cleanly separated files for the indicator and add-on
- Enum-driven customization in the UI
Up next, we’ll use this trend logic to bring the chart to life — with background coloring, state tracking, and visual filtering that helps you instantly see what the market is doing.
🎉 Prop Trading Discounts
💥91% off at Bulenox.com with the code MDT91