构建K线图趋势约束模型(第8部分):EA的开发(一)
内容:
引言
MetaEditor软件包含一个编译器,能够有效地管理在分析尝试中检测到的错误。这个工具帮助我发现了为什么之前的版本未能按预期显示风险-回报矩形。尽管程序成功编译,但问题并不在于代码本身。相反,问题在于在历史K线图的范围内没有任何内容被显示,这主要是由于某些特定的技术细节导致。 - 历史K线图的值默认设置过高,为5000根K线。
- 在单个程序中使用多个缓冲区会增加计算的复杂性,这可能会减缓指标图表窗口的显示速度。
在简要讨论我们如何解决遇到的问题之后,我们将转向本文的主要目标:开发基于改进的趋势约束指标的EA。以下展示了一个独立脚本运行的图像,它成功解决了我们最初希望通过主指标解决的问题。

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

趋势约束风险-回报(R-R)脚本:在移动平均线交叉时绘制风险和回报矩形
通过将这一功能独立出来,我们确保了指标能够平稳运行,避免了计算机或交易终端出现卡顿。将风险-回报矩形纳入其中并标记出场水平,使交易者能够提前直观地评估交易方向和目标,从而能够在没有EA的情况下进行手动交易。该脚本程序具备所需的逻辑,运行完美无缺。以下是我们的完整脚本程序。//+------------------------------------------------------------------+ //| Trend Constraint R-R.mq5 | //| Script program | //+------------------------------------------------------------------+ #property strict #property script_show_inputs #property copyright "2024 Clemence Benjamin" #property version "1.00" #property link "https://www.mql5.com/en/users/billionaire2024/seller" #property description "A script program for drawing risk and rewars rectangles based on Moving Averaage crossover." //--- input parameters input int FastMAPeriod = 14; input int SlowMAPeriod = 50; input double RiskHeightPoints = 5000.0; // Default height of the risk rectangle in points input double RewardHeightPoints = 15000.0; // Default height of the reward rectangle in points input color RiskColor = clrIndianRed; // Default risk color input color RewardColor = clrSpringGreen; // Default reward color input int MaxBars = 500; // Maximum bars to process input int RectangleWidth = 10; // Width of the rectangle in bars input bool FillRectangles = true; // Option to fill rectangles input int FillTransparency = 128; // Transparency level (0-255), 128 is 50% transparency //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- delete existing rectangles and lines DeleteExistingObjects(); //--- declare and initialize variables int i, limit; double FastMA[], SlowMA[]; double closePrice, riskLevel, rewardLevel; //--- calculate moving averages if (iMA(NULL, 0, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE) < 0 || iMA(NULL, 0, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE) < 0) { Print("Error in calculating moving averages."); return; } ArraySetAsSeries(FastMA, true); ArraySetAsSeries(SlowMA, true); CopyBuffer(iMA(NULL, 0, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE), 0, 0, MaxBars, FastMA); CopyBuffer(iMA(NULL, 0, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE), 0, 0, MaxBars, SlowMA); limit = MathMin(ArraySize(FastMA), ArraySize(SlowMA)); for (i = 1; i < limit - 1; i++) { //--- check for crossover if (FastMA[i] > SlowMA[i] && FastMA[i - 1] <= SlowMA[i - 1]) { //--- long position entry point (bullish crossover) closePrice = iClose(NULL, 0, i); riskLevel = closePrice + RiskHeightPoints * Point(); rewardLevel = closePrice - RewardHeightPoints * Point(); //--- draw risk rectangle DrawRectangle("Risk_" + IntegerToString(i), i, closePrice, i - RectangleWidth, riskLevel, RiskColor); //--- draw reward rectangle DrawRectangle("Reward_" + IntegerToString(i), i, closePrice, i - RectangleWidth, rewardLevel, RewardColor); //--- draw entry, stop loss, and take profit lines DrawPriceLine("Entry_" + IntegerToString(i), i, closePrice, clrBlue, "Entry: " + DoubleToString(closePrice, _Digits)); DrawPriceLine("StopLoss_" + IntegerToString(i), i, riskLevel, clrRed, "Stop Loss: " + DoubleToString(riskLevel, _Digits)); DrawPriceLine("TakeProfit_" + IntegerToString(i), i, rewardLevel, clrGreen, "Take Profit: " + DoubleToString(rewardLevel, _Digits)); } else if (FastMA[i] < SlowMA[i] && FastMA[i - 1] >= SlowMA[i - 1]) { //--- short position entry point (bearish crossover) closePrice = iClose(NULL, 0, i); riskLevel = closePrice - RiskHeightPoints * Point(); rewardLevel = closePrice + RewardHeightPoints * Point(); //--- draw risk rectangle DrawRectangle("Risk_" + IntegerToString(i), i, closePrice, i - RectangleWidth, riskLevel, RiskColor); //--- draw reward rectangle DrawRectangle("Reward_" + IntegerToString(i), i, closePrice, i - RectangleWidth, rewardLevel, RewardColor); //--- draw entry, stop loss, and take profit lines DrawPriceLine("Entry_" + IntegerToString(i), i, closePrice, clrBlue, "Entry: " + DoubleToString(closePrice, _Digits)); DrawPriceLine("StopLoss_" + IntegerToString(i), i, riskLevel, clrRed, "Stop Loss: " + DoubleToString(riskLevel, _Digits)); DrawPriceLine("TakeProfit_" + IntegerToString(i), i, rewardLevel, clrGreen, "Take Profit: " + DoubleToString(rewardLevel, _Digits)); } } } //+------------------------------------------------------------------+ //| Function to delete existing rectangles and lines | //+------------------------------------------------------------------+ void DeleteExistingObjects() { int totalObjects = ObjectsTotal(0, 0, -1); for (int i = totalObjects - 1; i >= 0; i--) { string name = ObjectName(0, i, 0, -1); if (StringFind(name, "Risk_") >= 0 || StringFind(name, "Reward_") >= 0 || StringFind(name, "Entry_") >= 0 || StringFind(name, "StopLoss_") >= 0 || StringFind(name, "TakeProfit_") >= 0) { ObjectDelete(0, name); } } } //+------------------------------------------------------------------+ //| Function to draw rectangles | //+------------------------------------------------------------------+ void DrawRectangle(string name, int startBar, double startPrice, int endBar, double endPrice, color rectColor) { if (ObjectFind(0, name) >= 0) ObjectDelete(0, name); datetime startTime = iTime(NULL, 0, startBar); datetime endTime = (endBar < 0) ? (TimeCurrent() + (PeriodSeconds() * (-endBar))) : iTime(NULL, 0, endBar); if (!ObjectCreate(0, name, OBJ_RECTANGLE, 0, startTime, startPrice, endTime, endPrice)) Print("Failed to create rectangle: ", name); // Set the color with transparency (alpha value) int alphaValue = FillTransparency; // Adjust transparency level (0-255) color fillColor = rectColor & 0x00FFFFFF | (alphaValue << 24); // Combine alpha with RGB ObjectSetInteger(0, name, OBJPROP_COLOR, rectColor); ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, name, OBJPROP_WIDTH, 1); ObjectSetInteger(0, name, OBJPROP_BACK, true); // Set to background if (FillRectangles) { ObjectSetInteger(0, name, OBJPROP_COLOR, fillColor); // Fill color with transparency } else { ObjectSetInteger(0, name, OBJPROP_COLOR, rectColor & 0x00FFFFFF); // No fill color } } //+------------------------------------------------------------------+ //| Function to draw price lines | //+------------------------------------------------------------------+ void DrawPriceLine(string name, int barIndex, double price, color lineColor, string labelText) { datetime time = iTime(NULL, 0, barIndex); datetime endTime = (barIndex - 2 * RectangleWidth < 0) ? (TimeCurrent() + (PeriodSeconds() * (-barIndex - 2 * RectangleWidth))) : iTime(NULL, 0, barIndex - 2 * RectangleWidth); // Extend line to the right if (!ObjectCreate(0, name, OBJ_TREND, 0, time, price, endTime, price)) Print("Failed to create price line: ", name); ObjectSetInteger(0, name, OBJPROP_COLOR, lineColor); ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, name, OBJPROP_WIDTH, 1); ObjectSetInteger(0, name, OBJPROP_BACK, true); // Set to background // Create text label string labelName = name + "_Label"; if (ObjectFind(0, labelName) >= 0) ObjectDelete(0, labelName); if (!ObjectCreate(0, labelName, OBJ_TEXT, 0, endTime, price)) Print("Failed to create label: ", labelName); ObjectSetInteger(0, labelName, OBJPROP_COLOR, lineColor); ObjectSetInteger(0, labelName, OBJPROP_ANCHOR, ANCHOR_LEFT); ObjectSetString(0, labelName, OBJPROP_TEXT, labelText); ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 10); ObjectSetInteger(0, labelName, OBJPROP_CORNER, CORNER_RIGHT_UPPER); ObjectSetInteger(0, labelName, OBJPROP_XOFFSET, 5); ObjectSetInteger(0, labelName, OBJPROP_YOFFSET, 0); }
让我们在继续开发专EA之前,深入讨论一下该脚本:
- 我们通过定义输入参数,允许用户自定义交易策略的关键点。我们提供了调整快速和慢速移动平均线周期的选项,设置风险和回报矩形的尺寸和颜色,确定要处理的最大K线数,以及选择是否用颜色填充矩形。这种设置使脚本能够根据用户的偏好适应不同的交易策略,这也构成了我们程序的输入参数部分:
//---- Input Parameters input int FastMAPeriod = 14; input int SlowMAPeriod = 50; input double RiskHeightPoints = 5000.0; input double RewardHeightPoints = 15000.0; input color RiskColor = clrIndianRed; input color RewardColor = clrSpringGreen; input int MaxBars = 500; input int RectangleWidth = 10; input bool FillRectangles = true; input int FillTransparency = 128;
- 在 (OnStart) 函数中,我们通过编程让脚本首先删除任何已存在的风险/回报矩形和价格线,从而确保图表保持整洁。随后,我们利用 (iMA) 函数计算快速和慢速移动平均线,并将这些值存储在数组中以便进一步处理。当脚本遍历图表上的K线时,我们设定了检测看涨交叉的条件,即快速移动平均线穿过并高于慢速移动平均线。当这些条件满足时,脚本会计算入场价格、风险水平(止损)和回报水平(获利)。然后,它会在图表上绘制矩形和价格线,有效地标记这些关键的交易水平,我们将在下面进一步解释相关的子代码片段:
//----Onstart Function void OnStart() { //--- delete existing rectangles and lines DeleteExistingObjects(); //--- declare and initialize variables int i, limit; double FastMA[], SlowMA[]; double closePrice, riskLevel, rewardLevel; //--- calculate moving averages if (iMA(NULL, 0, FastMAPeriod, 0, MODE_SMA, PRICE_CLOSE) < 0 || iMA(NULL, 0, SlowMAPeriod, 0, MODE_SMA, PRICE_CLOSE) < 0) { Print("Error in calculating moving averages."); return; }
- 为了保持图表的清晰度,我们开发了“删除现有对象”功能,脚本通过此功能移除所有与交易信号相关的先前绘制的对象。通过检查图表上所有对象的名称,脚本确保仅显示最新且相关的信息,从而使图表保持专注且避免杂乱:
//---- DeleteAllExistingObjects Function void DeleteExistingObjects() { int totalObjects = ObjectsTotal(0, 0, -1); for (int i = totalObjects - 1; i >= 0; i--) { string name = ObjectName(0, i, 0, -1); if (StringFind(name, "Risk_") >= 0 || StringFind(name, "Reward_") >= 0 || StringFind(name, "Entry_") >= 0 || StringFind(name, "StopLoss_") >= 0 || StringFind(name, "TakeProfit_") >= 0) { ObjectDelete(0, name); } } }
- 在“绘制矩形”功能中,我们确保脚本能够图形化呈现风险和回报水平。为此,脚本首先移除任何具有相同名称的现有矩形,以避免重复。随后,脚本根据K线索引计算矩形的起始时间和结束时间,并精心设置了颜色和透明度级别。这使得矩形能够在图表上突出显示,同时又不会掩盖其他重要细节:
///---Draw rectangle function void DrawRectangle(string name, int startBar, double startPrice, int endBar, double endPrice, color rectColor) { if (ObjectFind(0, name) >= 0) ObjectDelete(0, name); datetime startTime = iTime(NULL, 0, startBar); datetime endTime = (endBar < 0) ? (TimeCurrent() + (PeriodSeconds() * (-endBar))) : iTime(NULL, 0, endBar); if (!ObjectCreate(0, name, OBJ_RECTANGLE, 0, startTime, startPrice, endTime, endPrice)) Print("Failed to create rectangle: ", name); int alphaValue = FillTransparency; color fillColor = rectColor & 0x00FFFFFF | (alphaValue << 24); ObjectSetInteger(0, name, OBJPROP_COLOR, rectColor); ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, name, OBJPROP_WIDTH, 1); ObjectSetInteger(0, name, OBJPROP_BACK, true); if (FillRectangles) { ObjectSetInteger(0, name, OBJPROP_COLOR, fillColor); } else { ObjectSetInteger(0, name, OBJPROP_COLOR, rectColor & 0x00FFFFFF); } }
- 最后,我们实现了“绘制价格线”功能,指示脚本在入场、止损和获利水平处添加水平线。脚本将这些线延伸至整个图表,并添加显示相应价格水平的文本标签。这为用户提供了视觉参考,使他们能够快速识别并管理基于移动平均线生成信号的关键交易点。
///---- Draw Price Lines Function void DrawPriceLine(string name, int barIndex, double price, color lineColor, string labelText) { datetime time = iTime(NULL, 0, barIndex); datetime endTime = (barIndex - 2 * RectangleWidth < 0) ? (TimeCurrent() + (PeriodSeconds() * (-barIndex - 2 * RectangleWidth))) : iTime(NULL, 0, barIndex - 2 * RectangleWidth); if (!ObjectCreate(0, name, OBJ_TREND, 0, time, price, endTime, price)) Print("Failed to create price line: ", name); ObjectSetInteger(0, name, OBJPROP_COLOR, lineColor); ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID); ObjectSetInteger(0, name, OBJPROP_WIDTH, 1); ObjectSetInteger(0, name, OBJPROP_BACK, true); string labelName = name + "_Label"; if (ObjectFind(0, labelName) >= 0) ObjectDelete(0, labelName); if (!ObjectCreate(0, labelName, OBJ_TEXT, 0, endTime, price)) Print("Failed to create label: ", labelName); ObjectSetInteger(0, labelName, OBJPROP_COLOR, lineColor); ObjectSetInteger(0, labelName, OBJPROP_FONTSIZE, 10); ObjectSetString(0, labelName, OBJPROP_TEXT, labelText); ObjectSetInteger(0, labelName, OBJPROP_BACK, true); }
现在,作为交易工具包的一部分,该脚本可以定期启动,以图形化方式查看过去和当前时间的交易。接下来,我们将着手创建我们独特的EA。我将专注于解释从开发到最终可用的EA的整个过程。在本文中,我们将重点关注如何使其与我们之前制作的趋势约束指标(Trend Constraint V1.09)协同工作。
创建基于指标的EA:
要在 MQL5 中使用自定义指标创建EA,我们需要确保自定义指标文件(.ex5)已放置在 MetaTrader 5 平台的“指标”文件夹中,此处为趋势约束指标 V1.09。通过 MetaEditor,我们可以编写我们的 EA,使用 MQL5 函数来访问指标的缓冲区值。我们使用 (iCustom()) 函数在 EA 中调用自定义指标,指定必要的参数,例如交易品种和时间周期。
为了从指标缓冲区提取数据,我们使用 (CopyBuffer()) 函数,它会检索你打算用于交易信号分析的缓冲区值。然后,我们根据这些缓冲区值实现我们的交易逻辑,定义开仓、平仓或修改订单的条件,以符合我们的交易策略。整合风险管理功能,如止损和获利,以谨慎管理交易。使用 MetaTrader 5 策略测试器对 EA 进行回测,以评估其性能并微调其参数。最后,我们将在模拟账户环境中验证 EA 的功能,然后才转向实盘交易,以确保它在真实市场条件下能够有效运行。
我们可以从在 MetaEditor 中加载一个EA模板开始,然后根据本文中显示的示例进行开发或修改:

在 MetaEditor 中加载EA模板
为了引导你完成我们的EA的构建,我们将整个过程分为六个阶段。在我们逐步推进的过程中,我建议你直接将代码片段输入到 MetaEditor 中。这种动手实践的方法将帮助你更好地理解和掌握这些步骤,尤其是如果你是 EA 开发的新手的话。
1. 头部和基本数据
在头部部分,我们定义了EA的名称和用途。通过包含版权信息、指向我们个人资料的链接以及指定版本号,我们确保我们的 EA 是易于识别和追溯的。这些基本数据帮助我们以及其他人理解 EA 的来源和用途,尤其是当它被共享或修改时:
//You can replace the author details with yours. //+------------------------------------------------------------------+ //| Trend Constraint Expert.mq5 | //| Copyright 2024, Clemence Benjamin | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property strict #property copyright "Copyright 2024, Clemence Benjamin" #property link "https://www.mql5.com/en/users/billionaire2024/seller" #property version "1.0" #property description "An Expert based on the buffer6 and buffer7 of Trend Constraint V1.09"
2. 输入参数
在这里,我们定义了关键的输入参数,这些参数允许我们在不修改代码本身的情况下自定义EA的行为。通过设置诸如(手数、滑点、止损、获利和magic数字)等参数,我们使EA变得灵活且能够适应不同的交易策略。magic数字尤其重要,因为它允许我们唯一地识别由这个EA执行的交易,这在涉及多个EA或手动交易时至关重要。
///----------- EA inputs parameters for customizations input double Lots = 0.1; // Lot size input int Slippage = 3; // Slippage input double StopLoss = 50; // Stop Loss in points input double TakeProfit = 100; // Take Profit in points input int MagicNumber = 123456; // Magic number for orders
3. 初始化函数(OnInit)
在 OnInit 函数中,我们通过初始化必要的组件为 EA 的运行做好准备。我们首先尝试获取我们自定义指标“Trend Constraint V1.09”的句柄。这个句柄使我们能够以编程方式与指标进行交互。如果成功获取句柄,我们将继续设置缓冲区数组(Buffer6 和 Buffer7)为序列化形式,以便我们能够存储和操作指标值。然而,如果无法获取句柄,我们确保 EA 返回初始化失败,并附带一条错误消息以帮助我们诊断问题。
////-------Initialization Function int OnInit() { //--- Get the indicator handle indicator_handle = iCustom(Symbol(), PERIOD_CURRENT, "Trend Constraint V1.09"); if (indicator_handle < 0) { Print("Failed to get the indicator handle. Error: ", GetLastError()); return(INIT_FAILED); } //--- Set the buffer arrays as series ArraySetAsSeries(Buffer6, true); ArraySetAsSeries(Buffer7, true); return(INIT_SUCCEEDED); }
4. 反初始化函数(OnDeinit)
当我们的EA从图表中移除,或者当平台关闭时,(OnDeinit)函数会被执行。在这里,我们会释放指标句柄,确保释放EA运行期间分配的任何资源。这个清理步骤对于维护我们交易环境的效率和稳定性至关重要,因为它防止了不必要的资源占用。
///------Deinitialization Function(OnDeinit) void OnDeinit(const int reason) { //--- Release the indicator handle IndicatorRelease(indicator_handle); }
5. 主执行函数(OnTick)
(OnTick) 函数是真正进行交易操作的地方。每当接收到一个新的市场报价时,这个函数就会被调用。我们首先检查是否已经存在一个具有相同magic数字的未平仓头寸,以确保我们不会重复下单。接下来,我们从指标缓冲区(Buffer6 和 Buffer7)复制最新的值,以便做出交易决策。如果满足我们的买入或卖出信号条件,我们将构建并发送相应的交易请求。我们会仔细指定所有必要的参数,例如订单类型、价格、止损、获利和滑点,以有效执行我们的交易策略。
///---Main Execution Function(OnTick) void OnTick() { //--- Check if there is already an open position with the same MagicNumber if (PositionSelect(Symbol())) { if (PositionGetInteger(POSITION_MAGIC) == MagicNumber) { return; // Exit OnTick if there's an open position with the same MagicNumber } } //--- Calculate the indicator if (CopyBuffer(indicator_handle, 5, 0, 2, Buffer6) <= 0 || CopyBuffer(indicator_handle, 6, 0, 2, Buffer7) <= 0) { Print("Failed to copy buffer values. Error: ", GetLastError()); return; } //--- Check for a buy signal if (Buffer7[0] != EMPTY_VALUE) { double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK); double sl = NormalizeDouble(ask - StopLoss * _Point, _Digits); double tp = NormalizeDouble(ask + TakeProfit * _Point, _Digits); //--- Prepare the buy order request MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_DEAL; request.symbol = Symbol(); request.volume = Lots; request.type = ORDER_TYPE_BUY; request.price = ask; request.sl = sl; request.tp = tp; request.deviation = Slippage; request.magic = MagicNumber; request.comment = "Buy Order"; //--- Send the buy order if (!OrderSend(request, result)) { Print("Error opening buy order: ", result.retcode); } else { Print("Buy order opened successfully! Ticket: ", result.order); } } //--- Check for a sell signal if (Buffer6[0] != EMPTY_VALUE) { double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID); double sl = NormalizeDouble(bid + StopLoss * _Point, _Digits); double tp = NormalizeDouble(bid - TakeProfit * _Point, _Digits); //--- Prepare the sell order request MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_DEAL; request.symbol = Symbol(); request.volume = Lots; request.type = ORDER_TYPE_SELL; request.price = bid; request.sl = sl; request.tp = tp; request.deviation = Slippage; request.magic = MagicNumber; request.comment = "Sell Order"; //--- Send the sell order if (!OrderSend(request, result)) { Print("Error opening sell order: ", result.retcode); } else { Print("Sell order opened successfully! Ticket: ", result.order); } } }
6. 其他函数
我们还包含了几个其他函数来处理我们的 EA 可能遇到的不同事件,这些函数作为 EA 模板的一部分提供,而为了简化,我们目前并不使用它们:
- (OnTrade):在这里,我们可以处理在交易事件发生时需要执行的任何特定操作。虽然目前这个函数是空的,但它为我们提供了一个空间,以便在需要时添加逻辑。
- (OnTester):这个函数用于在回测期间进行自定义计算。通过返回一个值,我们可以根据特定指标优化我们的策略。
- (OnTesterInit, OnTesterPass, OnTesterDeinit):这些函数参与策略测试器中的优化过程。它们允许我们初始化设置,在每次优化后执行操作,并在之后清理资源。
- (OnChartEvent):这个函数允许我们处理各种图表事件,例如鼠标点击或按键操作。虽然它目前是空的,但我们可以利用这个空间为我们的 EA 添加交互功能。
///----Other Template functions available void OnTrade() { //--- Handle trade events if necessary } double OnTester() { double ret = 0.0; //--- Custom calculations for strategy tester return (ret); } void OnTesterInit() { //--- Initialization for the strategy tester } void OnTesterPass() { //--- Code executed after each pass in optimization } void OnTesterDeinit() { //--- Cleanup after tester runs } void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle chart events here }
最后将所有这些整合在一起,我们完整的程序如下:
//+------------------------------------------------------------------+ //| Trend Constraint Expert.mq5 | //| Copyright 2024, Clemence Benjamin | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property strict #property copyright "Copyright 2024, Clemence Benjamin" #property link "https://www.mql5.com/en/users/billionaire2024/seller" #property version "1.0" #property description "An Expert based on the buffer6 and buffer7 of Trend Constraint V1.09" //--- Input parameters for the EA input double Lots = 0.1; // Lot size input int Slippage = 3; // Slippage input double StopLoss = 50; // Stop Loss in points input double TakeProfit = 100; // Take Profit in points input int MagicNumber = 123456; // Magic number for orders //--- Indicator handle int indicator_handle; double Buffer6[]; double Buffer7[]; //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Get the indicator handle indicator_handle = iCustom(Symbol(), PERIOD_CURRENT, "Trend Constraint V1.09"); if (indicator_handle < 0) { Print("Failed to get the indicator handle. Error: ", GetLastError()); return(INIT_FAILED); } //--- Set the buffer arrays as series ArraySetAsSeries(Buffer6, true); ArraySetAsSeries(Buffer7, true); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Release the indicator handle IndicatorRelease(indicator_handle); } //+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick() { //--- Check if there is already an open position with the same MagicNumber if (PositionSelect(Symbol())) { if (PositionGetInteger(POSITION_MAGIC) == MagicNumber) { return; // Exit OnTick if there's an open position with the same MagicNumber } } //--- Calculate the indicator if (CopyBuffer(indicator_handle, 5, 0, 2, Buffer6) <= 0 || CopyBuffer(indicator_handle, 6, 0, 2, Buffer7) <= 0) { Print("Failed to copy buffer values. Error: ", GetLastError()); return; } //--- Check for a buy signal if (Buffer7[0] != EMPTY_VALUE) { double ask = SymbolInfoDouble(Symbol(), SYMBOL_ASK); double sl = NormalizeDouble(ask - StopLoss * _Point, _Digits); double tp = NormalizeDouble(ask + TakeProfit * _Point, _Digits); //--- Prepare the buy order request MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_DEAL; request.symbol = Symbol(); request.volume = Lots; request.type = ORDER_TYPE_BUY; request.price = ask; request.sl = sl; request.tp = tp; request.deviation = Slippage; request.magic = MagicNumber; request.comment = "Buy Order"; //--- Send the buy order if (!OrderSend(request, result)) { Print("Error opening buy order: ", result.retcode); } else { Print("Buy order opened successfully! Ticket: ", result.order); } } //--- Check for a sell signal if (Buffer6[0] != EMPTY_VALUE) { double bid = SymbolInfoDouble(Symbol(), SYMBOL_BID); double sl = NormalizeDouble(bid + StopLoss * _Point, _Digits); double tp = NormalizeDouble(bid - TakeProfit * _Point, _Digits); //--- Prepare the sell order request MqlTradeRequest request; MqlTradeResult result; ZeroMemory(request); ZeroMemory(result); request.action = TRADE_ACTION_DEAL; request.symbol = Symbol(); request.volume = Lots; request.type = ORDER_TYPE_SELL; request.price = bid; request.sl = sl; request.tp = tp; request.deviation = Slippage; request.magic = MagicNumber; request.comment = "Sell Order"; //--- Send the sell order if (!OrderSend(request, result)) { Print("Error opening sell order: ", result.retcode); } else { Print("Sell order opened successfully! Ticket: ", result.order); } } } //+------------------------------------------------------------------+ //| Trade function | //+------------------------------------------------------------------+ void OnTrade() { //--- Handle trade events if necessary } //+------------------------------------------------------------------+ //| Tester function | //+------------------------------------------------------------------+ double OnTester() { double ret = 0.0; //--- Custom calculations for strategy tester return (ret); } //+------------------------------------------------------------------+ //| TesterInit function | //+------------------------------------------------------------------+ void OnTesterInit() { //--- Initialization for the strategy tester } //+------------------------------------------------------------------+ //| TesterPass function | //+------------------------------------------------------------------+ void OnTesterPass() { //--- Code executed after each pass in optimization } //+------------------------------------------------------------------+ //| TesterDeinit function | //+------------------------------------------------------------------+ void OnTesterDeinit() { //--- Cleanup after tester runs } //+------------------------------------------------------------------+ //| ChartEvent function | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Handle chart events here } //+------------------------------------------------------------------+
在成功编译后的下一个阶段,我们将测试我们的程序。
测试:
在 MetaEditor 中,我们可以使用“编译”或“运行”按钮来准备我们的程序以进行测试。在这种情况下,编译成功,我们使用策略测试器在 Boom 500 指数上进行测试。

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

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

