我的方法略有不同。由于每个机器人都会写下交易日志,因此只需编写一个脚本即可。当我在符号图表上运行它时,它会询问机器人的编号(魔法),然后从名称中包含该符号和编号的文件中将交易加载到图表上--全部或仅多头/仅空头(将它们分开可能很有用)。这种方法允许使用净额交易,这对我来说非常重要。甚至还可以使用测试模式下的机器人交易--不进行交易,只是模拟、计算并将结果记录到文件中。
//+------------------------------------------------------------------+ //|| 交易结构。用于创建历史交易文件。 //+------------------------------------------------------------------+ struct SDeal { ulong ticket; // 优惠券 long order; // 交易开始所依据的命令 long pos_id; // 位置标识符 long time_msc; // 时间(毫秒 datetime time; // 时间。 double volume; // 体积 double price; // 价格 double profit; // 利润 double commission; // 交易佣金 double swap; // 期末累积掉期 double fee; // 交易付款,在交易完成后立即收取 double sl; // 止损水平 double tp; // 止盈水平 ENUM_DEAL_TYPE type; // 类型 ENUM_DEAL_ENTRY entry; // 改变位置的方法 ENUM_DEAL_REASON reason; // 进行交易的原因或来源 long magic; // 专家 ID int digits; // 数字字符 ushort symbol[16]; // 符号 ushort comment[64]; // 交易评论 ushort external_id[256]; // 外部交易系统(交易所)中的交易标识符 //--- 设置字符串属性 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()); } //--- 返回字符串属性 string Symbol(void) { return(::ShortArrayToString(symbol)); } string Comment(void) { return(::ShortArrayToString(comment)); } string ExternalID(void) { return(::ShortArrayToString(external_id)); } }; //+------------------------------------------------------------------+ //| 将历史交易保存到数组中 || //+------------------------------------------------------------------+ int SaveDealsToArray(SDeal &array[], bool logs=false) { //--- 交易结构 SDeal deal={}; //----------------------------------------请求查看从开始到当前时刻的间隔期间内的交易历史 if(!HistorySelect(0, TimeCurrent())) { Print("HistorySelect() failed. Error ", GetLastError()); return 0; } //--- 列表中的交易总数 int total=HistoryDealsTotal(); //--- 处理每笔交易 for(int i=0; i<total; i++) { //--- 获取下一笔交易的票据(自动选择交易以获取其属性) ulong ticket=HistoryDealGetTicket(i); if(ticket==0) continue; //--- 仅保存资产负债表和贸易交易 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; //--- 在结构中存储交易属性 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); //--- 增加数组并 int size=(int)array.Size(); ResetLastError(); if(ArrayResize(array, size+1, total)!=size+1) { Print("ArrayResize() failed. Error ", GetLastError()); continue; } //--- 在数组中存储交易 array[size]=deal; //--- 如果启用,将向日记账输出已保存交易的说明 if(logs) DealPrint(deal, i); } //--- 返回存储在数组中的交易数量 return (int)array.Size(); }
我不明白为什么架构逻辑会被忽略。
- 如果数组是逐个元素填充的,那么就应该有一个函数来填充一个元素。
- 如果一个元素是一个结构,那么它应该自己填充。
- 如果一次性为数组分配预留空间,为什么每次填充时都需要执行 "调整大小 "操作?
为什么不这样写呢?
//+------------------------------------------------------------------+ //|| 交易结构。用于创建历史交易文件。 //+------------------------------------------------------------------+ struct SDeal { ulong ticket; // 优惠券 long order; // 交易开始所依据的命令 long pos_id; // 位置标识符 long time_msc; // 时间(毫秒 datetime time; // 时间。 double volume; // 体积 double price; // 价格 double profit; // 利润 double commission; // 交易佣金 double swap; // 期末累积掉期 double fee; // 交易付款,在交易完成后立即收取 double sl; // 止损水平 double tp; // 止盈水平 ENUM_DEAL_TYPE type; // 类型 ENUM_DEAL_ENTRY entry; // 改变位置的方法 ENUM_DEAL_REASON reason; // 进行交易的原因或来源 long magic; // 专家 ID int digits; // 数字字符 ushort symbol[16]; // 符号 ushort comment[64]; // 交易评论 ushort external_id[256]; // 外部交易系统(交易所)中的交易标识符 //--- 设置字符串属性 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()); } //--- 返回字符串属性 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 ) { //--- 仅保存资产负债表和贸易交易 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)); } //+------------------------------------------------------------------+ //| 将历史交易保存到数组中 || //+------------------------------------------------------------------+ int SaveDealsToArray(SDeal &array[], bool logs=false) { int Amount = 0; //----------------------------------------请求查看从开始到当前时刻的间隔期间内的交易历史 if(HistorySelect(0, INT_MAX)) { //--- 列表中的交易总数 const int total=ArrayResize(array, HistoryDealsTotal()); //--- 处理每笔交易 for(int i=0; i<total; i++) if (SetDeal(array[Amount], HistoryDealGetTicket(i))) Amount++; } //--- 返回存储在数组中的交易数量 return(ArrayResize(array, Amount)); }
我自己在编写代码时还远远没有达到楷模的水平。不过,我们还是应该更理智一些。
又来了。
//+------------------------------------------------------------------+ //| 返回交易描述| //+------------------------------------------------------------------+ 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))); }
为什么结构不能自己打印?
如果我们谈论的是 MQ 结构,那么 MQL5 中就没有这些结构。作者不可能知道什么人需要它们。因此,MQL5 中只有基础结构和从基础结构继承的可能性,以便为标准结构赋予用户所需的功能。
编写这些代码是为了简化理解。
我们没有必要把所有东西都打包成一行行无法阅读的怪东西,即使这样做是可行的。
我不建议这样做。
撰写文章的目的是尽可能清晰地传达本质,而不是代码编写的高超技巧。我们有专门的线程来讨论高超的代码,你也可以参与其中。它们很有用。
文章的目的则不同。
美德是教科书的作者。我赞成逻辑性。
如果一个物体可以在没有帮助的情况下自己完成某件事情,那么它就应该去做,而不是替别人做。在代码编写的架构中,应该有一个最小的层次结构。在你的代码中,你只建议用一个结构数组来代替多个独立的事务属性数组。那么,你几乎没有使用任何结构(读/写和等价)。使用事务属性的程序风格也是如此。
如果我们谈论的是 MQ 结构,那么它们在 MQL5 中是不可用的。作者不可能知道什么人需要这些结构。因此,MQL5 中只有基础结构和从基础结构继承的可能性,以便为标准结构赋予用户所需的功能。
我不会提供这样的功能。
美德是《教程》的作者。我主张逻辑性。
如果一个对象可以在没有帮助的情况下自己完成某件事情,那么它就应该去做,而不是替别人做。在代码编写的架构中,应该有一个最小的层次结构。在你的代码中,你只提出用一个结构数组来代替多个独立的事务属性数组。那么,你几乎没有使用任何结构(读/写和等价)。使用事务属性的程序风格也是如此。
我使用了你在有关库的文章中建议的功能。在这里没有必要。
//+------------------------------------------------------------------+ //| 打开文件以供写入,返回句柄 //+------------------------------------------------------------------+ 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; } //--- 成功 return true; } //+------------------------------------------------------------------+ //|| 打开文件供读取,返回句柄 //+------------------------------------------------------------------+ 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; } //--- 成功 return true; } //+------------------------------------------------------------------+ // 从数组中将事务数据保存到文件中 //+------------------------------------------------------------------+ bool FileWriteDealsFromArray(SDeal &array[], ulong &file_size) { //--- 如果传递的是空数组,则报告并返回 false if(array.Size()==0) { PrintFormat("%s: Error! Empty deals array passed",__FUNCTION__); return false; } //--- 打开文件以便写入,获取文件句柄 int handle=INVALID_HANDLE; if(!FileOpenToWrite(handle)) return false; //--- 将文件指针移到文件末尾 bool res=true; ResetLastError(); res&=FileSeek(handle, 0, SEEK_END); if(!res) PrintFormat("%s: FileSeek(SEEK_END) failed. Error %d",__FUNCTION__, GetLastError()); //--- 将数组数据写入文件末尾 file_size=0; res&=(FileWriteArray(handle, array)==array.Size()); if(!res) PrintFormat("%s: FileWriteArray() failed. Error ",__FUNCTION__, GetLastError()); else file_size=FileSize(handle); //--- 关闭文件 FileClose(handle); return res; } //+------------------------------------------------------------------+ //| 将事务数据从文件加载到数组中 //+------------------------------------------------------------------+ bool FileReadDealsToArray(SDeal &array[], ulong &file_size) { //--- 打开文件供读取,获取文件句柄 int handle=INVALID_HANDLE; if(!FileOpenToRead(handle)) return false; //--- 将文件指针移到文件末尾 bool res=true; ResetLastError(); //--- 将数据从文件读入数组 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); //--- 关闭文件 FileClose(handle); return res; }
在突出显示和修正错误方面经常出现错误。
但是,为什么不换一种写法呢?
//+------------------------------------------------------------------+ // 从数组中将事务数据保存到文件中 //+------------------------------------------------------------------+ bool FileWriteDealsFromArray(SDeal &array[], long &file_size) { return((file_size = FileSave(PATH, array, FILE_COMMON) * sizeof(SDeal)) > 0); } //+------------------------------------------------------------------+ //| 将事务数据从文件加载到数组中 //+------------------------------------------------------------------+ bool FileReadDealsToArray2(SDeal &array[], long &file_size) { return((file_size = ArrayResize(array, (int)FileLoad(PATH, array, FILE_COMMON)) * sizeof(SDeal)) > 0); }
MetaTrader 5 策略测试器:错误、bug、改进建议
fxsaber, 2019.09.06 15:45
您忽略了一个很好的功能
MqlTick tiks[]; if (FileLoad("deribit1.out.bin", ticks)) { // ....
新文章 在 MetaTrader 5 中交易的可视评估和调整已发布:
我们想象一种情况:在某些账户上,使用不同的 EA 针对各种金融产品或多或少地进行活跃交易,在某些情况下,甚至要手动。现在,一段时间后,我们打算看看所有这些工作的成果。自然,我们能在终端中按下 Alt+E 组合键来查看标准交易报告。我们还能将成交图标加载到图表上,查看我们持仓的入场和离场时间。但如果我们打算看看交易的动态,如何处、以及如何开仓和平仓的,该怎么办呢?我们能够分别审查每个品种,也可以一次查看所有,包括开仓和平仓、下止损单的价位、以及它们的规模是否合理。如果我们问自己一个问题,“如果这样...会发生什么”。(此处有很多选择 — 不同的止损、运用不同的算法和准则、使用持仓尾随、或将止损移动到盈亏平衡点、等等。然后测试我们所有的“如果”,得到清晰、直观的结果。交易或许如何变化,如果...
事实证明,解决这种问题的一切都已就位。所有我们要做的就是将账户历史记录加载到一个文件当中 — 所有已完结的成交 — 然后在策略测试器中运行一个 EA,其从文件中读取成交,并在客户端的策略测试器中开仓/平仓。依靠这样的 EA,我们能够往其中添加代码来更改持仓离场的条件,并比较交易如何变化,以及会发生什么,如果...
这能为我们起什么作用?有另一款的工具,能够在账户上寻找运行了一段时间交易的最佳结果,并进行调整。可视测试令我们能够动态查看特定金融产品的持仓是否正确开仓、是否在正确的时间平仓等等。最重要的是,新算法能被简单地将添加到 EA 的代码之中,测试、获得结果,并调整正在该帐户内工作的 EA。
作者:Artyom Trishkin