Discussing the article: "Visual assessment and adjustment of trading in MetaTrader 5"

 

Check out the new article: Visual assessment and adjustment of trading in MetaTrader 5.

The strategy tester allows you to do more than just optimize your trading robot's parameters. I will show how to evaluate your account's trading history post-factum and make adjustments to your trading in the tester by changing the stop-losses of your open positions.

Let's imagine a situation: on some account, more or less active trading has been conducted for a fairly long time on various instruments using different EAs and, in some cases, even manually. And now, after some time, we want to see the results of all this work. Naturally, we can view standard trading reports in the terminal by pressing the Alt+E key combination. We can also load deal icons onto our chart and see entry and exit times for our positions. But what if we want to see the dynamics of our trading, where and how positions were opened and closed? We can view each symbol separately, or all at once, including the opening and closing of positions, the levels at which stop orders were placed, and whether their size was justified. What if we then ask ourselves the question "what would have happened if..." (and there are many options here — different stops, using different algorithms and criteria, using trailing positions, or moving stops to breakeven, etc.) and then test all of our "ifs" with a clear, visible result. How might trading change if...

It turns out that everything is already in place to solve such issues. All we need to do is load the account history into a file — all completed deals — and then run an EA in the strategy tester that reads deals from the file and opens/closes positions in the client terminal's strategy tester. With such an EA, we can add code to it to change the conditions for exiting positions and compare how the trading changes, and what would have happened if...

How can that be of use for us? Another tool for finding the best results and making adjustments to trading that has been running on an account for some time. Visual testing allows us to dynamically see whether positions on a particular instrument were opened correctly, whether they were closed at the right time, etc. And most importantly, a new algorithm can simply be added to the EA's code, tested, the results obtained, and adjustments made to the EAs working on this account.


Author: Artyom Trishkin

 

I have a slightly different approach. Since each robot writes a log of its trades, it is enough to make a script. When I run it on a symbol chart, it asks for the robot's number (magic) and from the file with this symbol and this number in the name it loads trades to the chart - all or only long/only short (it can be useful to separate them). This approach allows to work with netting, which is fundamentally important for me. And even with robots trading in test mode - without making trades, but only simulating, calculating and recording the results in a file.

 
Thanks a lot for the article - I will definitely test and watch....
I just need code fragments to record values and indicators of transactions in structures.
 
//+------------------------------------------------------------------+
//|| Transaction structure. Used to create a file of history of transactions|
//+------------------------------------------------------------------+
struct SDeal
  {
   ulong             ticket;                 // Deal Ticket
   long              order;                  // Order on the basis of which the deal was opened
   long              pos_id;                 // Position identifier
   long              time_msc;               // Time in milliseconds
   datetime          time;                   // Time.
   double            volume;                 // Volume
   double            price;                  // Price
   double            profit;                 // Profit
   double            commission;             // Commission on the transaction
   double            swap;                   // Accumulated swap at closing
   double            fee;                    // Payment for the transaction, charged immediately after the transaction is made
   double            sl;                     // Stop Loss level
   double            tp;                     // Take Profit level
   ENUM_DEAL_TYPE    type;                   // Type
   ENUM_DEAL_ENTRY   entry;                  // Method of changing the position
   ENUM_DEAL_REASON  reason;                 // Reason or source for conducting the transaction
   long              magic;                  // Expert ID
   int               digits;                 // Digits character
   ushort            symbol[16];             // Symbol
   ushort            comment[64];            // Transaction Commentary
   ushort            external_id[256];       // Deal identifier in the external trading system (at the Exchange)
   
//--- Setting string properties
   bool              SetSymbol(const string deal_symbol)          { return(::StringToShortArray(deal_symbol, symbol)==deal_symbol.Length());                }
   bool              SetComment(const string deal_comment)        { return(::StringToShortArray(deal_comment, comment)==deal_comment.Length());             }
   bool              SetExternalID(const string deal_external_id) { return(::StringToShortArray(deal_external_id, external_id)==deal_external_id.Length()); }
                       
//--- Return string properties
   string            Symbol(void)                                 { return(::ShortArrayToString(symbol));                                                   }
   string            Comment(void)                                { return(::ShortArrayToString(comment));                                                  }
   string            ExternalID(void)                             { return(::ShortArrayToString(external_id));                                              }
  };

