From e1a2801acda9539877784614301fa2c0ccb50c65 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Fri, 13 Mar 2026 19:47:06 +0200 Subject: [PATCH 01/40] wip: implement prototype --- mql40/indicators/Signal Performance.mq4 | 107 ++++++++++++++++++++++++ templates4/34 Signal Performance.tpl | 66 +++++++++++++++ 2 files changed, 173 insertions(+) create mode 100644 mql40/indicators/Signal Performance.mq4 create mode 100644 templates4/34 Signal Performance.tpl diff --git a/mql40/indicators/Signal Performance.mq4 b/mql40/indicators/Signal Performance.mq4 new file mode 100644 index 000000000..3c9c59bb8 --- /dev/null +++ b/mql40/indicators/Signal Performance.mq4 @@ -0,0 +1,107 @@ +/** + * Signal Performance + */ +#include +int __InitFlags[]; +int __DeinitFlags[]; + +////////////////////////////////////////////////////// Configuration //////////////////////////////////////////////////////// + +extern int MaxBarsBack = 10000; + +///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// + +#include +#include +#include + +#property indicator_separate_window +#property indicator_buffers 1 +#property indicator_color1 Blue + +// indicator buffer ids +#define MODE_MAIN 0 + +double main[]; + + +/** + * Initialization + * + * @return int - error status + */ +int onInit() { + string indicator = WindowExpertName(); + + // validate inputs + // MaxBarsBack + if (AutoConfiguration) MaxBarsBack = GetConfigInt(indicator, "MaxBarsBack", MaxBarsBack); + if (MaxBarsBack < -1) return(catch("onInit(1) invalid input parameter MaxBarsBack: "+ MaxBarsBack, ERR_INVALID_INPUT_PARAMETER)); + if (MaxBarsBack == -1) MaxBarsBack = INT_MAX; + + SetIndicatorOptions(); + return(catch("onInit(2)")); +} + + +/** + * Main function + * + * @return int - error status + */ +int onTick() { + // reset buffers before performing a full recalculation + if (!ValidBars) { + ArrayInitialize(main, 0); + SetIndicatorOptions(); + } + + // synchronize buffers with a shifted offline chart + if (ShiftedBars > 0) { + ShiftDoubleIndicatorBuffer(main, Bars, ShiftedBars, 0); + } + + // calculate start bar + int startbar = Min(MaxBarsBack-1, ChangedBars-1); + + // recalculate changed bars + for (int bar=startbar; bar >= 0; bar--) { + main[bar] = Close[bar]/pUnit; + } + return(last_error); +} + + +/** + * Set indicator options. After recompilation the function must be called from start() for options not to be ignored. + * + * @param bool redraw [optional] - whether to redraw the chart (default: no) + * + * @return bool - success status + */ +bool SetIndicatorOptions(bool redraw = false) { + redraw = redraw!=0; + + string indicatorName = WindowExpertName(); + IndicatorShortName(indicatorName); + + IndicatorBuffers(indicator_buffers); + SetIndexBuffer(MODE_MAIN, main); SetIndexEmptyValue(MODE_MAIN, 0); + IndicatorDigits(pDigits); + + SetIndexStyle(MODE_MAIN, DRAW_LINE, EMPTY, EMPTY, indicator_color1); + SetIndexLabel(MODE_MAIN, indicatorName); + + if (redraw) WindowRedraw(); + return(!catch("SetIndicatorOptions(1)")); +} + + +/** + * Return a string representation of all input parameters (for logging purposes). + * + * @return string + */ +string InputsToStr() { + return(StringConcatenate("MaxBarsBack=", MaxBarsBack, ";")); +} diff --git a/templates4/34 Signal Performance.tpl b/templates4/34 Signal Performance.tpl new file mode 100644 index 000000000..8238620b6 --- /dev/null +++ b/templates4/34 Signal Performance.tpl @@ -0,0 +1,66 @@ + + + +symbol=GBPUSD +period=60 +digits=5 + +leftpos=9229 +scale=1 +graph=1 +fore=0 +grid=0 +volume=0 +ohlc=0 +askline=0 +days=0 +descriptions=1 +scroll=1 +shift=1 +shift_size=10 + +fixed_pos=620 +window_left=0 +window_top=0 +window_right=1292 +window_bottom=812 +window_type=3 + +background_color=16316664 +foreground_color=0 +barup_color=30720 +bardown_color=210 +bullcandle_color=30720 +bearcandle_color=210 +chartline_color=11119017 +volumes_color=30720 +grid_color=14474460 +askline_color=11823615 +stops_color=17919 + + +height=1 +fixed_height=0 + +name=main + + + + +height=5000 +fixed_height=0 + +name=Custom Indicator + +name=Signal Performance +flags=339 +window_num=1 + + + +show_data=1 + + + From e3086594a733a4e9d980bed93e0e96ef23f8f380 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Sat, 14 Mar 2026 21:51:30 +0200 Subject: [PATCH 02/40] update Equity Recorder --- mql40/indicators/Equity Recorder.mq4 | 97 ++++++++++++++++------------ 1 file changed, 56 insertions(+), 41 deletions(-) diff --git a/mql40/indicators/Equity Recorder.mq4 b/mql40/indicators/Equity Recorder.mq4 index 7653f24b9..d2f8e2848 100644 --- a/mql40/indicators/Equity Recorder.mq4 +++ b/mql40/indicators/Equity Recorder.mq4 @@ -1,11 +1,7 @@ /** - * EquityRecorder + * Equity Recorder * - * Records the current trade account's equity curves. One original curve and another one with external assets. - * - * - * TODO: - * - document both equity curves + * Records two equity curves for the current trade account. One with actual equity and another one with added external assets. */ #include int __InitFlags[] = {INIT_TIMEZONE}; @@ -13,8 +9,8 @@ int __DeinitFlags[]; ////////////////////////////////////////////////////// Configuration //////////////////////////////////////////////////////// -extern string HistoryDirectory = "Synthetic-History"; // name of the directory to store recorded data -extern int HistoryFormat = 401; // written history format: 400 | 401 +extern string HistoryDirectory = "Synthetic-History"; // name of the directory to store history data +extern int HistoryFormat = 401; // format of written history files: 400 | 401 ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -42,9 +38,6 @@ int hSet [2]; // HistorySet handles string symbolSuffixes [] = {".EA" , ".EX" }; string symbolDescriptions[] = {"Equity of account {account-number}", "Equity of account {account-number} plus external assets"}; -string historyDirectory = ""; // directory to store history data -int historyFormat; // format of new history files: 400 | 401 - string indicatorName = ""; string legendLabel = ""; @@ -55,40 +48,35 @@ string legendLabel = ""; * @return int - error status */ int onInit() { - // read auto-configuration - string indicator = ProgramName(); - if (AutoConfiguration) { - HistoryDirectory = GetConfigString(indicator, "HistoryDirectory", HistoryDirectory); - HistoryFormat = GetConfigInt (indicator, "HistoryFormat", HistoryFormat); - } + indicatorName = ProgramName(); // validate inputs // HistoryDirectory - historyDirectory = StrTrim(HistoryDirectory); - if (IsAbsolutePath(historyDirectory)) return(catch("onInit(1) illegal input parameter HistoryDirectory: "+ DoubleQuoteStr(HistoryDirectory) +" (illegal directory name)", ERR_INVALID_INPUT_PARAMETER)); + string dir = HistoryDirectory; + if (AutoConfiguration) dir = GetConfigString(indicatorName, "HistoryDirectory", dir); + dir = StrTrim(dir); + if (IsAbsolutePath(dir)) return(catch("onInit(1) illegal input parameter HistoryDirectory: "+ DoubleQuoteStr(HistoryDirectory) +" (illegal directory name)", ERR_INVALID_INPUT_PARAMETER)); int illegalChars[] = {':', '*', '?', '"', '<', '>', '|'}; - if (StrContainsChars(historyDirectory, illegalChars)) return(catch("onInit(2) invalid input parameter HistoryDirectory: "+ DoubleQuoteStr(HistoryDirectory) +" (invalid directory name)", ERR_INVALID_INPUT_PARAMETER)); - historyDirectory = StrReplace(historyDirectory, "\\", "/"); - if (StrStartsWith(historyDirectory, "/")) return(catch("onInit(3) invalid input parameter HistoryDirectory: "+ DoubleQuoteStr(HistoryDirectory) +" (must not start with a slash)", ERR_INVALID_INPUT_PARAMETER)); - if (!InitTradeServerPath(historyDirectory)) return(last_error); - + if (StrContainsChars(dir, illegalChars)) return(catch("onInit(2) invalid input parameter HistoryDirectory: "+ DoubleQuoteStr(HistoryDirectory) +" (invalid directory name)", ERR_INVALID_INPUT_PARAMETER)); + dir = StrReplace(dir, "\\", "/"); + if (StrStartsWith(dir, "/")) return(catch("onInit(3) invalid input parameter HistoryDirectory: "+ DoubleQuoteStr(HistoryDirectory) +" (must not start with a slash)", ERR_INVALID_INPUT_PARAMETER)); + if (!InitTradeServerPath(dir)) return(last_error); + HistoryDirectory = dir; // HistoryFormat - if (HistoryFormat!=400 && HistoryFormat!=401) return(catch("onInit(4) invalid input parameter HistoryFormat: "+ HistoryFormat +" (must be 400 or 401)", ERR_INVALID_INPUT_PARAMETER)); - historyFormat = HistoryFormat; + if (AutoConfiguration) HistoryFormat = GetConfigInt(indicatorName, "HistoryFormat", HistoryFormat); + if (HistoryFormat!=400 && HistoryFormat!=401) return(catch("onInit(4) invalid input parameter HistoryFormat: "+ HistoryFormat +" (must be 400 or 401)", ERR_INVALID_INPUT_PARAMETER)); - // setup a chart ticker (online only) + // indicator labels and display options + SetIndicatorOptions(); + legendLabel = CreateChartLegend(); + + // setup a chart ticker if (!__tickTimerId && !__isTesting) { int hWnd = __ExecutionContext[EC.chart]; - int millis = 1000; // a virtual tick every second (1000 milliseconds) + int millis = 1000; // a virtual tick every second (1000 milliseconds) __tickTimerId = SetupTickTimer(hWnd, millis, NULL); if (!__tickTimerId) return(catch("onInit(5)->SetupTickTimer(hWnd="+ IntToHexStr(hWnd) +") failed", ERR_RUNTIME_ERROR)); } - - // indicator labels and display options - legendLabel = CreateChartLegend(); - indicatorName = ProgramName(); - SetIndexStyle(0, DRAW_NONE, EMPTY, EMPTY, CLR_NONE); - SetIndexLabel(0, NULL); return(catch("onInit(6)")); } @@ -148,17 +136,23 @@ int onDeinit() { * @return int - error status */ int onTick() { + if (!ValidBars) { + SetIndicatorOptions(); + } + if (!CalculateEquity()) return(last_error); if (!RecordEquity()) return(last_error); if (!__isSuperContext) { if (NE(currEquity[0], prevEquity[0], 2)) { - ObjectSetText(legendLabel, indicatorName +" "+ DoubleToStr(currEquity[0], 2), 9, "Arial Fett", Blue); + ObjectSetText(legendLabel, StringConcatenate(indicatorName, " ", DoubleToStr(currEquity[0], 2)), 9, "Arial Fett", Blue); int error = GetLastError(); - if (error && error!=ERR_OBJECT_DOES_NOT_EXIST) // on ObjectDrag or opened "Properties" dialog + if (error && error!=ERR_OBJECT_DOES_NOT_EXIST) { // on ObjectDrag or opened "Properties" dialog return(catch("onTick(1)", error)); + } } } + prevEquity[0] = currEquity[0]; prevEquity[1] = currEquity[1]; return(last_error); @@ -181,7 +175,8 @@ bool CalculateEquity() { double equity = AccountBalance(); for (int i=0; i < size; i++) { equity += profits[i]; - lastTickTime = Max(lastTickTime, MarketInfoEx(symbols[i], MODE_TIME, error, "CalculateEquity(1)")); if (error != NULL) return(false); + lastTickTime = Max(lastTickTime, MarketInfoEx(symbols[i], MODE_TIME, error, "CalculateEquity(1)")); + if (error != NULL) return(false); } // store resulting equity values @@ -214,11 +209,12 @@ bool RecordEquity() { for (int i=0; i < size; i++) { if (!hSet[i]) { string symbol = StrLeft(GetAccountNumber(), 8) + symbolSuffixes[i]; - string description = StrReplace(symbolDescriptions[i], "{account-number}", GetAccountNumber()); + string descr = StrReplace(symbolDescriptions[i], "{account-number}", GetAccountNumber()); - hSet[i] = HistorySet1.Get(symbol, historyDirectory); - if (hSet[i] == -1) - hSet[i] = HistorySet1.Create(symbol, description, 2, historyFormat, historyDirectory); + hSet[i] = HistorySet1.Get(symbol, HistoryDirectory); + if (hSet[i] == -1) { + hSet[i] = HistorySet1.Create(symbol, descr, 2, HistoryFormat, HistoryDirectory); + } if (!hSet[i]) return(false); } if (!HistorySet1.AddTick(hSet[i], now, currEquity[i], NULL)) return(false); @@ -227,6 +223,25 @@ bool RecordEquity() { } +/** + * Set indicator options. After recompilation the function must be called from start() for options not to be ignored. + * + * @param bool redraw [optional] - whether to redraw the chart (default: no) + * + * @return bool - success status + */ +bool SetIndicatorOptions(bool redraw = false) { + redraw = redraw!=0; + + IndicatorBuffers(indicator_buffers); + SetIndexStyle(0, DRAW_NONE, EMPTY, EMPTY, CLR_NONE); + SetIndexLabel(0, NULL); + + if (redraw) WindowRedraw(); + return(!catch("SetIndicatorOptions(1)")); +} + + /** * Return a string representation of all input parameters (for logging purposes). * From ebc8c5a22676af5704ea4b7f4ce4ec4d04749472 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Sun, 15 Mar 2026 21:53:39 +0200 Subject: [PATCH 03/40] documentation --- mql40/include/rsf/functions/ManageDoubleIndicatorBuffer.mqh | 2 +- mql40/include/rsf/functions/iBarShiftNext.mqh | 6 ++++-- mql40/indicators/ALMA.mq4 | 2 +- mql40/indicators/BFX Volume.mq4 | 2 +- mql40/indicators/CCI.mq4 | 2 +- mql40/indicators/ChartInfos.mq4 | 4 ++-- mql40/indicators/MA Channel Band.mq4 | 2 +- mql40/indicators/MA Channel.mq4 | 2 +- mql40/indicators/MACD.mq4 | 2 +- mql40/indicators/Moving Average.mq4 | 2 +- mql40/indicators/NonLagMA.mq4 | 2 +- 11 files changed, 15 insertions(+), 13 deletions(-) diff --git a/mql40/include/rsf/functions/ManageDoubleIndicatorBuffer.mqh b/mql40/include/rsf/functions/ManageDoubleIndicatorBuffer.mqh index c60f12346..eac91b8ef 100644 --- a/mql40/include/rsf/functions/ManageDoubleIndicatorBuffer.mqh +++ b/mql40/include/rsf/functions/ManageDoubleIndicatorBuffer.mqh @@ -2,7 +2,7 @@ * Manage a double array as additional indicator buffer, i.e. automatically resize it and shift the content according to * incoming new bar data. * - * In MQL4 the terminal manages a maximum of 8 indicator buffers. Additional buffers can be used but must be managed by the + * In MQL4.0 the terminal manages a maximum of 8 indicator buffers. Additional buffers can be used but must be managed by the * framework. Such buffers are for internal calculations only, they can't be drawn on the chart or accessed via iCustom(). * * @param int id - buffer index diff --git a/mql40/include/rsf/functions/iBarShiftNext.mqh b/mql40/include/rsf/functions/iBarShiftNext.mqh index bb1bc786b..e28141871 100644 --- a/mql40/include/rsf/functions/iBarShiftNext.mqh +++ b/mql40/include/rsf/functions/iBarShiftNext.mqh @@ -27,11 +27,13 @@ int iBarShiftNext(string symbol/*=NULL*/, int period/*=NULL*/, datetime time, in int bar = iBarShift(symbol, period, time, true); int error = GetLastError(); - if (error!=NO_ERROR) /*&&*/ if (error!=ERS_HISTORY_UPDATE) + if (error!=NO_ERROR) /*&&*/ if (error!=ERS_HISTORY_UPDATE) { return(_EMPTY_VALUE(catch("iBarShiftNext(2)->iBarShift("+ symbol +","+ PeriodDescription(period) +") => "+ bar, error))); + } - if (bar != -1) + if (bar != -1) { return(bar); + } // exact war TRUE und bar==-1: keine abdeckende Bar gefunden // Datenreihe holen diff --git a/mql40/indicators/ALMA.mq4 b/mql40/indicators/ALMA.mq4 index 1ad7484df..37bcf45a0 100644 --- a/mql40/indicators/ALMA.mq4 +++ b/mql40/indicators/ALMA.mq4 @@ -354,7 +354,7 @@ int onTick() { bool onTrendChange(int direction) { if (direction!=MODE_UPTREND && direction!=MODE_DOWNTREND) return(!catch("onTrendChange(1) invalid parameter direction: "+ direction, ERR_INVALID_PARAMETER)); - // skip the signal if it was already processed elsewhere + // skip the signal if it was already handled elsewhere string sPeriod = PeriodDescription(); string eventName = "rsf::"+ StdSymbol() +","+ sPeriod +".ALMA("+ MA.Periods +", "+ PriceTypeDescription(maAppliedPrice) +").onTrendChange("+ direction +")."+ TimeToStr(Time[0]), propertyName = ""; string message1 = shortName +" turned "+ ifString(direction==MODE_UPTREND, "up", "down") +" (bid: "+ NumberToStr(_Bid, PriceFormat) +")"; diff --git a/mql40/indicators/BFX Volume.mq4 b/mql40/indicators/BFX Volume.mq4 index 7b2e15d5f..e1eb0b3bc 100644 --- a/mql40/indicators/BFX Volume.mq4 +++ b/mql40/indicators/BFX Volume.mq4 @@ -227,7 +227,7 @@ bool onLevelCross(int direction) { if (direction!=MODE_LONG && direction!=MODE_SHORT) return(!catch("onLevelCross(1) invalid parameter direction: "+ direction, ERR_INVALID_PARAMETER)); string indicatorName = ProgramName(); - // skip the signal if it was already processed elsewhere + // skip the signal if it was already handled elsewhere string sPeriod = PeriodDescription(); string eventName = "rsf::"+ StdSymbol() +","+ sPeriod +"."+ indicatorName +".onCross("+ ifInt(direction==MODE_LONG, Signal.Level, -Signal.Level) +")."+ TimeToStr(Time[0]), propertyName = ""; string message1 = indicatorName +" crossed level "+ ifInt(direction==MODE_LONG, Signal.Level, -Signal.Level); diff --git a/mql40/indicators/CCI.mq4 b/mql40/indicators/CCI.mq4 index f0f27f1cf..7abfc11fc 100644 --- a/mql40/indicators/CCI.mq4 +++ b/mql40/indicators/CCI.mq4 @@ -273,7 +273,7 @@ bool onCommand(string cmd, string params, int keys) { bool onTrendChange(int direction) { if (direction!=MODE_LONG && direction!=MODE_SHORT) return(!catch("onTrendChange(1) invalid parameter direction: "+ direction, ERR_INVALID_PARAMETER)); - // skip the signal if it was already processed elsewhere + // skip the signal if it was already handled elsewhere string sPeriod = PeriodDescription(); string eventName = "rsf::"+ StdSymbol() +","+ sPeriod +"."+ indicatorName +".onTrendChange("+ direction +")."+ TimeToStr(Time[0]), propertyName = ""; string message1 = indicatorName +" signal "+ ifString(direction==MODE_LONG, "long", "short") +" (bid: "+ NumberToStr(_Bid, PriceFormat) +")"; diff --git a/mql40/indicators/ChartInfos.mq4 b/mql40/indicators/ChartInfos.mq4 index 1c759642b..3283533a0 100644 --- a/mql40/indicators/ChartInfos.mq4 +++ b/mql40/indicators/ChartInfos.mq4 @@ -5148,7 +5148,7 @@ bool onNewMFE(string configKey, double profit) { // convert profit value to cent units to simplify Get/SetProperty int iProfit = MathRound(profit * 100); - // skip the signal if it was already processed elsewhere + // skip the signal if it was already handled elsewhere // sound: once per system if (!__isTesting) { string property = GetMfaeSignalKey(configKey, I_PROFIT_MFE); @@ -5181,7 +5181,7 @@ bool onNewMAE(string configKey, double profit) { // convert profit value to cent units to simplify Get/SetProperty int iProfit = MathRound(profit * 100); - // skip the signal if it was already processed elsewhere + // skip the signal if it was already handled elsewhere // sound: once per system if (!__isTesting) { string property = GetMfaeSignalKey(configKey, I_PROFIT_MAE); diff --git a/mql40/indicators/MA Channel Band.mq4 b/mql40/indicators/MA Channel Band.mq4 index bc68d9e31..f97ad4ce3 100644 --- a/mql40/indicators/MA Channel Band.mq4 +++ b/mql40/indicators/MA Channel Band.mq4 @@ -253,7 +253,7 @@ int onTick() { bool onTrendChange(int direction) { if (direction!=MODE_UPTREND && direction!=MODE_DOWNTREND) return(!catch("onTrendChange(1) invalid parameter direction: "+ direction, ERR_INVALID_PARAMETER)); - // skip the signal if it was already processed elsewhere + // skip the signal if it was already handled elsewhere string sPeriod = PeriodDescription(); string eventName = "rsf::"+ StdSymbol() +","+ sPeriod +"."+ indicatorName +".onTrendChange("+ direction +")."+ TimeToStr(Time[0]), propertyName = ""; string message1 = "MA Channel cross "+ ifString(direction==MODE_UPTREND, "up", "down") +" (bid: "+ NumberToStr(_Bid, PriceFormat) +")"; diff --git a/mql40/indicators/MA Channel.mq4 b/mql40/indicators/MA Channel.mq4 index 00409d3db..92a794a0a 100644 --- a/mql40/indicators/MA Channel.mq4 +++ b/mql40/indicators/MA Channel.mq4 @@ -219,7 +219,7 @@ bool onCross(int direction) { if (!Signal.onBarCross) return(false); if (ChangedBars > 2) return(false); - // skip the signal if it was already processed elsewhere + // skip the signal if it was already handled elsewhere string sPeriod = PeriodDescription(); string eventName = "rsf::"+ StdSymbol() +","+ sPeriod +"."+ indicatorName +".onCross("+ direction +")."+ TimeToStr(Time[0]), propertyName = ""; string message1 = "bar close "+ ifString(direction==D_LONG, "above ", "below ") + indicatorName; diff --git a/mql40/indicators/MACD.mq4 b/mql40/indicators/MACD.mq4 index baef24711..947985029 100644 --- a/mql40/indicators/MACD.mq4 +++ b/mql40/indicators/MACD.mq4 @@ -348,7 +348,7 @@ int onTick() { bool onCross(int direction) { if (direction!=MODE_UPPER_SECTION && direction!=MODE_LOWER_SECTION) return(!catch("onCross(1) invalid parameter direction: "+ direction, ERR_INVALID_PARAMETER)); - // skip the signal if it was already processed elsewhere + // skip the signal if it was already handled elsewhere string sPeriod = PeriodDescription(); string eventName = "rsf::"+ StdSymbol() +","+ sPeriod +"."+ indicatorName +".onCross("+ direction +")."+ TimeToStr(Time[0]), propertyName = ""; string message1 = indicatorName +" crossed "+ ifString(direction==MODE_UPPER_SECTION, "up", "down") +" (bid: "+ NumberToStr(_Bid, PriceFormat) +")"; diff --git a/mql40/indicators/Moving Average.mq4 b/mql40/indicators/Moving Average.mq4 index 09991e0fe..27c8859e7 100644 --- a/mql40/indicators/Moving Average.mq4 +++ b/mql40/indicators/Moving Average.mq4 @@ -290,7 +290,7 @@ int onTick() { bool onTrendChange(int direction) { if (direction!=MODE_UPTREND && direction!=MODE_DOWNTREND) return(!catch("onTrendChange(1) invalid parameter direction: "+ direction, ERR_INVALID_PARAMETER)); - // skip the signal if it was already processed elsewhere + // skip the signal if it was already handled elsewhere string sPeriod = PeriodDescription(); string eventName = "rsf::"+ StdSymbol() +","+ sPeriod +"."+ MA.Method +"("+ MA.Periods +", "+ PriceTypeDescription(maAppliedPrice) +").onTrendChange("+ direction +")."+ TimeToStr(Time[0]), propertyName = ""; string message1 = indicatorName +" turned "+ ifString(direction==MODE_UPTREND, "up", "down") +" (bid: "+ NumberToStr(_Bid, PriceFormat) +")"; diff --git a/mql40/indicators/NonLagMA.mq4 b/mql40/indicators/NonLagMA.mq4 index f21d3117b..b4fd7295a 100644 --- a/mql40/indicators/NonLagMA.mq4 +++ b/mql40/indicators/NonLagMA.mq4 @@ -312,7 +312,7 @@ int onTick() { bool onTrendChange(int direction) { if (direction!=MODE_UPTREND && direction!=MODE_DOWNTREND) return(!catch("onTrendChange(1) invalid parameter direction: "+ direction, ERR_INVALID_PARAMETER)); - // skip the signal if it was already processed elsewhere + // skip the signal if it was already handled elsewhere string sPeriod = PeriodDescription(); string eventName = "rsf::"+ StdSymbol() +","+ sPeriod +".NLMA("+ WaveCycle.Periods +", "+ PriceTypeDescription(maAppliedPrice) +").onTrendChange("+ direction +")."+ TimeToStr(Time[0]), propertyName = ""; string message1 = shortName +" turned "+ ifString(direction==MODE_UPTREND, "up", "down") +" (bid: "+ NumberToStr(_Bid, PriceFormat) +")"; From 1fd0e880e65255afd227c5dd9226d2c5f0cea1c9 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Tue, 17 Mar 2026 15:51:42 +0200 Subject: [PATCH 04/40] re-implement offset buffer values --- mql40/experts/ZigZag EA.mq4 | 8 +- .../include/rsf/functions/iCustom/ZigZag.mqh | 18 +- mql40/include/rsf/functions/shared/Abs.mqh | 8 +- mql40/indicators/ZigZag.mq4 | 1065 ++++++++++------- 4 files changed, 670 insertions(+), 429 deletions(-) diff --git a/mql40/experts/ZigZag EA.mq4 b/mql40/experts/ZigZag EA.mq4 index e08c51600..140f6d604 100644 --- a/mql40/experts/ZigZag EA.mq4 +++ b/mql40/experts/ZigZag EA.mq4 @@ -461,8 +461,8 @@ bool IsZigZagSignal(double &signal[]) { * Get ZigZag buffer values at the specified bar offset. The returned values correspond to the documented indicator buffers. * * @param _In_ int bar - bar offset - * @param _Out_ int trend - MODE_TREND: combined buffers MODE_KNOWN_TREND + MODE_UNKNOWN_TREND - * @param _Out_ int reversalOffset - MODE_REVERSAL: bar offset of most recent ZigZag reversal to previous ZigZag semaphore + * @param _Out_ int trend - MODE_TREND: merged buffers MODE_KNOWN_TREND & MODE_UNKNOWN_TREND + * @param _Out_ int reversalOffset - MODE_REVERSAL_OFFSET: bar offset of most recent ZigZag reversal to the preceeding ZigZag semaphore * @param _Out_ double reversalPrice - MODE_UPPER_CROSS|MODE_LOWER_CROSS: reversal price if the bar denotes a ZigZag reversal; 0 otherwise * * @return bool - success status @@ -471,8 +471,8 @@ bool GetZigZagData(int bar, int &trend, int &reversalOffset, double &reversalPri // TODO: 56% of the EA's total runtime is spent in this function - trend = MathRound(icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_TREND, bar)); // 88% of the local time - reversalOffset = MathRound(icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_REVERSAL, bar)); // 6% of the local time + trend = MathRound(icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_TREND, bar)); // 88% of the local time + reversalOffset = MathRound(icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_REVERSAL_OFFSET, bar)); // 6% of the local time if (trend > 0) reversalPrice = icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_UPPER_CROSS, bar); // 6% of the local time else reversalPrice = icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_LOWER_CROSS, bar); diff --git a/mql40/include/rsf/functions/iCustom/ZigZag.mqh b/mql40/include/rsf/functions/iCustom/ZigZag.mqh index 46d30524d..d56624c4e 100644 --- a/mql40/include/rsf/functions/iCustom/ZigZag.mqh +++ b/mql40/include/rsf/functions/iCustom/ZigZag.mqh @@ -1,12 +1,14 @@ -#define ZigZag.MODE_SEMAPHORE_OPEN 0 // semaphore open prices -#define ZigZag.MODE_SEMAPHORE_CLOSE 1 // semaphore close prices -#define ZigZag.MODE_UPPER_BAND 2 // upper channel band -#define ZigZag.MODE_LOWER_BAND 3 // lower channel band -#define ZigZag.MODE_UPPER_CROSS 4 // upper channel band crossings -#define ZigZag.MODE_LOWER_CROSS 5 // lower channel band crossings -#define ZigZag.MODE_REVERSAL 6 // offset of the previous ZigZag reversal to the preceeding semaphore -#define ZigZag.MODE_TREND 7 // trend (combined buffers MODE_KNOWN_TREND and MODE_UNKNOWN_TREND) +#define ZigZag.MODE_SEMAPHORE_OPEN 0 // semaphore open prices: positive or 0 +#define ZigZag.MODE_SEMAPHORE_CLOSE 1 // semaphore close prices: positive or 0 (if open != close it forms a vertical line segment) +#define ZigZag.MODE_UPPER_BAND 2 // upper channel band: positive or 0 +#define ZigZag.MODE_LOWER_BAND 3 // lower channel band: positive or 0 +#define ZigZag.MODE_UPPER_CROSS 4 // upper channel band crossings: positive or 0 +#define ZigZag.MODE_LOWER_CROSS 5 // lower channel band crossings: positive or 0 +#define ZigZag.MODE_REVERSAL_OFFSET 6 // int: offset of the ZigZag reversal to the leg's start semaphore: non-negative or -1 +// intern MODE_SEMAPHORE_OFFSET 10 // int: offset of the current bar to the leg's end semaphore: non-negative or -1 +// intern MODE_TREND 11 // int: direction and length of the ZigZag leg: positive/negative or 0 +#define ZigZag.MODE_TREND 7 // int: merged buffers MODE_TREND & MODE_SEMAPHORE_OFFSET: positive/negative or 0 /** diff --git a/mql40/include/rsf/functions/shared/Abs.mqh b/mql40/include/rsf/functions/shared/Abs.mqh index 68ba2133b..b35024a3e 100644 --- a/mql40/include/rsf/functions/shared/Abs.mqh +++ b/mql40/include/rsf/functions/shared/Abs.mqh @@ -6,7 +6,11 @@ * @return int */ int Abs(int value) { - if (value == INT_MIN) return(INT_MAX); - if (value < 0) return(-value); + if (value == INT_MIN) { + return(INT_MAX); + } + if (value < 0) { + return(-value); + } return(value); } diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index c7c42a989..9d02a6bde 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -2,12 +2,13 @@ * A non-repainting ZigZag indicator suitable for automation * * - * MetaQuotes' ZigZag indicator is flawed and poorly implemented. It repaints drawn ZigZag extremes and cannot be used for - * automation. This indicator corrects those issues. Once the ZigZag direction has changed, the change is permanent. + * MetaQuotes' ZigZag indicator is flawed and poorly implemented. It repaints the calculated swing extremes, at times even + * two swings back. Also it cannot be used for automation. This indicator doesn't have such issues. Once the ZigZag direction + * has changed, the change is permanent. * - * Internally the indicator uses a Donchian channel for calculation. The indicator can draw vertical line segments if a long + * Internally the indicator uses a Donchian channel for calculation. The indicator draws vertical line segments if a single * price bar crosses both upper and lower Donchian channel. Additionally, it can display the trail of a ZigZag leg as it - * develops over time. The display can be switched from ZigZag lines to ZigZag markers (aka semaphores). + * develops over time. The display can be switched between full ZigZag lines or only swing extremes (aka ZigZag semaphores). * * The indicator allows manual stepping of the ZigZag period via hotkey and supports multiple signaling modes. * @@ -127,23 +128,24 @@ extern string Sound.onNewChannelLow = "Price Decline.wav"; #include // indicator buffer ids -#define MODE_SEMAPHORE_OPEN ZigZag.MODE_SEMAPHORE_OPEN // 0: semaphore open prices -#define MODE_SEMAPHORE_CLOSE ZigZag.MODE_SEMAPHORE_CLOSE // 1: semaphore close prices -#define MODE_UPPER_BAND ZigZag.MODE_UPPER_BAND // 2: upper channel band -#define MODE_LOWER_BAND ZigZag.MODE_LOWER_BAND // 3: lower channel band -#define MODE_UPPER_CROSS ZigZag.MODE_UPPER_CROSS // 4: upper channel band crossings -#define MODE_LOWER_CROSS ZigZag.MODE_LOWER_CROSS // 5: lower channel band crossings -#define MODE_REVERSAL ZigZag.MODE_REVERSAL // 6: offset of the previous reversal to the preceeding ZigZag semaphore -#define MODE_COMBINED_TREND ZigZag.MODE_TREND // 7: trend (combined buffers MODE_KNOWN_TREND and MODE_UNKNOWN_TREND) -#define MODE_HIGHER_HIGH 8 // 8: new High after an upper channel band crossing (potential new semaphore) -#define MODE_LOWER_LOW 9 // 9: new Low after a lower channel band crossing (potential new semaphore) -#define MODE_KNOWN_TREND 10 // 10: known direction and duration of a ZigZag leg -#define MODE_UNKNOWN_TREND 11 // 11: not yet known ZigZag direction and duration after the last High/Low +#define MODE_SEMAPHORE_OPEN ZigZag.MODE_SEMAPHORE_OPEN // 0: final semaphores, open price: positive or 0 +#define MODE_SEMAPHORE_CLOSE ZigZag.MODE_SEMAPHORE_CLOSE // 1: final semaphores, close price: positive or 0 (if open != close it forms a vertical line segment) +#define MODE_UPPER_BAND ZigZag.MODE_UPPER_BAND // 2: upper channel band: positive or 0 +#define MODE_LOWER_BAND ZigZag.MODE_LOWER_BAND // 3: lower channel band: positive or 0 +#define MODE_UPPER_CROSS ZigZag.MODE_UPPER_CROSS // 4: upper channel band crossings: positive or 0 +#define MODE_LOWER_CROSS ZigZag.MODE_LOWER_CROSS // 5: lower channel band crossings: positive or 0 +#define MODE_REVERSAL_OFFSET ZigZag.MODE_REVERSAL_OFFSET // 6: int: offset of the ZigZag reversal to the leg's start semaphore: non-negative or -1 +#define MODE_MERGED_TREND ZigZag.MODE_TREND // 7: int: merged buffers MODE_TREND & MODE_SEMAPHORE_OFFSET: positive/negative or 0 +#define MODE_UPPER_CROSS_HIGH 8 // 8: bar High of an upper channel band crossing: positive or 0 +#define MODE_LOWER_CROSS_LOW 9 // 9: bar Low of a lower channel band crossing: positive or 0 +#define MODE_SEMAPHORE_OFFSET 10 // 10: int: offset of the current bar to the leg's end semaphore: non-negative or -1 +#define MODE_TREND 11 // 11: int: direction and length of a ZigZag leg: positive/negative or 0 +#define MODE_SIGNAL_PERFORMANCE 12 // 12: accumulated signal performance in price units: positive/negative or EMPTY_VALUE #property indicator_chart_window #property indicator_buffers 8 // visible buffers int terminal_buffers = 8; // buffers managed by the terminal -int framework_buffers = 4; // buffers managed by the framework +int framework_buffers = 5; // buffers managed by the framework #property indicator_color1 DodgerBlue // the ZigZag line is built from two buffers using the color of the first buffer #property indicator_width1 1 // @@ -159,40 +161,36 @@ int framework_buffers = 4; // buffers manage #property indicator_color6 indicator_color4 // lower channel band crossings #property indicator_width6 0 // -#property indicator_color7 CLR_NONE // offset of last ZigZag reversal to previous ZigZag semaphore -#property indicator_color8 CLR_NONE // trend (combined buffers MODE_KNOWN_TREND and MODE_UNKNOWN_TREND) - -double semaphoreOpen []; // semaphore open prices -double semaphoreClose[]; // semaphore close prices (yields a vertical line segment if open != close) -double upperBand []; // upper channel band -double lowerBand []; // lower channel band -double upperCross []; // upper channel band crossings -double lowerCross []; // lower channel band crossings -double higherHigh []; // new High after an upper channel band crossing (potential new semaphore) -double lowerLow []; // new Low after a lower channel band crossing (potential new semaphore) -double reversal []; // offset of the previous reversal to the preceeding ZigZag semaphore -int knownTrend []; // known direction and duration of a ZigZag leg -int unknownTrend []; // not yet known ZigZag direction and duration after the last High/Low -double combinedTrend []; // resulting trend (combined buffers MODE_KNOWN_TREND and MODE_UNKNOWN_TREND) +#property indicator_color7 CLR_NONE // trend (merged buffers MODE_TREND & MODE_SEMAPHORE_OFFSET) +#property indicator_color8 CLR_NONE // offset of the previous ZigZag reversal to its preceeding semaphore + +double upperBand []; // upper channel band: positive or 0 +double lowerBand []; // lower channel band: positive or 0 +double upperCross []; // upper channel band crossings: positive or 0 +double upperCrossHigh []; // bar High of an upper channel band crossing (potential semaphore): positive or 0 +double lowerCross []; // lower channel band crossings: positive or 0 +double lowerCrossLow []; // bar Low of a lower channel band crossing (potential semaphore): positive or 0 +double semaphoreOpen []; // final semaphore, open price: positive or 0 +double semaphoreClose []; // final semaphore, close price: positive or 0 (if open != close it creates a vertical line segment) +double reversalOffset []; // int: offset of the ZigZag reversal to the leg's start semaphore (): non-negative or -1 +int semaphoreOffset []; // int: offset of the current to the leg's end semaphore: non-negative or -1 +int trend []; // int: direction and length of a ZigZag leg: positive/negative or 0 +double mergedTrend []; // int: merged buffers MODE_TREND & MODE_SEMAPHORE_OFFSET: positive/negative or 0 +double signalPerformance[]; // accumulated signal performance in price units: positive/negative or EMPTY_VALUE -#define MODE_FIRST_CROSSING 1 // crossing draw types -#define MODE_ALL_CROSSINGS 2 +string indicatorName = ""; +string shortName = ""; +string legendLabel = ""; +string legendInfo = ""; // additional chart legend info +string labels[]; // chart object labels int zigzagDrawType; int zigzagSymbol; int crossingDrawType; int crossingSymbol; -double sema1, sema2, sema3; // last 3 semaphores for breakout tracking -double lastLegHigh, lastLegLow; // leg high/low at the previous tick -double lastUpperBand; -double lastLowerBand; - -string indicatorName = ""; -string shortName = ""; -string legendLabel = ""; -string legendInfo = ""; // additional chart legend info -string labels[]; // chart object labels +#define MODE_FIRST_CROSSING 1 // crossing draw types +#define MODE_ALL_CROSSINGS 2 bool signal.onReversal.sound; bool signal.onReversal.alert; @@ -202,9 +200,15 @@ bool signal.onBreakout.sound; bool signal.onBreakout.alert; bool signal.onBreakout.mail; +double sema1, sema2, sema3; // last 3 semaphores for detection of ZigZag breakouts +double lastLegHigh, lastLegLow; // leg high/low at the previous tick + +double lastUpperBand; // detection of channel widenings +double lastLowerBand; // upper/lower band values at the previous tick + datetime skipSignals; // skip signals until the specified time to wait for possible data pumping datetime lastTick; -int lastSoundSignal; // GetTickCount() value of the last audible signal +int lastSoundSignal; // GetTickCount() value of the last audio signal // signal direction types #define D_LONG TRADE_DIRECTION_LONG // 1 @@ -221,12 +225,130 @@ int lastSoundSignal; // GetTickCount() #define EVENT_REVERSAL_DOWN 4 +// +// ZigZag leg up: formed by two channel crossings in order "low band, high band" which create the preceeding Low semaphore +// ZigZag leg down: formed by two channel crossings in order "high band, low band" which create the preceeding High semaphore +// +// It's not enough to track only the last channel crossing. For ZigZag, a new channel crossing does not necessarily mean a +// new ZigZag High/Low. The channel may narrow after a crossing and can be crossed again without creating a new High/Low. +// If only the last crossing is tracked, the position of the semaphore will be lost. Thus the semaphore position is tracked. +// +// +// Buffer contents of the possible bar types +// ----------------------------------------- +// • Bar before MaxBarsBack +// double upperBand : 0 (default) +// double lowerBand : 0 (default) +// double upperCross : 0 (default) +// double upperCrossHigh : 0 (default) +// double lowerCross : 0 (default) +// double lowerCrossLow : 0 (default) +// double semaphoreOpen : 0 (default) +// double semaphoreClose : 0 (default) +// int reversalOffset : -1 (default) +// int semaphoreOffset: -1 (default) +// int trend : 0 (default) +// +// • Bar between MaxBarsBack and first semaphore +// double upperBand : positive +// double lowerBand : positive +// double upperCross : 0 (default) or positive (only upper or lower values may be set, not both) +// double upperCrossHigh : 0 (default) or positive +// double lowerCross : 0 (default) or positive +// double lowerCrossLow : 0 (default) or positive +// double semaphoreOpen : 0 (default) +// double semaphoreClose : 0 (default) +// int reversalOffset : -1 (default) +// int semaphoreOffset: -1 (default) before any cross, otherwise non-negative +// int trend : 0 (default) +// +// • Bar of ZigZag leg up +// double upperBand : positive +// double lowerBand : positive +// double upperCross : 0 (default) or positive +// double upperCrossHigh : 0 (default) or positive +// double lowerCross : 0 (default) +// double lowerCrossLow : 0 (default) +// double semaphoreOpen : 0 (default) +// double semaphoreClose : 0 (default) +// int reversalOffset : -1 (default) before the reversal, otherwise positive +// int semaphoreOffset: -1 (default) before the reversal, otherwise non-negative +// int trend : positive +// +// • Bar of ZigZag leg down +// double upperBand : positive +// double lowerBand : positive +// double upperCross : 0 (default) +// double upperCrossHigh : 0 (default) +// double lowerCross : 0 (default) or positive +// double lowerCrossLow : 0 (default) or positive +// double semaphoreOpen : 0 (default) +// double semaphoreClose : 0 (default) +// int reversalOffset : -1 (default) before the reversal, otherwise positive +// int semaphoreOffset: -1 (default) before the reversal, otherwise non-negative +// int trend : negative +// +// • High semaphore bar +// double upperBand : positive +// double lowerBand : positive +// double upperCross : positive +// double upperCrossHigh : positive +// double lowerCross : 0 (default) +// double lowerCrossLow : 0 (default) +// double semaphoreOpen : positive +// double semaphoreClose : positive (open = close) +// int reversalOffset : positive (previous reversal offset) +// int semaphoreOffset: 0 +// int trend : positive (previous trend length) +// +// • Low semaphore bar +// double upperBand : positive +// double lowerBand : positive +// double upperCross : 0 (default) +// double upperCrossHigh : 0 (default) +// double lowerCross : positive +// double lowerCrossLow : positive +// double semaphoreOpen : positive +// double semaphoreClose : positive (open == close) +// int reversalOffset : positive (previous reversal offset) +// int semaphoreOffset: 0 +// int trend : negative (previous trend length) +// +// • Double crossing bar (high+low semaphore) after processing of the 2nd crossing +// double upperBand : positive +// double lowerBand : positive +// double upperCross : positive +// double upperCrossHigh : positive +// double lowerCross : positive +// double lowerCrossLow : positive +// double semaphoreOpen : positive +// double semaphoreClose : positive (open != close) +// int reversalOffset : 0 (the previous reversal occurred on the same bar) +// int semaphoreOffset: 0 (same as reversal offset) +// int trend : 0 (the whole last trend ocurred on the same bar) +// +// • Triple+ crossing bar (more than two semaphores) ??? +// +// • Bar 0 (zero), the current bar ??? +// + +bool debugging = false; +datetime devStartTime, devFirstCrossing, devFrom, devTo; +string semTypes[] = {"NULL", "LOW", "HIGH"}; + + /** * Initialization * * @return int - error status */ int onInit() { + devStartTime = D'2026.03.19 01:41'; // TODO: remove once finished + devFirstCrossing = D'2026.03.19 01:48'; // double crossings: P=8/2026.03.16 20:47 + + devFrom = devStartTime + 6 * Period() * MINUTES; + devTo = devFirstCrossing + 32 * Period() * MINUTES; + string indicator = WindowExpertName(); // validate inputs @@ -380,7 +502,7 @@ int onInit() { SetIndicatorOptions(); if (ShowChartLegend) legendLabel = CreateChartLegend(); - // Indicator events like reversals occur "on tick" and not on "bar open" or "bar close". + // Indicator events like reversals occur "on-tick" and not on "bar-open" or "bar-close". // We need a chart ticker to prevent invalid signals caused by ticks during data pumping. if (!__tickTimerId && !__isTesting) { int hWnd = __ExecutionContext[EC.chart]; @@ -420,26 +542,28 @@ int onTick() { if (!HandleCommands("ParameterStepper")) return(last_error); } - // framework: manage additional buffers - ManageDoubleIndicatorBuffer(MODE_HIGHER_HIGH, higherHigh ); - ManageDoubleIndicatorBuffer(MODE_LOWER_LOW, lowerLow ); - ManageIntIndicatorBuffer (MODE_KNOWN_TREND, knownTrend ); - ManageIntIndicatorBuffer (MODE_UNKNOWN_TREND, unknownTrend); + // manage additional fraemwork buffers + ManageDoubleIndicatorBuffer(MODE_UPPER_CROSS_HIGH, upperCrossHigh); + ManageDoubleIndicatorBuffer(MODE_LOWER_CROSS_LOW, lowerCrossLow); + ManageIntIndicatorBuffer (MODE_SEMAPHORE_OFFSET, semaphoreOffset, -1); + ManageIntIndicatorBuffer (MODE_TREND, trend); + ManageDoubleIndicatorBuffer(MODE_SIGNAL_PERFORMANCE, signalPerformance, EMPTY_VALUE); // reset buffers before performing a full recalculation if (!ValidBars) { - ArrayInitialize(semaphoreOpen, 0); - ArrayInitialize(semaphoreClose, 0); - ArrayInitialize(upperBand, 0); - ArrayInitialize(lowerBand, 0); - ArrayInitialize(upperCross, 0); - ArrayInitialize(lowerCross, 0); - ArrayInitialize(higherHigh, 0); - ArrayInitialize(lowerLow, 0); - ArrayInitialize(reversal, -1); - ArrayInitialize(knownTrend, 0); - ArrayInitialize(unknownTrend, 0); - ArrayInitialize(combinedTrend, 0); + ArrayInitialize(upperBand, 0); // double: positive or 0 + ArrayInitialize(lowerBand, 0); // double: positive or 0 + ArrayInitialize(upperCross, 0); // double: positive or 0 + ArrayInitialize(upperCrossHigh, 0); // double: positive or 0 + ArrayInitialize(lowerCross, 0); // double: positive or 0 + ArrayInitialize(lowerCrossLow, 0); // double: positive or 0 + ArrayInitialize(semaphoreOpen, 0); // double: positive or 0 + ArrayInitialize(semaphoreClose, 0); // double: positive or 0 + ArrayInitialize(reversalOffset, -1); // int: non-negative or -1 + ArrayInitialize(semaphoreOffset, -1); // int: non-negative or -1 + ArrayInitialize(trend, 0); // int: positive/negative or 0 + ArrayInitialize(mergedTrend, 0); // int: positive/negative or 0 + ArrayInitialize(signalPerformance, EMPTY_VALUE); // double: positive/negative or EMPTY_VALUE lastUpperBand = 0; lastLowerBand = 0; lastLegHigh = 0; @@ -452,44 +576,52 @@ int onTick() { // synchronize buffers with a shifted offline chart if (ShiftedBars > 0) { - ShiftDoubleIndicatorBuffer(semaphoreOpen, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(semaphoreClose, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(upperBand, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(lowerBand, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(upperCross, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(lowerCross, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(higherHigh, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(lowerLow, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(reversal, Bars, ShiftedBars, -1); - ShiftIntIndicatorBuffer (knownTrend, Bars, ShiftedBars, 0); - ShiftIntIndicatorBuffer (unknownTrend, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(combinedTrend, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(upperBand, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(lowerBand, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(upperCross, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(upperCrossHigh, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(lowerCross, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(lowerCrossLow, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(semaphoreOpen, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(semaphoreClose, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(reversalOffset, Bars, ShiftedBars, -1); + ShiftIntIndicatorBuffer (semaphoreOffset, Bars, ShiftedBars, -1); + ShiftIntIndicatorBuffer (trend, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(mergedTrend, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(signalPerformance, Bars, ShiftedBars, EMPTY_VALUE); } // check data pumping on every tick so the reversal handler can skip errornous signals if (!__isTesting) IsPossibleDataPumping(); + + if (debugging && Symbol()=="BTCUSD" && Period()==PERIOD_M1 && 5 <= ZigZag.Periods && ZigZag.Periods <= 15) { + MaxBarsBack = iBarShift(NULL, NULL, devStartTime); + } + + // calculate start bar - int startbar = Min(MaxBarsBack-1, ChangedBars-1, Bars-ZigZag.Periods); - if (startbar < 0 && MaxBarsBack) return(logInfo("onTick(1) Tick="+ Ticks +" Bars="+ Bars +" needed="+ ZigZag.Periods, ERR_HISTORY_INSUFFICIENT)); + int startBar = Min(MaxBarsBack-1, ChangedBars-1, Bars-ZigZag.Periods); + if (startBar < 0 && MaxBarsBack) return(logInfo("onTick(1) Tick="+ Ticks +" Bars="+ Bars +" needed="+ ZigZag.Periods, ERR_HISTORY_INSUFFICIENT)); // recalculate changed bars - if (startbar > 2) { - semaphoreOpen [startbar] = 0; - semaphoreClose[startbar] = 0; - upperBand [startbar] = 0; - lowerBand [startbar] = 0; - upperCross [startbar] = 0; - lowerCross [startbar] = 0; - higherHigh [startbar] = 0; - lowerLow [startbar] = 0; - reversal [startbar] = -1; - knownTrend [startbar] = 0; - unknownTrend [startbar] = 0; - combinedTrend [startbar] = 0; + if (startBar > 2) { // TODO: why 2 and not 1 + upperBand [startBar] = 0; + lowerBand [startBar] = 0; + upperCross [startBar] = 0; + upperCrossHigh [startBar] = 0; + lowerCross [startBar] = 0; + lowerCrossLow [startBar] = 0; + semaphoreOpen [startBar] = 0; + semaphoreClose [startBar] = 0; + reversalOffset [startBar] = -1; + semaphoreOffset [startBar] = -1; + trend [startBar] = 0; + mergedTrend [startBar] = 0; + signalPerformance[startBar] = EMPTY_VALUE; } - for (int bar=startbar; bar >= 0; bar--) { + for (int bar=startBar; bar >= 0; bar--) { // recalculate Donchian channel if (bar > 0) { upperBand[bar] = High[iHighest(NULL, NULL, MODE_HIGH, ZigZag.Periods, bar)]; @@ -501,60 +633,118 @@ int onTick() { } // recalculate channel crossings - if (upperBand[bar] > upperBand[bar+1]) { - upperCross[bar] = upperBand[bar+1]+Point; - higherHigh[bar] = upperBand[bar]; + if (upperBand[bar] > upperBand[bar+1] && upperBand[bar+1]) { + upperCross [bar] = upperBand[bar+1]+Point; + upperCrossHigh[bar] = upperBand[bar]; } if (lowerBand[bar] < lowerBand[bar+1]) { - lowerCross[bar] = lowerBand[bar+1]-Point; - lowerLow [bar] = lowerBand[bar]; + lowerCross [bar] = lowerBand[bar+1]-Point; + lowerCrossLow[bar] = lowerBand[bar]; } // recalculate ZigZag data - // if no channel crossing + // if no channel crossing (before or after the first semaphore) if (!upperCross[bar] && !lowerCross[bar]) { - reversal [bar] = reversal [bar+1]; // keep reversal offset (may be -1) - knownTrend [bar] = knownTrend [bar+1]; // keep known trend - unknownTrend[bar] = unknownTrend[bar+1] + 1; // increase unknown trend + trend [bar] = trend [bar+1]; // keep trend (may be 0) + reversalOffset [bar] = reversalOffset [bar+1]; // keep reversal offset (may be -1) + semaphoreOffset[bar] = semaphoreOffset[bar+1]; // get previous semaphore offset + if (semaphoreOffset[bar] > -1) { + semaphoreOffset[bar]++; // increase if it was set + } } // if two channel crossings (upper and lower band crossed by the same bar) else if (upperCross[bar] && lowerCross[bar]) { if (IsUpperCrossLast(bar)) { - if (!knownTrend[bar]) ProcessLowerCross(bar); // if bar not yet processed then process both crossings in order, - ProcessUpperCross(bar); // otherwise process only the last crossing + if (!trend[bar]) ProcessLowerCross(bar); // if bar not yet processed then process both crossings in order, + ProcessUpperCross(bar); // otherwise process only the last crossing } else { - if (!knownTrend[bar]) ProcessUpperCross(bar); // ... - ProcessLowerCross(bar); // ... + if (!trend[bar]) ProcessUpperCross(bar); // ... + ProcessLowerCross(bar); // ... } } - // if a single channel crossing + // if a single channel crossing (before or after the first semaphore) else if (upperCross[bar] != 0) ProcessUpperCross(bar); else ProcessLowerCross(bar); + // --- end: recalculate ZigZag data --- - // calculate combinedTrend[] - combinedTrend[bar] = Sign(knownTrend[bar]) * unknownTrend[bar] * 100000 + knownTrend[bar]; - // hide non-configured crossings - if (!crossingDrawType) { // hide all crossings - upperCross[bar] = 0; - lowerCross[bar] = 0; + // whether the processed bar is a reversal bar (not whether the current tick triggered a reversal) + bool isReversalBar = false; + if (!semaphoreOffset[bar]) { + isReversalBar = (Abs(trend[bar]) == reversalOffset[bar]); + } + + + + if (debugging && Ticks == 1) { + if (Time[bar] >= devFrom && Time[bar] <= devTo) { + debug("onTick(0.1) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" trend="+ trend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" semaphoreOffset="+ semaphoreOffset[bar]); + if (isReversalBar) { + debug("onTick(0.2) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" isReversalBar=1"); + } + if (semaphoreClose[bar] != NULL) { + debug("onTick(0.3) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" isSemaphoreBar=1"); + } + } } - else if (crossingDrawType == MODE_FIRST_CROSSING) { // hide all crossings except the 1st - bool isReversal = false; - if (!unknownTrend[bar]) { - int absTrend = MathAbs(knownTrend[bar]); - if (absTrend == reversal[bar]) isReversal = true; // reversal offset is 0 if the reversal occurred - else if (absTrend == 1 && !reversal[bar]) isReversal = true; // on the same bar as the preceeding semaphore + + + + // calculate signal performance (if enabled) + bool isPosition = (signalPerformance[bar+1] != EMPTY_VALUE); + double range; + + if (isReversalBar) { + if (isPosition) { + if (upperCross[bar] != 0) { + range = upperCross[bar] - Close[bar+1]; + signalPerformance[bar] = signalPerformance[bar+1] - range; // close existing position + signalPerformance[bar] += Close[bar] - upperCross[bar]; // open new position + } + else { + range = lowerCross[bar] - Close[bar+1]; + signalPerformance[bar] = signalPerformance[bar+1] + range; // close existing position + signalPerformance[bar] += lowerCross[bar] - Close[bar]; // open new position + } } + else { // open a new position + if (upperCross[bar] != 0) { + signalPerformance[bar] = Close[bar] - upperCross[bar]; // long + } + else { + signalPerformance[bar] = lowerCross[bar] - Close[bar]; // short + } + } + //debug("onTick(0.1) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" balance="+ DoubleToStr(signalPerformance[bar]/pUnit, pDigits)); + } + else if (isPosition) { // only update PnL + range = Close[bar] - Close[bar+1]; + if (trend[bar] > 0) { + signalPerformance[bar] = signalPerformance[bar+1] + range; + } + else { + signalPerformance[bar] = signalPerformance[bar+1] - range; + } + } + else { // keep previous PnL + signalPerformance[bar] = signalPerformance[bar+1]; + } - if (isReversal) { - if (reversal[bar+1] >= 0) { // keep preceeding reversals on the same bar - if (knownTrend[bar] > 0) lowerCross[bar] = 0; - else upperCross[bar] = 0; + + // hide non-configured crossings + if (!crossingDrawType) { // hide all crossings + upperCross[bar] = 0; + lowerCross[bar] = 0; + } + else if (crossingDrawType == MODE_FIRST_CROSSING) { // hide all crossings except the 1st + if (isReversalBar) { + if (reversalOffset[bar+1] >= 0) { // keep preceeding reversals on the same bar + if (trend[bar] > 0) lowerCross[bar] = 0; + else upperCross[bar] = 0; } } else { @@ -562,153 +752,159 @@ int onTick() { lowerCross[bar] = 0; } } + + // compose mergedTrend[] + mergedTrend[bar] = Sign(trend[bar]) * semaphoreOffset[bar] * 100000 + trend[bar]; } - if (__isChart && !__isSuperContext) { - if (ShowChartLegend) UpdateChartLegend(); - if (ChangedBars > 2) { - // resolve leg high/low - int type = NULL; - bar = FindPrevSemaphore(0, type); - if (type == MODE_HIGH) { - lastLegHigh = High[bar]; - lastLegLow = 0; - } - else { - lastLegHigh = 0; - lastLegLow = Low[bar]; - } + // TODO: + // - once finished: update logic in usage locations of icZigZag() - // resolve the last 3 semaphores - bar = FindPrevSemaphore(bar, type); - sema1 = ifDouble(type==MODE_HIGH, High[bar], Low[bar]); - bar = FindPrevSemaphore(bar, type); - sema2 = ifDouble(type==MODE_HIGH, High[bar], Low[bar]); - bar = FindPrevSemaphore(bar, type); - sema3 = ifDouble(type==MODE_HIGH, High[bar], Low[bar]); - if (Ticks==1 && TrackZigZagBalance && TrackZigZagBalance.Since) { - //debug("onTick(0.1) TrackZigZagBalance.Since="+ TimeToStr(TrackZigZagBalance.Since)); + if (__isChart && !__isSuperContext) { + if (ShowChartLegend) UpdateChartLegend(); - int currSem, prevBar, prevSem, size; - int currBar = FindPrevSemaphore(0, currSem); if (currBar == -1) return(last_error); + // track ZigZag balance (if enabled) + if (Ticks == 1) /*&&*/ if (TrackZigZagBalance && TrackZigZagBalance.Since) { + int currSem, prevBar, prevSem, size; + int currBar = FindSemaphore(0, currSem); if (currBar < 0) return(last_error); - double events[][3]; // [datetime, type, price] - ArraySetAsSeries(events, false); - ArrayResize(events, 0); + double events[][3]; // [datetime, type, price] + ArraySetAsSeries(events, false); + ArrayResize(events, 0); - while (true) { - prevSem = currSem; - prevBar = FindPrevSemaphore(currBar, prevSem); if (prevBar == -1) return(last_error); + while (true) { // TODO: these returns are wrong + prevBar = FindSemaphore(currBar, prevSem, currSem); if (prevBar < 0) return(last_error); - int reversalOffset = reversal[currBar]; - int reversalBar = prevBar - reversalOffset; // standard case + int reversalBar = prevBar - reversalOffset[currBar]; // standard case - if (!reversalOffset && reversal[currBar+1]==-1) { // reversal and next semaphore on the same bar - reversalBar = currBar; - } - if (Time[reversalBar] < TrackZigZagBalance.Since) break; - if (reversalBar > MaxBarsBack-ZigZag.Periods) break; - - size = ArrayRange(events, 0); - ArrayResize(events, size+2); - events[size][0] = Time[currBar]; - events[size][1] = ifInt(currSem==MODE_HIGH, EVENT_SEMAPHORE_HIGH, EVENT_SEMAPHORE_LOW); - events[size][2] = ifDouble(currSem==MODE_HIGH, High[currBar], Low[currBar]); - size++; - events[size][0] = Time[reversalBar]; // crosses may be invisible and buffers upper/lowerCross[] are empty - events[size][1] = ifInt(currSem==MODE_HIGH, EVENT_REVERSAL_UP, EVENT_REVERSAL_DOWN); - events[size][2] = ifDouble(currSem==MODE_HIGH, upperBand[reversalBar+1], lowerBand[reversalBar+1]); - - currBar = prevBar; - currSem = prevSem; + if (!reversalOffset[currBar] && reversalOffset[currBar+1]==-1) { // reversal and next semaphore on the same bar + reversalBar = currBar; } + if (Time[reversalBar] < TrackZigZagBalance.Since) break; + if (reversalBar > MaxBarsBack-ZigZag.Periods) break; - ArraySetAsSeries(events, true); size = ArrayRange(events, 0); + ArrayResize(events, size+2); + events[size][0] = Time[currBar]; + events[size][1] = ifInt(currSem==MODE_HIGH, EVENT_SEMAPHORE_HIGH, EVENT_SEMAPHORE_LOW); + events[size][2] = ifDouble(currSem==MODE_HIGH, High[currBar], Low[currBar]); + size++; + events[size][0] = Time[reversalBar]; // crosses may be invisible and buffers upper/lowerCross[] are empty + events[size][1] = ifInt(currSem==MODE_HIGH, EVENT_REVERSAL_UP, EVENT_REVERSAL_DOWN); + events[size][2] = ifDouble(currSem==MODE_HIGH, upperBand[reversalBar+1], lowerBand[reversalBar+1]); + + currBar = prevBar; + currSem = prevSem; + } - if (size > 0) { - double balance=0, prevSemaphore, prevReversal=events[0][2], markerOffset = CalculateMarkerOffset(); - bool prevBalanceReset = false; - - //debug("onTick(0.2) markerOffset="+ NumberToStr(markerOffset, ".1+")); - - string fontName = "Microsoft Tai Le Bold"; - int fontSize = 9; - color fontColor; - - for (int i=0; i < size; i++) { - datetime eventTime = events[i][0]; - int eventType = events[i][1]; - double eventPrice = events[i][2]; - - if (i % 2 == 0) { - // reversal: add up negative balances - if (eventType == EVENT_REVERSAL_UP) balance += (prevReversal-eventPrice); - else balance += (eventPrice-prevReversal); - - if (i > 0) { - if (balance > -HalfPoint) fontColor = C'45,181,45'; - else if (prevBalanceReset) fontColor = Blue; - else fontColor = Red; - string name = shortName + ifString(eventType==EVENT_REVERSAL_UP, ".up.", ".down.") + TimeToStr(eventTime); - ObjectCreateRegister(name, OBJ_TEXT, 0, eventTime, eventPrice-markerOffset); - ObjectSetText(name, NumberToStr(balance/pUnit, ",'R.0"), fontSize, fontName, fontColor); - - // reset positive balances - if (balance > -HalfPoint) balance = 0; - prevBalanceReset = false; - } - prevReversal = eventPrice; + ArraySetAsSeries(events, true); + size = ArrayRange(events, 0); + + if (size > 0) { + double balance=0, prevSemaphore, prevReversal=events[0][2], markerOffset = CalculateMarkerOffset(); + bool prevBalanceReset = false; + string fontName = "Microsoft Tai Le Bold"; + int fontSize = 9; + color fontColor; + + for (int i=0; i < size; i++) { + datetime eventTime = events[i][0]; + int eventType = events[i][1]; + double eventPrice = events[i][2]; + + if (i % 2 == 0) { + // reversal: add up negative balances + if (eventType == EVENT_REVERSAL_UP) balance += (prevReversal-eventPrice); + else balance += (eventPrice-prevReversal); + + if (i > 0) { + if (balance > -HalfPoint) fontColor = C'45,181,45'; + else if (prevBalanceReset) fontColor = Blue; + else fontColor = Red; + string name = shortName + ifString(eventType==EVENT_REVERSAL_UP, ".up.", ".down.") + TimeToStr(eventTime); + ObjectCreateRegister(name, OBJ_TEXT, 0, eventTime, eventPrice-markerOffset); + ObjectSetText(name, NumberToStr(balance/pUnit, ",'R.0"), fontSize, fontName, fontColor); + + // reset positive balances + if (balance > -HalfPoint) balance = 0; + prevBalanceReset = false; } - else { - // semaphore - if (eventType == EVENT_SEMAPHORE_HIGH) double gain = eventPrice - prevReversal; - else gain = prevReversal - eventPrice; - - // reset negative balances if recovered by the semaphore - if (balance < 0 && gain > -balance) { - balance = 0; - prevBalanceReset = true; - } + prevReversal = eventPrice; + } + else { + // semaphore + if (eventType == EVENT_SEMAPHORE_HIGH) double gain = eventPrice - prevReversal; + else gain = prevReversal - eventPrice; + + // reset negative balances if recovered by the semaphore + if (balance < 0 && gain > -balance) { + balance = 0; + prevBalanceReset = true; } } } } } - else { - // detect ZigZag breakouts (comparing legs against bands also detects breakouts on missed ticks) - if (Signal.onBreakout && sema3) { - if (knownTrend[0] > 0) { + + // detect ZigZag breakouts (comparing legs against bands also detects breakouts on missed ticks) + if (Signal.onBreakout) { + if (ChangedBars > 2) { + while (true) { + int resultType = 0; + bar = FindSemaphore(0, resultType); if (bar < 0) break; + + // resolve leg high/low + if (resultType == MODE_HIGH) { + lastLegHigh = High[bar]; + lastLegLow = 0; + } + else { + lastLegHigh = 0; + lastLegLow = Low[bar]; + } + + // resolve the last 3 semaphores + bar = FindSemaphore(bar, resultType, resultType); if (bar < 0) break; + sema1 = ifDouble(resultType==MODE_HIGH, High[bar], Low[bar]); + bar = FindSemaphore(bar, resultType, resultType); if (bar < 0) break; + sema2 = ifDouble(resultType==MODE_HIGH, High[bar], Low[bar]); + bar = FindSemaphore(bar, resultType, resultType); if (bar < 0) break; + sema3 = ifDouble(resultType==MODE_HIGH, High[bar], Low[bar]); + break; + } + } + else if (sema3 != 0) { + if (trend[0] > 0) { if (lastLegHigh < sema2+HalfPoint && upperBand[0] > sema2+HalfPoint) { onBreakout(D_LONG); } - lastLegHigh = High[unknownTrend[0]]; // leg high for comparison at the nex tick + lastLegHigh = High[semaphoreOffset[0]]; // leg high for comparison at the nex tick } - else if (knownTrend[0] < 0) { + else if (trend[0] < 0) { if ((!lastLegLow || lastLegLow > sema2-HalfPoint) && lowerBand[0] < sema2-HalfPoint) { onBreakout(D_SHORT); } - lastLegLow = Low[unknownTrend[0]]; // leg low for comparison at the nex tick + lastLegLow = Low[semaphoreOffset[0]]; // leg low for comparison at the nex tick } } + } - // detect Donchian channel widenings - if (Sound.onChannelWidening) { - if (ChangedBars == 2) { - lastUpperBand = upperBand[1]; - lastLowerBand = lowerBand[1]; - } - if (lastUpperBand && lastLowerBand) { - if (upperBand[0] > lastUpperBand+HalfPoint) onChannelWidening(D_LONG); - else if (lowerBand[0] < lastLowerBand-HalfPoint) onChannelWidening(D_SHORT); - } - lastUpperBand = upperBand[0]; - lastLowerBand = lowerBand[0]; + // detect Donchian channel widenings + if (Sound.onChannelWidening) /*&&*/ if (ChangedBars <= 2) { + if (ChangedBars == 2) { + lastUpperBand = upperBand[1]; + lastLowerBand = lowerBand[1]; } + if (lastUpperBand && lastLowerBand) { + if (upperBand[0] > lastUpperBand+HalfPoint) onChannelWidening(D_LONG); + else if (lowerBand[0] < lastLowerBand-HalfPoint) onChannelWidening(D_SHORT); + } + lastUpperBand = upperBand[0]; + lastLowerBand = lowerBand[0]; } } return(last_error); @@ -733,47 +929,6 @@ double CalculateMarkerOffset() { } -/** - * Whether a bar crossing both channel bands crossed the upper band first. The result is just a "best guess". - * - * @param int bar - bar offset - * - * @return bool - */ -bool IsUpperCrossFirst(int bar) { - static datetime lastBarTime; - static double lastBarHigh, lastBarLow; - static bool lastResult = -1; - - if (ChangedBars==1 && bar==0) { // to minimze "guessing" errors and have consistent results when - if (Time[0]==lastBarTime && lastResult != -1) { // a bar receives multiple ticks we cache the status of Bar[0] - if (EQ(High[0], lastBarHigh)) { // TODO: also cache result for Bar[1] - if (EQ(Low[0], lastBarLow)) { - return(lastResult); - } - } - } - } - - double ho = High [bar] - Open [bar]; - double ol = Open [bar] - Low [bar]; - double hc = High [bar] - Close[bar]; - double cl = Close[bar] - Low [bar]; - - double minOpen = MathMin(ho, ol); - double minClose = MathMin(hc, cl); - - if (minOpen < minClose) lastResult = (ho < ol); - else lastResult = (hc > cl); - - lastBarTime = Time[bar]; - lastBarHigh = High[bar]; - lastBarLow = Low [bar]; - - return(lastResult); -} - - /** * Whether a bar crossing both channel bands crossed the upper band last. The result is just a "best guess". * @@ -793,88 +948,100 @@ bool IsUpperCrossLast(int bar) { if (minOpen < minClose) return(ho > ol); return(hc < cl); - - IsUpperCrossFirst(NULL); } /** - * Find the chart offset of the ZigZag semaphore preceeding the specified semaphore. The found semaphore may be located at - * the same bar. + * Find the next ZigZag semaphore starting at the specified bar looking backwards. On a semaphore bar the semaphore of the + * bar itself is returned. Specify a semaphore type to be skipped to prevent this. * - * @param _In_ int bar - start bar - * @param _InOut_ int type - semaphore type, on of: MODE_HIGH|MODE_LOW|NULL - * In: type of semaphore to start searching from (NULL: any type) - * Out: type of the found semaphore (MODE_HIGH|MODE_LOW) + * @param _In_ int bar - bar to start searching from + * @param _Out_ int resultType - semaphore result type: MODE_HIGH|MODE_LOW + * @param _In_ int skipType [optional] - semaphore type on the start bar to be skipped: MODE_HIGH|MODE_LOW (default: no skipping) * - * @return int - chart offset of the found semaphore or EMPTY (-1) in case of errors + * @return int - chart offset of the found semaphore; + * EMPTY (-1) if no semaphore was found or in case of errors (parameter 'resultType' remains unchanged) */ -int FindPrevSemaphore(int bar, int &type) { - if (bar < 0 || bar >= Bars) return(_EMPTY(catch("FindPrevSemaphore(1) invalid parameter bar: "+ bar +" (out of range)", ERR_INVALID_PARAMETER))); - if (type != NULL) { - if (type!=MODE_HIGH && type!=MODE_LOW) return(_EMPTY(catch("FindPrevSemaphore(2) invalid parameter type: "+ type, ERR_INVALID_PARAMETER))); +int FindSemaphore(int bar, int &resultType, int skipType = NULL) { + if (bar < 0 || bar >= Bars) return(_EMPTY(catch("FindSemaphore(1) invalid parameter bar: "+ bar +" (out of range)", ERR_INVALID_PARAMETER))); + if (skipType != NULL) { + if (skipType!=MODE_HIGH && skipType!=MODE_LOW) return(_EMPTY(catch("FindSemaphore(2) invalid parameter skipType: "+ skipType, ERR_INVALID_PARAMETER))); + } + static int doDebug = 0; + if (debugging && (Ticks==1 && Time[bar] >= devFirstCrossing && Time[bar] <= devFirstCrossing + 2*MINUTES) || doDebug) { + debug("FindSemaphore(0.1) start @ "+ TimeToStr(Time[bar]) +" skipType="+ semTypes[skipType]); + doDebug++; } - if (!type || !semaphoreClose[bar]) { - if (semaphoreClose[bar] != NULL) { // semaphore is located at the current bar - int semBar = bar; - } - else { // semaphore is located somewhere before + // if no skipping (no skip type or not a semaphore bar), then return the next semaphore + if (!skipType || !semaphoreClose[bar]) { + if (!semaphoreClose[bar]) { // semaphore is located somewhere before bar++; - semBar = bar + unknownTrend[bar]; // if unknown[bar] != 0 it points to the preceeding semaphore; if = 0 it doesn't change a thing - if (!semaphoreClose[semBar]) { - // a semaphore on a regular bar is located at: bar + trend - // a semaphore on a large bar (semaphore + reversal to other side) is located at: bar + trend - 1 // Abs(knownTrend[semBar]) = 1 = new trend - int trend = Abs(knownTrend[bar]); - semBar = bar + trend - 1; - if (!semaphoreClose[semBar]) semBar++; - } } - if (!lowerLow [semBar]) type = MODE_HIGH; - else if (!higherHigh [semBar]) type = MODE_LOW; - else if (semaphoreOpen [semBar] < semaphoreClose[semBar]-HalfPoint) type = MODE_HIGH; - else if (semaphoreOpen [semBar] > semaphoreClose[semBar]+HalfPoint) type = MODE_LOW; - else if (semaphoreClose[semBar] == higherHigh[semBar]) type = MODE_HIGH; // semaphoreOpen == semaphoreClose - else type = MODE_LOW; // ... - return(semBar); + if (!semaphoreClose[bar] && semaphoreOffset[bar] > 0) { // navigate to the end semaphore (if any), + bar += semaphoreOffset[bar]; // a current bar has never semaphoreOffset=-1 + } + if (!semaphoreClose[bar] && trend[bar]) { // navigate to the start semaphore (if any) + bar += Abs(trend[bar]); + } + if (!semaphoreClose[bar]) { + if (doDebug > 0) { debug("FindSemaphore(0.2) return "+ EMPTY); doDebug--; } + return(EMPTY); + } + if (!lowerCrossLow [bar]) resultType = MODE_HIGH; + else if (!upperCrossHigh[bar]) resultType = MODE_LOW; + else if (semaphoreOpen [bar] < semaphoreClose[bar]-HalfPoint) resultType = MODE_HIGH; + else if (semaphoreOpen [bar] > semaphoreClose[bar]+HalfPoint) resultType = MODE_LOW; + else if (semaphoreClose[bar] > upperCrossHigh[bar]-HalfPoint) resultType = MODE_HIGH; // from here it holds: open == close + else resultType = MODE_LOW; // ... + if (doDebug > 0) { debug("FindSemaphore(0.3) return "+ bar +" = "+ TimeToStr(Time[bar])); doDebug--; } + return(bar); } + + // if skipping: some semaphore type to skip on the start bar (not used by ZigZag calculation) if (semaphoreOpen[bar] == semaphoreClose[bar]) { - bool isHigh = (semaphoreClose[bar] == higherHigh[bar]); + bool isHigh = (semaphoreClose[bar] == upperCrossHigh[bar]); - if (type == MODE_HIGH) { + if (skipType == MODE_HIGH) { if (isHigh) { - type = NULL; - return(FindPrevSemaphore(bar+1, type)); + int iTmp = FindSemaphore(bar+1, resultType); + if (doDebug > 0) { debug("FindSemaphore(0.4) return "+ iTmp +" = "+ TimeToStr(Time[iTmp])); doDebug--; } + return(iTmp); } - type = MODE_LOW; + resultType = MODE_LOW; } - else /*type == MODE_LOW*/ { + else /*skipType == MODE_LOW*/ { if (!isHigh) { - type = NULL; - return(FindPrevSemaphore(bar+1, type)); + iTmp = FindSemaphore(bar+1, resultType); + if (doDebug > 0) { debug("FindSemaphore(0.5) return "+ iTmp +" = "+ TimeToStr(Time[iTmp])); doDebug--; } + return(iTmp); } - type = MODE_HIGH; + resultType = MODE_HIGH; } } else { bool high2Low = (semaphoreOpen[bar] > semaphoreClose[bar]+HalfPoint); - if (type == MODE_HIGH) { + if (skipType == MODE_HIGH) { if (high2Low) { - type = NULL; - return(FindPrevSemaphore(bar+1, type)); + iTmp = FindSemaphore(bar+1, resultType); + if (doDebug > 0) { debug("FindSemaphore(0.6) return "+ iTmp +" = "+ TimeToStr(Time[iTmp])); doDebug--; } + return(iTmp); } - type = MODE_LOW; + resultType = MODE_LOW; } - else /*type == MODE_LOW*/ { + else /*skipType == MODE_LOW*/ { if (!high2Low) { - type = NULL; - return(FindPrevSemaphore(bar+1, type)); + iTmp = FindSemaphore(bar+1, resultType); + if (doDebug > 0) { debug("FindSemaphore(0.7) return "+ iTmp +" = "+ TimeToStr(Time[iTmp])); doDebug--; } + return(iTmp); } - type = MODE_HIGH; + resultType = MODE_HIGH; } } + + if (doDebug > 0) { debug("FindSemaphore(0.8) return "+ bar +" = "+ TimeToStr(Time[bar])); doDebug--; } return(bar); } @@ -888,49 +1055,80 @@ int FindPrevSemaphore(int bar, int &type) { * @return bool - success status */ bool ProcessUpperCross(int bar) { - int type, prevSem=FindPrevSemaphore(bar, type); // resolve the previous semaphore - int prevTrend = knownTrend[prevSem]; // trend at the previous semaphore + if (debugging && Ticks==1 && Time[bar] >= devFrom && Time[bar] <= devTo) { + debug("ProcessUpperCross(0.1) "+ TimeToStr(Time[bar])); + } + int lastSemType, lastSemBar = FindSemaphore(bar, lastSemType); // find the last semaphore + + if (debugging && Ticks==1 && Time[bar] >= devFrom && Time[bar] <= devTo) { + if (lastSemBar < 0) debug("ProcessUpperCross(0.2) found no semaphore"); + else debug("ProcessUpperCross(0.3) found semaphore at bar["+ lastSemBar +"] "+ TimeToStr(Time[lastSemBar]) +" type="+ semTypes[lastSemType] +" trend="+ trend[lastSemBar]); + } - if (prevTrend > 0) { - if (prevSem == bar) { // trend buffers are already set - if (semaphoreOpen[bar] != lowerLow[bar]) { // update existing semaphore - semaphoreOpen[bar] = higherHigh[bar]; + // an upper cross without a previous semaphore (near MaxBarsBack) + if (lastSemBar < 0) { + if (!last_error) { + semaphoreOpen [bar] = upperCrossHigh[bar]; // set new semaphore + semaphoreClose [bar] = upperCrossHigh[bar]; + trend [bar] = 0; // no trend + reversalOffset [bar] = -1; // no reversal + semaphoreOffset[bar] = 0; // current bar + } + return(!last_error); + } + + // another upper cross of a ZigZag leg up + if (lastSemType == MODE_HIGH) { + if (lastSemBar == bar) { // double crossing + if (semaphoreOpen[bar] != lowerCrossLow[bar]) { // keep trend buffers from first crossing + semaphoreOpen[bar] = upperCrossHigh[bar]; // update existing semaphore } - semaphoreClose[bar] = higherHigh[bar]; + semaphoreClose[bar] = upperCrossHigh[bar]; } else { - if (higherHigh[bar] > higherHigh[prevSem]) { // an uptrend continuation - UpdateTrend(prevSem, prevTrend, bar, false); // update existing trend - if (semaphoreOpen[prevSem] == semaphoreClose[prevSem]) { - semaphoreOpen [prevSem] = 0; // reset previous semaphore + if (upperCrossHigh[bar] > upperCrossHigh[lastSemBar]) { // an uptrend continuation + SetTrend(lastSemBar, trend[lastSemBar], bar, false); // update existing trend + + if (semaphoreOpen[lastSemBar] != semaphoreClose[lastSemBar]) { + SetTrend(lastSemBar-1, 1, bar, false); // fix trend=0 on double crossings } - semaphoreClose[prevSem] = semaphoreOpen[prevSem]; - semaphoreOpen [bar] = higherHigh[bar]; // set new semaphore - semaphoreClose[bar] = higherHigh[bar]; + else { + semaphoreOpen[lastSemBar] = 0; // reset previous semaphore + } + semaphoreClose [lastSemBar] = semaphoreOpen[lastSemBar]; + semaphoreOpen [bar] = upperCrossHigh[bar]; // set new semaphore + semaphoreClose [bar] = upperCrossHigh[bar]; + semaphoreOffset[bar] = 0; // current bar } else { // a lower High (unknown direction) - knownTrend [bar] = knownTrend [bar+1]; // keep known trend - unknownTrend[bar] = unknownTrend[bar+1] + 1; // increase unknown trend + trend [bar] = trend [bar+1]; // keep trend (may be 0) + semaphoreOffset[bar] = semaphoreOffset[bar+1] + 1; // increase semaphore offset } - reversal[bar] = reversal[bar+1]; // keep reversal offset + reversalOffset[bar] = reversalOffset[bar+1]; // keep reversal offset (may be -1) } } - else { // a reversal from "short" to "long" (new uptrend) - if (prevSem == bar) { - UpdateTrend(prevSem, 1, bar, false); // flip trend on same bar, keep semaphoreOpen[] + + // or an upper cross finishing a ZigZag leg down + else /*lastSemType == MODE_LOW*/ { // a reversal from "short" to "long" (new uptrend) + if (lastSemBar == bar) { + trend[bar] = 0; // double crossing: reset trend, keep semaphoreOpen[] } else { - UpdateTrend(prevSem-1, 1, bar, true); // set the new trend range, reset reversals - semaphoreOpen[bar] = higherHigh[bar]; // set new semaphore + SetTrend(lastSemBar-1, 1, bar, true); // set the new trend range, reset reversals + semaphoreOpen[bar] = upperCrossHigh[bar]; // set new semaphore } - semaphoreClose[bar] = higherHigh[bar]; - reversal [bar] = prevSem-bar; // set new reversal offset + semaphoreClose [bar] = upperCrossHigh[bar]; + reversalOffset [bar] = lastSemBar - bar; // set new reversal offset + semaphoreOffset[bar] = 0; // current bar sema3 = sema2; // update the last 3 semaphores sema2 = sema1; - sema1 = Low[prevSem]; + sema1 = Low[lastSemBar]; lastLegHigh = 0; // reset last leg high - if (Signal.onReversal && __isChart && ChangedBars <= 2) onReversal(D_LONG, upperCross[bar]); + + if (Signal.onReversal && __isChart && ChangedBars <= 2) { + onReversal(D_LONG, upperCross[bar]); + } } return(true); } @@ -945,76 +1143,108 @@ bool ProcessUpperCross(int bar) { * @return bool - success status */ bool ProcessLowerCross(int bar) { - int type, prevSem=FindPrevSemaphore(bar, type); // resolve the previous semaphore - int prevTrend = knownTrend[prevSem]; // trend at the previous semaphore + if (debugging && Ticks==1 && Time[bar] >= devFrom && Time[bar] <= devTo) { + debug("ProcessLowerCross(0.1) "+ TimeToStr(Time[bar])); + } + int lastSemType, lastSemBar = FindSemaphore(bar, lastSemType); // find the last semaphore - if (prevTrend < 0) { - if (prevSem == bar) { // trend buffers are already set - if (semaphoreOpen[bar] != higherHigh[bar]) { // update existing semaphore - semaphoreOpen [bar] = lowerLow[bar]; + if (debugging && Ticks==1 && Time[bar] >= devFrom && Time[bar] <= devTo) { + if (lastSemBar < 0) debug("ProcessLowerCross(0.2) found no semaphore"); + else debug("ProcessLowerCross(0.3) found semaphore at bar["+ lastSemBar +"] "+ TimeToStr(Time[lastSemBar]) +" type="+ semTypes[lastSemType] +" trend="+ trend[lastSemBar]); + } + + // a lower cross without a previous semaphore (near MaxBarsBack) + if (lastSemBar < 0) { + if (!last_error) { + semaphoreOpen [bar] = lowerCrossLow[bar]; // set new semaphore + semaphoreClose [bar] = lowerCrossLow[bar]; + trend [bar] = 0; // no trend + reversalOffset [bar] = -1; // no reversal + semaphoreOffset[bar] = 0; // current bar + } + return(!last_error); + } + + // another lower cross of a ZigZag leg down + if (lastSemType == MODE_LOW) { + if (lastSemBar == bar) { // double crossing + if (semaphoreOpen[bar] != upperCrossHigh[bar]) { // keep trend buffers from first crossing + semaphoreOpen [bar] = lowerCrossLow[bar]; // update existing semaphore } - semaphoreClose[bar] = lowerLow[bar]; + semaphoreClose[bar] = lowerCrossLow[bar]; } else { - if (lowerLow[bar] < lowerLow[prevSem]) { // a downtrend continuation - UpdateTrend(prevSem, prevTrend, bar, false); // update existing trend - if (semaphoreOpen[prevSem] == semaphoreClose[prevSem]) { - semaphoreOpen [prevSem] = 0; // reset previous semaphore + if (lowerCrossLow[bar] < lowerCrossLow[lastSemBar]) { // a downtrend continuation + SetTrend(lastSemBar, trend[lastSemBar], bar, false); // update existing trend + + if (semaphoreOpen[lastSemBar] != semaphoreClose[lastSemBar]) { + SetTrend(lastSemBar-1, -1, bar, false); // fix trend=0 on double crossings } - semaphoreClose[prevSem] = semaphoreOpen[prevSem]; - semaphoreOpen [bar] = lowerLow[bar]; // set new semaphore - semaphoreClose[bar] = lowerLow[bar]; + else { + semaphoreOpen[lastSemBar] = 0; // reset previous semaphore + } + semaphoreClose [lastSemBar] = semaphoreOpen[lastSemBar]; + semaphoreOpen [bar] = lowerCrossLow[bar]; // set new semaphore + semaphoreClose [bar] = lowerCrossLow[bar]; + semaphoreOffset[bar] = 0; // current bar } else { // a higher Low (unknown direction) - knownTrend [bar] = knownTrend [bar+1]; // keep known trend - unknownTrend[bar] = unknownTrend[bar+1] + 1; // increase unknown trend + trend [bar] = trend [bar+1]; // keep trend (may be 0) + semaphoreOffset[bar] = semaphoreOffset[bar+1] + 1; // increase semaphore offset } - reversal[bar] = reversal[bar+1]; // keep reversal offset + reversalOffset [bar] = reversalOffset [bar+1]; // keep reversal offset (may be -1) } } - else { // a reversal from "long" to "short" (new downtrend) - if (prevSem == bar) { - UpdateTrend(prevSem, -1, bar, false); // flip trend on same bar, keep semaphoreOpen[] + + // or a lower cross finishing a ZigZag leg up + else /*lastSemType == MODE_HIGH*/ { // a reversal from "long" to "short" (new downtrend) + if (lastSemBar == bar) { + trend[bar] = 0; // double crossing: reset trend, keep semaphoreOpen[] } else { - UpdateTrend(prevSem-1, -1, bar, true); // set the new trend, reset reversals - semaphoreOpen[bar] = lowerLow[bar]; // set new semaphore + SetTrend(lastSemBar-1, -1, bar, true); // set the new trend range, reset reversals + semaphoreOpen[bar] = lowerCrossLow[bar]; // set new semaphore } - semaphoreClose[bar] = lowerLow[bar]; - reversal [bar] = prevSem-bar; // set the new reversal offset + semaphoreClose [bar] = lowerCrossLow[bar]; + reversalOffset [bar] = lastSemBar - bar; // set the new reversal offset + semaphoreOffset[bar] = 0; // current bar sema3 = sema2; // update the last 3 semaphores sema2 = sema1; - sema1 = High[prevSem]; + sema1 = High[lastSemBar]; lastLegLow = 0; // reset last leg low - if (Signal.onReversal && __isChart && ChangedBars <= 2) onReversal(D_SHORT, lowerCross[bar]); + if (Signal.onReversal && __isChart && ChangedBars <= 2) { + onReversal(D_SHORT, lowerCross[bar]); + } } return(true); } /** - * Update/count forward the 'knownTrend' and 'unknownTrend' counter of the specified bar range. + * Update the trend[] values of the specified bar range. + * If fromValue is non-zero, trend[] values are increased per bar. If fromValue is 0, trend[] values are not increased. + * Also resets all semaphoreOffset[] values and optionally the reversalOffset[] values of the specified range. * - * @param int fromBar - start bar of the range to update - * @param int fromValue - start value for the trend counter - * @param int toBar - end bar of the range to update - * @param bool resetReversal - whether to reset the reversal buffer of the bar range + * @param int fromBar - start bar of the range to update (older) + * @param int fromValue - start value for the trend counter + * @param int toBar - end bar of the range to update (younger) + * @param bool resetReversals - whether to reset the reversalOffset[] buffer of the bar range */ -void UpdateTrend(int fromBar, int fromValue, int toBar, bool resetReversalBuffer) { - resetReversalBuffer = resetReversalBuffer!=0; - int value = fromValue; +void SetTrend(int fromBar, int fromValue, int toBar, bool resetReversals) { + resetReversals = resetReversals!=0; + int value = fromValue, sign, cross; for (int i=fromBar; i >= toBar; i--) { - knownTrend [i] = value; - unknownTrend [i] = 0; - combinedTrend[i] = Sign(knownTrend[i]) * unknownTrend[i] * 100000 + knownTrend[i]; + trend [i] = value; + semaphoreOffset[i] = -1; + mergedTrend [i] = trend[i]; - if (resetReversalBuffer) reversal[i] = -1; + if (resetReversals) reversalOffset[i] = -1; - if (value > 0) value++; - else value--; + if (value > 0) value++; + else if (value < 0) value--; } } @@ -1030,9 +1260,12 @@ void UpdateTrend(int fromBar, int fromValue, int toBar, bool resetReversalBuffer bool onReversal(int direction, double level) { if (direction!=D_LONG && direction!=D_SHORT) return(!catch("onReversal(1) invalid parameter direction: "+ direction, ERR_INVALID_PARAMETER)); if (!__isChart) return(true); - if (IsPossibleDataPumping()) return(true); // skip signals during possible data pumping + if (IsPossibleDataPumping()) { // skip signals during possible data pumping + logWarn("onReversal(P="+ ZigZag.Periods +") Tick="+ Ticks +" alleged data pumping (Bars="+ Bars +" ValidBars="+ ValidBars +" ChangedBars="+ ChangedBars +")"); + return(true); + } - // skip the signal if it was already processed elsewhere + // skip the signal if it was already handled elsewhere string sPeriod = PeriodDescription(); string eventName = "rsf::"+ StdSymbol() +","+ sPeriod +"."+ indicatorName +".onReversal("+ direction +")."+ TimeToStr(Time[0]), propertyName = ""; string message1 = ifString(direction==D_LONG, "up", "down") +" (level: "+ NumberToStr(level, PriceFormat) +")"; @@ -1103,7 +1336,7 @@ bool onBreakout(int direction) { if (!__isChart) return(true); if (IsPossibleDataPumping()) return(true); // skip signals during possible data pumping - // skip the signal if it was already processed elsewhere + // skip the signal if it was already handled elsewhere string sPeriod = PeriodDescription(); string eventName = "rsf::"+ StdSymbol() +","+ sPeriod +"."+ indicatorName +"(P="+ ZigZag.Periods +").onBreakout("+ direction +")."+ TimeToStr(Time[0]), propertyName = ""; string message1 = ifString(direction==D_LONG, "long", "short") +" (bid: "+ NumberToStr(_Bid, PriceFormat) +")"; @@ -1235,17 +1468,19 @@ bool ParameterStepper(int direction, int keys) { bool IsPossibleDataPumping() { if (__isTesting) return(false); - int waitPeriod = 20*SECONDS; + // TODO: What kind of nonsense is this implementation? + + int waitPeriod = 20 * SECONDS; datetime now = GetGmtTime(); - bool pumping = true; + bool isPumping = true; if (now > skipSignals) skipSignals = 0; if (!skipSignals) { if (now > lastTick + waitPeriod) skipSignals = now + waitPeriod; - else pumping = false; + else isPumping = false; } lastTick = now; - return(pumping); + return(isPumping); } @@ -1256,12 +1491,12 @@ void UpdateChartLegend() { static int lastTrend, lastTime, lastAccount; // update on full recalculation or if indicator name, trend, current bar or the account changed - if (!ValidBars || combinedTrend[0]!=lastTrend || Time[0]!=lastTime || AccountNumber()!=lastAccount) { - string sKnown = " "+ NumberToStr(knownTrend[0], "+."); - string sUnknown = ifString(!unknownTrend[0], "", "/"+ unknownTrend[0]); - string sReversal = " next reversal @" + NumberToStr(ifDouble(knownTrend[0] < 0, upperBand[0]+Point, lowerBand[0]-Point), PriceFormat); + if (!ValidBars || mergedTrend[0]!=lastTrend || Time[0]!=lastTime || AccountNumber()!=lastAccount) { + string sTrend = " "+ NumberToStr(trend[0], "+."); + string sUnknown = ifString(!semaphoreOffset[0], "", "/"+ semaphoreOffset[0]); + string sReversal = " next reversal @" + NumberToStr(ifDouble(trend[0] < 0, upperBand[0]+Point, lowerBand[0]-Point), PriceFormat); string sSignal = ifString(Signal.onReversal, " "+ legendInfo, ""); - string text = StringConcatenate(indicatorName, sKnown, sUnknown, sReversal, sSignal); + string text = StringConcatenate(indicatorName, sTrend, sUnknown, sReversal, sSignal); color clr = ZigZag.Color; if (clr == Aqua ) clr = DodgerBlue; @@ -1274,7 +1509,7 @@ void UpdateChartLegend() { int error = GetLastError(); if (error && error!=ERR_OBJECT_DOES_NOT_EXIST) catch("UpdateChartLegend(1)", error); // on ObjectDrag or opened "Properties" dialog - lastTrend = combinedTrend[0]; + lastTrend = mergedTrend[0]; lastTime = Time[0]; lastAccount = AccountNumber(); } @@ -1298,14 +1533,14 @@ bool SetIndicatorOptions(bool redraw = false) { IndicatorShortName(shortName); IndicatorBuffers(terminal_buffers); - SetIndexBuffer(MODE_SEMAPHORE_OPEN, semaphoreOpen ); SetIndexEmptyValue(MODE_SEMAPHORE_OPEN, 0); SetIndexLabel(MODE_SEMAPHORE_OPEN, NULL); - SetIndexBuffer(MODE_SEMAPHORE_CLOSE, semaphoreClose); SetIndexEmptyValue(MODE_SEMAPHORE_CLOSE, 0); SetIndexLabel(MODE_SEMAPHORE_CLOSE, NULL); - SetIndexBuffer(MODE_UPPER_BAND, upperBand ); SetIndexEmptyValue(MODE_UPPER_BAND, 0); SetIndexLabel(MODE_UPPER_BAND, donchianName +" upper band"); if (!Donchian.ShowChannel) SetIndexLabel(MODE_UPPER_BAND, NULL); - SetIndexBuffer(MODE_LOWER_BAND, lowerBand ); SetIndexEmptyValue(MODE_LOWER_BAND, 0); SetIndexLabel(MODE_LOWER_BAND, donchianName +" lower band"); if (!Donchian.ShowChannel) SetIndexLabel(MODE_LOWER_BAND, NULL); - SetIndexBuffer(MODE_UPPER_CROSS, upperCross ); SetIndexEmptyValue(MODE_UPPER_CROSS, 0); SetIndexLabel(MODE_UPPER_CROSS, shortName +" cross up"); if (!crossingDrawType) SetIndexLabel(MODE_UPPER_CROSS, NULL); - SetIndexBuffer(MODE_LOWER_CROSS, lowerCross ); SetIndexEmptyValue(MODE_LOWER_CROSS, 0); SetIndexLabel(MODE_LOWER_CROSS, shortName +" cross down"); if (!crossingDrawType) SetIndexLabel(MODE_LOWER_CROSS, NULL); - SetIndexBuffer(MODE_REVERSAL, reversal ); SetIndexEmptyValue(MODE_REVERSAL, -1); SetIndexLabel(MODE_REVERSAL, shortName +" reversal"); - SetIndexBuffer(MODE_COMBINED_TREND, combinedTrend ); SetIndexEmptyValue(MODE_COMBINED_TREND, 0); SetIndexLabel(MODE_COMBINED_TREND, shortName +" trend"); + SetIndexBuffer(MODE_SEMAPHORE_OPEN, semaphoreOpen ); SetIndexEmptyValue(MODE_SEMAPHORE_OPEN, 0); SetIndexLabel(MODE_SEMAPHORE_OPEN, NULL); + SetIndexBuffer(MODE_SEMAPHORE_CLOSE, semaphoreClose); SetIndexEmptyValue(MODE_SEMAPHORE_CLOSE, 0); SetIndexLabel(MODE_SEMAPHORE_CLOSE, NULL); + SetIndexBuffer(MODE_UPPER_BAND, upperBand ); SetIndexEmptyValue(MODE_UPPER_BAND, 0); SetIndexLabel(MODE_UPPER_BAND, donchianName +" upper band"); if (!Donchian.ShowChannel) SetIndexLabel(MODE_UPPER_BAND, NULL); + SetIndexBuffer(MODE_LOWER_BAND, lowerBand ); SetIndexEmptyValue(MODE_LOWER_BAND, 0); SetIndexLabel(MODE_LOWER_BAND, donchianName +" lower band"); if (!Donchian.ShowChannel) SetIndexLabel(MODE_LOWER_BAND, NULL); + SetIndexBuffer(MODE_UPPER_CROSS, upperCross ); SetIndexEmptyValue(MODE_UPPER_CROSS, 0); SetIndexLabel(MODE_UPPER_CROSS, shortName +" cross up"); if (!crossingDrawType) SetIndexLabel(MODE_UPPER_CROSS, NULL); + SetIndexBuffer(MODE_LOWER_CROSS, lowerCross ); SetIndexEmptyValue(MODE_LOWER_CROSS, 0); SetIndexLabel(MODE_LOWER_CROSS, shortName +" cross down"); if (!crossingDrawType) SetIndexLabel(MODE_LOWER_CROSS, NULL); + SetIndexBuffer(MODE_REVERSAL_OFFSET, reversalOffset); SetIndexEmptyValue(MODE_REVERSAL_OFFSET, -1); SetIndexLabel(MODE_REVERSAL_OFFSET, shortName +" reversal offset"); + SetIndexBuffer(MODE_MERGED_TREND, mergedTrend ); SetIndexEmptyValue(MODE_MERGED_TREND, 0); SetIndexLabel(MODE_MERGED_TREND, shortName +" trend"); IndicatorDigits(Digits); int drawType = ifInt(ZigZag.Width, zigzagDrawType, DRAW_NONE); @@ -1322,8 +1557,8 @@ bool SetIndicatorOptions(bool redraw = false) { SetIndexStyle(MODE_UPPER_CROSS, drawType, EMPTY, drawWidth, colorOr(Donchian.Crossing.Color, Donchian.Channel.UpperColor)); SetIndexArrow(MODE_UPPER_CROSS, crossingSymbol); SetIndexStyle(MODE_LOWER_CROSS, drawType, EMPTY, drawWidth, colorOr(Donchian.Crossing.Color, Donchian.Channel.LowerColor)); SetIndexArrow(MODE_LOWER_CROSS, crossingSymbol); - SetIndexStyle(MODE_REVERSAL, DRAW_NONE); - SetIndexStyle(MODE_COMBINED_TREND, DRAW_NONE); + SetIndexStyle(MODE_REVERSAL_OFFSET, DRAW_NONE); + SetIndexStyle(MODE_MERGED_TREND, DRAW_NONE); if (redraw) WindowRedraw(); return(!catch("SetIndicatorOptions(1)")); From becc54e0edea5cd9e80f3ce4ceda4ed5e4c14d9c Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Thu, 19 Mar 2026 10:23:56 +0200 Subject: [PATCH 05/40] update labels in "Data Window" --- .../include/rsf/functions/iCustom/ZigZag.mqh | 12 +++---- mql40/indicators/ZigZag.mq4 | 34 +++++++++---------- templates4/12 ZigZag(50).tpl | 8 ++--- templates4/13 ZigZag(50) + DC Width.tpl | 4 +-- templates4/14 ZigZag(50,30).tpl | 8 ++--- .../15 ZigZag(50,30) ovl DC Width.tpl | 8 ++--- templates4/31 XARD M1.tpl | 4 +-- 7 files changed, 38 insertions(+), 40 deletions(-) diff --git a/mql40/include/rsf/functions/iCustom/ZigZag.mqh b/mql40/include/rsf/functions/iCustom/ZigZag.mqh index d56624c4e..3cdc76247 100644 --- a/mql40/include/rsf/functions/iCustom/ZigZag.mqh +++ b/mql40/include/rsf/functions/iCustom/ZigZag.mqh @@ -1,14 +1,12 @@ -#define ZigZag.MODE_SEMAPHORE_OPEN 0 // semaphore open prices: positive or 0 -#define ZigZag.MODE_SEMAPHORE_CLOSE 1 // semaphore close prices: positive or 0 (if open != close it forms a vertical line segment) -#define ZigZag.MODE_UPPER_BAND 2 // upper channel band: positive or 0 -#define ZigZag.MODE_LOWER_BAND 3 // lower channel band: positive or 0 +#define ZigZag.MODE_UPPER_BAND 0 // upper channel band: positive or 0 +#define ZigZag.MODE_LOWER_BAND 1 // lower channel band: positive or 0 +#define ZigZag.MODE_SEMAPHORE_OPEN 2 // semaphore open prices: positive or 0 +#define ZigZag.MODE_SEMAPHORE_CLOSE 3 // semaphore close prices: positive or 0 (if open != close it forms a vertical line segment) #define ZigZag.MODE_UPPER_CROSS 4 // upper channel band crossings: positive or 0 #define ZigZag.MODE_LOWER_CROSS 5 // lower channel band crossings: positive or 0 #define ZigZag.MODE_REVERSAL_OFFSET 6 // int: offset of the ZigZag reversal to the leg's start semaphore: non-negative or -1 -// intern MODE_SEMAPHORE_OFFSET 10 // int: offset of the current bar to the leg's end semaphore: non-negative or -1 -// intern MODE_TREND 11 // int: direction and length of the ZigZag leg: positive/negative or 0 -#define ZigZag.MODE_TREND 7 // int: merged buffers MODE_TREND & MODE_SEMAPHORE_OFFSET: positive/negative or 0 +#define ZigZag.MODE_TREND 7 // int: merged internal buffers MODE_TREND & MODE_SEMAPHORE_OFFSET: positive/negative or 0 /** diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index 9d02a6bde..8cf3cc0f5 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -128,10 +128,10 @@ extern string Sound.onNewChannelLow = "Price Decline.wav"; #include // indicator buffer ids -#define MODE_SEMAPHORE_OPEN ZigZag.MODE_SEMAPHORE_OPEN // 0: final semaphores, open price: positive or 0 -#define MODE_SEMAPHORE_CLOSE ZigZag.MODE_SEMAPHORE_CLOSE // 1: final semaphores, close price: positive or 0 (if open != close it forms a vertical line segment) -#define MODE_UPPER_BAND ZigZag.MODE_UPPER_BAND // 2: upper channel band: positive or 0 -#define MODE_LOWER_BAND ZigZag.MODE_LOWER_BAND // 3: lower channel band: positive or 0 +#define MODE_UPPER_BAND ZigZag.MODE_UPPER_BAND // 0: upper channel band: positive or 0 +#define MODE_LOWER_BAND ZigZag.MODE_LOWER_BAND // 1: lower channel band: positive or 0 +#define MODE_SEMAPHORE_OPEN ZigZag.MODE_SEMAPHORE_OPEN // 2: final semaphores, open price: positive or 0 +#define MODE_SEMAPHORE_CLOSE ZigZag.MODE_SEMAPHORE_CLOSE // 3: final semaphores, close price: positive or 0 (if open != close it forms a vertical line segment) #define MODE_UPPER_CROSS ZigZag.MODE_UPPER_CROSS // 4: upper channel band crossings: positive or 0 #define MODE_LOWER_CROSS ZigZag.MODE_LOWER_CROSS // 5: lower channel band crossings: positive or 0 #define MODE_REVERSAL_OFFSET ZigZag.MODE_REVERSAL_OFFSET // 6: int: offset of the ZigZag reversal to the leg's start semaphore: non-negative or -1 @@ -147,14 +147,14 @@ extern string Sound.onNewChannelLow = "Price Decline.wav"; int terminal_buffers = 8; // buffers managed by the terminal int framework_buffers = 5; // buffers managed by the framework -#property indicator_color1 DodgerBlue // the ZigZag line is built from two buffers using the color of the first buffer -#property indicator_width1 1 // -#property indicator_color2 CLR_NONE // +#property indicator_color1 Blue // upper channel band +#property indicator_style1 STYLE_DOT // +#property indicator_color2 Magenta // lower channel band +#property indicator_style2 STYLE_DOT // -#property indicator_color3 Blue // upper channel band -#property indicator_style3 STYLE_DOT // -#property indicator_color4 Magenta // lower channel band -#property indicator_style4 STYLE_DOT // +#property indicator_color3 DodgerBlue // the ZigZag line is built from two buffers using the color of the first buffer +#property indicator_width3 1 // +#property indicator_color4 CLR_NONE // #property indicator_color5 indicator_color3 // upper channel band crossings #property indicator_width5 0 // @@ -1533,12 +1533,12 @@ bool SetIndicatorOptions(bool redraw = false) { IndicatorShortName(shortName); IndicatorBuffers(terminal_buffers); - SetIndexBuffer(MODE_SEMAPHORE_OPEN, semaphoreOpen ); SetIndexEmptyValue(MODE_SEMAPHORE_OPEN, 0); SetIndexLabel(MODE_SEMAPHORE_OPEN, NULL); - SetIndexBuffer(MODE_SEMAPHORE_CLOSE, semaphoreClose); SetIndexEmptyValue(MODE_SEMAPHORE_CLOSE, 0); SetIndexLabel(MODE_SEMAPHORE_CLOSE, NULL); - SetIndexBuffer(MODE_UPPER_BAND, upperBand ); SetIndexEmptyValue(MODE_UPPER_BAND, 0); SetIndexLabel(MODE_UPPER_BAND, donchianName +" upper band"); if (!Donchian.ShowChannel) SetIndexLabel(MODE_UPPER_BAND, NULL); - SetIndexBuffer(MODE_LOWER_BAND, lowerBand ); SetIndexEmptyValue(MODE_LOWER_BAND, 0); SetIndexLabel(MODE_LOWER_BAND, donchianName +" lower band"); if (!Donchian.ShowChannel) SetIndexLabel(MODE_LOWER_BAND, NULL); - SetIndexBuffer(MODE_UPPER_CROSS, upperCross ); SetIndexEmptyValue(MODE_UPPER_CROSS, 0); SetIndexLabel(MODE_UPPER_CROSS, shortName +" cross up"); if (!crossingDrawType) SetIndexLabel(MODE_UPPER_CROSS, NULL); - SetIndexBuffer(MODE_LOWER_CROSS, lowerCross ); SetIndexEmptyValue(MODE_LOWER_CROSS, 0); SetIndexLabel(MODE_LOWER_CROSS, shortName +" cross down"); if (!crossingDrawType) SetIndexLabel(MODE_LOWER_CROSS, NULL); + SetIndexBuffer(MODE_UPPER_BAND, upperBand ); SetIndexEmptyValue(MODE_UPPER_BAND, 0); SetIndexLabel(MODE_UPPER_BAND, donchianName +" upper band"); if (!Donchian.ShowChannel) SetIndexLabel(MODE_UPPER_BAND, NULL); + SetIndexBuffer(MODE_LOWER_BAND, lowerBand ); SetIndexEmptyValue(MODE_LOWER_BAND, 0); SetIndexLabel(MODE_LOWER_BAND, donchianName +" lower band"); if (!Donchian.ShowChannel) SetIndexLabel(MODE_LOWER_BAND, NULL); + SetIndexBuffer(MODE_SEMAPHORE_OPEN, semaphoreOpen ); SetIndexEmptyValue(MODE_SEMAPHORE_OPEN, 0); SetIndexLabel(MODE_SEMAPHORE_OPEN, NULL); + SetIndexBuffer(MODE_SEMAPHORE_CLOSE, semaphoreClose); SetIndexEmptyValue(MODE_SEMAPHORE_CLOSE, 0); SetIndexLabel(MODE_SEMAPHORE_CLOSE, shortName +" high/low"); if (!ZigZag.Width) SetIndexLabel(MODE_SEMAPHORE_CLOSE, NULL); + SetIndexBuffer(MODE_UPPER_CROSS, upperCross ); SetIndexEmptyValue(MODE_UPPER_CROSS, 0); SetIndexLabel(MODE_UPPER_CROSS, shortName +" reversal up"); if (!crossingDrawType) SetIndexLabel(MODE_UPPER_CROSS, NULL); + SetIndexBuffer(MODE_LOWER_CROSS, lowerCross ); SetIndexEmptyValue(MODE_LOWER_CROSS, 0); SetIndexLabel(MODE_LOWER_CROSS, shortName +" reversal down"); if (!crossingDrawType) SetIndexLabel(MODE_LOWER_CROSS, NULL); SetIndexBuffer(MODE_REVERSAL_OFFSET, reversalOffset); SetIndexEmptyValue(MODE_REVERSAL_OFFSET, -1); SetIndexLabel(MODE_REVERSAL_OFFSET, shortName +" reversal offset"); SetIndexBuffer(MODE_MERGED_TREND, mergedTrend ); SetIndexEmptyValue(MODE_MERGED_TREND, 0); SetIndexLabel(MODE_MERGED_TREND, shortName +" trend"); IndicatorDigits(Digits); diff --git a/templates4/12 ZigZag(50).tpl b/templates4/12 ZigZag(50).tpl index df256a2ef..a94422b8e 100644 --- a/templates4/12 ZigZag(50).tpl +++ b/templates4/12 ZigZag(50).tpl @@ -137,8 +137,8 @@ Signal.onBreakout=0 Sound.onChannelWidening=0 -style_2=2 -style_3=2 +style_0=2 +style_1=2 show_data=1 @@ -163,8 +163,8 @@ Signal.onBreakout=0 Sound.onChannelWidening=0 -style_2=2 -style_3=2 +style_0=2 +style_1=2 color_6=4294967295 color_7=4294967295 show_data=1 diff --git a/templates4/13 ZigZag(50) + DC Width.tpl b/templates4/13 ZigZag(50) + DC Width.tpl index 1ba512564..7e6ad6b36 100644 --- a/templates4/13 ZigZag(50) + DC Width.tpl +++ b/templates4/13 ZigZag(50) + DC Width.tpl @@ -136,8 +136,8 @@ Signal.onBreakout=0 Sound.onChannelWidening=0 -style_2=2 -style_3=2 +style_0=2 +style_1=2 show_data=1 diff --git a/templates4/14 ZigZag(50,30).tpl b/templates4/14 ZigZag(50,30).tpl index 17d66d333..5019ae4ef 100644 --- a/templates4/14 ZigZag(50,30).tpl +++ b/templates4/14 ZigZag(50,30).tpl @@ -137,8 +137,8 @@ Signal.onBreakout=0 Sound.onChannelWidening=0 -style_2=2 -style_3=2 +style_0=2 +style_1=2 show_data=1 @@ -163,8 +163,8 @@ Signal.onBreakout=0 Sound.onChannelWidening=0 -style_2=2 -style_3=2 +style_0=2 +style_1=2 color_6=4294967295 color_7=4294967295 show_data=1 diff --git a/templates4/15 ZigZag(50,30) ovl DC Width.tpl b/templates4/15 ZigZag(50,30) ovl DC Width.tpl index a34bc9c2e..0f007c83c 100644 --- a/templates4/15 ZigZag(50,30) ovl DC Width.tpl +++ b/templates4/15 ZigZag(50,30) ovl DC Width.tpl @@ -138,8 +138,8 @@ Signal.onBreakout=0 Sound.onChannelWidening=0 -style_2=2 -style_3=2 +style_0=2 +style_1=2 show_data=1 @@ -164,8 +164,8 @@ Signal.onBreakout=0 Sound.onChannelWidening=0 -style_2=2 -style_3=2 +style_0=2 +style_1=2 color_6=4294967295 color_7=4294967295 show_data=1 diff --git a/templates4/31 XARD M1.tpl b/templates4/31 XARD M1.tpl index ac104c9b3..cb5e01c33 100644 --- a/templates4/31 XARD M1.tpl +++ b/templates4/31 XARD M1.tpl @@ -365,8 +365,8 @@ Signal.onReversal=1 Signal.onReversal.Types=sound* | alert | mail | sms -style_2=2 -style_3=2 +style_0=2 +style_1=2 show_data=1 From 9eb2171f325d1d3032a22bc764dc7a121d1ddcbd Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Thu, 19 Mar 2026 20:44:42 +0200 Subject: [PATCH 06/40] add inputs for tracking of signal performance --- mql40/experts/ZigZag EA.mq4 | 4 +- .../include/rsf/functions/iCustom/ZigZag.mqh | 20 +- mql40/indicators/ZigZag.mq4 | 224 +++++++++--------- 3 files changed, 128 insertions(+), 120 deletions(-) diff --git a/mql40/experts/ZigZag EA.mq4 b/mql40/experts/ZigZag EA.mq4 index 140f6d604..25a1b7eb1 100644 --- a/mql40/experts/ZigZag EA.mq4 +++ b/mql40/experts/ZigZag EA.mq4 @@ -6,8 +6,8 @@ * * Requirements * ------------ - * • "mql40/experts/ZigZag EA" (this EA) - * • "mql40/indicators/ZigZag" (the MetaQuotes indicator can't be used) + * • "mql40/experts/ZigZag EA" (this file) + * • "mql40/indicators/ZigZag" (the MetaQuotes version can't be used) * • "mql40/scripts/Config" * • "mql40/scripts/Chart.ToggleOpenOrders" * • "mql40/scripts/Chart.ToggleTradeHistory" diff --git a/mql40/include/rsf/functions/iCustom/ZigZag.mqh b/mql40/include/rsf/functions/iCustom/ZigZag.mqh index 3cdc76247..41b2b7fbd 100644 --- a/mql40/include/rsf/functions/iCustom/ZigZag.mqh +++ b/mql40/include/rsf/functions/iCustom/ZigZag.mqh @@ -25,7 +25,7 @@ double icZigZag(int timeframe, int periods, int iBuffer, int iBar) { } double value = iCustom(NULL, timeframe, "ZigZag", - "separator", // string ____________________________ + "separator", // string ___a_________________________ periods, // int ZigZag.Periods 0, // int ZigZag.Periods.Step "Line", // string ZigZag.Type @@ -33,7 +33,7 @@ double icZigZag(int timeframe, int periods, int iBuffer, int iBar) { 1, // int ZigZag.Width CLR_NONE, // color ZigZag.Color - "separator", // string ____________________________ + "separator", // string ___b_________________________ false, // bool Donchian.ShowChannel CLR_NONE, // color Donchian.Channel.UpperColor CLR_NONE, // color Donchian.Channel.LowerColor @@ -42,16 +42,11 @@ double icZigZag(int timeframe, int periods, int iBuffer, int iBar) { 1, // int Donchian.Crossing.Width CLR_NONE, // color Donchian.Crossing.Color - "separator", // string ____________________________ - false, // bool TrackReversalBalance - 0, // datetime TrackReversalBalance.Since - false, // bool ProjectReversalBalance - - "separator", // string ____________________________ + "separator", // string ___c_________________________ false, // bool ShowChartLegend -1, // int MaxBarsBack - "separator", // string ____________________________ + "separator", // string ___d_________________________ false, // bool Signal.onReversal "", // string Signal.onReversal.Types @@ -65,7 +60,12 @@ double icZigZag(int timeframe, int periods, int iBuffer, int iBar) { "", // string Sound.onNewChannelHigh "", // string Sound.onNewChannelLow - "separator", // string ____________________________ + "separator", // string ___e_________________________ + false, // bool TrackSignalPerformance + 0, // datetime TrackSignalPerformance.Since + "", // string TrackSignalPerformance.Symbol + + "separator", // string _____________________________ false, // bool AutoConfiguration lpSuperContext, // int __lpSuperContext diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index 8cf3cc0f5..a532317f6 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -15,51 +15,59 @@ * * Input parameters * ---------------- - * • ZigZag.Periods: Lookback periods of the Donchian channel. - * • ZigZag.Periods.Step: Controls parameter "ZigZag.Periods" via keyboard. If non-zero it defines the step size of - * the parameter stepper. If 0 (zero) the parameter stepper is disabled. - * • ZigZag.Type: Whether to display the ZigZag line or ZigZag semaphores. - * • ZigZag.Semaphores.Symbol: Symbol used for ZigZag semaphores. - * • ZigZag.Width: The ZigZag's line width or semaphore size. - * • ZigZag.Color: Color of ZigZag line or semaphores. + * • ZigZag.Periods: Look-back periods of the Donchian channel. + * • ZigZag.Periods.Step: Controls parameter "ZigZag.Periods" via keyboard. If non-zero it defines the step size + * of the parameter stepper. If 0 (zero) the parameter stepper is disabled. + * • ZigZag.Type: Whether to display the ZigZag line or ZigZag semaphores. + * • ZigZag.Semaphores.Symbol: Graphic symbol used for ZigZag semaphores. + * • ZigZag.Width: The ZigZag's line width or semaphore size. + * • ZigZag.Color: Color of ZigZag line or semaphores. * - * • Donchian.ShowChannel: Whether to display the internal Donchian channel. - * • Donchian.Channel.UpperColor: Color of upper Donchian channel band. - * • Donchian.Channel.LowerColor: Color of lower Donchian channel band. + * • Donchian.ShowChannel: Whether to display the internal Donchian channel. + * • Donchian.Channel.UpperColor: Color of upper Donchian channel band. + * • Donchian.Channel.LowerColor: Color of lower Donchian channel band. * - * • Donchian.ShowCrossings: Which Donchian channel crossings to display, one of: - * "off": No crossings are displayed. - * "first": Only the first crossing per direction is displayed (the moment when ZigZag creates a new leg). - * "all": All crossings are displayed. Displays the trail of a ZigZag leg as it develops over time. - * • Donchian.Crossing.Symbol: Symbol used for Donchian channel crossings. - * • Donchian.Crossing.Width: Size of displayed Donchian channel crossings. - * • Donchian.Crossing.Color: Custom color of channel crossings (default: color of channel bands). + * • Donchian.ShowCrossings: Which Donchian channel crossings to display, one of: + * "off": No crossings are displayed. + * "first": Only the first crossing is displayed (the moment a new ZigZag leg appears). + * "all": All crossings are displayed. Displays the trail of a ZigZag leg as it develops over time. + * • Donchian.Crossing.Symbol: Graphic symbol used for Donchian channel crossings. + * • Donchian.Crossing.Width: Size of displayed Donchian channel crossings. + * • Donchian.Crossing.Color: Custom color of channel crossings (default: color of channel bands). * - * • TrackZigZagBalance: Whether to track and mark ZigZag balances. - * • TrackZigZagBalance.Since: Start date-time to track and mark ZigZag balances. - * • ProjectNextBalance: Whether to project price levels of the next zero-balance. + * • ShowChartLegend: Whether do display the chart legend. + * • MaxBarsBack: Maximum number of bars back to calculate the indicator for (affects performance). * - * • ShowChartLegend: Whether do display the chart legend. - * • MaxBarsBack: Maximum number of bars back to calculate the indicator for (performance). + * • Signal.onReversal: Whether to signal ZigZag reversals (the moment a new ZigZag leg appears). + * • Signal.onReversal.Types: Signaling methods, can be a combination of "sound", "alert" and/or "mail". * - * • Signal.onReversal: Whether to signal ZigZag reversals (the moment when ZigZag creates a new leg). - * • Signal.onReversal.Types: Reversal signaling methods, can be a combination of "sound", "alert", "mail". + * • Signal.onBreakout: Whether to signal ZigZag breakouts (a ZigZag leg exceeding the previous ZigZag leg). + * • Signal.onBreakout.Types: Signaling methods, can be a combination of "sound", "alert" and/or "mail". * - * • Signal.onBreakout: Whether to signal ZigZag breakouts (price exceeding the previous swing's ZigZag semaphore). - * • Signal.onBreakout.Types: Breakout signaling methods, can be a combination of "sound", "alert", "mail". + * • Signal.Sound.Up: Sound file for signals to the upside. + * • Signal.Sound.Down: Sound file for signals to the downside. * - * • Signal.Sound.Up: Sound file for reversal and breakout signals. - * • Signal.Sound.Down: Sound file for reversal and breakout signals. + * • Sound.onChannelWidening: Whether to play a sound on Donchian channel widening (channel crossings). + * • Sound.onNewChannelHigh: Sound file for channel widenings to the upside. + * • Sound.onNewChannelLow: Sound file for channel widenings to the downside. * - * • Sound.onChannelWidening: Whether to play a sound on Donchian channel widening (channel crossings). - * • Sound.onNewChannelHigh: Sound file for channel widenings to the upside. - * • Sound.onNewChannelLow: Sound file for channel widenings to the downside. + * • TrackSignalPerformance: Whether to track the performance of the reversal signal. + * • TrackSignalPerformance.Since: Start time to track signal performance from. + * • TrackSignalPerformance.Symbol: Custom symbol to use for performance tracking. * - * • AutoConfiguration: If enabled all input parameters can be overwritten with custom default values. + * • AutoConfiguration: If enabled all input parameters can be overwritten with custom default values. * * * * TODO: + * - rename ZigZag.MODE_TREND => ZigZag.MODE_COMBINED_TREND + * - rename MODE_MERGED_TREND => MODE_COMBINED_TREND + * - rename MODE_SEMAPHORE_OFFSET => MODE_NO_TREND + * - rename mergedTrend[] => combinedTrend[] + * - convert TrackSignalPerformance.Since to string + * - remove debug code + * - once finished, update logic in usage locations of icZigZag() + * * - rename Donchian.Crossing.Symbol values to "dot | thin-ring | ring | thick-ring" * - calculate/display ZigZag zero balance projections * - fix triple-crossing at GBPJPY,M5 2023.12.18 00:00, ZigZag(20) @@ -89,16 +97,11 @@ extern string Donchian.Crossing.Symbol = "dot | narrow-ring | ring | bol extern int Donchian.Crossing.Width = 1; extern color Donchian.Crossing.Color = CLR_NONE; -extern string ___c__________________________ = "=== ZigZag projections ==="; -extern bool TrackZigZagBalance = false; // whether to track ZigZag balances -extern datetime TrackZigZagBalance.Since = 0; // mark ZigZag balances since this time -extern bool ProjectNextBalance = false; // whether to project zero-balance levels - -extern string ___d__________________________ = "=== Display settings ==="; +extern string ___c__________________________ = "=== Display settings ==="; extern bool ShowChartLegend = true; extern int MaxBarsBack = 10000; // max. values to calculate (-1: all available) -extern string ___e__________________________ = "=== Signaling ==="; +extern string ___d__________________________ = "=== Signaling ==="; extern bool Signal.onReversal = false; // signal ZigZag reversals (first Donchian channel crossing) extern string Signal.onReversal.Types = "sound* | alert | mail"; @@ -112,8 +115,17 @@ extern bool Sound.onChannelWidening = false; extern string Sound.onNewChannelHigh = "Price Advance.wav"; extern string Sound.onNewChannelLow = "Price Decline.wav"; +extern string ___e__________________________ = "=== Signal performance ==="; +extern bool TrackSignalPerformance = false; // whether to track the signal performance +extern datetime TrackSignalPerformance.Since = 0; // start time to track signal performance from +extern string TrackSignalPerformance.Symbol = "(default)"; // custom symbol to use for performance tracking + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +bool TrackZigZagBalance = false; // whether to track ZigZag balances +datetime TrackZigZagBalance.Since = 0; // mark ZigZag balances since this time +bool ProjectNextBalance = false; // whether to project zero-balance levels + #include #include #include @@ -343,12 +355,16 @@ string semTypes[] = {"NULL", "LOW", "HIGH"}; * @return int - error status */ int onInit() { - devStartTime = D'2026.03.19 01:41'; // TODO: remove once finished - devFirstCrossing = D'2026.03.19 01:48'; // double crossings: P=8/2026.03.16 20:47 - - devFrom = devStartTime + 6 * Period() * MINUTES; + devStartTime = D'2026.03.19 01:41'; // TODO: remove once finished + devFirstCrossing = D'2026.03.19 01:48'; + // double crossings: + devFrom = devStartTime + 6 * Period() * MINUTES; // P=8, 2026.03.16 20:47 devTo = devFirstCrossing + 32 * Period() * MINUTES; + if (debugging && Symbol()=="BTCUSD" && Period()==PERIOD_M1 && ZigZag.Periods <= 20) { + MaxBarsBack = iBarShift(NULL, NULL, devStartTime); + } + string indicator = WindowExpertName(); // validate inputs @@ -428,28 +444,11 @@ int onInit() { if (Donchian.Channel.LowerColor == 0xFF000000) Donchian.Channel.LowerColor = CLR_NONE; if (Donchian.Crossing.Color == 0xFF000000) Donchian.Crossing.Color = CLR_NONE; - // TrackZigZagBalance - if (AutoConfiguration) TrackZigZagBalance = GetConfigBool(indicator, "TrackZigZagBalance", TrackZigZagBalance); - // TrackZigZagBalance.Since - datetime dtValue = TrackZigZagBalance.Since; - if (AutoConfiguration) { - sValue = GetConfigString(indicator, "TrackZigZagBalance.Since", ""); - if (sValue != "") { - int result[]; - if (!ParseDateTime(sValue, DATE_YYYYMMDD|TIME_OPTIONAL, result)) { - return(catch("onInit(9) invalid config parameter TrackZigZagBalance.Since: "+ DoubleQuoteStr(sValue), ERR_INVALID_INPUT_PARAMETER)); - } - TrackZigZagBalance.Since = DateTime2(result); - } - } - // ProjectNextBalance - if (AutoConfiguration) ProjectNextBalance = GetConfigBool(indicator, "ProjectNextBalance", ProjectNextBalance); - // ShowChartLegend if (AutoConfiguration) ShowChartLegend = GetConfigBool(indicator, "ShowChartLegend", ShowChartLegend); // MaxBarsBack if (AutoConfiguration) MaxBarsBack = GetConfigInt(indicator, "MaxBarsBack", MaxBarsBack); - if (MaxBarsBack < -1) return(catch("onInit(10) invalid input parameter MaxBarsBack: "+ MaxBarsBack, ERR_INVALID_INPUT_PARAMETER)); + if (MaxBarsBack < -1) return(catch("onInit(9) invalid input parameter MaxBarsBack: "+ MaxBarsBack, ERR_INVALID_INPUT_PARAMETER)); if (MaxBarsBack == -1) MaxBarsBack = INT_MAX; // Signal.onReversal @@ -458,7 +457,7 @@ int onInit() { ConfigureSignals(signalId, AutoConfiguration, Signal.onReversal); if (Signal.onReversal) { if (!ConfigureSignalTypes(signalId, Signal.onReversal.Types, AutoConfiguration, signal.onReversal.sound, signal.onReversal.alert, signal.onReversal.mail)) { - return(catch("onInit(11) invalid input parameter Signal.onReversal.Types: "+ DoubleQuoteStr(Signal.onReversal.Types), ERR_INVALID_INPUT_PARAMETER)); + return(catch("onInit(10) invalid input parameter Signal.onReversal.Types: "+ DoubleQuoteStr(Signal.onReversal.Types), ERR_INVALID_INPUT_PARAMETER)); } Signal.onReversal = (signal.onReversal.sound || signal.onReversal.alert || signal.onReversal.mail); if (Signal.onReversal) { @@ -471,7 +470,7 @@ int onInit() { ConfigureSignals(signalId, AutoConfiguration, Signal.onBreakout); if (Signal.onBreakout) { if (!ConfigureSignalTypes(signalId, Signal.onBreakout.Types, AutoConfiguration, signal.onBreakout.sound, signal.onBreakout.alert, signal.onBreakout.mail)) { - return(catch("onInit(12) invalid input parameter Signal.onBreakout.Types: "+ DoubleQuoteStr(Signal.onBreakout.Types), ERR_INVALID_INPUT_PARAMETER)); + return(catch("onInit(11) invalid input parameter Signal.onBreakout.Types: "+ DoubleQuoteStr(Signal.onBreakout.Types), ERR_INVALID_INPUT_PARAMETER)); } Signal.onBreakout = (signal.onBreakout.sound || signal.onBreakout.alert || signal.onBreakout.mail); } @@ -487,6 +486,23 @@ int onInit() { else legendInfo = StrLeft(legendInfo, -1) +",w)"; } + // TrackSignalPerformance + if (AutoConfiguration) TrackSignalPerformance = GetConfigBool(indicator, "TrackSignalPerformance", TrackSignalPerformance); + // TrackSignalPerformance.Since + datetime dtValue = TrackSignalPerformance.Since; + if (AutoConfiguration) { + sValue = GetConfigString(indicator, "TrackSignalPerformance.Since", ""); + if (sValue != "") { + int result[]; + if (!ParseDateTime(sValue, DATE_YYYYMMDD|TIME_OPTIONAL, result)) { + return(catch("onInit(12) invalid config parameter TrackSignalPerformance.Since: "+ DoubleQuoteStr(sValue), ERR_INVALID_INPUT_PARAMETER)); + } + TrackSignalPerformance.Since = DateTime2(result); + } + } + // TrackSignalPerformance.Symbol + if (AutoConfiguration) TrackSignalPerformance.Symbol = GetConfigBool(indicator, "TrackSignalPerformance.Symbol", TrackSignalPerformance.Symbol); + // reset global vars used by the various event handlers skipSignals = 0; lastTick = 0; @@ -594,12 +610,6 @@ int onTick() { // check data pumping on every tick so the reversal handler can skip errornous signals if (!__isTesting) IsPossibleDataPumping(); - - if (debugging && Symbol()=="BTCUSD" && Period()==PERIOD_M1 && 5 <= ZigZag.Periods && ZigZag.Periods <= 15) { - MaxBarsBack = iBarShift(NULL, NULL, devStartTime); - } - - // calculate start bar int startBar = Min(MaxBarsBack-1, ChangedBars-1, Bars-ZigZag.Periods); if (startBar < 0 && MaxBarsBack) return(logInfo("onTick(1) Tick="+ Ticks +" Bars="+ Bars +" needed="+ ZigZag.Periods, ERR_HISTORY_INSUFFICIENT)); @@ -642,8 +652,8 @@ int onTick() { lowerCrossLow[bar] = lowerBand[bar]; } - // recalculate ZigZag data - // if no channel crossing (before or after the first semaphore) + // --- start: recalculate ZigZag data --- + // if no channel crossing // before or after the first semaphore if (!upperCross[bar] && !lowerCross[bar]) { trend [bar] = trend [bar+1]; // keep trend (may be 0) reversalOffset [bar] = reversalOffset [bar+1]; // keep reversal offset (may be -1) @@ -670,7 +680,6 @@ int onTick() { else ProcessLowerCross(bar); // --- end: recalculate ZigZag data --- - // whether the processed bar is a reversal bar (not whether the current tick triggered a reversal) bool isReversalBar = false; if (!semaphoreOffset[bar]) { @@ -693,7 +702,6 @@ int onTick() { - // calculate signal performance (if enabled) bool isPosition = (signalPerformance[bar+1] != EMPTY_VALUE); double range; @@ -719,7 +727,7 @@ int onTick() { signalPerformance[bar] = lowerCross[bar] - Close[bar]; // short } } - //debug("onTick(0.1) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" balance="+ DoubleToStr(signalPerformance[bar]/pUnit, pDigits)); + if (Ticks==1) debug("onTick(0.4) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" reversalBar balance="+ DoubleToStr(signalPerformance[bar]/pUnit, pDigits)); } else if (isPosition) { // only update PnL range = Close[bar] - Close[bar+1]; @@ -735,6 +743,8 @@ int onTick() { } + + // hide non-configured crossings if (!crossingDrawType) { // hide all crossings upperCross[bar] = 0; @@ -759,8 +769,6 @@ int onTick() { - // TODO: - // - once finished: update logic in usage locations of icZigZag() @@ -1604,38 +1612,38 @@ bool RestoreStatus() { * @return string */ string InputsToStr() { - return(StringConcatenate("ZigZag.Periods=", ZigZag.Periods +";"+ NL, - "ZigZag.Periods.Step=", ZigZag.Periods.Step +";"+ NL, - "ZigZag.Type=", DoubleQuoteStr(ZigZag.Type) +";"+ NL, - "ZigZag.Semaphores.Symbol=", DoubleQuoteStr(ZigZag.Semaphores.Symbol) +";"+ NL, - "ZigZag.Width=", ZigZag.Width +";"+ NL, - "ZigZag.Color=", ColorToStr(ZigZag.Color) +";"+ NL, - - "Donchian.ShowChannel=", BoolToStr(Donchian.ShowChannel) +";"+ NL, - "Donchian.Channel.UpperColor=", ColorToStr(Donchian.Channel.UpperColor) +";"+ NL, - "Donchian.Channel.LowerColor=", ColorToStr(Donchian.Channel.LowerColor) +";"+ NL, - "Donchian.ShowCrossings=", DoubleQuoteStr(Donchian.ShowCrossings) +";"+ NL, - "Donchian.Crossing.Symbol=", DoubleQuoteStr(Donchian.Crossing.Symbol) +";"+ NL, - "Donchian.Crossing.Width=", Donchian.Crossing.Width +";"+ NL, - "Donchian.Crossing.Color=", ColorToStr(Donchian.Crossing.Color) +";"+ NL, - - "TrackZigZagBalance=", BoolToStr(TrackZigZagBalance) +";"+ NL, - "TrackZigZagBalance.Since=", TimeToStr(TrackZigZagBalance.Since) +";"+ NL, - "ProjectNextBalance=", BoolToStr(ProjectNextBalance) +";"+ NL, - - "ShowChartLegend=", BoolToStr(ShowChartLegend) +";"+ NL, - "MaxBarsBack=", MaxBarsBack +";"+ NL, - - "Signal.onReversal=", BoolToStr(Signal.onReversal) +";"+ NL, - "Signal.onReversal.Types=", DoubleQuoteStr(Signal.onReversal.Types) +";"+ NL, - "Signal.onBreakout=", BoolToStr(Signal.onBreakout) +";"+ NL, - "Signal.onBreakout.Types=", DoubleQuoteStr(Signal.onBreakout.Types) +";"+ NL, - "Signal.Sound.Up=", DoubleQuoteStr(Signal.Sound.Up) +";"+ NL, - "Signal.Sound.Down=", DoubleQuoteStr(Signal.Sound.Down) +";"+ NL, - - "Sound.onChannelWidening=", BoolToStr(Sound.onChannelWidening) +";"+ NL, - "Sound.onNewChannelHigh=", DoubleQuoteStr(Sound.onNewChannelHigh) +";"+ NL, - "Sound.onNewChannelLow=", DoubleQuoteStr(Sound.onNewChannelLow) +";") + return(StringConcatenate("ZigZag.Periods=", ZigZag.Periods +";"+ NL, + "ZigZag.Periods.Step=", ZigZag.Periods.Step +";"+ NL, + "ZigZag.Type=", DoubleQuoteStr(ZigZag.Type) +";"+ NL, + "ZigZag.Semaphores.Symbol=", DoubleQuoteStr(ZigZag.Semaphores.Symbol) +";"+ NL, + "ZigZag.Width=", ZigZag.Width +";"+ NL, + "ZigZag.Color=", ColorToStr(ZigZag.Color) +";"+ NL, + + "Donchian.ShowChannel=", BoolToStr(Donchian.ShowChannel) +";"+ NL, + "Donchian.Channel.UpperColor=", ColorToStr(Donchian.Channel.UpperColor) +";"+ NL, + "Donchian.Channel.LowerColor=", ColorToStr(Donchian.Channel.LowerColor) +";"+ NL, + "Donchian.ShowCrossings=", DoubleQuoteStr(Donchian.ShowCrossings) +";"+ NL, + "Donchian.Crossing.Symbol=", DoubleQuoteStr(Donchian.Crossing.Symbol) +";"+ NL, + "Donchian.Crossing.Width=", Donchian.Crossing.Width +";"+ NL, + "Donchian.Crossing.Color=", ColorToStr(Donchian.Crossing.Color) +";"+ NL, + + "ShowChartLegend=", BoolToStr(ShowChartLegend) +";"+ NL, + "MaxBarsBack=", MaxBarsBack +";"+ NL, + + "Signal.onReversal=", BoolToStr(Signal.onReversal) +";"+ NL, + "Signal.onReversal.Types=", DoubleQuoteStr(Signal.onReversal.Types) +";"+ NL, + "Signal.onBreakout=", BoolToStr(Signal.onBreakout) +";"+ NL, + "Signal.onBreakout.Types=", DoubleQuoteStr(Signal.onBreakout.Types) +";"+ NL, + "Signal.Sound.Up=", DoubleQuoteStr(Signal.Sound.Up) +";"+ NL, + "Signal.Sound.Down=", DoubleQuoteStr(Signal.Sound.Down) +";"+ NL, + + "Sound.onChannelWidening=", BoolToStr(Sound.onChannelWidening) +";"+ NL, + "Sound.onNewChannelHigh=", DoubleQuoteStr(Sound.onNewChannelHigh) +";"+ NL, + "Sound.onNewChannelLow=", DoubleQuoteStr(Sound.onNewChannelLow) +";"+ NL, + + "TrackSignalPerformance=", BoolToStr(TrackSignalPerformance) +";"+ NL, + "TrackSignalPerformance.Since=", TimeToStr(TrackSignalPerformance.Since) +";"+ NL, + "TrackSignalPerformance.Symbol=", DoubleQuoteStr(TrackSignalPerformance.Symbol) +";") ); // suppress compiler warnings From 953de6d4e6b0bc80cb8026cbd206243ffd33105c Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Thu, 19 Mar 2026 23:13:24 +0200 Subject: [PATCH 07/40] rename graphic ids to "dot | thin-ring | ring | thick-ring" --- mql40/indicators/ZigZag.mq4 | 23 +++++++++++------------ 1 file changed, 11 insertions(+), 12 deletions(-) diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index a532317f6..3ca5aa3ff 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -62,13 +62,12 @@ * TODO: * - rename ZigZag.MODE_TREND => ZigZag.MODE_COMBINED_TREND * - rename MODE_MERGED_TREND => MODE_COMBINED_TREND - * - rename MODE_SEMAPHORE_OFFSET => MODE_NO_TREND + * - rename MODE_SEMAPHORE_OFFSET => MODE_UNKNOWN_TREND * - rename mergedTrend[] => combinedTrend[] * - convert TrackSignalPerformance.Since to string * - remove debug code * - once finished, update logic in usage locations of icZigZag() * - * - rename Donchian.Crossing.Symbol values to "dot | thin-ring | ring | thick-ring" * - calculate/display ZigZag zero balance projections * - fix triple-crossing at GBPJPY,M5 2023.12.18 00:00, ZigZag(20) * - keep bar status in IsUpperCrossLast() @@ -84,7 +83,7 @@ extern string ___a__________________________ = "=== ZigZag settings ==="; extern int ZigZag.Periods = 40; // lookback periods of the Donchian channel extern int ZigZag.Periods.Step = 0; // step size for parameter stepper via hotkey extern string ZigZag.Type = "Lines* | Semaphores"; // ZigZag lines or reversal points (can be shortened) -extern string ZigZag.Semaphores.Symbol = "dot* | narrow-ring | ring | bold-ring"; +extern string ZigZag.Semaphores.Symbol = "dot* | thin-ring | ring | thick-ring"; extern int ZigZag.Width = 2; extern color ZigZag.Color = Blue; @@ -93,7 +92,7 @@ extern bool Donchian.ShowChannel = true; / extern color Donchian.Channel.UpperColor = Blue; extern color Donchian.Channel.LowerColor = Magenta; extern string Donchian.ShowCrossings = "off | first* | all"; // which channel crossings to display -extern string Donchian.Crossing.Symbol = "dot | narrow-ring | ring | bold-ring*"; +extern string Donchian.Crossing.Symbol = "dot | thin-ring | ring | thick-ring*"; extern int Donchian.Crossing.Width = 1; extern color Donchian.Crossing.Color = CLR_NONE; @@ -393,10 +392,10 @@ int onInit() { sValue = sValues[size-1]; } sValue = StrToLower(StrTrim(sValue)); - if (sValue == "dot" ) zigzagSymbol = 108; // that's Wingding characters - else if (sValue == "narrow-ring") zigzagSymbol = 161; // ... - else if (sValue == "ring" ) zigzagSymbol = 162; // ... - else if (sValue == "bold-ring" ) zigzagSymbol = 163; // ... + if (sValue == "dot" ) zigzagSymbol = 108; // that's Wingding characters + else if (sValue == "thin-ring" ) zigzagSymbol = 161; // ... + else if (sValue == "ring" ) zigzagSymbol = 162; // ... + else if (sValue == "thick-ring") zigzagSymbol = 163; // ... else return(catch("onInit(4) invalid input parameter ZigZag.Semaphores.Symbol: "+ DoubleQuoteStr(ZigZag.Semaphores.Symbol), ERR_INVALID_INPUT_PARAMETER)); ZigZag.Semaphores.Symbol = sValue; // ZigZag.Width @@ -425,10 +424,10 @@ int onInit() { sValue = sValues[size-1]; } sValue = StrToLower(StrTrim(sValue)); - if (sValue == "dot" ) crossingSymbol = 108; // that's Wingding characters - else if (sValue == "narrow-ring") crossingSymbol = 161; // ... - else if (sValue == "ring" ) crossingSymbol = 162; // ... - else if (sValue == "bold-ring" ) crossingSymbol = 163; // ... + if (sValue == "dot" ) crossingSymbol = 108; // that's Wingding characters + else if (sValue == "thin-ring" ) crossingSymbol = 161; // ... + else if (sValue == "ring" ) crossingSymbol = 162; // ... + else if (sValue == "thick-ring") crossingSymbol = 163; // ... else return(catch("onInit(7) invalid input parameter Donchian.Crossing.Symbol: "+ DoubleQuoteStr(Donchian.Crossing.Symbol), ERR_INVALID_INPUT_PARAMETER)); Donchian.Crossing.Symbol = sValue; // Donchian.Crossing.Width From 07cbe5606bb8a85ca01bc4db08f857dc1ee5566d Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Thu, 19 Mar 2026 23:20:32 +0200 Subject: [PATCH 08/40] rename const ZigZag.MODE_TREND to ZigZag.MODE_COMBINED_TREND --- mql40/experts/ZigZag EA.mq4 | 2 +- mql40/include/rsf/functions/iCustom/ZigZag.mqh | 2 +- mql40/indicators/ZigZag.mq4 | 3 +-- 3 files changed, 3 insertions(+), 4 deletions(-) diff --git a/mql40/experts/ZigZag EA.mq4 b/mql40/experts/ZigZag EA.mq4 index 25a1b7eb1..3b3f2ab06 100644 --- a/mql40/experts/ZigZag EA.mq4 +++ b/mql40/experts/ZigZag EA.mq4 @@ -471,7 +471,7 @@ bool GetZigZagData(int bar, int &trend, int &reversalOffset, double &reversalPri // TODO: 56% of the EA's total runtime is spent in this function - trend = MathRound(icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_TREND, bar)); // 88% of the local time + trend = MathRound(icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_COMBINED_TREND, bar)); // 88% of the local time reversalOffset = MathRound(icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_REVERSAL_OFFSET, bar)); // 6% of the local time if (trend > 0) reversalPrice = icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_UPPER_CROSS, bar); // 6% of the local time diff --git a/mql40/include/rsf/functions/iCustom/ZigZag.mqh b/mql40/include/rsf/functions/iCustom/ZigZag.mqh index 41b2b7fbd..cbfe432f8 100644 --- a/mql40/include/rsf/functions/iCustom/ZigZag.mqh +++ b/mql40/include/rsf/functions/iCustom/ZigZag.mqh @@ -6,7 +6,7 @@ #define ZigZag.MODE_UPPER_CROSS 4 // upper channel band crossings: positive or 0 #define ZigZag.MODE_LOWER_CROSS 5 // lower channel band crossings: positive or 0 #define ZigZag.MODE_REVERSAL_OFFSET 6 // int: offset of the ZigZag reversal to the leg's start semaphore: non-negative or -1 -#define ZigZag.MODE_TREND 7 // int: merged internal buffers MODE_TREND & MODE_SEMAPHORE_OFFSET: positive/negative or 0 +#define ZigZag.MODE_COMBINED_TREND 7 // int: merged internal buffers MODE_TREND & MODE_SEMAPHORE_OFFSET: positive/negative or 0 /** diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index 3ca5aa3ff..3aa0e27f5 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -60,7 +60,6 @@ * * * TODO: - * - rename ZigZag.MODE_TREND => ZigZag.MODE_COMBINED_TREND * - rename MODE_MERGED_TREND => MODE_COMBINED_TREND * - rename MODE_SEMAPHORE_OFFSET => MODE_UNKNOWN_TREND * - rename mergedTrend[] => combinedTrend[] @@ -146,7 +145,7 @@ bool ProjectNextBalance = false; // whether to project zero-balance #define MODE_UPPER_CROSS ZigZag.MODE_UPPER_CROSS // 4: upper channel band crossings: positive or 0 #define MODE_LOWER_CROSS ZigZag.MODE_LOWER_CROSS // 5: lower channel band crossings: positive or 0 #define MODE_REVERSAL_OFFSET ZigZag.MODE_REVERSAL_OFFSET // 6: int: offset of the ZigZag reversal to the leg's start semaphore: non-negative or -1 -#define MODE_MERGED_TREND ZigZag.MODE_TREND // 7: int: merged buffers MODE_TREND & MODE_SEMAPHORE_OFFSET: positive/negative or 0 +#define MODE_MERGED_TREND ZigZag.MODE_COMBINED_TREND // 7: int: merged buffers MODE_TREND & MODE_SEMAPHORE_OFFSET: positive/negative or 0 #define MODE_UPPER_CROSS_HIGH 8 // 8: bar High of an upper channel band crossing: positive or 0 #define MODE_LOWER_CROSS_LOW 9 // 9: bar Low of a lower channel band crossing: positive or 0 #define MODE_SEMAPHORE_OFFSET 10 // 10: int: offset of the current bar to the leg's end semaphore: non-negative or -1 From 22d312cb86d16fd097c463b033eea08f81e573b0 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Fri, 20 Mar 2026 00:16:42 +0200 Subject: [PATCH 09/40] rename const MODE_MERGED_TREND to MODE_COMBINED_TREND --- mql40/indicators/ZigZag.mq4 | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index 3aa0e27f5..4d1ee1ff2 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -60,7 +60,6 @@ * * * TODO: - * - rename MODE_MERGED_TREND => MODE_COMBINED_TREND * - rename MODE_SEMAPHORE_OFFSET => MODE_UNKNOWN_TREND * - rename mergedTrend[] => combinedTrend[] * - convert TrackSignalPerformance.Since to string @@ -145,7 +144,7 @@ bool ProjectNextBalance = false; // whether to project zero-balance #define MODE_UPPER_CROSS ZigZag.MODE_UPPER_CROSS // 4: upper channel band crossings: positive or 0 #define MODE_LOWER_CROSS ZigZag.MODE_LOWER_CROSS // 5: lower channel band crossings: positive or 0 #define MODE_REVERSAL_OFFSET ZigZag.MODE_REVERSAL_OFFSET // 6: int: offset of the ZigZag reversal to the leg's start semaphore: non-negative or -1 -#define MODE_MERGED_TREND ZigZag.MODE_COMBINED_TREND // 7: int: merged buffers MODE_TREND & MODE_SEMAPHORE_OFFSET: positive/negative or 0 +#define MODE_COMBINED_TREND ZigZag.MODE_COMBINED_TREND // 7: int: merged buffers MODE_TREND & MODE_SEMAPHORE_OFFSET: positive/negative or 0 #define MODE_UPPER_CROSS_HIGH 8 // 8: bar High of an upper channel band crossing: positive or 0 #define MODE_LOWER_CROSS_LOW 9 // 9: bar Low of a lower channel band crossing: positive or 0 #define MODE_SEMAPHORE_OFFSET 10 // 10: int: offset of the current bar to the leg's end semaphore: non-negative or -1 @@ -1546,7 +1545,7 @@ bool SetIndicatorOptions(bool redraw = false) { SetIndexBuffer(MODE_UPPER_CROSS, upperCross ); SetIndexEmptyValue(MODE_UPPER_CROSS, 0); SetIndexLabel(MODE_UPPER_CROSS, shortName +" reversal up"); if (!crossingDrawType) SetIndexLabel(MODE_UPPER_CROSS, NULL); SetIndexBuffer(MODE_LOWER_CROSS, lowerCross ); SetIndexEmptyValue(MODE_LOWER_CROSS, 0); SetIndexLabel(MODE_LOWER_CROSS, shortName +" reversal down"); if (!crossingDrawType) SetIndexLabel(MODE_LOWER_CROSS, NULL); SetIndexBuffer(MODE_REVERSAL_OFFSET, reversalOffset); SetIndexEmptyValue(MODE_REVERSAL_OFFSET, -1); SetIndexLabel(MODE_REVERSAL_OFFSET, shortName +" reversal offset"); - SetIndexBuffer(MODE_MERGED_TREND, mergedTrend ); SetIndexEmptyValue(MODE_MERGED_TREND, 0); SetIndexLabel(MODE_MERGED_TREND, shortName +" trend"); + SetIndexBuffer(MODE_COMBINED_TREND, mergedTrend ); SetIndexEmptyValue(MODE_COMBINED_TREND, 0); SetIndexLabel(MODE_COMBINED_TREND, shortName +" trend"); IndicatorDigits(Digits); int drawType = ifInt(ZigZag.Width, zigzagDrawType, DRAW_NONE); @@ -1564,7 +1563,7 @@ bool SetIndicatorOptions(bool redraw = false) { SetIndexStyle(MODE_LOWER_CROSS, drawType, EMPTY, drawWidth, colorOr(Donchian.Crossing.Color, Donchian.Channel.LowerColor)); SetIndexArrow(MODE_LOWER_CROSS, crossingSymbol); SetIndexStyle(MODE_REVERSAL_OFFSET, DRAW_NONE); - SetIndexStyle(MODE_MERGED_TREND, DRAW_NONE); + SetIndexStyle(MODE_COMBINED_TREND, DRAW_NONE); if (redraw) WindowRedraw(); return(!catch("SetIndicatorOptions(1)")); From a4da169b21166f4132f0ff543579cfa6513c00ca Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Fri, 20 Mar 2026 00:30:14 +0200 Subject: [PATCH 10/40] rename const MODE_SEMAPHORE_OFFSET to MODE_UNKNOWN_TREND --- .../include/rsf/functions/iCustom/ZigZag.mqh | 2 +- mql40/indicators/ZigZag.mq4 | 43 +++++++++---------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/mql40/include/rsf/functions/iCustom/ZigZag.mqh b/mql40/include/rsf/functions/iCustom/ZigZag.mqh index cbfe432f8..e168f089b 100644 --- a/mql40/include/rsf/functions/iCustom/ZigZag.mqh +++ b/mql40/include/rsf/functions/iCustom/ZigZag.mqh @@ -6,7 +6,7 @@ #define ZigZag.MODE_UPPER_CROSS 4 // upper channel band crossings: positive or 0 #define ZigZag.MODE_LOWER_CROSS 5 // lower channel band crossings: positive or 0 #define ZigZag.MODE_REVERSAL_OFFSET 6 // int: offset of the ZigZag reversal to the leg's start semaphore: non-negative or -1 -#define ZigZag.MODE_COMBINED_TREND 7 // int: merged internal buffers MODE_TREND & MODE_SEMAPHORE_OFFSET: positive/negative or 0 +#define ZigZag.MODE_COMBINED_TREND 7 // int: combined internal buffers MODE_TREND & MODE_UNKNOWN_TREND: positive/negative or 0 /** diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index 4d1ee1ff2..931543363 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -60,7 +60,6 @@ * * * TODO: - * - rename MODE_SEMAPHORE_OFFSET => MODE_UNKNOWN_TREND * - rename mergedTrend[] => combinedTrend[] * - convert TrackSignalPerformance.Since to string * - remove debug code @@ -144,11 +143,11 @@ bool ProjectNextBalance = false; // whether to project zero-balance #define MODE_UPPER_CROSS ZigZag.MODE_UPPER_CROSS // 4: upper channel band crossings: positive or 0 #define MODE_LOWER_CROSS ZigZag.MODE_LOWER_CROSS // 5: lower channel band crossings: positive or 0 #define MODE_REVERSAL_OFFSET ZigZag.MODE_REVERSAL_OFFSET // 6: int: offset of the ZigZag reversal to the leg's start semaphore: non-negative or -1 -#define MODE_COMBINED_TREND ZigZag.MODE_COMBINED_TREND // 7: int: merged buffers MODE_TREND & MODE_SEMAPHORE_OFFSET: positive/negative or 0 +#define MODE_COMBINED_TREND ZigZag.MODE_COMBINED_TREND // 7: int: merged buffers MODE_TREND & MODE_UNKNOWN_TREND: positive/negative or 0 #define MODE_UPPER_CROSS_HIGH 8 // 8: bar High of an upper channel band crossing: positive or 0 #define MODE_LOWER_CROSS_LOW 9 // 9: bar Low of a lower channel band crossing: positive or 0 -#define MODE_SEMAPHORE_OFFSET 10 // 10: int: offset of the current bar to the leg's end semaphore: non-negative or -1 -#define MODE_TREND 11 // 11: int: direction and length of a ZigZag leg: positive/negative or 0 +#define MODE_TREND 10 // 10: int: length of a ZigZag leg: positive/negative or 0 +#define MODE_UNKNOWN_TREND 11 // 11: int: number of bars after a leg's end semaphore: non-negative or -1 #define MODE_SIGNAL_PERFORMANCE 12 // 12: accumulated signal performance in price units: positive/negative or EMPTY_VALUE #property indicator_chart_window @@ -170,7 +169,7 @@ int framework_buffers = 5; // buffers manage #property indicator_color6 indicator_color4 // lower channel band crossings #property indicator_width6 0 // -#property indicator_color7 CLR_NONE // trend (merged buffers MODE_TREND & MODE_SEMAPHORE_OFFSET) +#property indicator_color7 CLR_NONE // trend (merged buffers MODE_TREND & MODE_UNKNOWN_TREND) #property indicator_color8 CLR_NONE // offset of the previous ZigZag reversal to its preceeding semaphore double upperBand []; // upper channel band: positive or 0 @@ -182,9 +181,9 @@ double lowerCrossLow []; // bar Low of a l double semaphoreOpen []; // final semaphore, open price: positive or 0 double semaphoreClose []; // final semaphore, close price: positive or 0 (if open != close it creates a vertical line segment) double reversalOffset []; // int: offset of the ZigZag reversal to the leg's start semaphore (): non-negative or -1 -int semaphoreOffset []; // int: offset of the current to the leg's end semaphore: non-negative or -1 -int trend []; // int: direction and length of a ZigZag leg: positive/negative or 0 -double mergedTrend []; // int: merged buffers MODE_TREND & MODE_SEMAPHORE_OFFSET: positive/negative or 0 +int trend []; // int: length of a ZigZag leg: positive/negative or 0 +int semaphoreOffset []; // int: number of bars after a leg's end semaphore: non-negative or -1 +double mergedTrend []; // int: merged buffers MODE_TREND & MODE_UNKNOWN_TREND: positive/negative or 0 double signalPerformance[]; // accumulated signal performance in price units: positive/negative or EMPTY_VALUE string indicatorName = ""; @@ -255,8 +254,8 @@ int lastSoundSignal; // GetTickCount() // double semaphoreOpen : 0 (default) // double semaphoreClose : 0 (default) // int reversalOffset : -1 (default) -// int semaphoreOffset: -1 (default) // int trend : 0 (default) +// int semaphoreOffset: -1 (default) // // • Bar between MaxBarsBack and first semaphore // double upperBand : positive @@ -268,8 +267,8 @@ int lastSoundSignal; // GetTickCount() // double semaphoreOpen : 0 (default) // double semaphoreClose : 0 (default) // int reversalOffset : -1 (default) -// int semaphoreOffset: -1 (default) before any cross, otherwise non-negative // int trend : 0 (default) +// int semaphoreOffset: -1 (default) before the last cross, otherwise non-negative // // • Bar of ZigZag leg up // double upperBand : positive @@ -281,8 +280,8 @@ int lastSoundSignal; // GetTickCount() // double semaphoreOpen : 0 (default) // double semaphoreClose : 0 (default) // int reversalOffset : -1 (default) before the reversal, otherwise positive -// int semaphoreOffset: -1 (default) before the reversal, otherwise non-negative // int trend : positive +// int semaphoreOffset: -1 (default) before the last cross, otherwise non-negative // // • Bar of ZigZag leg down // double upperBand : positive @@ -294,8 +293,8 @@ int lastSoundSignal; // GetTickCount() // double semaphoreOpen : 0 (default) // double semaphoreClose : 0 (default) // int reversalOffset : -1 (default) before the reversal, otherwise positive -// int semaphoreOffset: -1 (default) before the reversal, otherwise non-negative // int trend : negative +// int semaphoreOffset: -1 (default) before the last cross, otherwise non-negative // // • High semaphore bar // double upperBand : positive @@ -307,8 +306,8 @@ int lastSoundSignal; // GetTickCount() // double semaphoreOpen : positive // double semaphoreClose : positive (open = close) // int reversalOffset : positive (previous reversal offset) -// int semaphoreOffset: 0 // int trend : positive (previous trend length) +// int semaphoreOffset: 0 // // • Low semaphore bar // double upperBand : positive @@ -320,8 +319,8 @@ int lastSoundSignal; // GetTickCount() // double semaphoreOpen : positive // double semaphoreClose : positive (open == close) // int reversalOffset : positive (previous reversal offset) -// int semaphoreOffset: 0 // int trend : negative (previous trend length) +// int semaphoreOffset: 0 // // • Double crossing bar (high+low semaphore) after processing of the 2nd crossing // double upperBand : positive @@ -333,8 +332,8 @@ int lastSoundSignal; // GetTickCount() // double semaphoreOpen : positive // double semaphoreClose : positive (open != close) // int reversalOffset : 0 (the previous reversal occurred on the same bar) -// int semaphoreOffset: 0 (same as reversal offset) // int trend : 0 (the whole last trend ocurred on the same bar) +// int semaphoreOffset: 0 (same as reversal offset) // // • Triple+ crossing bar (more than two semaphores) ??? // @@ -556,10 +555,10 @@ int onTick() { } // manage additional fraemwork buffers - ManageDoubleIndicatorBuffer(MODE_UPPER_CROSS_HIGH, upperCrossHigh); - ManageDoubleIndicatorBuffer(MODE_LOWER_CROSS_LOW, lowerCrossLow); - ManageIntIndicatorBuffer (MODE_SEMAPHORE_OFFSET, semaphoreOffset, -1); - ManageIntIndicatorBuffer (MODE_TREND, trend); + ManageDoubleIndicatorBuffer(MODE_UPPER_CROSS_HIGH, upperCrossHigh ); + ManageDoubleIndicatorBuffer(MODE_LOWER_CROSS_LOW, lowerCrossLow ); + ManageIntIndicatorBuffer (MODE_TREND, trend ); + ManageIntIndicatorBuffer (MODE_UNKNOWN_TREND, semaphoreOffset, -1); ManageDoubleIndicatorBuffer(MODE_SIGNAL_PERFORMANCE, signalPerformance, EMPTY_VALUE); // reset buffers before performing a full recalculation @@ -573,8 +572,8 @@ int onTick() { ArrayInitialize(semaphoreOpen, 0); // double: positive or 0 ArrayInitialize(semaphoreClose, 0); // double: positive or 0 ArrayInitialize(reversalOffset, -1); // int: non-negative or -1 - ArrayInitialize(semaphoreOffset, -1); // int: non-negative or -1 ArrayInitialize(trend, 0); // int: positive/negative or 0 + ArrayInitialize(semaphoreOffset, -1); // int: non-negative or -1 ArrayInitialize(mergedTrend, 0); // int: positive/negative or 0 ArrayInitialize(signalPerformance, EMPTY_VALUE); // double: positive/negative or EMPTY_VALUE lastUpperBand = 0; @@ -598,8 +597,8 @@ int onTick() { ShiftDoubleIndicatorBuffer(semaphoreOpen, Bars, ShiftedBars, 0); ShiftDoubleIndicatorBuffer(semaphoreClose, Bars, ShiftedBars, 0); ShiftDoubleIndicatorBuffer(reversalOffset, Bars, ShiftedBars, -1); - ShiftIntIndicatorBuffer (semaphoreOffset, Bars, ShiftedBars, -1); ShiftIntIndicatorBuffer (trend, Bars, ShiftedBars, 0); + ShiftIntIndicatorBuffer (semaphoreOffset, Bars, ShiftedBars, -1); ShiftDoubleIndicatorBuffer(mergedTrend, Bars, ShiftedBars, 0); ShiftDoubleIndicatorBuffer(signalPerformance, Bars, ShiftedBars, EMPTY_VALUE); } @@ -622,8 +621,8 @@ int onTick() { semaphoreOpen [startBar] = 0; semaphoreClose [startBar] = 0; reversalOffset [startBar] = -1; - semaphoreOffset [startBar] = -1; trend [startBar] = 0; + semaphoreOffset [startBar] = -1; mergedTrend [startBar] = 0; signalPerformance[startBar] = EMPTY_VALUE; } From faf3970c105a9668865ba2bd97fa137f7e25f23d Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Fri, 20 Mar 2026 00:41:35 +0200 Subject: [PATCH 11/40] rename buffer semaphoreOffset[] to unknownTrend[] --- mql40/indicators/ZigZag.mq4 | 260 ++++++++++++++++++------------------ 1 file changed, 130 insertions(+), 130 deletions(-) diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index 931543363..32c0ecc4b 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -182,7 +182,7 @@ double semaphoreOpen []; // final semaphor double semaphoreClose []; // final semaphore, close price: positive or 0 (if open != close it creates a vertical line segment) double reversalOffset []; // int: offset of the ZigZag reversal to the leg's start semaphore (): non-negative or -1 int trend []; // int: length of a ZigZag leg: positive/negative or 0 -int semaphoreOffset []; // int: number of bars after a leg's end semaphore: non-negative or -1 +int unknownTrend []; // int: number of bars after a leg's end semaphore: non-negative or -1 double mergedTrend []; // int: merged buffers MODE_TREND & MODE_UNKNOWN_TREND: positive/negative or 0 double signalPerformance[]; // accumulated signal performance in price units: positive/negative or EMPTY_VALUE @@ -245,95 +245,95 @@ int lastSoundSignal; // GetTickCount() // Buffer contents of the possible bar types // ----------------------------------------- // • Bar before MaxBarsBack -// double upperBand : 0 (default) -// double lowerBand : 0 (default) -// double upperCross : 0 (default) -// double upperCrossHigh : 0 (default) -// double lowerCross : 0 (default) -// double lowerCrossLow : 0 (default) -// double semaphoreOpen : 0 (default) -// double semaphoreClose : 0 (default) -// int reversalOffset : -1 (default) -// int trend : 0 (default) -// int semaphoreOffset: -1 (default) +// double upperBand : 0 (default) +// double lowerBand : 0 (default) +// double upperCross : 0 (default) +// double upperCrossHigh: 0 (default) +// double lowerCross : 0 (default) +// double lowerCrossLow : 0 (default) +// double semaphoreOpen : 0 (default) +// double semaphoreClose: 0 (default) +// int reversalOffset: -1 (default) +// int trend : 0 (default) +// int unknownTrend : -1 (default) // // • Bar between MaxBarsBack and first semaphore -// double upperBand : positive -// double lowerBand : positive -// double upperCross : 0 (default) or positive (only upper or lower values may be set, not both) -// double upperCrossHigh : 0 (default) or positive -// double lowerCross : 0 (default) or positive -// double lowerCrossLow : 0 (default) or positive -// double semaphoreOpen : 0 (default) -// double semaphoreClose : 0 (default) -// int reversalOffset : -1 (default) -// int trend : 0 (default) -// int semaphoreOffset: -1 (default) before the last cross, otherwise non-negative +// double upperBand : positive +// double lowerBand : positive +// double upperCross : 0 (default) or positive (only upper or lower values may be set, not both) +// double upperCrossHigh: 0 (default) or positive +// double lowerCross : 0 (default) or positive +// double lowerCrossLow : 0 (default) or positive +// double semaphoreOpen : 0 (default) +// double semaphoreClose: 0 (default) +// int reversalOffset: -1 (default) +// int trend : 0 (default) +// int unknownTrend : -1 (default) before the last cross, otherwise non-negative // // • Bar of ZigZag leg up -// double upperBand : positive -// double lowerBand : positive -// double upperCross : 0 (default) or positive -// double upperCrossHigh : 0 (default) or positive -// double lowerCross : 0 (default) -// double lowerCrossLow : 0 (default) -// double semaphoreOpen : 0 (default) -// double semaphoreClose : 0 (default) -// int reversalOffset : -1 (default) before the reversal, otherwise positive -// int trend : positive -// int semaphoreOffset: -1 (default) before the last cross, otherwise non-negative +// double upperBand : positive +// double lowerBand : positive +// double upperCross : 0 (default) or positive +// double upperCrossHigh: 0 (default) or positive +// double lowerCross : 0 (default) +// double lowerCrossLow : 0 (default) +// double semaphoreOpen : 0 (default) +// double semaphoreClose: 0 (default) +// int reversalOffset: -1 (default) before the reversal, otherwise positive +// int trend : positive +// int unknownTrend : -1 (default) before the last cross, otherwise non-negative // // • Bar of ZigZag leg down -// double upperBand : positive -// double lowerBand : positive -// double upperCross : 0 (default) -// double upperCrossHigh : 0 (default) -// double lowerCross : 0 (default) or positive -// double lowerCrossLow : 0 (default) or positive -// double semaphoreOpen : 0 (default) -// double semaphoreClose : 0 (default) -// int reversalOffset : -1 (default) before the reversal, otherwise positive -// int trend : negative -// int semaphoreOffset: -1 (default) before the last cross, otherwise non-negative +// double upperBand : positive +// double lowerBand : positive +// double upperCross : 0 (default) +// double upperCrossHigh: 0 (default) +// double lowerCross : 0 (default) or positive +// double lowerCrossLow : 0 (default) or positive +// double semaphoreOpen : 0 (default) +// double semaphoreClose: 0 (default) +// int reversalOffset: -1 (default) before the reversal, otherwise positive +// int trend : negative +// int unknownTrend : -1 (default) before the last cross, otherwise non-negative // // • High semaphore bar -// double upperBand : positive -// double lowerBand : positive -// double upperCross : positive -// double upperCrossHigh : positive -// double lowerCross : 0 (default) -// double lowerCrossLow : 0 (default) -// double semaphoreOpen : positive -// double semaphoreClose : positive (open = close) -// int reversalOffset : positive (previous reversal offset) -// int trend : positive (previous trend length) -// int semaphoreOffset: 0 +// double upperBand : positive +// double lowerBand : positive +// double upperCross : positive +// double upperCrossHigh: positive +// double lowerCross : 0 (default) +// double lowerCrossLow : 0 (default) +// double semaphoreOpen : positive +// double semaphoreClose: positive (open = close) +// int reversalOffset: positive (previous reversal offset) +// int trend : positive (previous trend length) +// int unknownTrend : 0 // // • Low semaphore bar -// double upperBand : positive -// double lowerBand : positive -// double upperCross : 0 (default) -// double upperCrossHigh : 0 (default) -// double lowerCross : positive -// double lowerCrossLow : positive -// double semaphoreOpen : positive -// double semaphoreClose : positive (open == close) -// int reversalOffset : positive (previous reversal offset) -// int trend : negative (previous trend length) -// int semaphoreOffset: 0 +// double upperBand : positive +// double lowerBand : positive +// double upperCross : 0 (default) +// double upperCrossHigh: 0 (default) +// double lowerCross : positive +// double lowerCrossLow : positive +// double semaphoreOpen : positive +// double semaphoreClose: positive (open == close) +// int reversalOffset: positive (previous reversal offset) +// int trend : negative (previous trend length) +// int unknownTrend : 0 // // • Double crossing bar (high+low semaphore) after processing of the 2nd crossing -// double upperBand : positive -// double lowerBand : positive -// double upperCross : positive -// double upperCrossHigh : positive -// double lowerCross : positive -// double lowerCrossLow : positive -// double semaphoreOpen : positive -// double semaphoreClose : positive (open != close) -// int reversalOffset : 0 (the previous reversal occurred on the same bar) -// int trend : 0 (the whole last trend ocurred on the same bar) -// int semaphoreOffset: 0 (same as reversal offset) +// double upperBand : positive +// double lowerBand : positive +// double upperCross : positive +// double upperCrossHigh: positive +// double lowerCross : positive +// double lowerCrossLow : positive +// double semaphoreOpen : positive +// double semaphoreClose: positive (open != close) +// int reversalOffset: 0 (the previous reversal occurred on the same bar) +// int trend : 0 (the whole last trend ocurred on the same bar) +// int unknownTrend : 0 (same as reversal offset) // // • Triple+ crossing bar (more than two semaphores) ??? // @@ -555,10 +555,10 @@ int onTick() { } // manage additional fraemwork buffers - ManageDoubleIndicatorBuffer(MODE_UPPER_CROSS_HIGH, upperCrossHigh ); - ManageDoubleIndicatorBuffer(MODE_LOWER_CROSS_LOW, lowerCrossLow ); - ManageIntIndicatorBuffer (MODE_TREND, trend ); - ManageIntIndicatorBuffer (MODE_UNKNOWN_TREND, semaphoreOffset, -1); + ManageDoubleIndicatorBuffer(MODE_UPPER_CROSS_HIGH, upperCrossHigh ); + ManageDoubleIndicatorBuffer(MODE_LOWER_CROSS_LOW, lowerCrossLow ); + ManageIntIndicatorBuffer (MODE_TREND, trend ); + ManageIntIndicatorBuffer (MODE_UNKNOWN_TREND, unknownTrend, -1); ManageDoubleIndicatorBuffer(MODE_SIGNAL_PERFORMANCE, signalPerformance, EMPTY_VALUE); // reset buffers before performing a full recalculation @@ -573,7 +573,7 @@ int onTick() { ArrayInitialize(semaphoreClose, 0); // double: positive or 0 ArrayInitialize(reversalOffset, -1); // int: non-negative or -1 ArrayInitialize(trend, 0); // int: positive/negative or 0 - ArrayInitialize(semaphoreOffset, -1); // int: non-negative or -1 + ArrayInitialize(unknownTrend, -1); // int: non-negative or -1 ArrayInitialize(mergedTrend, 0); // int: positive/negative or 0 ArrayInitialize(signalPerformance, EMPTY_VALUE); // double: positive/negative or EMPTY_VALUE lastUpperBand = 0; @@ -598,7 +598,7 @@ int onTick() { ShiftDoubleIndicatorBuffer(semaphoreClose, Bars, ShiftedBars, 0); ShiftDoubleIndicatorBuffer(reversalOffset, Bars, ShiftedBars, -1); ShiftIntIndicatorBuffer (trend, Bars, ShiftedBars, 0); - ShiftIntIndicatorBuffer (semaphoreOffset, Bars, ShiftedBars, -1); + ShiftIntIndicatorBuffer (unknownTrend, Bars, ShiftedBars, -1); ShiftDoubleIndicatorBuffer(mergedTrend, Bars, ShiftedBars, 0); ShiftDoubleIndicatorBuffer(signalPerformance, Bars, ShiftedBars, EMPTY_VALUE); } @@ -622,7 +622,7 @@ int onTick() { semaphoreClose [startBar] = 0; reversalOffset [startBar] = -1; trend [startBar] = 0; - semaphoreOffset [startBar] = -1; + unknownTrend [startBar] = -1; mergedTrend [startBar] = 0; signalPerformance[startBar] = EMPTY_VALUE; } @@ -651,11 +651,11 @@ int onTick() { // --- start: recalculate ZigZag data --- // if no channel crossing // before or after the first semaphore if (!upperCross[bar] && !lowerCross[bar]) { - trend [bar] = trend [bar+1]; // keep trend (may be 0) - reversalOffset [bar] = reversalOffset [bar+1]; // keep reversal offset (may be -1) - semaphoreOffset[bar] = semaphoreOffset[bar+1]; // get previous semaphore offset - if (semaphoreOffset[bar] > -1) { - semaphoreOffset[bar]++; // increase if it was set + reversalOffset[bar] = reversalOffset[bar+1]; // keep reversal offset (may be -1) + trend [bar] = trend [bar+1]; // keep trend (may be 0) + unknownTrend [bar] = unknownTrend [bar+1]; // get previous unknown trend + if (unknownTrend[bar] > -1) { + unknownTrend[bar]++; // increase if it was set } } @@ -678,7 +678,7 @@ int onTick() { // whether the processed bar is a reversal bar (not whether the current tick triggered a reversal) bool isReversalBar = false; - if (!semaphoreOffset[bar]) { + if (!unknownTrend[bar]) { isReversalBar = (Abs(trend[bar]) == reversalOffset[bar]); } @@ -686,7 +686,7 @@ int onTick() { if (debugging && Ticks == 1) { if (Time[bar] >= devFrom && Time[bar] <= devTo) { - debug("onTick(0.1) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" trend="+ trend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" semaphoreOffset="+ semaphoreOffset[bar]); + debug("onTick(0.1) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" trend="+ trend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" unknownTrend="+ unknownTrend[bar]); if (isReversalBar) { debug("onTick(0.2) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" isReversalBar=1"); } @@ -760,7 +760,7 @@ int onTick() { } // compose mergedTrend[] - mergedTrend[bar] = Sign(trend[bar]) * semaphoreOffset[bar] * 100000 + trend[bar]; + mergedTrend[bar] = Sign(trend[bar]) * unknownTrend[bar] * 100000 + trend[bar]; } @@ -886,13 +886,13 @@ int onTick() { if (lastLegHigh < sema2+HalfPoint && upperBand[0] > sema2+HalfPoint) { onBreakout(D_LONG); } - lastLegHigh = High[semaphoreOffset[0]]; // leg high for comparison at the nex tick + lastLegHigh = High[unknownTrend[0]]; // leg high for comparison at the nex tick } else if (trend[0] < 0) { if ((!lastLegLow || lastLegLow > sema2-HalfPoint) && lowerBand[0] < sema2-HalfPoint) { onBreakout(D_SHORT); } - lastLegLow = Low[semaphoreOffset[0]]; // leg low for comparison at the nex tick + lastLegLow = Low[unknownTrend[0]]; // leg low for comparison at the nex tick } } } @@ -982,8 +982,8 @@ int FindSemaphore(int bar, int &resultType, int skipType = NULL) { if (!semaphoreClose[bar]) { // semaphore is located somewhere before bar++; } - if (!semaphoreClose[bar] && semaphoreOffset[bar] > 0) { // navigate to the end semaphore (if any), - bar += semaphoreOffset[bar]; // a current bar has never semaphoreOffset=-1 + if (!semaphoreClose[bar] && unknownTrend[bar] > 0) { // navigate to the end semaphore (if any), + bar += unknownTrend[bar]; // a current bar has never unknownTrend=-1 } if (!semaphoreClose[bar] && trend[bar]) { // navigate to the start semaphore (if any) bar += Abs(trend[bar]); @@ -1072,11 +1072,11 @@ bool ProcessUpperCross(int bar) { // an upper cross without a previous semaphore (near MaxBarsBack) if (lastSemBar < 0) { if (!last_error) { - semaphoreOpen [bar] = upperCrossHigh[bar]; // set new semaphore - semaphoreClose [bar] = upperCrossHigh[bar]; - trend [bar] = 0; // no trend - reversalOffset [bar] = -1; // no reversal - semaphoreOffset[bar] = 0; // current bar + semaphoreOpen [bar] = upperCrossHigh[bar]; // set new semaphore + semaphoreClose[bar] = upperCrossHigh[bar]; + reversalOffset[bar] = -1; // no reversal + trend [bar] = 0; // no trend + unknownTrend [bar] = 0; // current bar } return(!last_error); } @@ -1099,14 +1099,14 @@ bool ProcessUpperCross(int bar) { else { semaphoreOpen[lastSemBar] = 0; // reset previous semaphore } - semaphoreClose [lastSemBar] = semaphoreOpen[lastSemBar]; - semaphoreOpen [bar] = upperCrossHigh[bar]; // set new semaphore - semaphoreClose [bar] = upperCrossHigh[bar]; - semaphoreOffset[bar] = 0; // current bar + semaphoreClose[lastSemBar] = semaphoreOpen[lastSemBar]; + semaphoreOpen [bar] = upperCrossHigh[bar]; // set new semaphore + semaphoreClose[bar] = upperCrossHigh[bar]; + unknownTrend [bar] = 0; // current bar } else { // a lower High (unknown direction) - trend [bar] = trend [bar+1]; // keep trend (may be 0) - semaphoreOffset[bar] = semaphoreOffset[bar+1] + 1; // increase semaphore offset + trend [bar] = trend [bar+1]; // keep trend (may be 0) + unknownTrend[bar] = unknownTrend[bar+1] + 1; // increase unknown trend } reversalOffset[bar] = reversalOffset[bar+1]; // keep reversal offset (may be -1) } @@ -1121,9 +1121,9 @@ bool ProcessUpperCross(int bar) { SetTrend(lastSemBar-1, 1, bar, true); // set the new trend range, reset reversals semaphoreOpen[bar] = upperCrossHigh[bar]; // set new semaphore } - semaphoreClose [bar] = upperCrossHigh[bar]; - reversalOffset [bar] = lastSemBar - bar; // set new reversal offset - semaphoreOffset[bar] = 0; // current bar + semaphoreClose[bar] = upperCrossHigh[bar]; + reversalOffset[bar] = lastSemBar - bar; // set new reversal offset + unknownTrend [bar] = 0; // current bar sema3 = sema2; // update the last 3 semaphores sema2 = sema1; @@ -1160,11 +1160,11 @@ bool ProcessLowerCross(int bar) { // a lower cross without a previous semaphore (near MaxBarsBack) if (lastSemBar < 0) { if (!last_error) { - semaphoreOpen [bar] = lowerCrossLow[bar]; // set new semaphore - semaphoreClose [bar] = lowerCrossLow[bar]; - trend [bar] = 0; // no trend - reversalOffset [bar] = -1; // no reversal - semaphoreOffset[bar] = 0; // current bar + semaphoreOpen [bar] = lowerCrossLow[bar]; // set new semaphore + semaphoreClose[bar] = lowerCrossLow[bar]; + reversalOffset[bar] = -1; // no reversal + trend [bar] = 0; // no trend + unknownTrend [bar] = 0; // current bar } return(!last_error); } @@ -1187,14 +1187,14 @@ bool ProcessLowerCross(int bar) { else { semaphoreOpen[lastSemBar] = 0; // reset previous semaphore } - semaphoreClose [lastSemBar] = semaphoreOpen[lastSemBar]; - semaphoreOpen [bar] = lowerCrossLow[bar]; // set new semaphore - semaphoreClose [bar] = lowerCrossLow[bar]; - semaphoreOffset[bar] = 0; // current bar + semaphoreClose[lastSemBar] = semaphoreOpen[lastSemBar]; + semaphoreOpen [bar] = lowerCrossLow[bar]; // set new semaphore + semaphoreClose[bar] = lowerCrossLow[bar]; + unknownTrend [bar] = 0; // current bar } else { // a higher Low (unknown direction) - trend [bar] = trend [bar+1]; // keep trend (may be 0) - semaphoreOffset[bar] = semaphoreOffset[bar+1] + 1; // increase semaphore offset + trend [bar] = trend [bar+1]; // keep trend (may be 0) + unknownTrend[bar] = unknownTrend[bar+1] + 1; // increase unknown trend } reversalOffset [bar] = reversalOffset [bar+1]; // keep reversal offset (may be -1) } @@ -1209,9 +1209,9 @@ bool ProcessLowerCross(int bar) { SetTrend(lastSemBar-1, -1, bar, true); // set the new trend range, reset reversals semaphoreOpen[bar] = lowerCrossLow[bar]; // set new semaphore } - semaphoreClose [bar] = lowerCrossLow[bar]; - reversalOffset [bar] = lastSemBar - bar; // set the new reversal offset - semaphoreOffset[bar] = 0; // current bar + semaphoreClose[bar] = lowerCrossLow[bar]; + reversalOffset[bar] = lastSemBar - bar; // set the new reversal offset + unknownTrend [bar] = 0; // current bar sema3 = sema2; // update the last 3 semaphores sema2 = sema1; @@ -1229,7 +1229,7 @@ bool ProcessLowerCross(int bar) { /** * Update the trend[] values of the specified bar range. * If fromValue is non-zero, trend[] values are increased per bar. If fromValue is 0, trend[] values are not increased. - * Also resets all semaphoreOffset[] values and optionally the reversalOffset[] values of the specified range. + * Also resets all unknownTrend[] values and optionally the reversalOffset[] values of the specified range. * * @param int fromBar - start bar of the range to update (older) * @param int fromValue - start value for the trend counter @@ -1241,9 +1241,9 @@ void SetTrend(int fromBar, int fromValue, int toBar, bool resetReversals) { int value = fromValue, sign, cross; for (int i=fromBar; i >= toBar; i--) { - trend [i] = value; - semaphoreOffset[i] = -1; - mergedTrend [i] = trend[i]; + trend [i] = value; + unknownTrend[i] = -1; + mergedTrend [i] = trend[i]; if (resetReversals) reversalOffset[i] = -1; @@ -1497,7 +1497,7 @@ void UpdateChartLegend() { // update on full recalculation or if indicator name, trend, current bar or the account changed if (!ValidBars || mergedTrend[0]!=lastTrend || Time[0]!=lastTime || AccountNumber()!=lastAccount) { string sTrend = " "+ NumberToStr(trend[0], "+."); - string sUnknown = ifString(!semaphoreOffset[0], "", "/"+ semaphoreOffset[0]); + string sUnknown = ifString(!unknownTrend[0], "", "/"+ unknownTrend[0]); string sReversal = " next reversal @" + NumberToStr(ifDouble(trend[0] < 0, upperBand[0]+Point, lowerBand[0]-Point), PriceFormat); string sSignal = ifString(Signal.onReversal, " "+ legendInfo, ""); string text = StringConcatenate(indicatorName, sTrend, sUnknown, sReversal, sSignal); From a078f971ec25923fe58858bd613f1989042df3de Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Fri, 20 Mar 2026 00:46:29 +0200 Subject: [PATCH 12/40] rename buffer mergedTrend[] to combinedTrend[] --- mql40/experts/ZigZag EA.mq4 | 2 +- mql40/indicators/ZigZag.mq4 | 36 +++++++++++++++++------------------- 2 files changed, 18 insertions(+), 20 deletions(-) diff --git a/mql40/experts/ZigZag EA.mq4 b/mql40/experts/ZigZag EA.mq4 index 3b3f2ab06..a34e384d6 100644 --- a/mql40/experts/ZigZag EA.mq4 +++ b/mql40/experts/ZigZag EA.mq4 @@ -461,7 +461,7 @@ bool IsZigZagSignal(double &signal[]) { * Get ZigZag buffer values at the specified bar offset. The returned values correspond to the documented indicator buffers. * * @param _In_ int bar - bar offset - * @param _Out_ int trend - MODE_TREND: merged buffers MODE_KNOWN_TREND & MODE_UNKNOWN_TREND + * @param _Out_ int trend - MODE_TREND: combined buffers MODE_KNOWN_TREND & MODE_UNKNOWN_TREND * @param _Out_ int reversalOffset - MODE_REVERSAL_OFFSET: bar offset of most recent ZigZag reversal to the preceeding ZigZag semaphore * @param _Out_ double reversalPrice - MODE_UPPER_CROSS|MODE_LOWER_CROSS: reversal price if the bar denotes a ZigZag reversal; 0 otherwise * diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index 32c0ecc4b..ca641caa4 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -52,15 +52,13 @@ * • Sound.onNewChannelLow: Sound file for channel widenings to the downside. * * • TrackSignalPerformance: Whether to track the performance of the reversal signal. - * • TrackSignalPerformance.Since: Start time to track signal performance from. - * • TrackSignalPerformance.Symbol: Custom symbol to use for performance tracking. - * - * • AutoConfiguration: If enabled all input parameters can be overwritten with custom default values. + * • TrackSignalPerformance.Since: Start time to track signal performance from (default: MaxBarsBack). + * • TrackSignalPerformance.Symbol: Custom symbol to use for performance tracking (default: auto-generated). * + * • AutoConfiguration: If enabled all input parameters may use predefined defaults from the configuration. * * * TODO: - * - rename mergedTrend[] => combinedTrend[] * - convert TrackSignalPerformance.Since to string * - remove debug code * - once finished, update logic in usage locations of icZigZag() @@ -143,7 +141,7 @@ bool ProjectNextBalance = false; // whether to project zero-balance #define MODE_UPPER_CROSS ZigZag.MODE_UPPER_CROSS // 4: upper channel band crossings: positive or 0 #define MODE_LOWER_CROSS ZigZag.MODE_LOWER_CROSS // 5: lower channel band crossings: positive or 0 #define MODE_REVERSAL_OFFSET ZigZag.MODE_REVERSAL_OFFSET // 6: int: offset of the ZigZag reversal to the leg's start semaphore: non-negative or -1 -#define MODE_COMBINED_TREND ZigZag.MODE_COMBINED_TREND // 7: int: merged buffers MODE_TREND & MODE_UNKNOWN_TREND: positive/negative or 0 +#define MODE_COMBINED_TREND ZigZag.MODE_COMBINED_TREND // 7: int: combined buffers MODE_TREND & MODE_UNKNOWN_TREND: positive/negative or 0 #define MODE_UPPER_CROSS_HIGH 8 // 8: bar High of an upper channel band crossing: positive or 0 #define MODE_LOWER_CROSS_LOW 9 // 9: bar Low of a lower channel band crossing: positive or 0 #define MODE_TREND 10 // 10: int: length of a ZigZag leg: positive/negative or 0 @@ -169,7 +167,7 @@ int framework_buffers = 5; // buffers manage #property indicator_color6 indicator_color4 // lower channel band crossings #property indicator_width6 0 // -#property indicator_color7 CLR_NONE // trend (merged buffers MODE_TREND & MODE_UNKNOWN_TREND) +#property indicator_color7 CLR_NONE // trend (combined buffers MODE_TREND & MODE_UNKNOWN_TREND) #property indicator_color8 CLR_NONE // offset of the previous ZigZag reversal to its preceeding semaphore double upperBand []; // upper channel band: positive or 0 @@ -183,7 +181,7 @@ double semaphoreClose []; // final semaphor double reversalOffset []; // int: offset of the ZigZag reversal to the leg's start semaphore (): non-negative or -1 int trend []; // int: length of a ZigZag leg: positive/negative or 0 int unknownTrend []; // int: number of bars after a leg's end semaphore: non-negative or -1 -double mergedTrend []; // int: merged buffers MODE_TREND & MODE_UNKNOWN_TREND: positive/negative or 0 +double combinedTrend []; // int: combined buffers MODE_TREND & MODE_UNKNOWN_TREND: positive/negative or 0 double signalPerformance[]; // accumulated signal performance in price units: positive/negative or EMPTY_VALUE string indicatorName = ""; @@ -574,7 +572,7 @@ int onTick() { ArrayInitialize(reversalOffset, -1); // int: non-negative or -1 ArrayInitialize(trend, 0); // int: positive/negative or 0 ArrayInitialize(unknownTrend, -1); // int: non-negative or -1 - ArrayInitialize(mergedTrend, 0); // int: positive/negative or 0 + ArrayInitialize(combinedTrend, 0); // int: positive/negative or 0 ArrayInitialize(signalPerformance, EMPTY_VALUE); // double: positive/negative or EMPTY_VALUE lastUpperBand = 0; lastLowerBand = 0; @@ -599,7 +597,7 @@ int onTick() { ShiftDoubleIndicatorBuffer(reversalOffset, Bars, ShiftedBars, -1); ShiftIntIndicatorBuffer (trend, Bars, ShiftedBars, 0); ShiftIntIndicatorBuffer (unknownTrend, Bars, ShiftedBars, -1); - ShiftDoubleIndicatorBuffer(mergedTrend, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(combinedTrend, Bars, ShiftedBars, 0); ShiftDoubleIndicatorBuffer(signalPerformance, Bars, ShiftedBars, EMPTY_VALUE); } @@ -623,7 +621,7 @@ int onTick() { reversalOffset [startBar] = -1; trend [startBar] = 0; unknownTrend [startBar] = -1; - mergedTrend [startBar] = 0; + combinedTrend [startBar] = 0; signalPerformance[startBar] = EMPTY_VALUE; } @@ -759,8 +757,8 @@ int onTick() { } } - // compose mergedTrend[] - mergedTrend[bar] = Sign(trend[bar]) * unknownTrend[bar] * 100000 + trend[bar]; + // compose combinedTrend[] + combinedTrend[bar] = Sign(trend[bar]) * unknownTrend[bar] * 100000 + trend[bar]; } @@ -1241,9 +1239,9 @@ void SetTrend(int fromBar, int fromValue, int toBar, bool resetReversals) { int value = fromValue, sign, cross; for (int i=fromBar; i >= toBar; i--) { - trend [i] = value; - unknownTrend[i] = -1; - mergedTrend [i] = trend[i]; + trend [i] = value; + unknownTrend [i] = -1; + combinedTrend[i] = trend[i]; if (resetReversals) reversalOffset[i] = -1; @@ -1495,7 +1493,7 @@ void UpdateChartLegend() { static int lastTrend, lastTime, lastAccount; // update on full recalculation or if indicator name, trend, current bar or the account changed - if (!ValidBars || mergedTrend[0]!=lastTrend || Time[0]!=lastTime || AccountNumber()!=lastAccount) { + if (!ValidBars || combinedTrend[0]!=lastTrend || Time[0]!=lastTime || AccountNumber()!=lastAccount) { string sTrend = " "+ NumberToStr(trend[0], "+."); string sUnknown = ifString(!unknownTrend[0], "", "/"+ unknownTrend[0]); string sReversal = " next reversal @" + NumberToStr(ifDouble(trend[0] < 0, upperBand[0]+Point, lowerBand[0]-Point), PriceFormat); @@ -1513,7 +1511,7 @@ void UpdateChartLegend() { int error = GetLastError(); if (error && error!=ERR_OBJECT_DOES_NOT_EXIST) catch("UpdateChartLegend(1)", error); // on ObjectDrag or opened "Properties" dialog - lastTrend = mergedTrend[0]; + lastTrend = combinedTrend[0]; lastTime = Time[0]; lastAccount = AccountNumber(); } @@ -1544,7 +1542,7 @@ bool SetIndicatorOptions(bool redraw = false) { SetIndexBuffer(MODE_UPPER_CROSS, upperCross ); SetIndexEmptyValue(MODE_UPPER_CROSS, 0); SetIndexLabel(MODE_UPPER_CROSS, shortName +" reversal up"); if (!crossingDrawType) SetIndexLabel(MODE_UPPER_CROSS, NULL); SetIndexBuffer(MODE_LOWER_CROSS, lowerCross ); SetIndexEmptyValue(MODE_LOWER_CROSS, 0); SetIndexLabel(MODE_LOWER_CROSS, shortName +" reversal down"); if (!crossingDrawType) SetIndexLabel(MODE_LOWER_CROSS, NULL); SetIndexBuffer(MODE_REVERSAL_OFFSET, reversalOffset); SetIndexEmptyValue(MODE_REVERSAL_OFFSET, -1); SetIndexLabel(MODE_REVERSAL_OFFSET, shortName +" reversal offset"); - SetIndexBuffer(MODE_COMBINED_TREND, mergedTrend ); SetIndexEmptyValue(MODE_COMBINED_TREND, 0); SetIndexLabel(MODE_COMBINED_TREND, shortName +" trend"); + SetIndexBuffer(MODE_COMBINED_TREND, combinedTrend ); SetIndexEmptyValue(MODE_COMBINED_TREND, 0); SetIndexLabel(MODE_COMBINED_TREND, shortName +" trend"); IndicatorDigits(Digits); int drawType = ifInt(ZigZag.Width, zigzagDrawType, DRAW_NONE); From 5fb2c5a89da2e1626a4dee26a9f78fe6e7512b95 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Fri, 20 Mar 2026 08:48:11 +0200 Subject: [PATCH 13/40] wip: prepare writing of performance data --- mql40/include/rsf/MT4Expander.mqh | 23 +- mql40/include/rsf/core/expert.recorder.mqh | 2 +- mql40/include/rsf/history.mqh | 12 +- mql40/indicators/Signal Performance.mq4 | 99 +++++++- mql40/indicators/ZigZag.mq4 | 256 ++++++++++++--------- mql40/libraries/rsfHistory1.mq4 | 17 +- mql40/libraries/rsfHistory2.mq4 | 17 +- mql40/libraries/rsfHistory3.mq4 | 17 +- mql45/include/rsf/expander/errors.h | 2 +- 9 files changed, 283 insertions(+), 162 deletions(-) diff --git a/mql40/include/rsf/MT4Expander.mqh b/mql40/include/rsf/MT4Expander.mqh index 21e3761a9..32287cf53 100644 --- a/mql40/include/rsf/MT4Expander.mqh +++ b/mql40/include/rsf/MT4Expander.mqh @@ -1,8 +1,9 @@ /** * MT4Expander import declarations * - * Note: MQL4.0 supports up to 512 arrays per MQL module (in MQL4.5/MQL5 this limit was removed). To prevent hitting that - * limit all imports with array parameters are disabled in this file. Import needed functions per program to use them. + * Note: MQL4.0 supports a maximum of 512 array declarations per MQL module. In MQL4.5 and later, this restriction has been + * removed. To prevent this limit from being reached, all imports with array parameters have been disabled in this file. + * Import needed functions with array parameters manually to use them. */ #import "rsfMT4Expander.dll" @@ -24,7 +25,7 @@ bool IsUIThread(int threadId); bool LoadMqlProgramA(int hChart, int programType, string programName); int MT4InternalMsg(); - bool ReopenAlertDialog(int sound); + bool ReopenAlertDialog(int sound); // see notes at the top of the file //int SyncMainContext_init (int ec[], int programType, string programName, int uninitReason, int initFlags, int deinitFlags, string symbol, int timeframe, int digits, double point, int isTesting, int isVisualMode, int isOptimization, int recorder, int lpSec, int hChart, int droppedOnChart, int droppedOnPosX, int droppedOnPosY, string accountServer, int accountNumber); //int SyncMainContext_start (int ec[], double rates[][], int bars, int changedBars, int ticks, datetime tickTime, int isVirtual, double bid, double ask); //int SyncMainContext_deinit(int ec[], int uninitReason); @@ -53,8 +54,8 @@ bool DeleteIniKeyA(string fileName, string section, string key); bool DeleteIniSectionA(string fileName, string section); bool EmptyIniSectionA(string fileName, string section); - //int GetIniKeysA(string fileName, string section, int buffer[], int bufferSize); - //int GetIniSectionsA(string fileName, int buffer[], int bufferSize); + //int GetIniKeysA(string fileName, string section, int buffer[], int bufferSize); // see notes at the top of the file + //int GetIniSectionsA(string fileName, int buffer[], int bufferSize); // see notes at the top of the file string GetIniStringA(string fileName, string section, string key, string defaultValue); string GetIniStringRawA(string fileName, string section, string key, string defaultValue); bool IsGlobalConfigKeyA(string section, string key); @@ -92,12 +93,12 @@ int GetBoolsAddress (bool values[]); int GetIntsAddress (int values[]); int GetDoublesAddress(double values[]); - int GetStringAddress (string value ); // Warning: GetStringAddress() must be used with string array elements only. - int GetStringsAddress(string values[]); // Simple strings are passed to DLLs as copies and immediately destroyed - string GetStringA(int address); // after the call. Accessing such an address may cause a terminal crash. + int GetStringAddress (string value ); // Warning: GetStringAddress() must be used with string array elements only. + int GetStringsAddress(string values[]); // Simple strings are passed to DLLs as copies and immediately destroyed + string GetStringA(int address); // after the call. Accessing such an address may cause a terminal crash. bool MemCompare(int lpBufferA, int lpBufferB, int size); - // array functions + // array functions (see notes at the top of the file) //bool InitializeBOOLArray (bool &values[], int size, int initValue, int from, int count); //bool InitializeBoolArray (bool &values[], int size, bool initValue, int from, int count); //bool InitializeCharArray (char &values[], int size, char initValue, int from, int count); @@ -117,9 +118,9 @@ //bool ShiftDoubleIndicatorBuffer(double &buffer[], int size, int count, double emptyValue); // string functions - //string MD5Hash(int buffer[], int size); + //string MD5Hash(int buffer[], int size); // see notes at the top of the file string MD5HashA(string str); - //bool SortMqlStringsA(string values[], int size); + //bool SortMqlStringsA(string values[], int size); // see notes at the top of the file bool StrCompare(string s1, string s2); bool StrEndsWith(string str, string suffix); bool StrEndsWithI(string str, string suffix); diff --git a/mql40/include/rsf/core/expert.recorder.mqh b/mql40/include/rsf/core/expert.recorder.mqh index 20ba331b7..3ead647ca 100644 --- a/mql40/include/rsf/core/expert.recorder.mqh +++ b/mql40/include/rsf/core/expert.recorder.mqh @@ -349,7 +349,7 @@ bool Recorder_start() { | v419 HST_BUFFER_TICKS=On | | | | | | | 15.486 t/s | 14.286 t/s | +---------------------------+------------+-----------+--------------+-------------+-------------+--------------+--------------+--------------+ */ - int size=ArraySize(metric.hSet), flags=NULL; + int size = ArraySize(metric.hSet), flags = NULL; if (__isTesting) flags = HST_BUFFER_TICKS; double value; bool success = true; diff --git a/mql40/include/rsf/history.mqh b/mql40/include/rsf/history.mqh index 50edfea98..548f704ea 100644 --- a/mql40/include/rsf/history.mqh +++ b/mql40/include/rsf/history.mqh @@ -4,13 +4,13 @@ * * Notes: * ------ - * - The MQL4 language in terminal builds <= 509 imposes a limit of 16 open files per MQL module. In terminal builds > 509 - * this limit was extended to 64 open files per MQL module. It means older terminals can manage 1 full history set per MQL - * module and newer terminals can manage 7 full history sets per MQL module. But for some uses cases 7 history sets per MQL - * program are still not sufficient. For this reason there are 3 fully identical history libraries. With it newer terminal - * builds can manage max. 21 history sets per MQL program. + * - MT4 terminal builds <= 509 impose a limit of 16 open files per MQL module. In terminal builds > 509 this limit was + * extended to 64 open files per MQL module. This means older terminals can manage max. 1 full history set and newer + * terminals max. 7 full history sets per MQL module. For some uses cases that's still not sufficient. To overcome the + * limit there are 3 fully identical history libraries, extending the limit for terminal builds > 509 to max. 21 history + * sets per MQL program. * - * - Since terminal builds > 509 MT4 supports two history file formats. The format is identified in history files by the + * - Since terminal builds > 509 MetaTrader4 supports two history file formats. The format is identified in history files by * field HISTORY_HEADER.barFormat. The default bar format in builds <= 509 is "400" and in builds > 509 "401". * Builds <= 509 can only read/write format "400". Builds > 509 can read both formats but write only format "401". * diff --git a/mql40/indicators/Signal Performance.mq4 b/mql40/indicators/Signal Performance.mq4 index 3c9c59bb8..8c24dc9ad 100644 --- a/mql40/indicators/Signal Performance.mq4 +++ b/mql40/indicators/Signal Performance.mq4 @@ -7,13 +7,15 @@ int __DeinitFlags[]; ////////////////////////////////////////////////////// Configuration //////////////////////////////////////////////////////// -extern int MaxBarsBack = 10000; +extern int ZigZag.Periods = 50; +extern int MaxBarsBack = 10000; ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// #include #include #include +#include #property indicator_separate_window #property indicator_buffers 1 @@ -34,13 +36,16 @@ int onInit() { string indicator = WindowExpertName(); // validate inputs + // ZigZag.Periods + if (AutoConfiguration) ZigZag.Periods = GetConfigInt(indicator, "ZigZag.Periods", ZigZag.Periods); + if (ZigZag.Periods < 2) return(catch("onInit(1) invalid input parameter ZigZag.Periods: "+ ZigZag.Periods, ERR_INVALID_INPUT_PARAMETER)); // MaxBarsBack if (AutoConfiguration) MaxBarsBack = GetConfigInt(indicator, "MaxBarsBack", MaxBarsBack); - if (MaxBarsBack < -1) return(catch("onInit(1) invalid input parameter MaxBarsBack: "+ MaxBarsBack, ERR_INVALID_INPUT_PARAMETER)); + if (MaxBarsBack < -1) return(catch("onInit(2) invalid input parameter MaxBarsBack: "+ MaxBarsBack, ERR_INVALID_INPUT_PARAMETER)); if (MaxBarsBack == -1) MaxBarsBack = INT_MAX; SetIndicatorOptions(); - return(catch("onInit(2)")); + return(catch("onInit(3)")); } @@ -52,26 +57,104 @@ int onInit() { int onTick() { // reset buffers before performing a full recalculation if (!ValidBars) { - ArrayInitialize(main, 0); + ArrayInitialize(main, EMPTY_VALUE); SetIndicatorOptions(); } // synchronize buffers with a shifted offline chart if (ShiftedBars > 0) { - ShiftDoubleIndicatorBuffer(main, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(main, Bars, ShiftedBars, EMPTY_VALUE); } // calculate start bar - int startbar = Min(MaxBarsBack-1, ChangedBars-1); + int startbar = Min(MaxBarsBack-1, ChangedBars-1), trend, reversalOffset; + bool isReversal, isPosition; + double reversalUp, reversalDown, change; // recalculate changed bars for (int bar=startbar; bar >= 0; bar--) { - main[bar] = Close[bar]/pUnit; + if (!GetZigZagData(bar, trend, isReversal, reversalOffset, reversalUp, reversalDown)) { + return(last_error); + } + isPosition = (main[bar+1] != EMPTY_VALUE); + + if (isReversal) { // flip position + if (isPosition) { + if (trend > 0) { + change = reversalUp - Close[bar+1]; + main[bar] = main[bar+1] - change/pUnit; // close existing position + main[bar] += (Close[bar] - reversalUp)/pUnit; // open new position + } + else if (trend < 0) { + change = reversalDown - Close[bar+1]; + main[bar] = main[bar+1] + change/pUnit; // close existing position + main[bar] += (reversalDown - Close[bar])/pUnit; // open new position + } + else { + debug("onTick(0.1) cannot flip position with trend=0"); + } + } + else { + if (trend > 0) { + main[bar] = (Close[bar] - reversalUp)/pUnit; // open long + } + else if (trend < 0) { + main[bar] = (reversalDown - Close[bar])/pUnit; // open short + } + else { + debug("onTick(0.2) cannot open position with trend=0"); + } + } + } + else if (isPosition) { // update position + change = Close[bar] - Close[bar+1]; + if (trend > 0) { + main[bar] = main[bar+1] + change/pUnit; + } + else if (trend < 0) { + main[bar] = main[bar+1] - change/pUnit; + } + else { + debug("onTick(0.3) cannot update position with trend=0"); + } + } + else { // keep previous PnL + main[bar] = main[bar+1]; + } } return(last_error); } +/** + * Get ZigZag buffer values at the specified bar offset. + * + * @param _In_ int bar - bar offset + * @param _Out_ int trend - trend at the specified bar (internal buffer MODE_TREND) + * @param _Out_ bool isReversal - whether the specified bar is a reversal bar + * @param _Out_ int reversalOffset - buffer ZigZag.MODE_REVERSAL_OFFSET at the specified bar + * @param _Out_ double reversalPriceUp - buffer ZigZag.MODE_UPPER_CROSS at the specified bar + * @param _Out_ double reversalPriceDown - buffer ZigZag.MODE_LOWER_CROSS at the specified bar + * + * @return bool - success status + */ +bool GetZigZagData(int bar, int &trend, bool &isReversal, int &reversalOffset, double &reversalPriceUp, double &reversalPriceDown) { + int combinedTrend = MathRound(icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_COMBINED_TREND, bar)); + reversalOffset = MathRound(icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_REVERSAL_OFFSET, bar)); + reversalPriceUp = 0; + reversalPriceDown = 0; + + trend = combinedTrend % 100000; + isReversal = (Abs(trend) == reversalOffset); + + if (isReversal) { + reversalPriceUp = icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_UPPER_CROSS, bar); + reversalPriceDown = icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_LOWER_CROSS, bar); + } + return(!last_error); +} + + /** * Set indicator options. After recompilation the function must be called from start() for options not to be ignored. * @@ -86,7 +169,7 @@ bool SetIndicatorOptions(bool redraw = false) { IndicatorShortName(indicatorName); IndicatorBuffers(indicator_buffers); - SetIndexBuffer(MODE_MAIN, main); SetIndexEmptyValue(MODE_MAIN, 0); + SetIndexBuffer(MODE_MAIN, main); SetIndexEmptyValue(MODE_MAIN, EMPTY_VALUE); IndicatorDigits(pDigits); SetIndexStyle(MODE_MAIN, DRAW_LINE, EMPTY, EMPTY, indicator_color1); diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index ca641caa4..5a590f869 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -646,7 +646,7 @@ int onTick() { lowerCrossLow[bar] = lowerBand[bar]; } - // --- start: recalculate ZigZag data --- + // recalculate ZigZag data // if no channel crossing // before or after the first semaphore if (!upperCross[bar] && !lowerCross[bar]) { reversalOffset[bar] = reversalOffset[bar+1]; // keep reversal offset (may be -1) @@ -672,19 +672,18 @@ int onTick() { // if a single channel crossing (before or after the first semaphore) else if (upperCross[bar] != 0) ProcessUpperCross(bar); else ProcessLowerCross(bar); - // --- end: recalculate ZigZag data --- - - // whether the processed bar is a reversal bar (not whether the current tick triggered a reversal) - bool isReversalBar = false; - if (!unknownTrend[bar]) { - isReversalBar = (Abs(trend[bar]) == reversalOffset[bar]); - } + // whether the current bar is a reversal bar (not whether the current tick triggered a reversal) + bool isReversalBar = (Abs(trend[bar]) == reversalOffset[bar]); + // calculate signal performance + if (TrackSignalPerformance && !__isSuperContext && !__isTesting) { + RecalculateSignalPerformance(bar, isReversalBar); + } if (debugging && Ticks == 1) { if (Time[bar] >= devFrom && Time[bar] <= devTo) { - debug("onTick(0.1) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" trend="+ trend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" unknownTrend="+ unknownTrend[bar]); + debug("onTick(0.1) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" reversalOffset="+ _int(reversalOffset[bar]) +" trend="+ trend[bar] +" unknownTrend="+ unknownTrend[bar]); if (isReversalBar) { debug("onTick(0.2) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" isReversalBar=1"); } @@ -694,51 +693,6 @@ int onTick() { } } - - - // calculate signal performance (if enabled) - bool isPosition = (signalPerformance[bar+1] != EMPTY_VALUE); - double range; - - if (isReversalBar) { - if (isPosition) { - if (upperCross[bar] != 0) { - range = upperCross[bar] - Close[bar+1]; - signalPerformance[bar] = signalPerformance[bar+1] - range; // close existing position - signalPerformance[bar] += Close[bar] - upperCross[bar]; // open new position - } - else { - range = lowerCross[bar] - Close[bar+1]; - signalPerformance[bar] = signalPerformance[bar+1] + range; // close existing position - signalPerformance[bar] += lowerCross[bar] - Close[bar]; // open new position - } - } - else { // open a new position - if (upperCross[bar] != 0) { - signalPerformance[bar] = Close[bar] - upperCross[bar]; // long - } - else { - signalPerformance[bar] = lowerCross[bar] - Close[bar]; // short - } - } - if (Ticks==1) debug("onTick(0.4) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" reversalBar balance="+ DoubleToStr(signalPerformance[bar]/pUnit, pDigits)); - } - else if (isPosition) { // only update PnL - range = Close[bar] - Close[bar+1]; - if (trend[bar] > 0) { - signalPerformance[bar] = signalPerformance[bar+1] + range; - } - else { - signalPerformance[bar] = signalPerformance[bar+1] - range; - } - } - else { // keep previous PnL - signalPerformance[bar] = signalPerformance[bar+1]; - } - - - - // hide non-configured crossings if (!crossingDrawType) { // hide all crossings upperCross[bar] = 0; @@ -757,20 +711,78 @@ int onTick() { } } - // compose combinedTrend[] + // set combinedTrend[] combinedTrend[bar] = Sign(trend[bar]) * unknownTrend[bar] * 100000 + trend[bar]; } + if (__isChart && !__isSuperContext) { + if (ShowChartLegend) UpdateChartLegend(); + // record signal performance + if (TrackSignalPerformance && !__isTesting) { + RecordSignalPerformance(); + } + // detect ZigZag breakouts (comparing legs against bands also detects breakouts on missed ticks) + if (Signal.onBreakout) { + if (ChangedBars > 2) { + while (true) { + int resultType = 0; + bar = FindSemaphore(0, resultType); if (bar < 0) break; + // resolve leg high/low + if (resultType == MODE_HIGH) { + lastLegHigh = High[bar]; + lastLegLow = 0; + } + else { + lastLegHigh = 0; + lastLegLow = Low[bar]; + } + // resolve the last 3 semaphores + bar = FindSemaphore(bar, resultType, resultType); if (bar < 0) break; + sema1 = ifDouble(resultType==MODE_HIGH, High[bar], Low[bar]); + bar = FindSemaphore(bar, resultType, resultType); if (bar < 0) break; + sema2 = ifDouble(resultType==MODE_HIGH, High[bar], Low[bar]); + bar = FindSemaphore(bar, resultType, resultType); if (bar < 0) break; + sema3 = ifDouble(resultType==MODE_HIGH, High[bar], Low[bar]); + break; + } + } + else if (sema3 != 0) { + if (trend[0] > 0) { + if (lastLegHigh < sema2+HalfPoint && upperBand[0] > sema2+HalfPoint) { + onBreakout(D_LONG); + } + lastLegHigh = High[unknownTrend[0]]; // leg high for comparison at the nex tick + } + else if (trend[0] < 0) { + if ((!lastLegLow || lastLegLow > sema2-HalfPoint) && lowerBand[0] < sema2-HalfPoint) { + onBreakout(D_SHORT); + } + lastLegLow = Low[unknownTrend[0]]; // leg low for comparison at the nex tick + } + } + } - if (__isChart && !__isSuperContext) { - if (ShowChartLegend) UpdateChartLegend(); + // detect Donchian channel widenings + if (Sound.onChannelWidening) /*&&*/ if (ChangedBars <= 2) { + if (ChangedBars == 2) { + lastUpperBand = upperBand[1]; + lastLowerBand = lowerBand[1]; + } + if (lastUpperBand && lastLowerBand) { + if (upperBand[0] > lastUpperBand+HalfPoint) onChannelWidening(D_LONG); + else if (lowerBand[0] < lastLowerBand-HalfPoint) onChannelWidening(D_SHORT); + } + lastUpperBand = upperBand[0]; + lastLowerBand = lowerBand[0]; + } - // track ZigZag balance (if enabled) - if (Ticks == 1) /*&&*/ if (TrackZigZagBalance && TrackZigZagBalance.Since) { + // --- old ------------------------------------------------------------------------------------------------------------ + // track ZigZag balance + if (TrackZigZagBalance) { int currSem, prevBar, prevSem, size; int currBar = FindSemaphore(0, currSem); if (currBar < 0) return(last_error); @@ -786,8 +798,7 @@ int onTick() { if (!reversalOffset[currBar] && reversalOffset[currBar+1]==-1) { // reversal and next semaphore on the same bar reversalBar = currBar; } - if (Time[reversalBar] < TrackZigZagBalance.Since) break; - if (reversalBar > MaxBarsBack-ZigZag.Periods) break; + if (reversalBar > MaxBarsBack-ZigZag.Periods) break; size = ArrayRange(events, 0); ArrayResize(events, size+2); @@ -851,65 +862,88 @@ int onTick() { } } } + } + return(last_error); +} - // detect ZigZag breakouts (comparing legs against bands also detects breakouts on missed ticks) - if (Signal.onBreakout) { - if (ChangedBars > 2) { - while (true) { - int resultType = 0; - bar = FindSemaphore(0, resultType); if (bar < 0) break; - // resolve leg high/low - if (resultType == MODE_HIGH) { - lastLegHigh = High[bar]; - lastLegLow = 0; - } - else { - lastLegHigh = 0; - lastLegLow = Low[bar]; - } - - // resolve the last 3 semaphores - bar = FindSemaphore(bar, resultType, resultType); if (bar < 0) break; - sema1 = ifDouble(resultType==MODE_HIGH, High[bar], Low[bar]); - bar = FindSemaphore(bar, resultType, resultType); if (bar < 0) break; - sema2 = ifDouble(resultType==MODE_HIGH, High[bar], Low[bar]); - bar = FindSemaphore(bar, resultType, resultType); if (bar < 0) break; - sema3 = ifDouble(resultType==MODE_HIGH, High[bar], Low[bar]); - break; - } +/** + * Recalculate the signal performance for the specified bar. + * + * @param int bar - bar offset + * @param bool isReversal - whether the bar is a reversal bar + * + * @return bool - success status + */ +bool RecalculateSignalPerformance(int bar, bool isReversal) { + isReversal = (isReversal!=0); + bool isPosition = (signalPerformance[bar+1] != EMPTY_VALUE); + double change; + + // either flip the position + if (isReversal) { + if (isPosition) { + if (trend[bar] > 0) { + change = upperCross[bar] - Close[bar+1]; + signalPerformance[bar] = signalPerformance[bar+1] - change; // close existing short position + signalPerformance[bar] += Close[bar] - upperCross[bar]; // open new long position } - else if (sema3 != 0) { - if (trend[0] > 0) { - if (lastLegHigh < sema2+HalfPoint && upperBand[0] > sema2+HalfPoint) { - onBreakout(D_LONG); - } - lastLegHigh = High[unknownTrend[0]]; // leg high for comparison at the nex tick - } - else if (trend[0] < 0) { - if ((!lastLegLow || lastLegLow > sema2-HalfPoint) && lowerBand[0] < sema2-HalfPoint) { - onBreakout(D_SHORT); - } - lastLegLow = Low[unknownTrend[0]]; // leg low for comparison at the nex tick - } + else if (trend[bar] < 0) { + change = lowerCross[bar] - Close[bar+1]; + signalPerformance[bar] = signalPerformance[bar+1] + change; // close existing long position + signalPerformance[bar] += lowerCross[bar] - Close[bar]; // open new short position + } + else { + debug("RecalculateSignalPerformance(0.1) cannot yet flip position with trend = 0"); } } - - // detect Donchian channel widenings - if (Sound.onChannelWidening) /*&&*/ if (ChangedBars <= 2) { - if (ChangedBars == 2) { - lastUpperBand = upperBand[1]; - lastLowerBand = lowerBand[1]; + else { // open a new position + if (trend[bar] > 0) { + signalPerformance[bar] = Close[bar] - upperCross[bar]; // long position } - if (lastUpperBand && lastLowerBand) { - if (upperBand[0] > lastUpperBand+HalfPoint) onChannelWidening(D_LONG); - else if (lowerBand[0] < lastLowerBand-HalfPoint) onChannelWidening(D_SHORT); + else if (trend[bar] < 0) { + signalPerformance[bar] = lowerCross[bar] - Close[bar]; // short position + } + else { + debug("RecalculateSignalPerformance(0.2) cannot yet open position with trend = 0"); } - lastUpperBand = upperBand[0]; - lastLowerBand = lowerBand[0]; } + if (debugging && Ticks==1) debug("RecalculateSignalPerformance(0.3) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" reversalBar balance="+ DoubleToStr(signalPerformance[bar]/pUnit, pDigits)); } - return(last_error); + + // or update the position + else if (isPosition) { + change = Close[bar] - Close[bar+1]; + if (trend[bar] > 0) { + signalPerformance[bar] = signalPerformance[bar+1] + change; + } + else if (trend[bar] < 0) { + signalPerformance[bar] = signalPerformance[bar+1] - change; + } + else { + debug("RecalculateSignalPerformance(0.4) cannot yet update position with trend = 0"); + } + } + + // or keep existing PnL + else { + signalPerformance[bar] = signalPerformance[bar+1]; + } + return(true); +} + + +/** + * Record the signal performance for all changed bars. + * + * @return bool - success status + */ +bool RecordSignalPerformance() { + //if (!recorder.initialized) { + // if (recorder.mode == RECORDER_OFF) return(_true(Recorder_off())); + // if (!Recorder_init()) return(_false(Recorder_off())); + //} + return(true); } diff --git a/mql40/libraries/rsfHistory1.mq4 b/mql40/libraries/rsfHistory1.mq4 index c575bed22..0013b261c 100644 --- a/mql40/libraries/rsfHistory1.mq4 +++ b/mql40/libraries/rsfHistory1.mq4 @@ -10,17 +10,18 @@ * - Create a new history and delete all existing data (e.g. for writing a new history): * int hSet = HistorySet1.Create(symbol, description, digits, format); * - * - How to synchronize rsfHistory{1-3}.mq4: + * - How to synchronize rsfHistory{1,2,3}.mq4: * search: (HistoryFile|HistorySet)[1-3]\> * replace: \11 or \12 or \13 * * * Notes: * ------ - * - MQL4.0 in terminal builds <= 509 imposes a limit of 16 open files per MQL module. In terminal builds > 509 this limit - * is extended to 64 open files per MQL module. This means older terminals can manage max. 1 full history set and newer - * terminals max. 7 full history sets per MQL module. That's still not sufficient. To overcome the limits there are 3 - * identical history libraries, extending the limits for newer terminals to max. 21 history sets per MQL program. + * - MT4 terminal builds <= 509 impose a limit of 16 open files per MQL module. In terminal builds > 509 this limit was + * extended to 64 open files per MQL module. This means older terminals can manage max. 1 full history set and newer + * terminals max. 7 full history sets per MQL module. For some uses cases that's still not sufficient. To overcome the + * limit there are 3 fully identical history libraries, extending the limit for terminal builds > 509 to max. 21 history + * sets per MQL program. * * - Since terminal builds > 509 MT4 supports two history file formats. The format is identified in history files by the * field HISTORY_HEADER.barFormat. The default bar format in builds <= 509 is "400" and in builds > 509 "401". @@ -810,7 +811,7 @@ int HistoryFile1.FindBar(int hFile, datetime time, bool &lpBarExists[]) { * * @return bool - success status * - * NOTE: Time und Volume der gelesenen Bar werden validert, nicht jedoch die Barform. + * NOTE: Time und Volume der gelesenen Bar werden validiert, nicht jedoch die Barform. */ bool HistoryFile1.ReadBar(int hFile, int offset, double &bar[]) { if (hFile <= 0) return(!catch("HistoryFile1.ReadBar(1) invalid parameter hFile: "+ hFile, ERR_INVALID_PARAMETER)); @@ -909,7 +910,7 @@ bool HistoryFile1.ReadBar(int hFile, int offset, double &bar[]) { * * @return bool - success status * - * NOTE: Time und Volume der zu schreibenden Bar werden auf != NULL validert, alles andere nicht. Insbesondere wird nicht überprüft, ob die + * NOTE: Time und Volume der zu schreibenden Bar werden auf != NULL validiert, alles andere nicht. Insbesondere wird nicht überprüft, ob die * Bar-Time eine normalisierte OpenTime für den Timeframe der Historydatei ist. */ bool HistoryFile1.WriteBar(int hFile, int offset, double bar[], int flags=NULL) { @@ -1111,7 +1112,7 @@ bool HistoryFile1.UpdateBar(int hFile, int offset, double value) { * * @return bool - success status * - * NOTE: Time und Volume der einzufügenden Bar werden auf != NULL validert, alles andere nicht. Insbesondere wird nicht überprüft, ob die + * NOTE: Time und Volume der einzufügenden Bar werden auf != NULL validiert, alles andere nicht. Insbesondere wird nicht überprüft, ob die * Bar-Time eine normalisierte OpenTime für den Timeframe der Historydatei ist. */ bool HistoryFile1.InsertBar(int hFile, int offset, double bar[], int flags = NULL) { diff --git a/mql40/libraries/rsfHistory2.mq4 b/mql40/libraries/rsfHistory2.mq4 index 8a9c511ef..484279a42 100644 --- a/mql40/libraries/rsfHistory2.mq4 +++ b/mql40/libraries/rsfHistory2.mq4 @@ -10,17 +10,18 @@ * - Create a new history and delete all existing data (e.g. for writing a new history): * int hSet = HistorySet2.Create(symbol, description, digits, format); * - * - How to synchronize rsfHistory{1-3}.mq4: + * - How to synchronize rsfHistory{1,2,3}.mq4: * search: (HistoryFile|HistorySet)[1-3]\> * replace: \11 or \12 or \13 * * * Notes: * ------ - * - MQL4.0 in terminal builds <= 509 imposes a limit of 16 open files per MQL module. In terminal builds > 509 this limit - * is extended to 64 open files per MQL module. This means older terminals can manage max. 1 full history set and newer - * terminals max. 7 full history sets per MQL module. That's still not sufficient. To overcome the limits there are 3 - * identical history libraries, extending the limits for newer terminals to max. 21 history sets per MQL program. + * - MT4 terminal builds <= 509 impose a limit of 16 open files per MQL module. In terminal builds > 509 this limit was + * extended to 64 open files per MQL module. This means older terminals can manage max. 1 full history set and newer + * terminals max. 7 full history sets per MQL module. For some uses cases that's still not sufficient. To overcome the + * limit there are 3 fully identical history libraries, extending the limit for terminal builds > 509 to max. 21 history + * sets per MQL program. * * - Since terminal builds > 509 MT4 supports two history file formats. The format is identified in history files by the * field HISTORY_HEADER.barFormat. The default bar format in builds <= 509 is "400" and in builds > 509 "401". @@ -810,7 +811,7 @@ int HistoryFile2.FindBar(int hFile, datetime time, bool &lpBarExists[]) { * * @return bool - success status * - * NOTE: Time und Volume der gelesenen Bar werden validert, nicht jedoch die Barform. + * NOTE: Time und Volume der gelesenen Bar werden validiert, nicht jedoch die Barform. */ bool HistoryFile2.ReadBar(int hFile, int offset, double &bar[]) { if (hFile <= 0) return(!catch("HistoryFile2.ReadBar(1) invalid parameter hFile: "+ hFile, ERR_INVALID_PARAMETER)); @@ -909,7 +910,7 @@ bool HistoryFile2.ReadBar(int hFile, int offset, double &bar[]) { * * @return bool - success status * - * NOTE: Time und Volume der zu schreibenden Bar werden auf != NULL validert, alles andere nicht. Insbesondere wird nicht überprüft, ob die + * NOTE: Time und Volume der zu schreibenden Bar werden auf != NULL validiert, alles andere nicht. Insbesondere wird nicht überprüft, ob die * Bar-Time eine normalisierte OpenTime für den Timeframe der Historydatei ist. */ bool HistoryFile2.WriteBar(int hFile, int offset, double bar[], int flags=NULL) { @@ -1111,7 +1112,7 @@ bool HistoryFile2.UpdateBar(int hFile, int offset, double value) { * * @return bool - success status * - * NOTE: Time und Volume der einzufügenden Bar werden auf != NULL validert, alles andere nicht. Insbesondere wird nicht überprüft, ob die + * NOTE: Time und Volume der einzufügenden Bar werden auf != NULL validiert, alles andere nicht. Insbesondere wird nicht überprüft, ob die * Bar-Time eine normalisierte OpenTime für den Timeframe der Historydatei ist. */ bool HistoryFile2.InsertBar(int hFile, int offset, double bar[], int flags = NULL) { diff --git a/mql40/libraries/rsfHistory3.mq4 b/mql40/libraries/rsfHistory3.mq4 index 49ae0a38d..52f71e1f3 100644 --- a/mql40/libraries/rsfHistory3.mq4 +++ b/mql40/libraries/rsfHistory3.mq4 @@ -10,17 +10,18 @@ * - Create a new history and delete all existing data (e.g. for writing a new history): * int hSet = HistorySet3.Create(symbol, description, digits, format); * - * - How to synchronize rsfHistory{1-3}.mq4: + * - How to synchronize rsfHistory{1,2,3}.mq4: * search: (HistoryFile|HistorySet)[1-3]\> * replace: \11 or \12 or \13 * * * Notes: * ------ - * - MQL4.0 in terminal builds <= 509 imposes a limit of 16 open files per MQL module. In terminal builds > 509 this limit - * is extended to 64 open files per MQL module. This means older terminals can manage max. 1 full history set and newer - * terminals max. 7 full history sets per MQL module. That's still not sufficient. To overcome the limits there are 3 - * identical history libraries, extending the limits for newer terminals to max. 21 history sets per MQL program. + * - MT4 terminal builds <= 509 impose a limit of 16 open files per MQL module. In terminal builds > 509 this limit was + * extended to 64 open files per MQL module. This means older terminals can manage max. 1 full history set and newer + * terminals max. 7 full history sets per MQL module. For some uses cases that's still not sufficient. To overcome the + * limit there are 3 fully identical history libraries, extending the limit for terminal builds > 509 to max. 21 history + * sets per MQL program. * * - Since terminal builds > 509 MT4 supports two history file formats. The format is identified in history files by the * field HISTORY_HEADER.barFormat. The default bar format in builds <= 509 is "400" and in builds > 509 "401". @@ -810,7 +811,7 @@ int HistoryFile3.FindBar(int hFile, datetime time, bool &lpBarExists[]) { * * @return bool - success status * - * NOTE: Time und Volume der gelesenen Bar werden validert, nicht jedoch die Barform. + * NOTE: Time und Volume der gelesenen Bar werden validiert, nicht jedoch die Barform. */ bool HistoryFile3.ReadBar(int hFile, int offset, double &bar[]) { if (hFile <= 0) return(!catch("HistoryFile3.ReadBar(1) invalid parameter hFile: "+ hFile, ERR_INVALID_PARAMETER)); @@ -909,7 +910,7 @@ bool HistoryFile3.ReadBar(int hFile, int offset, double &bar[]) { * * @return bool - success status * - * NOTE: Time und Volume der zu schreibenden Bar werden auf != NULL validert, alles andere nicht. Insbesondere wird nicht überprüft, ob die + * NOTE: Time und Volume der zu schreibenden Bar werden auf != NULL validiert, alles andere nicht. Insbesondere wird nicht überprüft, ob die * Bar-Time eine normalisierte OpenTime für den Timeframe der Historydatei ist. */ bool HistoryFile3.WriteBar(int hFile, int offset, double bar[], int flags=NULL) { @@ -1111,7 +1112,7 @@ bool HistoryFile3.UpdateBar(int hFile, int offset, double value) { * * @return bool - success status * - * NOTE: Time und Volume der einzufügenden Bar werden auf != NULL validert, alles andere nicht. Insbesondere wird nicht überprüft, ob die + * NOTE: Time und Volume der einzufügenden Bar werden auf != NULL validiert, alles andere nicht. Insbesondere wird nicht überprüft, ob die * Bar-Time eine normalisierte OpenTime für den Timeframe der Historydatei ist. */ bool HistoryFile3.InsertBar(int hFile, int offset, double bar[], int flags = NULL) { diff --git a/mql45/include/rsf/expander/errors.h b/mql45/include/rsf/expander/errors.h index 82a7a6ece..1829faa03 100644 --- a/mql45/include/rsf/expander/errors.h +++ b/mql45/include/rsf/expander/errors.h @@ -71,7 +71,7 @@ // Runtime errors #define ERR_NO_MQLERROR 4000 // never generated error -#define ERR_ARRAY_INDEX_OUT_OF_RANGE 4002 // MQL5 for MT4: without "#property strict" this error is ignored and never set +#define ERR_ARRAY_INDEX_OUT_OF_RANGE 4002 // MQL4.5: without "#property strict" this error is ignored and never set #define ERR_NOT_ENOUGH_STACK_FOR_PARAM 4005 #define ERR_NOT_INITIALIZED_STRING 4008 #define ERR_NOT_INITIALIZED_ARRAYSTRING 4009 From 0be149185d62b7c821db9724ebde3fa7dd345d34 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Sun, 22 Mar 2026 16:13:53 +0200 Subject: [PATCH 14/40] update chart templates --- templates4/12 ZigZag(50).tpl | 28 -------- templates4/31 XARD M1.tpl | 2 +- ...fos).tpl => 34 Minimal (ChartInfos).tpl} | 0 templates4/34 Signal Performance.tpl | 66 ------------------- ...rs).tpl => 35 Empty (no indicators).tpl} | 0 5 files changed, 1 insertion(+), 95 deletions(-) rename templates4/{35 Minimal (ChartInfos).tpl => 34 Minimal (ChartInfos).tpl} (100%) delete mode 100644 templates4/34 Signal Performance.tpl rename templates4/{36 Empty (no indicators).tpl => 35 Empty (no indicators).tpl} (100%) diff --git a/templates4/12 ZigZag(50).tpl b/templates4/12 ZigZag(50).tpl index a94422b8e..3e3928394 100644 --- a/templates4/12 ZigZag(50).tpl +++ b/templates4/12 ZigZag(50).tpl @@ -141,33 +141,5 @@ style_0=2 style_1=2 show_data=1 - - -name=Custom Indicator - -name=ZigZag -flags=339 -window_num=0 - -ZigZag.Periods=30 -ZigZag.Width=0 -Donchian.ShowChannel=0 -Donchian.Channel.UpperColor=16711680 -Donchian.Channel.LowerColor=16711935 -Donchian.ShowCrossings=off | first* | all -Donchian.Crossing.Symbol=dot* | narrow-ring | ring | bold-ring -Donchian.Crossing.Width=1 -Signal.onReversal=0 -Signal.onReversal.Types=sound* | alert* | mail | sms -Signal.onBreakout=0 -Sound.onChannelWidening=0 - - -style_0=2 -style_1=2 -color_6=4294967295 -color_7=4294967295 -show_data=1 - diff --git a/templates4/31 XARD M1.tpl b/templates4/31 XARD M1.tpl index cb5e01c33..43a2c75b7 100644 --- a/templates4/31 XARD M1.tpl +++ b/templates4/31 XARD M1.tpl @@ -362,7 +362,7 @@ Donchian.Crossing.Width=3 Donchian.Crossing.Color=16711935 ShowChartLegend=1 Signal.onReversal=1 -Signal.onReversal.Types=sound* | alert | mail | sms +Signal.onReversal.Types=sound* | alert* | mail | sms style_0=2 diff --git a/templates4/35 Minimal (ChartInfos).tpl b/templates4/34 Minimal (ChartInfos).tpl similarity index 100% rename from templates4/35 Minimal (ChartInfos).tpl rename to templates4/34 Minimal (ChartInfos).tpl diff --git a/templates4/34 Signal Performance.tpl b/templates4/34 Signal Performance.tpl deleted file mode 100644 index 8238620b6..000000000 --- a/templates4/34 Signal Performance.tpl +++ /dev/null @@ -1,66 +0,0 @@ - - - -symbol=GBPUSD -period=60 -digits=5 - -leftpos=9229 -scale=1 -graph=1 -fore=0 -grid=0 -volume=0 -ohlc=0 -askline=0 -days=0 -descriptions=1 -scroll=1 -shift=1 -shift_size=10 - -fixed_pos=620 -window_left=0 -window_top=0 -window_right=1292 -window_bottom=812 -window_type=3 - -background_color=16316664 -foreground_color=0 -barup_color=30720 -bardown_color=210 -bullcandle_color=30720 -bearcandle_color=210 -chartline_color=11119017 -volumes_color=30720 -grid_color=14474460 -askline_color=11823615 -stops_color=17919 - - -height=1 -fixed_height=0 - -name=main - - - - -height=5000 -fixed_height=0 - -name=Custom Indicator - -name=Signal Performance -flags=339 -window_num=1 - - - -show_data=1 - - - diff --git a/templates4/36 Empty (no indicators).tpl b/templates4/35 Empty (no indicators).tpl similarity index 100% rename from templates4/36 Empty (no indicators).tpl rename to templates4/35 Empty (no indicators).tpl From ce2721cd161ed118c59bebbc47281bde5c62f2ae Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Sun, 22 Mar 2026 16:14:49 +0200 Subject: [PATCH 15/40] drop obsolete indicator "Signal Performance" --- mql40/indicators/Signal Performance.mq4 | 190 ------------------------ 1 file changed, 190 deletions(-) delete mode 100644 mql40/indicators/Signal Performance.mq4 diff --git a/mql40/indicators/Signal Performance.mq4 b/mql40/indicators/Signal Performance.mq4 deleted file mode 100644 index 8c24dc9ad..000000000 --- a/mql40/indicators/Signal Performance.mq4 +++ /dev/null @@ -1,190 +0,0 @@ -/** - * Signal Performance - */ -#include -int __InitFlags[]; -int __DeinitFlags[]; - -////////////////////////////////////////////////////// Configuration //////////////////////////////////////////////////////// - -extern int ZigZag.Periods = 50; -extern int MaxBarsBack = 10000; - -///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - -#include -#include -#include -#include - -#property indicator_separate_window -#property indicator_buffers 1 -#property indicator_color1 Blue - -// indicator buffer ids -#define MODE_MAIN 0 - -double main[]; - - -/** - * Initialization - * - * @return int - error status - */ -int onInit() { - string indicator = WindowExpertName(); - - // validate inputs - // ZigZag.Periods - if (AutoConfiguration) ZigZag.Periods = GetConfigInt(indicator, "ZigZag.Periods", ZigZag.Periods); - if (ZigZag.Periods < 2) return(catch("onInit(1) invalid input parameter ZigZag.Periods: "+ ZigZag.Periods, ERR_INVALID_INPUT_PARAMETER)); - // MaxBarsBack - if (AutoConfiguration) MaxBarsBack = GetConfigInt(indicator, "MaxBarsBack", MaxBarsBack); - if (MaxBarsBack < -1) return(catch("onInit(2) invalid input parameter MaxBarsBack: "+ MaxBarsBack, ERR_INVALID_INPUT_PARAMETER)); - if (MaxBarsBack == -1) MaxBarsBack = INT_MAX; - - SetIndicatorOptions(); - return(catch("onInit(3)")); -} - - -/** - * Main function - * - * @return int - error status - */ -int onTick() { - // reset buffers before performing a full recalculation - if (!ValidBars) { - ArrayInitialize(main, EMPTY_VALUE); - SetIndicatorOptions(); - } - - // synchronize buffers with a shifted offline chart - if (ShiftedBars > 0) { - ShiftDoubleIndicatorBuffer(main, Bars, ShiftedBars, EMPTY_VALUE); - } - - // calculate start bar - int startbar = Min(MaxBarsBack-1, ChangedBars-1), trend, reversalOffset; - bool isReversal, isPosition; - double reversalUp, reversalDown, change; - - // recalculate changed bars - for (int bar=startbar; bar >= 0; bar--) { - if (!GetZigZagData(bar, trend, isReversal, reversalOffset, reversalUp, reversalDown)) { - return(last_error); - } - isPosition = (main[bar+1] != EMPTY_VALUE); - - if (isReversal) { // flip position - if (isPosition) { - if (trend > 0) { - change = reversalUp - Close[bar+1]; - main[bar] = main[bar+1] - change/pUnit; // close existing position - main[bar] += (Close[bar] - reversalUp)/pUnit; // open new position - } - else if (trend < 0) { - change = reversalDown - Close[bar+1]; - main[bar] = main[bar+1] + change/pUnit; // close existing position - main[bar] += (reversalDown - Close[bar])/pUnit; // open new position - } - else { - debug("onTick(0.1) cannot flip position with trend=0"); - } - } - else { - if (trend > 0) { - main[bar] = (Close[bar] - reversalUp)/pUnit; // open long - } - else if (trend < 0) { - main[bar] = (reversalDown - Close[bar])/pUnit; // open short - } - else { - debug("onTick(0.2) cannot open position with trend=0"); - } - } - } - else if (isPosition) { // update position - change = Close[bar] - Close[bar+1]; - if (trend > 0) { - main[bar] = main[bar+1] + change/pUnit; - } - else if (trend < 0) { - main[bar] = main[bar+1] - change/pUnit; - } - else { - debug("onTick(0.3) cannot update position with trend=0"); - } - } - else { // keep previous PnL - main[bar] = main[bar+1]; - } - } - return(last_error); -} - - -/** - * Get ZigZag buffer values at the specified bar offset. - * - * @param _In_ int bar - bar offset - * @param _Out_ int trend - trend at the specified bar (internal buffer MODE_TREND) - * @param _Out_ bool isReversal - whether the specified bar is a reversal bar - * @param _Out_ int reversalOffset - buffer ZigZag.MODE_REVERSAL_OFFSET at the specified bar - * @param _Out_ double reversalPriceUp - buffer ZigZag.MODE_UPPER_CROSS at the specified bar - * @param _Out_ double reversalPriceDown - buffer ZigZag.MODE_LOWER_CROSS at the specified bar - * - * @return bool - success status - */ -bool GetZigZagData(int bar, int &trend, bool &isReversal, int &reversalOffset, double &reversalPriceUp, double &reversalPriceDown) { - int combinedTrend = MathRound(icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_COMBINED_TREND, bar)); - reversalOffset = MathRound(icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_REVERSAL_OFFSET, bar)); - reversalPriceUp = 0; - reversalPriceDown = 0; - - trend = combinedTrend % 100000; - isReversal = (Abs(trend) == reversalOffset); - - if (isReversal) { - reversalPriceUp = icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_UPPER_CROSS, bar); - reversalPriceDown = icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_LOWER_CROSS, bar); - } - return(!last_error); -} - - -/** - * Set indicator options. After recompilation the function must be called from start() for options not to be ignored. - * - * @param bool redraw [optional] - whether to redraw the chart (default: no) - * - * @return bool - success status - */ -bool SetIndicatorOptions(bool redraw = false) { - redraw = redraw!=0; - - string indicatorName = WindowExpertName(); - IndicatorShortName(indicatorName); - - IndicatorBuffers(indicator_buffers); - SetIndexBuffer(MODE_MAIN, main); SetIndexEmptyValue(MODE_MAIN, EMPTY_VALUE); - IndicatorDigits(pDigits); - - SetIndexStyle(MODE_MAIN, DRAW_LINE, EMPTY, EMPTY, indicator_color1); - SetIndexLabel(MODE_MAIN, indicatorName); - - if (redraw) WindowRedraw(); - return(!catch("SetIndicatorOptions(1)")); -} - - -/** - * Return a string representation of all input parameters (for logging purposes). - * - * @return string - */ -string InputsToStr() { - return(StringConcatenate("MaxBarsBack=", MaxBarsBack, ";")); -} From 965cb1a5df972769def0ef2959281937f8c75785 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Sun, 22 Mar 2026 16:16:19 +0200 Subject: [PATCH 16/40] documentation --- mql40/include/rsf/api.mqh | 4 ++-- mql40/include/rsf/stdlib.mqh | 24 +++--------------------- mql40/indicators/Equity Recorder.mq4 | 4 ++-- mql40/libraries/rsfStdlib.mq4 | 24 ++++++++++++------------ 4 files changed, 19 insertions(+), 37 deletions(-) diff --git a/mql40/include/rsf/api.mqh b/mql40/include/rsf/api.mqh index cdb32b2cf..e6ecfb0fa 100644 --- a/mql40/include/rsf/api.mqh +++ b/mql40/include/rsf/api.mqh @@ -629,7 +629,7 @@ bool ChartMarker.OrderSent_A(int ticket, int digits, color markerColor);; bool ChartMarker.OrderSent_B(int ticket, int digits, color markerColor, int type, double lots, string symbol, datetime openTime, double openPrice, double stopLoss, double takeProfit, string comment);; bool ChartMarker.PositionClosed_A(int ticket, int digits, color markerColor);; bool ChartMarker.PositionClosed_B(int ticket, int digits, color markerColor, int type, double lots, string symbol, datetime openTime, double openPrice, datetime closeTime, double closePrice);; -int CreateRawSymbol(string name, string description, string group, int digits, string baseCurrency, string marginCurrency, string directory="");; +int CreateRawSymbol(string name, string description, string group, int digits, string baseCurrency, string marginCurrency, string hstDirectory = "");; string CreateTempFile(string path, string prefix="");; int DecreasePeriod(int period=0);; bool DeletePendingOrders(color markerColor=CLR_NONE);; @@ -670,7 +670,7 @@ string IntegerToBinaryStr(int integer);; string IntegerToHexStr(int integer);; bool IntInArray(int &haystack[], int needle);; string IntsToStr(int &array[], string separator);; -bool IsRawSymbol(string symbol, string directory="");; +bool IsRawSymbol(string symbol, string hstDirectory = "");; bool IsReverseIndexedBoolArray(bool &array[]);; bool IsReverseIndexedDoubleArray(double &array[]);; bool IsReverseIndexedIntArray(int &array[]);; diff --git a/mql40/include/rsf/stdlib.mqh b/mql40/include/rsf/stdlib.mqh index 1c032a5be..df3b55f30 100644 --- a/mql40/include/rsf/stdlib.mqh +++ b/mql40/include/rsf/stdlib.mqh @@ -79,60 +79,46 @@ double SumDoubles(double array[]); - // buffer functions int InitializeDoubleBuffer(double buffer[], int size); - string BufferToStr (int buffer[]); string BufferToHexStr(int buffer[]); - int BufferGetChar(int buffer[], int pos); //int BufferSetChar(int buffer[], int pos, int chr); - // configuration int GetIniSections(string fileName, string &names[]); - // date/time int GetTimezoneToGmtOffset(datetime time, string timezone); int GetGmtToTimezoneOffset(datetime time, string timezone); - int GetTimezoneToFxtOffset(datetime time, string timezone); int GetFxtToTimezoneOffset(datetime time, string timezone); - bool GetTimezoneTransitions(datetime time, int prev[], int next[]); - // colors color RGB(int red, int green, int blue); int RGBToHSL(color rgb, double hsl[], bool human = false); color HSLToRGB(double hsl[3]); color ModifyColor(color rgb, double hue, double saturation, double lightness); - // files, i/o string CreateTempFile(string path, string prefix=""); string GetTempPath(); - int FindFileNames(string pattern, string results[], int flags); int FileReadLines(string filename, string lines[], bool skipEmptyLines); - bool EditFile(string filename); bool EditFiles(string filenames[]); - // locking bool AquireLock(string mutex); bool ReleaseLock(string mutex); - // strings bool DoubleQuoteStrings(string &array[]); bool SortStrings(string &values[]); string StringPad(string input, int length, string pad_string, int pad_type); - // trade functions and order handling bool IsTemporaryTradeError(int error); @@ -159,7 +145,6 @@ bool ChartMarker.PositionClosed_A(int ticket, int digits, color markerColor); bool ChartMarker.PositionClosed_B(int ticket, int digits, color markerColor, int type, double lots, string symbol, datetime openTime, double openPrice, datetime closeTime, double closePrice); - // toString helpers string CharToHexStr(int chr); string DoubleToStrEx(double value, int digits/*=0..16*/); @@ -167,7 +152,6 @@ string IntegerToHexStr(int decimal); string WaitForSingleObjectValueToStr(int value); string WordToHexStr(int word); - string BoolsToStr (bool array[], string separator); string CharsToStr (int array[], string separator); string DoublesToStr (double array[], string separator); @@ -183,23 +167,21 @@ string TicketsToStr.Position (int array[]); string TimesToStr (datetime array[], string separator); + // history management + int CreateRawSymbol(string name, string description, string group, int digits, string baseCurrency, string marginCurrency, string hstDirectory = ""); + bool IsRawSymbol(string symbol, string hstDirectory = ""); // other - int CreateRawSymbol(string name, string description, string group, int digits, string baseCurrency, string marginCurrency, string directory = ""); - bool IsRawSymbol(string symbol, string directory = ""); string GetSymbolName(string symbol); // alias of GetSymbolNameOrAlt(symbol, symbol) string GetSymbolNameOrAlt(string symbol, string altName); string GetSymbolNameStrict(string symbol); string GetLongSymbolName(string symbol); // alias of GetLongSymbolNameOrAlt(symbol, symbol) string GetLongSymbolNameOrAlt(string symbol, string altValue); string GetLongSymbolNameStrict(string symbol); - int IncreasePeriod(int period); int DecreasePeriod(int period); - bool SortOpenTickets(int &keys[][]); int SortTicketsChronological(int &tickets[]); - string GetWindowsShortcutTarget(string lnkFile); int WinExecWait(string cmdLine, int cmdShow); #import diff --git a/mql40/indicators/Equity Recorder.mq4 b/mql40/indicators/Equity Recorder.mq4 index d2f8e2848..2fa016f95 100644 --- a/mql40/indicators/Equity Recorder.mq4 +++ b/mql40/indicators/Equity Recorder.mq4 @@ -200,8 +200,8 @@ bool RecordEquity() { int dow = TimeDayOfWeekEx(now); if (dow==SATURDAY || dow==SUNDAY) { - if (!isOpenPosition || !prevEquity[0]) return(true); - bool isStale = (lastTickTime < GetServerTime()-2*MINUTES); + if (!isOpenPosition || !prevEquity[0]) return(true); + bool isStale = (lastTickTime < GetServerTime() - 2*MINUTES); if (isStale && EQ(currEquity[0], prevEquity[0], 2)) return(true); } diff --git a/mql40/libraries/rsfStdlib.mq4 b/mql40/libraries/rsfStdlib.mq4 index a2b678347..4b27717b0 100644 --- a/mql40/libraries/rsfStdlib.mq4 +++ b/mql40/libraries/rsfStdlib.mq4 @@ -7272,10 +7272,10 @@ string CreateTempFile(string path, string prefix="") { /** - * Whether a symbol exists in "symbols.raw" of a directory. + * Whether a symbol exists in "symbols.raw" of a history directory. * * @param string symbol - symbol - * @param string directory [optional] - directory, if: + * @param string directory [optional] - history directory, if: * empty: the current trade server directory (default) * relative path: relative to the MQL sandbox directory * absolute path: as is @@ -7340,7 +7340,7 @@ bool IsRawSymbol(string symbol, string directory = "") { /** - * Create a raw symbol and add it to "symbols.raw" of the specified directory. + * Create a new symbol and add it to "symbols.raw" of the specified history directory. * * @param string symbol - symbol * @param string descr - symbol description @@ -7362,8 +7362,6 @@ int CreateRawSymbol(string symbol, string descr, string group, int digits, strin if (StringLen(group) > MAX_SYMBOL_GROUP_LENGTH) return(_EMPTY(catch("CreateRawSymbol(4) invalid parameter group: "+ DoubleQuoteStr(group) +" (max "+ MAX_SYMBOL_GROUP_LENGTH +" chars)", ERR_INVALID_PARAMETER))); if (directory == "0") directory = ""; // (string) NULL - if (IsLogInfo()) logInfo("CreateRawSymbol(5) creating symbol \""+ directory + ifString(directory=="", "", "/") + symbol +"\" (group \""+ group +"\")"); - int groupIndex; color groupColor = CLR_NONE; @@ -7387,16 +7385,18 @@ int CreateRawSymbol(string symbol, string descr, string group, int digits, strin // create symbol /*SYMBOL*/int iSymbol[]; InitializeByteBuffer(iSymbol, SYMBOL_size); if (!SetRawSymbolTemplate (iSymbol, SYMBOL_TYPE_INDEX)) return(-1); - if (!StringLen(symbol_SetName (iSymbol, symbol ))) return(_EMPTY(catch("CreateRawSymbol(6)->symbol_SetName() => NULL", ERR_RUNTIME_ERROR))); - if (!StringLen(symbol_SetDescription (iSymbol, descr ))) return(_EMPTY(catch("CreateRawSymbol(7)->symbol_SetDescription() => NULL", ERR_RUNTIME_ERROR))); - if ( !symbol_SetDigits (iSymbol, digits )) return(_EMPTY(catch("CreateRawSymbol(8)->symbol_SetDigits() => FALSE", ERR_RUNTIME_ERROR))); - if (!StringLen(symbol_SetBaseCurrency (iSymbol, baseCurrency ))) return(_EMPTY(catch("CreateRawSymbol(9)->symbol_SetBaseCurrency() => NULL", ERR_RUNTIME_ERROR))); - if (!StringLen(symbol_SetMarginCurrency (iSymbol, marginCurrency))) return(_EMPTY(catch("CreateRawSymbol(10)->symbol_SetMarginCurrency() => NULL", ERR_RUNTIME_ERROR))); - if ( symbol_SetGroup (iSymbol, groupIndex ) < 0) return(_EMPTY(catch("CreateRawSymbol(11)->symbol_SetGroup() => -1", ERR_RUNTIME_ERROR))); - if ( symbol_SetBackgroundColor(iSymbol, groupColor ) == CLR_NONE) return(_EMPTY(catch("CreateRawSymbol(12)->symbol_SetBackgroundColor() => CLR_NONE", ERR_RUNTIME_ERROR))); + if (!StringLen(symbol_SetName (iSymbol, symbol ))) return(_EMPTY(catch("CreateRawSymbol(5)->symbol_SetName() => NULL", ERR_RUNTIME_ERROR))); + if (!StringLen(symbol_SetDescription (iSymbol, descr ))) return(_EMPTY(catch("CreateRawSymbol(6)->symbol_SetDescription() => NULL", ERR_RUNTIME_ERROR))); + if ( !symbol_SetDigits (iSymbol, digits )) return(_EMPTY(catch("CreateRawSymbol(7)->symbol_SetDigits() => FALSE", ERR_RUNTIME_ERROR))); + if (!StringLen(symbol_SetBaseCurrency (iSymbol, baseCurrency ))) return(_EMPTY(catch("CreateRawSymbol(8)->symbol_SetBaseCurrency() => NULL", ERR_RUNTIME_ERROR))); + if (!StringLen(symbol_SetMarginCurrency (iSymbol, marginCurrency))) return(_EMPTY(catch("CreateRawSymbol(9)->symbol_SetMarginCurrency() => NULL", ERR_RUNTIME_ERROR))); + if ( symbol_SetGroup (iSymbol, groupIndex ) < 0) return(_EMPTY(catch("CreateRawSymbol(10)->symbol_SetGroup() => -1", ERR_RUNTIME_ERROR))); + if ( symbol_SetBackgroundColor(iSymbol, groupColor ) == CLR_NONE) return(_EMPTY(catch("CreateRawSymbol(11)->symbol_SetBackgroundColor() => CLR_NONE", ERR_RUNTIME_ERROR))); // insert it into "symbols.raw" if (!InsertRawSymbol(iSymbol, directory)) return(-1); + + if (IsLogInfo()) logInfo("CreateRawSymbol(12) created symbol \""+ directory + ifString(directory=="", "", "/") + symbol +"\" (group \""+ group +"\")"); return(symbol_Id(iSymbol)); } From 66babe6a062253432801b40b30f0cd0dcf0f8bf9 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Sun, 22 Mar 2026 16:16:36 +0200 Subject: [PATCH 17/40] wip: working prototype --- mql40/indicators/ZigZag.mq4 | 162 ++++++++++++++++++++++++++++++++---- 1 file changed, 146 insertions(+), 16 deletions(-) diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index 5a590f869..a0fb41576 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -123,15 +123,18 @@ bool ProjectNextBalance = false; // whether to project zero-balance #include #include #include +#include +#include #include #include #include +#include +#include #include #include #include #include -#include -#include + // indicator buffer ids #define MODE_UPPER_BAND ZigZag.MODE_UPPER_BAND // 0: upper channel band: positive or 0 @@ -216,6 +219,19 @@ datetime skipSignals; // skip signals u datetime lastTick; int lastSoundSignal; // GetTickCount() value of the last audio signal + +// recorder status +bool recorder.initialized; +string recorder.hstDirectory = ""; +int recorder.hstFormat; +string recorder.symbol = ""; +string recorder.symbolDescr = ""; +string recorder.group = ""; +int recorder.priceBase; +int recorder.hSet; +datetime recorder.startTime; + + // signal direction types #define D_LONG TRADE_DIRECTION_LONG // 1 #define D_SHORT TRADE_DIRECTION_SHORT // 2 @@ -482,6 +498,8 @@ int onInit() { // TrackSignalPerformance if (AutoConfiguration) TrackSignalPerformance = GetConfigBool(indicator, "TrackSignalPerformance", TrackSignalPerformance); + if (__isSuperContext) TrackSignalPerformance = false; + if (__isTesting) TrackSignalPerformance = false; // TrackSignalPerformance.Since datetime dtValue = TrackSignalPerformance.Since; if (AutoConfiguration) { @@ -537,6 +555,13 @@ int onDeinit() { int id = __tickTimerId; __tickTimerId = NULL; if (!ReleaseTickTimer(id)) return(catch("onDeinit(1)->ReleaseTickTimer(timerId="+ id +") failed", ERR_RUNTIME_ERROR)); } + + // close an open history set + if (recorder.hSet != 0) { + int tmp = recorder.hSet; + recorder.hSet = NULL; + if (!HistorySet1.Close(tmp)) return(ERR_RUNTIME_ERROR); + } return(catch("onDeinit(2)")); } @@ -607,6 +632,7 @@ int onTick() { // calculate start bar int startBar = Min(MaxBarsBack-1, ChangedBars-1, Bars-ZigZag.Periods); if (startBar < 0 && MaxBarsBack) return(logInfo("onTick(1) Tick="+ Ticks +" Bars="+ Bars +" needed="+ ZigZag.Periods, ERR_HISTORY_INSUFFICIENT)); + if (!ValidBars) recorder.startTime = Time[startBar]; // recalculate changed bars if (startBar > 2) { // TODO: why 2 and not 1 @@ -674,10 +700,13 @@ int onTick() { else ProcessLowerCross(bar); // whether the current bar is a reversal bar (not whether the current tick triggered a reversal) - bool isReversalBar = (Abs(trend[bar]) == reversalOffset[bar]); + bool isReversalBar = false; + if (!unknownTrend[bar]) { + isReversalBar = (Abs(trend[bar]) == reversalOffset[bar]); + } // calculate signal performance - if (TrackSignalPerformance && !__isSuperContext && !__isTesting) { + if (TrackSignalPerformance) { RecalculateSignalPerformance(bar, isReversalBar); } @@ -719,7 +748,7 @@ int onTick() { if (ShowChartLegend) UpdateChartLegend(); // record signal performance - if (TrackSignalPerformance && !__isTesting) { + if (TrackSignalPerformance) { RecordSignalPerformance(); } @@ -876,7 +905,7 @@ int onTick() { * @return bool - success status */ bool RecalculateSignalPerformance(int bar, bool isReversal) { - isReversal = (isReversal!=0); + isReversal = (isReversal != 0); bool isPosition = (signalPerformance[bar+1] != EMPTY_VALUE); double change; @@ -894,7 +923,7 @@ bool RecalculateSignalPerformance(int bar, bool isReversal) { signalPerformance[bar] += lowerCross[bar] - Close[bar]; // open new short position } else { - debug("RecalculateSignalPerformance(0.1) cannot yet flip position with trend = 0"); + logWarn("RecalculateSignalPerformance(0.1) bar="+ bar +" "+ TimeToStr(Time[bar]) +" cannot yet flip position with trend=0"); } } else { // open a new position @@ -905,10 +934,9 @@ bool RecalculateSignalPerformance(int bar, bool isReversal) { signalPerformance[bar] = lowerCross[bar] - Close[bar]; // short position } else { - debug("RecalculateSignalPerformance(0.2) cannot yet open position with trend = 0"); + logWarn("RecalculateSignalPerformance(0.2) bar="+ bar +" "+ TimeToStr(Time[bar]) +" cannot yet open position with trend=0"); } } - if (debugging && Ticks==1) debug("RecalculateSignalPerformance(0.3) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" reversalBar balance="+ DoubleToStr(signalPerformance[bar]/pUnit, pDigits)); } // or update the position @@ -921,7 +949,7 @@ bool RecalculateSignalPerformance(int bar, bool isReversal) { signalPerformance[bar] = signalPerformance[bar+1] - change; } else { - debug("RecalculateSignalPerformance(0.4) cannot yet update position with trend = 0"); + logWarn("RecalculateSignalPerformance(0.4) bar="+ bar +" "+ TimeToStr(Time[bar]) +" cannot yet update position with trend=0"); } } @@ -934,19 +962,121 @@ bool RecalculateSignalPerformance(int bar, bool isReversal) { /** - * Record the signal performance for all changed bars. + * Record the signal performance for the specified bar. + * + * @param int bar - bar offset * * @return bool - success status */ -bool RecordSignalPerformance() { - //if (!recorder.initialized) { - // if (recorder.mode == RECORDER_OFF) return(_true(Recorder_off())); - // if (!Recorder_init()) return(_false(Recorder_off())); - //} +bool RecordSignalPerformance(int _bar = 0) { + if (!recorder.initialized) { + // create symbol and group + recorder.symbol = Symbol() +".zzr"; + recorder.symbolDescr = "ZigZag reversal"; + recorder.group = "ZigZag reversal"; + recorder.hstDirectory = Recorder_GetHstDirectory(); + recorder.hstFormat = Recorder_GetHstFormat(); + if (last_error != NULL) return(false); + + if (!IsRawSymbol(recorder.symbol, recorder.hstDirectory)) { + int symbolId = CreateRawSymbol(recorder.symbol, recorder.symbolDescr, recorder.group, pDigits, AccountCurrency(), AccountCurrency(), recorder.hstDirectory); + if (symbolId < 0) return(false); + } + + // open HistorySet + if (!recorder.hSet) { + recorder.hSet = HistorySet1.Get(recorder.symbol, recorder.hstDirectory); + if (recorder.hSet == -1) { + recorder.hSet = HistorySet1.Create(recorder.symbol, recorder.symbolDescr, pDigits, recorder.hstFormat, recorder.hstDirectory); + } + if (!recorder.hSet) return(false); + } + recorder.initialized = true; + debug("RecordSignalPerformance(0.1) Tick="+ Ticks +" recorder initialized"); + } + + int startBar = 0, flags = HST_BUFFER_TICKS|HST_FILL_GAPS; + + if (ChangedBars > 2) { // rewrite the full history (intentionally skip rewriting bar 1 on BarOpen) + if (recorder.hSet != 0) { + int tmp = recorder.hSet; + recorder.hSet = NULL; + if (!HistorySet1.Close(tmp)) return(false); // TODO: HistorySet.Create() should auto-close an open set but errors + } + startBar = iBarShiftNext(NULL, NULL, recorder.startTime); + debug("RecordSignalPerformance(0.2) Tick="+ Ticks +" rewriting all history since "+ TimeToStr(recorder.startTime) +" (bar "+ startBar +")"); + } + + for (int bar=startBar; bar > 0; bar--) { + if (!recorder.hSet) { + recorder.hSet = HistorySet1.Create(recorder.symbol, recorder.symbolDescr, pDigits, recorder.hstFormat, recorder.hstDirectory); + if (!recorder.hSet) return(false); + } + double value = signalPerformance[bar] + recorder.priceBase; + + if (value <= 0) { + switch(recorder.priceBase) { + case 0: recorder.priceBase = 1; break; + case 1: recorder.priceBase = 10; break; + case 10: recorder.priceBase = 100; break; + case 100: recorder.priceBase = 1000; break; + case 1000: recorder.priceBase = 10000; break; + case 10000: recorder.priceBase = 100000; break; + case 100000: recorder.priceBase = 1000000; break; + case 1000000: recorder.priceBase = 10000000; break; + } + debug("RecordSignalPerformance(0.3) Tick="+ Ticks +" updated price base to "+ DoubleToStr(recorder.priceBase, 2)); + + tmp = recorder.hSet; + recorder.hSet = NULL; + if (!HistorySet1.Close(tmp)) return(false); + startBar = iBarShiftNext(NULL, NULL, recorder.startTime); + bar = startBar + 1; + continue; + } + + if (bar == 0) flags = HST_FILL_GAPS; + else flags = HST_FILL_GAPS|HST_BUFFER_TICKS; + if (!HistorySet1.AddTick(recorder.hSet, Time[bar], value, flags)) return(false); + } return(true); } +/** + * Resolve the history directory for recorded timeseries. + * + * @return string - directory or an empty string in case of errors + */ +string Recorder_GetHstDirectory() { + string section = "SignalPerformance"; + string key = "HistoryDirectory", sValue=""; + + if (IsConfigKey(section, key)) { + sValue = GetConfigString(section, key, ""); + } + if (!StringLen(sValue)) return(_EMPTY_STR(catch("Recorder_GetHstDirectory(1) missing config value ["+ section +"]->"+ key, ERR_INVALID_CONFIG_VALUE))); + return(sValue); +} + + +/** + * Resolve the history format for recorded timeseries. + * + * @return int - history format or NULL (0) in case of errors + */ +int Recorder_GetHstFormat() { + string section = "SignalPerformance"; + string key = "HistoryFormat", sValue=""; + + if (IsConfigKey(section, key)) { + int iValue = GetConfigInt(section, key, 0); + } + if (iValue!=400 && iValue!=401) return(!catch("Recorder_GetHstFormat(1) invalid config value ["+ section +"]->"+ key +": "+ iValue +" (must be 400 or 401)", ERR_INVALID_CONFIG_VALUE)); + return(iValue); +} + + /** * Calculate the balance marker offset for the current view port of the chart. * From a9d51b0170394bba27edca18e88af3e21e7c6f6b Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Mon, 23 Mar 2026 01:12:18 +0200 Subject: [PATCH 18/40] fix PnL calculation --- mql40/indicators/ZigZag.mq4 | 70 +++++++++++++++++++++---------------- 1 file changed, 39 insertions(+), 31 deletions(-) diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index a0fb41576..49c14028a 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -906,57 +906,65 @@ int onTick() { */ bool RecalculateSignalPerformance(int bar, bool isReversal) { isReversal = (isReversal != 0); - bool isPosition = (signalPerformance[bar+1] != EMPTY_VALUE); - double change; + + bool isPosition; + if (signalPerformance[bar+1] == EMPTY_VALUE) { + isPosition = false; + signalPerformance[bar] = 0; + } + else { + isPosition = true; + signalPerformance[bar] = signalPerformance[bar+1]; + } // either flip the position if (isReversal) { - if (isPosition) { - if (trend[bar] > 0) { - change = upperCross[bar] - Close[bar+1]; - signalPerformance[bar] = signalPerformance[bar+1] - change; // close existing short position - signalPerformance[bar] += Close[bar] - upperCross[bar]; // open new long position - } - else if (trend[bar] < 0) { - change = lowerCross[bar] - Close[bar+1]; - signalPerformance[bar] = signalPerformance[bar+1] + change; // close existing long position - signalPerformance[bar] += lowerCross[bar] - Close[bar]; // open new short position - } - else { - logWarn("RecalculateSignalPerformance(0.1) bar="+ bar +" "+ TimeToStr(Time[bar]) +" cannot yet flip position with trend=0"); + if (trend[bar] > 0) { + if (isPosition) { + signalPerformance[bar] -= (upperCross[bar] - Close[bar+1]); // close existing short position } + signalPerformance[bar] += (Close[bar] - upperCross[bar]); // open new long position } - else { // open a new position - if (trend[bar] > 0) { - signalPerformance[bar] = Close[bar] - upperCross[bar]; // long position + else if (trend[bar] < 0) { + if (isPosition) { + signalPerformance[bar] += (lowerCross[bar] - Close[bar+1]); // close existing long position } - else if (trend[bar] < 0) { - signalPerformance[bar] = lowerCross[bar] - Close[bar]; // short position + signalPerformance[bar] += (lowerCross[bar] - Close[bar]); // open new short position + } + else /*trend == 0*/{ + // double crossing with two semaphores (at the moment of bar processing) + if (semaphoreOpen[bar] == Low[bar]) { // crossing order "Low, High" + if (isPosition) { + signalPerformance[bar] += (lowerCross[bar] - Close[bar+1]); // close existing long position + } + signalPerformance[bar] -= (upperCross[bar] - lowerCross[bar]); // open new short position and immediately close it + signalPerformance[bar] += (Close[bar] - upperCross[bar]); // open new long position } - else { - logWarn("RecalculateSignalPerformance(0.2) bar="+ bar +" "+ TimeToStr(Time[bar]) +" cannot yet open position with trend=0"); + else if (semaphoreOpen[bar] == High[bar]) { // crossing order "High, Low" + if (isPosition) { + signalPerformance[bar] -= (upperCross[bar] - Close[bar+1]); // close existing short position + } + signalPerformance[bar] -= (upperCross[bar] - lowerCross[bar]); // open new long position and immediately close it + signalPerformance[bar] += (lowerCross[bar] - Close[bar]); // open new short position } + else return(!catch("RecalculateSignalPerformance(1) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected reversal bar with trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); } } // or update the position else if (isPosition) { - change = Close[bar] - Close[bar+1]; if (trend[bar] > 0) { - signalPerformance[bar] = signalPerformance[bar+1] + change; + signalPerformance[bar] += (Close[bar] - Close[bar+1]); } else if (trend[bar] < 0) { - signalPerformance[bar] = signalPerformance[bar+1] - change; - } - else { - logWarn("RecalculateSignalPerformance(0.4) bar="+ bar +" "+ TimeToStr(Time[bar]) +" cannot yet update position with trend=0"); + signalPerformance[bar] -= (Close[bar] - Close[bar+1]); } + else return(!catch("RecalculateSignalPerformance(2) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected non-reversal bar with trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); } // or keep existing PnL - else { - signalPerformance[bar] = signalPerformance[bar+1]; - } + //else {} + return(true); } From 8cf1fc5ced7ca620170667a0e7231d157495f20a Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Mon, 23 Mar 2026 13:21:34 +0200 Subject: [PATCH 19/40] fix PnL update of bar 0 (zero) --- mql40/indicators/ZigZag.mq4 | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index 49c14028a..af7695715 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -707,7 +707,7 @@ int onTick() { // calculate signal performance if (TrackSignalPerformance) { - RecalculateSignalPerformance(bar, isReversalBar); + if (!RecalculateSignalPerformance(bar, isReversalBar)) return(last_error); } if (debugging && Ticks == 1) { @@ -749,7 +749,7 @@ int onTick() { // record signal performance if (TrackSignalPerformance) { - RecordSignalPerformance(); + if (!RecordSignalPerformance()) return(last_error); } // detect ZigZag breakouts (comparing legs against bands also detects breakouts on missed ticks) @@ -907,18 +907,13 @@ int onTick() { bool RecalculateSignalPerformance(int bar, bool isReversal) { isReversal = (isReversal != 0); - bool isPosition; - if (signalPerformance[bar+1] == EMPTY_VALUE) { - isPosition = false; - signalPerformance[bar] = 0; - } - else { - isPosition = true; - signalPerformance[bar] = signalPerformance[bar+1]; - } + bool isPosition = (signalPerformance[bar+1] != EMPTY_VALUE); + signalPerformance[bar] = signalPerformance[bar+1]; // either flip the position if (isReversal) { + if (!isPosition) signalPerformance[bar] = 0; + if (trend[bar] > 0) { if (isPosition) { signalPerformance[bar] -= (upperCross[bar] - Close[bar+1]); // close existing short position @@ -959,7 +954,9 @@ bool RecalculateSignalPerformance(int bar, bool isReversal) { else if (trend[bar] < 0) { signalPerformance[bar] -= (Close[bar] - Close[bar+1]); } - else return(!catch("RecalculateSignalPerformance(2) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected non-reversal bar with trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); + else { + return(!catch("RecalculateSignalPerformance(2) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected non-reversal bar with trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); + } } // or keep existing PnL @@ -1015,7 +1012,7 @@ bool RecordSignalPerformance(int _bar = 0) { debug("RecordSignalPerformance(0.2) Tick="+ Ticks +" rewriting all history since "+ TimeToStr(recorder.startTime) +" (bar "+ startBar +")"); } - for (int bar=startBar; bar > 0; bar--) { + for (int bar=startBar; bar >= 0; bar--) { if (!recorder.hSet) { recorder.hSet = HistorySet1.Create(recorder.symbol, recorder.symbolDescr, pDigits, recorder.hstFormat, recorder.hstDirectory); if (!recorder.hSet) return(false); From 27f593a31231eb316bf49c53c6c39999726901a8 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Mon, 23 Mar 2026 15:55:34 +0200 Subject: [PATCH 20/40] calculate and store OHLC of signal performance bars --- mql40/indicators/ZigZag.mq4 | 220 +++++++++++++++++++++--------------- 1 file changed, 131 insertions(+), 89 deletions(-) diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index af7695715..c90597cc1 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -137,24 +137,27 @@ bool ProjectNextBalance = false; // whether to project zero-balance // indicator buffer ids -#define MODE_UPPER_BAND ZigZag.MODE_UPPER_BAND // 0: upper channel band: positive or 0 -#define MODE_LOWER_BAND ZigZag.MODE_LOWER_BAND // 1: lower channel band: positive or 0 -#define MODE_SEMAPHORE_OPEN ZigZag.MODE_SEMAPHORE_OPEN // 2: final semaphores, open price: positive or 0 -#define MODE_SEMAPHORE_CLOSE ZigZag.MODE_SEMAPHORE_CLOSE // 3: final semaphores, close price: positive or 0 (if open != close it forms a vertical line segment) -#define MODE_UPPER_CROSS ZigZag.MODE_UPPER_CROSS // 4: upper channel band crossings: positive or 0 -#define MODE_LOWER_CROSS ZigZag.MODE_LOWER_CROSS // 5: lower channel band crossings: positive or 0 -#define MODE_REVERSAL_OFFSET ZigZag.MODE_REVERSAL_OFFSET // 6: int: offset of the ZigZag reversal to the leg's start semaphore: non-negative or -1 -#define MODE_COMBINED_TREND ZigZag.MODE_COMBINED_TREND // 7: int: combined buffers MODE_TREND & MODE_UNKNOWN_TREND: positive/negative or 0 -#define MODE_UPPER_CROSS_HIGH 8 // 8: bar High of an upper channel band crossing: positive or 0 -#define MODE_LOWER_CROSS_LOW 9 // 9: bar Low of a lower channel band crossing: positive or 0 -#define MODE_TREND 10 // 10: int: length of a ZigZag leg: positive/negative or 0 -#define MODE_UNKNOWN_TREND 11 // 11: int: number of bars after a leg's end semaphore: non-negative or -1 -#define MODE_SIGNAL_PERFORMANCE 12 // 12: accumulated signal performance in price units: positive/negative or EMPTY_VALUE +#define MODE_UPPER_BAND ZigZag.MODE_UPPER_BAND // 0: upper channel band: positive or 0 +#define MODE_LOWER_BAND ZigZag.MODE_LOWER_BAND // 1: lower channel band: positive or 0 +#define MODE_SEMAPHORE_OPEN ZigZag.MODE_SEMAPHORE_OPEN // 2: final semaphores, open price: positive or 0 +#define MODE_SEMAPHORE_CLOSE ZigZag.MODE_SEMAPHORE_CLOSE // 3: final semaphores, close price: positive or 0 (if open != close it forms a vertical line segment) +#define MODE_UPPER_CROSS ZigZag.MODE_UPPER_CROSS // 4: upper channel band crossings: positive or 0 +#define MODE_LOWER_CROSS ZigZag.MODE_LOWER_CROSS // 5: lower channel band crossings: positive or 0 +#define MODE_REVERSAL_OFFSET ZigZag.MODE_REVERSAL_OFFSET // 6: int: offset of the ZigZag reversal to the leg's start semaphore: non-negative or -1 +#define MODE_COMBINED_TREND ZigZag.MODE_COMBINED_TREND // 7: int: combined buffers MODE_TREND & MODE_UNKNOWN_TREND: positive/negative or 0 +#define MODE_UPPER_CROSS_HIGH 8 // 8: bar High of an upper channel band crossing: positive or 0 +#define MODE_LOWER_CROSS_LOW 9 // 9: bar Low of a lower channel band crossing: positive or 0 +#define MODE_TREND 10 // 10: int: length of a ZigZag leg: positive/negative or 0 +#define MODE_UNKNOWN_TREND 11 // 11: int: number of bars after a leg's end semaphore: non-negative or -1 +#define MODE_SIGNAL_PERFORMANCE_O 12 // 12: signal performance in price units: positive/negative or EMPTY_VALUE +#define MODE_SIGNAL_PERFORMANCE_H 13 // 13: ... +#define MODE_SIGNAL_PERFORMANCE_L 14 // 14: ... +#define MODE_SIGNAL_PERFORMANCE_C 15 // 15: ... #property indicator_chart_window #property indicator_buffers 8 // visible buffers int terminal_buffers = 8; // buffers managed by the terminal -int framework_buffers = 5; // buffers managed by the framework +int framework_buffers = 8; // buffers managed by the framework #property indicator_color1 Blue // upper channel band #property indicator_style1 STYLE_DOT // @@ -173,19 +176,22 @@ int framework_buffers = 5; // buffers manage #property indicator_color7 CLR_NONE // trend (combined buffers MODE_TREND & MODE_UNKNOWN_TREND) #property indicator_color8 CLR_NONE // offset of the previous ZigZag reversal to its preceeding semaphore -double upperBand []; // upper channel band: positive or 0 -double lowerBand []; // lower channel band: positive or 0 -double upperCross []; // upper channel band crossings: positive or 0 -double upperCrossHigh []; // bar High of an upper channel band crossing (potential semaphore): positive or 0 -double lowerCross []; // lower channel band crossings: positive or 0 -double lowerCrossLow []; // bar Low of a lower channel band crossing (potential semaphore): positive or 0 -double semaphoreOpen []; // final semaphore, open price: positive or 0 -double semaphoreClose []; // final semaphore, close price: positive or 0 (if open != close it creates a vertical line segment) -double reversalOffset []; // int: offset of the ZigZag reversal to the leg's start semaphore (): non-negative or -1 -int trend []; // int: length of a ZigZag leg: positive/negative or 0 -int unknownTrend []; // int: number of bars after a leg's end semaphore: non-negative or -1 -double combinedTrend []; // int: combined buffers MODE_TREND & MODE_UNKNOWN_TREND: positive/negative or 0 -double signalPerformance[]; // accumulated signal performance in price units: positive/negative or EMPTY_VALUE +double upperBand []; // upper channel band: positive or 0 +double lowerBand []; // lower channel band: positive or 0 +double upperCross []; // upper channel band crossings: positive or 0 +double upperCrossHigh []; // bar High of an upper channel band crossing (potential semaphore): positive or 0 +double lowerCross []; // lower channel band crossings: positive or 0 +double lowerCrossLow []; // bar Low of a lower channel band crossing (potential semaphore): positive or 0 +double semaphoreOpen []; // final semaphore, open price: positive or 0 +double semaphoreClose []; // final semaphore, close price: positive or 0 (if open != close it creates a vertical line segment) +double reversalOffset []; // int: offset of the ZigZag reversal to the leg's start semaphore (): non-negative or -1 +int trend []; // int: length of a ZigZag leg: positive/negative or 0 +int unknownTrend []; // int: number of bars after a leg's end semaphore: non-negative or -1 +double combinedTrend []; // int: combined buffers MODE_TREND & MODE_UNKNOWN_TREND: positive/negative or 0 +double signalPerformanceO[]; // signal performance in price units: positive/negative or EMPTY_VALUE +double signalPerformanceH[]; // ... +double signalPerformanceL[]; // ... +double signalPerformanceC[]; // ... string indicatorName = ""; string shortName = ""; @@ -578,27 +584,33 @@ int onTick() { } // manage additional fraemwork buffers - ManageDoubleIndicatorBuffer(MODE_UPPER_CROSS_HIGH, upperCrossHigh ); - ManageDoubleIndicatorBuffer(MODE_LOWER_CROSS_LOW, lowerCrossLow ); - ManageIntIndicatorBuffer (MODE_TREND, trend ); - ManageIntIndicatorBuffer (MODE_UNKNOWN_TREND, unknownTrend, -1); - ManageDoubleIndicatorBuffer(MODE_SIGNAL_PERFORMANCE, signalPerformance, EMPTY_VALUE); + ManageDoubleIndicatorBuffer(MODE_UPPER_CROSS_HIGH, upperCrossHigh); + ManageDoubleIndicatorBuffer(MODE_LOWER_CROSS_LOW, lowerCrossLow); + ManageIntIndicatorBuffer (MODE_TREND, trend); + ManageIntIndicatorBuffer (MODE_UNKNOWN_TREND, unknownTrend, -1); + ManageDoubleIndicatorBuffer(MODE_SIGNAL_PERFORMANCE_O, signalPerformanceO, EMPTY_VALUE); + ManageDoubleIndicatorBuffer(MODE_SIGNAL_PERFORMANCE_H, signalPerformanceH, EMPTY_VALUE); + ManageDoubleIndicatorBuffer(MODE_SIGNAL_PERFORMANCE_L, signalPerformanceL, EMPTY_VALUE); + ManageDoubleIndicatorBuffer(MODE_SIGNAL_PERFORMANCE_C, signalPerformanceC, EMPTY_VALUE); // reset buffers before performing a full recalculation if (!ValidBars) { - ArrayInitialize(upperBand, 0); // double: positive or 0 - ArrayInitialize(lowerBand, 0); // double: positive or 0 - ArrayInitialize(upperCross, 0); // double: positive or 0 - ArrayInitialize(upperCrossHigh, 0); // double: positive or 0 - ArrayInitialize(lowerCross, 0); // double: positive or 0 - ArrayInitialize(lowerCrossLow, 0); // double: positive or 0 - ArrayInitialize(semaphoreOpen, 0); // double: positive or 0 - ArrayInitialize(semaphoreClose, 0); // double: positive or 0 - ArrayInitialize(reversalOffset, -1); // int: non-negative or -1 - ArrayInitialize(trend, 0); // int: positive/negative or 0 - ArrayInitialize(unknownTrend, -1); // int: non-negative or -1 - ArrayInitialize(combinedTrend, 0); // int: positive/negative or 0 - ArrayInitialize(signalPerformance, EMPTY_VALUE); // double: positive/negative or EMPTY_VALUE + ArrayInitialize(upperBand, 0); // double: positive or 0 + ArrayInitialize(lowerBand, 0); // double: positive or 0 + ArrayInitialize(upperCross, 0); // double: positive or 0 + ArrayInitialize(upperCrossHigh, 0); // double: positive or 0 + ArrayInitialize(lowerCross, 0); // double: positive or 0 + ArrayInitialize(lowerCrossLow, 0); // double: positive or 0 + ArrayInitialize(semaphoreOpen, 0); // double: positive or 0 + ArrayInitialize(semaphoreClose, 0); // double: positive or 0 + ArrayInitialize(reversalOffset, -1); // int: non-negative or -1 + ArrayInitialize(trend, 0); // int: positive/negative or 0 + ArrayInitialize(unknownTrend, -1); // int: non-negative or -1 + ArrayInitialize(combinedTrend, 0); // int: positive/negative or 0 + ArrayInitialize(signalPerformanceO, EMPTY_VALUE); // double: positive/negative or EMPTY_VALUE + ArrayInitialize(signalPerformanceH, EMPTY_VALUE); // double: positive/negative or EMPTY_VALUE + ArrayInitialize(signalPerformanceL, EMPTY_VALUE); // double: positive/negative or EMPTY_VALUE + ArrayInitialize(signalPerformanceC, EMPTY_VALUE); // double: positive/negative or EMPTY_VALUE lastUpperBand = 0; lastLowerBand = 0; lastLegHigh = 0; @@ -611,19 +623,22 @@ int onTick() { // synchronize buffers with a shifted offline chart if (ShiftedBars > 0) { - ShiftDoubleIndicatorBuffer(upperBand, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(lowerBand, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(upperCross, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(upperCrossHigh, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(lowerCross, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(lowerCrossLow, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(semaphoreOpen, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(semaphoreClose, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(reversalOffset, Bars, ShiftedBars, -1); - ShiftIntIndicatorBuffer (trend, Bars, ShiftedBars, 0); - ShiftIntIndicatorBuffer (unknownTrend, Bars, ShiftedBars, -1); - ShiftDoubleIndicatorBuffer(combinedTrend, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(signalPerformance, Bars, ShiftedBars, EMPTY_VALUE); + ShiftDoubleIndicatorBuffer(upperBand, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(lowerBand, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(upperCross, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(upperCrossHigh, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(lowerCross, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(lowerCrossLow, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(semaphoreOpen, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(semaphoreClose, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(reversalOffset, Bars, ShiftedBars, -1); + ShiftIntIndicatorBuffer (trend, Bars, ShiftedBars, 0); + ShiftIntIndicatorBuffer (unknownTrend, Bars, ShiftedBars, -1); + ShiftDoubleIndicatorBuffer(combinedTrend, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(signalPerformanceO, Bars, ShiftedBars, EMPTY_VALUE); + ShiftDoubleIndicatorBuffer(signalPerformanceH, Bars, ShiftedBars, EMPTY_VALUE); + ShiftDoubleIndicatorBuffer(signalPerformanceL, Bars, ShiftedBars, EMPTY_VALUE); + ShiftDoubleIndicatorBuffer(signalPerformanceC, Bars, ShiftedBars, EMPTY_VALUE); } // check data pumping on every tick so the reversal handler can skip errornous signals @@ -636,19 +651,22 @@ int onTick() { // recalculate changed bars if (startBar > 2) { // TODO: why 2 and not 1 - upperBand [startBar] = 0; - lowerBand [startBar] = 0; - upperCross [startBar] = 0; - upperCrossHigh [startBar] = 0; - lowerCross [startBar] = 0; - lowerCrossLow [startBar] = 0; - semaphoreOpen [startBar] = 0; - semaphoreClose [startBar] = 0; - reversalOffset [startBar] = -1; - trend [startBar] = 0; - unknownTrend [startBar] = -1; - combinedTrend [startBar] = 0; - signalPerformance[startBar] = EMPTY_VALUE; + upperBand [startBar] = 0; + lowerBand [startBar] = 0; + upperCross [startBar] = 0; + upperCrossHigh [startBar] = 0; + lowerCross [startBar] = 0; + lowerCrossLow [startBar] = 0; + semaphoreOpen [startBar] = 0; + semaphoreClose [startBar] = 0; + reversalOffset [startBar] = -1; + trend [startBar] = 0; + unknownTrend [startBar] = -1; + combinedTrend [startBar] = 0; + signalPerformanceO[startBar] = EMPTY_VALUE; + signalPerformanceH[startBar] = EMPTY_VALUE; + signalPerformanceL[startBar] = EMPTY_VALUE; + signalPerformanceC[startBar] = EMPTY_VALUE; } for (int bar=startBar; bar >= 0; bar--) { @@ -906,41 +924,44 @@ int onTick() { */ bool RecalculateSignalPerformance(int bar, bool isReversal) { isReversal = (isReversal != 0); + bool isPosition = (signalPerformanceC[bar+1] != EMPTY_VALUE); - bool isPosition = (signalPerformance[bar+1] != EMPTY_VALUE); - signalPerformance[bar] = signalPerformance[bar+1]; + signalPerformanceO[bar] = signalPerformanceO[bar+1]; + signalPerformanceH[bar] = signalPerformanceH[bar+1]; + signalPerformanceL[bar] = signalPerformanceL[bar+1]; + signalPerformanceC[bar] = signalPerformanceC[bar+1]; // either flip the position if (isReversal) { - if (!isPosition) signalPerformance[bar] = 0; + if (!isPosition) signalPerformanceC[bar] = 0; if (trend[bar] > 0) { if (isPosition) { - signalPerformance[bar] -= (upperCross[bar] - Close[bar+1]); // close existing short position + signalPerformanceC[bar] -= (upperCross[bar] - Close[bar+1]); // close existing short position } - signalPerformance[bar] += (Close[bar] - upperCross[bar]); // open new long position + signalPerformanceC[bar] += (Close[bar] - upperCross[bar]); // open new long position } else if (trend[bar] < 0) { if (isPosition) { - signalPerformance[bar] += (lowerCross[bar] - Close[bar+1]); // close existing long position + signalPerformanceC[bar] += (lowerCross[bar] - Close[bar+1]); // close existing long position } - signalPerformance[bar] += (lowerCross[bar] - Close[bar]); // open new short position + signalPerformanceC[bar] += (lowerCross[bar] - Close[bar]); // open new short position } else /*trend == 0*/{ // double crossing with two semaphores (at the moment of bar processing) if (semaphoreOpen[bar] == Low[bar]) { // crossing order "Low, High" if (isPosition) { - signalPerformance[bar] += (lowerCross[bar] - Close[bar+1]); // close existing long position + signalPerformanceC[bar] += (lowerCross[bar] - Close[bar+1]); // close existing long position } - signalPerformance[bar] -= (upperCross[bar] - lowerCross[bar]); // open new short position and immediately close it - signalPerformance[bar] += (Close[bar] - upperCross[bar]); // open new long position + signalPerformanceC[bar] -= (upperCross[bar] - lowerCross[bar]); // open new short position and immediately close it + signalPerformanceC[bar] += (Close[bar] - upperCross[bar]); // open new long position } else if (semaphoreOpen[bar] == High[bar]) { // crossing order "High, Low" if (isPosition) { - signalPerformance[bar] -= (upperCross[bar] - Close[bar+1]); // close existing short position + signalPerformanceC[bar] -= (upperCross[bar] - Close[bar+1]); // close existing short position } - signalPerformance[bar] -= (upperCross[bar] - lowerCross[bar]); // open new long position and immediately close it - signalPerformance[bar] += (lowerCross[bar] - Close[bar]); // open new short position + signalPerformanceC[bar] -= (upperCross[bar] - lowerCross[bar]); // open new long position and immediately close it + signalPerformanceC[bar] += (lowerCross[bar] - Close[bar]); // open new short position } else return(!catch("RecalculateSignalPerformance(1) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected reversal bar with trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); } @@ -949,10 +970,16 @@ bool RecalculateSignalPerformance(int bar, bool isReversal) { // or update the position else if (isPosition) { if (trend[bar] > 0) { - signalPerformance[bar] += (Close[bar] - Close[bar+1]); + signalPerformanceC[bar] += (Close[bar] - Close[bar+1]); + signalPerformanceO[bar] = signalPerformanceC[bar] + (Open[bar] - Close[bar]); + signalPerformanceH[bar] = signalPerformanceC[bar] + (High[bar] - Close[bar]); + signalPerformanceL[bar] = signalPerformanceC[bar] + ( Low[bar] - Close[bar]); } else if (trend[bar] < 0) { - signalPerformance[bar] -= (Close[bar] - Close[bar+1]); + signalPerformanceC[bar] -= (Close[bar] - Close[bar+1]); + signalPerformanceO[bar] = signalPerformanceC[bar] - (Open[bar] - Close[bar]); + signalPerformanceH[bar] = signalPerformanceC[bar] - ( Low[bar] - Close[bar]); + signalPerformanceL[bar] = signalPerformanceC[bar] - (High[bar] - Close[bar]); } else { return(!catch("RecalculateSignalPerformance(2) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected non-reversal bar with trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); @@ -1012,14 +1039,16 @@ bool RecordSignalPerformance(int _bar = 0) { debug("RecordSignalPerformance(0.2) Tick="+ Ticks +" rewriting all history since "+ TimeToStr(recorder.startTime) +" (bar "+ startBar +")"); } + double O, H, L, C; + for (int bar=startBar; bar >= 0; bar--) { if (!recorder.hSet) { recorder.hSet = HistorySet1.Create(recorder.symbol, recorder.symbolDescr, pDigits, recorder.hstFormat, recorder.hstDirectory); if (!recorder.hSet) return(false); } - double value = signalPerformance[bar] + recorder.priceBase; - if (value <= 0) { + C = signalPerformanceC[bar] + recorder.priceBase; + if (C <= 0) { switch(recorder.priceBase) { case 0: recorder.priceBase = 1; break; case 1: recorder.priceBase = 10; break; @@ -1040,9 +1069,22 @@ bool RecordSignalPerformance(int _bar = 0) { continue; } + O = signalPerformanceO[bar]; + H = signalPerformanceH[bar]; + L = signalPerformanceL[bar]; + if (bar == 0) flags = HST_FILL_GAPS; else flags = HST_FILL_GAPS|HST_BUFFER_TICKS; - if (!HistorySet1.AddTick(recorder.hSet, Time[bar], value, flags)) return(false); + if (O > HalfPoint) { + if (!HistorySet1.AddTick(recorder.hSet, Time[bar], O + recorder.priceBase, flags)) return(false); + } + if (H > HalfPoint) { + if (!HistorySet1.AddTick(recorder.hSet, Time[bar], H + recorder.priceBase, flags)) return(false); + } + if (L > HalfPoint) { + if (!HistorySet1.AddTick(recorder.hSet, Time[bar], L + recorder.priceBase, flags)) return(false); + } + if (!HistorySet1.AddTick(recorder.hSet, Time[bar], C, flags)) return(false); } return(true); } From 583ed9643daae9ddfd5267c1ef7529fd9b94c0d7 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Tue, 24 Mar 2026 02:40:34 +0200 Subject: [PATCH 21/40] update processing of OHLC --- mql40/indicators/ZigZag.mq4 | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index c90597cc1..346e4ce88 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -669,6 +669,8 @@ int onTick() { signalPerformanceC[startBar] = EMPTY_VALUE; } + datetime startRecalc = GetTickCount(); + for (int bar=startBar; bar >= 0; bar--) { // recalculate Donchian channel if (bar > 0) { @@ -762,12 +764,22 @@ int onTick() { combinedTrend[bar] = Sign(trend[bar]) * unknownTrend[bar] * 100000 + trend[bar]; } + //if (Ticks == 1) { + // debug("onTick(0.4) Tick="+ Ticks +" foreach(bars="+ (startBar+1) +") => "+ DoubleToStr((GetTickCount()-startRecalc)/1000.0, 3) +" sec"); + //} + if (__isChart && !__isSuperContext) { if (ShowChartLegend) UpdateChartLegend(); // record signal performance if (TrackSignalPerformance) { + datetime startRecorder = GetTickCount(); + if (!RecordSignalPerformance()) return(last_error); + + //if (Ticks == 1) { + // debug("onTick(0.5) Tick="+ Ticks +" RecordSignalPerformance() => "+ DoubleToStr((GetTickCount()-startRecorder)/1000.0, 3) +" sec"); + //} } // detect ZigZag breakouts (comparing legs against bands also detects breakouts on missed ticks) @@ -933,7 +945,12 @@ bool RecalculateSignalPerformance(int bar, bool isReversal) { // either flip the position if (isReversal) { - if (!isPosition) signalPerformanceC[bar] = 0; + if (!isPosition) { + signalPerformanceO[bar] = 0; + signalPerformanceH[bar] = 0; + signalPerformanceL[bar] = 0; + signalPerformanceC[bar] = 0; + } if (trend[bar] > 0) { if (isPosition) { @@ -1073,8 +1090,8 @@ bool RecordSignalPerformance(int _bar = 0) { H = signalPerformanceH[bar]; L = signalPerformanceL[bar]; - if (bar == 0) flags = HST_FILL_GAPS; - else flags = HST_FILL_GAPS|HST_BUFFER_TICKS; + flags = HST_FILL_GAPS|HST_BUFFER_TICKS; + if (O > HalfPoint) { if (!HistorySet1.AddTick(recorder.hSet, Time[bar], O + recorder.priceBase, flags)) return(false); } @@ -1084,6 +1101,8 @@ bool RecordSignalPerformance(int _bar = 0) { if (L > HalfPoint) { if (!HistorySet1.AddTick(recorder.hSet, Time[bar], L + recorder.priceBase, flags)) return(false); } + + if (bar == 0) flags = HST_FILL_GAPS; if (!HistorySet1.AddTick(recorder.hSet, Time[bar], C, flags)) return(false); } return(true); From aeb720b425371334131ec517e9b0528358ba2382 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Tue, 24 Mar 2026 04:50:09 +0200 Subject: [PATCH 22/40] update PnL calculation of reversals --- mql40/indicators/ZigZag.mq4 | 44 +++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 14 deletions(-) diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index 346e4ce88..2b972111c 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -938,12 +938,12 @@ bool RecalculateSignalPerformance(int bar, bool isReversal) { isReversal = (isReversal != 0); bool isPosition = (signalPerformanceC[bar+1] != EMPTY_VALUE); - signalPerformanceO[bar] = signalPerformanceO[bar+1]; - signalPerformanceH[bar] = signalPerformanceH[bar+1]; - signalPerformanceL[bar] = signalPerformanceL[bar+1]; + signalPerformanceO[bar] = signalPerformanceC[bar+1]; + signalPerformanceH[bar] = signalPerformanceC[bar+1]; + signalPerformanceL[bar] = signalPerformanceC[bar+1]; signalPerformanceC[bar] = signalPerformanceC[bar+1]; - // either flip the position + // reversal bar, flip the position if (isReversal) { if (!isPosition) { signalPerformanceO[bar] = 0; @@ -954,18 +954,36 @@ bool RecalculateSignalPerformance(int bar, bool isReversal) { if (trend[bar] > 0) { if (isPosition) { - signalPerformanceC[bar] -= (upperCross[bar] - Close[bar+1]); // close existing short position + signalPerformanceC[bar] -= (upperCross[bar] - Close[bar+1]); // close short position + signalPerformanceO[bar] = signalPerformanceC[bar] - (Open[bar] - upperCross[bar]); + signalPerformanceC[bar] += (Close[bar] - upperCross[bar]); // open new long position + signalPerformanceH[bar] = MathMax(signalPerformanceO[bar], signalPerformanceC[bar]); + signalPerformanceL[bar] = MathMin(signalPerformanceO[bar], signalPerformanceC[bar]); // the intra-bar path is unknown + } + else { + signalPerformanceO[bar] = 0; // open long position + signalPerformanceH[bar] = ( High[bar] - upperCross[bar]); + signalPerformanceL[bar] = ( Low[bar] - upperCross[bar]); + signalPerformanceC[bar] = (Close[bar] - upperCross[bar]); } - signalPerformanceC[bar] += (Close[bar] - upperCross[bar]); // open new long position } else if (trend[bar] < 0) { if (isPosition) { - signalPerformanceC[bar] += (lowerCross[bar] - Close[bar+1]); // close existing long position + signalPerformanceC[bar] += (lowerCross[bar] - Close[bar+1]); // close long position + signalPerformanceO[bar] = signalPerformanceC[bar] + (Open[bar] - lowerCross[bar]); + signalPerformanceC[bar] += (lowerCross[bar] - Close[bar]); // open new short position + signalPerformanceH[bar] = MathMax(signalPerformanceO[bar], signalPerformanceC[bar]); + signalPerformanceL[bar] = MathMin(signalPerformanceO[bar], signalPerformanceC[bar]); // the intra-bar path is unknown + } + else { + signalPerformanceO[bar] = 0; // open short position + signalPerformanceH[bar] = (lowerCross[bar] - Low[bar]); + signalPerformanceL[bar] = (lowerCross[bar] - High[bar]); + signalPerformanceC[bar] = (lowerCross[bar] - Close[bar]); } - signalPerformanceC[bar] += (lowerCross[bar] - Close[bar]); // open new short position } else /*trend == 0*/{ - // double crossing with two semaphores (at the moment of bar processing) + // double crossing with two semaphores (at the moment when the bar is processed) if (semaphoreOpen[bar] == Low[bar]) { // crossing order "Low, High" if (isPosition) { signalPerformanceC[bar] += (lowerCross[bar] - Close[bar+1]); // close existing long position @@ -984,7 +1002,7 @@ bool RecalculateSignalPerformance(int bar, bool isReversal) { } } - // or update the position + // normal bar, update an existing position else if (isPosition) { if (trend[bar] > 0) { signalPerformanceC[bar] += (Close[bar] - Close[bar+1]); @@ -998,12 +1016,10 @@ bool RecalculateSignalPerformance(int bar, bool isReversal) { signalPerformanceH[bar] = signalPerformanceC[bar] - ( Low[bar] - Close[bar]); signalPerformanceL[bar] = signalPerformanceC[bar] - (High[bar] - Close[bar]); } - else { - return(!catch("RecalculateSignalPerformance(2) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected non-reversal bar with trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); - } + else return(!catch("RecalculateSignalPerformance(2) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected non-reversal bar with trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); } - // or keep existing PnL + // normal bar without a position (before first ZigZag reversal) //else {} return(true); From 0adadbca1678f97e67977c467923d9599a11f0ea Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Tue, 24 Mar 2026 11:38:12 +0200 Subject: [PATCH 23/40] fix missing sound indicator in chart legend --- mql40/indicators/ZigZag.mq4 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index 2b972111c..ad6c667d9 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -1743,7 +1743,7 @@ void UpdateChartLegend() { string sTrend = " "+ NumberToStr(trend[0], "+."); string sUnknown = ifString(!unknownTrend[0], "", "/"+ unknownTrend[0]); string sReversal = " next reversal @" + NumberToStr(ifDouble(trend[0] < 0, upperBand[0]+Point, lowerBand[0]-Point), PriceFormat); - string sSignal = ifString(Signal.onReversal, " "+ legendInfo, ""); + string sSignal = ifString(Signal.onReversal || Sound.onChannelWidening, " "+ legendInfo, ""); string text = StringConcatenate(indicatorName, sTrend, sUnknown, sReversal, sSignal); color clr = ZigZag.Color; From dbd86ea14986157d42157a840907176c394c2b72 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Tue, 24 Mar 2026 12:16:54 +0200 Subject: [PATCH 24/40] update input validation --- mql40/libraries/rsfHistory1.mq4 | 50 ++++++++++++++++----------------- mql40/libraries/rsfHistory2.mq4 | 50 ++++++++++++++++----------------- mql40/libraries/rsfHistory3.mq4 | 50 ++++++++++++++++----------------- 3 files changed, 75 insertions(+), 75 deletions(-) diff --git a/mql40/libraries/rsfHistory1.mq4 b/mql40/libraries/rsfHistory1.mq4 index 0013b261c..362fb9397 100644 --- a/mql40/libraries/rsfHistory1.mq4 +++ b/mql40/libraries/rsfHistory1.mq4 @@ -429,35 +429,35 @@ bool HistorySet1.Close(int hSet) { /** - * Fügt dem HistorySet eines Symbols einen Tick hinzu. Der Tick wird als letzter Tick (Close) der entsprechenden Bar gespeichert. - * - * @param int hSet - Set-Handle des Symbols - * @param datetime time - Zeitpunkt des Ticks - * @param double value - Datenwert - * @param int flags - zusätzliche, das Schreiben steuernde Flags (default: keine) - * • HST_BUFFER_TICKS: buffert aufeinanderfolgende Ticks und schreibt die Daten erst beim jeweils nächsten - * BarOpen-Event - * • HST_FILL_GAPS: füllt entstehende Gaps mit dem letzten Schlußkurs vor dem Gap + * Add a single tick to a symbol's HistorySet. The tick is stored as Close price of the last bar. * + * @param int hSet - handle of the HistorySet + * @param datetime time - tick time + * @param double value - tick value + * @param int flags [optional] - additional flags that control writing (default: none) + * HST_BUFFER_TICKS: Buffer ticks and flush data only at the next BarOpen event for the respective file. + * HST_FILL_GAPS: Fills any gaps with the last price before the gap. * @return bool - success status */ bool HistorySet1.AddTick(int hSet, datetime time, double value, int flags = NULL) { - // Validierung + // validation if (hSet <= 0) return(!catch("HistorySet1.AddTick(1) invalid parameter hSet: "+ hSet, ERR_INVALID_PARAMETER)); if (hSet != hs.hSet.lastValid) { if (hSet >= ArraySize(hs.hSet)) return(!catch("HistorySet1.AddTick(2) invalid parameter hSet: "+ hSet, ERR_INVALID_PARAMETER)); - if (hs.hSet[hSet] == 0) return(!catch("HistorySet1.AddTick(3) invalid parameter hSet: "+ hSet +" (unknown handle, symbol="+ DoubleQuoteStr(hs.symbol[hSet]) +")", ERR_INVALID_PARAMETER)); - if (hs.hSet[hSet] < 0) return(!catch("HistorySet1.AddTick(4) invalid parameter hSet: "+ hSet +" (closed handle, symbol="+ DoubleQuoteStr(hs.symbol[hSet]) +")", ERR_INVALID_PARAMETER)); + if (hs.hSet[hSet] == 0) return(!catch("HistorySet1.AddTick(3) invalid parameter hSet: "+ hSet +" (unknown handle, symbol="+ hs.symbol[hSet] +")", ERR_INVALID_PARAMETER)); + if (hs.hSet[hSet] < 0) return(!catch("HistorySet1.AddTick(4) invalid parameter hSet: "+ hSet +" (closed handle, symbol="+ hs.symbol[hSet] +")", ERR_INVALID_PARAMETER)); hs.hSet.lastValid = hSet; } - if (time <= 0) return(!catch("HistorySet1.AddTick(5) invalid parameter time: "+ time +" (symbol="+ DoubleQuoteStr(hs.symbol[hSet]) +")", ERR_INVALID_PARAMETER)); + if (time <= 0) return(!catch("HistorySet1.AddTick(5) invalid parameter time: "+ time +" (symbol="+ hs.symbol[hSet] +")", ERR_INVALID_PARAMETER)); + if (value <= 0) return(!catch("HistorySet1.AddTick(6) invalid parameter value: "+ NumberToStr(value, ".1+") +" (must be positive, symbol="+ hs.symbol[hSet] +")", ERR_INVALID_PARAMETER)); + if (value >= EMPTY_VALUE) return(!catch("HistorySet1.AddTick(7) invalid parameter value: "+ NumberToStr(value, ".1+") +" (too large, symbol="+ hs.symbol[hSet] +")", ERR_INVALID_PARAMETER)); - // Dateihandles holen und jeweils Tick hinzufügen - int hFile, sizeOfPeriods=ArraySize(periods); + int hFile, sizeOfPeriods = ArraySize(periods); + // get file handles and add tick for (int i=0; i < sizeOfPeriods; i++) { hFile = hs.hFile[hSet][i]; - if (!hFile) { // noch ungeöffnete Dateien öffnen + if (!hFile) { // open files that haven't been opened yet hFile = HistoryFile1.Open(hs.symbol[hSet], periods[i], hs.description[hSet], hs.digits[hSet], hs.format[hSet], FILE_READ|FILE_WRITE, hs.directory[hSet]); if (!hFile) return(false); hs.hFile[hSet][i] = hFile; @@ -1317,16 +1317,14 @@ bool HistoryFile1.MoveBars(int hFile, int fromOffset, int destOffset) { /** - * Fügt einer Historydatei einen weiteren Tick hinzu. Der Tick muß zur jüngsten Bar der Datei gehören und wird als Close-Preis gespeichert. - * - * @param int hFile - Handle der Historydatei - * @param datetime time - Zeitpunkt des Ticks - * @param double value - Datenwert - * @param int flags - zusätzliche, das Schreiben steuernde Flags (default: keine) - * • HST_BUFFER_TICKS: puffert aufeinanderfolgende Ticks und schreibt die Daten erst beim jeweils nächsten - * BarOpen-Event - * • HST_FILL_GAPS: füllt entstehende Gaps mit dem letzten Schlußkurs vor dem Gap + * Add a single tick to a history file. The tick must belong to the youngest bar in the file and becomes the bar's close price. * + * @param int hFile - handle of the history file + * @param datetime time - tick time + * @param double value - tick value + * @param int flags [optional] - additional flags that control writing (default: none) + * HST_BUFFER_TICKS: Buffer ticks and flush data only at the next BarOpen event for the file. + * HST_FILL_GAPS: Fills any gaps with the last price before the gap. * @return bool - success status */ bool HistoryFile1.AddTick(int hFile, datetime time, double value, int flags = NULL) { @@ -1339,6 +1337,8 @@ bool HistoryFile1.AddTick(int hFile, datetime time, double value, int flags = NU } if (time <= 0) return(!catch("HistoryFile1.AddTick(5) invalid parameter time: "+ time +" ("+ hf.symbol[hFile] +","+ PeriodDescription(hf.period[hFile]) +")", ERR_INVALID_PARAMETER)); if (time < hf.total.to.openTime[hFile]) return(!catch("HistoryFile1.AddTick(6) cannot add tick to a closed bar: tickTime="+ TimeToStr(time, TIME_FULL) +", last bar.time="+ TimeToStr(hf.total.to.openTime[hFile], TIME_FULL) +" ("+ hf.symbol[hFile] +","+ PeriodDescription(hf.period[hFile]) +")", ERR_INVALID_PARAMETER)); + if (value <= 0) return(!catch("HistoryFile1.AddTick(7) invalid parameter value: "+ NumberToStr(value, ".1+") +" (must be positive, symbol="+ hf.symbol[hFile] +","+ PeriodDescription(hf.period[hFile]) +")", ERR_INVALID_PARAMETER)); + if (value >= EMPTY_VALUE) return(!catch("HistoryFile1.AddTick(8) invalid parameter value: "+ NumberToStr(value, ".1+") +" (too large, symbol="+ hf.symbol[hFile] +","+ PeriodDescription(hf.period[hFile]) +")", ERR_INVALID_PARAMETER)); double bar[6]; bool barExists[1]; diff --git a/mql40/libraries/rsfHistory2.mq4 b/mql40/libraries/rsfHistory2.mq4 index 484279a42..d6f1823e8 100644 --- a/mql40/libraries/rsfHistory2.mq4 +++ b/mql40/libraries/rsfHistory2.mq4 @@ -429,35 +429,35 @@ bool HistorySet2.Close(int hSet) { /** - * Fügt dem HistorySet eines Symbols einen Tick hinzu. Der Tick wird als letzter Tick (Close) der entsprechenden Bar gespeichert. - * - * @param int hSet - Set-Handle des Symbols - * @param datetime time - Zeitpunkt des Ticks - * @param double value - Datenwert - * @param int flags - zusätzliche, das Schreiben steuernde Flags (default: keine) - * • HST_BUFFER_TICKS: buffert aufeinanderfolgende Ticks und schreibt die Daten erst beim jeweils nächsten - * BarOpen-Event - * • HST_FILL_GAPS: füllt entstehende Gaps mit dem letzten Schlußkurs vor dem Gap + * Add a single tick to a symbol's HistorySet. The tick is stored as Close price of the last bar. * + * @param int hSet - handle of the HistorySet + * @param datetime time - tick time + * @param double value - tick value + * @param int flags [optional] - additional flags that control writing (default: none) + * HST_BUFFER_TICKS: Buffer ticks and flush data only at the next BarOpen event for the respective file. + * HST_FILL_GAPS: Fills any gaps with the last price before the gap. * @return bool - success status */ bool HistorySet2.AddTick(int hSet, datetime time, double value, int flags = NULL) { - // Validierung + // validation if (hSet <= 0) return(!catch("HistorySet2.AddTick(1) invalid parameter hSet: "+ hSet, ERR_INVALID_PARAMETER)); if (hSet != hs.hSet.lastValid) { if (hSet >= ArraySize(hs.hSet)) return(!catch("HistorySet2.AddTick(2) invalid parameter hSet: "+ hSet, ERR_INVALID_PARAMETER)); - if (hs.hSet[hSet] == 0) return(!catch("HistorySet2.AddTick(3) invalid parameter hSet: "+ hSet +" (unknown handle, symbol="+ DoubleQuoteStr(hs.symbol[hSet]) +")", ERR_INVALID_PARAMETER)); - if (hs.hSet[hSet] < 0) return(!catch("HistorySet2.AddTick(4) invalid parameter hSet: "+ hSet +" (closed handle, symbol="+ DoubleQuoteStr(hs.symbol[hSet]) +")", ERR_INVALID_PARAMETER)); + if (hs.hSet[hSet] == 0) return(!catch("HistorySet2.AddTick(3) invalid parameter hSet: "+ hSet +" (unknown handle, symbol="+ hs.symbol[hSet] +")", ERR_INVALID_PARAMETER)); + if (hs.hSet[hSet] < 0) return(!catch("HistorySet2.AddTick(4) invalid parameter hSet: "+ hSet +" (closed handle, symbol="+ hs.symbol[hSet] +")", ERR_INVALID_PARAMETER)); hs.hSet.lastValid = hSet; } - if (time <= 0) return(!catch("HistorySet2.AddTick(5) invalid parameter time: "+ time +" (symbol="+ DoubleQuoteStr(hs.symbol[hSet]) +")", ERR_INVALID_PARAMETER)); + if (time <= 0) return(!catch("HistorySet2.AddTick(5) invalid parameter time: "+ time +" (symbol="+ hs.symbol[hSet] +")", ERR_INVALID_PARAMETER)); + if (value <= 0) return(!catch("HistorySet2.AddTick(6) invalid parameter value: "+ NumberToStr(value, ".1+") +" (must be positive, symbol="+ hs.symbol[hSet] +")", ERR_INVALID_PARAMETER)); + if (value >= EMPTY_VALUE) return(!catch("HistorySet2.AddTick(7) invalid parameter value: "+ NumberToStr(value, ".1+") +" (too large, symbol="+ hs.symbol[hSet] +")", ERR_INVALID_PARAMETER)); - // Dateihandles holen und jeweils Tick hinzufügen - int hFile, sizeOfPeriods=ArraySize(periods); + int hFile, sizeOfPeriods = ArraySize(periods); + // get file handles and add tick for (int i=0; i < sizeOfPeriods; i++) { hFile = hs.hFile[hSet][i]; - if (!hFile) { // noch ungeöffnete Dateien öffnen + if (!hFile) { // open files that haven't been opened yet hFile = HistoryFile2.Open(hs.symbol[hSet], periods[i], hs.description[hSet], hs.digits[hSet], hs.format[hSet], FILE_READ|FILE_WRITE, hs.directory[hSet]); if (!hFile) return(false); hs.hFile[hSet][i] = hFile; @@ -1317,16 +1317,14 @@ bool HistoryFile2.MoveBars(int hFile, int fromOffset, int destOffset) { /** - * Fügt einer Historydatei einen weiteren Tick hinzu. Der Tick muß zur jüngsten Bar der Datei gehören und wird als Close-Preis gespeichert. - * - * @param int hFile - Handle der Historydatei - * @param datetime time - Zeitpunkt des Ticks - * @param double value - Datenwert - * @param int flags - zusätzliche, das Schreiben steuernde Flags (default: keine) - * • HST_BUFFER_TICKS: puffert aufeinanderfolgende Ticks und schreibt die Daten erst beim jeweils nächsten - * BarOpen-Event - * • HST_FILL_GAPS: füllt entstehende Gaps mit dem letzten Schlußkurs vor dem Gap + * Add a single tick to a history file. The tick must belong to the youngest bar in the file and becomes the bar's close price. * + * @param int hFile - handle of the history file + * @param datetime time - tick time + * @param double value - tick value + * @param int flags [optional] - additional flags that control writing (default: none) + * HST_BUFFER_TICKS: Buffer ticks and flush data only at the next BarOpen event for the file. + * HST_FILL_GAPS: Fills any gaps with the last price before the gap. * @return bool - success status */ bool HistoryFile2.AddTick(int hFile, datetime time, double value, int flags = NULL) { @@ -1339,6 +1337,8 @@ bool HistoryFile2.AddTick(int hFile, datetime time, double value, int flags = NU } if (time <= 0) return(!catch("HistoryFile2.AddTick(5) invalid parameter time: "+ time +" ("+ hf.symbol[hFile] +","+ PeriodDescription(hf.period[hFile]) +")", ERR_INVALID_PARAMETER)); if (time < hf.total.to.openTime[hFile]) return(!catch("HistoryFile2.AddTick(6) cannot add tick to a closed bar: tickTime="+ TimeToStr(time, TIME_FULL) +", last bar.time="+ TimeToStr(hf.total.to.openTime[hFile], TIME_FULL) +" ("+ hf.symbol[hFile] +","+ PeriodDescription(hf.period[hFile]) +")", ERR_INVALID_PARAMETER)); + if (value <= 0) return(!catch("HistoryFile2.AddTick(7) invalid parameter value: "+ NumberToStr(value, ".1+") +" (must be positive, symbol="+ hf.symbol[hFile] +","+ PeriodDescription(hf.period[hFile]) +")", ERR_INVALID_PARAMETER)); + if (value >= EMPTY_VALUE) return(!catch("HistoryFile2.AddTick(8) invalid parameter value: "+ NumberToStr(value, ".1+") +" (too large, symbol="+ hf.symbol[hFile] +","+ PeriodDescription(hf.period[hFile]) +")", ERR_INVALID_PARAMETER)); double bar[6]; bool barExists[1]; diff --git a/mql40/libraries/rsfHistory3.mq4 b/mql40/libraries/rsfHistory3.mq4 index 52f71e1f3..e703f8d15 100644 --- a/mql40/libraries/rsfHistory3.mq4 +++ b/mql40/libraries/rsfHistory3.mq4 @@ -429,35 +429,35 @@ bool HistorySet3.Close(int hSet) { /** - * Fügt dem HistorySet eines Symbols einen Tick hinzu. Der Tick wird als letzter Tick (Close) der entsprechenden Bar gespeichert. - * - * @param int hSet - Set-Handle des Symbols - * @param datetime time - Zeitpunkt des Ticks - * @param double value - Datenwert - * @param int flags - zusätzliche, das Schreiben steuernde Flags (default: keine) - * • HST_BUFFER_TICKS: buffert aufeinanderfolgende Ticks und schreibt die Daten erst beim jeweils nächsten - * BarOpen-Event - * • HST_FILL_GAPS: füllt entstehende Gaps mit dem letzten Schlußkurs vor dem Gap + * Add a single tick to a symbol's HistorySet. The tick is stored as Close price of the last bar. * + * @param int hSet - handle of the HistorySet + * @param datetime time - tick time + * @param double value - tick value + * @param int flags [optional] - additional flags that control writing (default: none) + * HST_BUFFER_TICKS: Buffer ticks and flush data only at the next BarOpen event for the respective file. + * HST_FILL_GAPS: Fills any gaps with the last price before the gap. * @return bool - success status */ bool HistorySet3.AddTick(int hSet, datetime time, double value, int flags = NULL) { - // Validierung + // validation if (hSet <= 0) return(!catch("HistorySet3.AddTick(1) invalid parameter hSet: "+ hSet, ERR_INVALID_PARAMETER)); if (hSet != hs.hSet.lastValid) { if (hSet >= ArraySize(hs.hSet)) return(!catch("HistorySet3.AddTick(2) invalid parameter hSet: "+ hSet, ERR_INVALID_PARAMETER)); - if (hs.hSet[hSet] == 0) return(!catch("HistorySet3.AddTick(3) invalid parameter hSet: "+ hSet +" (unknown handle, symbol="+ DoubleQuoteStr(hs.symbol[hSet]) +")", ERR_INVALID_PARAMETER)); - if (hs.hSet[hSet] < 0) return(!catch("HistorySet3.AddTick(4) invalid parameter hSet: "+ hSet +" (closed handle, symbol="+ DoubleQuoteStr(hs.symbol[hSet]) +")", ERR_INVALID_PARAMETER)); + if (hs.hSet[hSet] == 0) return(!catch("HistorySet3.AddTick(3) invalid parameter hSet: "+ hSet +" (unknown handle, symbol="+ hs.symbol[hSet] +")", ERR_INVALID_PARAMETER)); + if (hs.hSet[hSet] < 0) return(!catch("HistorySet3.AddTick(4) invalid parameter hSet: "+ hSet +" (closed handle, symbol="+ hs.symbol[hSet] +")", ERR_INVALID_PARAMETER)); hs.hSet.lastValid = hSet; } - if (time <= 0) return(!catch("HistorySet3.AddTick(5) invalid parameter time: "+ time +" (symbol="+ DoubleQuoteStr(hs.symbol[hSet]) +")", ERR_INVALID_PARAMETER)); + if (time <= 0) return(!catch("HistorySet3.AddTick(5) invalid parameter time: "+ time +" (symbol="+ hs.symbol[hSet] +")", ERR_INVALID_PARAMETER)); + if (value <= 0) return(!catch("HistorySet3.AddTick(6) invalid parameter value: "+ NumberToStr(value, ".1+") +" (must be positive, symbol="+ hs.symbol[hSet] +")", ERR_INVALID_PARAMETER)); + if (value >= EMPTY_VALUE) return(!catch("HistorySet3.AddTick(7) invalid parameter value: "+ NumberToStr(value, ".1+") +" (too large, symbol="+ hs.symbol[hSet] +")", ERR_INVALID_PARAMETER)); - // Dateihandles holen und jeweils Tick hinzufügen - int hFile, sizeOfPeriods=ArraySize(periods); + int hFile, sizeOfPeriods = ArraySize(periods); + // get file handles and add tick for (int i=0; i < sizeOfPeriods; i++) { hFile = hs.hFile[hSet][i]; - if (!hFile) { // noch ungeöffnete Dateien öffnen + if (!hFile) { // open files that haven't been opened yet hFile = HistoryFile3.Open(hs.symbol[hSet], periods[i], hs.description[hSet], hs.digits[hSet], hs.format[hSet], FILE_READ|FILE_WRITE, hs.directory[hSet]); if (!hFile) return(false); hs.hFile[hSet][i] = hFile; @@ -1317,16 +1317,14 @@ bool HistoryFile3.MoveBars(int hFile, int fromOffset, int destOffset) { /** - * Fügt einer Historydatei einen weiteren Tick hinzu. Der Tick muß zur jüngsten Bar der Datei gehören und wird als Close-Preis gespeichert. - * - * @param int hFile - Handle der Historydatei - * @param datetime time - Zeitpunkt des Ticks - * @param double value - Datenwert - * @param int flags - zusätzliche, das Schreiben steuernde Flags (default: keine) - * • HST_BUFFER_TICKS: puffert aufeinanderfolgende Ticks und schreibt die Daten erst beim jeweils nächsten - * BarOpen-Event - * • HST_FILL_GAPS: füllt entstehende Gaps mit dem letzten Schlußkurs vor dem Gap + * Add a single tick to a history file. The tick must belong to the youngest bar in the file and becomes the bar's close price. * + * @param int hFile - handle of the history file + * @param datetime time - tick time + * @param double value - tick value + * @param int flags [optional] - additional flags that control writing (default: none) + * HST_BUFFER_TICKS: Buffer ticks and flush data only at the next BarOpen event for the file. + * HST_FILL_GAPS: Fills any gaps with the last price before the gap. * @return bool - success status */ bool HistoryFile3.AddTick(int hFile, datetime time, double value, int flags = NULL) { @@ -1339,6 +1337,8 @@ bool HistoryFile3.AddTick(int hFile, datetime time, double value, int flags = NU } if (time <= 0) return(!catch("HistoryFile3.AddTick(5) invalid parameter time: "+ time +" ("+ hf.symbol[hFile] +","+ PeriodDescription(hf.period[hFile]) +")", ERR_INVALID_PARAMETER)); if (time < hf.total.to.openTime[hFile]) return(!catch("HistoryFile3.AddTick(6) cannot add tick to a closed bar: tickTime="+ TimeToStr(time, TIME_FULL) +", last bar.time="+ TimeToStr(hf.total.to.openTime[hFile], TIME_FULL) +" ("+ hf.symbol[hFile] +","+ PeriodDescription(hf.period[hFile]) +")", ERR_INVALID_PARAMETER)); + if (value <= 0) return(!catch("HistoryFile3.AddTick(7) invalid parameter value: "+ NumberToStr(value, ".1+") +" (must be positive, symbol="+ hf.symbol[hFile] +","+ PeriodDescription(hf.period[hFile]) +")", ERR_INVALID_PARAMETER)); + if (value >= EMPTY_VALUE) return(!catch("HistoryFile3.AddTick(8) invalid parameter value: "+ NumberToStr(value, ".1+") +" (too large, symbol="+ hf.symbol[hFile] +","+ PeriodDescription(hf.period[hFile]) +")", ERR_INVALID_PARAMETER)); double bar[6]; bool barExists[1]; From d234356cd13b42fcf5ab2d39cb4d088a9d286d26 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Tue, 24 Mar 2026 17:15:55 +0200 Subject: [PATCH 25/40] improve handling of channel double crossings --- mql40/indicators/ZigZag.mq4 | 96 ++++++++++++++++++++++--------------- 1 file changed, 57 insertions(+), 39 deletions(-) diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index ad6c667d9..3339ed486 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -63,7 +63,6 @@ * - remove debug code * - once finished, update logic in usage locations of icZigZag() * - * - calculate/display ZigZag zero balance projections * - fix triple-crossing at GBPJPY,M5 2023.12.18 00:00, ZigZag(20) * - keep bar status in IsUpperCrossLast() * - document usage of iCustom() @@ -215,6 +214,10 @@ bool signal.onBreakout.sound; bool signal.onBreakout.alert; bool signal.onBreakout.mail; +int dbc_reversalOffset; // status vars for the 1st of a double crossing +int dbc_trend; +int dbc_unknownTrend; + double sema1, sema2, sema3; // last 3 semaphores for detection of ZigZag breakouts double lastLegHigh, lastLegLow; // leg high/low at the previous tick @@ -371,11 +374,11 @@ string semTypes[] = {"NULL", "LOW", "HIGH"}; * @return int - error status */ int onInit() { - devStartTime = D'2026.03.19 01:41'; // TODO: remove once finished - devFirstCrossing = D'2026.03.19 01:48'; + devStartTime = D'2025.05.17 19:45'; // TODO: remove once finished + devFirstCrossing = D'2025.05.17 20:07'; // double crossings: - devFrom = devStartTime + 6 * Period() * MINUTES; // P=8, 2026.03.16 20:47 - devTo = devFirstCrossing + 32 * Period() * MINUTES; + devFrom = devStartTime + 87 * Period() * MINUTES; // P=8, 2026.03.16 20:47 + devTo = devFirstCrossing + 69 * Period() * MINUTES; if (debugging && Symbol()=="BTCUSD" && Period()==PERIOD_M1 && ZigZag.Periods <= 20) { MaxBarsBack = iBarShift(NULL, NULL, devStartTime); @@ -611,6 +614,11 @@ int onTick() { ArrayInitialize(signalPerformanceH, EMPTY_VALUE); // double: positive/negative or EMPTY_VALUE ArrayInitialize(signalPerformanceL, EMPTY_VALUE); // double: positive/negative or EMPTY_VALUE ArrayInitialize(signalPerformanceC, EMPTY_VALUE); // double: positive/negative or EMPTY_VALUE + + dbc_reversalOffset = -1; + dbc_trend = 0; + dbc_unknownTrend = -1; + lastUpperBand = 0; lastLowerBand = 0; lastLegHigh = 0; @@ -706,12 +714,18 @@ int onTick() { // if two channel crossings (upper and lower band crossed by the same bar) else if (upperCross[bar] && lowerCross[bar]) { if (IsUpperCrossLast(bar)) { - if (!trend[bar]) ProcessLowerCross(bar); // if bar not yet processed then process both crossings in order, - ProcessUpperCross(bar); // otherwise process only the last crossing + ProcessLowerCross(bar); // process both crossings in order + dbc_reversalOffset = reversalOffset[bar]; // + dbc_trend = trend [bar]; // store bar values which get overwritten in the next call + dbc_unknownTrend = unknownTrend [bar]; // + ProcessUpperCross(bar); // overwrites above values } else { - if (!trend[bar]) ProcessUpperCross(bar); // ... - ProcessLowerCross(bar); // ... + ProcessUpperCross(bar); // process both crossings in order + dbc_reversalOffset = reversalOffset[bar]; // + dbc_trend = trend [bar]; // prevent bar values from getting lost in the next call + dbc_unknownTrend = unknownTrend [bar]; // + ProcessLowerCross(bar); // overwrites above values } } @@ -732,7 +746,7 @@ int onTick() { if (debugging && Ticks == 1) { if (Time[bar] >= devFrom && Time[bar] <= devTo) { - debug("onTick(0.1) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" reversalOffset="+ _int(reversalOffset[bar]) +" trend="+ trend[bar] +" unknownTrend="+ unknownTrend[bar]); + debug("onTick(0.1) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat) +" reversalOffset="+ _int(reversalOffset[bar]) +" trend="+ trend[bar] +" unknownTrend="+ unknownTrend[bar]); if (isReversalBar) { debug("onTick(0.2) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" isReversalBar=1"); } @@ -749,9 +763,16 @@ int onTick() { } else if (crossingDrawType == MODE_FIRST_CROSSING) { // hide all crossings except the 1st if (isReversalBar) { - if (reversalOffset[bar+1] >= 0) { // keep preceeding reversals on the same bar - if (trend[bar] > 0) lowerCross[bar] = 0; - else upperCross[bar] = 0; + if (upperCross[bar] && lowerCross[bar]) { // special handling for double crossings + bool dbc_isReversalBar = false; // inspect and process the 1st crossing + if (!dbc_unknownTrend) { // whether the first crossing also represents a reversal bar + dbc_isReversalBar = (Abs(dbc_trend) == dbc_reversalOffset); + } + if (!dbc_isReversalBar) { + if (semaphoreOpen[bar] < semaphoreClose[bar]) lowerCross[bar] = 0; + else upperCross[bar] = 0; + } + // keep the 2nd crossing (it always represents a reversal bar) } } else { @@ -936,13 +957,14 @@ int onTick() { */ bool RecalculateSignalPerformance(int bar, bool isReversal) { isReversal = (isReversal != 0); - bool isPosition = (signalPerformanceC[bar+1] != EMPTY_VALUE); signalPerformanceO[bar] = signalPerformanceC[bar+1]; signalPerformanceH[bar] = signalPerformanceC[bar+1]; signalPerformanceL[bar] = signalPerformanceC[bar+1]; signalPerformanceC[bar] = signalPerformanceC[bar+1]; + bool isPosition = (signalPerformanceC[bar] != EMPTY_VALUE); + // reversal bar, flip the position if (isReversal) { if (!isPosition) { @@ -999,6 +1021,10 @@ bool RecalculateSignalPerformance(int bar, bool isReversal) { signalPerformanceC[bar] += (lowerCross[bar] - Close[bar]); // open new short position } else return(!catch("RecalculateSignalPerformance(1) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected reversal bar with trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); + + signalPerformanceO[bar] = signalPerformanceC[bar+1]; + signalPerformanceH[bar] = MathMax(signalPerformanceO[bar], signalPerformanceC[bar]); + signalPerformanceL[bar] = MathMin(signalPerformanceO[bar], signalPerformanceC[bar]); } } @@ -1029,11 +1055,9 @@ bool RecalculateSignalPerformance(int bar, bool isReversal) { /** * Record the signal performance for the specified bar. * - * @param int bar - bar offset - * * @return bool - success status */ -bool RecordSignalPerformance(int _bar = 0) { +bool RecordSignalPerformance() { if (!recorder.initialized) { // create symbol and group recorder.symbol = Symbol() +".zzr"; @@ -1060,7 +1084,8 @@ bool RecordSignalPerformance(int _bar = 0) { debug("RecordSignalPerformance(0.1) Tick="+ Ticks +" recorder initialized"); } - int startBar = 0, flags = HST_BUFFER_TICKS|HST_FILL_GAPS; + int startBar = 0, flags = HST_FILL_GAPS|HST_BUFFER_TICKS; + double O, H, L, C; if (ChangedBars > 2) { // rewrite the full history (intentionally skip rewriting bar 1 on BarOpen) if (recorder.hSet != 0) { @@ -1072,16 +1097,20 @@ bool RecordSignalPerformance(int _bar = 0) { debug("RecordSignalPerformance(0.2) Tick="+ Ticks +" rewriting all history since "+ TimeToStr(recorder.startTime) +" (bar "+ startBar +")"); } - double O, H, L, C; - for (int bar=startBar; bar >= 0; bar--) { if (!recorder.hSet) { recorder.hSet = HistorySet1.Create(recorder.symbol, recorder.symbolDescr, pDigits, recorder.hstFormat, recorder.hstDirectory); if (!recorder.hSet) return(false); } - C = signalPerformanceC[bar] + recorder.priceBase; - if (C <= 0) { + O = signalPerformanceO[bar]; + H = signalPerformanceH[bar]; + L = signalPerformanceL[bar]; + C = signalPerformanceC[bar]; + if (C >= EMPTY_VALUE) continue; + + L += recorder.priceBase; + if (L <= 0) { switch(recorder.priceBase) { case 0: recorder.priceBase = 1; break; case 1: recorder.priceBase = 10; break; @@ -1102,24 +1131,13 @@ bool RecordSignalPerformance(int _bar = 0) { continue; } - O = signalPerformanceO[bar]; - H = signalPerformanceH[bar]; - L = signalPerformanceL[bar]; - - flags = HST_FILL_GAPS|HST_BUFFER_TICKS; - - if (O > HalfPoint) { - if (!HistorySet1.AddTick(recorder.hSet, Time[bar], O + recorder.priceBase, flags)) return(false); + if (!HistorySet1.AddTick(recorder.hSet, Time[bar], O + recorder.priceBase, flags)) return(false); + if (!HistorySet1.AddTick(recorder.hSet, Time[bar], H + recorder.priceBase, flags)) return(false); + if (!HistorySet1.AddTick(recorder.hSet, Time[bar], L, flags)) return(false); + if (bar == 0) { + flags &= ~HST_BUFFER_TICKS; // unset HST_BUFFER_TICKS on bar 0 (zero) } - if (H > HalfPoint) { - if (!HistorySet1.AddTick(recorder.hSet, Time[bar], H + recorder.priceBase, flags)) return(false); - } - if (L > HalfPoint) { - if (!HistorySet1.AddTick(recorder.hSet, Time[bar], L + recorder.priceBase, flags)) return(false); - } - - if (bar == 0) flags = HST_FILL_GAPS; - if (!HistorySet1.AddTick(recorder.hSet, Time[bar], C, flags)) return(false); + if (!HistorySet1.AddTick(recorder.hSet, Time[bar], C + recorder.priceBase, flags)) return(false); } return(true); } From f7a26795d174ca45e5f36fa1583fd3122ea99c3e Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Tue, 24 Mar 2026 21:13:58 +0200 Subject: [PATCH 26/40] rename inputs and constants/vars --- .../include/rsf/functions/iCustom/ZigZag.mqh | 18 +- mql40/indicators/ZigZag.mq4 | 532 +++++++++--------- 2 files changed, 275 insertions(+), 275 deletions(-) diff --git a/mql40/include/rsf/functions/iCustom/ZigZag.mqh b/mql40/include/rsf/functions/iCustom/ZigZag.mqh index e168f089b..da614b57f 100644 --- a/mql40/include/rsf/functions/iCustom/ZigZag.mqh +++ b/mql40/include/rsf/functions/iCustom/ZigZag.mqh @@ -25,7 +25,7 @@ double icZigZag(int timeframe, int periods, int iBuffer, int iBar) { } double value = iCustom(NULL, timeframe, "ZigZag", - "separator", // string ___a_________________________ + "separator", // string ___a_______________________ periods, // int ZigZag.Periods 0, // int ZigZag.Periods.Step "Line", // string ZigZag.Type @@ -33,7 +33,7 @@ double icZigZag(int timeframe, int periods, int iBuffer, int iBar) { 1, // int ZigZag.Width CLR_NONE, // color ZigZag.Color - "separator", // string ___b_________________________ + "separator", // string ___b_______________________ false, // bool Donchian.ShowChannel CLR_NONE, // color Donchian.Channel.UpperColor CLR_NONE, // color Donchian.Channel.LowerColor @@ -42,11 +42,11 @@ double icZigZag(int timeframe, int periods, int iBuffer, int iBar) { 1, // int Donchian.Crossing.Width CLR_NONE, // color Donchian.Crossing.Color - "separator", // string ___c_________________________ + "separator", // string ___c_______________________ false, // bool ShowChartLegend -1, // int MaxBarsBack - "separator", // string ___d_________________________ + "separator", // string ___d_______________________ false, // bool Signal.onReversal "", // string Signal.onReversal.Types @@ -60,12 +60,12 @@ double icZigZag(int timeframe, int periods, int iBuffer, int iBar) { "", // string Sound.onNewChannelHigh "", // string Sound.onNewChannelLow - "separator", // string ___e_________________________ - false, // bool TrackSignalPerformance - 0, // datetime TrackSignalPerformance.Since - "", // string TrackSignalPerformance.Symbol + "separator", // string ___e_______________________ + false, // bool TrackVirtualProfit + 0, // datetime TrackVirtualProfit.Since + "", // string TrackVirtualProfit.Symbol - "separator", // string _____________________________ + "separator", // string ___________________________ false, // bool AutoConfiguration lpSuperContext, // int __lpSuperContext diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index 3339ed486..a938d9fca 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -15,45 +15,45 @@ * * Input parameters * ---------------- - * • ZigZag.Periods: Look-back periods of the Donchian channel. - * • ZigZag.Periods.Step: Controls parameter "ZigZag.Periods" via keyboard. If non-zero it defines the step size - * of the parameter stepper. If 0 (zero) the parameter stepper is disabled. - * • ZigZag.Type: Whether to display the ZigZag line or ZigZag semaphores. - * • ZigZag.Semaphores.Symbol: Graphic symbol used for ZigZag semaphores. - * • ZigZag.Width: The ZigZag's line width or semaphore size. - * • ZigZag.Color: Color of ZigZag line or semaphores. + * • ZigZag.Periods: Look-back periods of the Donchian channel. + * • ZigZag.Periods.Step: Controls parameter "ZigZag.Periods" via keyboard. If non-zero it defines the step size + * of the parameter stepper. If 0 (zero) the parameter stepper is disabled. + * • ZigZag.Type: Whether to display the ZigZag line or ZigZag semaphores. + * • ZigZag.Semaphores.Symbol: Graphic symbol used for ZigZag semaphores. + * • ZigZag.Width: The ZigZag's line width or semaphore size. + * • ZigZag.Color: Color of ZigZag line or semaphores. * - * • Donchian.ShowChannel: Whether to display the internal Donchian channel. - * • Donchian.Channel.UpperColor: Color of upper Donchian channel band. - * • Donchian.Channel.LowerColor: Color of lower Donchian channel band. + * • Donchian.ShowChannel: Whether to display the internal Donchian channel. + * • Donchian.Channel.UpperColor: Color of upper Donchian channel band. + * • Donchian.Channel.LowerColor: Color of lower Donchian channel band. * - * • Donchian.ShowCrossings: Which Donchian channel crossings to display, one of: - * "off": No crossings are displayed. - * "first": Only the first crossing is displayed (the moment a new ZigZag leg appears). - * "all": All crossings are displayed. Displays the trail of a ZigZag leg as it develops over time. - * • Donchian.Crossing.Symbol: Graphic symbol used for Donchian channel crossings. - * • Donchian.Crossing.Width: Size of displayed Donchian channel crossings. - * • Donchian.Crossing.Color: Custom color of channel crossings (default: color of channel bands). + * • Donchian.ShowCrossings: Which Donchian channel crossings to display, one of: + * "off": No crossings are displayed. + * "first": Only the first crossing is displayed (the moment a new ZigZag leg appears). + * "all": All crossings are displayed. Displays the trail of a ZigZag leg as it develops over time. + * • Donchian.Crossing.Symbol: Graphic symbol used for Donchian channel crossings. + * • Donchian.Crossing.Width: Size of displayed Donchian channel crossings. + * • Donchian.Crossing.Color: Custom color of channel crossings (default: color of channel bands). * - * • ShowChartLegend: Whether do display the chart legend. - * • MaxBarsBack: Maximum number of bars back to calculate the indicator for (affects performance). + * • ShowChartLegend: Whether do display the chart legend. + * • MaxBarsBack: Maximum number of bars back to calculate the indicator for (affects performance). * - * • Signal.onReversal: Whether to signal ZigZag reversals (the moment a new ZigZag leg appears). - * • Signal.onReversal.Types: Signaling methods, can be a combination of "sound", "alert" and/or "mail". + * • Signal.onReversal: Whether to signal ZigZag reversals (the moment a new ZigZag leg appears). + * • Signal.onReversal.Types: Signaling methods, can be a combination of "sound", "alert" and/or "mail". * - * • Signal.onBreakout: Whether to signal ZigZag breakouts (a ZigZag leg exceeding the previous ZigZag leg). - * • Signal.onBreakout.Types: Signaling methods, can be a combination of "sound", "alert" and/or "mail". + * • Signal.onBreakout: Whether to signal ZigZag breakouts (a ZigZag leg exceeding the previous ZigZag leg). + * • Signal.onBreakout.Types: Signaling methods, can be a combination of "sound", "alert" and/or "mail". * - * • Signal.Sound.Up: Sound file for signals to the upside. - * • Signal.Sound.Down: Sound file for signals to the downside. + * • Signal.Sound.Up: Sound file for signals to the upside. + * • Signal.Sound.Down: Sound file for signals to the downside. * - * • Sound.onChannelWidening: Whether to play a sound on Donchian channel widening (channel crossings). - * • Sound.onNewChannelHigh: Sound file for channel widenings to the upside. - * • Sound.onNewChannelLow: Sound file for channel widenings to the downside. + * • Sound.onChannelWidening: Whether to play a sound on Donchian channel widening (channel crossings). + * • Sound.onNewChannelHigh: Sound file for channel widenings to the upside. + * • Sound.onNewChannelLow: Sound file for channel widenings to the downside. * - * • TrackSignalPerformance: Whether to track the performance of the reversal signal. - * • TrackSignalPerformance.Since: Start time to track signal performance from (default: MaxBarsBack). - * • TrackSignalPerformance.Symbol: Custom symbol to use for performance tracking (default: auto-generated). + * • TrackVirtualProfit: Whether to track the virtual PnL of reversal signals. + * • TrackVirtualProfit.Since: Start time to track virtual PnL from (default: MaxBarsBack). + * • TrackVirtualProfit.Symbol: Custom symbol to use for virtual PnL tracking (default: auto-generated). * * • AutoConfiguration: If enabled all input parameters may use predefined defaults from the configuration. * @@ -108,10 +108,10 @@ extern bool Sound.onChannelWidening = false; extern string Sound.onNewChannelHigh = "Price Advance.wav"; extern string Sound.onNewChannelLow = "Price Decline.wav"; -extern string ___e__________________________ = "=== Signal performance ==="; -extern bool TrackSignalPerformance = false; // whether to track the signal performance -extern datetime TrackSignalPerformance.Since = 0; // start time to track signal performance from -extern string TrackSignalPerformance.Symbol = "(default)"; // custom symbol to use for performance tracking +extern string ___e__________________________ = "=== Signal tracking ==="; +extern bool TrackVirtualProfit = false; // whether to track virtual PnL of signals +extern datetime TrackVirtualProfit.Since = 0; // start time to track virtual PnL from +extern string TrackVirtualProfit.Symbol = "(default)"; // custom symbol to use for virtual PnL tracking ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// @@ -136,74 +136,74 @@ bool ProjectNextBalance = false; // whether to project zero-balance // indicator buffer ids -#define MODE_UPPER_BAND ZigZag.MODE_UPPER_BAND // 0: upper channel band: positive or 0 -#define MODE_LOWER_BAND ZigZag.MODE_LOWER_BAND // 1: lower channel band: positive or 0 -#define MODE_SEMAPHORE_OPEN ZigZag.MODE_SEMAPHORE_OPEN // 2: final semaphores, open price: positive or 0 -#define MODE_SEMAPHORE_CLOSE ZigZag.MODE_SEMAPHORE_CLOSE // 3: final semaphores, close price: positive or 0 (if open != close it forms a vertical line segment) -#define MODE_UPPER_CROSS ZigZag.MODE_UPPER_CROSS // 4: upper channel band crossings: positive or 0 -#define MODE_LOWER_CROSS ZigZag.MODE_LOWER_CROSS // 5: lower channel band crossings: positive or 0 -#define MODE_REVERSAL_OFFSET ZigZag.MODE_REVERSAL_OFFSET // 6: int: offset of the ZigZag reversal to the leg's start semaphore: non-negative or -1 -#define MODE_COMBINED_TREND ZigZag.MODE_COMBINED_TREND // 7: int: combined buffers MODE_TREND & MODE_UNKNOWN_TREND: positive/negative or 0 -#define MODE_UPPER_CROSS_HIGH 8 // 8: bar High of an upper channel band crossing: positive or 0 -#define MODE_LOWER_CROSS_LOW 9 // 9: bar Low of a lower channel band crossing: positive or 0 -#define MODE_TREND 10 // 10: int: length of a ZigZag leg: positive/negative or 0 -#define MODE_UNKNOWN_TREND 11 // 11: int: number of bars after a leg's end semaphore: non-negative or -1 -#define MODE_SIGNAL_PERFORMANCE_O 12 // 12: signal performance in price units: positive/negative or EMPTY_VALUE -#define MODE_SIGNAL_PERFORMANCE_H 13 // 13: ... -#define MODE_SIGNAL_PERFORMANCE_L 14 // 14: ... -#define MODE_SIGNAL_PERFORMANCE_C 15 // 15: ... +#define MODE_UPPER_BAND ZigZag.MODE_UPPER_BAND // 0: upper channel band: positive or 0 +#define MODE_LOWER_BAND ZigZag.MODE_LOWER_BAND // 1: lower channel band: positive or 0 +#define MODE_SEMAPHORE_OPEN ZigZag.MODE_SEMAPHORE_OPEN // 2: final semaphores, open price: positive or 0 +#define MODE_SEMAPHORE_CLOSE ZigZag.MODE_SEMAPHORE_CLOSE // 3: final semaphores, close price: positive or 0 (if open != close it forms a vertical line segment) +#define MODE_UPPER_CROSS ZigZag.MODE_UPPER_CROSS // 4: upper channel band crossings: positive or 0 +#define MODE_LOWER_CROSS ZigZag.MODE_LOWER_CROSS // 5: lower channel band crossings: positive or 0 +#define MODE_REVERSAL_OFFSET ZigZag.MODE_REVERSAL_OFFSET // 6: int: offset of the ZigZag reversal to the leg's start semaphore: non-negative or -1 +#define MODE_COMBINED_TREND ZigZag.MODE_COMBINED_TREND // 7: int: combined buffers MODE_TREND & MODE_UNKNOWN_TREND: positive/negative or 0 +#define MODE_UPPER_CROSS_HIGH 8 // 8: bar High of an upper channel band crossing: positive or 0 +#define MODE_LOWER_CROSS_LOW 9 // 9: bar Low of a lower channel band crossing: positive or 0 +#define MODE_TREND 10 // 10: int: length of a ZigZag leg: positive/negative or 0 +#define MODE_UNKNOWN_TREND 11 // 11: int: number of bars after a leg's end semaphore: non-negative or -1 +#define MODE_VIRTUAL_PROFIT_O 12 // 12: signal performance in price units: positive/negative or EMPTY_VALUE +#define MODE_VIRTUAL_PROFIT_H 13 // 13: ... +#define MODE_VIRTUAL_PROFIT_L 14 // 14: ... +#define MODE_VIRTUAL_PROFIT_C 15 // 15: ... #property indicator_chart_window -#property indicator_buffers 8 // visible buffers -int terminal_buffers = 8; // buffers managed by the terminal -int framework_buffers = 8; // buffers managed by the framework - -#property indicator_color1 Blue // upper channel band -#property indicator_style1 STYLE_DOT // -#property indicator_color2 Magenta // lower channel band -#property indicator_style2 STYLE_DOT // - -#property indicator_color3 DodgerBlue // the ZigZag line is built from two buffers using the color of the first buffer -#property indicator_width3 1 // -#property indicator_color4 CLR_NONE // - -#property indicator_color5 indicator_color3 // upper channel band crossings -#property indicator_width5 0 // -#property indicator_color6 indicator_color4 // lower channel band crossings -#property indicator_width6 0 // - -#property indicator_color7 CLR_NONE // trend (combined buffers MODE_TREND & MODE_UNKNOWN_TREND) -#property indicator_color8 CLR_NONE // offset of the previous ZigZag reversal to its preceeding semaphore - -double upperBand []; // upper channel band: positive or 0 -double lowerBand []; // lower channel band: positive or 0 -double upperCross []; // upper channel band crossings: positive or 0 -double upperCrossHigh []; // bar High of an upper channel band crossing (potential semaphore): positive or 0 -double lowerCross []; // lower channel band crossings: positive or 0 -double lowerCrossLow []; // bar Low of a lower channel band crossing (potential semaphore): positive or 0 -double semaphoreOpen []; // final semaphore, open price: positive or 0 -double semaphoreClose []; // final semaphore, close price: positive or 0 (if open != close it creates a vertical line segment) -double reversalOffset []; // int: offset of the ZigZag reversal to the leg's start semaphore (): non-negative or -1 -int trend []; // int: length of a ZigZag leg: positive/negative or 0 -int unknownTrend []; // int: number of bars after a leg's end semaphore: non-negative or -1 -double combinedTrend []; // int: combined buffers MODE_TREND & MODE_UNKNOWN_TREND: positive/negative or 0 -double signalPerformanceO[]; // signal performance in price units: positive/negative or EMPTY_VALUE -double signalPerformanceH[]; // ... -double signalPerformanceL[]; // ... -double signalPerformanceC[]; // ... +#property indicator_buffers 8 // visible buffers +int terminal_buffers = 8; // buffers managed by the terminal +int framework_buffers = 8; // buffers managed by the framework + +#property indicator_color1 Blue // upper channel band +#property indicator_style1 STYLE_DOT // +#property indicator_color2 Magenta // lower channel band +#property indicator_style2 STYLE_DOT // + +#property indicator_color3 DodgerBlue // the ZigZag line is built from two buffers using the color of the first buffer +#property indicator_width3 1 // +#property indicator_color4 CLR_NONE // + +#property indicator_color5 indicator_color3 // upper channel band crossings +#property indicator_width5 0 // +#property indicator_color6 indicator_color4 // lower channel band crossings +#property indicator_width6 0 // + +#property indicator_color7 CLR_NONE // trend (combined buffers MODE_TREND & MODE_UNKNOWN_TREND) +#property indicator_color8 CLR_NONE // offset of the previous ZigZag reversal to its preceeding semaphore + +double upperBand []; // upper channel band: positive or 0 +double lowerBand []; // lower channel band: positive or 0 +double upperCross []; // upper channel band crossings: positive or 0 +double upperCrossHigh []; // bar High of an upper channel band crossing (potential semaphore): positive or 0 +double lowerCross []; // lower channel band crossings: positive or 0 +double lowerCrossLow []; // bar Low of a lower channel band crossing (potential semaphore): positive or 0 +double semaphoreOpen []; // final semaphore, open price: positive or 0 +double semaphoreClose []; // final semaphore, close price: positive or 0 (if open != close it creates a vertical line segment) +double reversalOffset []; // int: offset of the ZigZag reversal to the leg's start semaphore (): non-negative or -1 +int trend []; // int: length of a ZigZag leg: positive/negative or 0 +int unknownTrend []; // int: number of bars after a leg's end semaphore: non-negative or -1 +double combinedTrend []; // int: combined buffers MODE_TREND & MODE_UNKNOWN_TREND: positive/negative or 0 +double virtualProfit_O[]; // virtual signal PnL in price units: positive/negative or EMPTY_VALUE +double virtualProfit_H[]; // ... +double virtualProfit_L[]; // ... +double virtualProfit_C[]; // ... string indicatorName = ""; string shortName = ""; string legendLabel = ""; -string legendInfo = ""; // additional chart legend info -string labels[]; // chart object labels +string legendInfo = ""; // additional chart legend info +string labels[]; // chart object labels int zigzagDrawType; int zigzagSymbol; int crossingDrawType; int crossingSymbol; -#define MODE_FIRST_CROSSING 1 // crossing draw types +#define MODE_FIRST_CROSSING 1 // draw types of channel crossing #define MODE_ALL_CROSSINGS 2 bool signal.onReversal.sound; @@ -214,19 +214,19 @@ bool signal.onBreakout.sound; bool signal.onBreakout.alert; bool signal.onBreakout.mail; -int dbc_reversalOffset; // status vars for the 1st of a double crossing +int dbc_reversalOffset; // status vars for the 1st of a double crossing int dbc_trend; int dbc_unknownTrend; -double sema1, sema2, sema3; // last 3 semaphores for detection of ZigZag breakouts -double lastLegHigh, lastLegLow; // leg high/low at the previous tick +double sema1, sema2, sema3; // last 3 semaphores for detection of ZigZag breakouts +double lastLegHigh, lastLegLow; // leg high/low at the previous tick -double lastUpperBand; // detection of channel widenings -double lastLowerBand; // upper/lower band values at the previous tick +double lastUpperBand; // detection of channel widenings +double lastLowerBand; // upper/lower band values at the previous tick -datetime skipSignals; // skip signals until the specified time to wait for possible data pumping +datetime skipSignals; // skip signals until the specified time to wait for possible data pumping datetime lastTick; -int lastSoundSignal; // GetTickCount() value of the last audio signal +int lastSoundSignal; // GetTickCount() value of the last audio signal // recorder status @@ -242,8 +242,8 @@ datetime recorder.startTime; // signal direction types -#define D_LONG TRADE_DIRECTION_LONG // 1 -#define D_SHORT TRADE_DIRECTION_SHORT // 2 +#define D_LONG TRADE_DIRECTION_LONG // 1 +#define D_SHORT TRADE_DIRECTION_SHORT // 2 // parameter stepper directions #define STEP_UP 1 @@ -505,24 +505,24 @@ int onInit() { else legendInfo = StrLeft(legendInfo, -1) +",w)"; } - // TrackSignalPerformance - if (AutoConfiguration) TrackSignalPerformance = GetConfigBool(indicator, "TrackSignalPerformance", TrackSignalPerformance); - if (__isSuperContext) TrackSignalPerformance = false; - if (__isTesting) TrackSignalPerformance = false; - // TrackSignalPerformance.Since - datetime dtValue = TrackSignalPerformance.Since; + // TrackVirtualProfit + if (AutoConfiguration) TrackVirtualProfit = GetConfigBool(indicator, "TrackVirtualProfit", TrackVirtualProfit); + if (__isSuperContext) TrackVirtualProfit = false; + if (__isTesting) TrackVirtualProfit = false; + // TrackVirtualProfit.Since + datetime dtValue = TrackVirtualProfit.Since; if (AutoConfiguration) { - sValue = GetConfigString(indicator, "TrackSignalPerformance.Since", ""); + sValue = GetConfigString(indicator, "TrackVirtualProfit.Since", ""); if (sValue != "") { int result[]; if (!ParseDateTime(sValue, DATE_YYYYMMDD|TIME_OPTIONAL, result)) { - return(catch("onInit(12) invalid config parameter TrackSignalPerformance.Since: "+ DoubleQuoteStr(sValue), ERR_INVALID_INPUT_PARAMETER)); + return(catch("onInit(12) invalid config parameter TrackVirtualProfit.Since: "+ DoubleQuoteStr(sValue), ERR_INVALID_INPUT_PARAMETER)); } - TrackSignalPerformance.Since = DateTime2(result); + TrackVirtualProfit.Since = DateTime2(result); } } - // TrackSignalPerformance.Symbol - if (AutoConfiguration) TrackSignalPerformance.Symbol = GetConfigBool(indicator, "TrackSignalPerformance.Symbol", TrackSignalPerformance.Symbol); + // TrackVirtualProfit.Symbol + if (AutoConfiguration) TrackVirtualProfit.Symbol = GetConfigBool(indicator, "TrackVirtualProfit.Symbol", TrackVirtualProfit.Symbol); // reset global vars used by the various event handlers skipSignals = 0; @@ -586,34 +586,34 @@ int onTick() { if (!HandleCommands("ParameterStepper")) return(last_error); } - // manage additional fraemwork buffers - ManageDoubleIndicatorBuffer(MODE_UPPER_CROSS_HIGH, upperCrossHigh); - ManageDoubleIndicatorBuffer(MODE_LOWER_CROSS_LOW, lowerCrossLow); - ManageIntIndicatorBuffer (MODE_TREND, trend); - ManageIntIndicatorBuffer (MODE_UNKNOWN_TREND, unknownTrend, -1); - ManageDoubleIndicatorBuffer(MODE_SIGNAL_PERFORMANCE_O, signalPerformanceO, EMPTY_VALUE); - ManageDoubleIndicatorBuffer(MODE_SIGNAL_PERFORMANCE_H, signalPerformanceH, EMPTY_VALUE); - ManageDoubleIndicatorBuffer(MODE_SIGNAL_PERFORMANCE_L, signalPerformanceL, EMPTY_VALUE); - ManageDoubleIndicatorBuffer(MODE_SIGNAL_PERFORMANCE_C, signalPerformanceC, EMPTY_VALUE); + // manage additional framework buffers + ManageDoubleIndicatorBuffer(MODE_UPPER_CROSS_HIGH, upperCrossHigh); + ManageDoubleIndicatorBuffer(MODE_LOWER_CROSS_LOW, lowerCrossLow); + ManageIntIndicatorBuffer (MODE_TREND, trend); + ManageIntIndicatorBuffer (MODE_UNKNOWN_TREND, unknownTrend, -1); + ManageDoubleIndicatorBuffer(MODE_VIRTUAL_PROFIT_O, virtualProfit_O, EMPTY_VALUE); + ManageDoubleIndicatorBuffer(MODE_VIRTUAL_PROFIT_H, virtualProfit_H, EMPTY_VALUE); + ManageDoubleIndicatorBuffer(MODE_VIRTUAL_PROFIT_L, virtualProfit_L, EMPTY_VALUE); + ManageDoubleIndicatorBuffer(MODE_VIRTUAL_PROFIT_C, virtualProfit_C, EMPTY_VALUE); // reset buffers before performing a full recalculation if (!ValidBars) { - ArrayInitialize(upperBand, 0); // double: positive or 0 - ArrayInitialize(lowerBand, 0); // double: positive or 0 - ArrayInitialize(upperCross, 0); // double: positive or 0 - ArrayInitialize(upperCrossHigh, 0); // double: positive or 0 - ArrayInitialize(lowerCross, 0); // double: positive or 0 - ArrayInitialize(lowerCrossLow, 0); // double: positive or 0 - ArrayInitialize(semaphoreOpen, 0); // double: positive or 0 - ArrayInitialize(semaphoreClose, 0); // double: positive or 0 - ArrayInitialize(reversalOffset, -1); // int: non-negative or -1 - ArrayInitialize(trend, 0); // int: positive/negative or 0 - ArrayInitialize(unknownTrend, -1); // int: non-negative or -1 - ArrayInitialize(combinedTrend, 0); // int: positive/negative or 0 - ArrayInitialize(signalPerformanceO, EMPTY_VALUE); // double: positive/negative or EMPTY_VALUE - ArrayInitialize(signalPerformanceH, EMPTY_VALUE); // double: positive/negative or EMPTY_VALUE - ArrayInitialize(signalPerformanceL, EMPTY_VALUE); // double: positive/negative or EMPTY_VALUE - ArrayInitialize(signalPerformanceC, EMPTY_VALUE); // double: positive/negative or EMPTY_VALUE + ArrayInitialize(upperBand, 0); // double: positive or 0 + ArrayInitialize(lowerBand, 0); // double: positive or 0 + ArrayInitialize(upperCross, 0); // double: positive or 0 + ArrayInitialize(upperCrossHigh, 0); // double: positive or 0 + ArrayInitialize(lowerCross, 0); // double: positive or 0 + ArrayInitialize(lowerCrossLow, 0); // double: positive or 0 + ArrayInitialize(semaphoreOpen, 0); // double: positive or 0 + ArrayInitialize(semaphoreClose, 0); // double: positive or 0 + ArrayInitialize(reversalOffset, -1); // int: non-negative or -1 + ArrayInitialize(trend, 0); // int: positive/negative or 0 + ArrayInitialize(unknownTrend, -1); // int: non-negative or -1 + ArrayInitialize(combinedTrend, 0); // int: positive/negative or 0 + ArrayInitialize(virtualProfit_O, EMPTY_VALUE); // double: positive/negative or EMPTY_VALUE + ArrayInitialize(virtualProfit_H, EMPTY_VALUE); // double: positive/negative or EMPTY_VALUE + ArrayInitialize(virtualProfit_L, EMPTY_VALUE); // double: positive/negative or EMPTY_VALUE + ArrayInitialize(virtualProfit_C, EMPTY_VALUE); // double: positive/negative or EMPTY_VALUE dbc_reversalOffset = -1; dbc_trend = 0; @@ -631,22 +631,22 @@ int onTick() { // synchronize buffers with a shifted offline chart if (ShiftedBars > 0) { - ShiftDoubleIndicatorBuffer(upperBand, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(lowerBand, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(upperCross, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(upperCrossHigh, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(lowerCross, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(lowerCrossLow, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(semaphoreOpen, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(semaphoreClose, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(reversalOffset, Bars, ShiftedBars, -1); - ShiftIntIndicatorBuffer (trend, Bars, ShiftedBars, 0); - ShiftIntIndicatorBuffer (unknownTrend, Bars, ShiftedBars, -1); - ShiftDoubleIndicatorBuffer(combinedTrend, Bars, ShiftedBars, 0); - ShiftDoubleIndicatorBuffer(signalPerformanceO, Bars, ShiftedBars, EMPTY_VALUE); - ShiftDoubleIndicatorBuffer(signalPerformanceH, Bars, ShiftedBars, EMPTY_VALUE); - ShiftDoubleIndicatorBuffer(signalPerformanceL, Bars, ShiftedBars, EMPTY_VALUE); - ShiftDoubleIndicatorBuffer(signalPerformanceC, Bars, ShiftedBars, EMPTY_VALUE); + ShiftDoubleIndicatorBuffer(upperBand, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(lowerBand, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(upperCross, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(upperCrossHigh, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(lowerCross, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(lowerCrossLow, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(semaphoreOpen, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(semaphoreClose, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(reversalOffset, Bars, ShiftedBars, -1); + ShiftIntIndicatorBuffer (trend, Bars, ShiftedBars, 0); + ShiftIntIndicatorBuffer (unknownTrend, Bars, ShiftedBars, -1); + ShiftDoubleIndicatorBuffer(combinedTrend, Bars, ShiftedBars, 0); + ShiftDoubleIndicatorBuffer(virtualProfit_O, Bars, ShiftedBars, EMPTY_VALUE); + ShiftDoubleIndicatorBuffer(virtualProfit_H, Bars, ShiftedBars, EMPTY_VALUE); + ShiftDoubleIndicatorBuffer(virtualProfit_L, Bars, ShiftedBars, EMPTY_VALUE); + ShiftDoubleIndicatorBuffer(virtualProfit_C, Bars, ShiftedBars, EMPTY_VALUE); } // check data pumping on every tick so the reversal handler can skip errornous signals @@ -659,22 +659,22 @@ int onTick() { // recalculate changed bars if (startBar > 2) { // TODO: why 2 and not 1 - upperBand [startBar] = 0; - lowerBand [startBar] = 0; - upperCross [startBar] = 0; - upperCrossHigh [startBar] = 0; - lowerCross [startBar] = 0; - lowerCrossLow [startBar] = 0; - semaphoreOpen [startBar] = 0; - semaphoreClose [startBar] = 0; - reversalOffset [startBar] = -1; - trend [startBar] = 0; - unknownTrend [startBar] = -1; - combinedTrend [startBar] = 0; - signalPerformanceO[startBar] = EMPTY_VALUE; - signalPerformanceH[startBar] = EMPTY_VALUE; - signalPerformanceL[startBar] = EMPTY_VALUE; - signalPerformanceC[startBar] = EMPTY_VALUE; + upperBand [startBar] = 0; + lowerBand [startBar] = 0; + upperCross [startBar] = 0; + upperCrossHigh [startBar] = 0; + lowerCross [startBar] = 0; + lowerCrossLow [startBar] = 0; + semaphoreOpen [startBar] = 0; + semaphoreClose [startBar] = 0; + reversalOffset [startBar] = -1; + trend [startBar] = 0; + unknownTrend [startBar] = -1; + combinedTrend [startBar] = 0; + virtualProfit_O[startBar] = EMPTY_VALUE; + virtualProfit_H[startBar] = EMPTY_VALUE; + virtualProfit_L[startBar] = EMPTY_VALUE; + virtualProfit_C[startBar] = EMPTY_VALUE; } datetime startRecalc = GetTickCount(); @@ -739,9 +739,9 @@ int onTick() { isReversalBar = (Abs(trend[bar]) == reversalOffset[bar]); } - // calculate signal performance - if (TrackSignalPerformance) { - if (!RecalculateSignalPerformance(bar, isReversalBar)) return(last_error); + // calculate virtual PnL + if (TrackVirtualProfit) { + if (!RecalculateVirtualProfit(bar, isReversalBar)) return(last_error); } if (debugging && Ticks == 1) { @@ -792,14 +792,14 @@ int onTick() { if (__isChart && !__isSuperContext) { if (ShowChartLegend) UpdateChartLegend(); - // record signal performance - if (TrackSignalPerformance) { + // record virtual PnL + if (TrackVirtualProfit) { datetime startRecorder = GetTickCount(); - if (!RecordSignalPerformance()) return(last_error); + if (!RecordVirtualProfit()) return(last_error); //if (Ticks == 1) { - // debug("onTick(0.5) Tick="+ Ticks +" RecordSignalPerformance() => "+ DoubleToStr((GetTickCount()-startRecorder)/1000.0, 3) +" sec"); + // debug("onTick(0.5) Tick="+ Ticks +" RecordVirtualProfit() => "+ DoubleToStr((GetTickCount()-startRecorder)/1000.0, 3) +" sec"); //} } @@ -948,101 +948,101 @@ int onTick() { /** - * Recalculate the signal performance for the specified bar. + * Recalculate the virtual PnL for the specified bar. * * @param int bar - bar offset * @param bool isReversal - whether the bar is a reversal bar * * @return bool - success status */ -bool RecalculateSignalPerformance(int bar, bool isReversal) { +bool RecalculateVirtualProfit(int bar, bool isReversal) { isReversal = (isReversal != 0); - signalPerformanceO[bar] = signalPerformanceC[bar+1]; - signalPerformanceH[bar] = signalPerformanceC[bar+1]; - signalPerformanceL[bar] = signalPerformanceC[bar+1]; - signalPerformanceC[bar] = signalPerformanceC[bar+1]; + virtualProfit_O[bar] = virtualProfit_C[bar+1]; + virtualProfit_H[bar] = virtualProfit_C[bar+1]; + virtualProfit_L[bar] = virtualProfit_C[bar+1]; + virtualProfit_C[bar] = virtualProfit_C[bar+1]; - bool isPosition = (signalPerformanceC[bar] != EMPTY_VALUE); + bool isPosition = (virtualProfit_C[bar] != EMPTY_VALUE); // reversal bar, flip the position if (isReversal) { if (!isPosition) { - signalPerformanceO[bar] = 0; - signalPerformanceH[bar] = 0; - signalPerformanceL[bar] = 0; - signalPerformanceC[bar] = 0; + virtualProfit_O[bar] = 0; + virtualProfit_H[bar] = 0; + virtualProfit_L[bar] = 0; + virtualProfit_C[bar] = 0; } if (trend[bar] > 0) { if (isPosition) { - signalPerformanceC[bar] -= (upperCross[bar] - Close[bar+1]); // close short position - signalPerformanceO[bar] = signalPerformanceC[bar] - (Open[bar] - upperCross[bar]); - signalPerformanceC[bar] += (Close[bar] - upperCross[bar]); // open new long position - signalPerformanceH[bar] = MathMax(signalPerformanceO[bar], signalPerformanceC[bar]); - signalPerformanceL[bar] = MathMin(signalPerformanceO[bar], signalPerformanceC[bar]); // the intra-bar path is unknown + virtualProfit_C[bar] -= (upperCross[bar] - Close[bar+1]); // close short position + virtualProfit_O[bar] = virtualProfit_C[bar] - (Open[bar] - upperCross[bar]); + virtualProfit_C[bar] += (Close[bar] - upperCross[bar]); // open new long position + virtualProfit_H[bar] = MathMax(virtualProfit_O[bar], virtualProfit_C[bar]); + virtualProfit_L[bar] = MathMin(virtualProfit_O[bar], virtualProfit_C[bar]); // the intra-bar path is unknown } else { - signalPerformanceO[bar] = 0; // open long position - signalPerformanceH[bar] = ( High[bar] - upperCross[bar]); - signalPerformanceL[bar] = ( Low[bar] - upperCross[bar]); - signalPerformanceC[bar] = (Close[bar] - upperCross[bar]); + virtualProfit_O[bar] = 0; // open long position + virtualProfit_H[bar] = ( High[bar] - upperCross[bar]); + virtualProfit_L[bar] = ( Low[bar] - upperCross[bar]); + virtualProfit_C[bar] = (Close[bar] - upperCross[bar]); } } else if (trend[bar] < 0) { if (isPosition) { - signalPerformanceC[bar] += (lowerCross[bar] - Close[bar+1]); // close long position - signalPerformanceO[bar] = signalPerformanceC[bar] + (Open[bar] - lowerCross[bar]); - signalPerformanceC[bar] += (lowerCross[bar] - Close[bar]); // open new short position - signalPerformanceH[bar] = MathMax(signalPerformanceO[bar], signalPerformanceC[bar]); - signalPerformanceL[bar] = MathMin(signalPerformanceO[bar], signalPerformanceC[bar]); // the intra-bar path is unknown + virtualProfit_C[bar] += (lowerCross[bar] - Close[bar+1]); // close long position + virtualProfit_O[bar] = virtualProfit_C[bar] + (Open[bar] - lowerCross[bar]); + virtualProfit_C[bar] += (lowerCross[bar] - Close[bar]); // open new short position + virtualProfit_H[bar] = MathMax(virtualProfit_O[bar], virtualProfit_C[bar]); + virtualProfit_L[bar] = MathMin(virtualProfit_O[bar], virtualProfit_C[bar]); // the intra-bar path is unknown } else { - signalPerformanceO[bar] = 0; // open short position - signalPerformanceH[bar] = (lowerCross[bar] - Low[bar]); - signalPerformanceL[bar] = (lowerCross[bar] - High[bar]); - signalPerformanceC[bar] = (lowerCross[bar] - Close[bar]); + virtualProfit_O[bar] = 0; // open short position + virtualProfit_H[bar] = (lowerCross[bar] - Low[bar]); + virtualProfit_L[bar] = (lowerCross[bar] - High[bar]); + virtualProfit_C[bar] = (lowerCross[bar] - Close[bar]); } } else /*trend == 0*/{ // double crossing with two semaphores (at the moment when the bar is processed) - if (semaphoreOpen[bar] == Low[bar]) { // crossing order "Low, High" + if (semaphoreOpen[bar] == Low[bar]) { // crossing order "Low, High" if (isPosition) { - signalPerformanceC[bar] += (lowerCross[bar] - Close[bar+1]); // close existing long position + virtualProfit_C[bar] += (lowerCross[bar] - Close[bar+1]); // close existing long position } - signalPerformanceC[bar] -= (upperCross[bar] - lowerCross[bar]); // open new short position and immediately close it - signalPerformanceC[bar] += (Close[bar] - upperCross[bar]); // open new long position + virtualProfit_C[bar] -= (upperCross[bar] - lowerCross[bar]); // open new short position and immediately close it + virtualProfit_C[bar] += (Close[bar] - upperCross[bar]); // open new long position } - else if (semaphoreOpen[bar] == High[bar]) { // crossing order "High, Low" + else if (semaphoreOpen[bar] == High[bar]) { // crossing order "High, Low" if (isPosition) { - signalPerformanceC[bar] -= (upperCross[bar] - Close[bar+1]); // close existing short position + virtualProfit_C[bar] -= (upperCross[bar] - Close[bar+1]); // close existing short position } - signalPerformanceC[bar] -= (upperCross[bar] - lowerCross[bar]); // open new long position and immediately close it - signalPerformanceC[bar] += (lowerCross[bar] - Close[bar]); // open new short position + virtualProfit_C[bar] -= (upperCross[bar] - lowerCross[bar]); // open new long position and immediately close it + virtualProfit_C[bar] += (lowerCross[bar] - Close[bar]); // open new short position } - else return(!catch("RecalculateSignalPerformance(1) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected reversal bar with trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); + else return(!catch("RecalculateVirtualProfit(1) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected reversal bar with trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); - signalPerformanceO[bar] = signalPerformanceC[bar+1]; - signalPerformanceH[bar] = MathMax(signalPerformanceO[bar], signalPerformanceC[bar]); - signalPerformanceL[bar] = MathMin(signalPerformanceO[bar], signalPerformanceC[bar]); + virtualProfit_O[bar] = virtualProfit_C[bar+1]; + virtualProfit_H[bar] = MathMax(virtualProfit_O[bar], virtualProfit_C[bar]); + virtualProfit_L[bar] = MathMin(virtualProfit_O[bar], virtualProfit_C[bar]); } } // normal bar, update an existing position else if (isPosition) { if (trend[bar] > 0) { - signalPerformanceC[bar] += (Close[bar] - Close[bar+1]); - signalPerformanceO[bar] = signalPerformanceC[bar] + (Open[bar] - Close[bar]); - signalPerformanceH[bar] = signalPerformanceC[bar] + (High[bar] - Close[bar]); - signalPerformanceL[bar] = signalPerformanceC[bar] + ( Low[bar] - Close[bar]); + virtualProfit_C[bar] += (Close[bar] - Close[bar+1]); + virtualProfit_O[bar] = virtualProfit_C[bar] + (Open[bar] - Close[bar]); + virtualProfit_H[bar] = virtualProfit_C[bar] + (High[bar] - Close[bar]); + virtualProfit_L[bar] = virtualProfit_C[bar] + ( Low[bar] - Close[bar]); } else if (trend[bar] < 0) { - signalPerformanceC[bar] -= (Close[bar] - Close[bar+1]); - signalPerformanceO[bar] = signalPerformanceC[bar] - (Open[bar] - Close[bar]); - signalPerformanceH[bar] = signalPerformanceC[bar] - ( Low[bar] - Close[bar]); - signalPerformanceL[bar] = signalPerformanceC[bar] - (High[bar] - Close[bar]); + virtualProfit_C[bar] -= (Close[bar] - Close[bar+1]); + virtualProfit_O[bar] = virtualProfit_C[bar] - (Open[bar] - Close[bar]); + virtualProfit_H[bar] = virtualProfit_C[bar] - ( Low[bar] - Close[bar]); + virtualProfit_L[bar] = virtualProfit_C[bar] - (High[bar] - Close[bar]); } - else return(!catch("RecalculateSignalPerformance(2) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected non-reversal bar with trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); + else return(!catch("RecalculateVirtualProfit(2) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected non-reversal bar with trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); } // normal bar without a position (before first ZigZag reversal) @@ -1053,11 +1053,11 @@ bool RecalculateSignalPerformance(int bar, bool isReversal) { /** - * Record the signal performance for the specified bar. + * Record the timeseries with virtual PnL. * * @return bool - success status */ -bool RecordSignalPerformance() { +bool RecordVirtualProfit() { if (!recorder.initialized) { // create symbol and group recorder.symbol = Symbol() +".zzr"; @@ -1081,7 +1081,7 @@ bool RecordSignalPerformance() { if (!recorder.hSet) return(false); } recorder.initialized = true; - debug("RecordSignalPerformance(0.1) Tick="+ Ticks +" recorder initialized"); + debug("RecordVirtualProfit(0.1) Tick="+ Ticks +" recorder initialized"); } int startBar = 0, flags = HST_FILL_GAPS|HST_BUFFER_TICKS; @@ -1094,7 +1094,7 @@ bool RecordSignalPerformance() { if (!HistorySet1.Close(tmp)) return(false); // TODO: HistorySet.Create() should auto-close an open set but errors } startBar = iBarShiftNext(NULL, NULL, recorder.startTime); - debug("RecordSignalPerformance(0.2) Tick="+ Ticks +" rewriting all history since "+ TimeToStr(recorder.startTime) +" (bar "+ startBar +")"); + debug("RecordVirtualProfit(0.2) Tick="+ Ticks +" rewriting all history since "+ TimeToStr(recorder.startTime) +" (bar "+ startBar +")"); } for (int bar=startBar; bar >= 0; bar--) { @@ -1103,10 +1103,10 @@ bool RecordSignalPerformance() { if (!recorder.hSet) return(false); } - O = signalPerformanceO[bar]; - H = signalPerformanceH[bar]; - L = signalPerformanceL[bar]; - C = signalPerformanceC[bar]; + O = virtualProfit_O[bar]; + H = virtualProfit_H[bar]; + L = virtualProfit_L[bar]; + C = virtualProfit_C[bar]; if (C >= EMPTY_VALUE) continue; L += recorder.priceBase; @@ -1121,7 +1121,7 @@ bool RecordSignalPerformance() { case 100000: recorder.priceBase = 1000000; break; case 1000000: recorder.priceBase = 10000000; break; } - debug("RecordSignalPerformance(0.3) Tick="+ Ticks +" updated price base to "+ DoubleToStr(recorder.priceBase, 2)); + debug("RecordVirtualProfit(0.3) Tick="+ Ticks +" updated price base to "+ DoubleToStr(recorder.priceBase, 2)); tmp = recorder.hSet; recorder.hSet = NULL; @@ -1870,38 +1870,38 @@ bool RestoreStatus() { * @return string */ string InputsToStr() { - return(StringConcatenate("ZigZag.Periods=", ZigZag.Periods +";"+ NL, - "ZigZag.Periods.Step=", ZigZag.Periods.Step +";"+ NL, - "ZigZag.Type=", DoubleQuoteStr(ZigZag.Type) +";"+ NL, - "ZigZag.Semaphores.Symbol=", DoubleQuoteStr(ZigZag.Semaphores.Symbol) +";"+ NL, - "ZigZag.Width=", ZigZag.Width +";"+ NL, - "ZigZag.Color=", ColorToStr(ZigZag.Color) +";"+ NL, - - "Donchian.ShowChannel=", BoolToStr(Donchian.ShowChannel) +";"+ NL, - "Donchian.Channel.UpperColor=", ColorToStr(Donchian.Channel.UpperColor) +";"+ NL, - "Donchian.Channel.LowerColor=", ColorToStr(Donchian.Channel.LowerColor) +";"+ NL, - "Donchian.ShowCrossings=", DoubleQuoteStr(Donchian.ShowCrossings) +";"+ NL, - "Donchian.Crossing.Symbol=", DoubleQuoteStr(Donchian.Crossing.Symbol) +";"+ NL, - "Donchian.Crossing.Width=", Donchian.Crossing.Width +";"+ NL, - "Donchian.Crossing.Color=", ColorToStr(Donchian.Crossing.Color) +";"+ NL, - - "ShowChartLegend=", BoolToStr(ShowChartLegend) +";"+ NL, - "MaxBarsBack=", MaxBarsBack +";"+ NL, - - "Signal.onReversal=", BoolToStr(Signal.onReversal) +";"+ NL, - "Signal.onReversal.Types=", DoubleQuoteStr(Signal.onReversal.Types) +";"+ NL, - "Signal.onBreakout=", BoolToStr(Signal.onBreakout) +";"+ NL, - "Signal.onBreakout.Types=", DoubleQuoteStr(Signal.onBreakout.Types) +";"+ NL, - "Signal.Sound.Up=", DoubleQuoteStr(Signal.Sound.Up) +";"+ NL, - "Signal.Sound.Down=", DoubleQuoteStr(Signal.Sound.Down) +";"+ NL, - - "Sound.onChannelWidening=", BoolToStr(Sound.onChannelWidening) +";"+ NL, - "Sound.onNewChannelHigh=", DoubleQuoteStr(Sound.onNewChannelHigh) +";"+ NL, - "Sound.onNewChannelLow=", DoubleQuoteStr(Sound.onNewChannelLow) +";"+ NL, - - "TrackSignalPerformance=", BoolToStr(TrackSignalPerformance) +";"+ NL, - "TrackSignalPerformance.Since=", TimeToStr(TrackSignalPerformance.Since) +";"+ NL, - "TrackSignalPerformance.Symbol=", DoubleQuoteStr(TrackSignalPerformance.Symbol) +";") + return(StringConcatenate("ZigZag.Periods=", ZigZag.Periods +";"+ NL, + "ZigZag.Periods.Step=", ZigZag.Periods.Step +";"+ NL, + "ZigZag.Type=", DoubleQuoteStr(ZigZag.Type) +";"+ NL, + "ZigZag.Semaphores.Symbol=", DoubleQuoteStr(ZigZag.Semaphores.Symbol) +";"+ NL, + "ZigZag.Width=", ZigZag.Width +";"+ NL, + "ZigZag.Color=", ColorToStr(ZigZag.Color) +";"+ NL, + + "Donchian.ShowChannel=", BoolToStr(Donchian.ShowChannel) +";"+ NL, + "Donchian.Channel.UpperColor=", ColorToStr(Donchian.Channel.UpperColor) +";"+ NL, + "Donchian.Channel.LowerColor=", ColorToStr(Donchian.Channel.LowerColor) +";"+ NL, + "Donchian.ShowCrossings=", DoubleQuoteStr(Donchian.ShowCrossings) +";"+ NL, + "Donchian.Crossing.Symbol=", DoubleQuoteStr(Donchian.Crossing.Symbol) +";"+ NL, + "Donchian.Crossing.Width=", Donchian.Crossing.Width +";"+ NL, + "Donchian.Crossing.Color=", ColorToStr(Donchian.Crossing.Color) +";"+ NL, + + "ShowChartLegend=", BoolToStr(ShowChartLegend) +";"+ NL, + "MaxBarsBack=", MaxBarsBack +";"+ NL, + + "Signal.onReversal=", BoolToStr(Signal.onReversal) +";"+ NL, + "Signal.onReversal.Types=", DoubleQuoteStr(Signal.onReversal.Types) +";"+ NL, + "Signal.onBreakout=", BoolToStr(Signal.onBreakout) +";"+ NL, + "Signal.onBreakout.Types=", DoubleQuoteStr(Signal.onBreakout.Types) +";"+ NL, + "Signal.Sound.Up=", DoubleQuoteStr(Signal.Sound.Up) +";"+ NL, + "Signal.Sound.Down=", DoubleQuoteStr(Signal.Sound.Down) +";"+ NL, + + "Sound.onChannelWidening=", BoolToStr(Sound.onChannelWidening) +";"+ NL, + "Sound.onNewChannelHigh=", DoubleQuoteStr(Sound.onNewChannelHigh) +";"+ NL, + "Sound.onNewChannelLow=", DoubleQuoteStr(Sound.onNewChannelLow) +";"+ NL, + + "TrackVirtualProfit=", BoolToStr(TrackVirtualProfit) +";"+ NL, + "TrackVirtualProfit.Since=", TimeToStr(TrackVirtualProfit.Since) +";"+ NL, + "TrackVirtualProfit.Symbol=", DoubleQuoteStr(TrackVirtualProfit.Symbol) +";") ); // suppress compiler warnings From 830f770a5fba0571c8c5f5fe444a6e3ac78cb8e5 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Tue, 24 Mar 2026 23:06:21 +0200 Subject: [PATCH 27/40] update chart templates --- mql40/indicators/ZigZag.mq4 | 10 +++++----- templates4/21 MACD ALMA(8,38).tpl | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index a938d9fca..7f7e5b765 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -192,6 +192,10 @@ double virtualProfit_H[]; // ... double virtualProfit_L[]; // ... double virtualProfit_C[]; // ... +int dbc_reversalOffset; // status vars for the 1st of a double crossing +int dbc_trend; +int dbc_unknownTrend; + string indicatorName = ""; string shortName = ""; string legendLabel = ""; @@ -203,7 +207,7 @@ int zigzagSymbol; int crossingDrawType; int crossingSymbol; -#define MODE_FIRST_CROSSING 1 // draw types of channel crossing +#define MODE_FIRST_CROSSING 1 // draw types of channel crossings #define MODE_ALL_CROSSINGS 2 bool signal.onReversal.sound; @@ -214,10 +218,6 @@ bool signal.onBreakout.sound; bool signal.onBreakout.alert; bool signal.onBreakout.mail; -int dbc_reversalOffset; // status vars for the 1st of a double crossing -int dbc_trend; -int dbc_unknownTrend; - double sema1, sema2, sema3; // last 3 semaphores for detection of ZigZag breakouts double lastLegHigh, lastLegLow; // leg high/low at the previous tick diff --git a/templates4/21 MACD ALMA(8,38).tpl b/templates4/21 MACD ALMA(8,38).tpl index 2b2d2c37c..0d091f485 100644 --- a/templates4/21 MACD ALMA(8,38).tpl +++ b/templates4/21 MACD ALMA(8,38).tpl @@ -143,7 +143,7 @@ FastMA.Method=SMA | LWMA | EMA | SMMA| ALMA* FastMA.Periods=8 SlowMA.Method=SMA | LWMA | EMA | SMMA| ALMA* SlowMA.Periods=38 -VScale.Unit=price | bps-price* | bps-adr +VScale.Unit=price* | bps-price | bps-adr AutoConfiguration=0 From 96856ead580176377bb9d74390055633f975d0c9 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Wed, 25 Mar 2026 12:08:37 +0200 Subject: [PATCH 28/40] refactor RecalculateVirtualProfit() --- mql40/indicators/ZigZag.mq4 | 125 +++++++++++++++++++----------------- 1 file changed, 65 insertions(+), 60 deletions(-) diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index 7f7e5b765..92e24a56e 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -376,11 +376,11 @@ string semTypes[] = {"NULL", "LOW", "HIGH"}; int onInit() { devStartTime = D'2025.05.17 19:45'; // TODO: remove once finished devFirstCrossing = D'2025.05.17 20:07'; - // double crossings: - devFrom = devStartTime + 87 * Period() * MINUTES; // P=8, 2026.03.16 20:47 + + devFrom = devStartTime + 87 * Period() * MINUTES; devTo = devFirstCrossing + 69 * Period() * MINUTES; - if (debugging && Symbol()=="BTCUSD" && Period()==PERIOD_M1 && ZigZag.Periods <= 20) { + if (debugging && Symbol()=="BTCUSD" && Period()==PERIOD_M1) { MaxBarsBack = iBarShift(NULL, NULL, devStartTime); } @@ -739,9 +739,9 @@ int onTick() { isReversalBar = (Abs(trend[bar]) == reversalOffset[bar]); } - // calculate virtual PnL + // update virtual PnL if (TrackVirtualProfit) { - if (!RecalculateVirtualProfit(bar, isReversalBar)) return(last_error); + if (!UpdateVirtualProfit(bar, isReversalBar, virtualProfit_O, virtualProfit_H, virtualProfit_L, virtualProfit_C)) return(last_error); } if (debugging && Ticks == 1) { @@ -764,8 +764,8 @@ int onTick() { else if (crossingDrawType == MODE_FIRST_CROSSING) { // hide all crossings except the 1st if (isReversalBar) { if (upperCross[bar] && lowerCross[bar]) { // special handling for double crossings - bool dbc_isReversalBar = false; // inspect and process the 1st crossing - if (!dbc_unknownTrend) { // whether the first crossing also represents a reversal bar + bool dbc_isReversalBar = false; // process the 1st crossing + if (!dbc_unknownTrend) { // whether the first crossing represents a reversal bar dbc_isReversalBar = (Abs(dbc_trend) == dbc_reversalOffset); } if (!dbc_isReversalBar) { @@ -948,101 +948,106 @@ int onTick() { /** - * Recalculate the virtual PnL for the specified bar. + * Update the virtual PnL for the specified bar. * - * @param int bar - bar offset - * @param bool isReversal - whether the bar is a reversal bar + * @param _In_ int bar - bar offset + * @param _In_ bool isReversal - whether the bar is a reversal bar + * @param _InOut_ double vOpen [] - virtual PnL timeseries (passed by reference to simplify local var names) + * @param _InOut_ double vHigh [] - ... + * @param _InOut_ double vLow [] - ... + * @param _InOut_ double vClose[] - ... * * @return bool - success status */ -bool RecalculateVirtualProfit(int bar, bool isReversal) { +bool UpdateVirtualProfit(int bar, bool isReversal, double &vOpen[], double &vHigh[], double &vLow[], double &vClose[]) { isReversal = (isReversal != 0); - virtualProfit_O[bar] = virtualProfit_C[bar+1]; - virtualProfit_H[bar] = virtualProfit_C[bar+1]; - virtualProfit_L[bar] = virtualProfit_C[bar+1]; - virtualProfit_C[bar] = virtualProfit_C[bar+1]; + vOpen [bar] = vClose[bar+1]; + vHigh [bar] = vClose[bar+1]; + vLow [bar] = vClose[bar+1]; + vClose[bar] = vClose[bar+1]; - bool isPosition = (virtualProfit_C[bar] != EMPTY_VALUE); + bool isPosition = (vClose[bar] != EMPTY_VALUE); // reversal bar, flip the position if (isReversal) { if (!isPosition) { - virtualProfit_O[bar] = 0; - virtualProfit_H[bar] = 0; - virtualProfit_L[bar] = 0; - virtualProfit_C[bar] = 0; + vOpen [bar] = 0; + vHigh [bar] = 0; + vLow [bar] = 0; + vClose[bar] = 0; } if (trend[bar] > 0) { if (isPosition) { - virtualProfit_C[bar] -= (upperCross[bar] - Close[bar+1]); // close short position - virtualProfit_O[bar] = virtualProfit_C[bar] - (Open[bar] - upperCross[bar]); - virtualProfit_C[bar] += (Close[bar] - upperCross[bar]); // open new long position - virtualProfit_H[bar] = MathMax(virtualProfit_O[bar], virtualProfit_C[bar]); - virtualProfit_L[bar] = MathMin(virtualProfit_O[bar], virtualProfit_C[bar]); // the intra-bar path is unknown + vClose[bar] -= (upperCross[bar] - Close[bar+1]); // close short position + vOpen [bar] = vClose[bar] - (Open[bar] - upperCross[bar]); + vClose[bar] += (Close[bar] - upperCross[bar]); // open new long position + vHigh [bar] = MathMax(vOpen[bar], vClose[bar]); + vLow [bar] = MathMin(vOpen[bar], vClose[bar]); // the intra-bar path is unknown } else { - virtualProfit_O[bar] = 0; // open long position - virtualProfit_H[bar] = ( High[bar] - upperCross[bar]); - virtualProfit_L[bar] = ( Low[bar] - upperCross[bar]); - virtualProfit_C[bar] = (Close[bar] - upperCross[bar]); + vOpen [bar] = 0; // open long position + vHigh [bar] = ( High[bar] - upperCross[bar]); + vLow [bar] = ( Low[bar] - upperCross[bar]); + vClose[bar] = (Close[bar] - upperCross[bar]); } } else if (trend[bar] < 0) { if (isPosition) { - virtualProfit_C[bar] += (lowerCross[bar] - Close[bar+1]); // close long position - virtualProfit_O[bar] = virtualProfit_C[bar] + (Open[bar] - lowerCross[bar]); - virtualProfit_C[bar] += (lowerCross[bar] - Close[bar]); // open new short position - virtualProfit_H[bar] = MathMax(virtualProfit_O[bar], virtualProfit_C[bar]); - virtualProfit_L[bar] = MathMin(virtualProfit_O[bar], virtualProfit_C[bar]); // the intra-bar path is unknown + vClose[bar] += (lowerCross[bar] - Close[bar+1]); // close long position + vOpen [bar] = vClose[bar] + (Open[bar] - lowerCross[bar]); + vClose[bar] += (lowerCross[bar] - Close[bar]); // open new short position + vHigh [bar] = MathMax(vOpen[bar], vClose[bar]); + vLow [bar] = MathMin(vOpen[bar], vClose[bar]); // the intra-bar path is unknown } else { - virtualProfit_O[bar] = 0; // open short position - virtualProfit_H[bar] = (lowerCross[bar] - Low[bar]); - virtualProfit_L[bar] = (lowerCross[bar] - High[bar]); - virtualProfit_C[bar] = (lowerCross[bar] - Close[bar]); + vOpen [bar] = 0; // open short position + vHigh [bar] = (lowerCross[bar] - Low[bar]); + vLow [bar] = (lowerCross[bar] - High[bar]); + vClose[bar] = (lowerCross[bar] - Close[bar]); } } else /*trend == 0*/{ + // TODO: update OHLC // double crossing with two semaphores (at the moment when the bar is processed) - if (semaphoreOpen[bar] == Low[bar]) { // crossing order "Low, High" + if (semaphoreOpen[bar] == Low[bar]) { // crossing order "Low, High" if (isPosition) { - virtualProfit_C[bar] += (lowerCross[bar] - Close[bar+1]); // close existing long position + vClose[bar] += (lowerCross[bar] - Close[bar+1]); // close existing long position } - virtualProfit_C[bar] -= (upperCross[bar] - lowerCross[bar]); // open new short position and immediately close it - virtualProfit_C[bar] += (Close[bar] - upperCross[bar]); // open new long position + vClose[bar] -= (upperCross[bar] - lowerCross[bar]); // open new short position and immediately close it + vClose[bar] += (Close[bar] - upperCross[bar]); // open new long position } - else if (semaphoreOpen[bar] == High[bar]) { // crossing order "High, Low" + else if (semaphoreOpen[bar] == High[bar]) { // crossing order "High, Low" if (isPosition) { - virtualProfit_C[bar] -= (upperCross[bar] - Close[bar+1]); // close existing short position + vClose[bar] -= (upperCross[bar] - Close[bar+1]); // close existing short position } - virtualProfit_C[bar] -= (upperCross[bar] - lowerCross[bar]); // open new long position and immediately close it - virtualProfit_C[bar] += (lowerCross[bar] - Close[bar]); // open new short position + vClose[bar] -= (upperCross[bar] - lowerCross[bar]); // open new long position and immediately close it + vClose[bar] += (lowerCross[bar] - Close[bar]); // open new short position } - else return(!catch("RecalculateVirtualProfit(1) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected reversal bar with trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); + else return(!catch("UpdateVirtualProfit(1) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected reversal bar with trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); - virtualProfit_O[bar] = virtualProfit_C[bar+1]; - virtualProfit_H[bar] = MathMax(virtualProfit_O[bar], virtualProfit_C[bar]); - virtualProfit_L[bar] = MathMin(virtualProfit_O[bar], virtualProfit_C[bar]); + vOpen[bar] = vClose[bar+1]; + vHigh[bar] = MathMax(vOpen[bar], vClose[bar]); + vLow [bar] = MathMin(vOpen[bar], vClose[bar]); } } // normal bar, update an existing position else if (isPosition) { if (trend[bar] > 0) { - virtualProfit_C[bar] += (Close[bar] - Close[bar+1]); - virtualProfit_O[bar] = virtualProfit_C[bar] + (Open[bar] - Close[bar]); - virtualProfit_H[bar] = virtualProfit_C[bar] + (High[bar] - Close[bar]); - virtualProfit_L[bar] = virtualProfit_C[bar] + ( Low[bar] - Close[bar]); + vClose[bar] += (Close[bar] - Close[bar+1]); + vOpen [bar] = vClose[bar] + (Open[bar] - Close[bar]); + vHigh [bar] = vClose[bar] + (High[bar] - Close[bar]); + vLow [bar] = vClose[bar] + ( Low[bar] - Close[bar]); } else if (trend[bar] < 0) { - virtualProfit_C[bar] -= (Close[bar] - Close[bar+1]); - virtualProfit_O[bar] = virtualProfit_C[bar] - (Open[bar] - Close[bar]); - virtualProfit_H[bar] = virtualProfit_C[bar] - ( Low[bar] - Close[bar]); - virtualProfit_L[bar] = virtualProfit_C[bar] - (High[bar] - Close[bar]); + vClose[bar] -= (Close[bar] - Close[bar+1]); + vOpen [bar] = vClose[bar] - (Open[bar] - Close[bar]); + vHigh [bar] = vClose[bar] - ( Low[bar] - Close[bar]); + vLow [bar] = vClose[bar] - (High[bar] - Close[bar]); } - else return(!catch("RecalculateVirtualProfit(2) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected non-reversal bar with trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); + else return(!catch("UpdateVirtualProfit(2) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected non-reversal bar with trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); } // normal bar without a position (before first ZigZag reversal) @@ -1121,7 +1126,7 @@ bool RecordVirtualProfit() { case 100000: recorder.priceBase = 1000000; break; case 1000000: recorder.priceBase = 10000000; break; } - debug("RecordVirtualProfit(0.3) Tick="+ Ticks +" updated price base to "+ DoubleToStr(recorder.priceBase, 2)); + debug("RecordVirtualProfit(0.3) Tick="+ Ticks +" bar="+ bar +" updated price base to "+ DoubleToStr(recorder.priceBase, 2)); tmp = recorder.hSet; recorder.hSet = NULL; From d7598045d13562073fa4f680f085e9e84681a344 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Wed, 25 Mar 2026 12:48:40 +0200 Subject: [PATCH 29/40] simplify virtPnL calculation of double-crosses --- mql40/indicators/ZigZag.mq4 | 58 ++++++++++++++++++------------------- 1 file changed, 28 insertions(+), 30 deletions(-) diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index 92e24a56e..d79de64d3 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -769,8 +769,8 @@ int onTick() { dbc_isReversalBar = (Abs(dbc_trend) == dbc_reversalOffset); } if (!dbc_isReversalBar) { - if (semaphoreOpen[bar] < semaphoreClose[bar]) lowerCross[bar] = 0; - else upperCross[bar] = 0; + if (semaphoreOpen[bar] < semaphoreClose[bar]) lowerCross[bar] = 0; // crossing order "Low, High" + else upperCross[bar] = 0; // crossing order "High, Low" } // keep the 2nd crossing (it always represents a reversal bar) } @@ -978,72 +978,70 @@ bool UpdateVirtualProfit(int bar, bool isReversal, double &vOpen[], double &vHig vClose[bar] = 0; } - if (trend[bar] > 0) { + if (trend[bar] > 0) { // upper crossing, switch to long if (isPosition) { - vClose[bar] -= (upperCross[bar] - Close[bar+1]); // close short position - vOpen [bar] = vClose[bar] - (Open[bar] - upperCross[bar]); - vClose[bar] += (Close[bar] - upperCross[bar]); // open new long position + vOpen [bar] = vClose[bar+1]; + vClose[bar] -= (upperCross[bar] - Close[bar+1]); // close short position + vClose[bar] += (Close[bar] - upperCross[bar]); // open new long position vHigh [bar] = MathMax(vOpen[bar], vClose[bar]); - vLow [bar] = MathMin(vOpen[bar], vClose[bar]); // the intra-bar path is unknown + vLow [bar] = MathMin(vOpen[bar], vClose[bar]); // the exact intra-bar path is unknown } else { - vOpen [bar] = 0; // open long position - vHigh [bar] = ( High[bar] - upperCross[bar]); + vHigh [bar] = ( High[bar] - upperCross[bar]); // open long position vLow [bar] = ( Low[bar] - upperCross[bar]); vClose[bar] = (Close[bar] - upperCross[bar]); } } - else if (trend[bar] < 0) { + else if (trend[bar] < 0) { // lower crossing, switch to short if (isPosition) { - vClose[bar] += (lowerCross[bar] - Close[bar+1]); // close long position - vOpen [bar] = vClose[bar] + (Open[bar] - lowerCross[bar]); - vClose[bar] += (lowerCross[bar] - Close[bar]); // open new short position + vOpen [bar] = vClose[bar+1]; + vClose[bar] += (lowerCross[bar] - Close[bar+1]); // close long position + vClose[bar] += (lowerCross[bar] - Close[bar]); // open new short position vHigh [bar] = MathMax(vOpen[bar], vClose[bar]); - vLow [bar] = MathMin(vOpen[bar], vClose[bar]); // the intra-bar path is unknown + vLow [bar] = MathMin(vOpen[bar], vClose[bar]); // the exact intra-bar path is unknown } else { - vOpen [bar] = 0; // open short position - vHigh [bar] = (lowerCross[bar] - Low[bar]); + vHigh [bar] = (lowerCross[bar] - Low[bar]); // open short position vLow [bar] = (lowerCross[bar] - High[bar]); vClose[bar] = (lowerCross[bar] - Close[bar]); } } else /*trend == 0*/{ - // TODO: update OHLC // double crossing with two semaphores (at the moment when the bar is processed) - if (semaphoreOpen[bar] == Low[bar]) { // crossing order "Low, High" + if (semaphoreOpen[bar] < semaphoreClose[bar]) { // crossing order "Low, High" if (isPosition) { - vClose[bar] += (lowerCross[bar] - Close[bar+1]); // close existing long position + vOpen [bar] = vClose[bar+1]; + vClose[bar] += (lowerCross[bar] - Close[bar+1]); // close existing long position } - vClose[bar] -= (upperCross[bar] - lowerCross[bar]); // open new short position and immediately close it - vClose[bar] += (Close[bar] - upperCross[bar]); // open new long position + vClose[bar] -= (upperCross[bar] - lowerCross[bar]); // open new short position and immediately close it + vClose[bar] += (Close[bar] - upperCross[bar]); // open new long position } - else if (semaphoreOpen[bar] == High[bar]) { // crossing order "High, Low" + else if (semaphoreOpen[bar] > semaphoreClose[bar]) { // crossing order "High, Low" if (isPosition) { - vClose[bar] -= (upperCross[bar] - Close[bar+1]); // close existing short position + vOpen [bar] = vClose[bar+1]; + vClose[bar] -= (upperCross[bar] - Close[bar+1]); // close existing short position } - vClose[bar] -= (upperCross[bar] - lowerCross[bar]); // open new long position and immediately close it - vClose[bar] += (lowerCross[bar] - Close[bar]); // open new short position + vClose[bar] -= (upperCross[bar] - lowerCross[bar]); // open new long position and immediately close it + vClose[bar] += (lowerCross[bar] - Close[bar]); // open new short position } else return(!catch("UpdateVirtualProfit(1) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected reversal bar with trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); - vOpen[bar] = vClose[bar+1]; - vHigh[bar] = MathMax(vOpen[bar], vClose[bar]); + vHigh[bar] = MathMax(vOpen[bar], vClose[bar]); // the exact intra-bar path is unknown vLow [bar] = MathMin(vOpen[bar], vClose[bar]); } } - // normal bar, update an existing position + // normal bar without crossing, update an existing position else if (isPosition) { if (trend[bar] > 0) { + vOpen [bar] = vClose[bar+1]; vClose[bar] += (Close[bar] - Close[bar+1]); - vOpen [bar] = vClose[bar] + (Open[bar] - Close[bar]); vHigh [bar] = vClose[bar] + (High[bar] - Close[bar]); vLow [bar] = vClose[bar] + ( Low[bar] - Close[bar]); } else if (trend[bar] < 0) { + vOpen [bar] = vClose[bar+1]; vClose[bar] -= (Close[bar] - Close[bar+1]); - vOpen [bar] = vClose[bar] - (Open[bar] - Close[bar]); vHigh [bar] = vClose[bar] - ( Low[bar] - Close[bar]); vLow [bar] = vClose[bar] - (High[bar] - Close[bar]); } From 350127bb37fb6c7ef79a0cfee2646af675ff56f5 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Wed, 25 Mar 2026 13:59:00 +0200 Subject: [PATCH 30/40] fix virtPnL calculation bugs --- mql40/indicators/ZigZag.mq4 | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index d79de64d3..95bb4ec57 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -967,7 +967,7 @@ bool UpdateVirtualProfit(int bar, bool isReversal, double &vOpen[], double &vHig vLow [bar] = vClose[bar+1]; vClose[bar] = vClose[bar+1]; - bool isPosition = (vClose[bar] != EMPTY_VALUE); + bool isPosition = (vClose[bar] != EMPTY_VALUE), isLegUp; // reversal bar, flip the position if (isReversal) { @@ -1024,7 +1024,7 @@ bool UpdateVirtualProfit(int bar, bool isReversal, double &vOpen[], double &vHig vClose[bar] -= (upperCross[bar] - lowerCross[bar]); // open new long position and immediately close it vClose[bar] += (lowerCross[bar] - Close[bar]); // open new short position } - else return(!catch("UpdateVirtualProfit(1) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected reversal bar with trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); + else return(!catch("UpdateVirtualProfit(1) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected reversal bar: trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); vHigh[bar] = MathMax(vOpen[bar], vClose[bar]); // the exact intra-bar path is unknown vLow [bar] = MathMin(vOpen[bar], vClose[bar]); @@ -1033,20 +1033,28 @@ bool UpdateVirtualProfit(int bar, bool isReversal, double &vOpen[], double &vHig // normal bar without crossing, update an existing position else if (isPosition) { - if (trend[bar] > 0) { - vOpen [bar] = vClose[bar+1]; + if (!trend[bar]) { + if (unknownTrend[bar] <= 0) return(!catch("UpdateVirtualProfit(2) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected non-reversal bar: trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); + int semBar = bar + unknownTrend[bar]; + if (!semaphoreClose[semBar]) return(!catch("UpdateVirtualProfit(3) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected non-reversal bar without previous semaphore: trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); + isLegUp = (semaphoreClose[semBar] > High[semBar]-HalfPoint); + } + else { + isLegUp = (trend[bar] > 0); + } + + vOpen[bar] = vClose[bar+1]; + if (isLegUp) { vClose[bar] += (Close[bar] - Close[bar+1]); vHigh [bar] = vClose[bar] + (High[bar] - Close[bar]); vLow [bar] = vClose[bar] + ( Low[bar] - Close[bar]); } - else if (trend[bar] < 0) { - vOpen [bar] = vClose[bar+1]; + else /*isLegDown*/ { vClose[bar] -= (Close[bar] - Close[bar+1]); vHigh [bar] = vClose[bar] - ( Low[bar] - Close[bar]); vLow [bar] = vClose[bar] - (High[bar] - Close[bar]); } - else return(!catch("UpdateVirtualProfit(2) bar="+ bar +" "+ TimeToStr(Time[bar]) +" unexpected non-reversal bar with trend=0 unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); - } + } // normal bar without a position (before first ZigZag reversal) //else {} From 93270ffd63eb4aeff9aceddff8ec592f5d7cf37f Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Wed, 25 Mar 2026 21:17:30 +0200 Subject: [PATCH 31/40] update usage of icZigZag() --- mql40/experts/ZigZag EA.mq4 | 2 +- mql40/indicators/ZigZag.mq4 | 9 ++------- 2 files changed, 3 insertions(+), 8 deletions(-) diff --git a/mql40/experts/ZigZag EA.mq4 b/mql40/experts/ZigZag EA.mq4 index a34e384d6..8979a8257 100644 --- a/mql40/experts/ZigZag EA.mq4 +++ b/mql40/experts/ZigZag EA.mq4 @@ -429,7 +429,7 @@ bool IsZigZagSignal(double &signal[]) { signal[SIG_OP ] = 0; if (!GetZigZagData(0, trend, reversalOffset, reversalPrice)) return(!logError("IsZigZagSignal(1)->GetZigZagData(0) => FALSE", ERR_RUNTIME_ERROR)); - int absTrend = MathAbs(trend); + int absTrend = Abs(trend); bool isReversal = false; if (absTrend == reversalOffset) isReversal = true; // regular reversal else if (absTrend==1 && !reversalOffset) isReversal = true; // reversal after double crossing diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index 95bb4ec57..2103c99f0 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -55,17 +55,12 @@ * • TrackVirtualProfit.Since: Start time to track virtual PnL from (default: MaxBarsBack). * • TrackVirtualProfit.Symbol: Custom symbol to use for virtual PnL tracking (default: auto-generated). * - * • AutoConfiguration: If enabled all input parameters may use predefined defaults from the configuration. + * • AutoConfiguration: If enabled all input parameters may use predefined defaults from the configuration. * * * TODO: - * - convert TrackSignalPerformance.Since to string + * - update logic in usage locations of icZigZag() * - remove debug code - * - once finished, update logic in usage locations of icZigZag() - * - * - fix triple-crossing at GBPJPY,M5 2023.12.18 00:00, ZigZag(20) - * - keep bar status in IsUpperCrossLast() - * - document usage of iCustom() */ #include int __InitFlags[]; From c647452ee606c34f909051b55ac14a9e5e77e1b4 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Thu, 26 Mar 2026 12:20:55 +0200 Subject: [PATCH 32/40] update chart templates --- templates4/24 Channel Trend + CCI.tpl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/templates4/24 Channel Trend + CCI.tpl b/templates4/24 Channel Trend + CCI.tpl index 1b36a9b41..ac8f852c0 100644 --- a/templates4/24 Channel Trend + CCI.tpl +++ b/templates4/24 Channel Trend + CCI.tpl @@ -208,7 +208,7 @@ window_num=2 Periods=14 AppliedPrice=Open | High | Low | Close | Median | Typical* | Weighted -Signal.onTrendChange=1 +Signal.onTrendChange=0 Signal.onTrendChange.Types=sound* | alert* | mail From 7adb758d9f6a589a58bf75916e7f07343131c2e7 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Thu, 26 Mar 2026 12:23:27 +0200 Subject: [PATCH 33/40] update buffer ZigZag.MODE_COMBINED_TREND and fix usage of icZigZag() --- mql40/experts/ZigZag EA.mq4 | 119 ++++++++++++------ .../include/rsf/functions/iCustom/ZigZag.mqh | 17 ++- mql40/indicators/ZigZag.mq4 | 39 +++--- 3 files changed, 120 insertions(+), 55 deletions(-) diff --git a/mql40/experts/ZigZag EA.mq4 b/mql40/experts/ZigZag EA.mq4 index 8979a8257..4f88d5de5 100644 --- a/mql40/experts/ZigZag EA.mq4 +++ b/mql40/experts/ZigZag EA.mq4 @@ -292,7 +292,7 @@ bool onCommand(string cmd, string params, int keys) { case STATUS_WAITING: case STATUS_STOPPED: double signal[] = {0,0,0}; - signal[SIG_OP] = ifInt(GetZigZagTrend(0) > 0, SIG_OP_LONG, SIG_OP_SHORT); + signal[SIG_OP] = ifInt(GetZigZagDirection(0) > 0, SIG_OP_LONG, SIG_OP_SHORT); log("onCommand(1) "+ instance.name +" "+ DoubleQuoteStr(fullCmd), NO_ERROR, LOG_INFO); return(StartTrading(signal)); } @@ -405,7 +405,8 @@ bool GetDonchianChannel(int bar, double &upperBand, double &lowerBand) { /** - * Whether a new ZigZag reversal occurred. + * Whether a new ZigZag reversal occurred. Returns the same result for the same tick. + * Multiple reversals can occur in a single bar. The function returns a new signal for every new reversal (if any). * * @param _Out_ double signal[] - array receiving signal details * @@ -414,9 +415,8 @@ bool GetDonchianChannel(int bar, double &upperBand, double &lowerBand) { bool IsZigZagSignal(double &signal[]) { if (last_error != NULL) return(false); - static int lastTick, lastSigType, lastSigOp, lastSigBar, lastSigBarOp; - static double lastSigPrice, reversalPrice; - int trend, reversalOffset; + static int lastTick, lastSigType, lastSigOp; + static double lastSigPrice; if (Ticks == lastTick) { signal[SIG_TYPE ] = lastSigType; @@ -428,26 +428,27 @@ bool IsZigZagSignal(double &signal[]) { signal[SIG_PRICE] = 0; signal[SIG_OP ] = 0; - if (!GetZigZagData(0, trend, reversalOffset, reversalPrice)) return(!logError("IsZigZagSignal(1)->GetZigZagData(0) => FALSE", ERR_RUNTIME_ERROR)); - int absTrend = Abs(trend); - bool isReversal = false; - if (absTrend == reversalOffset) isReversal = true; // regular reversal - else if (absTrend==1 && !reversalOffset) isReversal = true; // reversal after double crossing + static int lastBarTime, lastBarReversalType; + static bool lastBarIsReversal; - if (isReversal) { - if (trend > 0) int sigOp = SIG_OP_CLOSE_SHORT|SIG_OP_LONG; - else sigOp = SIG_OP_CLOSE_LONG|SIG_OP_SHORT; + int reversalType; + double reversalPrice; + bool isReversal = IsZigZagReversalBar(0, reversalType, reversalPrice); - if (Time[0]!=lastSigBar || sigOp!=lastSigBarOp) { + if (isReversal) { + bool isNewReversal = (Time[0] != lastBarTime || !lastBarIsReversal || reversalType != lastBarReversalType); + if (isNewReversal) { signal[SIG_TYPE ] = SIG_TYPE_ZIGZAG; signal[SIG_PRICE] = reversalPrice; - signal[SIG_OP ] = sigOp; - - if (IsLogNotice()) logNotice("IsZigZagSignal(2) "+ instance.name +" "+ ifString(sigOp & SIG_OP_LONG, "long", "short") +" reversal at "+ NumberToStr(reversalPrice, PriceFormat) +" (market: "+ NumberToStr(_Bid, PriceFormat) +"/"+ NumberToStr(_Ask, PriceFormat) +")"); - lastSigBar = Time[0]; - lastSigBarOp = sigOp; + if (reversalType == MODE_UPPER) signal[SIG_OP] = SIG_OP_CLOSE_SHORT|SIG_OP_LONG; + else signal[SIG_OP] = SIG_OP_CLOSE_LONG|SIG_OP_SHORT; + if (IsLogNotice()) logNotice("IsZigZagSignal(1) "+ instance.name +" "+ ifString(reversalType == MODE_UPPER, "long", "short") +" reversal at "+ NumberToStr(reversalPrice, PriceFormat) +" (market: "+ NumberToStr(_Bid, PriceFormat) +"/"+ NumberToStr(_Ask, PriceFormat) +")"); } } + lastBarTime = Time[0]; + lastBarIsReversal = isReversal; + lastBarReversalType = reversalType; + lastTick = Ticks; lastSigType = signal[SIG_TYPE ]; lastSigPrice = signal[SIG_PRICE]; @@ -458,40 +459,80 @@ bool IsZigZagSignal(double &signal[]) { /** - * Get ZigZag buffer values at the specified bar offset. The returned values correspond to the documented indicator buffers. + * Whether the specified bar is a ZigZag reversal bar. Not whether the current tick triggered a reversal. + * Multiple reversals can occur in a single bar. The function returns the data of the last reversal (if any). * - * @param _In_ int bar - bar offset - * @param _Out_ int trend - MODE_TREND: combined buffers MODE_KNOWN_TREND & MODE_UNKNOWN_TREND - * @param _Out_ int reversalOffset - MODE_REVERSAL_OFFSET: bar offset of most recent ZigZag reversal to the preceeding ZigZag semaphore - * @param _Out_ double reversalPrice - MODE_UPPER_CROSS|MODE_LOWER_CROSS: reversal price if the bar denotes a ZigZag reversal; 0 otherwise + * @param _In_ int bar - bar offset + * @param _Out_ int reversalType - type of the reversal: MODE_UPPER | MODE_LOWER + * @param _Out_ double reversalPrice - reversal price * - * @return bool - success status + * @return bool */ -bool GetZigZagData(int bar, int &trend, int &reversalOffset, double &reversalPrice) { +bool IsZigZagReversalBar(int bar, int &reversalType, double &reversalPrice) { // TODO: 56% of the EA's total runtime is spent in this function + reversalType = NULL; + reversalPrice = NULL; + + int combinedTrend = icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_COMBINED_TREND, bar); // 85% of the local time here - // TODO: 56% of the EA's total runtime is spent in this function + int trend = combinedTrend & 0xFFFF; // extract LOWORD and manually sign-extend, + if (trend & 0x8000 != 0) trend |= 0xFFFF0000; // i.e. convert int to signed short - trend = MathRound(icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_COMBINED_TREND, bar)); // 88% of the local time - reversalOffset = MathRound(icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_REVERSAL_OFFSET, bar)); // 6% of the local time + int unknownTrend = (combinedTrend >> 16) & 0xFFFF; // extract HIWORD and manually sign-extend, + if (unknownTrend & 0x8000 != 0) unknownTrend |= 0xFFFF0000; // i.e. convert int to signed short + if (unknownTrend < 0) return(!catch("IsZigZagReversalBar(1) unexpected bar="+ bar +" "+ TimeToStr(Time[bar]) +" trend=0 unknownTrend="+ unknownTrend, ERR_ILLEGAL_STATE)); - if (trend > 0) reversalPrice = icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_UPPER_CROSS, bar); // 6% of the local time - else reversalPrice = icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_LOWER_CROSS, bar); - return(!last_error && trend); + if (!unknownTrend) { + int reversalOffset = icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_REVERSAL_OFFSET, bar); + + if (Abs(trend) == reversalOffset) { + int semBar = bar + reversalOffset; // last semaphore bar before the reversal + double semaphoreClose = icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_SEMAPHORE_CLOSE, semBar); + if (!semaphoreClose) return(!catch("IsZigZagReversalBar(2) unexpected bar="+ semBar +" "+ TimeToStr(Time[semBar]) +" trend=0 unknownTrend="+ unknownTrend +" semaphoreClose=0", ERR_ILLEGAL_STATE)); + + if (semaphoreClose > High[bar]-HalfPoint) { + reversalType = MODE_LOWER; + reversalPrice = icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_LOWER_CROSS, bar); + } + else { + reversalType = MODE_UPPER; + reversalPrice = icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_UPPER_CROSS, bar); + } + return(true); + } + } + return(false); } /** - * Get the length of the ZigZag trend at the specified bar offset. + * Get the direction of the ZigZag leg at the specified bar. * * @param int bar - bar offset * - * @return int - trend length or NULL (0) in case of errors + * @return int - a positive value for an upward leg and a negative value for a downward leg, + * or NULL (0) in case of errors */ -int GetZigZagTrend(int bar) { - int trend, iNull; - double dNull; - if (!GetZigZagData(bar, trend, iNull, dNull)) return(NULL); - return(trend % 100000); +int GetZigZagDirection(int bar) { + int combinedTrend = icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_COMBINED_TREND, bar); + + int trend = combinedTrend & 0xFFFF; // extract LOWORD and manually sign-extend, + if (trend & 0x8000 != 0) trend |= 0xFFFF0000; // i.e. convert int to signed short + + if (!trend) { + int unknownTrend = (combinedTrend >> 16) & 0xFFFF; // extract HIWORD and manually sign-extend, + if (unknownTrend & 0x8000 != 0) unknownTrend |= 0xFFFF0000; // i.e. convert int to signed short + if (unknownTrend < 0) return(!catch("GetZigZagDirection(1) unexpected bar="+ bar +" "+ TimeToStr(Time[bar]) +" trend=0 unknownTrend="+ unknownTrend, ERR_ILLEGAL_STATE)); + + int semBar = bar + unknownTrend; + double semaphoreClose = icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_SEMAPHORE_CLOSE, semBar); + if (!semaphoreClose) return(!catch("GetZigZagDirection(2) unexpected bar="+ semBar +" "+ TimeToStr(Time[semBar]) +" trend=0 unknownTrend="+ unknownTrend +" semaphoreClose=0", ERR_ILLEGAL_STATE)); + + if (semaphoreClose > High[semBar]-HalfPoint) { + return(1); + } + return(-1); + } + return(trend); } diff --git a/mql40/include/rsf/functions/iCustom/ZigZag.mqh b/mql40/include/rsf/functions/iCustom/ZigZag.mqh index da614b57f..b90c70c78 100644 --- a/mql40/include/rsf/functions/iCustom/ZigZag.mqh +++ b/mql40/include/rsf/functions/iCustom/ZigZag.mqh @@ -6,7 +6,19 @@ #define ZigZag.MODE_UPPER_CROSS 4 // upper channel band crossings: positive or 0 #define ZigZag.MODE_LOWER_CROSS 5 // lower channel band crossings: positive or 0 #define ZigZag.MODE_REVERSAL_OFFSET 6 // int: offset of the ZigZag reversal to the leg's start semaphore: non-negative or -1 -#define ZigZag.MODE_COMBINED_TREND 7 // int: combined internal buffers MODE_TREND & MODE_UNKNOWN_TREND: positive/negative or 0 +#define ZigZag.MODE_COMBINED_TREND 7 // int: combined internal buffers MODE_TREND and MODE_UNKNOWN_TREND: positive/negative or 0 + +/** + * Notes + * ----- + * Since MQL4.0 limits the number of available indicator buffers to 8, MODE_TREND and MODE_UNKNOWN_TREND are combined into + * a single buffer ZigZag.MODE_COMBIND_TREND (7). To retrieve the original values with iCustom(), input "TrendBufferAsDecimal" + * must be set to FALSE. + * + * Each value from buffer ZigZag.MODE_COMBIND_TREND must be cast to an integer. The LOWORD of this integer holds the MODE_TREND + * value, and the HIWORD of the integer holds the MODE_UNKNOWN_TREND value. For final results, both values must be converted + * to signed short (sign extension). + */ /** @@ -65,6 +77,9 @@ double icZigZag(int timeframe, int periods, int iBuffer, int iBar) { 0, // datetime TrackVirtualProfit.Since "", // string TrackVirtualProfit.Symbol + "separator", // string ___f_______________________ + false, // bool TrendBufferAsDecimal + "separator", // string ___________________________ false, // bool AutoConfiguration lpSuperContext, // int __lpSuperContext diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index 2103c99f0..abe5091f8 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -55,12 +55,19 @@ * • TrackVirtualProfit.Since: Start time to track virtual PnL from (default: MaxBarsBack). * • TrackVirtualProfit.Symbol: Custom symbol to use for virtual PnL tracking (default: auto-generated). * + * • TrendBufferAsDecimal: Whether buffer ZigZag.MODE_COMBINED_TREND (7) contains decimal or binary data. * • AutoConfiguration: If enabled all input parameters may use predefined defaults from the configuration. * * - * TODO: - * - update logic in usage locations of icZigZag() - * - remove debug code + * Usage with iCustom() + * -------------------- + * Since MQL4.0 limits the number of available indicator buffers to 8, MODE_TREND and MODE_UNKNOWN_TREND are combined into + * a single buffer ZigZag.MODE_COMBIND_TREND (7). To retrieve the original values with iCustom(), input "TrendBufferAsDecimal" + * must be set to FALSE. + * + * Each value from buffer ZigZag.MODE_COMBIND_TREND must be cast to an integer. The LOWORD of this integer holds the MODE_TREND + * value, and the HIWORD of the integer holds the MODE_UNKNOWN_TREND value. For final results, both values must be converted + * to signed short (sign extension). */ #include int __InitFlags[]; @@ -108,6 +115,9 @@ extern bool TrackVirtualProfit = false; extern datetime TrackVirtualProfit.Since = 0; // start time to track virtual PnL from extern string TrackVirtualProfit.Symbol = "(default)"; // custom symbol to use for virtual PnL tracking +extern string ___f__________________________ = ""; +extern bool TrendBufferAsDecimal = true; // decimal or binary (see notes in file header) + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// bool TrackZigZagBalance = false; // whether to track ZigZag balances @@ -138,7 +148,7 @@ bool ProjectNextBalance = false; // whether to project zero-balance #define MODE_UPPER_CROSS ZigZag.MODE_UPPER_CROSS // 4: upper channel band crossings: positive or 0 #define MODE_LOWER_CROSS ZigZag.MODE_LOWER_CROSS // 5: lower channel band crossings: positive or 0 #define MODE_REVERSAL_OFFSET ZigZag.MODE_REVERSAL_OFFSET // 6: int: offset of the ZigZag reversal to the leg's start semaphore: non-negative or -1 -#define MODE_COMBINED_TREND ZigZag.MODE_COMBINED_TREND // 7: int: combined buffers MODE_TREND & MODE_UNKNOWN_TREND: positive/negative or 0 +#define MODE_COMBINED_TREND ZigZag.MODE_COMBINED_TREND // 7: int: combined buffers MODE_TREND and MODE_UNKNOWN_TREND (see notes in file header) #define MODE_UPPER_CROSS_HIGH 8 // 8: bar High of an upper channel band crossing: positive or 0 #define MODE_LOWER_CROSS_LOW 9 // 9: bar Low of a lower channel band crossing: positive or 0 #define MODE_TREND 10 // 10: int: length of a ZigZag leg: positive/negative or 0 @@ -181,7 +191,7 @@ double semaphoreClose []; // final semaphore, double reversalOffset []; // int: offset of the ZigZag reversal to the leg's start semaphore (): non-negative or -1 int trend []; // int: length of a ZigZag leg: positive/negative or 0 int unknownTrend []; // int: number of bars after a leg's end semaphore: non-negative or -1 -double combinedTrend []; // int: combined buffers MODE_TREND & MODE_UNKNOWN_TREND: positive/negative or 0 +double combinedTrend []; // int: combined buffers MODE_TREND and MODE_UNKNOWN_TREND (see notes in file header) double virtualProfit_O[]; // virtual signal PnL in price units: positive/negative or EMPTY_VALUE double virtualProfit_H[]; // ... double virtualProfit_L[]; // ... @@ -672,8 +682,6 @@ int onTick() { virtualProfit_C[startBar] = EMPTY_VALUE; } - datetime startRecalc = GetTickCount(); - for (int bar=startBar; bar >= 0; bar--) { // recalculate Donchian channel if (bar > 0) { @@ -776,14 +784,11 @@ int onTick() { } } - // set combinedTrend[] - combinedTrend[bar] = Sign(trend[bar]) * unknownTrend[bar] * 100000 + trend[bar]; + // set combinedTrend[], decimal for "Data Window", HIWORD + LOWORD for iCustom() + if (TrendBufferAsDecimal) combinedTrend[bar] = Sign(trend[bar]) * unknownTrend[bar] * 100000 + trend[bar]; + else combinedTrend[bar] = (unknownTrend[bar] << 16) | trend[bar]; } - //if (Ticks == 1) { - // debug("onTick(0.4) Tick="+ Ticks +" foreach(bars="+ (startBar+1) +") => "+ DoubleToStr((GetTickCount()-startRecalc)/1000.0, 3) +" sec"); - //} - if (__isChart && !__isSuperContext) { if (ShowChartLegend) UpdateChartLegend(); @@ -1511,7 +1516,9 @@ void SetTrend(int fromBar, int fromValue, int toBar, bool resetReversals) { for (int i=fromBar; i >= toBar; i--) { trend [i] = value; unknownTrend [i] = -1; - combinedTrend[i] = trend[i]; + + if (TrendBufferAsDecimal) combinedTrend[i] = trend[i]; // hide unknownTrend=-1 + else combinedTrend[i] = (unknownTrend[i] << 16) | trend[i]; // iCustom() if (resetReversals) reversalOffset[i] = -1; @@ -1907,7 +1914,9 @@ string InputsToStr() { "TrackVirtualProfit=", BoolToStr(TrackVirtualProfit) +";"+ NL, "TrackVirtualProfit.Since=", TimeToStr(TrackVirtualProfit.Since) +";"+ NL, - "TrackVirtualProfit.Symbol=", DoubleQuoteStr(TrackVirtualProfit.Symbol) +";") + "TrackVirtualProfit.Symbol=", DoubleQuoteStr(TrackVirtualProfit.Symbol) +";"+ NL, + + "TrendBufferAsDecimal=", BoolToStr(TrendBufferAsDecimal) +";") ); // suppress compiler warnings From 116d57085b83bcf5f1d840b6a74232fbb84b48b3 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Thu, 26 Mar 2026 12:26:08 +0200 Subject: [PATCH 34/40] remove debug code --- mql40/indicators/ZigZag.mq4 | 85 ++++++------------------------------- 1 file changed, 13 insertions(+), 72 deletions(-) diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index abe5091f8..7fd216855 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -368,10 +368,6 @@ datetime recorder.startTime; // • Bar 0 (zero), the current bar ??? // -bool debugging = false; -datetime devStartTime, devFirstCrossing, devFrom, devTo; -string semTypes[] = {"NULL", "LOW", "HIGH"}; - /** * Initialization @@ -379,16 +375,6 @@ string semTypes[] = {"NULL", "LOW", "HIGH"}; * @return int - error status */ int onInit() { - devStartTime = D'2025.05.17 19:45'; // TODO: remove once finished - devFirstCrossing = D'2025.05.17 20:07'; - - devFrom = devStartTime + 87 * Period() * MINUTES; - devTo = devFirstCrossing + 69 * Period() * MINUTES; - - if (debugging && Symbol()=="BTCUSD" && Period()==PERIOD_M1) { - MaxBarsBack = iBarShift(NULL, NULL, devStartTime); - } - string indicator = WindowExpertName(); // validate inputs @@ -747,28 +733,16 @@ int onTick() { if (!UpdateVirtualProfit(bar, isReversalBar, virtualProfit_O, virtualProfit_H, virtualProfit_L, virtualProfit_C)) return(last_error); } - if (debugging && Ticks == 1) { - if (Time[bar] >= devFrom && Time[bar] <= devTo) { - debug("onTick(0.1) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat) +" reversalOffset="+ _int(reversalOffset[bar]) +" trend="+ trend[bar] +" unknownTrend="+ unknownTrend[bar]); - if (isReversalBar) { - debug("onTick(0.2) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" isReversalBar=1"); - } - if (semaphoreClose[bar] != NULL) { - debug("onTick(0.3) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" isSemaphoreBar=1"); - } - } - } - // hide non-configured crossings - if (!crossingDrawType) { // hide all crossings + if (!crossingDrawType) { // hide all crossings upperCross[bar] = 0; lowerCross[bar] = 0; } - else if (crossingDrawType == MODE_FIRST_CROSSING) { // hide all crossings except the 1st + else if (crossingDrawType == MODE_FIRST_CROSSING) { // hide all crossings except the 1st if (isReversalBar) { - if (upperCross[bar] && lowerCross[bar]) { // special handling for double crossings - bool dbc_isReversalBar = false; // process the 1st crossing - if (!dbc_unknownTrend) { // whether the first crossing represents a reversal bar + if (upperCross[bar] && lowerCross[bar]) { // special handling for double crossings + bool dbc_isReversalBar = false; // process the 1st crossing + if (!dbc_unknownTrend) { // whether the first crossing represents a reversal bar dbc_isReversalBar = (Abs(dbc_trend) == dbc_reversalOffset); } if (!dbc_isReversalBar) { @@ -860,8 +834,8 @@ int onTick() { lastLowerBand = lowerBand[0]; } - // --- old ------------------------------------------------------------------------------------------------------------ - // track ZigZag balance + // --- old: track ZigZag balance -------------------------------------------------------------------------------------- + // if (TrackZigZagBalance) { int currSem, prevBar, prevSem, size; int currBar = FindSemaphore(0, currSem); if (currBar < 0) return(last_error); @@ -1244,11 +1218,6 @@ int FindSemaphore(int bar, int &resultType, int skipType = NULL) { if (skipType != NULL) { if (skipType!=MODE_HIGH && skipType!=MODE_LOW) return(_EMPTY(catch("FindSemaphore(2) invalid parameter skipType: "+ skipType, ERR_INVALID_PARAMETER))); } - static int doDebug = 0; - if (debugging && (Ticks==1 && Time[bar] >= devFirstCrossing && Time[bar] <= devFirstCrossing + 2*MINUTES) || doDebug) { - debug("FindSemaphore(0.1) start @ "+ TimeToStr(Time[bar]) +" skipType="+ semTypes[skipType]); - doDebug++; - } // if no skipping (no skip type or not a semaphore bar), then return the next semaphore if (!skipType || !semaphoreClose[bar]) { @@ -1262,7 +1231,6 @@ int FindSemaphore(int bar, int &resultType, int skipType = NULL) { bar += Abs(trend[bar]); } if (!semaphoreClose[bar]) { - if (doDebug > 0) { debug("FindSemaphore(0.2) return "+ EMPTY); doDebug--; } return(EMPTY); } if (!lowerCrossLow [bar]) resultType = MODE_HIGH; @@ -1271,28 +1239,22 @@ int FindSemaphore(int bar, int &resultType, int skipType = NULL) { else if (semaphoreOpen [bar] > semaphoreClose[bar]+HalfPoint) resultType = MODE_LOW; else if (semaphoreClose[bar] > upperCrossHigh[bar]-HalfPoint) resultType = MODE_HIGH; // from here it holds: open == close else resultType = MODE_LOW; // ... - if (doDebug > 0) { debug("FindSemaphore(0.3) return "+ bar +" = "+ TimeToStr(Time[bar])); doDebug--; } return(bar); } - // if skipping: some semaphore type to skip on the start bar (not used by ZigZag calculation) if (semaphoreOpen[bar] == semaphoreClose[bar]) { bool isHigh = (semaphoreClose[bar] == upperCrossHigh[bar]); if (skipType == MODE_HIGH) { if (isHigh) { - int iTmp = FindSemaphore(bar+1, resultType); - if (doDebug > 0) { debug("FindSemaphore(0.4) return "+ iTmp +" = "+ TimeToStr(Time[iTmp])); doDebug--; } - return(iTmp); + return(FindSemaphore(bar+1, resultType)); } resultType = MODE_LOW; } else /*skipType == MODE_LOW*/ { if (!isHigh) { - iTmp = FindSemaphore(bar+1, resultType); - if (doDebug > 0) { debug("FindSemaphore(0.5) return "+ iTmp +" = "+ TimeToStr(Time[iTmp])); doDebug--; } - return(iTmp); + return(FindSemaphore(bar+1, resultType)); } resultType = MODE_HIGH; } @@ -1302,23 +1264,18 @@ int FindSemaphore(int bar, int &resultType, int skipType = NULL) { if (skipType == MODE_HIGH) { if (high2Low) { - iTmp = FindSemaphore(bar+1, resultType); - if (doDebug > 0) { debug("FindSemaphore(0.6) return "+ iTmp +" = "+ TimeToStr(Time[iTmp])); doDebug--; } - return(iTmp); + return(FindSemaphore(bar+1, resultType)); } resultType = MODE_LOW; } else /*skipType == MODE_LOW*/ { if (!high2Low) { - iTmp = FindSemaphore(bar+1, resultType); - if (doDebug > 0) { debug("FindSemaphore(0.7) return "+ iTmp +" = "+ TimeToStr(Time[iTmp])); doDebug--; } - return(iTmp); + return(FindSemaphore(bar+1, resultType)); } resultType = MODE_HIGH; } } - if (doDebug > 0) { debug("FindSemaphore(0.8) return "+ bar +" = "+ TimeToStr(Time[bar])); doDebug--; } return(bar); } @@ -1332,16 +1289,8 @@ int FindSemaphore(int bar, int &resultType, int skipType = NULL) { * @return bool - success status */ bool ProcessUpperCross(int bar) { - if (debugging && Ticks==1 && Time[bar] >= devFrom && Time[bar] <= devTo) { - debug("ProcessUpperCross(0.1) "+ TimeToStr(Time[bar])); - } int lastSemType, lastSemBar = FindSemaphore(bar, lastSemType); // find the last semaphore - if (debugging && Ticks==1 && Time[bar] >= devFrom && Time[bar] <= devTo) { - if (lastSemBar < 0) debug("ProcessUpperCross(0.2) found no semaphore"); - else debug("ProcessUpperCross(0.3) found semaphore at bar["+ lastSemBar +"] "+ TimeToStr(Time[lastSemBar]) +" type="+ semTypes[lastSemType] +" trend="+ trend[lastSemBar]); - } - // an upper cross without a previous semaphore (near MaxBarsBack) if (lastSemBar < 0) { if (!last_error) { @@ -1420,16 +1369,8 @@ bool ProcessUpperCross(int bar) { * @return bool - success status */ bool ProcessLowerCross(int bar) { - if (debugging && Ticks==1 && Time[bar] >= devFrom && Time[bar] <= devTo) { - debug("ProcessLowerCross(0.1) "+ TimeToStr(Time[bar])); - } int lastSemType, lastSemBar = FindSemaphore(bar, lastSemType); // find the last semaphore - if (debugging && Ticks==1 && Time[bar] >= devFrom && Time[bar] <= devTo) { - if (lastSemBar < 0) debug("ProcessLowerCross(0.2) found no semaphore"); - else debug("ProcessLowerCross(0.3) found semaphore at bar["+ lastSemBar +"] "+ TimeToStr(Time[lastSemBar]) +" type="+ semTypes[lastSemType] +" trend="+ trend[lastSemBar]); - } - // a lower cross without a previous semaphore (near MaxBarsBack) if (lastSemBar < 0) { if (!last_error) { @@ -1540,7 +1481,7 @@ bool onReversal(int direction, double level) { if (direction!=D_LONG && direction!=D_SHORT) return(!catch("onReversal(1) invalid parameter direction: "+ direction, ERR_INVALID_PARAMETER)); if (!__isChart) return(true); if (IsPossibleDataPumping()) { // skip signals during possible data pumping - logWarn("onReversal(P="+ ZigZag.Periods +") Tick="+ Ticks +" alleged data pumping (Bars="+ Bars +" ValidBars="+ ValidBars +" ChangedBars="+ ChangedBars +")"); + //logWarn("onReversal(P="+ ZigZag.Periods +") Tick="+ Ticks +" alleged data pumping (Bars="+ Bars +" ValidBars="+ ValidBars +" ChangedBars="+ ChangedBars +")"); return(true); } @@ -1747,7 +1688,7 @@ bool ParameterStepper(int direction, int keys) { bool IsPossibleDataPumping() { if (__isTesting) return(false); - // TODO: What kind of nonsense is this implementation? + // TODO: review this seemingly strange implementation int waitPeriod = 20 * SECONDS; datetime now = GetGmtTime(); From 06e257f110cbe692ec4c7071f5636579bbfdb4c6 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Thu, 26 Mar 2026 13:32:08 +0200 Subject: [PATCH 35/40] update FindSemaphore() --- mql40/indicators/ZigZag.mq4 | 59 +++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index 7fd216855..61287658f 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -1203,15 +1203,18 @@ bool IsUpperCrossLast(int bar) { /** - * Find the next ZigZag semaphore starting at the specified bar looking backwards. On a semaphore bar the semaphore of the + * Find the previous ZigZag semaphore starting at the specified bar looking backwards. On a semaphore bar the semaphore of the * bar itself is returned. Specify a semaphore type to be skipped to prevent this. * * @param _In_ int bar - bar to start searching from - * @param _Out_ int resultType - semaphore result type: MODE_HIGH|MODE_LOW - * @param _In_ int skipType [optional] - semaphore type on the start bar to be skipped: MODE_HIGH|MODE_LOW (default: no skipping) + * @param _Out_ int resultType - type of the found semaphore: MODE_HIGH | MODE_LOW + * @param _In_ int skipType [optional] - semaphore on the start bar to be skipped: MODE_HIGH | MODE_LOW (default: none) * * @return int - chart offset of the found semaphore; - * EMPTY (-1) if no semaphore was found or in case of errors (parameter 'resultType' remains unchanged) + * EMPTY (-1) if no semaphore was found or in case of errors + * + * Note: If called from ProcessUpper/LowerCross() semaphore, trend and reversal data may not yet be set and can't be used + * on the current bar. In this case navigate back to a completed bar. */ int FindSemaphore(int bar, int &resultType, int skipType = NULL) { if (bar < 0 || bar >= Bars) return(_EMPTY(catch("FindSemaphore(1) invalid parameter bar: "+ bar +" (out of range)", ERR_INVALID_PARAMETER))); @@ -1222,29 +1225,27 @@ int FindSemaphore(int bar, int &resultType, int skipType = NULL) { // if no skipping (no skip type or not a semaphore bar), then return the next semaphore if (!skipType || !semaphoreClose[bar]) { if (!semaphoreClose[bar]) { // semaphore is located somewhere before - bar++; + bar++; // causes ProcessUpper/LowerCross() to work on a completed bar } - if (!semaphoreClose[bar] && unknownTrend[bar] > 0) { // navigate to the end semaphore (if any), - bar += unknownTrend[bar]; // a current bar has never unknownTrend=-1 + if (!semaphoreClose[bar] && unknownTrend[bar] > 0) { // navigate to the leg's end semaphore (if any), + bar += unknownTrend[bar]; } - if (!semaphoreClose[bar] && trend[bar]) { // navigate to the start semaphore (if any) + if (!semaphoreClose[bar] && trend[bar]) { // navigate to the leg's start semaphore (if any) bar += Abs(trend[bar]); } if (!semaphoreClose[bar]) { return(EMPTY); } - if (!lowerCrossLow [bar]) resultType = MODE_HIGH; - else if (!upperCrossHigh[bar]) resultType = MODE_LOW; - else if (semaphoreOpen [bar] < semaphoreClose[bar]-HalfPoint) resultType = MODE_HIGH; - else if (semaphoreOpen [bar] > semaphoreClose[bar]+HalfPoint) resultType = MODE_LOW; - else if (semaphoreClose[bar] > upperCrossHigh[bar]-HalfPoint) resultType = MODE_HIGH; // from here it holds: open == close - else resultType = MODE_LOW; // ... + if (semaphoreClose[bar] > High[bar]-HalfPoint) resultType = MODE_HIGH; + else resultType = MODE_LOW; return(bar); } - // if skipping: some semaphore type to skip on the start bar (not used by ZigZag calculation) + // on a semaphore bar: skip the specified semaphore type (used by ZigZag breakout tracking and TrackZigZagBalance) + + // either the bar holds a single semaphore if (semaphoreOpen[bar] == semaphoreClose[bar]) { - bool isHigh = (semaphoreClose[bar] == upperCrossHigh[bar]); + bool isHigh = (semaphoreClose[bar] > High[bar]-HalfPoint); if (skipType == MODE_HIGH) { if (isHigh) { @@ -1258,24 +1259,24 @@ int FindSemaphore(int bar, int &resultType, int skipType = NULL) { } resultType = MODE_HIGH; } + return(bar); } - else { - bool high2Low = (semaphoreOpen[bar] > semaphoreClose[bar]+HalfPoint); - if (skipType == MODE_HIGH) { - if (high2Low) { - return(FindSemaphore(bar+1, resultType)); - } - resultType = MODE_LOW; + // or the bar holds two semaphores + bool high2low = (semaphoreOpen[bar] > semaphoreClose[bar]+HalfPoint); + + if (skipType == MODE_HIGH) { + if (high2low) { // TODO: this looks rather strange + return(FindSemaphore(bar+1, resultType)); } - else /*skipType == MODE_LOW*/ { - if (!high2Low) { - return(FindSemaphore(bar+1, resultType)); - } - resultType = MODE_HIGH; + resultType = MODE_LOW; + } + else /*skipType == MODE_LOW*/ { + if (!high2low) { // TODO: this looks rather strange + return(FindSemaphore(bar+1, resultType)); } + resultType = MODE_HIGH; } - return(bar); } From 42dfdcb73db701d884da724fa758f915b6494b07 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Thu, 26 Mar 2026 15:20:19 +0200 Subject: [PATCH 36/40] disable inactive tracking inputs --- .../include/rsf/functions/iCustom/ZigZag.mqh | 4 +-- mql40/indicators/ZigZag.mq4 | 30 ++++++++++++++++--- 2 files changed, 28 insertions(+), 6 deletions(-) diff --git a/mql40/include/rsf/functions/iCustom/ZigZag.mqh b/mql40/include/rsf/functions/iCustom/ZigZag.mqh index b90c70c78..97b18cb7f 100644 --- a/mql40/include/rsf/functions/iCustom/ZigZag.mqh +++ b/mql40/include/rsf/functions/iCustom/ZigZag.mqh @@ -74,8 +74,8 @@ double icZigZag(int timeframe, int periods, int iBuffer, int iBar) { "separator", // string ___e_______________________ false, // bool TrackVirtualProfit - 0, // datetime TrackVirtualProfit.Since - "", // string TrackVirtualProfit.Symbol + //0, // datetime TrackVirtualProfit.Since + //"", // string TrackVirtualProfit.Symbol "separator", // string ___f_______________________ false, // bool TrendBufferAsDecimal diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index 61287658f..a9afaf2a2 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -112,8 +112,8 @@ extern string Sound.onNewChannelLow = "Price Decline.wav"; extern string ___e__________________________ = "=== Signal tracking ==="; extern bool TrackVirtualProfit = false; // whether to track virtual PnL of signals -extern datetime TrackVirtualProfit.Since = 0; // start time to track virtual PnL from -extern string TrackVirtualProfit.Symbol = "(default)"; // custom symbol to use for virtual PnL tracking + datetime TrackVirtualProfit.Since = 0; // start time to track virtual PnL from + string TrackVirtualProfit.Symbol = "(default)"; // custom symbol to use for virtual PnL tracking extern string ___f__________________________ = ""; extern bool TrendBufferAsDecimal = true; // decimal or binary (see notes in file header) @@ -1357,6 +1357,19 @@ bool ProcessUpperCross(int bar) { onReversal(D_LONG, upperCross[bar]); } } + + + // FATAL BTCUSD,M1 ZigZag::onTick(2) bar 0 2026.03.26 15:55 illegal reversal bar: trend=0 unknownTrend=0 reversalOffset=0 upperCross=69'402.10 lowerCross=0.00 semOpen=69'403.71 semClose=69'403.71 [ERR_ILLEGAL_STATE] + // FATAL BTCUSD,M1 ZigZag::onTick(2) bar 0 2026.03.26 15:57 illegal reversal bar: trend=0 unknownTrend=0 reversalOffset=0 upperCross=69'412.18 lowerCross=0.00 semOpen=69'417.38 semClose=69'417.38 [ERR_ILLEGAL_STATE] + + if (!trend[bar] && !unknownTrend[bar] || !reversalOffset[bar]) { + if (semaphoreOpen[bar] == semaphoreClose[bar]) { + if (!lowerCross[bar]) { + debug("ProcessUpperCross(0.1) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" lastSemBar="+ lastSemBar); + } + return(!catch("ProcessUpperCross(0.2) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" illegal reversal bar: trend="+ trend[bar] +" unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); + } + } return(true); } @@ -1437,6 +1450,15 @@ bool ProcessLowerCross(int bar) { onReversal(D_SHORT, lowerCross[bar]); } } + + if (!trend[bar] && !unknownTrend[bar] && !reversalOffset[bar]) { + if (semaphoreOpen[bar] == semaphoreClose[bar]) { + if (!upperCross[bar]) { + debug("ProcessLowerCross(0.1) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" lastSemBar="+ lastSemBar); + } + return(!catch("ProcessLowerCross(0.2) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" illegal reversal bar: trend="+ trend[bar] +" unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); + } + } return(true); } @@ -1855,8 +1877,8 @@ string InputsToStr() { "Sound.onNewChannelLow=", DoubleQuoteStr(Sound.onNewChannelLow) +";"+ NL, "TrackVirtualProfit=", BoolToStr(TrackVirtualProfit) +";"+ NL, - "TrackVirtualProfit.Since=", TimeToStr(TrackVirtualProfit.Since) +";"+ NL, - "TrackVirtualProfit.Symbol=", DoubleQuoteStr(TrackVirtualProfit.Symbol) +";"+ NL, + //"TrackVirtualProfit.Since=", TimeToStr(TrackVirtualProfit.Since) +";"+ NL, + //"TrackVirtualProfit.Symbol=", DoubleQuoteStr(TrackVirtualProfit.Symbol) +";"+ NL, "TrendBufferAsDecimal=", BoolToStr(TrendBufferAsDecimal) +";") ); From 35b49b309368b88d01c938188dc7b05a50265480 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Thu, 26 Mar 2026 15:40:06 +0200 Subject: [PATCH 37/40] document bar types --- mql40/indicators/ZigZag.mq4 | 27 +++++++++++++-------------- 1 file changed, 13 insertions(+), 14 deletions(-) diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index a9afaf2a2..393b12184 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -298,7 +298,7 @@ datetime recorder.startTime; // int trend : 0 (default) // int unknownTrend : -1 (default) before the last cross, otherwise non-negative // -// • Bar of ZigZag leg up +// • Bar of ZigZag leg up (no semaphore) // double upperBand : positive // double lowerBand : positive // double upperCross : 0 (default) or positive @@ -307,9 +307,9 @@ datetime recorder.startTime; // double lowerCrossLow : 0 (default) // double semaphoreOpen : 0 (default) // double semaphoreClose: 0 (default) -// int reversalOffset: -1 (default) before the reversal, otherwise positive +// int reversalOffset: positive // int trend : positive -// int unknownTrend : -1 (default) before the last cross, otherwise non-negative +// int unknownTrend : 0 or positive // // • Bar of ZigZag leg down // double upperBand : positive @@ -320,9 +320,9 @@ datetime recorder.startTime; // double lowerCrossLow : 0 (default) or positive // double semaphoreOpen : 0 (default) // double semaphoreClose: 0 (default) -// int reversalOffset: -1 (default) before the reversal, otherwise positive +// int reversalOffset: positive // int trend : negative -// int unknownTrend : -1 (default) before the last cross, otherwise non-negative +// int unknownTrend : 0 or positive // // • High semaphore bar // double upperBand : positive @@ -333,8 +333,8 @@ datetime recorder.startTime; // double lowerCrossLow : 0 (default) // double semaphoreOpen : positive // double semaphoreClose: positive (open = close) -// int reversalOffset: positive (previous reversal offset) -// int trend : positive (previous trend length) +// int reversalOffset: positive +// int trend : positive // int unknownTrend : 0 // // • Low semaphore bar @@ -346,11 +346,11 @@ datetime recorder.startTime; // double lowerCrossLow : positive // double semaphoreOpen : positive // double semaphoreClose: positive (open == close) -// int reversalOffset: positive (previous reversal offset) -// int trend : negative (previous trend length) +// int reversalOffset: positive +// int trend : negative // int unknownTrend : 0 // -// • Double crossing bar (high+low semaphore) after processing of the 2nd crossing +// • Double crossing bar (high + low semaphore) after processing of the 2nd crossing // double upperBand : positive // double lowerBand : positive // double upperCross : positive @@ -360,12 +360,11 @@ datetime recorder.startTime; // double semaphoreOpen : positive // double semaphoreClose: positive (open != close) // int reversalOffset: 0 (the previous reversal occurred on the same bar) -// int trend : 0 (the whole last trend ocurred on the same bar) +// int trend : 0 (the complete leg ocurred in the same bar) // int unknownTrend : 0 (same as reversal offset) // -// • Triple+ crossing bar (more than two semaphores) ??? -// -// • Bar 0 (zero), the current bar ??? +// • Triple+ crossing bar (more than two semaphores) +// Same as double crossing. The last crossing overwrites values from previous crossings. // From 5da23d2da06af08126599ccd0a5f3904a2e84185 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Thu, 26 Mar 2026 16:20:51 +0200 Subject: [PATCH 38/40] bugfix in FindSemaphore() --- mql40/indicators/ZigZag.mq4 | 46 ++++++++++++------------------------- 1 file changed, 15 insertions(+), 31 deletions(-) diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index 393b12184..3798cf924 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -1212,8 +1212,9 @@ bool IsUpperCrossLast(int bar) { * @return int - chart offset of the found semaphore; * EMPTY (-1) if no semaphore was found or in case of errors * - * Note: If called from ProcessUpper/LowerCross() semaphore, trend and reversal data may not yet be set and can't be used - * on the current bar. In this case navigate back to a completed bar. + * Note: If called from ProcessUpper/LowerCross() semaphore, trend and reversal data may not yet be set or hold values of a + * previous tick. Especially semaphoreOpen/Close may not hold the High/Low of the current bar because the current tick + * extended the range. */ int FindSemaphore(int bar, int &resultType, int skipType = NULL) { if (bar < 0 || bar >= Bars) return(_EMPTY(catch("FindSemaphore(1) invalid parameter bar: "+ bar +" (out of range)", ERR_INVALID_PARAMETER))); @@ -1235,16 +1236,21 @@ int FindSemaphore(int bar, int &resultType, int skipType = NULL) { if (!semaphoreClose[bar]) { return(EMPTY); } - if (semaphoreClose[bar] > High[bar]-HalfPoint) resultType = MODE_HIGH; - else resultType = MODE_LOW; + if (!lowerCrossLow [bar]) resultType = MODE_HIGH; + else if (!upperCrossHigh[bar]) resultType = MODE_LOW; + else if (semaphoreOpen [bar] < semaphoreClose[bar]-HalfPoint) resultType = MODE_HIGH; + else if (semaphoreOpen [bar] > semaphoreClose[bar]+HalfPoint) resultType = MODE_LOW; + // now it holds: semaphoreOpen == semaphoreClose + else if (semaphoreClose[bar] > upperCrossHigh[bar]-HalfPoint) resultType = MODE_HIGH; + else resultType = MODE_LOW; return(bar); } // on a semaphore bar: skip the specified semaphore type (used by ZigZag breakout tracking and TrackZigZagBalance) // either the bar holds a single semaphore - if (semaphoreOpen[bar] == semaphoreClose[bar]) { - bool isHigh = (semaphoreClose[bar] > High[bar]-HalfPoint); + if (semaphoreOpen[bar] == semaphoreClose[bar]) { // don't test against High[bar] + bool isHigh = (semaphoreClose[bar] > upperCrossHigh[bar]-HalfPoint); if (skipType == MODE_HIGH) { if (isHigh) { @@ -1265,13 +1271,13 @@ int FindSemaphore(int bar, int &resultType, int skipType = NULL) { bool high2low = (semaphoreOpen[bar] > semaphoreClose[bar]+HalfPoint); if (skipType == MODE_HIGH) { - if (high2low) { // TODO: this looks rather strange + if (high2low) { // TODO: this looks rather strange return(FindSemaphore(bar+1, resultType)); } resultType = MODE_LOW; } else /*skipType == MODE_LOW*/ { - if (!high2low) { // TODO: this looks rather strange + if (!high2low) { // TODO: this looks rather strange return(FindSemaphore(bar+1, resultType)); } resultType = MODE_HIGH; @@ -1356,19 +1362,6 @@ bool ProcessUpperCross(int bar) { onReversal(D_LONG, upperCross[bar]); } } - - - // FATAL BTCUSD,M1 ZigZag::onTick(2) bar 0 2026.03.26 15:55 illegal reversal bar: trend=0 unknownTrend=0 reversalOffset=0 upperCross=69'402.10 lowerCross=0.00 semOpen=69'403.71 semClose=69'403.71 [ERR_ILLEGAL_STATE] - // FATAL BTCUSD,M1 ZigZag::onTick(2) bar 0 2026.03.26 15:57 illegal reversal bar: trend=0 unknownTrend=0 reversalOffset=0 upperCross=69'412.18 lowerCross=0.00 semOpen=69'417.38 semClose=69'417.38 [ERR_ILLEGAL_STATE] - - if (!trend[bar] && !unknownTrend[bar] || !reversalOffset[bar]) { - if (semaphoreOpen[bar] == semaphoreClose[bar]) { - if (!lowerCross[bar]) { - debug("ProcessUpperCross(0.1) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" lastSemBar="+ lastSemBar); - } - return(!catch("ProcessUpperCross(0.2) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" illegal reversal bar: trend="+ trend[bar] +" unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); - } - } return(true); } @@ -1449,15 +1442,6 @@ bool ProcessLowerCross(int bar) { onReversal(D_SHORT, lowerCross[bar]); } } - - if (!trend[bar] && !unknownTrend[bar] && !reversalOffset[bar]) { - if (semaphoreOpen[bar] == semaphoreClose[bar]) { - if (!upperCross[bar]) { - debug("ProcessLowerCross(0.1) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" lastSemBar="+ lastSemBar); - } - return(!catch("ProcessLowerCross(0.2) Tick="+ Ticks +" bar="+ bar +" "+ TimeToStr(Time[bar]) +" illegal reversal bar: trend="+ trend[bar] +" unknownTrend="+ unknownTrend[bar] +" reversalOffset="+ _int(reversalOffset[bar]) +" upperCross="+ NumberToStr(upperCross[bar], PriceFormat) +" lowerCross="+ NumberToStr(lowerCross[bar], PriceFormat) +" semOpen="+ NumberToStr(semaphoreOpen[bar], PriceFormat) +" semClose="+ NumberToStr(semaphoreClose[bar], PriceFormat), ERR_ILLEGAL_STATE)); - } - } return(true); } @@ -1524,7 +1508,7 @@ bool onReversal(int direction, double level) { eventAction = !GetWindowPropertyA(hWndTerminal, propertyName); SetWindowPropertyA(hWndTerminal, propertyName, 1); } - if (eventAction) logInfo("onReversal(P="+ ZigZag.Periods +") "+ message1); + if (eventAction) logInfo("onReversal(P="+ ZigZag.Periods +") Tick="+ Ticks +" "+ message1); } // sound: once per system From 997276cf030c3dd255e3c0112e29acfa5f0450aa Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Thu, 26 Mar 2026 16:59:37 +0200 Subject: [PATCH 39/40] move adjustment of recorder.priceBase to UpdateVirtualProfit() --- mql40/indicators/ZigZag.mq4 | 60 +++++++++++-------------------------- 1 file changed, 17 insertions(+), 43 deletions(-) diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index 3798cf924..662ec7a20 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -241,7 +241,7 @@ int recorder.hstFormat; string recorder.symbol = ""; string recorder.symbolDescr = ""; string recorder.group = ""; -int recorder.priceBase; +int recorder.priceBase = 1; int recorder.hSet; datetime recorder.startTime; @@ -772,7 +772,7 @@ int onTick() { if (!RecordVirtualProfit()) return(last_error); //if (Ticks == 1) { - // debug("onTick(0.5) Tick="+ Ticks +" RecordVirtualProfit() => "+ DoubleToStr((GetTickCount()-startRecorder)/1000.0, 3) +" sec"); + // debug("onTick(0.1) Tick="+ Ticks +" RecordVirtualProfit() => "+ DoubleToStr((GetTickCount()-startRecorder)/1000.0, 3) +" sec"); //} } @@ -1032,6 +1032,12 @@ bool UpdateVirtualProfit(int bar, bool isReversal, double &vOpen[], double &vHig // normal bar without a position (before first ZigZag reversal) //else {} + // adjust the price base for the timeseries to always be positive + double hstValue = vLow[bar] + recorder.priceBase; + while (hstValue <= 0) { + recorder.priceBase *= 10; + hstValue = vLow[bar] + recorder.priceBase; + } return(true); } @@ -1055,71 +1061,39 @@ bool RecordVirtualProfit() { int symbolId = CreateRawSymbol(recorder.symbol, recorder.symbolDescr, recorder.group, pDigits, AccountCurrency(), AccountCurrency(), recorder.hstDirectory); if (symbolId < 0) return(false); } - - // open HistorySet - if (!recorder.hSet) { - recorder.hSet = HistorySet1.Get(recorder.symbol, recorder.hstDirectory); - if (recorder.hSet == -1) { - recorder.hSet = HistorySet1.Create(recorder.symbol, recorder.symbolDescr, pDigits, recorder.hstFormat, recorder.hstDirectory); - } - if (!recorder.hSet) return(false); - } recorder.initialized = true; - debug("RecordVirtualProfit(0.1) Tick="+ Ticks +" recorder initialized"); } int startBar = 0, flags = HST_FILL_GAPS|HST_BUFFER_TICKS; double O, H, L, C; if (ChangedBars > 2) { // rewrite the full history (intentionally skip rewriting bar 1 on BarOpen) - if (recorder.hSet != 0) { + if (recorder.hSet > 0) { int tmp = recorder.hSet; recorder.hSet = NULL; if (!HistorySet1.Close(tmp)) return(false); // TODO: HistorySet.Create() should auto-close an open set but errors } startBar = iBarShiftNext(NULL, NULL, recorder.startTime); - debug("RecordVirtualProfit(0.2) Tick="+ Ticks +" rewriting all history since "+ TimeToStr(recorder.startTime) +" (bar "+ startBar +")"); + //debug("RecordVirtualProfit(0.1) Tick="+ Ticks +" rewriting all history since bar "+ startBar); } - for (int bar=startBar; bar >= 0; bar--) { - if (!recorder.hSet) { - recorder.hSet = HistorySet1.Create(recorder.symbol, recorder.symbolDescr, pDigits, recorder.hstFormat, recorder.hstDirectory); - if (!recorder.hSet) return(false); - } + if (recorder.hSet <= 0) { + recorder.hSet = HistorySet1.Create(recorder.symbol, recorder.symbolDescr, pDigits, recorder.hstFormat, recorder.hstDirectory); + if (!recorder.hSet) return(false); + } + for (int bar=startBar; bar >= 0; bar--) { O = virtualProfit_O[bar]; H = virtualProfit_H[bar]; L = virtualProfit_L[bar]; C = virtualProfit_C[bar]; if (C >= EMPTY_VALUE) continue; - L += recorder.priceBase; - if (L <= 0) { - switch(recorder.priceBase) { - case 0: recorder.priceBase = 1; break; - case 1: recorder.priceBase = 10; break; - case 10: recorder.priceBase = 100; break; - case 100: recorder.priceBase = 1000; break; - case 1000: recorder.priceBase = 10000; break; - case 10000: recorder.priceBase = 100000; break; - case 100000: recorder.priceBase = 1000000; break; - case 1000000: recorder.priceBase = 10000000; break; - } - debug("RecordVirtualProfit(0.3) Tick="+ Ticks +" bar="+ bar +" updated price base to "+ DoubleToStr(recorder.priceBase, 2)); - - tmp = recorder.hSet; - recorder.hSet = NULL; - if (!HistorySet1.Close(tmp)) return(false); - startBar = iBarShiftNext(NULL, NULL, recorder.startTime); - bar = startBar + 1; - continue; - } - if (!HistorySet1.AddTick(recorder.hSet, Time[bar], O + recorder.priceBase, flags)) return(false); if (!HistorySet1.AddTick(recorder.hSet, Time[bar], H + recorder.priceBase, flags)) return(false); - if (!HistorySet1.AddTick(recorder.hSet, Time[bar], L, flags)) return(false); + if (!HistorySet1.AddTick(recorder.hSet, Time[bar], L + recorder.priceBase, flags)) return(false); if (bar == 0) { - flags &= ~HST_BUFFER_TICKS; // unset HST_BUFFER_TICKS on bar 0 (zero) + flags &= ~HST_BUFFER_TICKS; // unset HST_BUFFER_TICKS on bar 0 (zero) } if (!HistorySet1.AddTick(recorder.hSet, Time[bar], C + recorder.priceBase, flags)) return(false); } From 7307060df86b63a488a92ea9601fb1844103bc76 Mon Sep 17 00:00:00 2001 From: Peter Walther Date: Thu, 26 Mar 2026 17:54:10 +0200 Subject: [PATCH 40/40] fix bit-wise precedence bug --- mql40/experts/ZigZag EA.mq4 | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/mql40/experts/ZigZag EA.mq4 b/mql40/experts/ZigZag EA.mq4 index 4f88d5de5..a912fe34c 100644 --- a/mql40/experts/ZigZag EA.mq4 +++ b/mql40/experts/ZigZag EA.mq4 @@ -475,10 +475,10 @@ bool IsZigZagReversalBar(int bar, int &reversalType, double &reversalPrice) { int combinedTrend = icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_COMBINED_TREND, bar); // 85% of the local time here int trend = combinedTrend & 0xFFFF; // extract LOWORD and manually sign-extend, - if (trend & 0x8000 != 0) trend |= 0xFFFF0000; // i.e. convert int to signed short + if ((trend & 0x8000) != 0) trend |= 0xFFFF0000; // i.e. convert int to signed short int unknownTrend = (combinedTrend >> 16) & 0xFFFF; // extract HIWORD and manually sign-extend, - if (unknownTrend & 0x8000 != 0) unknownTrend |= 0xFFFF0000; // i.e. convert int to signed short + if ((unknownTrend & 0x8000) != 0) unknownTrend |= 0xFFFF0000; // i.e. convert int to signed short if (unknownTrend < 0) return(!catch("IsZigZagReversalBar(1) unexpected bar="+ bar +" "+ TimeToStr(Time[bar]) +" trend=0 unknownTrend="+ unknownTrend, ERR_ILLEGAL_STATE)); if (!unknownTrend) { @@ -515,12 +515,12 @@ bool IsZigZagReversalBar(int bar, int &reversalType, double &reversalPrice) { int GetZigZagDirection(int bar) { int combinedTrend = icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_COMBINED_TREND, bar); - int trend = combinedTrend & 0xFFFF; // extract LOWORD and manually sign-extend, - if (trend & 0x8000 != 0) trend |= 0xFFFF0000; // i.e. convert int to signed short + int trend = combinedTrend & 0xFFFF; // extract LOWORD and manually sign-extend, + if ((trend & 0x8000) != 0) trend |= 0xFFFF0000; // i.e. convert int to signed short if (!trend) { - int unknownTrend = (combinedTrend >> 16) & 0xFFFF; // extract HIWORD and manually sign-extend, - if (unknownTrend & 0x8000 != 0) unknownTrend |= 0xFFFF0000; // i.e. convert int to signed short + int unknownTrend = (combinedTrend >> 16) & 0xFFFF; // extract HIWORD and manually sign-extend, + if ((unknownTrend & 0x8000) != 0) unknownTrend |= 0xFFFF0000; // i.e. convert int to signed short if (unknownTrend < 0) return(!catch("GetZigZagDirection(1) unexpected bar="+ bar +" "+ TimeToStr(Time[bar]) +" trend=0 unknownTrend="+ unknownTrend, ERR_ILLEGAL_STATE)); int semBar = bar + unknownTrend;