English Русский Español Deutsch 日本語 Português
preview
构建K线趋势约束模型(第九部分):多策略智能交易系统(EA)(三)

构建K线趋势约束模型(第九部分):多策略智能交易系统(EA)(三)

MetaTrader 5示例 |
1 046 0
Clemence Benjamin
Clemence Benjamin

概述

在算法交易中,于既定趋势中精准捕捉最优入场点始终是一大挑战。许多策略因难以把握时机或频繁产生虚假信号,导致交易表现不佳。这一问题在日线趋势中尤为突出——微小波动可能严重干扰执行精度。

背离策略通过检测价格走势与动量指标之间的差异,提供了一种强效的过滤手段,可识别潜在反转或延续信号。将背离检测功能集成至趋势约束智能交易系统中,交易者能显著提升入场点定位的精准度。

该方法不仅优化了交易准确性,更结合了MQL5的强大功能,确保交易策略的稳定高效执行。在本文中,我们将探讨背离策略的基本原理,以及将其集成至MQL5智能交易系统的具体步骤,介绍通过新增交易执行条件对趋势约束智能交易系统进行优化升级,并通过回测结果展示其实际应用效果。

核心内容:

  1. 背离策略的基础原理
  2. 集成背离检测的步骤
  3. 增强型趋势约束智能交易系统:引入基于背离的新交易执行条件
  4. 回测结果分析和实际应用价值
  5. 结论


背离策略的基础原理

背离是技术分析中的核心概念,通过对比价格走势与指标方向,为交易者揭示潜在的价格反转或延续信号。 

背离的意义:

当资产价格走势与某一技术指标方向相反时,即形成背离,通常预示趋势减弱或即将反转。这一概念在识别趋势可能回调或彻底转向的时机时尤为有效。

背离的类型:

  1. 看涨背离:当资产价格创出新低,但指标(如RSI)显示更高低点时,表明下行趋势减弱。这可能意味着卖压正在消退,价格有望向上反弹。
  2. 看跌背离:当价格创出新高,但指标显示更低高点时,表明上行趋势减弱。这可能预示价格即将下跌。

背景核查:

背离是技术分析中的关键概念,深刻影响着市场行为与交易者策略。Bart和Masse(1981)在《意见分歧与风险》(Divergence of Opinion and Risk)一文中强调,市场观点的差异会加剧风险与价格波动,而这一观点恰恰印证了背离在技术分析中的核心作用。

Tilehnouei和Shivaraj(2013)的实证研究表明,在特定情境下,MACD等工具通过背离信号分析市场动能的表现可能优于RSI。这一研究揭示了不同指标在捕捉背离现象时的差异化优势。基于上述研究,结合RSI、MACD与价格行为等指标的交互验证,可显著强化背离策略在系统性交易框架中的实用性——这一结论亦得到行业多源数据支持。

下一部分,我们将进入实战环节,探讨如何在EA开发中具体实现背离策略。


集成背离检测的步骤

要将背离检测功能集成至MQL5 EA中,我们首先需通过iRSI()等函数计算相对强弱指数(RSI)值,并将其与价格走势进行对比分析。通过iHigh()iLow()函数在特定周期内识别价格极值点,为后续背离判断提供基础数据。在本项目中,我将背离分为两类:常规背离(反转型)与隐藏背离(延续型)。

常规背离:

常规背离用于提示潜在趋势反转,当价格创出新低而指标形成更高低点时,形成看涨形态;当价格创出新高而指标形成更低高点时,形成看跌形态。

// Regular divergence conditions in code
bool CheckRegularBearishDivergence(int period = 14, ENUM_TIMEFRAMES timeframe = PERIOD_H1)
{
    double priceHigh1 = iHigh(_Symbol, timeframe, 2);
    double priceHigh2 = iHigh(_Symbol, timeframe, 8);
    double rsiHigh1 = iRSI(_Symbol, timeframe, period, PRICE_CLOSE, 2);
    double rsiHigh2 = iRSI(_Symbol, timeframe, period, PRICE_CLOSE, 8);
    
    if(priceHigh1 > priceHigh2 && rsiHigh1 < rsiHigh2) return true;
    return false;
}

为了直观地呈现常规背离,以下两张图分别展示了看涨背离与看跌背离:

Boom 300指数H4看涨背离

Boom 300指数H4看涨背离:价格创出新低的B点,而RSI指标在D处出现更高的低点


Boom 300指数H4看跌背离

Boom 300指数H4看跌背离:价格形成更高的B点,而RSI在D处却出现更低的高点

