
构建MQL5自优化智能交易系统(第二部分):美元兑日元(USDJPY)剥头皮策略
在上一篇关于MQL5自优化智能交易系统的文章中(点击此处回顾),我们通过线性回归模型生成交易信号。如今反思,或许我们无需依赖复杂的机器学习模型。倒不如把机器学习视为“如何用动态规则解决现实问题”的范例,然后我们以同样简洁的思维与逻辑,引导交易应用迈向更高盈利,而无需维护庞大代码库。
今日主题:在日线周期上稳健交易美元兑日元。策略核心:基于K线形态。具体而言,我们将基于K线反转形态(吞没形态)进行交易。看涨吞没(Bullish Engulfing)判定规则:如果当日开盘价低于前一日收盘价,且当日收盘价高于前一日开盘价,则视为看涨吞没。下图1给出了示例。此类K线形态被认为在某一价位水平遭遇了强劲的拒绝力量,预示反转。
图例1:看涨K线形态示例解析
交易者普遍认为,吞没形态是市场趋势即将反转的强烈信号。如果能够正确识别形态,价格通常会沿着新趋势方向持续进行(详见图2)。这构成了我们交易策略的核心逻辑:精准捕捉这类反转形态。看跌吞没形态同理,其判定规则与看涨形态相反,可用于识别下跌趋势的起点。
图例2:在此特例中,K线图形态有效性验证
通常认为这类策略所有时间周期都有效。然而,我更青睐日线周期,其信号相对最为可靠,因此将其作为本次操练的首选周期。接下来,让我们着手实现一套策略,依据对这些特定市场形态的理解来生成入场信号。我们也将关注于通过对原始策略进行调整,观察是否能够进一步提升盈利能力。
MQL5入门指南
为实现基于K线形态的交易盈利目标,我们的程序将包含以下6个核心模块:
部分 | 目的 |
---|---|
初始化 | 该部分负责加载并设置全局变量。 |
反初始化 | 释放应用不再占用的资源,确保终端用户体验稳定。 |
OnTick | 更新系统变量,并在当前图表上扫描我们的K线形态。 |
自定义函数 | 执行实现目标所需的专项任务。 |
系统常量 | 终端用户不可更改的常量。 |
全局变量 | 持续记录最近一次下单的方向、当前市场报价以及波动水平。为此我们创建了诸如 “trade” 与 “bid” 等变量。平均真实波幅(ATR)将用于为持仓设置止损与止盈。 |
初始阶段,策略只在发现K线形态时才下单。若形态出现且当前无持仓,则执行信号,并通过ATR设置调整止损。否则,系统仅管理已开仓的交易。因此,系统的全局变量如下:
变量 | 描述 |
---|---|
交易 | 用于识别我们当前已开仓的持仓方向,方便后续动态调整止损价位或执行其他交易逻辑 |
atr_handler | 确保止损能够持续更新。 |
bid, ask | 实时跟踪市场价格。 |
首先,我们将构建策略的基准版本。让我们先从定义系统常量开始。这些常量使用#define指令创建。#define指令会指示嵌入在MQL5编辑器中的预处理器,将代码中所有出现的宏标识符自动替换为其右侧指定的赋值内容。
我们定义的首个系统常量是 “SYMBOL”。编译时,预处理器会把代码里所有 “SYMBOL” 替换为 “USDJPY”。这一简单机制让我们对系统的控制达到完全且可预测,并确保所有测试保持一致,这正是我们所期望的特性。
//+------------------------------------------------------------------+ //| Dynamic Stops Benchmark.mq5 | //| Gamuchirai Zororo Ndawana | //| https://www.mql5.com/en/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Zororo Ndawana" #property link "https://www.mql5.com/en/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| This trading application is intended to serve as our benchmark. | //| Our goal is to learn what it will take to surpass the benchmark. | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| System constants | //+------------------------------------------------------------------+ #define SYMBOL "USDJPY" //--- System pair #define DAILY PERIOD_D1 //--- Daily time frame #define VOL 0.1 //--- Trading volume #define ATR_PERIOD 14 //--- Technical Indicator ATR Period #define ATR_MULTIPLE 2 //--- Stop loss ATR multiple
我们还需要加载交易库。
//+------------------------------------------------------------------+ //| Libraries | //+------------------------------------------------------------------+ #include <Trade/Trade.mqh> CTrade Trade;
现在我们将定义全局变量。这些变量将帮助我们跟踪我们的未平仓头寸和当前市场报价。
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int trade = 0; int atr_handler; double atr[]; double bid,ask;
初始化时,我们将调用一个专门的函数来初始化系统变量。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- setup(); //--- return(INIT_SUCCEEDED); }
若我们不再使用交易应用程序,则需要释放所有不再使用的技术指标资源。
//+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Release the indicator release(); }
我们将在每日交易结束时统一更新一次系统变量。此设置可根据您的风险管理需求灵活调整。我当前选择每日更新一次,是为了确保回测能够快速完成,从而更高效地对比策略修改的不同效果。
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- update(); } //+------------------------------------------------------------------+
我们构建的第一个自定义函数,将用于释放所有不再使用的系统资源。
//+------------------------------------------------------------------+ //| Custom Functions | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Release variables we don't need | //+------------------------------------------------------------------+ void release(void) { IndicatorRelease(atr_handler); }
每日更新一次系统变量。
//+------------------------------------------------------------------+ //| Update system | //+------------------------------------------------------------------+ void update(void) { static datetime daily_timestamp; datetime daily_time = iTime(SYMBOL,DAILY,0); if(daily_timestamp != daily_time) { //--- Update the time daily_timestamp = daily_time; //--- Update system variables daily_update(); //--- Do we have an oppurtunity to trade? if((PositionsTotal() == 0)) find_setup(); //--- Do we have positions to manage? if(PositionsTotal() > 0) manage_setup(); } }
仅在新的止损或止盈设定能带来更高盈利的前提下,我们才对持仓的止损与止盈进行更新。
//+------------------------------------------------------------------+ //| Manage our trades | //+------------------------------------------------------------------+ void manage_setup(void) { //--- Select the position if(PositionSelect(SYMBOL)) { //--- Get ready to update the SL/TP double initial_sl = PositionGetDouble(POSITION_SL); double initial_tp = PositionGetDouble(POSITION_TP); double buy_sl = (ask - (ATR_MULTIPLE * atr[0])); double sell_sl = (bid + (ATR_MULTIPLE * atr[0])); double buy_tp = (ask + (ATR_MULTIPLE * atr[0])); double sell_tp = (bid - (ATR_MULTIPLE * atr[0])); double new_sl = ((trade == 1) && (initial_sl < buy_sl))? (buy_sl) : ((trade == -1) && (initial_sl > sell_sl)) ? (sell_sl) : (initial_sl); double new_tp = ((trade == 1) && (initial_tp < buy_tp))? (buy_tp) : ((trade == -1) && (initial_tp > sell_tp)) ? (sell_tp) : (initial_tp); //--- Update the position Trade.PositionModify(SYMBOL,new_sl,new_tp); } }
配置技术指标。到目前位置,我们仅需管理1个技术指标。
//+------------------------------------------------------------------+ //| Get our technical indicators ready | //+------------------------------------------------------------------+ void setup(void) { atr_handler = iATR(SYMBOL,DAILY,ATR_PERIOD); }
更新系统状态。
//+------------------------------------------------------------------+ //| Daily update routine | //+------------------------------------------------------------------+ void daily_update(void) { //--- Get current prices ask = SymbolInfoDouble(SYMBOL,SYMBOL_ASK); bid = SymbolInfoDouble(SYMBOL,SYMBOL_BID); //--- Update Technical Indicators CopyBuffer(atr_handler,0,0,1,atr); //--- Check for engulfing candles. int candles_state = check_candles(); //--- Give feedback Comment("Candle State: ",candles_state); }
检测K线形态。如果检测到目标形态,则返回 1(做多) 或 -1(做空) 并执行相应交易。如果未检测到,则保持观望,等待下一次信号。
//+------------------------------------------------------------------+ //| Check if we have any engulfing candles | //+------------------------------------------------------------------+ int check_candles(void) { //--- Return 1 if we have a bullish engulfing candle if((iOpen(SYMBOL,DAILY,0) < iClose(SYMBOL,DAILY,1)) && (iClose(SYMBOL,DAILY,0) > iOpen(SYMBOL,DAILY,1))) return(1); //--- Return -1 if we have a bearish engulfing candle if((iOpen(SYMBOL,DAILY,0) > iClose(SYMBOL,DAILY,1)) && (iClose(SYMBOL,DAILY,0) < iOpen(SYMBOL,DAILY,1))) return(-1); //--- Otherwise return 0 return(0); }
如果K线状态不为0,系统即认定已出现交易信号。否则,当前无需任何操作。
//+------------------------------------------------------------------+ //| Find setup | //+------------------------------------------------------------------+ void find_setup(void) { //--- Our sentiment is bullish int candles_state = check_candles(); if(candles_state == 1) { Trade.Buy(VOL,SYMBOL,ask,(ask - (ATR_MULTIPLE * atr[0])),(ask + (ATR_MULTIPLE * atr[0])),""); trade = 1; } //--- Our sentiment is bearish if(candles_state == -1) { Trade.Sell(VOL,SYMBOL,bid,(bid + (ATR_MULTIPLE * atr[0])),(bid - (ATR_MULTIPLE * atr[0])),""); trade = -1; } } //+------------------------------------------------------------------+
下图3展示了系统的实际界面。系统会持续监测K线形态是否出现,一旦发现立即下单。在当前配置下,止损与止盈每天仅调整一次,时间点为每日收盘后。
图例3:美元兑日元日线周期上的策略可视化
我们将基于4年历史数据(2020年1月1日至 2024年11月30日)上测试这套新策略。如需跟随测试并修改这些设定,请务必同步调整系统变量。否则,无论指定何种交易品种或周期,系统都将继续按美元兑日元日线进行交易。
图例4:回测的主要设置
随机延迟最接近真实交易环境,可用于对系统进行压力测试。如果您日后打算实盘使用本策略,请务必把“入金”和账户杠杆调整为你实际交易账户的对应数值。
图例5:回测所选的建模方式与账户规模
策略生成的资金曲线颇具前景。在此次回测中,剥头皮策略使账户规模增长约4%。与所有交易策略一样,它同样经历了持续的亏损期。然而,该策略最令人称道的是,在亏损期后的强劲恢复能力。
图例6:可视化随时间变化的账户余额
现在,让我们更审慎地观察策略表现。当前版本下,策略的夏普比率为1.12,胜率44.68%。若要将平均亏损从133.22美元降至更接近0,而又不过度削弱平均盈利,我们需要做些什么?
图例7:回测性能的详细分析
优化我们的策略表现
我们的系统当前已实现盈利,但仍有改进空间。我们能否通过调整策略,更有效地控制亏损交易?我将提出以下优化方向:
调整建议 | 设计目的 |
---|---|
附加确认 | 在现有盈利策略上并行采用附加确认手段,有望进一步过滤掉更多亏损交易。 |
为止损增加额外缓冲 | 我们希望尽可能减少在盈利交易中被止损的次数。 |
纳入市场波动率 | 不同市场往往具有独特的波动水平。交易策略应尝试参考历史波动率,为当前价格水平提供“专业交易员”般的背景分析。 |
期望通过这些改进,降低亏损交易占比。但任何调整都需要权衡利弊。换句话说,我们的新策略偶尔会错过旧策略能轻松捕获的盈利单。
显而易见,如果对平均亏损放任不管,最终可能吞噬我们辛苦积累的盈利。为实现上述目标,我们必须对当前应用版本进行变更。
系统变更 | 描述 |
---|---|
新系统变量 | 为了把市场波动纳入考量,首先要决定回溯多少历史数据。为此我们引入一个与名称相符的新系统变量——“fetch”。此外,我们还需固定所用技术指标的参数。 |
技术指标 | 可借助技术指标策略获得附加确认。今天,我们将采用“移动平均线通道”策略。因此,需要创建新的指标句柄与缓冲区来存储这些信息。 |
信号共振 | 我们将新增一个全局变量“sentiment”。只有当K线形态和技术指标同时看涨(均为 1)或同时看跌(均为 -1)时,sentiment的取值才是1或-1。否则,其值为0。我们的系统仅在sentiment不为0时才下单。 |
自定义函数 | 为了确保系统按预期运行,我们需扩展部分既有函数,并新增若干函数。 |
确认策略概述
我们的确认策略将基于“移动平均线通道”交易策略。该策略用两条分别跟踪最高价与最低价的移动平均线。由这两条平均线构成通道。请注意,两条均线互不交叉。因此,当某一根K线完全收盘于两条移动平均线所界定的区域之外时,系统将生成入场信号。
该策略的核心逻辑在于:两条移动平均线高低值之间的价格区间被视为稳定区域。而当价格突破这两条均线所界定的区域时,我们则认为市场出现了失衡状态。该策略认为,这种失衡标志着新趋势正在失衡方向上形成。下图8、展示了该策略的具体应用方式。
红色箭头表示根据策略,该区域是建立空头头寸的理想位置。信号通常在价格重新回到均线通道内之前保持有效。此时,我们可能平仓离场,并等待下一次失衡信号的出现。第二次失衡由蓝色箭头标示。由于此次失衡出现在均线通道上方,所以策略会将其解读为买入信号。
图例8:基于移动平均线通道的进出场策略示意图
我们将进一步拓展该策略,结合市场历史高低点数据进行优化。具体而言,我们将计算过去一年至今市场历史最高价与最低价的中点。此外,我们将利用这一信息对策略进行约束:仅当收盘价高于历史高低价中点时,才允许开立多头头寸;而当收盘价低于该中点时,则开立空头头寸。
图9中的红色水平线代表过去一年至今高低价的平均值(即中点)。该中点由系统每日更新,并作为策略的核心参考基准,用于评估当前价格水平的相对位置。
图例9:过去一年至今市场历史最高价与最低价的中点
持仓类型 | 新持仓条件 |
---|---|
多头 | 已形成看涨吞没形态的K线,且价格位于移动平均线通道上方,同时当前价格水平高于年度平均波动率区间。 |
空头 | 已形成看跌吞没形态的K线,且价格位于移动平均线通道上方,同时当前价格水平高于年度平均波动率区间。 |
希望将两种策略结合使用后,能够过滤掉旧系统中导致亏损的交易,同时保留我们想要保留的盈利交易。让我们开始实施这些调整,并验证其有效性。首先需定义新系统的变量,包括移动平均线通道的计算周期,以及用于计算中点的历史数据范围。
//+------------------------------------------------------------------+ //| USDJPY Price Action Benchmark 2 | //| Gamuchirai Zororo Ndawana | //| https://www.mql5.com/en/gamuchiraindawa | //+------------------------------------------------------------------+ #property copyright "Gamuchirai Zororo Ndawana" #property link "https://www.mql5.com/en/gamuchiraindawa" #property version "1.00" //+------------------------------------------------------------------+ //| This trading application is intended to surpass our benchmark. | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| System constants | //+------------------------------------------------------------------+ //--- I have intentionally omitted parts of the system that remained unchanged #define FETCH 365 //--- How much should we fetch? #define MA_PERIOD 90 //--- Moving average period
我们还需要定义一些附加的全局变量,用于跟踪已定义的市场状态。
//+------------------------------------------------------------------+ //| Global variables | //+------------------------------------------------------------------+ int sentiment = 0; int trade = 0; int ma_high_handler, ma_low_handler; double ma_high[],ma_low[];
应用程序的主体部分将保持不变。不过,部分被调用的函数已发生变更。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Setup our system varaibles setup(); //--- return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Release any resources release(); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Update system variables update(); } //+------------------------------------------------------------------+
现在,让我们来回顾一下对自定义函数所做的修改。前两项修改涉及加载技术指标,并在使用后释放。
//+------------------------------------------------------------------+ //| Custom Functions | //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Release our technical indicators | //+------------------------------------------------------------------+ void release(void) { IndicatorRelease(atr_handler); IndicatorRelease(ma_low_handler); IndicatorRelease(ma_high_handler); }
对于代码库的这些修改是相辅相成的,且易于理解。
//+------------------------------------------------------------------+ //| Get our technical indicators ready | //+------------------------------------------------------------------+ void setup(void) { atr_handler = iATR(SYMBOL,DAILY,ATR_PERIOD); ma_high_handler = iMA(SYMBOL,DAILY,MA_PERIOD,0,MODE_EMA,PRICE_HIGH); ma_low_handler = iMA(SYMBOL,DAILY,MA_PERIOD,0,MODE_EMA,PRICE_LOW); }
我们的每日更新流程也需要扩展。目前,我们还希望了解当前价格水平与该市场历史波动区间(预期噪声水平)的对比情况。若K线图形态与价格水平传递的信号一致,我们将进一步通过移动平均通道验证当前是否为合适的交易执行时机。
//+------------------------------------------------------------------+ //| Daily update routine | //+------------------------------------------------------------------+ void daily_update(void) { //--- Get current prices ask = SymbolInfoDouble(SYMBOL,SYMBOL_ASK); bid = SymbolInfoDouble(SYMBOL,SYMBOL_BID); //--- Update Technical Indicators CopyBuffer(atr_handler,0,0,1,atr); CopyBuffer(ma_high_handler,0,0,1,ma_high); CopyBuffer(ma_low_handler,0,0,1,ma_low); //--- Check for engulfing candles. int candles_state = check_candles(); //--- Compare current price levels to historical price levels in the market int price_state = check_price_levels(); //--- Check our tech //--- What is our sentiment? //--- Our sentiment is well defined. if(candles_state == price_state) sentiment = candles_state; //--- Wait. if(candles_state != price_state) sentiment = 0; //--- Give feedback Comment("Sentiment: ",sentiment,"\nCandle State: ",candles_state,"\nPrice State: ",price_state); }
我们将使用MQL5的向量类型,轻松实现市场统计数据的实时计算与动态跟踪。
//+------------------------------------------------------------------+ //| Check if we are closer to the all time high or low | //+------------------------------------------------------------------+ int check_price_levels(void) { //--- Get historical prices vector highs = vector::Zeros(FETCH); vector lows = vector::Zeros(FETCH); highs.CopyRates(SYMBOL,DAILY,COPY_RATES_HIGH,0,FETCH); lows.CopyRates(SYMBOL,DAILY,COPY_RATES_LOW,0,FETCH); //--- First we shall calculate the mid point between the all time high and low vector mid = ((highs + lows) / 2); //--- Return 1 if we are above the mid point if(iClose(SYMBOL,DAILY,0) > mid.Mean()) return(1); //--- Return -1 if we are above the mid point if(iClose(SYMBOL,DAILY,0) < mid.Mean()) return(-1); //--- Otherwise return 0 return(0); }我们新的交易信号筛选规则将引入两项附加的过滤条件。价格相对于年度中点的水平和相对于移动平均线通道的水平。如果两种策略方向一致,我们将据此执行交易。
//+------------------------------------------------------------------+ //| Find setup | //+------------------------------------------------------------------+ void find_setup(void) { //--- Our sentiment is bullish if(sentiment == 1) { if((iOpen(SYMBOL,DAILY,0) > ma_high[0]) && (iClose(SYMBOL,DAILY,0) > ma_high[0])) { Trade.Buy(VOL,SYMBOL,ask,(ask - (ATR_MULTIPLE * atr[0])),(ask + (ATR_MULTIPLE * atr[0])),""); trade = 1; } } //--- Our sentiment is bearish if(sentiment == -1) { if((iOpen(SYMBOL,DAILY,0) < ma_low[0]) && (iClose(SYMBOL,DAILY,0) < ma_low[0])) { Trade.Sell(VOL,SYMBOL,bid,(bid + (ATR_MULTIPLE * atr[0])),(bid - (ATR_MULTIPLE * atr[0])),""); trade = -1; } } } //+------------------------------------------------------------------+
我们可以观察策略的实际运行效果。需要注意的是,当前策略会跟踪三项条件,只有在全部满足时才会开仓。我们希望通过精心筛选这些条件,避免它们因偶然因素同时成立。
图例10:我们正在对修订后的美元兑日元超短线交易策略进行历史数据回测
如前所述,为了确保两次回测的一致性,回测时长与周期的设置将保持固定。因此,本次回测的日期范围与前一次测试完全一致。
图例11:两次回测的参数设置将保持一致
请注意,可根据实际交易环境灵活调整以下参数,以适配不同的市场条件。
图例12:第二批回测参数设置
与首次回测相比,新策略的资金曲线回撤期显著减少。例如,在2020年1月至2023年12月期间,初始策略的资金曲线长期在初始本金附近震荡。而新策略的资金曲线未出现此类不利特征。从2022年9月至回测结束,新资金曲线呈现波动更低的稳健增长趋势。
图例13:修订后的交易策略生成的资金曲线
进一步地分析表明,我们成功将平均亏损金额与亏损交易占比显著降低至接近0的水平。然而,亏损交易比例仅从55%微降至54%,降幅有限。此外,策略调整也导致整体盈利能力有所下降。但这一问题可通过安全扩大交易头寸规模加以弥补。市场环境瞬息万变,新实施的安全机制或将在未来发挥关键作用。
图例14:我们第二种交易策略的详细分析
结论
在本文中,我们探讨了通过K线图形态信号进行交易所蕴含的潜在机会。尽管此类策略饱受争议,例如:K线图形态形成后,价格走势未必如预期般延续,这样可能会引发对策略有效性的质疑。
然而,若遵循本文提出的策略优化方案,并结合您对市场的独到理解,我认为完全有理由消除对该策略盈利能力的疑虑。当前面临的挑战在于:我们往往难以预判,所做的策略调整将如何具体影响到最终的盈利表现。
文件 | 描述 |
---|---|
美元兑日元价格行为基准策略 | 该版本是我们交易策略的初始高波动性版本,其盈利能力更强,但同时伴随更高风险。 |
美元兑日元价格行为策略2.0版 | 这是我们共同优化后的策略版本,在保持同等盈利水平的同时,致力于最小化潜在的亏损。 |
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/16643
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。




我可以测试 EA 吗? 我下载了它,但由于某些原因,它没有显示在 EA 下。 如蒙帮助,不胜感激。 谢谢。