English Русский Español Deutsch 日本語
preview
构建K线趋势约束模型(第九部分):多策略EA(2)

构建K线趋势约束模型(第九部分):多策略EA(2)

MetaTrader 5示例 |
129 1
Clemence Benjamin
Clemence Benjamin

内容:


概述

20世纪,理查德·唐奇安(Richard Donchian)通过对金融市场的深入研究,构建了一套趋势跟踪策略体系,其核心工具——唐奇安通道(Donchian Channels)由此诞生。我们在前文曾简要提及过他的理论贡献,而本文将聚焦于如何将基于该理论的策略转化为实战代码。据多方资料显示,唐奇安通道框架内蕴含着突破、爬升、均值回归等多种策略变体。海量文献证实,这一理论在当代交易中仍保持着强劲的生命力。通过整合唐奇安通道策略,我们旨在为趋势约束型EA注入更多交易机会,提升其在不同市场环境下的盈利能力和适应性。

当前网络上热门的唐奇安通道策略包括:突破策略、爬升策略及均值回归策略等。知名交易员雷纳·泰奥(Rayner Teo)等业内人士,也通过教学课程帮助交易者掌握该通道的实战应用技巧。

MetaTrader 5平台已内置免费的唐奇安通道指标,这为我们的项目开发提供了关键优势。通过获取指标源码,我们得以深入解析其内部结构,从而更高效地将其整合至趋势约束型EA中。随着代码复杂度提升,我们将优先独立开发新策略模块,再逐步融入主程序框架。后续章节将深入解析相关理论,并持续优化算法性能。


什么是唐奇安通道(Donchian Channels)

唐奇安通道是一种技术分析工具,由上轨、中轨和下轨三条线构成,用于追踪价格运动中的高点和低点。其发明者正是前文提及的趋势跟踪交易先驱——理查德·唐奇安。三条轨道的核心定义如下:
  • 上轨:反映指定周期(如最近20根K线)内的最高价。
  • 下轨:反映相同周期内的最低价。
  • 中轨:通常为上下轨的平均值,可作为动态参考线。
以下为唐奇安通道在MetaTrader 5中的实盘效果图:


唐奇安通道

唐奇安通道线


在MetaTrader 5中访问唐奇安通道

通过导航窗口的"指标"选项卡即可快速访问,操作流程如下图所示:

在MetaTrader 5中访问唐奇安通道

MetaTrader 5导航窗口

调用后,将指标拖拽至目标图表即可完成部署,如下图所示。该示例采用波动率150(1秒)指数的默认参数。

为图表添加唐奇安通道

在MetaTrader 5中为图表添加唐奇安通道

我们首先在图表上部署该指标,旨在通过可视化观察价格与通道的互动关系。这种直观分析有助于我们在编写算法前,先建立清晰的交易逻辑认知。接下来,我们将演示如何在MetaEditor 5中获取该指标的源代码。


在MetaEditor中访问唐奇安通道的源代码

要在MetaEditor 5中打开指标源文件进行编辑,需通过导航窗口的"免费指标"选项卡定位目标文件(与MetaTrader 5平台操作路径一致)。关键区别在于,此处操作的是可编辑的源代码文件而非编译后的二进制版本。双击文件即可展开代码视图。具体操作可参考以下图示。

MetaEditor中的唐奇安通道指标

在MetaEditor中访问唐奇安通道源代码


在趋势约束EA中实现唐奇安通道策略

在整合新工具前,需明确核心约束参数以建立交易规则框架。我们始终遵循"先定义约束条件"的开发原则。例如,仅在日线(D1)出现阳线时执行买入操作,阴线时执行卖出操作。基于此逻辑,我们将优先识别与当前趋势方向一致的通道信号机会。以看涨日线(D1)的情况为例,重点关注以下三类信号:

  1. 价格触及通道下轨时触发买入信号(高胜率)
  2. 中轨反弹信号(胜率中等)
  3. 上轨突破信号(胜率较低)

以下图示展示了三种信号的视觉效果。

Benjc交易系统的唐奇安通道图解

唐奇安通道策略体系

本示例将采用突破策略,即监测价格收盘价突破通道上下轨的临界条件。接下来我们将深入分析指标源码,定位关键数据缓冲区。


指标源代码预览

MetaTrader 5平台内置了标准版唐奇安通道指标。您可通过前述方法直接在MetaEditor 5中访问查阅。

//+------------------------------------------------------------------+
//|                                             Donchian Channel.mq5 |
//|                              Copyright 2009-2024, MetaQuotes Ltd |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright   "2009-2024, MetaQuotes Ltd"
#property link        "http://www.mql5.com"
#property description "Donchian Channel"
//---
#property indicator_chart_window
#property indicator_buffers 3
#property indicator_plots   3
#property indicator_type1   DRAW_LINE
#property indicator_color1  clrBlue
#property indicator_type2   DRAW_LINE
#property indicator_color2  clrGray
#property indicator_type3   DRAW_LINE
#property indicator_color3  clrRed
//--- labels
#property indicator_label1  "Upper Donchian"
#property indicator_label2  "Middle Donchian"
#property indicator_label3  "Lower Donchian"