//+------------------------------------------------------------------+
//| Saves transactions from history into an array ||
//+------------------------------------------------------------------+
int SaveDealsToArray(SDeal &array[], bool logs=false)
  {
//--- transaction structure
   SDeal deal={};
   
//--- request the history of deals in the interval from the beginning to the current moment 
   if(!HistorySelect(0, TimeCurrent()))
     {
      Print("HistorySelect() failed. Error ", GetLastError());
      return 0;
     }
   
//--- total number of deals in the list 
   int total=HistoryDealsTotal(); 

//--- process each transaction 
   for(int i=0; i<total; i++) 
     { 
      //--- get a ticket of the next deal (the deal is automatically selected to get its properties)
      ulong ticket=HistoryDealGetTicket(i);
      if(ticket==0)
         continue;
      
      //--- save only balance sheet and trade transactions
      ENUM_DEAL_TYPE deal_type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket, DEAL_TYPE);
      if(deal_type!=DEAL_TYPE_BUY && deal_type!=DEAL_TYPE_SELL && deal_type!=DEAL_TYPE_BALANCE)
         continue;
      
      //--- store transaction properties in the structure
      deal.ticket=ticket;
      deal.type=deal_type;
      deal.order=HistoryDealGetInteger(ticket, DEAL_ORDER);
      deal.entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(ticket, DEAL_ENTRY);
      deal.reason=(ENUM_DEAL_REASON)HistoryDealGetInteger(ticket, DEAL_REASON);
      deal.time=(datetime)HistoryDealGetInteger(ticket, DEAL_TIME);
      deal.time_msc=HistoryDealGetInteger(ticket, DEAL_TIME_MSC);
      deal.pos_id=HistoryDealGetInteger(ticket, DEAL_POSITION_ID);
      deal.volume=HistoryDealGetDouble(ticket, DEAL_VOLUME);
      deal.price=HistoryDealGetDouble(ticket, DEAL_PRICE);
      deal.profit=HistoryDealGetDouble(ticket, DEAL_PROFIT);
      deal.commission=HistoryDealGetDouble(ticket, DEAL_COMMISSION);
      deal.swap=HistoryDealGetDouble(ticket, DEAL_SWAP);
      deal.fee=HistoryDealGetDouble(ticket, DEAL_FEE);
      deal.sl=HistoryDealGetDouble(ticket, DEAL_SL);
      deal.tp=HistoryDealGetDouble(ticket, DEAL_TP);
      deal.magic=HistoryDealGetInteger(ticket, DEAL_MAGIC);
      deal.SetSymbol(HistoryDealGetString(ticket, DEAL_SYMBOL));
      deal.SetComment(HistoryDealGetString(ticket, DEAL_COMMENT));
      deal.SetExternalID(HistoryDealGetString(ticket, DEAL_EXTERNAL_ID));
      deal.digits=(int)SymbolInfoInteger(deal.Symbol(), SYMBOL_DIGITS);
      
      //--- increment the array and
      int size=(int)array.Size();
      ResetLastError();
      if(ArrayResize(array, size+1, total)!=size+1)
        {
         Print("ArrayResize() failed. Error ", GetLastError());
         continue;
        }
      //--- store the transaction in the array
      array[size]=deal;
      //--- if enabled, output the description of the saved transaction to the journal
      if(logs)
         DealPrint(deal, i);
     }
//--- return the number of deals stored in the array
   return (int)array.Size();
  }

I don't understand why the architecture logic is ignored.

  • If the array is filled element by element, there should be a function to fill an element.
  • If an element is a structure, it should be filled by itself.
  • If you allocate a reserve space for the array at once, why do you need to perform "resizing" at each filling?


Why not write, for example, like this?

