There was no MQL5 source code available for the Dynamic Zones Polychromatic Momentum, so I attempted to create this version.
To summarize:
To summarize:
- It doesn’t repaint on closed bars.
- The problem is the signal line and dynamic zones don’t recalculate or display correctly on the current (open) bar.
If you don’t need the current bar, the code can be tweaked so it just leaves bar 0 blank. If you do find the current bar useful, I’d really appreciate if someone could help fix that part.
Inserted Code
//+------------------------------------------------------------------+
//| Created by Th3W1z4rd using ChatGPT 5 Thinking model |
//| Dynamic Zones Polychromatic Momentum for MT5 |
//| MT4 version by Mladen Rakic |
//| https://forex-station.com/attach/file/3257739 |
//+------------------------------------------------------------------+
#property copyright "Public domain"
#property version "1.961"
#property strict
// separate subwindow, 5 buffers, 4 plotted lines
#property indicator_separate_window
#property indicator_buffers 5
#property indicator_plots 4
// plot 0: zero line
#property indicator_type1 DRAW_LINE
#property indicator_color1 clrDimGray
#property indicator_label1 "Zero"
#property indicator_style1 STYLE_DOT
// plot 1: upper dynamic zone
#property indicator_type2 DRAW_LINE
#property indicator_color2 clrCrimson
#property indicator_label2 "Overbought"
#property indicator_style2 STYLE_DOT
// plot 2: lower dynamic zone
#property indicator_type3 DRAW_LINE
#property indicator_color3 clrDarkGreen
#property indicator_label3 "Oversold"
#property indicator_style3 STYLE_DOT
// plot 3: main momentum line with color by slope
#property indicator_type4 DRAW_COLOR_LINE
#property indicator_color4 C'0,95,190', clrCrimson
#property indicator_width4 3
#property indicator_label4 "Momentum"
// price source options
enum enPrices { pr_close=0, pr_open, pr_high, pr_low, pr_median, pr_typical, pr_weighted, pr_average };
// core settings
input int MomentumLength = 14; // momentum lookback length
input enPrices Price = pr_close; // price used for momentum
input int SmoothLength = 10; // adaptive smoother length
input double SmoothPhase = 0.0; // adaptive smoother phase
// dynamic zones settings
input int DzLookBackBars = 35; // rolling window for quantiles
input double DzStartBuyProbability = 0.05; // lower zone quantile
input double DzStartSellProbability = 0.95; // upper zone quantile
// optional redraw controls
input bool UseTimerRecalc = false; // enable timer-based redraw
input int TimerSeconds = 1; // timer interval seconds
input int TimerRecalcBars = 1500; // reserved
// behavior flags
input bool StrictClosedBars = true; // keep history frozen
input bool MirrorBar1ToBar0 = false; // legacy toggle, not used
// indicator buffers
double ZeroBuffer[]; // median of momentum (DZ mid)
double ObBuffer[]; // upper dynamic zone
double OsBuffer[]; // lower dynamic zone
double MomBuffer[]; // smoothed momentum
double MomColor[]; // color index for momentum slope
// scratch arrays for smoothing and quantiles
static double qtmp[]; // temp values for window quantile
double wrk[][10]; // state used by iSmooth
#define bsmax 5
#define bsmin 6
#define volty 7
#define vsum 8
#define avolty 9
// state tracking
uint g_last_calc_ms = 0; // timestamp of last calc
uint g_skip_timer_until_ms = 0; // throttle chart-change refresh
int g_last_committed_closed = -1;// last closed bar processed
// clamp index into [0, n-1]
int clamp_index(int idx, int n)
{
if(n <= 0) return 0;
if(idx < 0) return 0;
if(idx >= n) return n - 1;
return idx;
}
// adaptive smoother, keeps rolling state in wrk
double iSmooth(double price, double length, double phase, int i, int bars, int s=0)
{
if(length <= 1) return(price);
if(bars <= 0) return(price);
if(ArrayRange(wrk,0) != bars) ArrayResize(wrk, bars);
int r = bars - i - 1;
r = clamp_index(r, bars);
// seed on first record
if(r == 0)
{
int k;
for(k = 0; k < 7; k++) wrk[r][k + s] = price;
for(; k < 10; k++) wrk[r][k + s] = 0.0;
return(price);
}
// volatility-adaptive section
double len1 = MathMax(MathLog(MathSqrt(0.5 * (length - 1))) / MathLog(2.0) + 2.0, 0);
double pow1 = MathMax(len1 - 2.0, 0.5);
double del1 = price - wrk[r - 1][bsmax + s];
double del2 = price - wrk[r - 1][bsmin + s];
double div = 1.0 / (10.0 + 10.0 * (MathMin(MathMax(length - 10, 0), 100)) / 100);
int forBar = MathMin(r, 10);
wrk[r][volty + s] = 0;
if(MathAbs(del1) > MathAbs(del2)) wrk[r][volty + s] = MathAbs(del1);
if(MathAbs(del1) < MathAbs(del2)) wrk[r][volty + s] = MathAbs(del2);
int r_for = clamp_index(r - forBar, bars);
wrk[r][vsum + s] = wrk[r - 1][vsum + s] + (wrk[r][volty + s] - wrk[r_for][volty + s]) * div;
wrk[r][avolty + s] = wrk[r - 1][avolty + s] + (2.0 / (MathMax(MathMin(length, 30) + 1.0, 1.0))) * (wrk[r][vsum + s] - wrk[r - 1][avolty + s]);
double dVolty = wrk[r][avolty + s] > 0 ? wrk[r][volty + s] / wrk[r][avolty + s] : 0;
if(dVolty < 1) dVolty = 1.0;
double pow2 = MathPow(dVolty, pow1);
double len2 = MathSqrt(0.5 * (length - 1)) * len1;
double Kv = MathPow(len2 / (len2 + 1), MathSqrt(pow2));
if(del1 > 0) wrk[r][bsmax + s] = price; else wrk[r][bsmax + s] = price - Kv * del1;
if(del2 < 0) wrk[r][bsmin + s] = price; else wrk[r][bsmin + s] = price - Kv * del2;
// filter core
double R = MathMax(MathMin(phase, 100), -100) / 100.0 + 1.5;
double beta = 0.45 * (length - 1) / (0.45 * (length - 1) + 2);
double alpha = MathPow(beta, pow2);
wrk[r][0 + s] = price + alpha * (wrk[r - 1][0 + s] - price);
wrk[r][1 + s] = (price - wrk[r][0 + s]) * (1 - beta) + beta * wrk[r - 1][1 + s];
wrk[r][2 + s] = (wrk[r][0 + s] + R * wrk[r][1 + s]);
wrk[r][3 + s] = (wrk[r][2 + s] - wrk[r - 1][4 + s]) * MathPow((1 - alpha), 2) + MathPow(alpha, 2) * wrk[r - 1][3 + s];
wrk[r][4 + s] = (wrk[r - 1][4 + s] + wrk[r][3 + s]);
return(wrk[r][4 + s]);
}
// pick price per input setting
double getPrice(enPrices p, const double &open[], const double &close[], const double &high[], const double &low[], int i)
{
int n = ArraySize(close);
i = clamp_index(i, n);
switch(p)
{
case pr_close: return close[i];
case pr_open: return open[i];
case pr_high: return high[i];
case pr_low: return low[i];
case pr_median: return (high[i] + low[i]) / 2.0;
case pr_typical: return (high[i] + low[i] + close[i]) / 3.0;
case pr_weighted:return (high[i] + low[i] + close[i] + close[i]) / 4.0;
case pr_average: return (high[i] + low[i] + open[i] + close[i]) / 4.0;
}
return close[i];
}
// rolling quantile over closed bars only; excludes bar 0
double window_quantile(const double &arr[], int i, int bars, int lookBack, double q, int last_closed)
{
if(bars <= 0) return EMPTY_VALUE;
i = clamp_index(i, bars);
if(lookBack <= 1) return arr[i];
if(q < 0.0) q = 0.0;
if(q > 1.0) q = 1.0;
int start = MathMax(i, 1); // avoid using bar 0 in zone stats
if(last_closed < start) return arr[i];
int max_take = MathMin(lookBack, last_closed - start + 1);
if(max_take <= 0) return arr[i];
ArrayResize(qtmp, max_take);
int count = 0;
// collect valid values
for(int j = 0; j < max_take; j++)
{
double v = arr[start + j];
if(v != EMPTY_VALUE && MathIsValidNumber(v))
qtmp[count++] = v;
}
if(count <= 0) return arr[i];
ArrayResize(qtmp, count);
ArraySort(qtmp);
// linear interpolation between ranks
double pos = q * (count - 1);
int idx = (int)MathFloor(pos);
double frac = pos - idx;
if(idx >= count - 1) return qtmp[count - 1];
return qtmp[idx] * (1.0 - frac) + qtmp[idx + 1] * frac;
}
// compute momentum and zones for bar i using data up to last_closed
void compute_bar(const int bars_clamped,
const double &open[],
const double &high[],
const double &low[],
const double &close[],
int i,
int last_closed)
{
int n = ArraySize(close);
if(n <= 0) return;
last_closed = clamp_index(last_closed, n);
i = clamp_index(i, n);
// weighted momentum using 1/sqrt(k+1)
double sumMom = 0.0, sumW = 0.0;
for(int k = 0; (i + k + 1) <= last_closed && k < MomentumLength; k++)
{
double w = MathSqrt(k + 1.0);
double price_i = getPrice(Price, open, close, high, low, i);
double price_ik1 = getPrice(Price, open, close, high, low, i + k + 1);
sumMom += (price_i - price_ik1) / w;
sumW += w;
}
double raw = (sumW > 0.0) ? sumMom / sumW : 0.0;
// smooth momentum
MomBuffer[i] = iSmooth(raw, SmoothLength, SmoothPhase, i, bars_clamped, 0);
// dynamic zones from quantiles of closed bars only
const double loq = DzStartBuyProbability;
const double hiq = DzStartSellProbability;
OsBuffer[i] = window_quantile(MomBuffer, i, bars_clamped, DzLookBackBars, loq, last_closed);
ObBuffer[i] = window_quantile(MomBuffer, i, bars_clamped, DzLookBackBars, hiq, last_closed);
ZeroBuffer[i] = window_quantile(MomBuffer, i, bars_clamped, DzLookBackBars, 0.5, last_closed);
// color by slope vs next bar
int next = i + 1;
double slope = (next <= last_closed && MomBuffer[next] != EMPTY_VALUE)
? (MomBuffer[i] - MomBuffer[next]) : 0.0;
MomColor[i] = (slope >= 0.0) ? 0.0 : 1.0;
}
// set buffers and visuals
int OnInit()
{
SetIndexBuffer(0, ZeroBuffer, INDICATOR_DATA);
SetIndexBuffer(1, ObBuffer, INDICATOR_DATA);
SetIndexBuffer(2, OsBuffer, INDICATOR_DATA);
SetIndexBuffer(3, MomBuffer, INDICATOR_DATA);
SetIndexBuffer(4, MomColor, INDICATOR_COLOR_INDEX);
ArraySetAsSeries(ZeroBuffer, true);
ArraySetAsSeries(ObBuffer, true);
ArraySetAsSeries(OsBuffer, true);
ArraySetAsSeries(MomBuffer, true);
ArraySetAsSeries(MomColor, true);
IndicatorSetString(INDICATOR_SHORTNAME, " ");
// minimal legend
for(int p=0; p<4; ++p)
{
PlotIndexSetInteger(p, PLOT_SHOW_DATA, false);
PlotIndexSetString(p, PLOT_LABEL, "");
}
// color indexes for momentum plot
PlotIndexSetInteger(3, PLOT_COLOR_INDEXES, 2);
// warmup period before first drawn momentum
PlotIndexSetInteger(3, PLOT_DRAW_BEGIN, MomentumLength + SmoothLength + DzLookBackBars);
if(UseTimerRecalc && TimerSeconds > 0) EventSetTimer(TimerSeconds);
g_last_committed_closed = -1;
return(INIT_SUCCEEDED);
}
// main calculation loop
int OnCalculate(const int rates_total,
const int prev_calculated,
const datetime &time[],
const double &open[],
const double &high[],
const double &low[],
const double &close[],
const long &tick_volume[],
const long &volume[],
const int &spread[])
{
// need at least one closed bar
if(rates_total < 2)
{
if(rates_total > 0)
{
ZeroBuffer[0] = ObBuffer[0] = OsBuffer[0] = MomBuffer[0] = EMPTY_VALUE;
MomColor[0] = 0.0;
}
return(prev_calculated);
}
// clear on first run
if(prev_calculated == 0)
{
int upto = rates_total - 1;
for(int i = 0; i <= upto; ++i)
{
ZeroBuffer[i] = EMPTY_VALUE;
ObBuffer[i] = EMPTY_VALUE;
OsBuffer[i] = EMPTY_VALUE;
MomBuffer[i] = EMPTY_VALUE;
MomColor[i] = 0.0;
}
}
// indices
int last_closed = rates_total - 2; // last closed bar
int bars_clamped = last_closed + 1; // working length for smoother
int min_index = 0; // include bar 0
// full recompute when needed
if(prev_calculated == 0 || g_last_committed_closed > last_closed || g_last_committed_closed < 0)
{
for(int i = last_closed; i >= min_index; --i)
compute_bar(bars_clamped, open, high, low, close, i, last_closed);
g_last_committed_closed = last_closed;
}
// new closed bars: update recent window and refresh bar 0
else if(last_closed > g_last_committed_closed)
{
int warm = MomentumLength + SmoothLength + DzLookBackBars + 5;
int first = MathMax(min_index, last_closed - warm);
for(int i = last_closed; i >= first; --i)
compute_bar(bars_clamped, open, high, low, close, i, last_closed);
compute_bar(bars_clamped, open, high, low, close, 0, last_closed);
g_last_committed_closed = last_closed;
}
// no new closed bars: keep history frozen, refresh bar 0 only
else
{
compute_bar(bars_clamped, open, high, low, close, 0, last_closed);
}
g_last_calc_ms = GetTickCount();
return(rates_total);
}
// rebuild closed history on chart re-scale or symbol/TF change
void FullRecomputeFromChart()
{
int bars_total = Bars(_Symbol, _Period);
if(bars_total < 2) return;
int last_closed = bars_total - 2;
int bars_clamped = last_closed + 1;
int min_index = 0;
MqlRates rates[];
int copied = CopyRates(_Symbol, _Period, 0, bars_clamped, rates);
if(copied < bars_clamped) return;
ArraySetAsSeries(rates, true);
static double o[], h[], l[], c[];
ArrayResize(o, bars_clamped); ArrayResize(h, bars_clamped);
ArrayResize(l, bars_clamped); ArrayResize(c, bars_clamped);
for(int i=0; i<bars_clamped; i++){ o[i]=rates[i].open; h[i]=rates[i].high; l[i]=rates[i].low; c[i]=rates[i].close; }
if(ArrayRange(wrk,0) != bars_clamped) ArrayResize(wrk, bars_clamped);
for(int i = last_closed; i >= min_index; --i)
compute_bar(bars_clamped, o, h, l, c, i, last_closed);
g_last_committed_closed = last_closed;
g_last_calc_ms = GetTickCount();
ChartRedraw();
}
// minimal event handlers
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
if(id == CHARTEVENT_CHART_CHANGE)
{
g_skip_timer_until_ms = GetTickCount() + 1500;
FullRecomputeFromChart();
}
}
void OnTimer()
{
if(!UseTimerRecalc) return;
ChartRedraw();
}
void OnDeinit(const int reason)
{
if(UseTimerRecalc) EventKillTimer();
}