隐藏背离

另一方面,隐藏背离则暗示趋势延续。在上升趋势中,若价格创出更高的低点,而指标却出现更低的低点,则形成隐藏看涨背离;在下降趋势中,若价格创出更低的高点,而指标却出现更高的高点,则形成隐藏看跌背离。

//RSI and Price Levels declaration and hidden divergence condition
bool CheckHiddenBullishDivergence(int period = 14, ENUM_TIMEFRAMES timeframe = PERIOD_H1)
{
    double priceLow1 = iLow(_Symbol, timeframe, 2);
    double priceLow2 = iLow(_Symbol, timeframe, 8);
    double rsiLow1 = iRSI(_Symbol, timeframe, period, PRICE_CLOSE, 2);
    double rsiLow2 = iRSI(_Symbol, timeframe, period, PRICE_CLOSE, 8);
    
    if(priceLow1 > priceLow2 && rsiLow1 < rsiLow2) return true;
    return false;
}

以下图片展示了看涨隐藏背离与看跌隐藏背离的形态。请务必对照前文描述,并在自己的行情图中练习识别同类模式。

Boom 300指数H4看涨隐藏背离

Boom 300指数H4:看涨隐藏背离

Boom 300指数H4看跌隐藏背离

Boom 300指数H4:看跌隐藏背离

将上述背离类型集成至EA需通过代码实现在每个tick或K线收盘时检测背离条件,并利用iRSI()或iMACD()等函数计算指标值。检测到背离信号后,需结合每日市场情绪识别的趋势约束条件进行信号过滤。


增强型趋势约束智能交易系统:引入基于背离的新交易执行条件

关于上述环节,当背离信号最终被确认时,需引入辅助指标作为订单执行的二次验证工具。可选指标众多,本项目将采用MACD与RSI组合验证。其他可选确认指标包括: 

  1. 布林带
  2. 随机振荡器
  3. 能量潮(OBV)和成交量加权平均价(VWAP)
  4. 平均动向指数(ADX)

移动平均收敛发散指标(MACD)说明:

选择原因:MACD可验证动能变化。当RSI检测到背离时,MACD可通过趋势强度或衰减的二次确认,提升信号的可靠性。 

其工作原理如下:

EA将监测与背离信号同步的MACD线交叉或柱状图变化。例如,若出现看跌背离,同时MACD线跌破信号线或柱状图开始收缩,则确认信号有效。

MACD指标核心特性:

如需预览MetaEditor 5内置的MACD等指标代码,可进入软件安装目录下的Examples/Indicators文件夹,请参考下图说明:

在MetaEditor 5中访问MACD源码

MetaEditor 5:访问MACD指标源文件

访问源码的目的是为了快速理解指标缓冲区(Buffers)的设计逻辑,从而便于在自定义EA中无缝调用和适配这些数据。以下为MACD指标中我们关注的缓冲区声明代码段。

//--- indicator buffers
double ExtMacdBuffer[];
double ExtSignalBuffer[];
double ExtFastMaBuffer[];
double ExtSlowMaBuffer[];

int    ExtFastMaHandle;
int    ExtSlowMaHandle;

现在,请注意缓冲区,让我们按以下步骤逐步开发。

背离策略开发: 

步骤1:参数声明与输入配置

 首先,为背离策略声明输入参数,设置MACD缓冲区,并初始化CTrade类。

#include <Trade\Trade.mqh>
CTrade trade;

input bool UseDivergenceStrategy = true;        // Enable/Disable Divergence Strategy
input int DivergenceMACDPeriod = 12;            // MACD Fast EMA period
input int DivergenceSignalPeriod = 9;           // MACD Signal period
input double DivergenceLots = 1.0;              // Lot size for Divergence trades
input double DivergenceStopLoss = 300;          // Stop Loss in points for Divergence
input double DivergenceTakeProfit = 500;        // Take Profit in points for Divergence
input int DivergenceMagicNumber = 87654321;     // Magic number for Divergence Strategy
input int DivergenceLookBack = 8;               // Number of periods to look back for divergence

double ExtMacdBuffer[]; // MACD values
double ExtSignalBuffer[]; // Signal line values
int macd_handle; // MACD indicator handle

步骤2:初始化MACD指标

在OnInit()函数中初始化MACD指标句柄并且分配缓冲区内存。