//+------------------------------------------------------------------+
//|| Transaction structure. Used to create a file of history of transactions|
//+------------------------------------------------------------------+
struct SDeal
  {
   ulong             ticket;                 // Deal Ticket
   long              order;                  // Order on the basis of which the deal was opened
   long              pos_id;                 // Position identifier
   long              time_msc;               // Time in milliseconds
   datetime          time;                   // Time.
   double            volume;                 // Volume
   double            price;                  // Price
   double            profit;                 // Profit
   double            commission;             // Commission on the transaction
   double            swap;                   // Accumulated swap at closing
   double            fee;                    // Payment for the transaction, charged immediately after the transaction is made
   double            sl;                     // Stop Loss level
   double            tp;                     // Take Profit level
   ENUM_DEAL_TYPE    type;                   // Type
   ENUM_DEAL_ENTRY   entry;                  // Method of changing the position
   ENUM_DEAL_REASON  reason;                 // Reason or source for conducting the transaction
   long              magic;                  // Expert ID
   int               digits;                 // Digits character
   ushort            symbol[16];             // Symbol
   ushort            comment[64];            // Transaction Commentary
   ushort            external_id[256];       // Deal identifier in the external trading system (at the Exchange)
   
//--- Setting string properties
   bool              SetSymbol(const string deal_symbol)          { return(::StringToShortArray(deal_symbol, symbol)==deal_symbol.Length());                }
   bool              SetComment(const string deal_comment)        { return(::StringToShortArray(deal_comment, comment)==deal_comment.Length());             }
   bool              SetExternalID(const string deal_external_id) { return(::StringToShortArray(deal_external_id, external_id)==deal_external_id.Length()); }
                       
//--- Return string properties
   string            Symbol(void)                                 { return(::ShortArrayToString(symbol));                                                   }
   string            Comment(void)                                { return(::ShortArrayToString(comment));                                                  }
   string            ExternalID(void)                             { return(::ShortArrayToString(external_id));                                              }
   
   bool Set( const ulong _ticket )   
   {
      this.ticket=_ticket;
      this.type=(ENUM_DEAL_TYPE)HistoryDealGetInteger(_ticket, DEAL_TYPE);
      this.order=HistoryDealGetInteger(_ticket, DEAL_ORDER);
      this.entry=(ENUM_DEAL_ENTRY)HistoryDealGetInteger(_ticket, DEAL_ENTRY);
      this.reason=(ENUM_DEAL_REASON)HistoryDealGetInteger(_ticket, DEAL_REASON);
      this.time=(datetime)HistoryDealGetInteger(_ticket, DEAL_TIME);
      this.time_msc=HistoryDealGetInteger(_ticket, DEAL_TIME_MSC);
      this.pos_id=HistoryDealGetInteger(_ticket, DEAL_POSITION_ID);
      this.volume=HistoryDealGetDouble(_ticket, DEAL_VOLUME);
      this.price=HistoryDealGetDouble(_ticket, DEAL_PRICE);
      this.profit=HistoryDealGetDouble(_ticket, DEAL_PROFIT);
      this.commission=HistoryDealGetDouble(_ticket, DEAL_COMMISSION);
      this.swap=HistoryDealGetDouble(_ticket, DEAL_SWAP);
      this.fee=HistoryDealGetDouble(_ticket, DEAL_FEE);
      this.sl=HistoryDealGetDouble(_ticket, DEAL_SL);
      this.tp=HistoryDealGetDouble(_ticket, DEAL_TP);
      this.magic=HistoryDealGetInteger(_ticket, DEAL_MAGIC);
      this.SetSymbol(HistoryDealGetString(_ticket, DEAL_SYMBOL));
      this.SetComment(HistoryDealGetString(_ticket, DEAL_COMMENT));
      this.SetExternalID(HistoryDealGetString(_ticket, DEAL_EXTERNAL_ID));
      this.digits=(int)SymbolInfoInteger(this.Symbol(), SYMBOL_DIGITS);
      
      return((bool)this.time);
   }
  };

bool SetDeal( SDeal &deal, const ulong ticket )
{
  //--- save only balance sheet and trade transactions
  const ENUM_DEAL_TYPE deal_type = (ENUM_DEAL_TYPE)HistoryDealGetInteger(ticket, DEAL_TYPE);

  return((deal_type!=DEAL_TYPE_BUY && deal_type!=DEAL_TYPE_SELL && deal_type!=DEAL_TYPE_BALANCE) && deal.Set(ticket));
}

