文章 "在 MetaTrader 5 中交易的可视评估和调整"

 

新文章 在 MetaTrader 5 中交易的可视评估和调整已发布:

策略测试器允许您所做的不光是优化交易机器人的参数。我将展示如何在事后评估您账户的交易历史,并通过在测试器中更改持仓的止损来调整您的交易。

我们想象一种情况:在某些账户上,使用不同的 EA 针对各种金融产品或多或少地进行活跃交易,在某些情况下,甚至要手动。现在,一段时间后,我们打算看看所有这些工作的成果。自然,我们能在终端中按下 Alt+E 组合键来查看标准交易报告。我们还能将成交图标加载到图表上,查看我们持仓的入场和离场时间。但如果我们打算看看交易的动态,如何处、以及如何开仓和平仓的,该怎么办呢?我们能够分别审查每个品种,也可以一次查看所有,包括开仓和平仓、下止损单的价位、以及它们的规模是否合理。如果我们问自己一个问题,“如果这样...会发生什么”。(此处有很多选择 — 不同的止损、运用不同的算法和准则、使用持仓尾随、或将止损移动到盈亏平衡点、等等。然后测试我们所有的“如果”,得到清晰、直观的结果。交易或许如何变化,如果...

事实证明,解决这种问题的一切都已就位。所有我们要做的就是将账户历史记录加载到一个文件当中 — 所有已完结的成交 — 然后在策略测试器中运行一个 EA,其从文件中读取成交,并在客户端的策略测试器中开仓/平仓。依靠这样的 EA,我们能够往其中添加代码来更改持仓离场的条件,并比较交易如何变化,以及会发生什么,如果...

这能为我们起什么作用?有另一款的工具,能够在账户上寻找运行了一段时间交易的最佳结果,并进行调整。可视测试令我们能够动态查看特定金融产品的持仓是否正确开仓、是否在正确的时间平仓等等。最重要的是,新算法能被简单地将添加到 EA 的代码之中,测试、获得结果,并调整正在该帐户内工作的 EA。


作者:Artyom Trishkin

 

我的方法略有不同。由于每个机器人都会写下交易日志,因此只需编写一个脚本即可。当我在符号图表上运行它时,它会询问机器人的编号(魔法),然后从名称中包含该符号和编号的文件中将交易加载到图表上--全部或仅多头/仅空头(将它们分开可能很有用)。这种方法允许使用净额交易,这对我来说非常重要。甚至还可以使用测试模式下的机器人交易--不进行交易,只是模拟、计算并将结果记录到文件中。

 
非常感谢这篇文章--我一定会进行测试和观察....。
我只需要代码片段来记录结构中的交易值和指标。
 
//+------------------------------------------------------------------+
//|| 交易结构。用于创建历史交易文件。
//+------------------------------------------------------------------+
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));
  }


我自己在编写代码时还远远没有达到楷模的水平。不过,我们还是应该更理智一些。

 
fxsaber #:
  • 如果元素是一个结构,它应该自填充。

又来了。

//+------------------------------------------------------------------+
//| 返回交易描述|
//+------------------------------------------------------------------+
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)));
  }

为什么结构不能自己打印?

 
fxsaber #:
如果一个元素是一个结构,它应该是自填的。

请显示标准的自出版和自填充结构。

编写这些代码是为了简化理解。

我们的目标不是把所有东西都包装成一个无法阅读的单行怪胎,即使这样做也行得通。

撰写文章的目的是尽可能清晰地传达本质,而不是代码编写的高超技巧。对于高超的代码编写,我们有专门的主题,您可以参与其中。它们很有用。

文章的目的则不同。

 
Artyom Trishkin #:

请展示标准的自印和自填结构。

如果我们谈论的是 MQ 结构,那么 MQL5 中就没有这些结构。作者不可能知道什么人需要它们。因此,MQL5 中只有基础结构和从基础结构继承的可能性,以便为标准结构赋予用户所需的功能。

编写这些代码是为了简化理解。

我们没有必要把所有东西都打包成一行行无法阅读的怪东西,即使这样做是可行的。

我不建议这样做。

撰写文章的目的是尽可能清晰地传达本质,而不是代码编写的高超技巧。我们有专门的线程来讨论高超的代码,你也可以参与其中。它们很有用。

文章的目的则不同。

美德是教科书的作者。我赞成逻辑性。


如果一个物体可以在没有帮助的情况下自己完成某件事情,那么它就应该去做,而不是替别人做。在代码编写的架构中,应该有一个最小的层次结构。在你的代码中,你只建议用一个结构数组来代替多个独立的事务属性数组。那么,你几乎没有使用任何结构(读/写和等价)。使用事务属性的程序风格也是如此。

 
fxsaber #:

如果我们谈论的是 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);
}
 
fxsaber #:
但是,为什么不换一种写法呢?

这篇文章针对的是培训水平较低的读者。它旨在覆盖广大读者。那些经验丰富的老手不需要这些文章。他们有自己的小胡子 )

 
Artyom Trishkin #:

这篇文章针对的是培训水平较低的读者。为了让更多的读者阅读。

交易、自动交易系统和交易策略测试论坛

MetaTrader 5 策略测试器:错误、bug、改进建议

fxsaber, 2019.09.06 15:45

您忽略了一个很好的功能

MqlTick tiks[];

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