DeltaMomentumConviction (DMC)
A multi-lookback momentum framework designed to filter noise by requiring confluence between delta movement and raw price action.
Core Logic
-
Confluence Requirement: Each lookback row evaluates both delta direction and price direction. Confluence (agreement) is required for inclusion; divergence results in a neutral state.
-
Conviction Scoring: Consensus breadth cascades into a continuous conviction score. Low confluence reduces score intensity, effectively dimming the visual and analytical output.
-
Kinetic Filter: Entry signals require a “price-move” confirmation. If delta shifts without a corresponding price move (e.g., lack of dip/rise during a potential reversal), the signal is discarded as noise.
Visual Hierarchy
| Layer | Description |
|---|---|
| Background | Color represents trend direction; opacity scales with conviction magnitude. |
| MasterTrend | Block plot representing unanimous breadth; opacity fades as conviction wanes. |
| Row_1..N | Block plots: Green/Red (Confluent); Gray (Divergent). |
| ConvictionLine | Continuous line; brightness denotes confluence ratio. |
| Entry Dots | Plotted on conviction zero-crossings + kinetic confirmation. |
Entry Signal Logic
-
Long: Conviction crosses from negative to positive AND MasterTrend is UP AND price dipped below the drawdown start point.
-
Short: Conviction crosses from positive to negative AND MasterTrend is DOWN AND price rose above the drawdown start point.
Data Output
-
TrendValue / ConvictionScore
-
BreadthScore / DepthScore
-
DeltaMomo / EntrySignal
Self-calibrating: The system utilizes relative movement thresholds rather than fixed knobs.
#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;
#endregion
// DeltaMomentumConviction
//
// Multi-lookback delta momentum consensus with magnitude weighting.
//
// Combines delta momentum runs with multi-lookback consensus detection,
// adds continuous conviction scoring and pullback-recovery entry signals.
//
// Visual layers (bottom to top):
// ConvictionLine — continuous line, brightness = confluence ratio, color = direction
// Zero line — dashed reference at conviction = 0
// Entry dots — appear on conviction zero-cross with kinetic price confirmation
// Row_1..Row_N — Block plots: green/red when delta+price confluent, gray when divergent
// MasterTrend — Block plot, flips on unanimous confluent breadth, opacity fades with |conviction|
// Background — color from trend direction, opacity from conviction magnitude
//
// Delta-price confluence model:
// Each row checks both delta momentum direction AND price direction for that lookback.
// Only rows where both agree (confluent) contribute to directional consensus.
// Divergent rows (delta and price disagree) render neutral gray and count as nothing.
// This cascades: breadth drops → conviction drops → master dims → background fades.
//
// Entry signal logic (self-calibrating, no threshold knobs):
// Long: conviction crosses from negative to positive while master trend = UP
// Short: conviction crosses from positive to negative while master trend = DOWN
// Kinetic filter: price must have moved counter-trend during the drawdown phase
// (on renko, close must have dipped below drawdown start price for longs, or
// risen above it for shorts — if price didn’t move, it’s delta noise, not a pullback)
//
// Transparent output series for Data Box / Market Analyzer:
// TrendValue, ConvictionScore, BreadthScore, DepthScore, DeltaMomo, EntrySignal
namespace NinjaTrader.NinjaScript.Indicators
{
[Gui.CategoryOrder(“Parameters”, 1)]
[Gui.CategoryOrder(“Color”, 2)]
[Gui.CategoryOrder(“Opacity”, 3)]
public class DeltaMomentumConviction : Indicator
{
private OrderFlowCumulativeDelta cumulativeDelta;
private bool trendUp = true;
// Entry signal drawdown tracking with kinetic price filter
private bool _inDrawdown = false;
private double _drawdownRefPrice = 0;
private double _drawdownExtreme = 0;
// Pre-built brush arrays at discrete opacity steps (avoids per-bar allocation)
private const int OpacitySteps = 20;
private Brush[] bgUpBrushes;
private Brush[] bgDownBrushes;
// Intensity-scaled brushes for heat map rows and master trend
private const int IntensitySteps = 10;
private Brush[] upIntensityBrushes;
private Brush[] downIntensityBrushes;
private Brush[] masterUpBrushes;
private Brush[] masterDownBrushes;
private Brush divergentBrush;
// Fixed plot indices
private const int IdxMasterTrend = 0;
private const int IdxTrendValue = 1;
private const int IdxConviction = 2;
private const int IdxBreadth = 3;
private const int IdxDepth = 4;
private const int IdxDeltaMomo = 5;
private const int IdxConvLine = 6;
private const int IdxEntrySignal = 7;
private const int IdxRowStart = 8;
// Conviction line Y mapping: conviction [-1, +1] → Y [ConvCenter ± ConvScale]
private const double ConvCenter = 1.5;
private const double ConvScale = 1.2;
// Row Y offset — rows start above the conviction zone with a gap
private const double RowOffset = 5.0;
protected override void OnStateChange()
{
if (State == State.SetDefaults)
{
Description = @"Multi-lookback delta momentum consensus with magnitude weighting and entry signals.";
Name = "DeltaMomentumConviction";
Calculate = Calculate.OnEachTick;
IsOverlay = false;
DisplayInDataBox = true;
DrawOnPricePanel = false;
DrawHorizontalGridLines = false;
DrawVerticalGridLines = false;
PaintPriceMarkers = false;
ScaleJustification = NinjaTrader.Gui.Chart.ScaleJustification.Right;
IsSuspendedWhileInactive = true;
MaximumBarsLookBack = MaximumBarsLookBack.Infinite;
ShowTransparentPlotsInDataBox = true;
// Parameters
Lookback = 10;
NormPeriod = 200;
DeltaType = CumulativeDeltaType.BidAsk;
ShowTrendMap = true;
// Color
UpColor = Brushes.Lime;
DownColor = Brushes.Crimson;
BgUpColor = Brushes.DarkGreen;
BgDownColor = Brushes.DarkRed;
EnableBG = true;
EnableBGAll = true;
// Opacity
BgMinOpacity = 5;
BgMaxOpacity = 40;
// Fixed plots (indices 0-7)
AddPlot(new Stroke(Brushes.White, 2), PlotStyle.Block, "MasterTrend");
AddPlot(Brushes.Transparent, "TrendValue");
AddPlot(Brushes.Transparent, "ConvictionScore");
AddPlot(Brushes.Transparent, "BreadthScore");
AddPlot(Brushes.Transparent, "DepthScore");
AddPlot(Brushes.Transparent, "DeltaMomo");
AddPlot(new Stroke(Brushes.DodgerBlue, 2), PlotStyle.Line, "ConvictionLine");
AddPlot(new Stroke(Brushes.Yellow, 4), PlotStyle.Dot, "EntrySignal");
// Zero reference line for conviction
AddLine(new Stroke(Brushes.DimGray, DashStyleHelper.Dash, 0.5f), ConvCenter, "Zero");
}
else if (State == State.Configure)
{
// Tick series required for OrderFlowCumulativeDelta
AddDataSeries(BarsPeriodType.Tick, 1);
// Dynamic row plots (indices 8..7+Lookback)
for (int i = 0; i < Lookback; i++)
{
AddPlot(Brushes.White, "Row_" + (i + 1));
Plots[IdxRowStart + i].PlotStyle = PlotStyle.Block;
Plots[IdxRowStart + i].Width = 2;
}
// Freeze plot brushes for workspace restore safety
if (UpColor != null) { Brush b = UpColor.Clone(); b.Freeze(); UpColor = b; }
if (DownColor != null) { Brush b = DownColor.Clone(); b.Freeze(); DownColor = b; }
// Pre-build intensity brushes for heat map rows (opacity 0.15-1.0)
upIntensityBrushes = new Brush[IntensitySteps + 1];
downIntensityBrushes = new Brush[IntensitySteps + 1];
masterUpBrushes = new Brush[IntensitySteps + 1];
masterDownBrushes = new Brush[IntensitySteps + 1];
for (int s = 0; s <= IntensitySteps; s++)
{
double frac = (double)s / IntensitySteps;
double plotOp = 0.15 + frac * 0.85;
if (UpColor != null)
{ Brush b = UpColor.Clone(); b.Opacity = plotOp; b.Freeze(); upIntensityBrushes[s] = b; }
if (DownColor != null)
{ Brush b = DownColor.Clone(); b.Opacity = plotOp; b.Freeze(); downIntensityBrushes[s] = b; }
double masterOp = 0.20 + frac * 0.80;
if (UpColor != null)
{ Brush b = UpColor.Clone(); b.Opacity = masterOp; b.Freeze(); masterUpBrushes[s] = b; }
if (DownColor != null)
{ Brush b = DownColor.Clone(); b.Opacity = masterOp; b.Freeze(); masterDownBrushes[s] = b; }
}
// Divergent cell brush — neutral gray for rows without delta-price confluence
divergentBrush = Brushes.DimGray.Clone();
divergentBrush.Opacity = 0.3;
divergentBrush.Freeze();
// Pre-build background brushes at discrete opacity steps
bgUpBrushes = new Brush[OpacitySteps + 1];
bgDownBrushes = new Brush[OpacitySteps + 1];
double minOp = BgMinOpacity / 100.0;
double maxOp = BgMaxOpacity / 100.0;
for (int s = 0; s <= OpacitySteps; s++)
{
double frac = (double)s / OpacitySteps;
double opacity = minOp + frac * (maxOp - minOp);
if (BgUpColor != null)
{
Brush b = BgUpColor.Clone();
b.Opacity = opacity;
b.Freeze();
bgUpBrushes[s] = b;
}
if (BgDownColor != null)
{
Brush b = BgDownColor.Clone();
b.Opacity = opacity;
b.Freeze();
bgDownBrushes[s] = b;
}
}
}
else if (State == State.DataLoaded)
{
cumulativeDelta = OrderFlowCumulativeDelta(DeltaType, CumulativeDeltaPeriod.Bar, 0);
}
}
protected override void OnBarUpdate()
{
// All computation on tick series — primary series ignored
if (BarsInProgress != 1)
return;
// Sync hosted cumulative delta
cumulativeDelta.Update(cumulativeDelta.BarsArray[1].Count - 1, 1);
// Need at least 1 prior bar for DeltaMomo[1] access
if (CurrentBars[0] < 1)
return;
// ----------------------------------------------------------------
// Delta momentum accumulation
// ----------------------------------------------------------------
double barDelta = cumulativeDelta.DeltaClose[0];
double prevMomo = Values[IdxDeltaMomo][1];
double momo;
if (barDelta > 0)
momo = (prevMomo > 0) ? prevMomo + barDelta : barDelta;
else if (barDelta < 0)
momo = (prevMomo < 0) ? prevMomo + barDelta : barDelta;
else
momo = prevMomo; // zero delta — no new info, carry forward
Values[IdxDeltaMomo][0] = momo;
// Need Lookback bars of DeltaMomo history for trend map comparison
if (CurrentBars[0] < Lookback + 1)
return;
// ----------------------------------------------------------------
// Rolling max for self-calibrating normalization
// (computed first — row intensity and depth both need it)
// ----------------------------------------------------------------
double absMomo = Math.Abs(momo);
int lookbackBars = Math.Min(NormPeriod, CurrentBars[0]);
double rollingMax = 0;
for (int j = 0; j < lookbackBars; j++)
{
double val = Math.Abs(Values[IdxDeltaMomo][j]);
if (val > rollingMax)
rollingMax = val;
}
// ----------------------------------------------------------------
// Multi-lookback comparison with delta-price confluence
// Only rows where BOTH delta momentum and price agree count
// toward the directional consensus. Divergent rows render gray.
// ----------------------------------------------------------------
int upCount = 0;
int downCount = 0;
for (int k = 0; k < Lookback; k++)
{
int barsBack = k + 1;
double deltaDiff = momo - Values[IdxDeltaMomo][barsBack];
double priceDiff = Closes[0][0] - Closes[0][barsBack];
// Confluence: both delta and price move in the same direction
bool confluent = (deltaDiff > 0 && priceDiff > 0) || (deltaDiff < 0 && priceDiff < 0);
if (confluent)
{
if (deltaDiff > 0) upCount++;
else downCount++;
}
// Row rendering
int plotIdx = IdxRowStart + k;
if (ShowTrendMap)
{
Values[plotIdx][0] = RowOffset + Lookback - k;
if (confluent)
{
double intensity = (rollingMax > 0) ? Math.Min(Math.Abs(deltaDiff) / (2.0 * rollingMax), 1.0) : 0.5;
int iStep = (int)Math.Round(intensity * IntensitySteps);
iStep = Math.Max(0, Math.Min(iStep, IntensitySteps));
PlotBrushes[plotIdx][0] = deltaDiff > 0
? upIntensityBrushes[iStep]
: downIntensityBrushes[iStep];
}
else
{
PlotBrushes[plotIdx][0] = divergentBrush;
}
}
else
{
Values[plotIdx][0] = double.NaN;
}
}
// ----------------------------------------------------------------
// Breadth Score — consensus fraction [-1, +1]
// ----------------------------------------------------------------
double breadth = (double)(upCount - downCount) / Lookback;
Values[IdxBreadth][0] = breadth;
// ----------------------------------------------------------------
// Depth Score [0, 1] — uses rollingMax already computed above
// ----------------------------------------------------------------
double depth = (rollingMax > 0) ? Math.Min(absMomo / rollingMax, 1.0) : 0;
Values[IdxDepth][0] = depth;
// ----------------------------------------------------------------
// Conviction Score + Master Trend
// ----------------------------------------------------------------
double conviction = breadth * depth;
Values[IdxConviction][0] = conviction;
// Master trend latch — flip only on unanimity
bool prevTrendUp = trendUp;
if (upCount == Lookback && !trendUp)
trendUp = true;
else if (downCount == Lookback && trendUp)
trendUp = false;
// Reset drawdown tracking on trend flip
if (trendUp != prevTrendUp)
_inDrawdown = false;
// Master trend visual — opacity from |conviction|
double absConv = Math.Abs(conviction);
if (ShowTrendMap)
{
Values[IdxMasterTrend][0] = RowOffset + Lookback + 2;
int masterStep = (int)Math.Round(absConv * IntensitySteps);
masterStep = Math.Max(0, Math.Min(masterStep, IntensitySteps));
PlotBrushes[IdxMasterTrend][0] = trendUp ? masterUpBrushes[masterStep] : masterDownBrushes[masterStep];
}
else
{
Values[IdxMasterTrend][0] = double.NaN;
}
Values[IdxTrendValue][0] = trendUp ? 1 : -1;
// ----------------------------------------------------------------
// Conviction line — sqrt expansion spreads small values away from center,
// brightness from |conviction| gives equal gradient on both sides
// ----------------------------------------------------------------
double expandedConv = Math.Sign(conviction) * Math.Sqrt(Math.Abs(conviction));
Values[IdxConvLine][0] = ConvCenter + expandedConv * ConvScale;
int convLineStep = (int)Math.Round(Math.Abs(conviction) * IntensitySteps);
convLineStep = Math.Max(0, Math.Min(convLineStep, IntensitySteps));
PlotBrushes[IdxConvLine][0] = conviction >= 0
? upIntensityBrushes[convLineStep]
: downIntensityBrushes[convLineStep];
// ----------------------------------------------------------------
// Entry signal — conviction zero-cross with kinetic price filter
// Requires price to have actually moved counter-trend during drawdown
// ----------------------------------------------------------------
double prevConv = Values[IdxConviction][1];
bool entrySignal = false;
if (trendUp)
{
// Enter drawdown phase when conviction goes negative
if (prevConv < 0 && !_inDrawdown)
{
_inDrawdown = true;
_drawdownRefPrice = Closes[0][1];
_drawdownExtreme = Closes[0][0];
}
else if (_inDrawdown && prevConv < 0)
{
_drawdownExtreme = Math.Min(_drawdownExtreme, Closes[0][0]);
}
// Recovery: conviction crosses positive after kinetically confirmed pullback
bool kineticPullback = _inDrawdown && (_drawdownRefPrice - _drawdownExtreme > 0);
if (kineticPullback && conviction > 0 && prevConv <= 0)
{
entrySignal = true;
_inDrawdown = false;
}
}
else
{
// Enter drawdown phase when conviction goes positive (short trend)
if (prevConv > 0 && !_inDrawdown)
{
_inDrawdown = true;
_drawdownRefPrice = Closes[0][1];
_drawdownExtreme = Closes[0][0];
}
else if (_inDrawdown && prevConv > 0)
{
_drawdownExtreme = Math.Max(_drawdownExtreme, Closes[0][0]);
}
// Recovery: conviction crosses negative after kinetically confirmed rally
bool kineticRally = _inDrawdown && (_drawdownExtreme - _drawdownRefPrice > 0);
if (kineticRally && conviction < 0 && prevConv >= 0)
{
entrySignal = true;
_inDrawdown = false;
}
}
if (entrySignal)
{
Values[IdxEntrySignal][0] = ConvCenter;
PlotBrushes[IdxEntrySignal][0] = trendUp
? upIntensityBrushes[IntensitySteps]
: downIntensityBrushes[IntensitySteps];
}
else
{
Values[IdxEntrySignal][0] = double.NaN;
}
// ----------------------------------------------------------------
// Dynamic background opacity from conviction
// ----------------------------------------------------------------
if (EnableBG)
{
int bgStep = (int)Math.Round(absConv * OpacitySteps);
bgStep = Math.Max(0, Math.Min(bgStep, OpacitySteps));
Brush bg = trendUp ? bgUpBrushes[bgStep] : bgDownBrushes[bgStep];
if (EnableBGAll)
BackBrushAll = bg;
else
BackBrush = bg;
}
}
/// <summary>
/// When ShowTrendMap = true: full range for rows + master + conviction zone.
/// When ShowTrendMap = false: tight range around conviction zone — fills the panel.
/// </summary>
public override void OnCalculateMinMax()
{
MinValue = -0.5;
MaxValue = ShowTrendMap ? RowOffset + Lookback + 4 : 3.5;
}
#region Properties
// --- Programmatic access ---
[Browsable(false)] [XmlIgnore] public Series<double> MasterTrend { get { return Values[IdxMasterTrend]; } }
[Browsable(false)] [XmlIgnore] public Series<double> TrendValue { get { return Values[IdxTrendValue]; } }
[Browsable(false)] [XmlIgnore] public Series<double> ConvictionScore { get { return Values[IdxConviction]; } }
[Browsable(false)] [XmlIgnore] public Series<double> BreadthScore { get { return Values[IdxBreadth]; } }
[Browsable(false)] [XmlIgnore] public Series<double> DepthScore { get { return Values[IdxDepth]; } }
[Browsable(false)] [XmlIgnore] public Series<double> DeltaMomo { get { return Values[IdxDeltaMomo]; } }
[Browsable(false)] [XmlIgnore] public Series<double> ConvictionLine { get { return Values[IdxConvLine]; } }
[Browsable(false)] [XmlIgnore] public Series<double> EntrySignal { get { return Values[IdxEntrySignal]; } }
// --- Parameters ---
[NinjaScriptProperty]
[Range(1, 20)]
[Display(Name = "Lookback [1-20]", Description = "Number of bars back to compare delta momentum", Order = 1, GroupName = "Parameters")]
public int Lookback
{ get; set; }
[NinjaScriptProperty]
[Range(50, 500)]
[Display(Name = "Norm Period [50-500]", Description = "Rolling window for self-calibrating depth normalization", Order = 2, GroupName = "Parameters")]
public int NormPeriod
{ get; set; }
[Display(Name = "Delta Type", Description = "BidAsk or UpDownTick", Order = 3, GroupName = "Parameters")]
public CumulativeDeltaType DeltaType
{ get; set; }
[Display(Name = "Show Trend Map", Description = "When off, isolates conviction line with entry dots", Order = 4, GroupName = "Parameters")]
public bool ShowTrendMap
{ get; set; }
// --- Color ---
[XmlIgnore]
[Display(Name = "Up Color", Order = 1, GroupName = "Color")]
public Brush UpColor
{ get; set; }
[Browsable(false)]
public string UpColorSerializable
{
get { return Serialize.BrushToString(UpColor); }
set { UpColor = Serialize.StringToBrush(value); }
}
[XmlIgnore]
[Display(Name = "Down Color", Order = 2, GroupName = "Color")]
public Brush DownColor
{ get; set; }
[Browsable(false)]
public string DownColorSerializable
{
get { return Serialize.BrushToString(DownColor); }
set { DownColor = Serialize.StringToBrush(value); }
}
[Display(Name = "Enable Background", Order = 3, GroupName = "Color")]
public bool EnableBG
{ get; set; }
[Display(Name = "Color Full Chart", Order = 4, GroupName = "Color")]
public bool EnableBGAll
{ get; set; }
[XmlIgnore]
[Display(Name = "Background Up", Order = 5, GroupName = "Color")]
public Brush BgUpColor
{ get; set; }
[Browsable(false)]
public string BgUpColorSerializable
{
get { return Serialize.BrushToString(BgUpColor); }
set { BgUpColor = Serialize.StringToBrush(value); }
}
[XmlIgnore]
[Display(Name = "Background Down", Order = 6, GroupName = "Color")]
public Brush BgDownColor
{ get; set; }
[Browsable(false)]
public string BgDownColorSerializable
{
get { return Serialize.BrushToString(BgDownColor); }
set { BgDownColor = Serialize.StringToBrush(value); }
}
// --- Opacity ---
[Range(0, 100)]
[Display(Name = "Background Min Opacity", Description = "Opacity at zero conviction", Order = 1, GroupName = "Opacity")]
public int BgMinOpacity
{ get; set; }
[Range(0, 100)]
[Display(Name = "Background Max Opacity", Description = "Opacity at full conviction", Order = 2, GroupName = "Opacity")]
public int BgMaxOpacity
{ get; set; }
#endregion
}
}
GIving back with some tools I’ve built.
You can answer any question you have about this and how it works by using AI, just pour the code into an AI took and interrogate the code.