//--- input parameter
input int  InpDonchianPeriod=20;    // period of the channel
input bool InpShowLabel     =true;  // show price of the level

//--- indicator buffers
double    ExtUpBuffer[];
double    ExtMdBuffer[];
double    ExtDnBuffer[];

//--- unique prefix to identify indicator objects
string ExtPrefixUniq;

//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- define buffers
   SetIndexBuffer(0, ExtUpBuffer);
   SetIndexBuffer(1, ExtMdBuffer);
   SetIndexBuffer(2, ExtDnBuffer);

//--- set a 1-bar offset for each line
   PlotIndexSetInteger(0, PLOT_SHIFT, 1);
   PlotIndexSetInteger(1, PLOT_SHIFT, 1);
   PlotIndexSetInteger(2, PLOT_SHIFT, 1);

//--- indicator name
   IndicatorSetString(INDICATOR_SHORTNAME, "Donchian Channel");
//--- number of digits of indicator value
   IndicatorSetInteger(INDICATOR_DIGITS, _Digits);

//--- prepare prefix for objects
   string number=StringFormat("%I64d", GetTickCount64());
   ExtPrefixUniq=StringSubstr(number, StringLen(number)-4);
   ExtPrefixUniq=ExtPrefixUniq+"_DN";
   Print("Indicator \"Donchian Channels\" started, prefix=", ExtPrefixUniq);

   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime &time[],
                const double &open[],
                const double &high[],
                const double &low[],
                const double &close[],
                const long &tick_volume[],
                const long &volume[],
                const int &spread[])
  {
//--- if the indicator has previously been calculated, start from the bar preceding the last one
   int start=prev_calculated-1;

//--- if this is the first calculation of the indicator, then move by InpDonchianPeriod bars form the beginning
   if(prev_calculated==0)
      start=InpDonchianPeriod+1;

//--- calculate levels for all bars in a loop
   for(int i=start; i<rates_total; i++)
     {
      //--- get max/min values for the last InpDonchianPeriod bars
      int    highest_bar_index=ArrayMaximum(high, i-InpDonchianPeriod+1, InpDonchianPeriod);
      int    lowest_bar_index=ArrayMinimum(low, i-InpDonchianPeriod+1, InpDonchianPeriod);;
      double highest=high[highest_bar_index];
      double lowest=low[lowest_bar_index];

      //--- write values into buffers
      ExtUpBuffer[i]=highest;
      ExtDnBuffer[i]=lowest;
      ExtMdBuffer[i]=(highest+lowest)/2;
     }

//--- draw labels on levels
   if(InpShowLabel)
     {
      ShowPriceLevels(time[rates_total-1], rates_total-1);
      ChartRedraw();
     }

//--- succesfully calculated
   return(rates_total);
  }
//+------------------------------------------------------------------+
//| Custom indicator deinitialization function                       |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//--- delete all our graphical objects after use
   Print("Indicator \"Donchian Channels\" stopped, delete all objects with prefix=", ExtPrefixUniq);
   ObjectsDeleteAll(0, ExtPrefixUniq, 0, OBJ_ARROW_RIGHT_PRICE);
   ChartRedraw(0);
  }
//+------------------------------------------------------------------+
//|  Show prices' levels                                             |
//+------------------------------------------------------------------+
void ShowPriceLevels(datetime time, int last_index)
  {
   ShowRightPrice(ExtPrefixUniq+"_UP", time, ExtUpBuffer[last_index], clrBlue);
   ShowRightPrice(ExtPrefixUniq+"_MD", time, ExtMdBuffer[last_index], clrGray);
   ShowRightPrice(ExtPrefixUniq+"_Dn", time, ExtDnBuffer[last_index], clrRed);
  }
//+------------------------------------------------------------------+
//| Create or Update "Right Price Label" object                      |
//+------------------------------------------------------------------+
bool ShowRightPrice(const string name, datetime time, double price, color clr)
  {
   if(!ObjectCreate(0, name, OBJ_ARROW_RIGHT_PRICE, 0, time, price))
     {
      ObjectMove(0, name, 0, time, price);
      return(false);
     }

//--- make the label size adaptive
   long scale=2;
   if(!ChartGetInteger(0, CHART_SCALE, 0, scale))
     {
      //--- output an error message to the Experts journal
      Print(__FUNCTION__+", ChartGetInteger(CHART_SCALE) failed, error = ", GetLastError());
     }
   int width=scale>1 ? 2:1;  // if chart scale > 1, then label size = 2

   ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
   ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID);
   ObjectSetInteger(0, name, OBJPROP_WIDTH, width);
   ObjectSetInteger(0, name, OBJPROP_BACK, false);
   ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
   ObjectSetInteger(0, name, OBJPROP_SELECTED, false);
   ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
   ObjectSetInteger(0, name, OBJPROP_ZORDER, 0);

   return(true);
  }
//+------------------------------------------------------------------+

