English Русский Español Deutsch 日本語 Português
preview
构建K线图趋势约束模型(第8部分):EA的开发(一)

构建K线图趋势约束模型(第8部分):EA的开发(一)

MetaTrader 5测试者 |
912 2
Clemence Benjamin
Clemence Benjamin

内容:


引言

MetaEditor软件包含一个编译器,能够有效地管理在分析尝试中检测到的错误。这个工具帮助我发现了为什么之前的版本未能按预期显示风险-回报矩形。尽管程序成功编译,但问题并不在于代码本身。相反,问题在于在历史K线图的范围内没有任何内容被显示,这主要是由于某些特定的技术细节导致。
  1. 历史K线图的值默认设置过高,为5000根K线。
  2. 在单个程序中使用多个缓冲区会增加计算的复杂性,这可能会减缓指标图表窗口的显示速度。

    在简要讨论我们如何解决遇到的问题之后,我们将转向本文的主要目标:开发基于改进的趋势约束指标的EA。以下展示了一个独立脚本运行的图像,它成功解决了我们最初希望通过主指标解决的问题。

    为移动平均线交叉绘制的风险-回报比率

    使用矩形自动绘制的风险和回报矩形


    之前绘制风险和回报矩形时所遇挑战的解决方案

    为解决指标程序中的挑战:

    1. 我们将回顾周期从5000根K线减少到1000根,从而显著减少了需要计算的数据量。
    2. 我们通过创建一个独立脚本作为工具集的一部分,来减轻程序的工作量。该脚本专门检查指标中由缓冲区6和缓冲区7处理的条件。一旦这些条件满足,脚本就会绘制必要的风险-回报矩形,并放置带有入场价格、止损和获利价格标签的线条。然而,需要注意的是,该脚本只执行一次性任务,并不持续运行。用户需要手动将脚本添加到图表中,才能通过绘制的图形和价格标记可视化交易水平。

    以下是脚本程序启动的图像:

    趋势约束风险-回报(R-R)脚本启动

    趋势约束风险-回报(R-R)脚本:在移动平均线交叉时绘制风险和回报矩形

    通过将这一功能独立出来,我们确保了指标能够平稳运行,避免了计算机或交易终端出现卡顿。将风险-回报矩形纳入其中并标记出场水平,使交易者能够提前直观地评估交易方向和目标,从而能够在没有EA的情况下进行手动交易。该脚本程序具备所需的逻辑,运行完美无缺。以下是我们的完整脚本程序。

    //+------------------------------------------------------------------+
    //|                                        Trend Constraint R-R.mq5  |
    //|                                                  Script program  |
    //+------------------------------------------------------------------+
    #property strict
    #property script_show_inputs
    #property copyright "2024 Clemence Benjamin"
    #property version "1.00"
    #property link "https://www.mql5.com/en/users/billionaire2024/seller"
    #property description "A script program for drawing risk and rewars rectangles based on Moving Averaage crossover."
    
    
    
    //--- input parameters
    input int FastMAPeriod = 14;
    input int SlowMAPeriod = 50;
    input double RiskHeightPoints = 5000.0; // Default height of the risk rectangle in points
    input double RewardHeightPoints = 15000.0; // Default height of the reward rectangle in points
    input color RiskColor = clrIndianRed; // Default risk color
    input color RewardColor = clrSpringGreen; // Default reward color
    input int MaxBars = 500; // Maximum bars to process
    input int RectangleWidth = 10; // Width of the rectangle in bars
    input bool FillRectangles = true; // Option to fill rectangles
    input int FillTransparency = 128; // Transparency level (0-255), 128 is 50% transparency
    
    //+------------------------------------------------------------------+
    //| Script program start function                                    |
    //+------------------------------------------------------------------+
    void OnStart()
    {
      //--- delete existing rectangles and lines
      DeleteExistingObjects();
    
      //--- declare and initialize variables
      int i, limit;
      double FastMA[], SlowMA[];
      double closePrice, riskLevel, rewardLevel;
    
      //--- calculate moving averages
      if (iMA(NULL, 0, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE) < 0 || iMA(NULL, 0, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE) < 0)
      {
        Print("Error in calculating moving averages.");
        return;
      }
    
      ArraySetAsSeries(FastMA, true);
      ArraySetAsSeries(SlowMA, true);
    
      CopyBuffer(iMA(NULL, 0, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE), 0, 0, MaxBars, FastMA);
      CopyBuffer(iMA(NULL, 0, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE), 0, 0, MaxBars, SlowMA);
    
      limit = MathMin(ArraySize(FastMA), ArraySize(SlowMA));
    
      for (i = 1; i < limit - 1; i++)
      {
        //--- check for crossover
        if (FastMA[i] > SlowMA[i] && FastMA[i - 1] <= SlowMA[i - 1])
        {
          //--- long position entry point (bullish crossover)
          closePrice = iClose(NULL, 0, i);
          riskLevel = closePrice + RiskHeightPoints * Point();
          rewardLevel = closePrice - RewardHeightPoints * Point();
    
          //--- draw risk rectangle
          DrawRectangle("Risk_" + IntegerToString(i), i, closePrice, i - RectangleWidth, riskLevel, RiskColor);
    
          //--- draw reward rectangle
          DrawRectangle("Reward_" + IntegerToString(i), i, closePrice, i - RectangleWidth, rewardLevel, RewardColor);
    
          //--- draw entry, stop loss, and take profit lines
          DrawPriceLine("Entry_" + IntegerToString(i), i, closePrice, clrBlue, "Entry: " + DoubleToString(closePrice, _Digits));
          DrawPriceLine("StopLoss_" + IntegerToString(i), i, riskLevel, clrRed, "Stop Loss: " + DoubleToString(riskLevel, _Digits));
          DrawPriceLine("TakeProfit_" + IntegerToString(i), i, rewardLevel, clrGreen, "Take Profit: " + DoubleToString(rewardLevel, _Digits));
        }
        else if (FastMA[i] < SlowMA[i] && FastMA[i - 1] >= SlowMA[i - 1])
        {
          //--- short position entry point (bearish crossover)
          closePrice = iClose(NULL, 0, i);
          riskLevel = closePrice - RiskHeightPoints * Point();
          rewardLevel = closePrice + RewardHeightPoints * Point();
    
          //--- draw risk rectangle
          DrawRectangle("Risk_" + IntegerToString(i), i, closePrice, i - RectangleWidth, riskLevel, RiskColor);
    
          //--- draw reward rectangle
          DrawRectangle("Reward_" + IntegerToString(i), i, closePrice, i - RectangleWidth, rewardLevel, RewardColor);
    
          //--- draw entry, stop loss, and take profit lines
          DrawPriceLine("Entry_" + IntegerToString(i), i, closePrice, clrBlue, "Entry: " + DoubleToString(closePrice, _Digits));
          DrawPriceLine("StopLoss_" + IntegerToString(i), i, riskLevel, clrRed, "Stop Loss: " + DoubleToString(riskLevel, _Digits));
          DrawPriceLine("TakeProfit_" + IntegerToString(i), i, rewardLevel, clrGreen, "Take Profit: " + DoubleToString(rewardLevel, _Digits));
        }
      }
    }
    
    //+------------------------------------------------------------------+
    //| Function to delete existing rectangles and lines                 |
    //+------------------------------------------------------------------+
    void DeleteExistingObjects()
    {
      int totalObjects = ObjectsTotal(0, 0, -1);
      for (int i = totalObjects - 1; i >= 0; i--)
      {
        string name = ObjectName(0, i, 0, -1);
        if (StringFind(name, "Risk_") >= 0 || StringFind(name, "Reward_") >= 0 ||
            StringFind(name, "Entry_") >= 0 || StringFind(name, "StopLoss_") >= 0 ||
            StringFind(name, "TakeProfit_") >= 0)
        {
          ObjectDelete(0, name);
        }
      }
    }
    
    //+------------------------------------------------------------------+
    //| Function to draw rectangles                                      |
    //+------------------------------------------------------------------+
    void DrawRectangle(string name, int startBar, double startPrice, int endBar, double endPrice, color rectColor)
    {
      if (ObjectFind(0, name) >= 0)
        ObjectDelete(0, name);
    
      datetime startTime = iTime(NULL, 0, startBar);
      datetime endTime = (endBar < 0) ? (TimeCurrent() + (PeriodSeconds() * (-endBar))) : iTime(NULL, 0, endBar);
    
      if (!ObjectCreate(0, name, OBJ_RECTANGLE, 0, startTime, startPrice, endTime, endPrice))
        Print("Failed to create rectangle: ", name);
    
      // Set the color with transparency (alpha value)
      int alphaValue = FillTransparency; // Adjust transparency level (0-255)
      color fillColor = rectColor & 0x00FFFFFF | (alphaValue << 24); // Combine alpha with RGB
    
      ObjectSetInteger(0, name, OBJPROP_COLOR, rectColor);
      ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
      ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
      ObjectSetInteger(0, name, OBJPROP_BACK, true); // Set to background
    
      if (FillRectangles)
      {
        ObjectSetInteger(0, name, OBJPROP_COLOR, fillColor); // Fill color with transparency
      }
      else
      {
        ObjectSetInteger(0, name, OBJPROP_COLOR, rectColor & 0x00FFFFFF); // No fill color
      }
    }
    
    //+------------------------------------------------------------------+
    //| Function to draw price lines                                     |
    //+------------------------------------------------------------------+
    void DrawPriceLine(string name, int barIndex, double price, color lineColor, string labelText)
    {
      datetime time = iTime(NULL, 0, barIndex);
      datetime endTime = (barIndex - 2 * RectangleWidth < 0) ? (TimeCurrent() + (PeriodSeconds() * (-barIndex - 2 * RectangleWidth))) : iTime(NULL, 0, barIndex - 2 * RectangleWidth); // Extend line to the right
    
      if (!ObjectCreate(0, name, OBJ_TREND, 0, time, price, endTime, price))
        Print("Failed to create price line: ", name);
    
      ObjectSetInteger(0, name, OBJPROP_COLOR, lineColor);
      ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
      ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
      ObjectSetInteger(0, name, OBJPROP_BACK, true); // Set to background
    
      // Create text label
      string labelName = name + "_Label";
      if (ObjectFind(0, labelName) >= 0)
        ObjectDelete(0, labelName);
    
      if (!ObjectCreate(0, labelName, OBJ_TEXT, 0, endTime, price))
        Print("Failed to create label: ", labelName);
    
      ObjectSetInteger(0, labelName, OBJPROP_COLOR, lineColor);
      ObjectSetInteger(0, labelName, OBJPROP_ANCHOR, ANCHOR_LEFT);
      ObjectSetString(0, labelName, OBJPROP_TEXT, labelText);
      ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 10);
      ObjectSetInteger(0, labelName, OBJPROP_CORNER, CORNER_RIGHT_UPPER);
      ObjectSetInteger(0, labelName, OBJPROP_XOFFSET, 5);
      ObjectSetInteger(0, labelName, OBJPROP_YOFFSET, 0);
    }

    让我们在继续开发专EA之前,深入讨论一下该脚本:

    • 我们通过定义输入参数,允许用户自定义交易策略的关键点。我们提供了调整快速和慢速移动平均线周期的选项,设置风险和回报矩形的尺寸和颜色,确定要处理的最大K线数,以及选择是否用颜色填充矩形。这种设置使脚本能够根据用户的偏好适应不同的交易策略,这也构成了我们程序的输入参数部分:

    //---- Input Parameters
    input int FastMAPeriod = 14;
    input int SlowMAPeriod = 50;
    input double RiskHeightPoints = 5000.0;
    input double RewardHeightPoints = 15000.0;
    input color RiskColor = clrIndianRed;
    input color RewardColor = clrSpringGreen;
    input int MaxBars = 500;
    input int RectangleWidth = 10;
    input bool FillRectangles = true;
    input int FillTransparency = 128;

    • 在 (OnStart) 函数中,我们通过编程让脚本首先删除任何已存在的风险/回报矩形和价格线,从而确保图表保持整洁。随后,我们利用 (iMA) 函数计算快速和慢速移动平均线,并将这些值存储在数组中以便进一步处理。当脚本遍历图表上的K线时,我们设定了检测看涨交叉的条件,即快速移动平均线穿过并高于慢速移动平均线。当这些条件满足时,脚本会计算入场价格、风险水平(止损)和回报水平(获利)。然后,它会在图表上绘制矩形和价格线,有效地标记这些关键的交易水平,我们将在下面进一步解释相关的子代码片段:

    //----Onstart Function
    void OnStart()
    {
      //--- delete existing rectangles and lines
      DeleteExistingObjects();
    
      //--- declare and initialize variables
      int i, limit;
      double FastMA[], SlowMA[];
      double closePrice, riskLevel, rewardLevel;
    
      //--- calculate moving averages
      if (iMA(NULL, 0, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE) < 0 || iMA(NULL, 0, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE) < 0)
      {
        Print("Error in calculating moving averages.");
        return;
      }
    

    • 为了保持图表的清晰度,我们开发了“删除现有对象”功能,脚本通过此功能移除所有与交易信号相关的先前绘制的对象。通过检查图表上所有对象的名称,脚本确保仅显示最新且相关的信息,从而使图表保持专注且避免杂乱:

    //---- DeleteAllExistingObjects Function
    void DeleteExistingObjects()
    {
      int totalObjects = ObjectsTotal(0, 0, -1);
      for (int i = totalObjects - 1; i >= 0; i--)
      {
        string name = ObjectName(0, i, 0, -1);
        if (StringFind(name, "Risk_") >= 0 || StringFind(name, "Reward_") >= 0 ||
            StringFind(name, "Entry_") >= 0 || StringFind(name, "StopLoss_") >= 0 ||
            StringFind(name, "TakeProfit_") >= 0)
        {
          ObjectDelete(0, name);
        }
      }
    }

    • 在“绘制矩形”功能中,我们确保脚本能够图形化呈现风险和回报水平。为此,脚本首先移除任何具有相同名称的现有矩形,以避免重复。随后,脚本根据K线索引计算矩形的起始时间和结束时间,并精心设置了颜色和透明度级别。这使得矩形能够在图表上突出显示,同时又不会掩盖其他重要细节:
    ///---Draw rectangle function
    void DrawRectangle(string name, int startBar, double startPrice, int endBar, double endPrice, color rectColor)
    {
      if (ObjectFind(0, name) >= 0)
        ObjectDelete(0, name);
    
      datetime startTime = iTime(NULL, 0, startBar);
      datetime endTime = (endBar < 0) ? (TimeCurrent() + (PeriodSeconds() * (-endBar))) : iTime(NULL, 0, endBar);
    
      if (!ObjectCreate(0, name, OBJ_RECTANGLE, 0, startTime, startPrice, endTime, endPrice))
        Print("Failed to create rectangle: ", name);
    
      int alphaValue = FillTransparency;
      color fillColor = rectColor & 0x00FFFFFF | (alphaValue << 24);
    
      ObjectSetInteger(0, name, OBJPROP_COLOR, rectColor);
      ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
      ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
      ObjectSetInteger(0, name, OBJPROP_BACK, true);
    
      if (FillRectangles)
      {
        ObjectSetInteger(0, name, OBJPROP_COLOR, fillColor);
      }
      else
      {
        ObjectSetInteger(0, name, OBJPROP_COLOR, rectColor & 0x00FFFFFF);
      }
    }

      • 最后,我们实现了“绘制价格线”功能,指示脚本在入场、止损和获利水平处添加水平线。脚本将这些线延伸至整个图表,并添加显示相应价格水平的文本标签。这为用户提供了视觉参考,使他们能够快速识别并管理基于移动平均线生成信号的关键交易点。

      ///---- Draw Price Lines Function
      void DrawPriceLine(string name, int barIndex, double price, color lineColor, string labelText)
      {
        datetime time = iTime(NULL, 0, barIndex);
        datetime endTime = (barIndex - 2 * RectangleWidth < 0) ? (TimeCurrent() + (PeriodSeconds() * (-barIndex - 2 * RectangleWidth))) : iTime(NULL, 0, barIndex - 2 * RectangleWidth);
      
        if (!ObjectCreate(0, name, OBJ_TREND, 0, time, price, endTime, price))
          Print("Failed to create price line: ", name);
      
        ObjectSetInteger(0, name, OBJPROP_COLOR, lineColor);
        ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
        ObjectSetInteger(0, name, OBJPROP_WIDTH, 1);
        ObjectSetInteger(0, name, OBJPROP_BACK, true);
      
        string labelName = name + "_Label";
        if (ObjectFind(0, labelName) >= 0)
          ObjectDelete(0, labelName);
      
        if (!ObjectCreate(0, labelName, OBJ_TEXT, 0, endTime, price))
          Print("Failed to create label: ", labelName);
      
        ObjectSetInteger(0, labelName, OBJPROP_COLOR, lineColor);
        ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 10);
        ObjectSetString(0, labelName, OBJPROP_TEXT, labelText);
        ObjectSetInteger(0, labelName, OBJPROP_BACK, true);
      }
      

      现在,作为交易工具包的一部分,该脚本可以定期启动,以图形化方式查看过去和当前时间的交易。接下来,我们将着手创建我们独特的EA。我将专注于解释从开发到最终可用的EA的整个过程。在本文中,我们将重点关注如何使其与我们之前制作的趋势约束指标(Trend Constraint V1.09)协同工作。


      创建基于指标的EA:

      要在 MQL5 中使用自定义指标创建EA,我们需要确保自定义指标文件(.ex5)已放置在 MetaTrader 5 平台的“指标”文件夹中,此处为趋势约束指标 V1.09。通过 MetaEditor,我们可以编写我们的 EA,使用 MQL5 函数来访问指标的缓冲区值。我们使用 (iCustom()) 函数在 EA 中调用自定义指标,指定必要的参数,例如交易品种和时间周期。

      为了从指标缓冲区提取数据,我们使用 (CopyBuffer()) 函数,它会检索你打算用于交易信号分析的缓冲区值。然后,我们根据这些缓冲区值实现我们的交易逻辑,定义开仓、平仓或修改订单的条件,以符合我们的交易策略。整合风险管理功能,如止损和获利,以谨慎管理交易。使用 MetaTrader 5 策略测试器对 EA 进行回测,以评估其性能并微调其参数。最后,我们将在模拟账户环境中验证 EA 的功能,然后才转向实盘交易,以确保它在真实市场条件下能够有效运行。

      我们可以从在 MetaEditor 中加载一个EA模板开始,然后根据本文中显示的示例进行开发或修改:

      在 MetaEditor 中加载EA模板

      在 MetaEditor 中加载EA模板

      为了引导你完成我们的EA的构建,我们将整个过程分为六个阶段。在我们逐步推进的过程中,我建议你直接将代码片段输入到 MetaEditor 中。这种动手实践的方法将帮助你更好地理解和掌握这些步骤,尤其是如果你是 EA 开发的新手的话。

      1. 头部和基本数据

      在头部部分,我们定义了EA的名称和用途。通过包含版权信息、指向我们个人资料的链接以及指定版本号,我们确保我们的 EA 是易于识别和追溯的。这些基本数据帮助我们以及其他人理解 EA 的来源和用途,尤其是当它被共享或修改时:

      //You can replace the author details with yours.
      //+------------------------------------------------------------------+
      //|                                    Trend Constraint Expert.mq5   |
      //|                                Copyright 2024, Clemence Benjamin |
      //|                                             https://www.mql5.com |
      //+------------------------------------------------------------------+
      #property strict
      #property copyright "Copyright 2024, Clemence Benjamin"
      #property link      "https://www.mql5.com/en/users/billionaire2024/seller"
      #property version   "1.0"
      #property description "An Expert based on the buffer6 and buffer7 of Trend Constraint V1.09"

      2. 输入参数

      在这里,我们定义了关键的输入参数,这些参数允许我们在不修改代码本身的情况下自定义EA的行为。通过设置诸如(手数、滑点、止损、获利和magic数字)等参数,我们使EA变得灵活且能够适应不同的交易策略。magic数字尤其重要,因为它允许我们唯一地识别由这个EA执行的交易,这在涉及多个EA或手动交易时至关重要。

      ///----------- EA inputs parameters for customizations
      input double Lots = 0.1;          // Lot size
      input int Slippage = 3;           // Slippage
      input double StopLoss = 50;       // Stop Loss in points
      input double TakeProfit = 100;    // Take Profit in points
      input int MagicNumber = 123456;   // Magic number for orders

      3. 初始化函数(OnInit)

      在 OnInit 函数中,我们通过初始化必要的组件为 EA 的运行做好准备。我们首先尝试获取我们自定义指标“Trend Constraint V1.09”的句柄。这个句柄使我们能够以编程方式与指标进行交互。如果成功获取句柄,我们将继续设置缓冲区数组(Buffer6 和 Buffer7)为序列化形式,以便我们能够存储和操作指标值。然而,如果无法获取句柄,我们确保 EA 返回初始化失败,并附带一条错误消息以帮助我们诊断问题。

      ////-------Initialization Function
      int OnInit()
        {
         //--- Get the indicator handle
         indicator_handle = iCustom(Symbol(), PERIOD_CURRENT, "Trend Constraint V1.09");
         if (indicator_handle < 0)
           {
            Print("Failed to get the indicator handle. Error: ", GetLastError());
            return(INIT_FAILED);
           }
      
         //--- Set the buffer arrays as series
         ArraySetAsSeries(Buffer6, true);
         ArraySetAsSeries(Buffer7, true);
      
         return(INIT_SUCCEEDED);
        }
      

      4. 反初始化函数(OnDeinit)

      当我们的EA从图表中移除,或者当平台关闭时,(OnDeinit)函数会被执行。在这里,我们会释放指标句柄,确保释放EA运行期间分配的任何资源。这个清理步骤对于维护我们交易环境的效率和稳定性至关重要,因为它防止了不必要的资源占用。

      ///------Deinitialization Function(OnDeinit)
      void OnDeinit(const int reason)
        {
         //--- Release the indicator handle
         IndicatorRelease(indicator_handle);
        }
      

      5. 主执行函数(OnTick)

      (OnTick) 函数是真正进行交易操作的地方。每当接收到一个新的市场报价时,这个函数就会被调用。我们首先检查是否已经存在一个具有相同magic数字的未平仓头寸,以确保我们不会重复下单。接下来,我们从指标缓冲区(Buffer6 和 Buffer7)复制最新的值,以便做出交易决策。如果满足我们的买入或卖出信号条件,我们将构建并发送相应的交易请求。我们会仔细指定所有必要的参数,例如订单类型、价格、止损、获利和滑点,以有效执行我们的交易策略。

      ///---Main Execution Function(OnTick)
      void OnTick()
        {
         //--- Check if there is already an open position with the same MagicNumber
         if (PositionSelect(Symbol()))
           {
            if (PositionGetInteger(POSITION_MAGIC) == MagicNumber)
              {
               return; // Exit OnTick if there's an open position with the same MagicNumber
              }
           }
      
         //--- Calculate the indicator
         if (CopyBuffer(indicator_handle, 5, 0, 2, Buffer6) <= 0 || CopyBuffer(indicator_handle, 6, 0, 2, Buffer7) <= 0)
           {
            Print("Failed to copy buffer values. Error: ", GetLastError());
            return;
           }
      
         //--- Check for a buy signal
         if (Buffer7[0] != EMPTY_VALUE)
           {
            double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
            double sl = NormalizeDouble(ask - StopLoss * _Point, _Digits);
            double tp = NormalizeDouble(ask + TakeProfit * _Point, _Digits);
      
            //--- Prepare the buy order request
            MqlTradeRequest request;
            MqlTradeResult result;
            ZeroMemory(request);
            ZeroMemory(result);
      
            request.action = TRADE_ACTION_DEAL;
            request.symbol = Symbol();
            request.volume = Lots;
            request.type = ORDER_TYPE_BUY;
            request.price = ask;
            request.sl = sl;
            request.tp = tp;
            request.deviation = Slippage;
            request.magic = MagicNumber;
            request.comment = "Buy Order";
      
            //--- Send the buy order
            if (!OrderSend(request, result))
              {
               Print("Error opening buy order: ", result.retcode);
              }
            else
              {
               Print("Buy order opened successfully! Ticket: ", result.order);
              }
           }
      
         //--- Check for a sell signal
         if (Buffer6[0] != EMPTY_VALUE)
           {
            double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
            double sl = NormalizeDouble(bid + StopLoss * _Point, _Digits);
            double tp = NormalizeDouble(bid - TakeProfit * _Point, _Digits);
      
            //--- Prepare the sell order request
            MqlTradeRequest request;
            MqlTradeResult result;
            ZeroMemory(request);
            ZeroMemory(result);
      
            request.action = TRADE_ACTION_DEAL;
            request.symbol = Symbol();
            request.volume = Lots;
            request.type = ORDER_TYPE_SELL;
            request.price = bid;
            request.sl = sl;
            request.tp = tp;
            request.deviation = Slippage;
            request.magic = MagicNumber;
            request.comment = "Sell Order";
      
            //--- Send the sell order
            if (!OrderSend(request, result))
              {
               Print("Error opening sell order: ", result.retcode);
              }
            else
              {
               Print("Sell order opened successfully! Ticket: ", result.order);
              }
           }
        }
      

      6. 其他函数

      我们还包含了几个其他函数来处理我们的 EA 可能遇到的不同事件,这些函数作为 EA 模板的一部分提供,而为了简化,我们目前并不使用它们:

      • (OnTrade):在这里,我们可以处理在交易事件发生时需要执行的任何特定操作。虽然目前这个函数是空的,但它为我们提供了一个空间,以便在需要时添加逻辑。
      • (OnTester):这个函数用于在回测期间进行自定义计算。通过返回一个值,我们可以根据特定指标优化我们的策略。
      • (OnTesterInit, OnTesterPass, OnTesterDeinit):这些函数参与策略测试器中的优化过程。它们允许我们初始化设置,在每次优化后执行操作,并在之后清理资源。
      • (OnChartEvent):这个函数允许我们处理各种图表事件,例如鼠标点击或按键操作。虽然它目前是空的,但我们可以利用这个空间为我们的 EA 添加交互功能。
      ///----Other Template functions available
      void OnTrade()
        {
         //--- Handle trade events if necessary
        }
      
      double OnTester()
        {
         double ret = 0.0;
         //--- Custom calculations for strategy tester
         return (ret);
        }
      
      void OnTesterInit()
        {
         //--- Initialization for the strategy tester
        }
      
      void OnTesterPass()
        {
         //--- Code executed after each pass in optimization
        }
      
      void OnTesterDeinit()
        {
         //--- Cleanup after tester runs
        }
      
      void OnChartEvent(const int id,
                        const long &lparam,
                        const double &dparam,
                        const string &sparam)
        {
         //--- Handle chart events here
        }
      

      最后将所有这些整合在一起,我们完整的程序如下:

      //+------------------------------------------------------------------+
      //|                                    Trend Constraint Expert.mq5   |
      //|                                Copyright 2024, Clemence Benjamin |
      //|                                             https://www.mql5.com |
      //+------------------------------------------------------------------+
      #property strict
      #property copyright "Copyright 2024, Clemence Benjamin"
      #property link      "https://www.mql5.com/en/users/billionaire2024/seller"
      #property version   "1.0"
      #property description "An Expert based on the buffer6 and buffer7 of Trend Constraint V1.09"
      
      //--- Input parameters for the EA
      input double Lots = 0.1;          // Lot size
      input int Slippage = 3;           // Slippage
      input double StopLoss = 50;       // Stop Loss in points
      input double TakeProfit = 100;    // Take Profit in points
      input int MagicNumber = 123456;   // Magic number for orders
      
      //--- Indicator handle
      int indicator_handle;
      double Buffer6[];
      double Buffer7[];
      
      //+------------------------------------------------------------------+
      //| Expert initialization function                                   |
      //+------------------------------------------------------------------+
      int OnInit()
        {
         //--- Get the indicator handle
         indicator_handle = iCustom(Symbol(), PERIOD_CURRENT, "Trend Constraint V1.09");
         if (indicator_handle < 0)
           {
            Print("Failed to get the indicator handle. Error: ", GetLastError());
            return(INIT_FAILED);
           }
      
         //--- Set the buffer arrays as series
         ArraySetAsSeries(Buffer6, true);
         ArraySetAsSeries(Buffer7, true);
      
         return(INIT_SUCCEEDED);
        }
      
      //+------------------------------------------------------------------+
      //| Expert deinitialization function                                 |
      //+------------------------------------------------------------------+
      void OnDeinit(const int reason)
        {
         //--- Release the indicator handle
         IndicatorRelease(indicator_handle);
        }
      
      //+------------------------------------------------------------------+
      //| Expert tick function                                             |
      //+------------------------------------------------------------------+
      void OnTick()
        {
         //--- Check if there is already an open position with the same MagicNumber
         if (PositionSelect(Symbol()))
           {
            if (PositionGetInteger(POSITION_MAGIC) == MagicNumber)
              {
               return; // Exit OnTick if there's an open position with the same MagicNumber
              }
           }
      
         //--- Calculate the indicator
         if (CopyBuffer(indicator_handle, 5, 0, 2, Buffer6) <= 0 || CopyBuffer(indicator_handle, 6, 0, 2, Buffer7) <= 0)
           {
            Print("Failed to copy buffer values. Error: ", GetLastError());
            return;
           }
      
         //--- Check for a buy signal
         if (Buffer7[0] != EMPTY_VALUE)
           {
            double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK);
            double sl = NormalizeDouble(ask - StopLoss * _Point, _Digits);
            double tp = NormalizeDouble(ask + TakeProfit * _Point, _Digits);
      
            //--- Prepare the buy order request
            MqlTradeRequest request;
            MqlTradeResult result;
            ZeroMemory(request);
            ZeroMemory(result);
      
            request.action = TRADE_ACTION_DEAL;
            request.symbol = Symbol();
            request.volume = Lots;
            request.type = ORDER_TYPE_BUY;
            request.price = ask;
            request.sl = sl;
            request.tp = tp;
            request.deviation = Slippage;
            request.magic = MagicNumber;
            request.comment = "Buy Order";
      
            //--- Send the buy order
            if (!OrderSend(request, result))
              {
               Print("Error opening buy order: ", result.retcode);
              }
            else
              {
               Print("Buy order opened successfully! Ticket: ", result.order);
              }
           }
      
         //--- Check for a sell signal
         if (Buffer6[0] != EMPTY_VALUE)
           {
            double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID);
            double sl = NormalizeDouble(bid + StopLoss * _Point, _Digits);
            double tp = NormalizeDouble(bid - TakeProfit * _Point, _Digits);
      
            //--- Prepare the sell order request
            MqlTradeRequest request;
            MqlTradeResult result;
            ZeroMemory(request);
            ZeroMemory(result);
      
            request.action = TRADE_ACTION_DEAL;
            request.symbol = Symbol();
            request.volume = Lots;
            request.type = ORDER_TYPE_SELL;
            request.price = bid;
            request.sl = sl;
            request.tp = tp;
            request.deviation = Slippage;
            request.magic = MagicNumber;
            request.comment = "Sell Order";
      
            //--- Send the sell order
            if (!OrderSend(request, result))
              {
               Print("Error opening sell order: ", result.retcode);
              }
            else
              {
               Print("Sell order opened successfully! Ticket: ", result.order);
              }
           }
        }
      
      //+------------------------------------------------------------------+
      //| Trade function                                                   |
      //+------------------------------------------------------------------+
      void OnTrade()
        {
         //--- Handle trade events if necessary
        }
      
      //+------------------------------------------------------------------+
      //| Tester function                                                  |
      //+------------------------------------------------------------------+
      double OnTester()
        {
         double ret = 0.0;
         //--- Custom calculations for strategy tester
         return (ret);
        }
      
      //+------------------------------------------------------------------+
      //| TesterInit function                                              |
      //+------------------------------------------------------------------+
      void OnTesterInit()
        {
         //--- Initialization for the strategy tester
        }
      
      //+------------------------------------------------------------------+
      //| TesterPass function                                              |
      //+------------------------------------------------------------------+
      void OnTesterPass()
        {
         //--- Code executed after each pass in optimization
        }
      
      //+------------------------------------------------------------------+
      //| TesterDeinit function                                            |
      //+------------------------------------------------------------------+
      void OnTesterDeinit()
        {
         //--- Cleanup after tester runs
        }
      
      //+------------------------------------------------------------------+
      //| ChartEvent function                                              |
      //+------------------------------------------------------------------+
      void OnChartEvent(const int id,
                        const long &lparam,
                        const double &dparam,
                        const string &sparam)
        {
         //--- Handle chart events here
        }
      //+------------------------------------------------------------------+
      

      在成功编译后的下一个阶段,我们将测试我们的程序。


      测试:

      在 MetaEditor 中,我们可以使用“编译”或“运行”按钮来准备我们的程序以进行测试。在这种情况下,编译成功,我们使用策略测试器在 Boom 500 指数上进行测试。

      启用策略测试器

      从导航栏启动策略测试器

      一个策略测试器面板会打开,允许你在点击右下角的“开始”按钮之前调整一些设置。例如,EA 中的默认手数设置为 0.1 手,但对于 Boom 500 指数,我不得不将其增加到至少 0.2 手。

      策略测试器

      策略测试器面板

      令人惊喜的是,我们的系统在策略测试器中的表现非常出色,如下图所示:

      测试回放

      测试结果可视化:趋势约束EA


      结论

      添加风险和回报矩形为交易者提供了一种清晰的图形化交易表示,使监控和管理未平仓头寸变得更加容易。这种视觉辅助工具在快速变动的市场中尤其有用,因为快速决策是必要的。矩形作为交易潜在结果的持续提醒,帮助交易者保持与其原始交易计划的一致性。

      (Trend Constraint V1.09)指标与EA之间的成功协作突显了交易策略中工具之间协同作用的重要性。指标识别潜在趋势和反转,而EA则根据这些信息执行交易并管理风险。这种整合方法导致了一个更具凝聚力和有效的交易策略。

      以下是附带的指标、脚本和EA。仍有改进和修改的空间。希望这些信息对你有价值。欢迎你在评论区分享你的想法。祝您交易愉快!

      附件 说明
      (Trend_Constraint V1.09.mq5) EA所使用指标的源代码。
      (Trend Constraint R-R.mq5) 风险-回报矩形脚本的源代码。
      (Trend Constraint Expert.mq5) 与(趋势约束指标 V1.09)完美配合使用的EA的源代码。

      返回内容目录

      本文由MetaQuotes Ltd译自英文
      原文地址: https://www.mql5.com/en/articles/15321

      最近评论 | 前往讨论 (2)
      Helga Gustana Argita
      Helga Gustana Argita | 14 8月 2024 在 19:50
      你好,我有一些错误,如何解决?

      2024.08.15 00:47:15.123 2024.08.01 00:00:00 无法加载自定义指标'Trend Constraint V1.09' [4802]
      2024.08.15 00:47:15.123 2024.08.01 00:00:00 'Trend_Constraint_Expert.mq5' 中的指标创建错误 (1,1)

      Clemence Benjamin
      Clemence Benjamin | 15 8月 2024 在 22:43
      argatafx28 自定义指标'Trend Constraint V1.09' [4802]
      2024.08.15 00:47:15.123 2024.08.01 00:00:00 'Trend_Constraint_Expert.mq5' 中的指标创建错误 (1,1)

      您好@argatafx28

      1. 确保已安装指标 Trend Constraint V1.09,以便 EA 运行。
      2. Trend Constraint V1.09 使用ShellExecute 整合电报,您能否确保在启动指标时允许依赖 DLL?


      启用允许 DLL

      顺便问一下,您使用的是哪个操作系统?
      开发多币种 EA 交易系统(第 16 部分):不同报价历史对测试结果的影响 开发多币种 EA 交易系统(第 16 部分):不同报价历史对测试结果的影响
      正在开发中的 EA 预计在与不同经纪商进行交易时都会表现出良好的效果。但目前我们一直使用 MetaQuotes 模拟账户的报价进行测试。让我们看看我们的 EA 是否准备好使用与测试和优化期间使用的报价不同的交易账户。
      动物迁徙优化(AMO)算法 动物迁徙优化(AMO)算法
      本文介绍了AMO算法,该算法通过模拟动物的季节性迁徙来寻找适合生存和繁殖的最优条件。AMO的主要特点包括使用拓扑邻域和概率更新机制,使得其易于实现,并且能够灵活应用于各种优化任务。
      让新闻交易轻松上手(第3部分):执行交易 让新闻交易轻松上手(第3部分):执行交易
      在本文中,我们的新闻交易EA将根据存储在数据库中的经济日历开始交易。此外,我们将改进EA的图表,以显示更多关于即将到来的经济日历事件的相关信息。
      神经网络变得简单(第 97 部分):搭配 MSFformer 训练模型 神经网络变得简单(第 97 部分):搭配 MSFformer 训练模型
      在探索各种模型架构设计时,我们往往对模型训练过程的关注投入不足。在本文中,我旨在弥补这一差距。