//+------------------------------------------------------------------+
//| Saves transactions from history into an array ||
//+------------------------------------------------------------------+
int SaveDealsToArray(SDeal &array[], bool logs=false)
  {
   int Amount = 0;
   
//--- request the history of deals in the interval from the beginning to the current moment 
   if(HistorySelect(0, INT_MAX))
   {
  //--- total number of deals in the list 
     const int total=ArrayResize(array, HistoryDealsTotal()); 
  
  //--- process each transaction 
     for(int i=0; i<total; i++) 
       if (SetDeal(array[Amount], HistoryDealGetTicket(i))) 
         Amount++;
   }

//--- return the number of deals stored in the array
   return(ArrayResize(array, Amount));
  }


I myself am far from writing code in an exemplary way. But let's be more sensible somehow.

 
fxsaber #:
  • If the element is a structure, it should self populate.

Here we go again.

//+------------------------------------------------------------------+
//| Returns a description of the transaction|
//+------------------------------------------------------------------+
string DealDescription(SDeal &deal, const int index)
  {
   string indexs=StringFormat("% 5d", index);
   if(deal.type!=DEAL_TYPE_BALANCE)
      return(StringFormat("%s: deal #%I64u %s, type %s, Position #%I64d %s (magic %I64d), Price %.*f at %s, sl %.*f, tp %.*f",
                          indexs, deal.ticket, DealEntryDescription(deal.entry), DealTypeDescription(deal.type),
                          deal.pos_id, deal.Symbol(), deal.magic, deal.digits, deal.price,
                          TimeToString(deal.time, TIME_DATE|TIME_MINUTES|TIME_SECONDS), deal.digits, deal.sl, deal.digits, deal.tp));
   else
      return(StringFormat("%s: deal #%I64u %s, type %s %.2f %s at %s",
                          indexs, deal.ticket, DealEntryDescription(deal.entry), DealTypeDescription(deal.type),
                          deal.profit, AccountInfoString(ACCOUNT_CURRENCY), TimeToString(deal.time)));
  }

Why doesn't the structure print itself?

 
fxsaber #:
If an element is a structure, it should self-fill.

Show the standard self-publishing and self-filling structures, please.

These codes are written to simplify understanding.

There is no goal to wrap everything into an unreadable, one-line monstrosity, even if it works.

Articles are written to convey the essence as clearly as possible, not the virtuosity of code writing. There are special threads for virtuosity, and you participate in them. And they are useful.

Articles have a different purpose.

 
Artyom Trishkin #:

Show the standard self-printing and self-filling structures, please.

If we are talking about MQ structures, they are not available in MQL5. The authors could not know what and who would need them. Therefore, there is only the base and the possibility of inheritance from them to endow standard structures with the functionality required by the user.

These codes are written to simplify understanding.

There is no purpose to wrap everything into an unreadable monstrosity packed in one line, even if it works.

I don't suggest such a thing.

Articles are written to convey the essence as clearly as possible, not the virtuosity of code writing. There are special threads for virtuosity and you participate in them. And they are useful.

Articles have a different purpose.

Virtuosity is the author of the Textbook. I'm in favour of logicality.


If an object can do something with itself without help, it should do it, not for it. There should be a minimal hierarchy in the architecture of code writing. In your code, you only propose to replace several separate arrays of transaction properties with a single array of transaction properties using a structure. Well, you use almost nothing from the structure (reading/writing and equating). The same procedural style of working with transaction properties.

 
fxsaber #:

If we are talking about MQ structures, they are not available in MQL5. The authors could not know what and who would need them. Therefore, there is only the base and the possibility of inheritance from them to endow standard structures with the functionality required by the user.

I do not offer such a thing.

Virtuosity is the author of the Tutorial. I advocate logicality.


If an object can do something with itself without help, it should do it, not for it. There should be a minimal hierarchy in the architecture of code writing. In your code, you only propose to replace several separate arrays of transaction properties with a single array of transaction properties using a structure. Well, you use almost nothing from the structure (reading/writing and equating). The same procedural style of working with transaction properties.