上述自定义代码实现了唐奇安通道(Donchian Channel)指标。该指标计算并显示三条线:上轨线(表示指定周期内的最高价)、下轨线(表示同一周期内的最低价)以及中轨线(上轨与下轨的平均值)。该指标旨在可视化潜在的突破点,其输入参数可自定义通道周期,并可选择在图表上显示价格标签。代码包括用于设置指标缓冲区和属性的初始化函数、一个逐根K线更新通道线数值的计算循环,以及用于管理图表对象和价格标签的函数。总体而言,该工具通过历史价格水平帮助交易者识别趋势及潜在交易机会。

为便于理解,我们将拆解上述代码段,并在后续章节中定位关键缓冲区以推进开发工作。

缓冲区声明:

代码中声明了三个缓冲区:ExtUpBuffer[](存储唐奇安通道上轨值)、ExtMdBuffer[],(存储中轨值)和ExtDnBuffer[](存储下轨值)。

double ExtUpBuffer[];
double ExtMdBuffer[];
double ExtDnBuffer[];

OnInit中设置缓冲区:

SetIndexBuffer 函数将图表绘图(线条)与缓冲区关联,使它们能够在图表上绘制和更新。

SetIndexBuffer(0, ExtUpBuffer);
SetIndexBuffer(1, ExtMdBuffer);
SetIndexBuffer(2, ExtDnBuffer);

OnCalculate计算缓冲区值

此代码计算指定周期内的最高价、最低价及平均价,并将结果存储到各K线对应的缓冲区中。

for(int i=start; i<rates_total; i++)
{
   //--- calculate highest and lowest for the Donchian period
   int highest_bar_index = ArrayMaximum(high, i-InpDonchianPeriod+1, InpDonchianPeriod);
   int lowest_bar_index  = ArrayMinimum(low, i-InpDonchianPeriod+1, InpDonchianPeriod);
   double highest = high[highest_bar_index];
   double lowest  = low[lowest_bar_index];

   //--- assign values to buffers
   ExtUpBuffer[i] = highest;
   ExtDnBuffer[i] = lowest;
   ExtMdBuffer[i] = (highest + lowest) / 2;
}

为了生成买入信号,该策略使用上轨缓冲区(ExtUpBuffer),当价格收盘价突破上唐奇安线时触发买入。相反,当价格收盘价跌破下唐奇安线时,根据下轨缓冲区(ExtDnBuffer)触发卖出信号。此外,中轨(ExtMdBuffer)可以作为趋势过滤器,仅在价格高于中轨时允许买入交易,表明一个更强的上升趋势,从而优化策略。掌握上述逻辑后,我们可以正式进入EA的开发阶段。


代码开发

得益于唐奇安通道作为内置指标的特性,我们只需聚焦其缓冲区即可构建交易信号生成逻辑。为清晰起见,我们将先开发独立的唐奇安通道突破型EA,再将其整合至趋势约束型EA中。现在,我们将专注于使用唐奇安通道的突破策略。突破条件很简单:当价格收盘价突破通道边界时触发交易。具体可参考前文图解中的策略说明。

刚开始,我们将在MetaEditor 5中创建一个新文件,如下图所示。我将其命名为“BreakoutEA”,因为我们的主要关注点将是这种突破策略。

新EA

在MetaEditor中启动新EA项目

我将流程分为五个主要部分,您可以按照下面的步骤逐步了解整个开发过程。最初,在启动EA时,我们将从基本模板开始,暂时不勾选其他部分。在该模板下方,我们将解释说明最终将组合在一起的关键组件。

在这个基本模板中,您会发现一些基本属性,例如(#property strict)指令。该指令确保编译器强制正确使用数据类型,有助于防止因类型不匹配而引起的潜在编程错误。另一个关键方面是包括交易库,它提供了有效管理交易操作的必要工具。这些步骤为开发过程奠定了坚实的基础。

//+------------------------------------------------------------------+
//|                                                   BreakoutEA.mq5 |
//|                                Copyright 2024, Clemence Benjamin |
//|             https://www.mql5.com/en/users/billionaire2024/seller |
//+------------------------------------------------------------------+
#property copyright "Copyright 2024, Clemence Benjamin"
#property link      "https://www.mql5.com/en/users/billionaire2024/seller"
#property version   "1.00"
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   
//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   
  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   
  }
//+------------------------------------------------------------------+

1. 初始化EA

在初始化部分,定义了EA的输入参数。这些参数允许我们根据交易偏好配置EA。关键输入包括唐奇安通道周期、风险回报比、交易的手数以及止损和获利的点数。

// Input parameters
input int InpDonchianPeriod = 20;      // Period for Donchian Channel
input double RiskRewardRatio = 1.5;    // Risk-to-reward ratio
input double LotSize = 0.1;            // Default lot size for trading
input double pipsToStopLoss = 15;      // Stop loss in pips
input double pipsToTakeProfit = 30;    // Take profit in pips

// Indicator handle storage
int handle;
string indicatorKey;