int OnInit()
{
    macd_handle = iMACD(_Symbol, PERIOD_CURRENT, DivergenceMACDPeriod, 26, DivergenceSignalPeriod, PRICE_CLOSE);
    if (macd_handle == INVALID_HANDLE)
    {
        Print("Failed to initialize MACD. Error: ", GetLastError());
        return INIT_FAILED;
    }
    ArrayResize(ExtMacdBuffer, DivergenceLookBack);
    ArrayResize(ExtSignalBuffer, DivergenceLookBack);
    return INIT_SUCCEEDED;
}

步骤3:背离信号检测

当资产价格走势与指标(本例中为MACD)出现方向性分歧时,即形成背离信号。本策略可识别以下四种背离类型:常规看涨背离、隐藏看涨背离、常规看跌背离和隐藏看跌背离。每种背离类型均需满足特定的条件,即通过对比价格的高点或低点与对应MACD指标的高点或低点,以判断背离信号是否存在。

bool CheckBullishRegularDivergence()
{
    double priceLow1 = iLow(_Symbol, PERIOD_CURRENT, 2);
    double priceLow2 = iLow(_Symbol, PERIOD_CURRENT, DivergenceLookBack);
    double macdLow1 = ExtMacdBuffer[2];
    double macdLow2 = ExtMacdBuffer[DivergenceLookBack - 1];
    return (priceLow1 < priceLow2 && macdLow1 > macdLow2);
}

bool CheckBearishHiddenDivergence()
{
    double priceHigh1 = iHigh(_Symbol, PERIOD_CURRENT, 2);
    double priceHigh2 = iHigh(_Symbol, PERIOD_CURRENT, DivergenceLookBack);
    double macdHigh1 = ExtMacdBuffer[2];
    double macdHigh2 = ExtMacdBuffer[DivergenceLookBack - 1];
    return (priceHigh1 < priceHigh2 && macdHigh1 > macdHigh2);
}

步骤4:交易逻辑

首先,该策略会确认背离交易已启用,且当前基于背离信号的开仓数不超过三笔。它会获取MACD缓冲区数据,一旦获取失败会重试,并仅在完整K线上执行交易。此外,策略还将交易与日线K线趋势对齐,确保日线看涨时只做多,日线看跌时只做空。

void CheckDivergenceTrading()
{
    if (!UseDivergenceStrategy) return;

    int openDivergencePositions = CountOrdersByMagic(DivergenceMagicNumber);
    if (openDivergencePositions == 0 || openDivergencePositions < 3)
    {
        if (CopyBuffer(macd_handle, 0, 0, DivergenceLookBack, ExtMacdBuffer) > 0 &&
            CopyBuffer(macd_handle, 1, 0, DivergenceLookBack, ExtSignalBuffer) > 0)
        {
            double dailyClose = iClose(_Symbol, PERIOD_D1, 0);
            double dailyOpen = iOpen(_Symbol, PERIOD_D1, 0);
            bool isDailyBullish = dailyClose > dailyOpen;
            bool isDailyBearish = dailyClose < dailyOpen;

            if (isDailyBullish && 
                (CheckBullishRegularDivergence() && ExtMacdBuffer[0] > ExtSignalBuffer[0]) || 
                CheckBullishHiddenDivergence())
            {
                ExecuteDivergenceOrder(true);
            }

            if (isDailyBearish && 
                (CheckBearishRegularDivergence() && ExtMacdBuffer[0] < ExtSignalBuffer[0]) || 
                CheckBearishHiddenDivergence())
            {
                ExecuteDivergenceOrder(false);
            }
        }
    }
}

步骤5:下单执行

一旦检测到背离,策略即用预设的“手数、止损、止盈”等参数执行交易。ExecuteDivergenceOrder函数会根据交易方向计算合适的价位,并通过交易对象下达买单或卖单。

void ExecuteDivergenceOrder(bool isBuy)
{
    trade.SetExpertMagicNumber(DivergenceMagicNumber);

    double currentPrice = isBuy ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);
    double stopLossPrice = isBuy ? currentPrice - DivergenceStopLoss * _Point : currentPrice + DivergenceStopLoss * _Point;
    double takeProfitPrice = isBuy ? currentPrice + DivergenceTakeProfit * _Point : currentPrice - DivergenceTakeProfit * _Point;

    if (isBuy)
    {
        if (trade.Buy(DivergenceLots, _Symbol, 0, stopLossPrice, takeProfitPrice, "Divergence Buy"))
            Print("Divergence Buy order placed.");
    }
    else
    {
        if (trade.Sell(DivergenceLots, _Symbol, 0, stopLossPrice, takeProfitPrice, "Divergence Sell"))
            Print("Divergence Sell order placed.");
    }
}

