通过原始代码优化和调整来改进回测结果
概述
算法交易中对可靠回测结果的追求,不仅取决于稳健的策略逻辑,还取决于底层代码的效率和精度。原始代码优化和调整对于确保 EA 交易按预期运行至关重要,它能最大限度地减少计算开销,同时最大限度地提高执行精度。优化不当的代码可能会通过延迟订单执行、错误的信号检测或掩盖策略真正潜力的资源耗尽问题扭曲回测结果。
在策略制定过程中,我们将采取几个关键步骤,以确保 EA 交易在功能上强大且技术上可靠。我们将从添加自定义辅助函数和可重用逻辑块开始,以简化操作并避免重复代码。然后,我们将引入结构良好的变量和常量,以提高代码可读性并简化参数调整。这些基础性的调整将有助于维护代码,并在大量回测负载或多交易品种测试期间改善整体执行时间。
另一个主要的改进领域是更有效地利用技术指标。我们将不再盲目地在每个分时报价或柱形计算指标,而是采用更智能的更新逻辑来减少负载和延迟。我们还将尝试不同的指标组合,以支持 EA 逻辑中更好的决策。通过结合结构代码细化、性能感知功能和指标优化,我们可以大幅提高回测的质量和速度,使我们更接近一个持续盈利和可部署的策略。
策略开发
我们算法交易策略的开发始于一种结构化、系统化的形态识别和信号验证方法。该策略的核心采用了一个基于烛形的框架,旨在识别高概率反转场景。对于多头头寸,逻辑系统性地检测到三个连续的看涨烛形,然后是一个或两个修正看跌烛形,最终在索引 1(最近关闭的柱形)处确认看涨烛形。
相反,空头头寸则是由一种相反的形态触发的:连续三根看跌烛形,之后是一根或两根回撤看涨烛形,最后是索引 1 的一根确认看跌烛形。这种配置确保信号仅在形成新的柱形时才得到验证,使执行与已确认的价格走势保持一致,而不是与柱形内的波动保持一致。

为了实现这一逻辑,该策略的架构将优先考虑模块化代码设计和计算效率。首先,将实现辅助函数来抽象重复性任务,例如烛形分类(看涨/看跌判断)和序列验证(连续烛形形态检查)。这些函数将利用 MQL5 的原生价格数据访问方法,包括 `iOpen()` 和 `iClose()`,同时通过静态变量缓存来最大限度地减少冗余计算。
其次,事件驱动执行模型将确保 EA 交易仅在新柱形成时处理信号,这是通过索引 1 的时间戳比较确定的。这种方法可以减少不必要的计算量,并防止在柱形生成过程中出现误触发。