// Expert initialization function
int OnInit() {
    // Create a unique key for the indicator based on the symbol and period
    indicatorKey = StringFormat("%s_%d", Symbol(), InpDonchianPeriod);
    
    // Load the Donchian Channel indicator
    handle = iCustom(Symbol(), Period(), "Free Indicators\\Donchian Channel", InpDonchianPeriod);
    
    // Check if the indicator loaded successfully
    if (handle == INVALID_HANDLE) {
        Print("Failed to load the indicator. Error: ", GetLastError());
        return INIT_FAILED;
    }
    return INIT_SUCCEEDED;
}

基于交易品种和设定的周期生成唯一的标识键,确保每个指标实例均可被独立区分。使用iCustom()函数加载唐奇安通道指标,需指定其在MetaTrader目录中的路径(Free Indicators\\Donchian Channel)。若加载失败(返回INVALID_HANDLE),系统将打印错误信息并终止初始化流程,避免因缺失关键指标数据导致后续逻辑异常。必须明确指定存储路径,因该指标未放置在默认的指标根目录下,否则将触发下图所示错误。在大多数情况下,若指标加载失败,EA将无法正常运行。

//Typical Journal log when the EA fails to locate an indicator in the root indicators storage.
2024.10.20 08:49:04.117 2022.01.01 00:00:00   cannot load custom indicator 'Donchian Channel' [4802]
2024.10.20 08:49:04.118 2022.01.01 00:00:00   indicator create error in 'DonchianEA.mq5' (1,1)
2024.10.20 08:49:04.118 OnInit critical error
2024.10.20 08:49:04.118 tester stopped because OnInit failed

2. 清理与反初始化

清理部分负责释放EA所使用的资源。这在OnDeinit()函数中完成,该函数在EA被移除或MetaTrader 5关闭时被调用。该函数确保使用IndicatorRelease()释放指标句柄。彻底清理资源对于防止内存泄漏和维护整个平台的性能至关重要。

// Expert deinitialization function
void OnDeinit(const int reason) {
    // Release the indicator handle to free up resources
    IndicatorRelease(handle);
}

3. 主执行逻辑

主执行逻辑位于OnTick()函数中,该函数在每个市场tick或价格变化时被触发。在该函数中,使用PositionsTotal()函数检查当前是否有任何未平仓头寸。如果没有未平仓头寸,程序将通过调用一个单独的函数来评估交易条件。这种结构防止了一次性开启多个交易,因为这样可能导致过度交易。

// Main execution function with block-based control
void OnTick() {
    // Check if any positions are currently open
    if (PositionsTotal() == 0) {
        CheckTradingConditions();
    }
}

4. 评估交易条件

在这部分中,EA将市场条件与唐奇安通道的上下带进行比较。调整指标的缓冲区大小以适应最新数据。CopyBuffer()函数检索唐奇安通道的最新值。

// Check trading conditions based on indicator buffers
void CheckTradingConditions() {
    double ExtUpBuffer[], ExtDnBuffer[];  // Buffers for upper and lower Donchian bands

    // Resize buffers to hold the latest data

    ArrayResize(ExtUpBuffer, 2);
    ArrayResize(ExtDnBuffer, 2);

    // Get the latest values from the Donchian Channel
    if (CopyBuffer(handle, 0, 0, 2, ExtUpBuffer) <= 0 || CopyBuffer(handle, 2, 0, 2, ExtDnBuffer) <= 0) {
        Print("Error reading indicator buffer. Error: ", GetLastError());
        return;
    }

    // Get the close price of the current candle
    double closePrice = iClose(Symbol(), Period(), 0);

    // Buy condition: Closing price is above the upper Donchian band
    if (closePrice > ExtUpBuffer[1]) {
        double stopLoss = closePrice - pipsToStopLoss * _Point; // Calculate stop loss
        double takeProfit = closePrice + pipsToTakeProfit * _Point; // Calculate take profit
        OpenBuy(LotSize, stopLoss, takeProfit);
    }

    // Sell condition: Closing price is below the lower Donchian band
    if (closePrice < ExtDnBuffer[1]) {
        double stopLoss = closePrice + pipsToStopLoss * _Point; // Calculate stop loss
        double takeProfit = closePrice - pipsToTakeProfit * _Point; // Calculate take profit
        OpenSell(LotSize, stopLoss, takeProfit);
    }
}

获取当前K线的收盘价,该数据是判断交易信号的核心依据。交易条件的定义:如果收盘价超过上轨,则触发买入订单;如果价格跌破下轨,则下达卖出订单。止损和获利水平是根据用户定义的点差值计算得出的,以便有效管理风险。

5. 订单执行函数

订单执行函数负责执行买入和卖出交易。每个函数都尝试使用CTrade类的方法来下单,这样可以简化交易管理。在尝试执行交易后,程序会检查订单是否成功。如果失败,会打印错误消息以告知交易者失败情况。这些函数封装了交易逻辑,并为依据之前设定的条件下单提供了清晰的接口。

// Open a buy order
void OpenBuy(double lotSize, double stopLoss, double takeProfit) {
    // Attempt to open a buy order
    if (trade.Buy(lotSize, Symbol(), 0, stopLoss, takeProfit, "Buy Order")) {
        Print("Buy order placed: Symbol = ", Symbol(), ", LotSize = ", lotSize);
    } else {
        Print("Failed to open buy order. Error: ", GetLastError());
    }
}

