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.
//+------------------------------------------------------------------+ //|| 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.
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?
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.
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.
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); }
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)) { // ....
- Free trading apps
- Over 8,000 signals for copying
- Economic news for exploring financial markets
You agree to website policy and terms of use
Check out the new article: Visual assessment and adjustment of trading in MetaTrader 5.
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