Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
e1a2801
wip: implement prototype
rosasurfer Mar 13, 2026
e308659
update Equity Recorder
rosasurfer Mar 14, 2026
ebc8c5a
documentation
rosasurfer Mar 15, 2026
1fd0e88
re-implement offset buffer values
rosasurfer Mar 17, 2026
becc54e
update labels in "Data Window"
rosasurfer Mar 19, 2026
9eb2171
add inputs for tracking of signal performance
rosasurfer Mar 19, 2026
953de6d
rename graphic ids to "dot | thin-ring | ring | thick-ring"
rosasurfer Mar 19, 2026
07cbe56
rename const ZigZag.MODE_TREND to ZigZag.MODE_COMBINED_TREND
rosasurfer Mar 19, 2026
22d312c
rename const MODE_MERGED_TREND to MODE_COMBINED_TREND
rosasurfer Mar 19, 2026
a4da169
rename const MODE_SEMAPHORE_OFFSET to MODE_UNKNOWN_TREND
rosasurfer Mar 19, 2026
faf3970
rename buffer semaphoreOffset[] to unknownTrend[]
rosasurfer Mar 19, 2026
a078f97
rename buffer mergedTrend[] to combinedTrend[]
rosasurfer Mar 19, 2026
5fb2c5a
wip: prepare writing of performance data
rosasurfer Mar 20, 2026
0be1491
update chart templates
rosasurfer Mar 22, 2026
ce2721c
drop obsolete indicator "Signal Performance"
rosasurfer Mar 22, 2026
965cb1a
documentation
rosasurfer Mar 22, 2026
66babe6
wip: working prototype
rosasurfer Mar 22, 2026
a9d51b0
fix PnL calculation
rosasurfer Mar 22, 2026
8cf1fc5
fix PnL update of bar 0 (zero)
rosasurfer Mar 23, 2026
27f593a
calculate and store OHLC of signal performance bars
rosasurfer Mar 23, 2026
583ed96
update processing of OHLC
rosasurfer Mar 24, 2026
aeb720b
update PnL calculation of reversals
rosasurfer Mar 24, 2026
0adadbc
fix missing sound indicator in chart legend
rosasurfer Mar 24, 2026
dbd86ea
update input validation
rosasurfer Mar 24, 2026
d234356
improve handling of channel double crossings
rosasurfer Mar 24, 2026
f7a2679
rename inputs and constants/vars
rosasurfer Mar 24, 2026
830f770
update chart templates
rosasurfer Mar 24, 2026
96856ea
refactor RecalculateVirtualProfit()
rosasurfer Mar 25, 2026
d759804
simplify virtPnL calculation of double-crosses
rosasurfer Mar 25, 2026
350127b
fix virtPnL calculation bugs
rosasurfer Mar 25, 2026
93270ff
update usage of icZigZag()
rosasurfer Mar 25, 2026
c647452
update chart templates
rosasurfer Mar 26, 2026
7adb758
update buffer ZigZag.MODE_COMBINED_TREND and fix usage of icZigZag()
rosasurfer Mar 26, 2026
116d570
remove debug code
rosasurfer Mar 26, 2026
06e257f
update FindSemaphore()
rosasurfer Mar 26, 2026
42dfdcb
disable inactive tracking inputs
rosasurfer Mar 26, 2026
35b49b3
document bar types
rosasurfer Mar 26, 2026
5da23d2
bugfix in FindSemaphore()
rosasurfer Mar 26, 2026
997276c
move adjustment of recorder.priceBase to UpdateVirtualProfit()
rosasurfer Mar 26, 2026
7307060
fix bit-wise precedence bug
rosasurfer Mar 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
123 changes: 82 additions & 41 deletions mql40/experts/ZigZag EA.mq4
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -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));
}
Expand Down Expand Up @@ -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
*
Expand All @@ -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;
Expand All @@ -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];
Expand All @@ -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);
}


Expand Down
23 changes: 12 additions & 11 deletions mql40/include/rsf/MT4Expander.mqh
Original file line number Diff line number Diff line change
@@ -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"

Expand All @@ -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);
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
Expand All @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions mql40/include/rsf/api.mqh
Original file line number Diff line number Diff line change
Expand Up @@ -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);;
Expand Down Expand Up @@ -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[]);;
Expand Down
2 changes: 1 addition & 1 deletion mql40/include/rsf/core/expert.recorder.mqh
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
6 changes: 4 additions & 2 deletions mql40/include/rsf/functions/iBarShiftNext.mqh
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading
Loading