// Open a sell order
void OpenSell(double lotSize, double stopLoss, double takeProfit) {
    // Attempt to open a sell order
    if (trade.Sell(lotSize, Symbol(), 0, stopLoss, takeProfit, "Sell Order")) {
        Print("Sell order placed: Symbol = ", Symbol(), ", LotSize = ", lotSize);
    } else {
        Print("Failed to open sell order. Error: ", GetLastError());
    }
}

我们的唐奇安通道突破型EA现已完整开发完毕,功能模块一应俱全,可直接投入使用。

//+----------------------------------------------------------------------+
//|                                                       BreakoutEA.mq5 |
//|                                    Copyright 2024, Clemence Benjamin |
//|                 https://www.mql5.com/en/users/billionaire2024/seller |
//+----------------------------------------------------------------------+
#property copyright "Copyright 2024, Clemence Benjamin"
#property link      "https://www.mql5.com/en/users/billionaire2024/seller"
#property version   "1.00"
#property strict
#include <Trade\Trade.mqh> // Include the trade library

// Input parameters
input int InpDonchianPeriod = 20;      // Period for Donchian Channel
input double RiskRewardRatio = 1.5;    // Risk-to-reward ratio
input double LotSize = 0.1;            // Default lot size for trading
input double pipsToStopLoss = 15;      // Stop loss in pips
input double pipsToTakeProfit = 30;    // Take profit in pips

// Indicator handle storage
int handle;
string indicatorKey;
double ExtUpBuffer[];  // Upper Donchian buffer
double ExtDnBuffer[];  // Lower Donchian buffer

// Trade instance
CTrade trade;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    indicatorKey = StringFormat("%s_%d", Symbol(), InpDonchianPeriod); // Create a unique key for the indicator
    handle = iCustom(Symbol(), Period(), "Free Indicators\\Donchian Channel", InpDonchianPeriod);
    if (handle == INVALID_HANDLE)
    {
        Print("Failed to load the indicator. Error: ", GetLastError());
        return INIT_FAILED;
    }
    return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    // Release the indicator handle
    IndicatorRelease(handle);
}

//+------------------------------------------------------------------+
//| Main execution function with block-based control                 |
//+------------------------------------------------------------------+
void OnTick()
{
    // Check if any positions are currently open
    if (PositionsTotal() == 0)
    {
        CheckTradingConditions();
    }
}

//+------------------------------------------------------------------+
//| Check trading conditions based on indicator buffers              |
//+------------------------------------------------------------------+
void CheckTradingConditions()
{
    // Resize buffers to get the latest data
    ArrayResize(ExtUpBuffer, 2);
    ArrayResize(ExtDnBuffer, 2);

    // Get the latest values from the Donchian Channel
    if (CopyBuffer(handle, 0, 0, 2, ExtUpBuffer) <= 0 || CopyBuffer(handle, 2, 0, 2, ExtDnBuffer) <= 0)
    {
        Print("Error reading indicator buffer. Error: ", GetLastError());
        return;
    }

    // Get the close price of the current candle
    double closePrice = iClose(Symbol(), Period(), 0);

    // Buy condition: Closing price is above the upper Donchian band
    if (closePrice > ExtUpBuffer[1])
    {
        double stopLoss = closePrice - pipsToStopLoss * _Point; // Calculate stop loss
        double takeProfit = closePrice + pipsToTakeProfit * _Point; // Calculate take profit
        OpenBuy(LotSize, stopLoss, takeProfit);
    }

    // Sell condition: Closing price is below the lower Donchian band
    if (closePrice < ExtDnBuffer[1])
    {
        double stopLoss = closePrice + pipsToStopLoss * _Point; // Calculate stop loss
        double takeProfit = closePrice - pipsToTakeProfit * _Point; // Calculate take profit
        OpenSell(LotSize, stopLoss, takeProfit);
    }
}

//+------------------------------------------------------------------+
//| Open a buy order                                                 |
//+------------------------------------------------------------------+
void OpenBuy(double lotSize, double stopLoss, double takeProfit)
{
    if (trade.Buy(lotSize, Symbol(), 0, stopLoss, takeProfit, "Buy Order"))
    {
        Print("Buy order placed: Symbol = ", Symbol(), ", LotSize = ", lotSize);
    }
    else
    {
        Print("Failed to open buy order. Error: ", GetLastError());
    }
}

//+------------------------------------------------------------------+
//| Open a sell order                                                |
//+------------------------------------------------------------------+
void OpenSell(double lotSize, double stopLoss, double takeProfit)
{
    if (trade.Sell(lotSize, Symbol(), 0, stopLoss, takeProfit, "Sell Order"))
    {
        Print("Sell order placed: Symbol = ", Symbol(), ", LotSize = ", lotSize);
    }
    else
    {
        Print("Failed to open sell order. Error: ", GetLastError());
    }
}

//+------------------------------------------------------------------+

在整合至趋势约束主代码前,先对突破型EA进行独立测试。需要特别说明的是,当前版本并非最终版,尚未实现与整体目标匹配的趋势约束逻辑。

将突破型EA添加到图表中

