Multi-Symbol EAs Strategy Tester code
-
Please add this logic into the base code of fxdreema to allow multi symbol/account management testing EXAMPLE BELOW
https://www.youtube.com/watch?v=ec6-LAtlrOc
This will be extremely beneficial to all users who can then use the strategy tester in MQL5 to test a strategy with management on multiple pairs at the same time showing the effect on the whole account and not just one symbol.
//NOTE 2: Testing using multiple symbols takes longer than testing a single symbol because the EA
// needs to process multiple price data streams, multiple indicators etc.//NOTE 3: Backtesting multiple symbols simultaneously can increase the demand for resources considerably on
// the host PC. Monitor memory to ensure this is not causing paging to occur since this will slow down
// the process considerably. If paging does occur, additional memory may need to be purchased.#include <StdLibErr.mqh>
//INPUTS
input string TradeSymbols = "AUDCAD|AUDJPY|AUDNZD|AUDUSD|EURUSD"; //Symbol(s) or ALL or CURRENT
input int BBandsPeriods = 20; //Bollinger Bands Periods
input double BBandsDeviations = 1.0; //Bollinger Bands Deviations//GENERAL GLOBALS
string AllSymbolsString = "AUDCAD|AUDJPY|AUDNZD|AUDUSD|CADJPY|EURAUD|EURCAD|EURGBP|EURJPY|EURNZD|EURUSD|GBPAUD|GBPCAD|GBPJPY|GBPNZD|GBPUSD|NZDCAD|NZDJPY|NZDUSD|USDCAD|USDCHF|USDJPY";
int NumberOfTradeableSymbols;
string SymbolArray[];
int TicksReceivedCount = 0;//INDICATOR HANDLES
int handle_BollingerBands[];
//Place additional indicator handles here as required
//OPEN TRADE ARRAYS
ulong OpenTradeOrderTicket[]; //To store 'order' ticket for trades
//Place additional trade arrays here as required to assist with open trade managementint OnInit()
{
if(TradeSymbols == "CURRENT") //Override TradeSymbols input variable and use the current chart symbol only
{
NumberOfTradeableSymbols = 1;ArrayResize(SymbolArray, 1); SymbolArray[0] = Symbol(); Print("EA will process ", SymbolArray[0], " only"); } else { string TradeSymbolsToUse = ""; if(TradeSymbols == "ALL") TradeSymbolsToUse = AllSymbolsString; else TradeSymbolsToUse = TradeSymbols; //CONVERT TradeSymbolsToUse TO THE STRING ARRAY SymbolArray NumberOfTradeableSymbols = StringSplit(TradeSymbolsToUse, '|', SymbolArray); Print("EA will process: ", TradeSymbolsToUse); } //RESIZE OPEN TRADE ARRAYS (based on how many symbols are being traded) ResizeCoreArrays(); //RESIZE INDICATOR HANDLE ARRAYS ResizeIndicatorHandleArrays(); Print("All arrays sized to accomodate ", NumberOfTradeableSymbols, " symbols"); //INITIALIZE ARAYS for(int SymbolLoop=0; SymbolLoop < NumberOfTradeableSymbols; SymbolLoop++) OpenTradeOrderTicket[SymbolLoop] = 0; //INSTANTIATE INDICATOR HANDLES if(!SetUpIndicatorHandles()) return(INIT_FAILED); return(INIT_SUCCEEDED);}
void OnDeinit(const int reason)
{
Comment("\n\rMulti-Symbol EA Stopped");
}
void OnTick()
{
TicksReceivedCount++;
string indicatorMetrics = "";//LOOP THROUGH EACH SYMBOL TO CHECK FOR ENTRIES AND EXITS, AND THEN OPEN/CLOSE TRADES AS APPROPRIATE for(int SymbolLoop = 0; SymbolLoop < NumberOfTradeableSymbols; SymbolLoop++) { string CurrentIndicatorValues; //passed by ref below //GET OPEN SIGNAL (BOLLINGER BANDS SIMPLY USED AS AN EXAMPLE) string OpenSignalStatus = GetBBandsOpenSignalStatus(SymbolLoop, CurrentIndicatorValues); StringConcatenate(indicatorMetrics, indicatorMetrics, SymbolArray[SymbolLoop], " | ", CurrentIndicatorValues, " | OPEN_STATUS=", OpenSignalStatus, " | "); //GET CLOSE SIGNAL (BOLLINGER BANDS SIMPLY USED AS AN EXAMPLE) string CloseSignalStatus = GetBBandsCloseSignalStatus(SymbolLoop); StringConcatenate(indicatorMetrics, indicatorMetrics, "CLOSE_STATUS=", CloseSignalStatus, "\n\r"); //PROCESS TRADE OPENS if((OpenSignalStatus == "LONG" || OpenSignalStatus == "SHORT") && OpenTradeOrderTicket[SymbolLoop] == 0) ProcessTradeOpen(SymbolLoop, OpenSignalStatus); //PROCESS TRADE CLOSURES else if((CloseSignalStatus == "CLOSE_LONG" || CloseSignalStatus == "CLOSE_SHORT") && OpenTradeOrderTicket[SymbolLoop] != 0) ProcessTradeClose(SymbolLoop, CloseSignalStatus); } //OUTPUT INFORMATION AND METRICS TO THE CHART (No point wasting time on this code if in the Strategy Tester) if(!MQLInfoInteger(MQL_TESTER)) OutputStatusToChart(indicatorMetrics);}
void ResizeCoreArrays()
{
ArrayResize(OpenTradeOrderTicket, NumberOfTradeableSymbols);
//Add other trade arrays here as required
}void ResizeIndicatorHandleArrays()
{
//Indicator Handles
ArrayResize(handle_BollingerBands, NumberOfTradeableSymbols);
//Add other indicators here as required by your EA
}//SET UP REQUIRED INDICATOR HANDLES (arrays because of multi-symbol capability in EA)
bool SetUpIndicatorHandles()
{
//Bollinger Bands
for(int SymbolLoop=0; SymbolLoop < NumberOfTradeableSymbols; SymbolLoop++)
{
//Reset any previous error codes so that only gets set if problem setting up indicator handle
ResetLastError();handle_BollingerBands[SymbolLoop] = iBands(SymbolArray[SymbolLoop], Period(), BBandsPeriods, 0, BBandsDeviations, PRICE_CLOSE); if(handle_BollingerBands[SymbolLoop] == INVALID_HANDLE) { string outputMessage = ""; if(GetLastError() == 4302) outputMessage = "Symbol needs to be added to the MarketWatch"; else StringConcatenate(outputMessage, "(error code ", GetLastError(), ")"); MessageBox("Failed to create handle of the iBands indicator for " + SymbolArray[SymbolLoop] + "/" + EnumToString(Period()) + "\n\r\n\r" + outputMessage + "\n\r\n\rEA will now terminate."); //Don't proceed return false; } Print("Handle for iBands / ", SymbolArray[SymbolLoop], " / ", EnumToString(Period()), " successfully created"); } //All completed without errors so return true return true;}
string GetBBandsOpenSignalStatus(int SymbolLoop, string& signalDiagnosticMetrics)
{
string CurrentSymbol = SymbolArray[SymbolLoop];//Need to copy values from indicator buffers to local buffers int numValuesNeeded = 3; double bufferUpper[]; double bufferLower[]; bool fillSuccessUpper = tlamCopyBuffer(handle_BollingerBands[SymbolLoop], UPPER_BAND, bufferUpper, numValuesNeeded, CurrentSymbol, "BBANDS"); bool fillSuccessLower = tlamCopyBuffer(handle_BollingerBands[SymbolLoop], LOWER_BAND, bufferLower, numValuesNeeded, CurrentSymbol, "BBANDS"); if(fillSuccessUpper == false || fillSuccessLower == false) return("FILL_ERROR"); //No need to log error here. Already done from tlamCopyBuffer() function double CurrentBBandsUpper = bufferUpper[0]; double CurrentBBandsLower = bufferLower[0]; double CurrentClose = iClose(CurrentSymbol, Period(), 0); //SET METRICS FOR BBANDS WHICH GET RETURNED TO CALLING FUNCTION BY REF FOR OUTPUT TO CHART StringConcatenate(signalDiagnosticMetrics, "UPPER=", DoubleToString(CurrentBBandsUpper, (int)SymbolInfoInteger(CurrentSymbol, SYMBOL_DIGITS)), " | LOWER=", DoubleToString(CurrentBBandsLower, (int)SymbolInfoInteger(CurrentSymbol, SYMBOL_DIGITS)), " | CLOSE=" + DoubleToString(CurrentClose, (int)SymbolInfoInteger(CurrentSymbol, SYMBOL_DIGITS))); //INSERT YOUR OWN ENTRY LOGIC HERE //e.g. //if(CurrentClose > CurrentBBandsUpper) // return("SHORT"); //else if(CurrentClose < CurrentBBandsLower) // return("LONG"); //else return("NO_TRADE");}
string GetBBandsCloseSignalStatus(int SymbolLoop)
{
string CurrentSymbol = SymbolArray[SymbolLoop];//Need to copy values from indicator buffers to local buffers int numValuesNeeded = 3; double bufferUpper[]; double bufferLower[]; bool fillSuccessUpper = tlamCopyBuffer(handle_BollingerBands[SymbolLoop], UPPER_BAND, bufferUpper, numValuesNeeded, CurrentSymbol, "BBANDS"); bool fillSuccessLower = tlamCopyBuffer(handle_BollingerBands[SymbolLoop], LOWER_BAND, bufferLower, numValuesNeeded, CurrentSymbol, "BBANDS"); if(fillSuccessUpper == false || fillSuccessLower == false) return("FILL_ERROR"); //No need to log error here. Already done from tlamCopyBuffer() function double CurrentBBandsUpper = bufferUpper[0]; double CurrentBBandsLower = bufferLower[0]; double CurrentClose = iClose(CurrentSymbol, Period(), 0); //INSERT YOUR OWN ENTRY LOGIC HERE //e.g. //if(CurrentClose < CurrentBBandsLower) // return("CLOSE_SHORT"); //else if(CurrentClose > CurrentBBandsUpper) // return("CLOSE_LONG"); //else return("NO_CLOSE_SIGNAL");}
bool tlamCopyBuffer(int ind_handle, // handle of the indicator
int buffer_num, // for indicators with multiple buffers
double &localArray[], // local array
int numBarsRequired, // number of values to copy
string symbolDescription,
string indDesc)
{int availableBars; bool success = false; int failureCount = 0; //Sometimes a delay in prices coming through can cause failure, so allow 3 attempts while(!success) { availableBars = BarsCalculated(ind_handle); if(availableBars < numBarsRequired) { failureCount++; if(failureCount >= 3) { Print("Failed to calculate sufficient bars in tlamCopyBuffer() after ", failureCount, " attempts (", symbolDescription, "/", indDesc, " - Required=", numBarsRequired, " Available=", availableBars, ")"); return(false); } Print("Attempt ", failureCount, ": Insufficient bars calculated for ", symbolDescription, "/", indDesc, "(Required=", numBarsRequired, " Available=", availableBars, ")"); //Sleep for 0.1s to allow time for price data to become usable Sleep(100); } else { success = true; if(failureCount > 0) //only write success message if previous failures registered Print("Succeeded on attempt ", failureCount+1); } } ResetLastError(); int numAvailableBars = CopyBuffer(ind_handle, buffer_num, 0, numBarsRequired, localArray); if(numAvailableBars != numBarsRequired) { Print("Failed to copy data from indicator with error code ", GetLastError(), ". Bars required = ", numBarsRequired, " but bars copied = ", numAvailableBars); return(false); } //Ensure that elements indexed like in a timeseries (with index 0 being the current, 1 being one bar back in time etc.) ArraySetAsSeries(localArray, true); return(true);}
void ProcessTradeOpen(int SymbolLoop, string TradeDirection)
{
string CurrentSymbol = SymbolArray[SymbolLoop];//INSERT YOUR PRE-CHECKS HERE //SETUP MqlTradeRequest orderRequest and MqlTradeResult orderResult HERE //Ensure that CurrentSymbol is used as the symbol //bool success = OrderSend(orderRequest, orderResult); //CHECK FOR ERRORS AND HANDLE EXCEPTIONS HERE //SET TRADE ARRAY TO PREVENT FUTURE TRADES BEING OPENED UNTIL THIS IS CLOSED //OpenTradeOrderTicket[SymbolLoop] = orderResult.order;}
void ProcessTradeClose(int SymbolLoop, string CloseDirection)
{
string CurrentSymbol = SymbolArray[SymbolLoop];//INCLUSE PRE-CLOSURE CHECKS HERE //SETUP CTrade tradeObject HERE //bool bCloseCheck = tradeObject.PositionClose(OpenTradeOrderTicket[SymbolLoop], 0); //CHECK FOR ERRORS AND HANDLE EXCEPTIONS HERE //IF SUCCESSFUL SET TRADE ARRAY TO 0 TO ALLOW FUTURE TRADES TO BE OPENED //OpenTradeOrderTicket[SymbolLoop] = 0;}
void OutputStatusToChart(string additionalMetrics)
{
//GET GMT OFFSET OF MT5 SERVER
double offsetInHours = (TimeCurrent() - TimeGMT()) / 3600.0;//SYMBOLS BEING TRADED string symbolsText = "SYMBOLS BEING TRADED: "; for(int SymbolLoop=0; SymbolLoop < NumberOfTradeableSymbols; SymbolLoop++) StringConcatenate(symbolsText, symbolsText, " ", SymbolArray[SymbolLoop]); Comment("\n\rMT5 SERVER TIME: ", TimeCurrent(), " (OPERATING AT UTC/GMT", StringFormat("%+.1f", offsetInHours), ")\n\r\n\r", Symbol(), " TICKS RECEIVED: ", TicksReceivedCount, "\n\r\n\r", symbolsText, "\n\r\n\r", additionalMetrics);}
-
@jamesdaly Don't get me wrong but, if you have the code why don't you simply paste it into a 'custom mql code' block yourself?

-
The real reason is im not smart enough to make this code work on all projects.
If you know how to do this please share the project
If the code could be added to the foundations ,then muti testing should always work ,benefiting everyone and saving alot of testing time.
-
@jamesdaly I wish I could do it, my friend! I'm afraid I'm not a programmer.

-
@FXDREEMA could this be added in a future update. So all memebers can benefit from multi symbol testing?
-
It can be done with the blocks that are already in Dreema, just a matter of configuration.
I've used 2 asset classes to generate signals to tell me when to trade a 3rd. -
Hi
from some further reading it seems this would have to be built into fxdreema as even with custom blocks we cannot use the #include function and we need this to enable #include <StdLibErr.mqh>Please consider adding this. Multiple currency back testing is crucial to building portfolio EA's
-
@tipsywisdom you know how to backtest multiple symbols at one time please share your project
-
you dont need custom anything.
I have a tutorial posted recently that includes how I did it.
-
@tipsywisdom i couldn't find your tutorial on multiple pair back testing. please link it in this chat.
i have figured a work around which is to set market for each tick but having some issues with the trade management side not updating each trade with the correct trailing stops and other things. are pulling in from the other pair before updating.
-