Analyzing Balance/Equity graphs by symbols and EAs' ORDER_MAGIC
Contents
- Setting a task
- 1. Commission. Swap. Profit
- 2. Calculating equity and balance on history
- 2.1. "Open positions" list
- 2.2. Floating profit equation
- 2.3. Avoiding issues: Are all symbols available?
- 2.4. Considering opened and closed positions on history
- 2.5. How to count the floating profit (Equity)
- 3. Integrating the EA to the dialog box (panel)
- 3.1. General view of the dialog box
- 3.2. Interacting with the box
- 3.3. Small adjustment - considering selected magic number
- 4. MFE and MAE distribution graphs
- Conclusion
Setting a task
With the introduction of hedging, MetaTrader 5 provides an excellent opportunity to trade several Expert Advisors (or strategies) on a single trading account simultaneously. The entire trading account can be tracked in the Signals service with the ability to receive the statistical data. However, one important issue remains: How to visualize the contribution of each strategy on the Balance and Equity graphs?
It is not uncommon in trading when one strategy is profitable, while the second one is loss-making. As a result, the overall result may hang around zero. In this case, it is useful to build the Balance and Equity graphs for each trading strategy separately.
1. Commission. Swap. Profit
The total financial trade result is defined by summing the three parameters:
Result=Deal commission +Cumulative swap on close+ Deal profit
These trade properties are received using HistoryDealGetDouble() with the following IDs:
DEAL_COMMISSION |
Deal commission |
double |
DEAL_SWAP |
Cumulative swap on close |
double |
DEAL_PROFIT |
Deal profit |
double |
Example of receiving trade properties from the trading history within a specified time interval in the HistoryDealGetTicket.mq5 script.
The script operation results (excluding trades of DEAL_ENTRY_IN type since they have no financial result):
4: deal #36774600 at 2017.02.15 10:17:50 Entry out, sell vol: 0.01 comm: 0 swap: 0.02 profit: 1.52 NZDUSD.m (order #47802989, position ID 47770449)
...
12: deal #36798157 at 2017.02.15 16:44:17 Entry out, buy vol: 0.01 comm: 0 swap: -0.01 profit: 2.98 EURUSD.m (order #47827771, position ID 47685190)
13: deal #36798161 at 2017.02.15 16:44:17 Entry out, buy vol: 0.01 comm: 0 swap: -0.02 profit: 5.99 EURUSD.m (order #47827785, position ID 47665575)
14: deal #36798176 at 2017.02.15 16:44:17 Entry out, buy vol: 0.01 comm: 0 swap: -0.02 profit: 5.93 EURUSD.m (order #47827805, position ID 47605603)
15: deal #36798185 at 2017.02.15 16:44:18 Entry out, buy vol: 0.01 comm: 0 swap: -0.03 profit: 5.98 EURUSD.m (order #47827821, position ID 47502789)
16: deal #36798196 at 2017.02.15 16:44:18 Entry out, buy vol: 0.01 comm: 0 swap: -0.03 profit: 8.96 EURUSD.m (order #47827832, position ID 47419515)
17: deal #36798203 at 2017.02.15 16:44:18 Entry out, buy vol: 0.01 comm: 0 swap: -0.06 profit: 8.92 EURUSD.m (order #47827835, position ID 47130461)
18: deal #36798212 at 2017.02.15 16:44:19 Entry out, sell vol: 0.01 comm: 0 swap: -0.48 profit: -21.07 EURUSD.m (order #47827845, position ID 46868574)
...
25: deal #36824799 at 2017.02.15 19:57:57 Entry out, sell vol: 0.01 comm: 0 swap: 0 profit: 2.96 NZDUSD.m (order #47855548, position ID 47817757)
26: deal #36824800 at 2017.02.15 19:57:58 Entry out, sell vol: 0.01 comm: 0 swap: 0 profit: 3.01 NZDUSD.m (order #47855549, position ID 47790966)
27: deal #36824801 at 2017.02.15 19:57:58 Entry out, sell vol: 0.01 comm: 0 swap: 0.02 profit: 3.07 NZDUSD.m (order #47855550, position ID 47777495)
28: deal #36824802 at 2017.02.15 19:57:58 Entry out, sell vol: 0.01 comm: 0 swap: 0.02 profit: 3 NZDUSD.m (order #47855551, position ID 47759307)
29: deal #36824803 at 2017.02.15 19:57:59 Entry out, sell vol: 0.01 comm: 0 swap: 0.02 profit: 1.52 NZDUSD.m (order #47855552, position ID 47682775)
...
33: deal #36832775 at 2017.02.16 00:58:41 Entry out, sell vol: 0.01 comm: 0 swap: 0.05 profit: 2.96 NZDUSD.m (order #47863883, position ID 47826616)
34: deal #36832776 at 2017.02.16 00:58:41 Entry out, sell vol: 0.01 comm: 0 swap: 0.05 profit: 3.05 NZDUSD.m (order #47863884, position ID 47803010)
35: deal #36832777 at 2017.02.16 00:58:41 Entry out, sell vol: 0.01 comm: 0 swap: 0.05 profit: 2.98 NZDUSD.m (order #47863885, position ID 47792294)
36: deal #36832778 at 2017.02.16 00:58:42 Entry out, sell vol: 0.01 comm: 0 swap: 0.07 profit: 2.88 NZDUSD.m (order #47863886, position ID 47713741)
...
As we can see, swap and profit can have either "+" or "-" sign. Therefore, addition is used in the total financial result equation for swap and profit.
2. Calculating equity and balance on history
The general operation principle includes forming the list of "Open positions". The trading history is divided into segments 5-15 minutes each (this parameter will be defined more precisely later). Then we should do the following for each segment successively:
- search for market exit trades. If they are detected, we should re-calculate the Balance and Equity graphs' value based on the total financial result of such trades. Correct the "Open positions" list.
- calculate the Equity graph value using open positions.
We need a deal class to consider open positions on a selected segment. Let's implement it in the CHistoryDeal class — in the HistoryDeal.mqh include file:
Method | Value |
---|---|
TicketDeal | Get/set "Deal ticket" property |
PosIDDeal | Get/set "Position ID" property |
SymbolDeal | Get/set "Deal symbol" property |
TypeDeal | Get/set "Deal type" property from the ENUM_DEAL_TYPE enumeration |
EntryDeal | Get/set "Trade direction" property from the ENUM_DEAL_ENTRY enumeration |
VolumeDeal | Get/set "Trade volume property" |
PriceDeal | Get/set "Trade open price" property |
Profit equations are taken from the ENUM_SYMBOL_CALC_MODE enumeration description. While doing so, always keep in mind the currency, in which the profit for the symbol is calculated. The profit currency can be obtained:
- via the CSymbolInfo::CurrencyProfit trading class
- via accessing the SymbolInfoString symbol property:
SymbolInfoString(m_name,SYMBOL_CURRENCY_BASE);
- or by simply opening the symbol specification in the terminal:
Fig. 1. RTS-3.17 specification
2.3. Avoiding issues: Are all symbols available?
Possible issues:
- a symbol from the trading history is missing in the "Market Watch" (search is performed by the common symbol list);
- symbol profit currency cannot be re-calculated into deposit currency. In other words, there is no symbol for re-calculation in the "Market Watch".
To take into account possible errors in the program's (not terminal's!) global variables, we introduce the array for "bad" symbols not present in the "Market Watch", as well as the ones, for which it is impossible to re-calculate the profit currency into a deposit one:
string m_arr_defective_symbols[]; // array for defective symbols
The sequence of passing the check (all stages pass in the SearchDefectiveSymbols function):
- create a temporary (auxiliary) array of all symbols present in the "Market Watch";
- create a temporary (auxiliary) array of all trading history symbols (in the specified segment of dates from ... to ... );
- looking for symbols from the trading history in the "Market Watch". If any of the symbols is not found, it is sent to the list of "bad" symbols;
- symbols from trading history are added (displayed) in the "Market Watch". If the operation fails, the symbol is sent to the list of "bad" symbols;
- get the profit currency from the symbol properties. If the symbol profit currency is different from the deposit one, move to 5.1 sub-step.
- Trying to find a "Market Watch" symbol for re-calculation. The symbol should correspond to "deposit currency"+"profit currency" or "profit currency"+"deposit currency". If the operation fails, the symbol is sent to the list of "bad" symbols
After the "SearchDefectiveSymbols" function is complete, the array with "bad" symbols from the trading history is formed. "Bad" symbols are not used in further calculations (of the Balance and Equity graphs).
2.4. Considering opened and closed positions on history
All balance changes are displayed in the m_arr_balance_equity two-dimensional array.
Request the history within the "from date" and "to date" dates (EA inputs) and divide it by the number of cycles having the duration of "timer (minutes)". The work is done in the "CreateBalanceEquity" function. All trades are written in the "ArrOpenPositions" dynamic pointers array. The subsequent work with the array is adding and removing deals.
Depending on the "Deal direction" parameter — ENUM_DEAL_ENTRY, the Balance and Equity charts are calculated differently. If you want to check the behavior during opening, closing, partial closing or reversing positions on hedging and netting accounts, you can launch the HistorySelect.mq5 script.
- DEAL_ENTRY_IN — market entry. Write a trade to the ArrOpenPositions array
- DEAL_ENTRY_OUT — market exit. Searching in the ArrOpenPositions array for a trade of DEAL_ENTRY_IN type with the same DEAL_POSITION_ID
- if the search returns 'false', no changes are made in the Balance and Equity graphs
- if 'true', verify the volume (in lots)
- if the volumes are the same, remove a trade from the ArrOpenPositions array and make changes in the Balance graph (m_arr_balance_equity array)
- if the trade volume from the ArrOpenPositions array is higher, correct the trade volume in the ArrOpenPositions array and make changes in the Balance graph (the m_arr_balance_equity array)
- if the trade volume from the ArrOpenPositions array is lower, remove a trade from the ArrOpenPositions array and make changes in the Balance graph (the m_arr_balance_equity array)
- DEAL_ENTRY_INOUT — reversal. Search the ArrOpenPositions array for a trade of the DEAL_ENTRY_IN type having the same DEAL_POSITION_ID
- if the search returns 'false', no changes are made in the Balance and Equity graphs
- if 'true', correct the trade volume and type in the ArrOpenPositions array and make changes in the Balance graph (the m_arr_balance_equity array)
- DEAL_ENTRY_OUT_BY — close by an opposite position. Search the ArrOpenPositions array for a trade of the DEAL_ENTRY_IN type having the same DEAL_POSITION_ID
- if the volumes are the same, remove a trade from the ArrOpenPositions array and make changes in the Balance graph (m_arr_balance_equity array)
- if the volumes are different, correct the trade volume in the ArrOpenPositions array and make changes in the Balance graph (m_arr_balance_equity array)
2.5. How to count the floating profit (Equity)
It is necessary to go along the entire ArrOpenPositions array and calculate a floating profit for all trades in the array. If a trade is in the ArrOpenPositions array, we have certain data on each trade with the most important being as follows:
- symbol
- type (BUY or SELL)
- volume
- deal price,
and a final date the trading history is requested for. The only data missing are the symbol prices of the trade as of the final date, up to which the trading history is requested. Prices are received using CopyTicks in CalculationFloatingProfit. As already mentioned, the profit equations are taken from the ENUM_SYMBOL_CALC_MODE enumeration description.
During profiling, it turned out that the CopyTicks method is very time consuming, especially when the CalculationFloatingProfit array contains multiple trades at one symbol. In such cases, the same data are requested several times, since a symbol and a date are the same. We have implemented caching of results (CopyTicks) to accelerate the process:
//--- caching results "CopyTicks" static string arr_name[]; static double arr_ask[]; static double arr_bid[]; static datetime prev_time=0; int number=-1; if(time>prev_time) { prev_time=time; ArrayFree(arr_name); ArrayFree(arr_ask); ArrayFree(arr_bid); } found=false; size=ArraySize(arr_name); if(size>0) for(int i=0;i<size;i++) { if(name==arr_name[i]) { number=i; found=true; break; } } if(found) { ArrayResize(ticks_array,1); ticks_array[0].ask=arr_ask[number]; ticks_array[0].bid=arr_bid[number]; } else { //--- int copy_ticks=-1; int count=0; while(copy_ticks==-1 && count<5) { copy_ticks=CopyTicks(name,ticks_array,COPY_TICKS_INFO,1000*(ulong)time,1); //if(copy_ticks==-1)
Bid and ask results are saved to local arrays: if ticks have already been received for a symbol within that time interval, these ticks are simply taken from the local arrays. This approach accelerates the process 10-15 times.
3. Integrating the EA to the dialog box (panel)
Now that the EA's basics are ready, it is time to make the interface more user-friendly. Considering the article's name "Analyzing Balance/Equity graphs by symbols and EAs' ORDER_MAGIC", it is necessary to provide several filtration features:
- show all/one or several selected symbols;
- display all magic numbers/by one or several selected symbols.
We already have the m_arr_all_trade_symbols array to consider all trade symbols. It is declared on the global program level. Let's introduce yet another array m_arr_all_magics for considering all magic numbers. To do this, upgrade the FillArrayTradeSymbols function: now, the m_arr_all_magics array is to be filled in it as well.
3.1. General view of the dialog box
Fig. 2. General view of the panel
After forming the arrays of "bad" symbols, all trading symbols and magic numbers, the two lists are filled in the panel (elements based on the CComboBox class): the left list is filled with all trade symbols, the right one is filled with all magic numbers. Selecting all symbols and magic numbers comes first in the lists:
Fig. 3. Dropdown lists
The panel EA operation logic is as follows: only after pressing the Start button, the system checks what has been selected in the two dropdown lists. Depending on the selected parameters, the Balance/Equity graphs are plotted in the lists based on the trading history.
I decided that transferring the entire EA code to the panel class (APHDialog.mqh) would take too much time. The alternative solution: introducing the m_ready, m_symbol and m_magic internal variables to the panel class:
//+------------------------------------------------------------------+ //| Class CAPHDialog | //| Usage: main dialog of the Controls application | //+------------------------------------------------------------------+ class CAPHDialog : public CAppDialog { private: CLabel m_label_symbols; // the label object CComboBox m_combo_box_symbols; // the combo box object CLabel m_label_magics; // the label object CComboBox m_combo_box_magics; // the combo box object CButton m_button_start; // the button object //--- bool m_ready; // true -> you can build graphics string m_symbol; ulong m_magic; public:
Decision on the m_ready variable status is made when processing the Start button click:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CAPHDialog::OnClickButtonStart(void) { if(m_combo_box_symbols.Select()!=" " && m_combo_box_magics.Select()!=" ") m_ready=true; else m_ready=false; //Comment(__FUNCTION__+" ButtonStartclick"+"\n"+ // "Symbols: "+"\""+m_combo_box_symbols.Select()+"\""+"\n"+ // "Magic: "+"\""+m_combo_box_magics.Select()+"\""+"\n"+ // "m_ready: "+IntegerToString(m_ready)); }
Note the highlighted string: right after creating and filling the dropdown lists (these are the m_combo_box_symbols and m_combo_box_magics elements in our case), the element with the " " value (space) is set in the lists.
Decision on the m_symbol and m_magic variables states is made when processing the click on the appropriate dropdown list:
//+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CAPHDialog::OnChangeComboBoxSymbols(void) { //Comment(__FUNCTION__+" \""+m_combo_box_symbols.Select()+"\""); if(m_combo_box_symbols.Select()=="All symbols") m_symbol=""; else m_symbol=m_combo_box_symbols.Select(); } //+------------------------------------------------------------------+ //| Event handler | //+------------------------------------------------------------------+ void CAPHDialog::OnChangeComboBoxMagics(void) { //Comment(__FUNCTION__+" \""+m_combo_box_magics.Select()+"\""); if(m_combo_box_magics.Select()=="All magics") m_magic=-1; else m_magic=StringToInteger(m_combo_box_magics.Select()); }
Thus, the three variables m_ready, m_symbol and m_magic are filled when clicking the Start button. Now, all that is left to do is inform the EA that parameters have been selected in the panel. The solution is simple: in the EA, we should start the timer with the interval of three seconds. The timer is to survey the panel. To do this, let's write the CAPHDialog::IsReady method in the panel.
//+------------------------------------------------------------------+ //| On the panel there are chosen parameters | //+------------------------------------------------------------------+ bool CAPHDialog::IsReady(string &symbol,ulong &magic) { if(m_ready) { symbol=m_symbol; magic=m_magic; m_ready=false; return(true); } else return(false); }
In this method, we will write the values of internal variables to the variables passed by reference and reset the m_ready internal variable.
3.3. Small adjustment - considering selected magic number
Trades are selected according to the specified conditions: by a symbol or by all symbols and magic or by all magic numbers in GetHistory:
//--- for all deals for(int i=0;i<deals;i++) { deal_ticket = HistoryDealGetTicket(i); deal_position_ID = HistoryDealGetInteger(deal_ticket,DEAL_POSITION_ID); deal_symbol = HistoryDealGetString(deal_ticket,DEAL_SYMBOL); deal_type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(deal_ticket,DEAL_TYPE); deal_entry = (ENUM_DEAL_ENTRY)HistoryDealGetInteger(deal_ticket,DEAL_ENTRY); deal_volume = HistoryDealGetDouble(deal_ticket,DEAL_VOLUME); deal_price = HistoryDealGetDouble(deal_ticket,DEAL_PRICE); deal_commission = HistoryDealGetDouble(deal_ticket,DEAL_COMMISSION); deal_swap = HistoryDealGetDouble(deal_ticket,DEAL_SWAP); deal_profit = HistoryDealGetDouble(deal_ticket,DEAL_PROFIT); deal_magic = HistoryDealGetInteger(deal_ticket,DEAL_MAGIC); if(sSymbol!="") if(deal_symbol!=sSymbol) continue; if(uMagic!=ULONG_MAX) if(deal_magic!=uMagic) continue; //--- onle BUY or SELL
Note that for the uMagic variable, ULONG_MAX means "all magics", while for the sSymbol variable, "" means "All symbols".
Plotting the balance and floating profit based on the trading history (first, for all symbols, then — only for one):
Video
4. MFE and MAE distribution graphs
Maximum profit (MFE) and maximum loss (MAE) values are recorded for each open order during its lifetime. These parameters additionally characterize each closed order using the values of the maximum unrealized potential and maximum permitted risk. MFE/Profit and MAE/Profit distribution graphs display each order as a point with received profit/loss value plotted along the X-axis, while maximum displayed values of potential profit (MFE) and potential loss (MAE) are plotted along the Y-axis.
4.1. MFE and MAE accounting principle
The m_arr_pos_id array for positions accounting and the m_arr_mfe_mae array for considering MFE, total financial result and MAE are declared on the global program level:
long m_arr_pos_id[]; double m_arr_mfe_mae[][3]; // [][0] - mfe, [][1] - final financial result,[][2] - mae
Subsequently, the MFE and MAE distribution point graphs are plotted based on the m_arr_mfe_mae array.
The m_arr_pos_id array for positions accounting and the m_arr_mfe_mae one for considering MFE always have one size in the first dimension — thus, the "i" position (m_arr_pos_id[i]) always has a clear match m_arr_mfe_mae[i][][][]. The sizes of these two arrays are set in GetHistory:
if(deal_symbol=="") DebugBreak(); ArrOpenPositions.Add(HistoryDeal); //--- mfe, mae int size=ArraySize(m_arr_pos_id); ArrayResize(m_arr_pos_id,size+1,10); ArrayResize(m_arr_mfe_mae,size+1,10); m_arr_pos_id[size]=deal_position_ID; // [][0] - mfe, [][1] - final financial result,[][2] - mae m_arr_mfe_mae[size][0]=0.0; m_arr_mfe_mae[size][1]=0.0; m_arr_mfe_mae[size][2]=0.0; continue; } //--- if(deal_entry==DEAL_ENTRY_OUT)
The function that is responsible for recording the maximum profit and loss for each position:
//+------------------------------------------------------------------+ //| Add Result Mfe Mae | //+------------------------------------------------------------------+ void AddResultMfeMae(const long pos_id,const double floating_profit,const double financial_result) { // [][0] - mfe (profit), [][1] - final financial result,[][2] - mae (loss) //--- search pos_id int position=-1; int size=ArraySize(m_arr_pos_id); for(int i=0;i<size;i++) if(m_arr_pos_id[i]==pos_id) { position=i; break; } if(position==-1) return; //--- if(floating_profit==0.0) return; if(floating_profit>0.0) // profit { if(m_arr_mfe_mae[position][0]<floating_profit) m_arr_mfe_mae[position][0]=floating_profit; } else // loss { if(m_arr_mfe_mae[position][2]>floating_profit) m_arr_mfe_mae[position][2]=floating_profit; } m_arr_mfe_mae[position][1]=financial_result; }
All three parameters should be passed. In other words, if our virtual position is still in the ArrOpenPositions list of open positions and its floating profit is -20.2, the call looks as follows:
AddResultMfeMae(pos_id,-20.2,0.0);
if our virtual position is still in the ArrOpenPositions list of open positions and its floating profit is +5.81, the call looks as follows:
AddResultMfeMae(pos_id,5.81,0.0);
if our virtual position is removed from the ArrOpenPositions list of open positions and its total financial result is -3.06, the call looks as follows:
AddResultMfeMae(pos_id,0.0,-3.06);
In other words, if there is a floating profit, the total financial result is 0. If the position is closed, the floating profit is equal to 0.
- DEAL_ENTRY_IN - market entry. Write a trade to the ArrOpenPositions array. Let's create a new element in the m_arr_pos_id and m_arr_mfe_mae arrays
- DEAL_ENTRY_OUT - market exit. Searching in the ArrOpenPositions array for a trade of DEAL_ENTRY_IN type with the same DEAL_POSITION_ID
- if the search returns 'false', no changes are made in the Balance and Equity graphs
- if 'true', verify the volume (in lots)
- if the volumes are the same, remove a trade from the ArrOpenPositions array and make changes in the Balance graph (m_arr_balance_equity). Write [floating profit 0.0][total financial result] to the m_arr_mfe_mae array.
- if the trade volume from the ArrOpenPositions array is higher, correct the trade volume in the ArrOpenPositions array and make changes in the m_arr_balance_equity array (the Balance graph). Write [floating profit 0.0][total financial result] to the m_arr_mfe_mae array.
- if the trade volume from the ArrOpenPositions array is lower, remove the trade from the ArrOpenPositions array and make changes in the m_arr_balance_equity array (the Balance graph). Write [floating profit 0.0][total financial result] to the m_arr_mfe_mae array.
- DEAL_ENTRY_INOUT - reversal. Search the ArrOpenPositions array for a trade of the DEAL_ENTRY_IN type having the same DEAL_POSITION_ID
- if the search returns 'false', no changes are made in the Balance and Equity graphs
- if 'true', correct the trade volume and type in the ArrOpenPositions array and make changes in the m_arr_balance_equity array (the Balance chart). Write [floating profit 0.0][total financial result] to the m_arr_mfe_mae array.
- DEAL_ENTRY_OUT_BY - close by an opposite position. Search the ArrOpenPositions array for a trade of the DEAL_ENTRY_IN type having the same DEAL_POSITION_ID
- if the volumes are the same, remove a trade from the ArrOpenPositions array and make changes in the Balance graph (m_arr_balance_equity). Write [floating profit 0.0][total financial result] to the m_arr_mfe_mae array.
- if the volumes are different, correct the trade volume in the ArrOpenPositions array and make changes in the m_arr_balance_equity array (the Balance graph). Write [floating profit 0.0][total financial result] to the m_arr_mfe_mae array.
Besides, in GetHistory, write [floating profit][total financial result 0.0] to the m_arr_mfe_mae array for each position if you want to re-calculate a floating profit.
In order to display both Balance/Equity and MFE/MAE graphs, the box should be slightly changed:
Fig. 4. Changed box
MFE (maximum profit) and MAE (maximum loss) are built using the two coordinates: X - position's total financial result, Y - MFE or MAE value, respectively.
Fig. 5. MFE
Fig. 6. MAE
Conclusion
Now, you can see the balance and equity statistics per each symbol and magic number on hedge accounts when applying several EAs simultaneously. In other words, you are able to visually define the contribution of each EA (ORDER_MAGIC) to the overall balance, as well as each EA's drawdown.
Programs used in the article:
# |
Name |
Type |
Description |
---|---|---|---|
1 |
HistoryDeal.mqh | Library | Class for considering open positions on a selected segment |
2 |
HistoryDealGetTicket.mq5 | EA | Example of receiving trade properties from the trading history within a specified time interval |
3 |
APHDialog.mqh | Library | EA dialog box class |
4 |
Accounting_positions_on_history.mq5 | EA | The main EA described in the article |
5 |
MQL5.zip | Archive | The archive containing the main EA and its include files. |
Translated from Russian by MetaQuotes Ltd.
Original article: https://www.mql5.com/ru/articles/3046
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use