右键单击EA列表,选择“测试”以打开测试窗口。此处,您可以选择并设置EA进行测试。查看下方的性能表现。

使用测试器

突破型EA在策略测试器中的表现

完成得不错!我们成功地执行了订单,这是关键性的突破。现在,我们可以基于此框架优化盈利性并过滤掉无效交易。这也凸显了下一阶段的重要性,在这一阶段,我们将引入约束条件以剔除低概率的交易信号。


整合到趋势约束EA中

合并两个EA代码涉及将双方的功能合并,共享功能在最终的EA中成为主要部分。此外,每个EA的独特功能将扩大合并代码的总体规模和功能。例如,趋势约束EA和突破型EA中存在共同属性,我们将其整合到统一的程序中。参考以下代码段,突出了这些共享属性。

// We merge it to one
#property strict
#include <Trade\Trade.mqh>  // Include the trade library

现在,我们开始进行整合。 当将两个或多个策略合并到一个EA中时,保留整体交易逻辑的核心函数包括OnInit()OnTick(),以及仓位管理(例如OpenBuy()andOpenSell())。这些函数构成了EA的核心,分别负责指标初始化、实时市场分析和订单处理。

与此同时,来自唐奇安通道突破策略和趋势约束EA中的RSI与移动平均线趋势跟踪策略,用于扩展现有程序,并作为独立条件整合到OnTick()函数中。EA同时评估唐奇安通道的突破信号和RSI及移动平均线的趋势信号,使其能够更全面地应对市场条件。

通过整合这些独立的功能,增强了EA的决策能力,从而形成一个更稳健的交易策略,能够适应不同的市场动态。

1. 初始化函数

初始化函数(OnInit())至关重要,因为它为EA将要使用的两种交易策略设置必要的指标。当EA首次加载时会调用此函数,确保在任何交易操作开始之前,所有必要的组件都已准备就绪。它负责初始化相对强弱指数(RSI)和唐奇安通道指标。如果这些指标中的任何一个未能正确初始化,该函数将返回错误状态,阻止交易系统运行,避免潜在的市场风险。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
    // Initialize RSI handle
    rsi_handle = iRSI(_Symbol, PERIOD_CURRENT, RSI_Period, PRICE_CLOSE);
    if (rsi_handle == INVALID_HANDLE)
    {
        Print("Failed to create RSI indicator handle");
        return INIT_FAILED;
    }

    // Create a handle for the Donchian Channel
    handle = iCustom(Symbol(), Period(), "Free Indicators\\Donchian Channel", InpDonchianPeriod);
    if (handle == INVALID_HANDLE)
    {
        Print("Failed to load the Donchian Channel indicator. Error: ", GetLastError());
        return INIT_FAILED;
    }

    return INIT_SUCCEEDED;
}

2. 主执行逻辑

主要的执行逻辑在OnTick()函数中处理,该函数在每次市场出现新的tick时都会被调用。此函数是EA的核心,负责根据不断变化的市场条件协调评估各种交易策略。它会依次调用负责检查趋势跟踪策略和突破策略的函数。此外,还包含对过期订单的检查,通过确保没有已过时却仍然处于活跃状态的仓位,使得EA有效地管理风险。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    // Execute both strategies independently on each tick
    CheckTrendConstraintTrading();
    CheckBreakoutTrading();
    CheckOrderExpiration(); // Check for expired Trend Following orders
}

3. 模块化交易条件函数

趋势跟踪策略:

该函数在进行决策之前会检查是否存在未平仓头寸。如果没有任何未平仓头寸,它会获取当前的相对强弱指数(RSI)值,并计算短期和长期移动平均线以确定市场趋势。如果市场处于上升趋势且RSI显示超卖状态,它可能会下达买入订单。相反,如果市场处于下降趋势且RSI显示超买状态,它可能会下达卖出订单。如果存在现有的未平仓头寸,该函数会通过拖曳止损机制进行管理。

//+------------------------------------------------------------------+
//| Check and execute Trend Constraint EA trading logic              |
//+------------------------------------------------------------------+
void CheckTrendConstraintTrading()
{
    // Check if there are any positions open
    if (PositionsTotal() == 0)
    {
        // Get RSI value
        double rsi_value;
        double rsi_values[];
        if (CopyBuffer(rsi_handle, 0, 0, 1, rsi_values) <= 0)
        {
            Print("Failed to get RSI value");
            return;
        }
        rsi_value = rsi_values[0];

        // Calculate moving averages
        double ma_short = iMA(_Symbol, PERIOD_CURRENT, 50, 0, MODE_EMA, PRICE_CLOSE);
        double ma_long = iMA(_Symbol, PERIOD_CURRENT, 200, 0, MODE_EMA, PRICE_CLOSE);

        // Determine trend direction
        bool is_uptrend = ma_short > ma_long;
        bool is_downtrend = ma_short < ma_long;

        // Check for buy conditions
        if (is_uptrend && rsi_value < RSI_Oversold)
        {
            double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
            double stopLossPrice = currentPrice - StopLoss * _Point;
            double takeProfitPrice = currentPrice + TakeProfit * _Point;

            // Attempt to open a Buy order
            if (trade.Buy(Lots, _Symbol, 0, stopLossPrice, takeProfitPrice, "Trend Following Buy") > 0)
            {
                Print("Trend Following Buy order placed.");
            }
            else
            {
                Print("Error placing Trend Following Buy order: ", GetLastError());
            }
        }
        // Check for sell conditions
        else if (is_downtrend && rsi_value > RSI_Overbought)
        {
            double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            double stopLossPrice = currentPrice + StopLoss * _Point;
            double takeProfitPrice = currentPrice - TakeProfit * _Point;

            // Attempt to open a Sell order
            if (trade.Sell(Lots, _Symbol, 0, stopLossPrice, takeProfitPrice, "Trend Following Sell") > 0)
            {
                Print("Trend Following Sell order placed.");
            }
            else
            {
                Print("Error placing Trend Following Sell order: ", GetLastError());
            }
        }
    }
    else
    {
        // Implement Trailing Stop for open positions
        TrailingStopLogic();
    }
}