步骤6:订单管理

为防止过度交易,策略使用CountOrdersByMagic工具函数统计所有带指定magic编号的持仓数量。这样可以确保背离类交易不超过设定的最大持仓上限。

int CountOrdersByMagic(int magic)
{
    int count = 0;
    for (int i = 0; i < PositionsTotal(); i++)
    {
        ulong ticket = PositionGetTicket(i);
        if (PositionSelectByTicket(ticket))
        {
            if (PositionGetInteger(POSITION_MAGIC) == magic)
            {
                count++;
            }
        }
    }
    return count;
}

magic编号: 

另一个关键要点是为我们的持仓赋予唯一身份。在此,我们为背离策略所管理的所有交易分配了一个唯一的magic编号。

    // Ensure the magic number is set for the trade
    trade.SetExpertMagicNumber(DivergenceMagicNumber);

步骤7:盈利保护

我们为趋势约束型智能交易系统引入了盈利保护功能,该功能集成动态利润锁定逻辑,可实时保障已开仓位的盈利安全。LockProfits函数会扫描所有活跃持仓,识别盈利超过100点阈值的仓位。针对每个符合条件的仓位,系统根据profitLockerPoints参数(例如:距入场价20点)计算新的止损价位。

此调整将止损位向当前价格方向移动,实现盈利锁定效果。对于买单,止损位被上移至入场价上方;对于卖单,止损位则被下移至入场价下方。函数仅在新止损位能提供更优保护时更新,确保风险控制最优化。成功修改后会记录日志以便追踪。该功能在保障既有利润的同时,为仓位保留进一步盈利空间。

以下是利润锁定函数代码:

//+------------------------------------------------------------------+
//| Profit Locking Logic                                             |
//+------------------------------------------------------------------+
void LockProfits()
{
    for (int i = PositionsTotal() - 1; i >= 0; i--)
    {
        ulong ticket = PositionGetTicket(i);
        if (PositionSelectByTicket(ticket))
        {
            double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN);
            double currentProfit = PositionGetDouble(POSITION_PROFIT);
            double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
            
            // Convert profit to points
            double profitPoints = MathAbs(currentProfit / _Point);

            // Check if profit has exceeded 100 points
            if (profitPoints >= 100)
            {
                double newStopLoss;
                
                if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                {
                    newStopLoss = entryPrice + profitLockerPoints * _Point; // 20 points above entry for buys
                }
                else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
                {
                    newStopLoss = entryPrice - profitLockerPoints * _Point; // 20 points below entry for sells
                }
                else
                {
                    continue; // Skip if not a buy or sell position
                }

                // Modify stop loss only if the new stop loss is more protective
                double currentStopLoss = PositionGetDouble(POSITION_SL);
                if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                {
                    if (currentStopLoss < newStopLoss || currentStopLoss == 0)
                    {
                        if (trade.PositionModify(ticket, newStopLoss, PositionGetDouble(POSITION_TP)))
                        {
                            Print("Profit locking for buy position: Stop Loss moved to ", newStopLoss);
                        }
                    }
                }
                else // POSITION_TYPE_SELL
                {
                    if (currentStopLoss > newStopLoss || currentStopLoss == 0)
                    {
                        if (trade.PositionModify(ticket, newStopLoss, PositionGetDouble(POSITION_TP)))
                        {
                            Print("Profit locking for sell position: Stop Loss moved to ", newStopLoss);
                        }
                    }
                }
            }
        }
    }
}

步骤8:集成至OnTick()主循环

在EA的核心循环中调用背离交易逻辑模块。

void OnTick()
{
    CheckDivergenceTrading();
}

步骤9:系统关闭处理

void OnDeinit(const int reason)
{
    IndicatorRelease(macd_handle);
}

将该策略集成到主趋势约束智能交易系统。

从本系列上一篇文章中可知,我们已经开发了一个基于唐奇安通道的EA,该EA同时包含两种策略。今天,我们引入第3种策略,并用布尔变量来控制各策略的启用和禁用。

// Input parameters for controlling strategies
input bool UseTrendFollowingStrategy = false;   // Enable/Disable Trend Constraint Strategy
input bool UseBreakoutStrategy = false;         // Enable/Disable Breakout Strategy
input bool UseDivergenceStrategy = true;        // Enable/Disable Divergence Strategy

我们将背离策略的启用状态设置为true,以避免在策略测试过程中产生混淆。

