项目可协助创建可盈利的交易机器人! 或至少,看似可以

15 六月 2020, 11:20
MetaQuotes
1
1 811

交易机器人的创建总是从一个小文件开始,然后随着您实现更多其他函数和自定义对象,文件会逐渐增长。 大多数 MQL5 程序员采用包含文件(MQH)来应对此问题。 然而,有一个更好的解决方案:在一个项目中开始开发任意交易应用程序。 这样做的原因有很多。


项目的益处

项目是带有 MQPROJ 扩展名的单独文件,该文件存储程序设置、编译参数,以及有关项目中涉及的所有文件的信息。 导航器中提供了一个单独的选项卡,可方便地操控项目。 在此选项卡中,所有文件,譬如包含、资源、头文件和其他文件,都按类别排列。

项目的示例

您会看到,项目不仅仅只是在单独目录下排列的一组文件和文件夹。 它允许将复杂的程序分解为平衡结构排列的元素。 所有必需的信息均在掌握:

  • 含有输入参数的设置文件是为了进行测试和优化
  • OpenCL 程序的源代码
  • 图像和声音媒体文件
  • 资源和其他数据

在项目中,程序各部分之间的所有关联都清晰可见,故此您可轻松地在所有用到的文件之间导航。 甚而,多位程序员可以通过内置的 MQL5 存储在一个项目上进行协作。


创建一个项目

利用 MQL5 向导,创建一个新项目作为普通 MQL5 程序。 单击“新建项目”,然后流转所需的步骤:设置程序名称,添加输入参数并指定采用的事件应对程序。 MQL5 向导完成后,将打开一个 MQPROJ 文件。 该文件可管理项目属性。

项目属性>


在此,您可以指定版本、设置程序说明、添加图标并管理附加选项:

  1. 最大程度优化 — 优化 EX5 可执行文件,从而获得最佳性能。 如果禁用该选项,则源代码的编译可以更快地完成,但生成的 EX5 文件运行速度会慢得多。
  2. 检查浮点除法器 — 检查除法运算中 double 和 float 类型的实数是否不等于零。 如果禁用该选项,则操作速度会更高。 不过,您应该对自己的代码完全有信心。
  3. 利用测试器优化缓存 — 默认情况下,测试器处于启用状态,因此测试器将所有已通过测试的结果保存到优化缓存。 该数据在重新计算时还会用到。 缓存可通过 tester_no_cache 属性禁用。 作为选项,您可以取消所选项目中的相关选项。

如果项目文件被关闭,则可以用“属性”关联菜单中的相应命令将其重新打开。 为了更深入地了解 MQPROJ 文件的内容,您可用“打开”命令以文本格式打开它。 由此,您可以查看项目的内部结构。