I use the features you suggest in the articles on the library. It is not necessary here.

 
//+------------------------------------------------------------------+
//|| Opens file for writing, returns handle ||
//+------------------------------------------------------------------+
bool FileOpenToWrite(int &handle)
  {
   ResetLastError();
   handle=FileOpen(PATH, FILE_WRITE|FILE_BIN|FILE_COMMON);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: FileOpen() failed. Error %d",__FUNCTION__, GetLastError());
      return false;
     }
//--- successfully
   return true;
  }
//+------------------------------------------------------------------+
//|| Opens file for reading, returns handle |
//+------------------------------------------------------------------+
bool FileOpenToRead(int &handle)
  {
   ResetLastError();
   handle=FileOpen(PATH, FILE_READ|FILE_BIN|FILE_COMMON);
   if(handle==INVALID_HANDLE)
     {
      PrintFormat("%s: FileOpen() failed. Error %d",__FUNCTION__, GetLastError());
      return false;
     }
//--- successfully
   return true;
  }
//+------------------------------------------------------------------+
//| Saves the transaction data from the array || to a file
//+------------------------------------------------------------------+
bool FileWriteDealsFromArray(SDeal &array[], ulong &file_size)
  {
//--- if an empty array is passed - report it and return false
   if(array.Size()==0)
     {
      PrintFormat("%s: Error! Empty deals array passed",__FUNCTION__);
      return false;
     }
     
//--- open the file for writing, get its handle
   int handle=INVALID_HANDLE;
   if(!FileOpenToWrite(handle))
      return false;
   
//--- move the file pointer to the end of the file 
   bool res=true;
   ResetLastError();
   res&=FileSeek(handle, 0, SEEK_END);
   if(!res)
      PrintFormat("%s: FileSeek(SEEK_END) failed. Error %d",__FUNCTION__, GetLastError());
   
//--- write the array data to the end of the file 
   file_size=0;
   res&=(FileWriteArray(handle, array)==array.Size());
   if(!res)
      PrintFormat("%s: FileWriteArray() failed. Error ",__FUNCTION__, GetLastError());
   else
      file_size=FileSize(handle);

//--- close the file 
   FileClose(handle);
   return res;
  }
//+------------------------------------------------------------------+
//| Loads transaction data from the file into the array ||
//+------------------------------------------------------------------+
bool FileReadDealsToArray(SDeal &array[], ulong &file_size)
  {
//--- open the file for reading, get its handle
   int handle=INVALID_HANDLE;
   if(!FileOpenToRead(handle))
      return false;
   
//--- move the file pointer to the end of the file 
   bool res=true;
   ResetLastError();
   
//--- read data from the file into an array
   file_size=0;
// res=(FileReadArray(handle, array)>0);
   res=(ArrayResize(array, FileReadArray(handle, array))>0);
   if(!res)
      PrintFormat("%s: FileWriteArray() failed. Error ",__FUNCTION__, GetLastError());
   else
      file_size=FileSize(handle);

//--- close the file 
   FileClose(handle);
   return res;
  }

There is a frequent error in highlighting and fixing it.

But still, why not write all this code differently?

//+------------------------------------------------------------------+
//| Saves the transaction data from the array || to a file
//+------------------------------------------------------------------+
bool FileWriteDealsFromArray(SDeal &array[], long &file_size)
{
  return((file_size = FileSave(PATH, array, FILE_COMMON) * sizeof(SDeal)) > 0);
}

//+------------------------------------------------------------------+
//| Loads transaction data from the file into the array ||
//+------------------------------------------------------------------+ 
bool FileReadDealsToArray2(SDeal &array[], long &file_size)
{
  return((file_size = ArrayResize(array, (int)FileLoad(PATH, array, FILE_COMMON)) * sizeof(SDeal)) > 0);
}
 
fxsaber #:
But still, why not write all this code differently?

The article is intended for a rather low level of training. It is intended to cover a wide part of readers. Those who are experienced and experienced don't need these articles. They have their own moustaches )

 
Artyom Trishkin #:

The article is intended for a fairly low level of training. In order to reach a broad section of the reading public.

Forum on trading, automated trading systems and testing trading strategies

MetaTrader 5 Strategy Tester: errors, bugs, suggestions for improving its work

fxsaber, 2019.09.06 15:45

A good feature you are ignoring

MqlTick tiks[];

if (FileLoad("deribit1.out.bin", ticks))
{
// ....