最终,我们需将策略严谨地整合至主程序的代码中:

//+------------------------------------------------------------------+
//|                                      Trend Constraint Expert.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.02"

#include <Trade\Trade.mqh>
CTrade trade;

// Input parameters for controlling strategies
input bool UseTrendFollowingStrategy = false;   // Enable/Disable Trend Following Strategy
input bool UseBreakoutStrategy = false;         // Enable/Disable Breakout Strategy
input bool UseDivergenceStrategy = true;        // Enable/Disable Divergence Strategy

// Input parameters for Trend Constraint Strategy
input int    RSI_Period = 14;           // RSI period
input double RSI_Overbought = 70.0;     // RSI overbought level
input double RSI_Oversold = 30.0;       // RSI oversold level
input double Lots = 0.1;                // Lot size
input double StopLoss = 100;            // Stop Loss in points
input double TakeProfit = 200;          // Take Profit in points
input double TrailingStop = 50;         // Trailing Stop in points
input int    MagicNumber = 12345678;    // Magic number for the Trend Constraint EA
input int    OrderLifetime = 43200;     // Order lifetime in seconds (12 hours)

// Input parameters for Breakout Strategy
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 for Breakout
input double pipsToTakeProfit = 30;     // Take profit in pips for Breakout

// Input parameters for Divergence Strategy
input int DivergenceMACDPeriod = 12;    // MACD Fast EMA period
input int DivergenceSignalPeriod = 9;   // MACD Signal period
input double DivergenceLots = 1.0;      // Lot size for Divergence trades
input double DivergenceStopLoss = 300;   // Stop Loss in points for Divergence
input double DivergenceTakeProfit = 500; // Take Profit in points for Divergence
input int DivergenceMagicNumber = 87654321;     // Magic number for Divergence Strategy
input int DivergenceLookBack = 8;       // Number of periods to look back for divergence
input double profitLockerPoints  = 20;  // Number of profit points to lock 

// Indicator handle storage
int rsi_handle;                         
int handle;                             // Handle for Donchian Channel
int macd_handle;


double ExtUpBuffer[];                   // Upper Donchian buffer
double ExtDnBuffer[];                   // Lower Donchian buffer
double ExtMacdBuffer[];                 // MACD buffer
double ExtSignalBuffer[];               // Signal buffer
int globalMagicNumber;

//+------------------------------------------------------------------+
//| 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. Error: ", GetLastError());
        return INIT_FAILED;
    }

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

    // Initialize MACD handle for divergence
    globalMagicNumber = DivergenceMagicNumber;
    macd_handle = iMACD(_Symbol, PERIOD_CURRENT, DivergenceMACDPeriod, 26, DivergenceSignalPeriod, PRICE_CLOSE);
    if (macd_handle == INVALID_HANDLE)
    {
        Print("Failed to create MACD indicator handle for divergence strategy. Error: ", GetLastError());
        return INIT_FAILED;
    }

    // Resize arrays for MACD buffers
    ArrayResize(ExtMacdBuffer, DivergenceLookBack);
    ArrayResize(ExtSignalBuffer, DivergenceLookBack);

    return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
    IndicatorRelease(rsi_handle);
    IndicatorRelease(handle);
    IndicatorRelease(macd_handle);
}

//+------------------------------------------------------------------+
//| Check and execute Trend Following EA trading logic               |
//+------------------------------------------------------------------+
void CheckTrendFollowing()
{
    if (PositionsTotal() >= 2) return; // Ensure no more than 2 orders from this strategy

    double rsi_value;
    double rsi_values[];
    if (CopyBuffer(rsi_handle, 0, 0, 1, rsi_values) <= 0)
    {
        Print("Failed to get RSI value. Error: ", GetLastError());
        return;
    }
    rsi_value = rsi_values[0];

    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);

    bool is_uptrend = ma_short > ma_long;
    bool is_downtrend = ma_short < ma_long;

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

        // Corrected Buy method call with 6 parameters
        if (trade.Buy(Lots, _Symbol, 0, stopLossPrice, takeProfitPrice, "Trend Following Buy"))
        {
            Print("Trend Following Buy order placed.");
        }
    }
    else if (is_downtrend && rsi_value > RSI_Overbought)
    {
        double currentPrice = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
        double stopLossPrice = currentPrice + StopLoss * _Point;
        double takeProfitPrice = currentPrice - TakeProfit * _Point;

        // Corrected Sell method call with 6 parameters
        if (trade.Sell(Lots, _Symbol, 0, stopLossPrice, takeProfitPrice, "Trend Following Sell"))
        {
            Print("Trend Following Sell order placed.");
        }
    }
}

