diff --git a/mql40/experts/ZigZag EA.mq4 b/mql40/experts/ZigZag EA.mq4 index e08c51600..a912fe34c 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" @@ -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 = MathAbs(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: bar offset of most recent ZigZag reversal to previous 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_TREND, bar)); // 88% of the local time - reversalOffset = MathRound(icZigZag(NULL, ZigZag.Periods, ZigZag.MODE_REVERSAL, 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/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/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/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/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/include/rsf/functions/iCustom/ZigZag.mqh b/mql40/include/rsf/functions/iCustom/ZigZag.mqh index 46d30524d..97b18cb7f 100644 --- a/mql40/include/rsf/functions/iCustom/ZigZag.mqh +++ b/mql40/include/rsf/functions/iCustom/ZigZag.mqh @@ -1,12 +1,24 @@ -#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_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 +#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). + */ /** @@ -25,7 +37,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 +45,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 +54,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 +72,15 @@ double icZigZag(int timeframe, int periods, int iBuffer, int iBar) { "", // string Sound.onNewChannelHigh "", // string Sound.onNewChannelLow - "separator", // string ____________________________ + "separator", // string ___e_______________________ + false, // bool TrackVirtualProfit + //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/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/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/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/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/Equity Recorder.mq4 b/mql40/indicators/Equity Recorder.mq4 index 7653f24b9..2fa016f95 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 @@ -205,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); } @@ -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). * 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) +")"; diff --git a/mql40/indicators/ZigZag.mq4 b/mql40/indicators/ZigZag.mq4 index c7c42a989..662ec7a20 100644 --- a/mql40/indicators/ZigZag.mq4 +++ b/mql40/indicators/ZigZag.mq4 @@ -2,68 +2,72 @@ * 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. * * * 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. + * • 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 can be overwritten with custom default values. + * • 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. * * + * 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. * - * TODO: - * - 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() - * - document usage of iCustom() + * 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[]; @@ -75,7 +79,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; @@ -84,20 +88,15 @@ 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; -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"; @@ -111,88 +110,110 @@ extern bool Sound.onChannelWidening = false; extern string Sound.onNewChannelHigh = "Price Advance.wav"; extern string Sound.onNewChannelLow = "Price Decline.wav"; +extern string ___e__________________________ = "=== Signal tracking ==="; +extern bool TrackVirtualProfit = false; // whether to track virtual PnL of signals + 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) + ///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// +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 +#include +#include #include #include #include +#include +#include #include #include #include #include -#include -#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_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 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 +#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 = 4; // 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_color3 Blue // upper channel band -#property indicator_style3 STYLE_DOT // -#property indicator_color4 Magenta // lower channel band -#property indicator_style4 STYLE_DOT // - -#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 // 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) - -#define MODE_FIRST_CROSSING 1 // crossing draw types -#define MODE_ALL_CROSSINGS 2 +#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 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[]; // ... +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 = ""; +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 // draw types of channel crossings +#define MODE_ALL_CROSSINGS 2 bool signal.onReversal.sound; bool signal.onReversal.alert; @@ -202,13 +223,32 @@ bool signal.onBreakout.sound; bool signal.onBreakout.alert; bool signal.onBreakout.mail; -datetime skipSignals; // skip signals until the specified time to wait for possible data pumping +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 + + +// recorder status +bool recorder.initialized; +string recorder.hstDirectory = ""; +int recorder.hstFormat; +string recorder.symbol = ""; +string recorder.symbolDescr = ""; +string recorder.group = ""; +int recorder.priceBase = 1; +int recorder.hSet; +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 @@ -221,6 +261,113 @@ 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 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 unknownTrend : -1 (default) before the last cross, otherwise non-negative +// +// • Bar of ZigZag leg up (no semaphore) +// 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: positive +// int trend : positive +// int unknownTrend : 0 or 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: positive +// int trend : negative +// int unknownTrend : 0 or positive +// +// • 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 +// int trend : positive +// 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 +// int trend : negative +// 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 complete leg ocurred in the same bar) +// int unknownTrend : 0 (same as reversal offset) +// +// • Triple+ crossing bar (more than two semaphores) +// Same as double crossing. The last crossing overwrites values from previous crossings. +// + + /** * Initialization * @@ -255,10 +402,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 @@ -287,10 +434,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 @@ -306,28 +453,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 @@ -336,7 +466,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) { @@ -349,7 +479,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); } @@ -365,6 +495,25 @@ int onInit() { else legendInfo = StrLeft(legendInfo, -1) +",w)"; } + // 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, "TrackVirtualProfit.Since", ""); + if (sValue != "") { + int result[]; + if (!ParseDateTime(sValue, DATE_YYYYMMDD|TIME_OPTIONAL, result)) { + return(catch("onInit(12) invalid config parameter TrackVirtualProfit.Since: "+ DoubleQuoteStr(sValue), ERR_INVALID_INPUT_PARAMETER)); + } + TrackVirtualProfit.Since = DateTime2(result); + } + } + // TrackVirtualProfit.Symbol + if (AutoConfiguration) TrackVirtualProfit.Symbol = GetConfigBool(indicator, "TrackVirtualProfit.Symbol", TrackVirtualProfit.Symbol); + // reset global vars used by the various event handlers skipSignals = 0; lastTick = 0; @@ -380,7 +529,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]; @@ -405,6 +554,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)")); } @@ -420,26 +576,39 @@ 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 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(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(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; + dbc_unknownTrend = -1; + lastUpperBand = 0; lastLowerBand = 0; lastLegHigh = 0; @@ -452,44 +621,53 @@ 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 (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 if (!__isTesting) IsPossibleDataPumping(); // 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)); + if (!ValidBars) recorder.startTime = Time[startBar]; // 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; + 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; } - 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 +679,76 @@ 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 + 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 + } } // 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 + 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 (!knownTrend[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 } } - // 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); - // calculate combinedTrend[] - combinedTrend[bar] = Sign(knownTrend[bar]) * unknownTrend[bar] * 100000 + knownTrend[bar]; + // whether the current 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]); + } + + // update virtual PnL + if (TrackVirtualProfit) { + if (!UpdateVirtualProfit(bar, isReversalBar, virtualProfit_O, virtualProfit_H, virtualProfit_L, virtualProfit_C)) return(last_error); + } // 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 - 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 - } - - 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; + 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 + dbc_isReversalBar = (Abs(dbc_trend) == dbc_reversalOffset); + } + if (!dbc_isReversalBar) { + 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) } } else { @@ -562,152 +756,163 @@ int onTick() { lowerCross[bar] = 0; } } + + // 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 (__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]; - } + // record virtual PnL + if (TrackVirtualProfit) { + datetime startRecorder = GetTickCount(); - // 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 (!RecordVirtualProfit()) return(last_error); + //if (Ticks == 1) { + // debug("onTick(0.1) Tick="+ Ticks +" RecordVirtualProfit() => "+ DoubleToStr((GetTickCount()-startRecorder)/1000.0, 3) +" sec"); + //} + } - if (Ticks==1 && TrackZigZagBalance && TrackZigZagBalance.Since) { - //debug("onTick(0.1) TrackZigZagBalance.Since="+ TimeToStr(TrackZigZagBalance.Since)); - - int currSem, prevBar, prevSem, size; - int currBar = FindPrevSemaphore(0, currSem); if (currBar == -1) return(last_error); - - double events[][3]; // [datetime, type, price] - ArraySetAsSeries(events, false); - ArrayResize(events, 0); - + // detect ZigZag breakouts (comparing legs against bands also detects breakouts on missed ticks) + if (Signal.onBreakout) { + if (ChangedBars > 2) { while (true) { - prevSem = currSem; - prevBar = FindPrevSemaphore(currBar, prevSem); if (prevBar == -1) return(last_error); - - int reversalOffset = reversal[currBar]; - int reversalBar = prevBar - reversalOffset; // standard case + int resultType = 0; + bar = FindSemaphore(0, resultType); if (bar < 0) break; - if (!reversalOffset && reversal[currBar+1]==-1) { // reversal and next semaphore on the same bar - reversalBar = currBar; + // resolve leg high/low + if (resultType == MODE_HIGH) { + lastLegHigh = High[bar]; + lastLegLow = 0; } - 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; - } - - ArraySetAsSeries(events, true); - size = ArrayRange(events, 0); - - 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; - } - 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 { + 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 { - // detect ZigZag breakouts (comparing legs against bands also detects breakouts on missed ticks) - if (Signal.onBreakout && sema3) { - if (knownTrend[0] > 0) { + 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 (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 } } + } + + // 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]; + } - // detect Donchian channel widenings - if (Sound.onChannelWidening) { - if (ChangedBars == 2) { - lastUpperBand = upperBand[1]; - lastLowerBand = lowerBand[1]; + // --- old: track ZigZag balance -------------------------------------------------------------------------------------- + // + if (TrackZigZagBalance) { + 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); + + while (true) { // TODO: these returns are wrong + prevBar = FindSemaphore(currBar, prevSem, currSem); if (prevBar < 0) return(last_error); + + int reversalBar = prevBar - reversalOffset[currBar]; // standard case + + if (!reversalOffset[currBar] && reversalOffset[currBar+1]==-1) { // reversal and next semaphore on the same bar + reversalBar = currBar; } - if (lastUpperBand && lastLowerBand) { - if (upperBand[0] > lastUpperBand+HalfPoint) onChannelWidening(D_LONG); - else if (lowerBand[0] < lastLowerBand-HalfPoint) onChannelWidening(D_SHORT); + 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; + } + + 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; + } + 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; + } + } } - lastUpperBand = upperBand[0]; - lastLowerBand = lowerBand[0]; } } } @@ -716,61 +921,235 @@ int onTick() { /** - * Calculate the balance marker offset for the current view port of the chart. + * Update the virtual PnL for the specified bar. * - * @return double + * @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 */ -double CalculateMarkerOffset() { - double minPrice = NormalizeDouble(WindowPriceMin(), Digits); - double maxPrice = NormalizeDouble(WindowPriceMax(), Digits); - if (!minPrice || !maxPrice) return(0); // chart not yet ready +bool UpdateVirtualProfit(int bar, bool isReversal, double &vOpen[], double &vHigh[], double &vLow[], double &vClose[]) { + isReversal = (isReversal != 0); + + vOpen [bar] = vClose[bar+1]; + vHigh [bar] = vClose[bar+1]; + vLow [bar] = vClose[bar+1]; + vClose[bar] = vClose[bar+1]; + + bool isPosition = (vClose[bar] != EMPTY_VALUE), isLegUp; + + // reversal bar, flip the position + if (isReversal) { + if (!isPosition) { + vOpen [bar] = 0; + vHigh [bar] = 0; + vLow [bar] = 0; + vClose[bar] = 0; + } - double priceRange = maxPrice - minPrice; - if (priceRange < HalfPoint) return(0); // chart with ScaleFix=1 after resizing to zero height + if (trend[bar] > 0) { // upper crossing, switch to long + if (isPosition) { + 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 exact intra-bar path is unknown + } + else { + 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) { // lower crossing, switch to short + if (isPosition) { + 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 exact intra-bar path is unknown + } + else { + vHigh [bar] = (lowerCross[bar] - Low[bar]); // open short position + vLow [bar] = (lowerCross[bar] - High[bar]); + vClose[bar] = (lowerCross[bar] - Close[bar]); + } + } + else /*trend == 0*/{ + // double crossing with two semaphores (at the moment when the bar is processed) + if (semaphoreOpen[bar] < semaphoreClose[bar]) { // crossing order "Low, High" + if (isPosition) { + 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 + } + else if (semaphoreOpen[bar] > semaphoreClose[bar]) { // crossing order "High, Low" + if (isPosition) { + 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 + } + 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)); - double offset = NormalizeDouble(priceRange * 0.007, Digits); // 7% - return(offset); + vHigh[bar] = MathMax(vOpen[bar], vClose[bar]); // the exact intra-bar path is unknown + vLow [bar] = MathMin(vOpen[bar], vClose[bar]); + } + } + + // normal bar without crossing, update an existing position + else if (isPosition) { + 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 /*isLegDown*/ { + vClose[bar] -= (Close[bar] - Close[bar+1]); + vHigh [bar] = vClose[bar] - ( Low[bar] - Close[bar]); + vLow [bar] = vClose[bar] - (High[bar] - Close[bar]); + } + } + + // 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); } /** - * Whether a bar crossing both channel bands crossed the upper band first. The result is just a "best guess". - * - * @param int bar - bar offset + * Record the timeseries with virtual PnL. * - * @return bool + * @return bool - success status */ -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); - } - } +bool RecordVirtualProfit() { + 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); } + recorder.initialized = true; } - double ho = High [bar] - Open [bar]; - double ol = Open [bar] - Low [bar]; - double hc = High [bar] - Close[bar]; - double cl = Close[bar] - Low [bar]; + int startBar = 0, flags = HST_FILL_GAPS|HST_BUFFER_TICKS; + double O, H, L, C; - double minOpen = MathMin(ho, ol); - double minClose = MathMin(hc, cl); + 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("RecordVirtualProfit(0.1) Tick="+ Ticks +" rewriting all history since bar "+ startBar); + } - if (minOpen < minClose) lastResult = (ho < ol); - else lastResult = (hc > cl); + 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; + + 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 + recorder.priceBase, flags)) return(false); + if (bar == 0) { + 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); + } + return(true); +} - lastBarTime = Time[bar]; - lastBarHigh = High[bar]; - lastBarLow = Low [bar]; - return(lastResult); +/** + * 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. + * + * @return double + */ +double CalculateMarkerOffset() { + double minPrice = NormalizeDouble(WindowPriceMin(), Digits); + double maxPrice = NormalizeDouble(WindowPriceMax(), Digits); + if (!minPrice || !maxPrice) return(0); // chart not yet ready + + double priceRange = maxPrice - minPrice; + if (priceRange < HalfPoint) return(0); // chart with ScaleFix=1 after resizing to zero height + + double offset = NormalizeDouble(priceRange * 0.007, Digits); // 7% + return(offset); } @@ -793,87 +1172,89 @@ 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 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 - 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) * - * @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) + * @return int - chart offset of the found semaphore; + * EMPTY (-1) if no semaphore was found or in case of errors * - * @return int - chart offset of the found semaphore or EMPTY (-1) in case of errors + * 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 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))); } - if (!type || !semaphoreClose[bar]) { - if (semaphoreClose[bar] != NULL) { // semaphore is located at the current bar - int semBar = bar; + // 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++; // causes ProcessUpper/LowerCross() to work on a completed bar } - else { // 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 (!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 leg's start semaphore (if any) + bar += Abs(trend[bar]); } - 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]) { + 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; + // now it holds: semaphoreOpen == semaphoreClose + else if (semaphoreClose[bar] > upperCrossHigh[bar]-HalfPoint) resultType = MODE_HIGH; + else resultType = MODE_LOW; + return(bar); } - if (semaphoreOpen[bar] == semaphoreClose[bar]) { - bool isHigh = (semaphoreClose[bar] == higherHigh[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]) { // don't test against High[bar] + bool isHigh = (semaphoreClose[bar] > upperCrossHigh[bar]-HalfPoint); - if (type == MODE_HIGH) { + if (skipType == MODE_HIGH) { if (isHigh) { - type = NULL; - return(FindPrevSemaphore(bar+1, type)); + return(FindSemaphore(bar+1, resultType)); } - type = MODE_LOW; + resultType = MODE_LOW; } - else /*type == MODE_LOW*/ { + else /*skipType == MODE_LOW*/ { if (!isHigh) { - type = NULL; - return(FindPrevSemaphore(bar+1, type)); + return(FindSemaphore(bar+1, resultType)); } - type = MODE_HIGH; + resultType = MODE_HIGH; } + return(bar); } - else { - bool high2Low = (semaphoreOpen[bar] > semaphoreClose[bar]+HalfPoint); - if (type == MODE_HIGH) { - if (high2Low) { - type = NULL; - return(FindPrevSemaphore(bar+1, type)); - } - type = 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 /*type == MODE_LOW*/ { - if (!high2Low) { - type = NULL; - return(FindPrevSemaphore(bar+1, type)); - } - type = 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); } @@ -888,49 +1269,72 @@ 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 + int lastSemType, lastSemBar = FindSemaphore(bar, lastSemType); // find the last semaphore + + // 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]; + reversalOffset[bar] = -1; // no reversal + trend [bar] = 0; // no trend + unknownTrend [bar] = 0; // current bar + } + return(!last_error); + } - if (prevTrend > 0) { - if (prevSem == bar) { // trend buffers are already set - if (semaphoreOpen[bar] != lowerLow[bar]) { // update existing semaphore - semaphoreOpen[bar] = higherHigh[bar]; + // 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 + } + else { + semaphoreOpen[lastSemBar] = 0; // reset previous semaphore } - semaphoreClose[prevSem] = semaphoreOpen[prevSem]; - semaphoreOpen [bar] = higherHigh[bar]; // set new semaphore - semaphoreClose[bar] = higherHigh[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) - knownTrend [bar] = knownTrend [bar+1]; // keep known trend + trend [bar] = trend [bar+1]; // keep trend (may be 0) unknownTrend[bar] = unknownTrend[bar+1] + 1; // increase unknown trend } - 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 + unknownTrend [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 +1349,102 @@ 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 + int lastSemType, lastSemBar = FindSemaphore(bar, lastSemType); // find the last semaphore + + // 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]; + reversalOffset[bar] = -1; // no reversal + trend [bar] = 0; // no trend + unknownTrend [bar] = 0; // current bar + } + return(!last_error); + } - if (prevTrend < 0) { - if (prevSem == bar) { // trend buffers are already set - if (semaphoreOpen[bar] != higherHigh[bar]) { // update existing semaphore - semaphoreOpen [bar] = lowerLow[bar]; + // 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 + } + else { + semaphoreOpen[lastSemBar] = 0; // reset previous semaphore } - semaphoreClose[prevSem] = semaphoreOpen[prevSem]; - semaphoreOpen [bar] = lowerLow[bar]; // set new semaphore - semaphoreClose[bar] = lowerLow[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) - knownTrend [bar] = knownTrend [bar+1]; // keep known trend + trend [bar] = trend [bar+1]; // keep trend (may be 0) unknownTrend[bar] = unknownTrend[bar+1] + 1; // increase unknown trend } - 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 + unknownTrend [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 unknownTrend[] 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; + unknownTrend [i] = -1; + + if (TrendBufferAsDecimal) combinedTrend[i] = trend[i]; // hide unknownTrend=-1 + else combinedTrend[i] = (unknownTrend[i] << 16) | trend[i]; // iCustom() - 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 +1460,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) +")"; @@ -1049,7 +1482,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 @@ -1103,7 +1536,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 +1668,19 @@ bool ParameterStepper(int direction, int keys) { bool IsPossibleDataPumping() { if (__isTesting) return(false); - int waitPeriod = 20*SECONDS; + // TODO: review this seemingly strange 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); } @@ -1257,11 +1692,11 @@ void UpdateChartLegend() { // 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 sTrend = " "+ NumberToStr(trend[0], "+."); string sUnknown = ifString(!unknownTrend[0], "", "/"+ unknownTrend[0]); - string sReversal = " next reversal @" + NumberToStr(ifDouble(knownTrend[0] < 0, upperBand[0]+Point, lowerBand[0]-Point), PriceFormat); - string sSignal = ifString(Signal.onReversal, " "+ legendInfo, ""); - string text = StringConcatenate(indicatorName, sKnown, sUnknown, sReversal, sSignal); + string sReversal = " next reversal @" + NumberToStr(ifDouble(trend[0] < 0, upperBand[0]+Point, lowerBand[0]-Point), PriceFormat); + string sSignal = ifString(Signal.onReversal || Sound.onChannelWidening, " "+ legendInfo, ""); + string text = StringConcatenate(indicatorName, sTrend, sUnknown, sReversal, sSignal); color clr = ZigZag.Color; if (clr == Aqua ) clr = DodgerBlue; @@ -1298,14 +1733,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_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_COMBINED_TREND, combinedTrend ); SetIndexEmptyValue(MODE_COMBINED_TREND, 0); SetIndexLabel(MODE_COMBINED_TREND, shortName +" trend"); IndicatorDigits(Digits); int drawType = ifInt(ZigZag.Width, zigzagDrawType, DRAW_NONE); @@ -1322,8 +1757,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_COMBINED_TREND, DRAW_NONE); if (redraw) WindowRedraw(); return(!catch("SetIndicatorOptions(1)")); @@ -1369,38 +1804,40 @@ 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, + + "TrackVirtualProfit=", BoolToStr(TrackVirtualProfit) +";"+ NL, + //"TrackVirtualProfit.Since=", TimeToStr(TrackVirtualProfit.Since) +";"+ NL, + //"TrackVirtualProfit.Symbol=", DoubleQuoteStr(TrackVirtualProfit.Symbol) +";"+ NL, + + "TrendBufferAsDecimal=", BoolToStr(TrendBufferAsDecimal) +";") ); // suppress compiler warnings diff --git a/mql40/libraries/rsfHistory1.mq4 b/mql40/libraries/rsfHistory1.mq4 index c575bed22..362fb9397 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". @@ -428,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; @@ -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) { @@ -1316,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) { @@ -1338,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 8a9c511ef..d6f1823e8 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". @@ -428,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; @@ -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) { @@ -1316,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) { @@ -1338,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 49ae0a38d..e703f8d15 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". @@ -428,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; @@ -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) { @@ -1316,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) { @@ -1338,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]; 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)); } 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 diff --git a/templates4/12 ZigZag(50).tpl b/templates4/12 ZigZag(50).tpl index df256a2ef..3e3928394 100644 --- a/templates4/12 ZigZag(50).tpl +++ b/templates4/12 ZigZag(50).tpl @@ -137,36 +137,8 @@ Signal.onBreakout=0 Sound.onChannelWidening=0 -style_2=2 -style_3=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_2=2 -style_3=2 -color_6=4294967295 -color_7=4294967295 +style_0=2 +style_1=2 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/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 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 diff --git a/templates4/31 XARD M1.tpl b/templates4/31 XARD M1.tpl index ac104c9b3..43a2c75b7 100644 --- a/templates4/31 XARD M1.tpl +++ b/templates4/31 XARD M1.tpl @@ -362,11 +362,11 @@ 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_2=2 -style_3=2 +style_0=2 +style_1=2 show_data=1 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/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