突破策略函数: 

该函数旨在根据唐奇安通道指标评估突破条件。它调整必要的缓冲区大小以捕获最新数据,并检查潜在的突破机会。该策略寻找特定的用于突破的价格水平,这可能表明价格在一个方向上有显著的移动。当满足这些条件时,EA将相应地执行订单。

/+-------------------------------------------------------------------+
//| Check and execute Breakout EA trading logic                      |
//+------------------------------------------------------------------+
void CheckBreakoutTrading()
{
    // Resize buffers to get the latest data
    ArrayResize(ExtUpBuffer, 2);
    ArrayResize(ExtDnBuffer, 2);

    // Get the latest values from the Donchian Channel
if (CopyBuffer(handle, 0, 0, 2, ExtUpBuffer) <= 0 || CopyBuffer(handle, 2, 0, 2, ExtDnBuffer) <= 0)
    {
        Print("Error reading Donchian Channel buffer. Error: ", GetLastError());
        return;
    }

    // Get the close price of the current candle
    double closePrice = iClose(Symbol(), Period(), 0);
    
    // Get the daily open and close for the previous day
    double lastOpen = iOpen(Symbol(), PERIOD_D1, 1);
    double lastClose = iClose(Symbol(), PERIOD_D1, 1);

    // Determine if the last day was bullish or bearish
    bool isBullishDay = lastClose > lastOpen; // Bullish if close > open
    bool isBearishDay = lastClose < lastOpen; // Bearish if close < open

    // Check if there are any open positions before executing breakout strategy
    if (PositionsTotal() == 0) // Only proceed if no positions are open
    {
        // Buy condition: Closing price is above the upper Donchian band on a bullish day
        if (closePrice > ExtUpBuffer[1] && isBullishDay)
        {
            double stopLoss = closePrice - pipsToStopLoss * _Point; // Calculate stop loss
            double takeProfit = closePrice + pipsToTakeProfit * _Point; // Calculate take profit
            OpenBreakoutBuyOrder(stopLoss, takeProfit);
        }

        // Sell condition: Closing price is below the lower Donchian band on a bearish day
        if (closePrice < ExtDnBuffer[1] && isBearishDay)
        {
            double stopLoss = closePrice + pipsToStopLoss * _Point; // Calculate stop loss
            double takeProfit = closePrice - pipsToTakeProfit * _Point; // Calculate take profit
            OpenBreakoutSellOrder(stopLoss, takeProfit);
        }
    }
}


 检查突破交易:

该函数从唐奇安通道指标中获取最新值以分析市场条件。确定当前K线的收盘价以及前一天的每日开盘价和收盘价。根据对这些价格的比较,判断前一天是看涨还是看跌。之后,该函数在执行突破策略之前会检查是否存在未平仓头寸。如果没有未平仓头寸,它会检查开仓条件(如果在看涨日收盘价高于上轨,则开多仓;如果在看跌日收盘价低于下轨,则开空仓)。在尝试下单之前,还会计算每笔交易的止损和获利水平。

//+------------------------------------------------------------------+
//| Open a buy order for the Breakout strategy                       |
//+------------------------------------------------------------------+
void OpenBreakoutBuyOrder(double stopLoss, double takeProfit)
{
    if (trade.Buy(LotSize, _Symbol, 0, stopLoss, takeProfit, "Breakout Buy"))
    {
        Print("Breakout Buy order placed.");
    }
    else
    {
        Print("Error placing Breakout Buy order: ", GetLastError());
    }
}

//+------------------------------------------------------------------+
//| Open a sell order for the Breakout strategy                      |
//+------------------------------------------------------------------+
void OpenBreakoutSellOrder(double stopLoss, double takeProfit)
{
    if (trade.Sell(LotSize, _Symbol, 0, stopLoss, takeProfit, "Breakout Sell"))
    {
        Print("Breakout Sell order placed.");
    }
    else
    {
        Print("Error placing Breakout Sell order: ", GetLastError());
    }
}

4. 订单到期检查