//+------------------------------------------------------------------+
//| Check and execute Breakout EA trading logic                      |
//+------------------------------------------------------------------+
void CheckBreakoutTrading()
{
    if (PositionsTotal() >= 2) return; // Ensure no more than 2 orders from this strategy

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

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

    double closePrice = iClose(_Symbol, PERIOD_CURRENT, 0);
    double lastOpen = iOpen(_Symbol, PERIOD_D1, 1);
    double lastClose = iClose(_Symbol, PERIOD_D1, 1);

    bool isBullishDay = lastClose > lastOpen;
    bool isBearishDay = lastClose < lastOpen;

    if (isBullishDay && closePrice > ExtUpBuffer[1])
    {
        double stopLoss = closePrice - pipsToStopLoss * _Point;
        double takeProfit = closePrice + pipsToTakeProfit * _Point;
        if (trade.Buy(LotSize, _Symbol, 0, stopLoss, takeProfit, "Breakout Buy") > 0)
        {
            Print("Breakout Buy order placed.");
        }
    }
    else if (isBearishDay && closePrice < ExtDnBuffer[1])
    {
        double stopLoss = closePrice + pipsToStopLoss * _Point;
        double takeProfit = closePrice - pipsToTakeProfit * _Point;
        if (trade.Sell(LotSize, _Symbol, 0, stopLoss, takeProfit, "Breakout Sell") > 0)
        {
            Print("Breakout Sell order placed.");
        }
    }
}

//+------------------------------------------------------------------+
//| DIVERGENCE TRADING STRATEGY                                      |
//+------------------------------------------------------------------+

bool CheckBullishRegularDivergence()
{
    double priceLow1 = iLow(_Symbol, PERIOD_CURRENT, 2);
    double priceLow2 = iLow(_Symbol, PERIOD_CURRENT, DivergenceLookBack);
    double macdLow1 = ExtMacdBuffer[2];
    double macdLow2 = ExtMacdBuffer[DivergenceLookBack - 1];

    return (priceLow1 < priceLow2 && macdLow1 > macdLow2);
}

bool CheckBullishHiddenDivergence()
{
    double priceLow1 = iLow(_Symbol, PERIOD_CURRENT, 2);
    double priceLow2 = iLow(_Symbol, PERIOD_CURRENT, DivergenceLookBack);
    double macdLow1 = ExtMacdBuffer[2];
    double macdLow2 = ExtMacdBuffer[DivergenceLookBack - 1];

    return (priceLow1 > priceLow2 && macdLow1 < macdLow2);
}

bool CheckBearishRegularDivergence()
{
    double priceHigh1 = iHigh(_Symbol, PERIOD_CURRENT, 2);
    double priceHigh2 = iHigh(_Symbol, PERIOD_CURRENT, DivergenceLookBack);
    double macdHigh1 = ExtMacdBuffer[2];
    double macdHigh2 = ExtMacdBuffer[DivergenceLookBack - 1];

    return (priceHigh1 > priceHigh2 && macdHigh1 < macdHigh2);
}

bool CheckBearishHiddenDivergence()
{
    double priceHigh1 = iHigh(_Symbol, PERIOD_CURRENT, 2);
    double priceHigh2 = iHigh(_Symbol, PERIOD_CURRENT, DivergenceLookBack);
    double macdHigh1 = ExtMacdBuffer[2]; 
    double macdHigh2 = ExtMacdBuffer[DivergenceLookBack - 1];

    return (priceHigh1 < priceHigh2 && macdHigh1 > macdHigh2);
}