测试结果可视化:趋势约束EA
结论
添加风险和回报矩形为交易者提供了一种清晰的图形化交易表示,使监控和管理未平仓头寸变得更加容易。这种视觉辅助工具在快速变动的市场中尤其有用,因为快速决策是必要的。矩形作为交易潜在结果的持续提醒,帮助交易者保持与其原始交易计划的一致性。
(Trend Constraint V1.09)指标与EA之间的成功协作突显了交易策略中工具之间协同作用的重要性。指标识别潜在趋势和反转,而EA则根据这些信息执行交易并管理风险。这种整合方法导致了一个更具凝聚力和有效的交易策略。
以下是附带的指标、脚本和EA。仍有改进和修改的空间。希望这些信息对你有价值。欢迎你在评论区分享你的想法。祝您交易愉快!
| 附件 | 说明 |
|---|---|
| (Trend_Constraint V1.09.mq5) | EA所使用指标的源代码。 |
| (Trend Constraint R-R.mq5) | 风险-回报矩形脚本的源代码。 |
| (Trend Constraint Expert.mq5) | 与(趋势约束指标 V1.09)完美配合使用的EA的源代码。 |
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/15321
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。
开发多币种 EA 交易系统(第 16 部分):不同报价历史对测试结果的影响
动物迁徙优化(AMO)算法
您好@argatafx28