Files
profitable-expert-advisor/lab/EAs/Derivative.mq5
T

1583 lines
31 KiB
Plaintext
Raw Normal View History

2026-05-27 14:59:00 +02:00
//+------------------------------------------------------------------+
//| Derivative.mq5 |
//| EA: finite-difference d1d3 of price + optional demo signals |
//| (Former indicator — attach as Expert Advisor on chart.) |
//+------------------------------------------------------------------+
#property copyright "Lab"
#property link ""
#property version "3.00"
#property strict
#include <Trade/Trade.mqh>
#include <Canvas/Canvas.mqh>
#property description "DERIVATIVE_CALC EA v3 — derivatives + optional trades; canvas strip or legacy DerivativePlots."
#property description "Canvas mode draws d1/d2/d3 at bottom without indicators; legacy mode optional."
enum ENUM_DERIVATIVE_VIEW
{
DERIVATIVE_ALL = 0,
DERIVATIVE_LEVEL_1 = 1,
DERIVATIVE_LEVEL_2 = 2,
DERIVATIVE_LEVEL_3 = 3
};
input group "=== Instrument ==="
input string InpSymbol = ""; // blank = chart symbol
input group "=== Series ==="
input ENUM_TIMEFRAMES InpSignalTF = PERIOD_CURRENT; // PERIOD_CURRENT = chart TF
input ENUM_APPLIED_PRICE InpAppliedPrice = PRICE_CLOSE;
input group "=== Layout (reporting focus) ==="
input ENUM_DERIVATIVE_VIEW InpWhichDerivative = DERIVATIVE_ALL; // Which values drive Comment / optional trade filter
input group "=== Calculus discretization ==="
input int InpDiffStep = 1;
input bool InpNormalizePoints = true;
input group "=== Smoothing ==="
input int InpSmoothPeriod = 0;
input group "=== On-chart guide (labels on main window) ==="
input bool InpShowHelpPanel = true;
input color InpHelpTitleColor = clrWhite;
input color InpHelpBodyColor = clrSilver;
input group "=== Display ==="
input bool InpShowComment = true; // Status line + d1/d2/d3 on chart
input int InpCommentThrottleMs = 200; // Min real-time ms between Comment() calls (0=off). Visual tester floods redraws without this.
input bool InpDebugTrace = false; // Experts/Journal: derivatives + attach diagnostics
input group "=== Canvas strip (EA draws d1/d2/d3 — no indicator .ex5) ==="
input bool InpUseCanvasPlots = true; // Three stacked strips at bottom (bitmap on main window)
input int InpCanvasPlotBars = 320; // Bars across width (series 0 = current)
input int InpCanvasPanelHeight = 210; // Total pixel height for three strips
input int InpCanvasBottomMargin = 28; // From chart bottom (CORNER_LEFT_LOWER)
input int InpCanvasSideMargin = 4; // Left/right inset
input int InpCanvasRedrawMs = 350; // Min ms between canvas rebuilds
input color InpCanvasBgColor = clrBlack;
input color InpCanvasGridColor = clrDimGray;
input group "=== Legacy: DerivativePlots indicator (optional) ==="
input bool InpAutoAttachDerivativePlots = false; // Requires DerivativePlots.ex5 in Indicators
input bool InpAttachPlotsInTester = false; // Non-visual tester: set true if .ex5 present
input string InpPlotsIndicatorPath = "DerivativePlots"; // .ex5 basename in Indicators folder
input bool InpPlotsSeparateWindows = false; // Three iCustom instances + stacked subwindows
input bool InpPlotsUnifyYScale = true; // DerivativePlots InpUnifyPlotYScale
input group "=== Optional demo trading (off by default) ==="
input bool InpTradeEnabled = false;
input double InpLots = 0.01;
input ulong InpMagic = 931001;
input int InpSlippagePoints = 30;
input int InpAtrPeriod = 14;
input double InpSlAtrMult = 2.0;
input double InpTpAtrMult = 3.0;
CTrade g_trade;
datetime g_lastBarTime = 0;
string g_chartSymbol = "";
bool g_pendingDerivativePlotsAttach = false;
bool g_derivativePlotsFailedToLoad = false;
bool g_derivPlotsAttachDone = false;
uint g_lastCommentWallMs = 0;
CCanvas g_deriv_canvas;
bool g_deriv_canvas_created = false;
uint g_lastCanvasRedrawMs = 0;
const string HELPER_FAMILY = "DerivRead";
const string DERIV_CANVAS_OBJ = "DerivEA_CanvasStrip_v3";
void CommentThrottled(const string text)
{
if(InpCommentThrottleMs <= 0)
{
Comment(text);
return;
}
const uint now = GetTickCount();
if(g_lastCommentWallMs != 0 && (now - g_lastCommentWallMs) < (uint)InpCommentThrottleMs)
return;
g_lastCommentWallMs = now;
Comment(text);
}
string DerivativePlotsMissingHint()
{
if(!g_derivativePlotsFailedToLoad || !InpAutoAttachDerivativePlots)
return "";
const string want = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Indicators\\" + InpPlotsIndicatorPath + ".ex5";
return "\n--- DerivativePlots NOT loaded ---\nPlace compiled file:\n" + want +
"\n(Navigator: Indicators -> right-click -> Open folder -> paste .mq5, Compile.)";
}
string HelpPrefix()
{
return HELPER_FAMILY + "_EA_L" + IntegerToString((int)InpWhichDerivative) + "_";
}
void DeleteOurHelpObjects()
{
const string px = HelpPrefix();
ObjectDelete(0, px + "title");
ObjectDelete(0, px + "body");
ObjectDelete(0, px + "interp");
}
bool LabelCreateMain(const string name, const int corner, const int xd, const int yd,
const string text, const color clr, const int fontSize, const int anchor)
{
if(!ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0))
return false;
ObjectSetInteger(0, name, OBJPROP_CORNER, corner);
ObjectSetInteger(0, name, OBJPROP_ANCHOR, anchor);
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, xd);
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, yd);
ObjectSetString(0, name, OBJPROP_TEXT, text);
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize);
ObjectSetString(0, name, OBJPROP_FONT, "Consolas");
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
return true;
}
void TryBuildHelpPanel()
{
if(!InpShowHelpPanel)
{
DeleteOurHelpObjects();
return;
}
const string px = HelpPrefix();
ObjectDelete(0, px + "title");
ObjectDelete(0, px + "body");
ObjectDelete(0, px + "interp");
const int x0 = 8;
string title = "DERIVATIVE_CALC EA — readout\n";
string body = "";
string interp = "";
if(InpWhichDerivative == DERIVATIVE_ALL)
{
body =
"d1 = slope of price / step h (velocity)\n"
"d2 = change of d1 (acceleration)\n"
"d3 = change of d2 (jerk)\n"
"See Experts log + Comment line for numbers.";
interp = "Optional demo trades use Which derivative + sign rules (inputs).";
}
else if(InpWhichDerivative == DERIVATIVE_LEVEL_1)
{
title = "EA focus: d1 only\n";
body = "d1 > 0 : rising over h bars; < 0 falling; cross 0 : flip.";
interp = "Demo buy bias if d1>0 & d2>0 when trade enabled.";
}
else if(InpWhichDerivative == DERIVATIVE_LEVEL_2)
{
title = "EA focus: d2 only\n";
body = "d2 : momentum building (+) or fading (-) vs d1.";
interp = "Use with price context.";
}
else
{
title = "EA focus: d3 only\n";
body = "d3 : noisy; regime / climax hints.";
interp = "Large |d3| → acceleration changing fast.";
}
if(!LabelCreateMain(px + "title", CORNER_LEFT_UPPER, x0, 20, title, InpHelpTitleColor, 10, ANCHOR_LEFT_UPPER))
return;
if(!LabelCreateMain(px + "body", CORNER_LEFT_UPPER, x0, 42, body, InpHelpBodyColor, 8, ANCHOR_LEFT_UPPER))
{
ObjectDelete(0, px + "title");
return;
}
if(!LabelCreateMain(px + "interp", CORNER_LEFT_LOWER, x0, 8, interp, InpHelpBodyColor, 8, ANCHOR_LEFT_LOWER))
{
ObjectDelete(0, px + "title");
ObjectDelete(0, px + "body");
return;
}
}
double AppliedFromRates(const MqlRates &r)
{
switch(InpAppliedPrice)
{
case PRICE_OPEN: return r.open;
case PRICE_HIGH: return r.high;
case PRICE_LOW: return r.low;
case PRICE_CLOSE: return r.close;
case PRICE_MEDIAN: return (r.high + r.low) * 0.5;
case PRICE_TYPICAL: return (r.high + r.low + r.close) / 3.0;
case PRICE_WEIGHTED:return (r.high + r.low + r.close + r.close) / 4.0;
default: return r.close;
}
}
void SmoothPriceArray(const int total, const double &src[], double &dst[])
{
ArrayResize(dst, total);
const int p = InpSmoothPeriod;
if(p <= 1)
{
ArrayCopy(dst, src);
return;
}
const double alpha = 2.0 / (p + 1.0);
const int oldest = total - 1;
double ema = src[oldest];
dst[oldest] = ema;
for(int i = oldest - 1; i >= 0; i--)
{
ema = alpha * src[i] + (1.0 - alpha) * ema;
dst[i] = ema;
}
}
double SrcAt(const int i, const bool useSmooth, const double &smooth[], const double &raw[])
{
return useSmooth ? smooth[i] : raw[i];
}
bool ComputeDerivatives(const string sym, const ENUM_TIMEFRAMES tf,
double &out_d1, double &out_d2, double &out_d3)
{
out_d1 = out_d2 = out_d3 = 0.0;
const int h = MathMax(InpDiffStep, 1);
const int needBars = 50 + h * 6;
MqlRates rates[];
ArraySetAsSeries(rates, true);
const int n = CopyRates(sym, tf, 0, needBars, rates);
if(n < h * 3 + 5)
return false;
double raw[];
ArrayResize(raw, n);
ArraySetAsSeries(raw, true);
for(int i = 0; i < n; i++)
raw[i] = AppliedFromRates(rates[i]);
double smoothed[];
SmoothPriceArray(n, raw, smoothed);
const bool useSmooth = (InpSmoothPeriod > 1);
const double scale = InpNormalizePoints ? SymbolInfoDouble(sym, SYMBOL_POINT) : 1.0;
if(scale <= 0.0)
return false;
const int i = 1;
if(i + h >= n)
return false;
const double d1_i = (SrcAt(i, useSmooth, smoothed, raw) - SrcAt(i + h, useSmooth, smoothed, raw)) / ((double)h * scale);
if(i + 2 * h >= n)
{
out_d1 = d1_i;
return true;
}
const double d1_ip = (SrcAt(i + h, useSmooth, smoothed, raw) - SrcAt(i + 2 * h, useSmooth, smoothed, raw)) / ((double)h * scale);
const double d2_i = (d1_i - d1_ip) / ((double)h * scale);
if(i + 3 * h >= n)
{
out_d1 = d1_i;
out_d2 = d2_i;
return true;
}
const double d1_ip2 = (SrcAt(i + 2 * h, useSmooth, smoothed, raw) - SrcAt(i + 3 * h, useSmooth, smoothed, raw)) / ((double)h * scale);
const double d2_ip = (d1_ip - d1_ip2) / ((double)h * scale);
const double d3_i = (d2_i - d2_ip) / ((double)h * scale);
out_d1 = d1_i;
out_d2 = d2_i;
out_d3 = d3_i;
return true;
}
double CanvasSeriesAt(const int row, const int si,
const double &d1[], const double &d2[], const double &d3[])
{
if(row == 0)
return d1[si];
if(row == 1)
return d2[si];
return d3[si];
}
bool ComputeDerivativeSeries(const string sym, const ENUM_TIMEFRAMES tf,
const int plotBars,
double &d1[], double &d2[], double &d3[])
{
const int h = MathMax(InpDiffStep, 1);
const int need = plotBars + h * 4 + 10;
MqlRates rates[];
ArraySetAsSeries(rates, true);
const int n = CopyRates(sym, tf, 0, need, rates);
if(n < h * 3 + 5)
return false;
double raw[];
ArrayResize(raw, n);
ArraySetAsSeries(raw, true);
for(int i = 0; i < n; i++)
raw[i] = AppliedFromRates(rates[i]);
double smoothed[];
SmoothPriceArray(n, raw, smoothed);
const bool useSmooth = (InpSmoothPeriod > 1);
const double scale = InpNormalizePoints ? SymbolInfoDouble(sym, SYMBOL_POINT) : 1.0;
if(scale <= 0.0)
return false;
ArrayResize(d1, plotBars);
ArrayResize(d2, plotBars);
ArrayResize(d3, plotBars);
ArrayInitialize(d1, EMPTY_VALUE);
ArrayInitialize(d2, EMPTY_VALUE);
ArrayInitialize(d3, EMPTY_VALUE);
const int d1Count = MathMin(plotBars, n - h);
for(int si = 0; si < d1Count; si++)
d1[si] = (SrcAt(si, useSmooth, smoothed, raw) - SrcAt(si + h, useSmooth, smoothed, raw)) / ((double)h * scale);
for(int si = 0; si < plotBars; si++)
{
if(si + 2 * h >= n || si + h >= d1Count)
break;
d2[si] = (d1[si] - d1[si + h]) / ((double)h * scale);
}
for(int si = 0; si < plotBars; si++)
{
if(si + 3 * h >= n)
break;
if(si + h >= plotBars)
break;
if(d2[si] == EMPTY_VALUE || d2[si + h] == EMPTY_VALUE)
continue;
d3[si] = (d2[si] - d2[si + h]) / ((double)h * scale);
}
return true;
}
void UpdateDerivativeCanvasStrip()
{
if(!InpUseCanvasPlots)
return;
ENUM_TIMEFRAMES tf = InpSignalTF;
if(tf == PERIOD_CURRENT)
tf = (ENUM_TIMEFRAMES)Period();
double d1[], d2[], d3[];
if(!ComputeDerivativeSeries(g_chartSymbol, tf, InpCanvasPlotBars, d1, d2, d3))
return;
const int chartW = (int)ChartGetInteger(0, CHART_WIDTH_IN_PIXELS);
if(chartW < 80)
return;
const int panelW = MathMax(60, chartW - InpCanvasSideMargin * 2);
const int panelH = MathMax(90, InpCanvasPanelHeight);
const int x0 = InpCanvasSideMargin;
const int y0 = InpCanvasBottomMargin;
if(!g_deriv_canvas_created)
{
if(!g_deriv_canvas.CreateBitmapLabel(0, 0, DERIV_CANVAS_OBJ, x0, y0, panelW, panelH, COLOR_FORMAT_ARGB_NORMALIZE))
{
if(InpDebugTrace)
Print("DERIVATIVE_CALC: canvas CreateBitmapLabel failed err=", GetLastError());
return;
}
ObjectSetInteger(0, DERIV_CANVAS_OBJ, OBJPROP_CORNER, CORNER_LEFT_LOWER);
ObjectSetInteger(0, DERIV_CANVAS_OBJ, OBJPROP_ANCHOR, ANCHOR_LEFT_LOWER);
ObjectSetInteger(0, DERIV_CANVAS_OBJ, OBJPROP_SELECTABLE, false);
ObjectSetInteger(0, DERIV_CANVAS_OBJ, OBJPROP_HIDDEN, true);
g_deriv_canvas_created = true;
}
else
{
g_deriv_canvas.Resize(panelW, panelH);
ObjectSetInteger(0, DERIV_CANVAS_OBJ, OBJPROP_XDISTANCE, x0);
ObjectSetInteger(0, DERIV_CANVAS_OBJ, OBJPROP_YDISTANCE, y0);
}
g_deriv_canvas.Erase(ColorToARGB(InpCanvasBgColor, 255));
const int rows = 3;
const int rowH = MathMax(24, panelH / rows);
const uint clrLines[3] = {
ColorToARGB(clrDodgerBlue, 235),
ColorToARGB(clrOrange, 235),
ColorToARGB(clrMagenta, 235)
};
const string tags[3] = { "d1 velocity", "d2 acceleration", "d3 jerk" };
const int nPts = MathMin(InpCanvasPlotBars, ArraySize(d1));
if(nPts < 3)
{
g_deriv_canvas.Update();
return;
}
for(int r = 0; r < rows; r++)
{
const int yBase = r * rowH;
const int midY = yBase + rowH / 2;
g_deriv_canvas.LineAA(0.0, (double)midY, (double)(panelW - 1), (double)midY, ColorToARGB(InpCanvasGridColor, 70));
double vmin = DBL_MAX;
double vmax = -DBL_MAX;
for(int si = 0; si < nPts; si++)
{
const double v = CanvasSeriesAt(r, si, d1, d2, d3);
if(v == EMPTY_VALUE || !MathIsValidNumber(v))
continue;
if(v < vmin)
vmin = v;
if(v > vmax)
vmax = v;
}
if(vmin == DBL_MAX)
continue;
if(MathAbs(vmax - vmin) < 1e-15)
{
vmin -= 1.0;
vmax += 1.0;
}
g_deriv_canvas.FontSet("Consolas", -90);
g_deriv_canvas.TextOut(4, yBase + 2, tags[r], ColorToARGB(clrSilver, 220));
const double denom = (double)MathMax(1, nPts - 1);
for(int si = 0; si < nPts - 1; si++)
{
const double v0 = CanvasSeriesAt(r, si, d1, d2, d3);
const double v1 = CanvasSeriesAt(r, si + 1, d1, d2, d3);
if(v0 == EMPTY_VALUE || v1 == EMPTY_VALUE)
continue;
const double xf0 = (double)(panelW - 1) * (double)(nPts - 1 - si) / denom;
const double xf1 = (double)(panelW - 1) * (double)(nPts - 2 - si) / denom;
const double t0 = (v0 - vmin) / (vmax - vmin);
const double t1 = (v1 - vmin) / (vmax - vmin);
const int py0 = yBase + 3 + (int)((double)(rowH - 6) * (1.0 - t0));
const int py1 = yBase + 3 + (int)((double)(rowH - 6) * (1.0 - t1));
g_deriv_canvas.LineAA(xf0, (double)py0, xf1, (double)py1, clrLines[r]);
}
}
g_deriv_canvas.Update();
ChartRedraw(0);
}
bool HasOurPosition(const string sym)
{
for(int i = PositionsTotal() - 1; i >= 0; i--)
{
const ulong t = PositionGetTicket(i);
if(t == 0 || !PositionSelectByTicket(t))
continue;
if(PositionGetString(POSITION_SYMBOL) != sym)
continue;
if((ulong)PositionGetInteger(POSITION_MAGIC) != InpMagic)
continue;
return true;
}
return false;
}
double AtrPoints(const string sym, const ENUM_TIMEFRAMES tf)
{
const int h = iATR(sym, tf, InpAtrPeriod);
if(h == INVALID_HANDLE)
return 0.0;
double b[];
ArraySetAsSeries(b, true);
if(CopyBuffer(h, 0, 1, 1, b) != 1)
{
IndicatorRelease(h);
return 0.0;
}
IndicatorRelease(h);
const double pt = SymbolInfoDouble(sym, SYMBOL_POINT);
return (pt > 0.0 ? b[0] / pt : 0.0);
}
void RemoveDerivativePlotsIndicatorsFromChart()
{
const int nw = (int)ChartGetInteger(0, CHART_WINDOWS_TOTAL);
for(int w = nw - 1; w >= 0; w--)
{
const int nc = ChartIndicatorsTotal(0, w);
for(int k = nc - 1; k >= 0; k--)
{
const string nm = ChartIndicatorName(0, w, k);
if(StringFind(nm, "DERIV_") >= 0 ||
StringFind(nm, "DERIV_PLOTS") >= 0 ||
StringFind(nm, InpPlotsIndicatorPath) >= 0 ||
StringFind(nm, "DerivativePlots") >= 0)
ChartIndicatorDelete(0, w, nm);
}
}
}
bool DerivativePlotsAlreadyOnChart()
{
const int nw = (int)ChartGetInteger(0, CHART_WINDOWS_TOTAL);
for(int w = 0; w < nw; w++)
{
const int nc = ChartIndicatorsTotal(0, w);
for(int k = 0; k < nc; k++)
{
const string nm = ChartIndicatorName(0, w, k);
if(StringFind(nm, "DERIV_") >= 0 || StringFind(nm, "DERIV_PLOTS") >= 0 ||
StringFind(nm, InpPlotsIndicatorPath) >= 0)
return true;
}
}
return false;
}
// Pass every DerivativePlots input (same order as .mq5) so each WhichDerivative gets its own handle.
int MakeDerivativePlotsHandle(const string sym, const ENUM_TIMEFRAMES tf,
const ENUM_DERIVATIVE_VIEW which, const bool unifyY)
{
return iCustom(sym, tf, InpPlotsIndicatorPath,
InpAppliedPrice,
which,
unifyY,
InpDiffStep,
InpNormalizePoints,
InpSmoothPeriod,
true,
InpDebugTrace,
false);
}
bool AttachDerivativePlotsIndicator(const string sym, const ENUM_TIMEFRAMES tf)
{
if(!InpAutoAttachDerivativePlots)
return false;
if(g_derivPlotsAttachDone)
return true;
const string wantPath = TerminalInfoString(TERMINAL_DATA_PATH) + "\\MQL5\\Indicators\\" + InpPlotsIndicatorPath + ".ex5";
// ChartIndicatorAdd(chart, subwindow, handle). Subwindow index: use ChartWindowsTotal() before each add
// so new panes are appended below existing windows (ATR etc.). Fixed 1,2,3 collides with other indicators.
const bool unifyPass = InpPlotsSeparateWindows ? false : InpPlotsUnifyYScale;
if(InpPlotsSeparateWindows)
{
ResetLastError();
const int ind1 = MakeDerivativePlotsHandle(sym, tf, DERIVATIVE_LEVEL_1, unifyPass);
if(ind1 == INVALID_HANDLE)
{
g_derivativePlotsFailedToLoad = true;
Print("DERIVATIVE_CALC: iCustom(", InpPlotsIndicatorPath, ", d1) failed err=", GetLastError(),
". Required:\n ", wantPath);
if(InpDebugTrace)
PrintFormat("DERIVATIVE_CALC dbg iCustom d1 sym=%s tf=%s", sym, EnumToString(tf));
return false;
}
ResetLastError();
const int ind2 = MakeDerivativePlotsHandle(sym, tf, DERIVATIVE_LEVEL_2, unifyPass);
if(ind2 == INVALID_HANDLE)
{
IndicatorRelease(ind1);
g_derivativePlotsFailedToLoad = true;
Print("DERIVATIVE_CALC: iCustom(", InpPlotsIndicatorPath, ", d2) failed err=", GetLastError(),
". Required:\n ", wantPath);
return false;
}
ResetLastError();
const int ind3 = MakeDerivativePlotsHandle(sym, tf, DERIVATIVE_LEVEL_3, unifyPass);
if(ind3 == INVALID_HANDLE)
{
IndicatorRelease(ind1);
IndicatorRelease(ind2);
g_derivativePlotsFailedToLoad = true;
Print("DERIVATIVE_CALC: iCustom(", InpPlotsIndicatorPath, ", d3) failed err=", GetLastError(),
". Required:\n ", wantPath);
return false;
}
if(InpDebugTrace)
PrintFormat("DERIVATIVE_CALC dbg triple iCustom handles ind1=%d ind2=%d ind3=%d (should differ)",
ind1, ind2, ind3);
RemoveDerivativePlotsIndicatorsFromChart();
int sw = (int)ChartGetInteger(0, CHART_WINDOWS_TOTAL);
ResetLastError();
const bool ok1 = ChartIndicatorAdd(0, sw, ind1);
const int err1 = GetLastError();
sw = (int)ChartGetInteger(0, CHART_WINDOWS_TOTAL);
ResetLastError();
const bool ok2 = ChartIndicatorAdd(0, sw, ind2);
const int err2 = GetLastError();
sw = (int)ChartGetInteger(0, CHART_WINDOWS_TOTAL);
ResetLastError();
const bool ok3 = ChartIndicatorAdd(0, sw, ind3);
const int err3 = GetLastError();
IndicatorRelease(ind1);
IndicatorRelease(ind2);
IndicatorRelease(ind3);
if(!ok1 || !ok2 || !ok3)
{
g_derivativePlotsFailedToLoad = true;
Print("DERIVATIVE_CALC: ChartIndicatorAdd (3 panes) failed ok=", ok1, ",", ok2, ",", ok3,
" err=", err1, ",", err2, ",", err3, ". File: ", wantPath);
return false;
}
g_derivativePlotsFailedToLoad = false;
g_derivPlotsAttachDone = true;
ChartRedraw(0);
Print("DERIVATIVE_CALC: DerivativePlots attached as three stacked subwindows (indices chosen from CHART_WINDOWS_TOTAL).");
if(InpDebugTrace)
PrintFormat("DERIVATIVE_CALC dbg triple attach OK sym=%s tf=%s", sym, EnumToString(tf));
return true;
}
ResetLastError();
const int ind = MakeDerivativePlotsHandle(sym, tf, InpWhichDerivative, unifyPass);
if(ind == INVALID_HANDLE)
{
g_derivativePlotsFailedToLoad = true;
Print("DERIVATIVE_CALC: iCustom(\"", InpPlotsIndicatorPath, "\") failed err=", GetLastError(),
". MT5 could not read the compiled indicator. Required file:\n ", wantPath,
"\nCopy lab\\\\EAs\\\\DerivativePlots.mq5 into that Indicators folder, open in MetaEditor, press Compile (F7).");
if(InpDebugTrace)
PrintFormat("DERIVATIVE_CALC dbg iCustom sym=%s tf=%s applied=%d which=%d unify=%s h=%d norm=%s sm=%d",
sym, EnumToString(tf), (int)InpAppliedPrice, (int)InpWhichDerivative,
InpPlotsUnifyYScale ? "on" : "off",
InpDiffStep, InpNormalizePoints ? "on" : "off", InpSmoothPeriod);
return false;
}
RemoveDerivativePlotsIndicatorsFromChart();
int sw = (int)ChartGetInteger(0, CHART_WINDOWS_TOTAL);
ResetLastError();
const bool ok = ChartIndicatorAdd(0, sw, ind);
const int errAfterAdd = GetLastError();
IndicatorRelease(ind);
if(!ok)
{
g_derivativePlotsFailedToLoad = true;
Print("DERIVATIVE_CALC: ChartIndicatorAdd failed err=", errAfterAdd,
". Expected file present: ", wantPath);
return false;
}
g_derivativePlotsFailedToLoad = false;
g_derivPlotsAttachDone = true;
ChartRedraw(0);
Print("DERIVATIVE_CALC: subwindow indicator attached (inputs synced from EA).");
if(InpDebugTrace)
PrintFormat("DERIVATIVE_CALC dbg attach OK handle_was_valid ChartIndicatorAdd err=%d sym=%s tf=%s sw=%d",
errAfterAdd, sym, EnumToString(tf), sw);
return true;
}
void TryDemoTrade(const string sym, const ENUM_TIMEFRAMES tf,
const double d1, const double d2, const double d3)
{
if(!InpTradeEnabled || HasOurPosition(sym))
return;
bool wantBuy = false;
bool wantSell = false;
switch(InpWhichDerivative)
{
case DERIVATIVE_ALL:
case DERIVATIVE_LEVEL_1:
wantBuy = (d1 > 0.0 && d2 > 0.0);
wantSell = (d1 < 0.0 && d2 < 0.0);
break;
case DERIVATIVE_LEVEL_2:
wantBuy = (d2 > 0.0);
wantSell = (d2 < 0.0);
break;
default:
wantBuy = (d3 > 0.0);
wantSell = (d3 < 0.0);
break;
}
if(!wantBuy && !wantSell)
return;
MqlTick tick;
if(!SymbolInfoTick(sym, tick))
return;
const double atrPts = AtrPoints(sym, tf);
const double pt = SymbolInfoDouble(sym, SYMBOL_POINT);
const double slPts = MathMax(atrPts * InpSlAtrMult, 10.0);
const double tpPts = MathMax(atrPts * InpTpAtrMult, 10.0);
double sl = 0.0, tp = 0.0;
if(wantBuy)
{
sl = tick.ask - slPts * pt;
tp = tick.ask + tpPts * pt;
g_trade.Buy(InpLots, sym, tick.ask, sl, tp, "DERIVATIVE_CALC demo");
}
else if(wantSell)
{
sl = tick.bid + slPts * pt;
tp = tick.bid - tpPts * pt;
g_trade.Sell(InpLots, sym, tick.bid, sl, tp, "DERIVATIVE_CALC demo");
}
}
int OnInit()
{
g_derivPlotsAttachDone = false;
g_lastCommentWallMs = 0;
g_chartSymbol = InpSymbol;
StringTrimLeft(g_chartSymbol);
StringTrimRight(g_chartSymbol);
if(StringLen(g_chartSymbol) == 0)
g_chartSymbol = _Symbol;
if(!SymbolSelect(g_chartSymbol, true))
{
Print("DERIVATIVE_CALC EA: cannot select symbol ", g_chartSymbol);
return INIT_FAILED;
}
g_trade.SetExpertMagicNumber((long)InpMagic);
g_trade.SetDeviationInPoints(InpSlippagePoints);
g_trade.SetTypeFillingBySymbol(g_chartSymbol);
ENUM_TIMEFRAMES tf = InpSignalTF;
if(tf == PERIOD_CURRENT)
tf = (ENUM_TIMEFRAMES)Period();
Print("DERIVATIVE_CALC EA started on ", g_chartSymbol, " ", EnumToString(tf),
". This is an Expert Advisor — not the Accelerator indicator.");
if(InpDebugTrace)
PrintFormat("DERIVATIVE_CALC dbg chart_TF=%s signal_TF=%s normalize=%s h=%d sm=%d tester=%s visual=%s",
EnumToString((ENUM_TIMEFRAMES)Period()), EnumToString(tf),
InpNormalizePoints ? "on" : "off", InpDiffStep, InpSmoothPeriod,
MQLInfoInteger(MQL_TESTER) ? "yes" : "no",
MQLInfoInteger(MQL_VISUAL_MODE) ? "yes" : "no");
DeleteOurHelpObjects();
TryBuildHelpPanel();
// Do not call iCustom / ChartIndicatorAdd here — Strategy Tester treats failed indicator load in OnInit as a critical error.
// Attachment runs on first OnTick instead (see g_pendingDerivativePlotsAttach).
if(InpUseCanvasPlots)
{
RemoveDerivativePlotsIndicatorsFromChart();
EventSetMillisecondTimer(120);
}
else
EventKillTimer();
if(InpAutoAttachDerivativePlots && !InpUseCanvasPlots)
{
RemoveDerivativePlotsIndicatorsFromChart();
const bool in_tester = (MQLInfoInteger(MQL_TESTER) != 0);
const bool visual = (MQLInfoInteger(MQL_VISUAL_MODE) != 0);
const bool skip_tester_attach = (in_tester && !visual && !InpAttachPlotsInTester);
if(skip_tester_attach)
{
Print("DERIVATIVE_CALC: non-visual Strategy Tester - skipping DerivativePlots attach. ",
"Use visual mode for subwindow plots, or set InpAttachPlotsInTester=true if DerivativePlots.ex5 is in MQL5\\Indicators\\.");
}
else
{
g_pendingDerivativePlotsAttach = true;
Print("DERIVATIVE_CALC: DerivativePlots attach scheduled on first tick (OnInit cannot safely load custom indicators in tester).");
}
}
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason)
{
EventKillTimer();
if(g_deriv_canvas_created)
{
g_deriv_canvas.Destroy();
g_deriv_canvas_created = false;
}
ObjectDelete(0, DERIV_CANVAS_OBJ);
g_derivPlotsAttachDone = false;
DeleteOurHelpObjects();
Comment("");
}
void OnTimer()
{
if(!InpUseCanvasPlots)
return;
const uint now = GetTickCount();
if(InpCanvasRedrawMs > 0 && g_lastCanvasRedrawMs != 0 &&
(now - g_lastCanvasRedrawMs) < (uint)InpCanvasRedrawMs)
return;
g_lastCanvasRedrawMs = now;
UpdateDerivativeCanvasStrip();
}
void OnTick()
{
ENUM_TIMEFRAMES tf = InpSignalTF;
if(tf == PERIOD_CURRENT)
tf = (ENUM_TIMEFRAMES)Period();
if(g_pendingDerivativePlotsAttach && !InpUseCanvasPlots)
{
g_pendingDerivativePlotsAttach = false;
AttachDerivativePlotsIndicator(g_chartSymbol, tf);
}
const datetime barOpen = iTime(g_chartSymbol, tf, 0);
if(barOpen == 0)
return;
if(barOpen == g_lastBarTime)
return;
g_lastBarTime = barOpen;
double d1 = 0.0, d2 = 0.0, d3 = 0.0;
if(!ComputeDerivatives(g_chartSymbol, tf, d1, d2, d3))
{
if(InpDebugTrace)
PrintFormat("DERIVATIVE_CALC dbg ComputeDerivatives FAILED sym=%s tf=%s bar=%s pt=%.12g",
g_chartSymbol, EnumToString(tf), TimeToString(barOpen, TIME_DATE | TIME_MINUTES),
SymbolInfoDouble(g_chartSymbol, SYMBOL_POINT));
if(InpShowComment)
CommentThrottled("DERIVATIVE_CALC: not enough bars yet on " + g_chartSymbol + " " + EnumToString(tf) +
DerivativePlotsMissingHint());
return;
}
if(InpDebugTrace)
PrintFormat("DERIVATIVE_CALC dbg bar=%s sym=%s chart_TF=%s signal_TF=%s | d1=%.8g d2=%.8g d3=%.8g | pt=%.12g norm=%s h=%d",
TimeToString(barOpen, TIME_DATE | TIME_MINUTES), g_chartSymbol,
EnumToString((ENUM_TIMEFRAMES)Period()), EnumToString(tf),
d1, d2, d3, SymbolInfoDouble(g_chartSymbol, SYMBOL_POINT),
InpNormalizePoints ? "on" : "off", InpDiffStep);
if(InpShowComment)
{
string c = "DERIVATIVE_CALC EA | " + g_chartSymbol +
"\nChart TF: " + EnumToString((ENUM_TIMEFRAMES)Period()) +
" Signal TF (inputs): " + EnumToString(tf) +
"\nd1=" + DoubleToString(d1, 4) + " d2=" + DoubleToString(d2, 4) + " d3=" + DoubleToString(d3, 4) +
"\n(InpWhichDerivative=" + IntegerToString((int)InpWhichDerivative) +
" h=" + IntegerToString(InpDiffStep) + " sm=" + IntegerToString(InpSmoothPeriod) + ")" +
(InpUseCanvasPlots
? "\nPlots: EA canvas strip (bottom of chart)."
: ("\nPlots below: DerivativePlots indicator." + DerivativePlotsMissingHint()));
CommentThrottled(c);
}
TryDemoTrade(g_chartSymbol, tf, d1, d2, d3);
}
//+------------------------------------------------------------------+