void CheckDivergenceTrading()
{
    if (!UseDivergenceStrategy) return;

    // Check if no position is open or if less than 3 positions are open
    int openDivergencePositions = CountOrdersByMagic(DivergenceMagicNumber);
    if (openDivergencePositions == 0 || openDivergencePositions < 3)
    {
        int barsAvailable = Bars(_Symbol, PERIOD_CURRENT);
        if (barsAvailable < DivergenceLookBack * 2)
        {
            Print("Not enough data bars for MACD calculation.");
            return;
        }

        int attempt = 0;
        while(attempt < 6)
        {
            if (CopyBuffer(macd_handle, 0, 0, DivergenceLookBack, ExtMacdBuffer) > 0 &&
                CopyBuffer(macd_handle, 1, 0, DivergenceLookBack, ExtSignalBuffer) > 0)
                break; 
            
            Print("Failed to copy MACD buffer, retrying...");
            Sleep(1000);
            attempt++;
        }
        if(attempt == 6)
        {
            Print("Failed to copy MACD buffers after ", attempt, " attempts.");
            return;
        }

        if(TimeCurrent() == iTime(_Symbol, PERIOD_CURRENT, 0))
        {
            Print("Skipping trade due to incomplete bar data.");
            return;
        }

        double currentClose = iClose(_Symbol, PERIOD_CURRENT, 0);
        double dailyClose = iClose(_Symbol, PERIOD_D1, 0);
        double dailyOpen = iOpen(_Symbol, PERIOD_D1, 0);
        bool isDailyBullish = dailyClose > dailyOpen;
        bool isDailyBearish = dailyClose < dailyOpen;

        // Only proceed with buy orders if D1 is bullish
        if (isDailyBullish)
        {
            if ((CheckBullishRegularDivergence() && ExtMacdBuffer[0] > ExtSignalBuffer[0]) ||
                CheckBullishHiddenDivergence())
            {
                ExecuteDivergenceOrder(true);
            }
        }

        // Only proceed with sell orders if D1 is bearish
        if (isDailyBearish)
        {
            if ((CheckBearishRegularDivergence() && ExtMacdBuffer[0] < ExtSignalBuffer[0]) ||
                CheckBearishHiddenDivergence())
            {
                ExecuteDivergenceOrder(false);
            }
        }
    }
    else
    {
        Print("Divergence strategy: Maximum number of positions reached.");
    }
}

void ExecuteDivergenceOrder(bool isBuy)
{
    // Ensure the magic number is set for the trade
    trade.SetExpertMagicNumber(DivergenceMagicNumber);
    
    double currentPrice = isBuy ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);
    double stopLossPrice = isBuy ? currentPrice - DivergenceStopLoss * _Point : currentPrice + DivergenceStopLoss * _Point;
    double takeProfitPrice = isBuy ? currentPrice + DivergenceTakeProfit * _Point : currentPrice - DivergenceTakeProfit * _Point;

    if (isBuy)
    {
        if (trade.Buy(DivergenceLots, _Symbol, 0, stopLossPrice, takeProfitPrice, "Divergence Buy"))
        {
            Print("Divergence Buy order placed.");
        }
    }
    else
    {
        if (trade.Sell(DivergenceLots, _Symbol, 0, stopLossPrice, takeProfitPrice, "Divergence Sell"))
        {
            Print("Divergence Sell order placed.");
        }
    }
}

int CountOrdersByMagic(int magic)
{
    int count = 0;
    for (int i = 0; i < PositionsTotal(); i++)
    {
        ulong ticket = PositionGetTicket(i);
        if (PositionSelectByTicket(ticket))
        {
            if (PositionGetInteger(POSITION_MAGIC) == magic)
            {
                count++;
            }
        }
    }
    return count;
}
//+------------------------------------------------------------------+
//| Profit Locking Logic                                             |
//+------------------------------------------------------------------+
void LockProfits()
{
    for (int i = PositionsTotal() - 1; i >= 0; i--)

    {
        ulong ticket = PositionGetTicket(i);
        if (PositionSelectByTicket(ticket))
        {
            double entryPrice = PositionGetDouble(POSITION_PRICE_OPEN);
            double currentProfit = PositionGetDouble(POSITION_PROFIT);
            double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
            
            // Convert profit to points
            double profitPoints = MathAbs(currentProfit / _Point);

            // Check if profit has exceeded 100 points
            if (profitPoints >= 100)
            {
                double newStopLoss;
                
                if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                {
                    newStopLoss = entryPrice + profitLockerPoints * _Point; // 20 points above entry for buys
                }
                else if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)
                {
                    newStopLoss = entryPrice - profitLockerPoints * _Point; // 20 points below entry for sells
                }
                else
                {
                    continue; // Skip if not a buy or sell position
                }

                // Modify stop loss only if the new stop loss is more protective
                double currentStopLoss = PositionGetDouble(POSITION_SL);
                if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
                {
                    if (currentStopLoss < newStopLoss || currentStopLoss == 0)
                    {
                        if (trade.PositionModify(ticket, newStopLoss, PositionGetDouble(POSITION_TP)))
                        {
                            Print("Profit locking for buy position: Stop Loss moved to ", newStopLoss);
                        }
                    }
                }
                else // POSITION_TYPE_SELL
                {
                    if (currentStopLoss > newStopLoss || currentStopLoss == 0)
                    {
                        if (trade.PositionModify(ticket, newStopLoss, PositionGetDouble(POSITION_TP)))
                        {
                            Print("Profit locking for sell position: Stop Loss moved to ", newStopLoss);
                        }
                    }
                }
            }
        }
    }
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
    if (UseTrendFollowingStrategy) CheckTrendFollowing();
    if (UseBreakoutStrategy) CheckBreakoutTrading();
    if (UseDivergenceStrategy) CheckDivergenceTrading();
    LockProfits(); // Call this function to check and lock profits
}