CheckOrderExpiration函数会检查所有未平仓头寸,以识别并关闭那些超出指定生命周期的头寸。这一功能对于保持交易环境的活跃、有效管理风险以及防止持仓时间过长而超出策略建议范围至关重要。该函数会检查每个头寸的magic数字,以确定其是否属于趋势跟踪策略,并将当前时间与头寸的开仓时间进行比较,以判断是否需要平仓。

//+------------------------------------------------------------------+
//| Check for expired Trend Following orders                         |
//+------------------------------------------------------------------+
void CheckOrderExpiration()
{
    for (int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if (PositionSelectByTicket(ticket))
        {
            long magicNumber = PositionGetInteger(POSITION_MAGIC);

            // Check if it's a Trend Following position
            if (magicNumber == MagicNumber)
            {
                datetime openTime = (datetime)PositionGetInteger(POSITION_TIME);
                if (TimeCurrent() - openTime >= OrderLifetime)
                {
                    // Attempt to close the position
                    if (trade.PositionClose(ticket))
                    {
                        Print("Trend Following position closed due to expiration.");
                    }
                    else
                    {
                        Print("Error closing position due to expiration: ", GetLastError());
                    }
                }
            }
        }
    }
}

5. 拖曳止损逻辑

TrailingStopLogic方法负责通过根据拖曳止损规则调整止损水平来管理现有的未平仓头寸。对于多头头寸,如果当前价格超过拖曳止损阈值,它会将止损位向上移动。对于空头头寸,当满足条件时,它会降低止损位。这种方法通过允许止损跟随有利的价格走势来锁定利润,从而在市场反转时降低亏损风险。

//+------------------------------------------------------------------+
//| Manage trailing stops for open positions                         |
//+------------------------------------------------------------------+
void TrailingStopLogic()
{
    for (int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if (PositionSelectByTicket(ticket))
        {
            double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_BID);
            double stopLoss = PositionGetDouble(POSITION_SL);

            // Update stop loss for long positions
            if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
            {
                if (currentPrice - stopLoss > TrailingStop * _Point || stopLoss == 0)
                {
                    trade.PositionModify(ticket, currentPrice - TrailingStop * _Point, PositionGetDouble(POSITION_TP));
                }
            }
            // Update stop loss for short positions
            else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
            {
                if (stopLoss - currentPrice > TrailingStop * _Point || stopLoss == 0)
                {
                    trade.PositionModify(ticket, currentPrice + TrailingStop * _Point, PositionGetDouble(POSITION_TP));
                }
            }
        }
    }
}

6. 清理函数

OnDeinit()函数作为清理例程,在EA从图表中被移除时自动执行。该函数负责释放任何已分配的资源或指标句柄,确保不会出现内存泄漏或悬挂引用。它用于确认EA已被正确地反初始化,并记录这一操作。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    // Release indicators and handles
    IndicatorRelease(rsi_handle);
    IndicatorRelease(handle);
    Print("Expert deinitialized.");
}


测试结果

下图展示了我们在策略测试器中的实测效果,新功能均正常运行。

趋势约束EA唐奇安突破和趋势跟踪

趋势约束EA:唐奇安通道突破和趋势跟踪策略


结论

我们的探讨涉及了如何通过将多种策略整合到一个EA中来适应不同的市场条件这一挑战。我们最初开发了一个小型突破型EA,以有效管理突破过程,然后将其整合到我们的主要趋势约束EA中。这种整合通过将突破策略与更高时间框架的市场情绪(特别是D1K线图分析)对齐,增强了EA的功能,减少了过度交易的执行。

在策略测试器中执行的每一笔交易都清晰地标注了所使用的策略,为理解其背后的运行机制提供了透明度和清晰度。可以说,要有效地解决一个复杂的问题,就需要将其分解为更小、更易于管理的组成部分,并逐步解决每一个部分,而我们正是通过先开发一个小型EA再进行整合来实现的。

尽管当前实现已展现出潜力,但它仍处于开发阶段,还存在诸多优化空间。EA作为一个教学工具,展示了不同的策略如何协同工作以实现更好的交易结果。我鼓励大家尝试使用提供的EA,并根据自己的交易策略进行修改。在我们共同探索算法交易中多种策略组合的可能性时,您的反馈至关重要。

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

附加的文件 |
BreakoutEA.mq5 (5.07 KB)
最近评论 | 前往讨论 (1)
ramontds
ramontds | 31 10月 2024 在 10:26
在这里学到了很多,非常感谢!
交易策略 交易策略
各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
如何使用 MetaTrader 和 Google Sheets 创建交易日志 如何使用 MetaTrader 和 Google Sheets 创建交易日志
使用 MetaTrader 和 Google Sheets 创建交易日志!您将学习如何通过 HTTP POST 同步您的交易数据,并使用 HTTP 请求来获取它。最后,您有一个交易日志,可以帮助您有效地跟踪您的交易。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
基于主成分的特征选择与降维 基于主成分的特征选择与降维
本文深入探讨了改进型前向选择成分分析(Forward Selection Component Analysis,FSCA)算法的实现,该算法灵感源自Luca Puggini和Sean McLoone在《前向选择成分分析:算法与应用》一文中所提出的研究。