开发的最后阶段将侧重于性能优化和稳健性测试。将整合成交量过滤器或波动率调整止损水平等技术指标,以细化进出场精度。此外,还会预先分配历史缓冲区数组来存储烛形形态,从而减少运行时内存分配开销。通过结合模块化代码设计、事件驱动执行和策略指标集成等要素,EA 交易将实现响应能力和资源效率之间的平衡。
代码://+------------------------------------------------------------------+ //| Includes | //+------------------------------------------------------------------+ #include <Trade/Trade.mqh> MqlTick CTick, PTick; CTrade trade; enum InLot{ Lot_fixed, Lot_dynamic, }; //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ input group "--------------General Inputs--------------" input int RSI_Period = 14; input int TakeProfit = 100; // TP in points input ulong MagicNumber = 888888; // EA identifier input int MaxBarsCheck = 8; // Historical bars to analyze input InLot Lot_mode = Lot_fixed; input double In_Lot = 0.01; input bool TrailYourStop = true; input int trailingStop = 50; //input int StopLoss = 234; //+------------------------------------------------------------------+ //| Global Variables | //+------------------------------------------------------------------+ int rsiHandle, macdHanlde; datetime lastBarTime; double pointMultiplier; double StopLoss = 150; bool bullish_pattern_met = false; bool bearish_pattern_met = false; double first_bullish_low = 0.0; double first_bearish_high = 0.0; ENUM_TIMEFRAMES TimeFrame = PERIOD_CURRENT;
EA 交易的架构集成了必要的库和数据结构,以实现无缝的交易功能。包含 <Trade/Trade.mqh> 库可以访问 CTrade 类,从而简化开仓、修改和平仓等订单管理操作。采用两种 MqlTick 结构 —— ` CTick`和`PTick` —— 来跟踪实时价格数据,确保执行时间和市场状况分析的精确性。枚举( InLot )定义了 EA 的仓位大小设置方法,提供固定( Lot_fixed )和动态( Lot_dynamic )模式,后者保留用于未来迭代中潜在的风险调整手数计算。
用户可通过专用输入参数界面进行自定义设置,无需更改核心代码即可调整参数。关键设置包括 RSI_Period (默认值:14),它控制着指标敏感度,我们将在后续开发过程中使用它,以及“TakeProfit”和“trailingStop” 值(以点为单位)来定义风险回报阈值。`MagicNumber` 参数确保交易识别的完整性,而`MaxBarsCheck` 决定模式分析的历史数据深度。通过 Lot_mode 和 In_Lot 实现仓位大小的灵活性,追踪止损功能( TrailYourStop 、 trailingStop )可在交易进展顺利时自动保护利润。
在内部,全局变量管理复杂的操作工作流程。指标句柄( rsiHandle 、 macdHandle )在运行时存储和引用指标实例,而`lastBarTime`通过记录最近分析的烛形的时间戳来促进时间同步。布尔标志(bullish_pattern_met、bearish_pattern_met)和 swing-level markers(first_bullish_low、first_bearish_high)动态监控市场状况,TimeFrame 指定 EA 的运行周期,默认为活动图表的时间周期,以便立即使用。
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(){ // Create RSI and MACD indicator handles rsiHandle = iRSI(_Symbol, TimeFrame, RSI_Period, PRICE_CLOSE); macdHanlde = iMACD(_Symbol, TimeFrame, 12, 26, 9, PRICE_CLOSE); return(INIT_SUCCEEDED); }
`Oninit()` 函数通过为其周期和价格类型分配正确的设置来正确初始化 RSI 和 MACD 指标。这些指标已存储在内存中,以便将来用于优化交易信号。设置成功后返回 INIT_SUCCEEDED,以确认 EA 已准备好运行。
void OnTick(){ if(!NewBarTrigger()) return; ThreeBar(); } //+------------------------------------------------------------------+ //+------------------------------------------------------------------+ //| Detects new bar formation | //+------------------------------------------------------------------+ bool NewBarTrigger(){ datetime currentTime = iTime(_Symbol, PERIOD_CURRENT, 0); if(currentTime != lastBarTime){ lastBarTime = currentTime; return true; } return false; } double getHigh(int index) { return iHigh(_Symbol, _Period, index); } double getLow(int index) { return iLow(_Symbol, _Period, index); } //+------------------------------------------------------------------+ //| Execute trade with risk parameters | //+------------------------------------------------------------------+ void ExecuteTrade(ENUM_ORDER_TYPE tradeType) { double point = SymbolInfoDouble(_Symbol, SYMBOL_POINT); double price = (tradeType == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID); // Convert StopLoss and TakeProfit from pips to actual price distances double sl_distance = StopLoss * point; double tp_distance = TakeProfit * point; double sl = (tradeType == ORDER_TYPE_BUY) ? price - sl_distance : price + sl_distance; double tp = (tradeType == ORDER_TYPE_BUY) ? price + tp_distance : price - tp_distance; trade.PositionOpen(_Symbol, tradeType, In_Lot, price, sl, tp, NULL); }
`OnTick()` 函数是我们 EA 的心跳,在每个市场分时报价更新时运行。为了避免过度处理和不必要的计算,它首先使用 `NewBarTrigger()` 检查是否有新的烛形。 如果形成了新的烛形,我们就会调用`ThreeBar()`函数,其中包含了我们的交易逻辑和形态检测程序。
`NewBarTrigger()` 函数负责检测新烛形的开始。它通过将当前烛形的开启时间与先前存储的开启时间(`lastBarTime`)进行比较来实现这一点。如果检测到新的时间戳,它会更新 `lastBarTime` 并返回 true,从而允许 `OnTick()` 继续执行。这样可以确保每个柱形只做出一次交易决策,这对于我们依赖柱形关闭确认的策略至关重要。`getHigh()` 和 `getLow()` 辅助函数是简单的实用函数调用,用于返回当前图表上特定柱形索引处的最高价和最低价。
`ExecuteTrade()` 函数用于处理具有适当风险参数的交易。它首先根据交易类型(买入或卖出)获取当前市场价格,然后通过使用符号的“Point”值转换用户定义的点/点值,以实际价格单位计算止损和止盈。然后,将这些计算出的水平传递给 `CTrade` 类的 `PositionOpen()` 方法,以确保每笔交易都以准确和一致的风险控制执行。
//+------------------------------------------------------------------+ //| Candle pattern detection Function | //+------------------------------------------------------------------+ void ThreeBar() { // Settings string symbol = Symbol(); // Current symbol ENUM_TIMEFRAMES timeframe = PERIOD_M5; // Timeframe (e.g., 1-hour) // Pattern detection variables int min_bullish_count = 3; int min_bearish_count = 3; int check_candles = 6; // Check the last 6 candles for patterns bool bullish_pattern = false; bool bearish_pattern = false; static bool bullish_pattern_detected = false; static bool bearish_pattern_detected = false; // Loop through recent candles to search for patterns for (int i = check_candles; i >= min_bullish_count; i--) { // Reset pattern flags for each loop iteration bullish_pattern = false; bearish_pattern = false; // 1. Check for Bullish Trend Pattern int bullish_count = 0; int bearish_count = 0; // Count initial bullish candles for (int j = i; j >= i - min_bullish_count + 1; j--) { if (iClose(symbol, timeframe, j) > iOpen(symbol, timeframe, j)){ bullish_count++; first_bullish_low = getLow(5); } else break; } // Check for 1 or 2 bearish candles followed by a bullish candle if (bullish_count >= min_bullish_count) { for (int j = i - bullish_count; j >= i - bullish_count - 1; j--) { if (iClose(symbol, timeframe, j) < iOpen(symbol, timeframe, j)) bearish_count++; else break; } if ((bearish_count == 1 || bearish_count == 2) && iClose(symbol, timeframe, i - bullish_count - bearish_count) > iOpen(symbol, timeframe, i - bullish_count - bearish_count)) { bullish_pattern = true; } } // 2. Check for Bearish Trend Pattern int bearish_candles_count = 0; int bullish_candles_count = 0; // Count initial bearish candles for (int j = i; j >= i - min_bearish_count + 1; j--) { if (iClose(symbol, timeframe, j) < iOpen(symbol, timeframe, j)){ bearish_candles_count++; first_bearish_high = getHigh(5); } else break; } // Check for 1 or 2 bullish candles followed by a bearish candle if (bearish_candles_count >= min_bearish_count) { for (int j = i - bearish_candles_count; j >= i - bearish_candles_count - 1; j--) { if (iClose(symbol, timeframe, j) > iOpen(symbol, timeframe, j)) bullish_candles_count++; else break; } if ((bullish_candles_count == 1 || bullish_candles_count == 2) && iClose(symbol, timeframe, i - bearish_candles_count - bullish_candles_count) < iOpen(symbol, timeframe, i - bearish_candles_count - bullish_candles_count)) { bearish_pattern = true; } } // Print result and call functions only once for each pattern if (bullish_pattern && !bullish_pattern_detected) { Print("Bullish pattern conditions are met"); ExecuteTrade(ORDER_TYPE_BUY); // Call to buyer function bullish_pattern_detected = true; } else if (!bullish_pattern) { bullish_pattern_detected = false; // Reset when conditions no longer met } if (bearish_pattern && !bearish_pattern_detected) { Print("Bearish pattern conditions are met"); ExecuteTrade(ORDER_TYPE_SELL); // Call to seller function bearish_pattern_detected = true; } else if (!bearish_pattern) { bearish_pattern_detected = false; // Reset when conditions no longer met } } }
`ThreeBar()` 函数是我们 EA 交易的核心形态检测逻辑。它会分析 M5 时间周期或您加载 EA 交易的任何时间周期上的近期烛形形态,以检测潜在的看涨或看跌趋势反转设置。其逻辑是从扫描最后六根烛形开始,识别出至少连续三根阳线或阴线之后出现短暂回调(一根或两根相反方向的烛形)的模式,然后出现一根确认烛形,方向与原始趋势一致。如果识别出这样的结构,则表明存在有效形态。
在每次扫描循环中,都会捕获关键价格点,例如看涨波动的低点或看跌波动的高点,以备将来可能使用,例如结构突破或交易过滤器。

目前,当一根阳线后出现一根阴线时,EA 会开两个仓位,并在阴线收盘后再开一个仓位;但当两根阴线后出现一根阳线时,EA 只会开一个仓位。卖出信号也出现了同样的情况。为了解决这个问题,我们需要添加 `break`,一旦某个形态触发了交易,它就会立即停止循环。这样可以防止 EA 进一步循环到更早的烛形,并在不同的位置找到相同的形态,尤其是在结构允许 1 根和 2 根 柱形回调的情况下。
// Print result and call functions only once for each pattern if (bullish_pattern && !bullish_pattern_detected) { Print("Bullish pattern conditions are met"); ExecuteTrade(ORDER_TYPE_BUY); bullish_pattern_detected = true; break; // prevent further detection in this run } else if (!bullish_pattern) { bullish_pattern_detected = false; } if (bearish_pattern && !bearish_pattern_detected) { Print("Bearish pattern conditions are met"); ExecuteTrade(ORDER_TYPE_SELL); bearish_pattern_detected = true; break; // prevent further detection in this run } else if (!bearish_pattern) { bearish_pattern_detected = false; }
整合指标过滤器:
// Indicator buffers double rsi_value[1]; // Use array for buffer storage double macd_main[1]; double macd_signal[1]; // Get indicator values - with error checking if (CopyBuffer(rsiHandle, 0, 0, 1, rsi_value) <= 0) { Print("RSI CopyBuffer error: ", GetLastError()); return; } if (CopyBuffer(macdHandle, 0, 0, 1, macd_main) <= 0) { Print("MACD Main Line error: ", GetLastError()); return; } if (CopyBuffer(macdHandle, 1, 0, 1, macd_signal) <= 0) { Print("MACD Signal Line error: ", GetLastError()); return; }
在将指标过滤器纳入交易逻辑之前,我们必须使用 `CopyBuffer()` 获取并验证 RSI 和 MACD 指标值。这样可以确保 EA 从指标柄获取最新读数。在上面的代码中,我们使用数组来存储每个指标缓冲区中的单个值,并在每次缓冲区复制后都包含错误处理,以捕获任何问题,例如句柄访问失败或数据丢失。
如果任何缓冲区无法检索数据,该函数将提前退出并记录相应的错误,从而防止 EA 根据无效或缺失的指标输入做出交易决策。
// Filter + Trade Logic if (bullish_pattern && !bullish_pattern_detected && rsi_value[0] > 50 && macd_main[0] > macd_signal[0]) { Print("Bullish pattern confirmed with RSI(", rsi_value[0], ") and MACD(", macd_main[0], "/", macd_signal[0], ")"); ExecuteTrade(ORDER_TYPE_BUY); bullish_pattern_detected = true; break; } else if (!bullish_pattern) bullish_pattern_detected = false; if (bearish_pattern && !bearish_pattern_detected && rsi_value[0] < 50 && macd_main[0] < macd_signal[0]) { Print("Bearish pattern confirmed with RSI(", rsi_value[0], ") and MACD(", macd_main[0], "/", macd_signal[0], ")"); ExecuteTrade(ORDER_TYPE_SELL); bearish_pattern_detected = true; break; } else if (!bearish_pattern) bearish_pattern_detected = false;
单凭三根柱形形态往往无法得出较差的回测结果,因为它没有考虑整体市场趋势,导致经常出现错误信号。为了提高性能,我们通过将 RSI 和 MACD 指标直接纳入交易决策模块来引入趋势确认。
为使看涨信号有效,我们现在要求 RSI 高于 50 表示看涨动能,MACD 主线高于信号线表示上行趋势确认。同样地,对于看跌信号,RSI 必须低于 50,MACD 主线必须低于信号线。这种过滤逻辑通过将入场点与更广泛的市场动能保持一致,从而提高交易准确性。
通过嵌入这些过滤器,我们确保由三根柱形形态触发的交易只有在反映当前市场方向的技术指标支持下才会执行。这种双重确认方法降低了波动或横盘行情中的交易频率,并增加了进入背后有动量的交易的概率。
最终,这种改进增强了 EA 的决策能力,从而获得了更一致、更有利的回测结果,同时有助于避免仅仅依靠柱形结构造成的剧烈波动。
(XAU/USD) 回测结果
本次回测采用 XAUUSD(黄金/美元)1 小时时间周期内的历史价格数据进行评估,测试窗口期为 4 个月(2021 年 11 月 26 日至 2022 年 3 月 17 日)。主要参数包括:
- RSI 周期数:14
- 风险管理:止盈点数 (TP) 500 点,止损点数 (SL) 150 点,固定手数
- 分析配置:8 个柱形的历史回顾期
市场环境
在测试期间,黄金的平均交易价格为每盎司 1,841.59 美元,波动幅度从每盎司 1,752.95 美元(低)到每盎司 2,070.48 美元(高)不等。这种上升趋势反映了强烈的看涨倾向,尤其是在 2022 年初,价格飙升了约 18%,达到 3 月份的峰值。这一时期明显的上涨趋势与宏观经济因素相吻合。


结论
在这个优化过程中,我们通过引入关键技术指标 —— RSI 和 MACD,解决了仅依靠三根柱形形态进行交易入场的局限性。我们首先通过指示句柄和缓冲区复制以及错误检查来确保可靠的数据检索。然后,我们将这些指标作为交易执行逻辑中的确认过滤器,使交易与当前市场趋势保持一致。通过过滤掉震荡行情或逆势行情中的低概率信号,可以显著提高交易质量和回测一致性。
总而言之,将原始价格行为模式与 RSI 和 MACD 等基于动量的指标相结合,可以创建一个更具情境感知能力的交易系统。这种分层方法增强了 EA 交易区分高质量和低质量交易设置的能力。通过要求柱形形态与更广泛的趋势信号相吻合,EA 会变得更加自律,对短期噪音的反应也更小,最终在回测和实盘交易中都能提高性能和稳健性。
进一步改进和研究:
| 领域 | 建议 | 益处 |
|---|---|---|
| 动态止损或止盈 | 根据 ATR 或近期波动率调整止损/止盈。 | 使风险管理能够适应市场条件 |
| 趋势过滤器增强 | 引入移动平均线(例如,200 周期移动平均线)来定义长期趋势 | 提高进场方向的准确性 |
| 交易频率控制 | 增加交易冷却时间或每日交易次数上限 | 减少过度交易和交易集中现象 |
| 多时间周期分析 | 确认更高时间框架(例如,H1、H4)上的 RSI 和 MACD 趋势。 | 通过跨时间周期对齐来提高信号可靠性 |
| 新闻过滤 | 集成经济日历或新闻时间检测 | 避免在高影响事件期间进行交易 |
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/17702
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
MQL5交易工具(第一部分):构建交互式可视化挂单交易助手工具
您应当知道的 MQL5 向导技术(第 55 部分):配备优先经验回放的 SAC
新手在交易中的10个基本错误
价格行为分析工具包开发(第二十一部分):市场结构反转检测工具
请告诉我 CTick 和 PTick 是如何工作的。我看到这两个函数都与 Includes 一起声明,但在代码的其他地方都没有引用。换句话说,CTick 跟踪什么;PTick 跟踪什么?
(我在 MQ 文档中什么也找不到)。
The pursuit of reliable back-test results in algorithmic trading hinges not only on robust strategy logic but also on the efficiency and precision of the underlying code. Raw code optimization and tweaking are critical to ensuring that Expert Advisors (EAs) perform as intended, minimizing computational overhead while maximizing execution accuracy. Poorly optimized code can distort back-test outcomes through delayed order execution, incorrect signal detection, or resource exhaustion—issues that mask a strategy’s true potential.
在策略开发过程中,我们将采取几个关键步骤,确保EA 功能强大、技术可靠。首先,我们将添加自定义辅助函数和可重复使用的逻辑块,以简化操作并避免重复代码。然后,我们将引入结构良好的变量和常量,以提高代码的可读性并简化参数调整。这些基础性调整将有助于维护代码,并在大量反向测试负载或多符号测试中缩短整体执行时间。
另一个主要改进领域涉及更有效地利用技术指标。我们将实施更智能的更新逻辑,以减少负载和滞后,而不是盲目地在每个刻度线或条形图上计算指标。我们还将尝试不同的指标组合,以支持 EA 逻辑中更好的决策。通过将结构性代码改进、性能感知功能和指标优化相结合,我们可以大幅提高回测的质量和速度,使 我们更接近可持续盈利和可部署的策略。
我不想冒犯任何人,但我不得不说这篇文章是失败的。上面是导言,其中有很多有力的肯定和,但作者都没有执行。
我的初衷是想指出代码中存在的问题,但这些问题实在太多了......所以我只指出第 2 点。
健壮的代码?错误检查在哪里?不做任何检查就直接使用输入,不检查 stoplevels,不检查保证金,不检查交易请求......
使用 iClose/iOpen 逐根蜡烛?这些函数处理数据的速度最慢。
此外,getLow(5) 似乎是个错误。我没有进一步检查。
我不会对交易算法 本身发表评论,我只是注意到作者很方便地选择了历史上 EA 取得良好结果的一个短暂时期。
这篇文章非常糟糕。