回测结果与实战应用

要进行测试,需在交易终端的“Experts”栏目下找到 趋势约束型智能交易系统。请您务必确保在模拟账户测试运行。打开策略测试器(Strategy Tester)窗口后,可通过调整输入参数实现不同场景的优化。下图展示了默认参数设置下的界面示例:

输入设置

趋势约束型智能交易系统:模拟账户参数配置 

我们在策略测试器中运行了回测,所有交易均成功执行。系统将每轮交易中的最大持仓数量限制为三笔,有效避免了无节制的订单堆积问题。下图展示了回测过程中的部分交易记录:

测试中的EA

趋势约束型智能交易系统:欧元兑美元(EURUSD)M15时间框架测试

下图表明,我们的持仓管理功能正按预期运行。根据实现逻辑,最多仅保留三笔订单,每笔均带有“Divergence Sell”注释,易于识别且完全符合策略设定。


趋势约束型智能交易系统:每次最大持仓数量为三笔


结论

我们深入地探讨了多种类型的背离形态,并将其以代码形式整合至一套协同策略中——该策略融合了RSI、MACD等指标,以生成精准的交易信号。为进一步提升信号的可靠性,我们引入了日线级K线趋势约束条件,确保所有交易信号均与更广泛的市场趋势保持一致。当前,我们的趋势约束型智能交易系统已具备三大独立且可配置的交易策略模块,用户可根据自身偏好及市场环境灵活调整参数组合。

在交易管理优化方面,我们为每笔订单分配了唯一标识符(MAGIC编号),实现了对持仓订单的精准管控,并限制了单策略的最大持仓数量。此外,我们开发了自定义的利润锁定功能:当市场在触及止盈目标前出现反转时,系统将动态调整止损价位,确保已获利润不被侵蚀。这一设计兼顾了风险控制与策略灵活性,使EA具备更强的稳健性与适应性。下方附上EA的完整源代码文件。欢迎下载使用、测试不同参数配置,并在评论区分享您的使用反馈。重要提示:本示例仅供教学用途,所有测试务必在模拟账户中进行。

祝各位交易者开发顺利!

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

附加的文件 |
您应当知道的 MQL5 向导技术(第 50 部分):动量振荡器 您应当知道的 MQL5 向导技术(第 50 部分):动量振荡器
动量振荡器是另一个用于衡量动量的比尔·威廉姆斯(Bill Williams)指标。它能生成多个信号,因此我们像之前的文章一样,利用 MQL5 向导类和汇编,在形态基础上审查这些信号。
开发多币种 EA 交易(第 19 部分):创建用 Python 实现的阶段 开发多币种 EA 交易(第 19 部分):创建用 Python 实现的阶段
到目前为止,我们已经探讨了仅在标准策略测试器中启动顺序程序以优化 EA 的自动化。但是,如果我们想在两次启动之间使用其他方法对获得的数据进行一些处理呢?我们将尝试添加创建由用 Python 编写的程序执行的新优化阶段的功能。
构建MQL5自优化智能交易系统(第二部分):美元兑日元(USDJPY)剥头皮策略 构建MQL5自优化智能交易系统(第二部分):美元兑日元(USDJPY)剥头皮策略
今天我们齐聚一堂,挑战为美元兑日元(USDJPY)货币对打造一套全新交易策略。我们将基于日线图上的K线形态开发交易策略,因为日线级别的信号通常蕴含更强的市场动能。初始策略已实现盈利,这激励我们进一步优化策略,并增加风险控制层以保护已获利资本。
您应当知道的 MQL5 向导技术(第 49 部分):搭配近端政策优化的强化学习 您应当知道的 MQL5 向导技术(第 49 部分):搭配近端政策优化的强化学习
近端政策优化是强化学习中的另一种算法,通常以网络形式以非常小的增量步幅更新政策,以便确保模型的稳定性。我们以向导汇编的智能系统来试验其作用,如同我们之前的文章一样。