{
  "platform"    :"mt5",
  "program_type":"expert",
  "copyright"   :"Copyright 2019, MetaQuotes Software Corp.",
  "link"        :"https:\/\/www.mql5.com",
  "version"     :"1.00",
  "description" :"The mean reversion strategy: the price breaks the channel border outwards and reverts back towards the average. The channel is represented by Bollinger Bands. The Expert Advisor enters the market using limit orders, which can only be opened in the trend direction.",
  "icon"        :"Mean Reversion.ico",
  "optimize"    :"1",
  "fpzerocheck" :"1",
  "tester_no_cache":"0",
  "tester_everytick_calculate":"0",

  "files":
  [
    {
      "path":".\\Mean Reversion.mq5",
      "compile":"true",
      "relative_to_project":"true"
    },
    {
      "path":"MQL5\\Include\\Trade\\Trade.mqh",
      "compile":"false",
      "relative_to_project":"false"
    },
....


    交易规则

    我们应用经典规则:价格触及布林带(bollinger band)则入场。 这是交易策略之一,期望价格返回其平均值。

    基于布林带入场

    入场只用限价单。 附加规则如下:仅追随趋势方向进行交易。 因此,买入限价将在上升趋势的通道下边界处放置。 在下降趋势中,卖出限价将被放置在上边界。

    有很多方法可以确定趋势方向。 我们采用最简单的方法:两个移动平均值的相对位置。 如果快速 EMA 高于慢速 EMA,则定义为上升趋势。 当这些线以相反的方式排列时,可识别为下降趋势。

    利用两个移动平均值判断趋势

    这个简单的规则有一个缺点:行情总是既有上升趋势,亦或下降趋势。 因此,这样的系统在横盘期间会产生很多假入场。 为了避免这种情况,要添加另一条规则,该规则允许在布林带之间的距离足够大时才准予下挂单。 最佳方式应采用相对值来测量通道宽度,而非点数。 可以参考测量点数波动的 ATR 指标判断宽度。

    • 通道宽度低于 k*ATR 表示横盘,因此不允许下单。
    • 如果通道宽度大于 k*ATR,则追随趋势方向,在通道边界处放置限价挂单。

    此处的 k 是需要寻找的某个系数。

    利用 ATR 计算布林通道的宽度


    因此,在项目创建期间,我们需要指定 8 个输入参数来判定交易信号。 智能交易系统将采用固定交易手数,其值应在 InpLot 参数中指定。 另一个不可优化的参数是 InpMagicNumber。 利用它,我们可以指示 EA 仅处理其自己的订单和持仓。

    //--- 通道参数
    input int             InpBBPeriod   =20;           // 布林线指标周期
    input double          InpBBDeviation=2.0;          // 布林带与均线的偏离
    //-- 计算趋势的 EMA 周期 
    input int             InpFastEMA    =12;           // 快速 EMA 周期
    input int             InpSlowEMA    =26;           // 慢速 EMA 周期
    //-- ATR 参数
    input int             InpATRPeriod  =14;           // ATR 周期
    input double          InpATRCoeff   =1.0;          // ATR 横盘判定系数
    //--- 资本管理
    input double          InpLot        =0.1;          // 交易量手数
    //--- 时间帧参数
    input ENUM_TIMEFRAMES InpBBTF       =PERIOD_M15;   // 计算布林带值的时间帧
    input ENUM_TIMEFRAMES InpMATF       =PERIOD_M15;   // 趋势判定的时间帧
    //--- 智能交易系统进行交易业务的标识码
    input long            InpMagicNumber=245600;       // 魔幻数字
    
    

    还添加了 InpBBTF 和 InpMATF 参数,从而避免手动选择判定趋势和通道宽度的时间帧。 在这种情况下,于优化过程中即可发现最佳时间帧的参数值。 EA 可在 M1 时间帧内运行,同时利用来自 M15 的布林带数据,和 M30 的移动平均线。 ATR 未用到任何输入参数,否则此示例将会有许多参数。


    编写函数

    创建项目之后,我们可以继续开发智能交易系统。 以下代码展示了阐述规则的三个主要函数。

    布林通道宽度的计算很简单:从指标缓冲区复制值。

    //+------------------------------------------------------------------+
    //| 获取通道边界的数值                                                  |
    //+------------------------------------------------------------------+
    bool ChannelBoundsCalculate(double &up, double &low)
      {
    //--- 获取布林带指标值 
       double bbup_buffer[];
       double bblow_buffer[];
       if(CopyBuffer(ExtBBHandle, 1, 1, 1, bbup_buffer)==-1)
         {
          PrintFormat("%s: Failed CopyBuffer(ExtBBHandle,0,1,2,bbup_buffer), code=%d", __FILE__, GetLastError());
          return(false);
         }
    
       if((CopyBuffer(ExtBBHandle, 2, 1, 1, bblow_buffer)==-1))
         {
          PrintFormat("%s: Failed CopyBuffer(ExtBBHandle,0,1,2,bblow_buffer), code=%d", __FILE__, GetLastError());
          return(false);
         }
       low=bblow_buffer[0];
       up =bbup_buffer[0];
    //--- 成功
       return(true);
      }
    
    

    横盘判定方法也很简单。 首先,获取通道边界的值,然后计算宽度,再与 ATR 值乘以 InpATRCoeff 系数后的结果值进行比较。

    //+------------------------------------------------------------------+
    //|  如果通道太窄(代表横盘),则返回 true                               |
    //+------------------------------------------------------------------+
    int IsRange()
      {
    //--- 获取最后已完成柱线上的 ATR 值
       double atr_buffer[];
       if(CopyBuffer(ExtATRHandle, 0, 1, 1, atr_buffer)==-1)
         {
          PrintFormat("%s: Failed CopyBuffer(ExtATRHandle,0,1,2,atr_buffer), code=%d", __FILE__, GetLastError());
          return(NO_VALUE);
         }
       double atr=atr_buffer[0];
    //--- 获取通道边界
       if(!ChannelBoundsCalculate(ExtUpChannel, ExtLowChannel))
          return(NO_VALUE);
       ExtChannelRange=ExtUpChannel-ExtLowChannel;
    //--- 如果通道宽度小于 ATR*系数,则为横盘
       if(ExtChannelRange<InpATRCoeff*atr)
          return(true);
    //--- 未检测到横盘
       return(false);
      }
    
    

    从代码中可以看到,有时会返回 NO_VALUE 宏代码,这意味着某些参数计算失败。

    #define NO_VALUE      INT_MAX                      // 计算信号或趋势时,无效数值

    趋势判定函数的代码最长。

    //+------------------------------------------------------------------+
    //| 对于上升趋势,返回 1;对于下降趋势,返回 -1(0 = 无趋势)               |
    //+------------------------------------------------------------------+
    int TrendCalculate()
      {
    //--- 首现,检查横盘 
       int is_range=IsRange();
    //--- 检查结果
       if(is_range==NO_VALUE)
         {
          //--- 如果检查失败,则提前终止,并返回 “无值” 响应
          return(NO_VALUE);
         }
    //--- 横盘期间无法判定方向
       if(is_range==true) // 范围太窄,返回"横盘"
          return(0);
    //--- 获取最后已完成柱线上的 ATR 值
       double atr_buffer[];
       if(CopyBuffer(ExtBBHandle, 0, 1, 1, atr_buffer)==-1)
         {
          PrintFormat("%s: Failed CopyBuffer(ExtATRHandle,0,1,2,atr_buffer), code=%d", __FILE__, GetLastError());
          return(NO_VALUE);
         }
    //--- 在最后已完成的柱线上获取快速 EMA 值
       double fastma_buffer[];
       if(CopyBuffer(ExtFastMAHandle, 0, 1, 1, fastma_buffer)==-1)
         {
          PrintFormat("%s: Failed CopyBuffer(ExtFastMAHandle,0,1,2,fastma_buffer), code=%d", __FILE__, GetLastError());
          return(NO_VALUE);
         }
    //--- 在最后已完成的柱线上获取慢速 EMA 值
       double slowma_buffer[];
       if(CopyBuffer(ExtSlowMAHandle, 0, 1, 1, slowma_buffer)==-1)
         {
          PrintFormat("%s: Failed CopyBuffer(ExtSlowMAHandle,0,1,2,slowma_buffer), code=%d", __FILE__, GetLastError());
          return(NO_VALUE);
         }
    //--- 默认时,趋势未定义
       int trend=0;
    //--- 如果快速 EMA 高于慢速
       if(fastma_buffer[0]>slowma_buffer[0])
          trend=1;   // 上行
    //--- 如果快速 EMA 低于慢速
       if(fastma_buffer[0]<slowma_buffer[0])
          trend=-1;  // 下行
    //--- 返回趋势方向
       return(trend);
      }
    
    

    交易算法的最后一个函数是新柱线判断。 根据逻辑,趋势将在出现新柱线时确定。

    //+------------------------------------------------------------------+
    //| 检查当前时间帧内是否出现新的柱线,                                    |
    //| 以及计算趋势和信号                                                 |
    //+------------------------------------------------------------------+
    bool IsNewBar(int &trend)
      {
    //--- 永久存储函数调用之间的当前柱线开立时间
       static datetime timeopen=0;
    //--- 获取当前柱线的开立时间 
       datetime time=iTime(NULL, InpMATF, 0);
    //--- 如果时间未有变化,则柱线不是新的,因此以 “false” 值退出
       if(time==timeopen)
          return(false);
    //--- 这根柱线是新的,应该计算趋势方向
       trend=TrendCalculate();
    //--- 如果无法获得趋势方向,则退出,并在下次调用里重试
       if(trend==NO_VALUE)
          return(false);
    //--- 所有检查均已成功执行:柱线是新的,且已获得趋势方向
       timeopen=time; //记住当前柱线的开立时间,以便进一步调用。
    //---
       return(true);
      }
    
    

    上面的逻辑允许组织 EA 操作,以便所有的交易操作在整根柱线上仅执行一次。 因此,测试结果不依赖于即时报价的生成模式。

    完整的交易算法在 OnTick() 应对程序中提供:

    • 首先判定新柱线和趋势方向已出现。
    • 如果没有趋势或有开仓,则尝试删除挂单,并退出处理程序。
    • 如果存在方向性趋势,且没有挂单,则尝试在通道边界放置一笔限价挂单。
    • 如果已有挂单,且在新柱线里尚未修改,则尝试将其移动到当前通道边界。
    //+------------------------------------------------------------------+
    //| 智能系统的即时报价应对函                                             |
    //+------------------------------------------------------------------+
    void OnTick()
      {
       static bool order_sent    =false;    // 无法在当前柱线上放置限价挂单
       static bool order_deleted =false;    // 无法在当前柱线上删除限价挂单
       static bool order_modified=false;    // 无法在当前柱线上修改限价挂单
    //--- 如果输入参数无效,则在第一时间停止测试
       if(!ExtInputsValidated)
          TesterStop();
    //--- 检查新柱线和趋势方向的出现
       if(IsNewBar(ExtTrend))
         {
          //--- 将静态变量的值重置为其原始状态
          order_sent    =false;
          order_deleted =false;
          order_modified=false;
         }
    //--- 在当前柱线上创建辅助变量,从而保证仅调用一次检查
       bool order_exist   =OrderExist();
       bool trend_detected=TrendDetected(ExtTrend);
    //--- 如果尚无趋势或已开仓,则删除挂单
       if(!trend_detected || PositionExist())
          if(!order_deleted)
            {
             order_deleted=DeleteLimitOrders();
             //--- 如果订单已成功删除,则此柱线内不再需要其他操作
             if(order_deleted)
               {
                //--- 禁止放置和修改挂单
                order_sent    =true;
                order_modified=true;
                return;
               }
            }
    
    //--- 若有趋势
       if(trend_detected)
         {
          //--- 如果未发现挂单,则在通道边界处下挂单
          if(!order_exist && !order_sent)
            {
             order_sent=SendLimitOrder(ExtTrend);
             if(order_sent)
                order_modified=true;
            }
          //--- 如果在当前柱线上尚未移动挂单,则尝试将挂单移动到通道边界
          if(order_exist && !order_modified)
             order_modified=ModifyLimitOrder(ExtTrend);
         }
    //---
      }
    
    

    智能交易系统的其他交易函数都是标准的。 源代码可在 MetaTrader 5 终端的标准发布软件包中获得。 它们位于 MQL5\Experts\Examples 之下。

    MeanReversion 项目位于导航器中


    优化参数,并添加设置文件

    现在,EA 已准备就绪,我们在策略测试器中搜索最佳参数。 您是否知道测试器提供了若干选项,可利用 Ctr+C 组合轻松将“设置”和“输入”选卡中的数值复制到剪贴板当中? 因此,您可将您的设置提供给其他人,例如经由自由职业板块的聊天提供给客户,而无需将其保存到设置文件之中。 客户可利用 Ctr+V 将数据复制到剪贴板,并粘贴到测试器的“设置”选卡当中。

    保存到设置文件也是一种便捷的方案。 市场上的许多卖家提供了此类文件,因此产品买家可以立即加载相应的参数集,并针对所需的金融工具进行测试或优化 EA。 需要为每种交易的金融工具创建单独的设置文件。 如果平台里存在许多智能交易系统,则在计算机上,此类文件的数量可能会很庞大。 协同项目,您的客户可以立即访问所需的文件,而无需每次更改品种时重复在磁盘上搜索它们。

    此为一个项目如何帮助在 EA 的 EX5 文件中直接添加相应参数集的示例。 选择执行优化的目标品种。 例如,EURUSD。 依据您的需要,设置开始、步进和停止等优化参数,然后启动优化过程。 一旦结束,在“优化”选卡中双击最佳通关递次,然后将该递次的输入参数值插入“参数”选项卡,并运行单次测试。 找到的参数可以保存到设置文件中。 然而,不需要单独提供它。 以清晰的名称保存参数集,例如 EURUSD.set。 这意味着该参数集是用于指定货币对,而不是 GBPJPY。

    将输入保存到设置文件

    针对您的 EA 可能交易的每个品种,重复此操作。 因此,您可能有若干数量的预置文件,比如 9。 将这些文件添加到您的项目里。 创建相应的文件夹 “Settings and files\Set”,从而将它们与源文件分开。 配合项目,您可以维护订单,和正确的文件结构。

    将设置文件添加到项目


    现在,编译项目,并在策略测试器里打开 MeanReversion EA。 在“输入”选项卡上,新的菜单项“从 EA 中加载”会出现在关联菜单里。 可以从此菜单访问所有可用的设置文件。

    从 EA 加载输入参数

    故此,智能交易系统的已编译 EX5 文件是一套含有全部参数集的完整产品。 可立即拿此策略进行测试,而无需为每个所需的品种设置边界和步长。 购买了您的交易机器人的买家和用户,一定会很欣赏这种便利。


    在真实数据上运行策略

    在 2019 年 9 月,MeanReversion 智能交易系统在模拟账户上启动。 目的是实时发现编程和交易错误。 EA 是在投资组合模式下,针对多个品种启动的(这本是优化的最初想法)。 为 EA 租用了一个内置的 VPS,并出于监视目的,在 Many MeanReversion Optimized 基础上创建了一个私人信号。 

    交易结果共计 9 个月

    推出后的第一个月,EA 取得了积极的成果。 随后是连续 5 个月的亏损。 虚拟主机采用自动续租功能,因此 EA 以完全自主的模式运行。 它的交易持续朝着亏光本金方向发展。 然后,在三月份,外汇市场发生了一些变化,EA 突然产生了创纪录的利润。 在接下来的两个月中,结果很矛盾。 曾经的辉煌增长一去不复返。

    按品种针对成交和结果的分析表明,亏损是由三个含日元的货币兑,和 AUDUSD 造成的。 智能交易系统没有展示出令人印象深刻的结果。 无论如何,即便如此简单的交易逻辑,它也已经在投资组合模式下运行了 9 个月,某些品种的亏损由其他货币对的利润所弥补。

    按品种分布

    自启动以来,智能交易系统参数从未修改过,在此期间也未执行其他迁移。 EA 是在 9 个月前编译的,并在内置 VPS 的八个图表上启动。 它仍在运行,没有任何人工干预。 我们甚至不记得为什么只启动了九个设置文件中的八个。 甚而,我们不记得所用的参数。 尽管如此,为教学目的而创建的 MeanReversion 智能交易系统项目仍在运行,截至 2020 年 6 月 10 日,并仍表现出盈利倾向。


    切换到项目,并享受其益处

    项目允许开发人员创建任何复杂度的程序,并在开发过程中协同协作。 与志趣相投的人们一起工作时,您可以更迅速地开发应用程序,相互交流思路和技能,并可提高代码质量。

    在此智能交易系统中采用的交易规则非常简单,但它可作为创建其他众多交易机器人的模板。 替换判断趋势方向、横盘状态、或入场级别和方法的函数(例如,您可以用市价单替代限价挂单)。 如果仅在横盘期内进行交易,也许会获得更好的结果。 另外,EA 缺乏尾随停止、止损和止盈设置。 为了改善 EA,您可以做更多的事情。

    利用 MetaTrader 5 标准发布软件包中的 MeanReversion EA,您可以研究和评估项目的优势。 创建您自己的项目,或将此项目复制到新文件夹中,然后开始实验。 开始启用项目,并自行评估其便利性!

    本文译自 MetaQuotes Software Corp. 撰写的俄文原文
    原文地址: https://www.mql5.com/ru/articles/7863

    最近评论 | 前往讨论 (1)
    q1016979461
    q1016979461 | 18 6月 2020 在 14:25
    有点复杂    而且用的是MT4    没看懂这个代码    能不能在MT4上构建和使用       
    连续前行优化 (第四部分): 优化管理器(自动优化器) 连续前行优化 (第四部分): 优化管理器(自动优化器)

    本文主要目的在于阐述运用我们的应用程序进行操控的机制及其能力。 因此,本文可视为有关如何运用该应用程序的指南。 它涵盖了所有可能的陷阱,以及应用程序用法的细节。

    DoEasy 函数库中的时间序列(第三十五部分):柱线对象和品种时间序列列表 DoEasy 函数库中的时间序列(第三十五部分):柱线对象和品种时间序列列表

    本文开始 DoEasy 函数库的新系列,与创建相关,从而简化和快速进行程序开发。 在当前文章中,我们将为函数库实现访问和操控品种时间序列数据的功能。 我们计划创建柱线(Bar)对象,来存储时间序列的主要和扩展的柱线数据,并将柱线对象置于时间序列列表之中,从而便于对象的搜索和排序。

    预测时间序列(第 2 部分):最小二乘支持向量机(LS-SVM) 预测时间序列(第 2 部分):最小二乘支持向量机(LS-SVM)

    本文交流的是基于支持向量法,预测时间序列算法的理论和实际应用。 它还提议采用 MQL 来实现,并提供了测试指标和智能交易系统。 该技术尚未在 MQL 中实现。 但是首先,我们必须了解相关的数学知识。

    连续前行优化 (第五部分): 自动优化器项目概述和 GUI 的创建 连续前行优化 (第五部分): 自动优化器项目概述和 GUI 的创建

    本文深入讲述在 MetaTrader 5 终端里的前向优化。 在先前的文章中,我们研究了生成和过滤优化报告的方法,并开始分析负责优化过程的应用程序的内部结构。 自动优化器是作为 C# 应用程序实现的,并且拥有自己的图形界面。 第五篇文章专门论述了此图形界面的创建。