English Русский Español Deutsch 日本語
preview
让手动回测变得简单:为MQL5策略测试器构建自定义工具包

让手动回测变得简单:为MQL5策略测试器构建自定义工具包

MetaTrader 5测试者 |
503 6
Allan Munene Mutiiria
Allan Munene Mutiiria

引言

回测交易策略是成功交易的基石,但将每个想法都自动化可能会让人感到束缚,而手动测试又往往缺乏结构和精度。如果您能将手动交易的控制力与 MetaTrader 5 策略测试器的强大功能结合起来呢?在本文中,我们介绍一个自定义的 MetaQuotes Language 5 (MQL5) EA,它能将手动回测转变为一个直观、高效的过程——为您配备一个工具包,让您按照自己的方式测试策略。我们将按以下顺序涵盖这些步骤:

  1. 计划:设计手动回测工具包
  2. 在MQL5中实现:让工具包变为现实
  3. 实战回测:使用工具包
  4. 结论

到文末,您将获得一个实用的解决方案,能够在策略测试器中快速、自信地回测和完善您的交易策略。


计划:设计手动回测工具包

我们的目标是创建一个工具包,它将手动控制与MetaTrader 5中策略测试器的快速回测速度相结合,从而避开传统手动测试时缓慢的实时报价。我们将设计一个带有图表按钮的程序,用于触发买入或卖出交易、调整手数、设置止损(SL)和止盈(TP)水平,并通过一个“恐慌(Panic)按钮”关闭所有持仓——该程序可与任何策略完全集成,无论是指标、K线形态还是价格行为策略,所有这些都能以测试器的加速速度运行。这个灵活的设置将使我们能够以速度和精度交互式地测试任何交易方法,在模拟环境中简化策略的完善过程。简而言之,这是我们所实现的目标的图形展示:

IMAGE PLAN


在MQL5中实现:让工具包变为现实

要在MQL5中创建该程序,我们需要定义程序的基础数据,然后定义一些用户输入参数,最后,我们需要包含一些能够执行交易的库文件。

//+------------------------------------------------------------------+
//|                       Manual backtest toolkit in Strategy Tester |
//|                        Copyright 2025, Forex Algo-Trader, Allan. |
//|                                 "https://t.me/Forex_Algo_Trader" |
//+------------------------------------------------------------------+
#property copyright "Forex Algo-Trader, Allan"
#property link      "https://t.me/Forex_Algo_Trader"
#property version   "1.00"
#property description "This EA Enables manual backtest in the strategy tester"
#property strict //--- Enforce strict coding rules to catch errors early

#define BTN_BUY "BTN BUY" //--- Define the name for the Buy button
#define BTN_SELL "BTN SELL" //--- Define the name for the Sell button
#define BTN_P "BTN P" //--- Define the name for the button that increase lot size
#define BTN_M "BTN M" //--- Define the name for the button that decrease lot size
#define BTN_LOT "BTN LOT" //--- Define the name for the lot size display button
#define BTN_CLOSE "BTN CLOSE" //--- Define the name for the button that close all positions
#define BTN_SL "BTN SL" //--- Define the name for the Stop Loss display button
#define BTN_SL1M "BTN SL1M" //--- Define the name for the button that slightly lower Stop Loss
#define BTN_SL2M "BTN SL2M" //--- Define the name for the button that greatly lower Stop Loss
#define BTN_SL1P "BTN SL1P" //--- Define the name for the button that slightly raise Stop Loss
#define BTN_SL2P "BTN SL2P" //--- Define the name for the button that greatly raise Stop Loss
#define BTN_TP "BTN TP" //--- Define the name for the Take Profit display button
#define BTN_TP1M "BTN TP1M" //--- Define the name for the button that slightly lower Take Profit
#define BTN_TP2M "BTN TP2M" //--- Define the name for the button that greatly lower Take Profit
#define BTN_TP1P "BTN TP1P" //--- Define the name for the button that slightly raise Take Profit
#define BTN_TP2P "BTN TP2P" //--- Define the name for the button that greatly raise Take Profit
#define BTN_YES "BTN YES" //--- Define the name for the button that confirm a trade
#define BTN_NO "BTN NO" //--- Define the name for the button that cancel a trade
#define BTN_IDLE "BTN IDLE" //--- Define the name for the idle button between Yes and No
#define HL_SL "HL SL" //--- Define the name for the Stop Loss horizontal line
#define HL_TP "HL TP" //--- Define the name for the Take Profit horizontal line

#include <Trade/Trade.mqh> //--- Bring in the Trade library needed for trading functions
CTrade obj_Trade; //--- Create a trading object to handle trade operations

bool tradeInAction = false; //--- Track whether a trade setup is currently active
bool isHaveTradeLevels = false; //--- Track whether Stop Loss and Take Profit levels are shown

input double init_lot = 0.03;
input int slow_pts = 10;
input int fast_pts = 100;

在这里,我们首先使用 #define 关键字定义一组交互式按钮,如“BTN_BUY”(买入)和“BTN_SELL”(卖出),以便在我们想要的时候启动交易,让我们能直接控制入场点;同时,“BTN_P”和“BTN_M”按钮则允许我们微调“init_lot”(初始手数)的大小——初始设置为0.03——以匹配我们的风险偏好。我们还加入了“BTN_CLOSE”作为我们的紧急出口,这是一种快速一键关闭所有持仓的方式;同时,我们依赖“tradeInAction”来跟踪我们是否正在设置交易,并依靠“isHaveTradeLevels”来在止损和止盈的图形元素激活时发出信号。

接着,我们调用来自“<Trade/Trade.mqh>”的“CTrade”类,创建一个“obj_Trade”对象,以流畅高效地处理交易执行。为了给我们提供更大的灵活性,我们添加了可调整的 输入参数,例如将“slow_pts”设置为10,“fast_pts”设置为100,这样我们就可以即时微调我们的止损和止盈水平,确保我们的工具包能够适应我们正在测试的任何策略。现在,由于我们需要创建面板按钮,让我们来创建一个函数,该函数包含所有可能的输入参数,以实现代码的可重用性和自定义。

//+------------------------------------------------------------------+
//| Create button function                                           |
//+------------------------------------------------------------------+
void CreateBtn(string objName,int xD,int yD,int xS,int yS,string txt,
               int fs=13,color clrTxt=clrWhite,color clrBg=clrBlack,
               color clrBd=clrBlack,string font="Calibri"){
   ObjectCreate(0,objName,OBJ_BUTTON,0,0,0); //--- Create a new button object on the chart
   ObjectSetInteger(0,objName,OBJPROP_XDISTANCE, xD); //--- Set the button's horizontal position
   ObjectSetInteger(0,objName,OBJPROP_YDISTANCE, yD); //--- Set the button's vertical position
   ObjectSetInteger(0,objName,OBJPROP_XSIZE, xS); //--- Set the button's width
   ObjectSetInteger(0,objName,OBJPROP_YSIZE, yS); //--- Set the button's height
   ObjectSetInteger(0,objName,OBJPROP_CORNER, CORNER_LEFT_UPPER); //--- Position the button from the top-left corner
   ObjectSetString(0,objName,OBJPROP_TEXT, txt); //--- Set the text displayed on the button
   ObjectSetInteger(0,objName,OBJPROP_FONTSIZE, fs); //--- Set the font size of the button text
   ObjectSetInteger(0,objName,OBJPROP_COLOR, clrTxt); //--- Set the color of the button text
   ObjectSetInteger(0,objName,OBJPROP_BGCOLOR, clrBg); //--- Set the background color of the button
   ObjectSetInteger(0,objName,OBJPROP_BORDER_COLOR,clrBd); //--- Set the border color of the button
   ObjectSetString(0,objName,OBJPROP_FONT,font); //--- Set the font style of the button text

   ChartRedraw(0); //--- Refresh the chart to show the new button
}

在这里,我们定义了“CreateBtn”函数,用于在图表上构建每一个按钮——例如“BTN_BUY”或“BTN_SELL”。该函数接收诸如“objName”(用于标识按钮)、“xD”和“yD”(用于定义水平和垂直位置)、“xS”和“yS”(用于定义宽度和高度)以及“txt”(用于显示我们想要的标签,如“BUY”或“SELL”)等输入参数。为实现此功能,我们使用 ObjectCreate 函数在图表上放置一个新的 OBJ_BUTTON 对象,为简单起见,将其基准坐标设置为 (0,0,0)。然后,我们使用 ObjectSetInteger 函数精确地定位它,将“OBJPROP_XDISTANCE”设置为“xD”,将“OBJPROP_YDISTANCE”设置为“yD”,确保它恰好位于我们需要的位置;同时,我们使用“OBJPROP_XSIZE”(对应“xS”)和“OBJPROP_YSIZE”(对应“yS”)来设置其大小,以适应我们的设计。

我们通过将“OBJPROP_CORNER”设置为 CORNER_LEFT_UPPER,将其锚定在左上角,使布局保持一致;并使用 ObjectSetString 将“OBJPROP_TEXT”设置为“txt”,使按钮能清晰地显示其用途。在样式方面,我们将“OBJPROP_FONTSIZE”调整为“fs”(默认为13),文本颜色“OBJPROP_COLOR”调整为“clrTxt”(默认为白色),背景色 OBJPROP_BGCOLOR 调整为“clrBg”(默认为黑色),边框颜色“OBJPROP_BORDER_COLOR”调整为“clrBd”(默认为黑色),同时将“OBJPROP_FONT”设置为“font”(默认为“Calibri”),以获得简洁的外观。最后,我们使用 ChartRedraw 函数刷新图表,窗口ID为“0”,从而立即显示我们的新按钮,以便我们可以在策略测试器中与之交互。现在,无论何时我们想创建一个按钮,都可以调用此函数,我们将首先在 OnInit 事件处理程序中调用它。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit(){
   CreateBtn(BTN_P,150,45,40,25,CharToString(217),15,clrBlack,clrWhite,clrBlack,"Wingdings"); //--- Make the button to increase lot size with an up arrow
   CreateBtn(BTN_LOT,190,45,60,25,string(init_lot),12,clrWhite,clrGray,clrBlack); //--- Make the button showing the current lot size
   CreateBtn(BTN_M,250,45,40,25,CharToString(218),15,clrBlack,clrWhite,clrBlack,"Wingdings"); //--- Make the button to decrease lot size with a down arrow
   CreateBtn(BTN_BUY,110,70,110,30,"BUY",15,clrWhite,clrGreen,clrBlack); //--- Make the Buy button with a green background
   CreateBtn(BTN_SELL,220,70,110,30,"SELL",15,clrWhite,clrRed,clrBlack); //--- Make the Sell button with a red background
   CreateBtn(BTN_CLOSE,110,100,220,30,"PANIC BUTTON (X)",15,clrWhite,clrBlack,clrBlack); //--- Make the emergency button to close all trades
         
   return(INIT_SUCCEEDED); //--- Tell the system the EA start up successfully
}

在这里,我们通过 OnInit 事件处理程序启动我们的手动回测工具包,在策略测试器中设置其界面。我们使用“CreateBtn”函数将“BTN_P”放置在“xD”为150、“yD”为45的位置,并使用来自 CharToString(217) 的“Wingdings”向上箭头;将“BTN_LOT”放置在“xD”为190的位置,显示“init_lot”;将“BTN_M”放置在“xD”为250的位置,并使用来自“CharToString(218)”的向下箭头——所有这些都为手数控制而设计。然后,我们添加“BTN_BUY”在“xD”为110、“yD”为70的位置,标签为“BUY”,背景色为“clrGreen”;添加“BTN_SELL”在“xD”为220的位置,标签为“SELL”,背景色为“clrRed”;以及“BTN_CLOSE”在“xD”为110、“yD”为100的位置,标签为“PANIC BUTTON (X)”,背景色为“clrBlack”;最后通过 return 和 INIT_SUCCEEDED 信号表示初始化成功。我们用于图标的“Wingdings”字体来自MQL5已定义的字符表,如下所示。

WINGDINGS

当我们运行程序时,得到如下输出。

BUTTONS INTERFACE

既然我们已经设定了基础背景,就需要读取按钮的状态和值,以便将它们用于交易。因此,我们也需要相关的实现函数。

int GetState(string Name){return (int)ObjectGetInteger(0,Name,OBJPROP_STATE);} //--- Get whether a button is pressed or not
string GetValue(string Name){return ObjectGetString(0,Name,OBJPROP_TEXT);} //--- Get the text shown on an object
double GetValueHL(string Name){return ObjectGetDouble(0,Name,OBJPROP_PRICE);} //--- Get the price level of a horizontal line

在这里,我们定义了“GetState”函数来检查按钮点击,其中我们使用 ObjectGetInteger 函数和“OBJPROP_STATE”属性来返回“Name”是否被按下;使用“GetValue”函数通过 ObjectGetString 和“OBJPROP_TEXT”从“Name”获取文本;以及使用“GetValueHL”函数通过 ObjectGetDoubleOBJPROP_PRICE 获取“Name”的价格水平,以实现精确的交易控制。现在,我们可以在 OnTick 事件处理程序中使用这些函数来获取按钮状态,因为我们无法在策略测试器中直接使用 OnChartEvent 事件处理程序。以下是我们实现这一点的方法。

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick(){
   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get the current Ask price and adjust it to the right decimal places
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get the current Bid price and adjust it to the right decimal places

   if (GetState(BTN_BUY)==true || GetState(BTN_SELL)){ //--- Check if either the Buy or Sell button is clicked
      tradeInAction = true; //--- Set trade setup to active
   }
}

在这里,我们使用 OnTick 事件处理程序来驱动我们工具包在策略测试器中的实时操作。我们使用 NormalizeDouble 函数和 SymbolInfoDouble 将“Ask”设置为当前的 SYMBOL_ASK 价格,将“Bid”设置为当前的 SYMBOL_BID 价格,两者都通过 _Digits 进行调整以确保精度。如果“GetState”显示“BTN_BUY”或“BTN_SELL”为真,我们就将“tradeInAction”设置为true,以开始我们的交易设置。至此,我们需要额外的交易级别功能,使我们能够设置级别并进行动态调整。让我们为此创建一个函数。

//+------------------------------------------------------------------+
//| Create high low function                                         |
//+------------------------------------------------------------------+
void createHL(string objName,datetime time1,double price1,color clr){
   if (ObjectFind(0,objName) < 0){ //--- Check if the horizontal line doesn’t already exist
      ObjectCreate(0,objName,OBJ_HLINE,0,time1,price1); //--- Create a new horizontal line at the specified price
      ObjectSetInteger(0,objName,OBJPROP_TIME,time1); //--- Set the time property (though not critical for HLINE)
      ObjectSetDouble(0,objName,OBJPROP_PRICE,price1); //--- Set the price level of the horizontal line
      ObjectSetInteger(0,objName,OBJPROP_COLOR,clr); //--- Set the color of the line (red for SL, green for TP)
      ObjectSetInteger(0,objName,OBJPROP_STYLE,STYLE_DASHDOTDOT); //--- Set the line style to dash-dot-dot
      ChartRedraw(0); //--- Refresh the chart to display the new line
   }
}

首先,我们定义了“createHL”函数,用于在策略测试器中为我们的工具包绘制水平线。在该函数中,我们使用 ObjectFind 函数检查“objName”是否存在,如果返回值小于0(即对象不存在),我们就使用 ObjectCreate 函数在“time1”和“price1”位置创建一个“OBJ_HLINE”对象。接着,我们使用 ObjectSetInteger 函数将“OBJPROP_TIME”设置为“time1”,将“OBJPROP_COLOR”设置为“clr”,并将“OBJPROP_STYLE”设置为“STYLE_DASHDOTDOT”;我们使用 ObjectSetDouble 函数将“OBJPROP_PRICE”设置为“price1”;最后,我们使用 ChartRedraw 函数并以“0”为窗口ID来刷新图表,从而将其显示出来。然后,我们将此函数集成到另一个函数中,以无缝地创建交易级别,如下所示。

//+------------------------------------------------------------------+
//| Create trade levels function                                     |
//+------------------------------------------------------------------+
void CreateTradeLevels(){
   double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); //--- Get unnoticed the current Ask price, adjusted for digits
   double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); //--- Get the current Bid price, adjusted for digits
   
   string level_SL,level_TP; //--- Declare variables to hold SL and TP levels as strings
   if (GetState(BTN_BUY)==true){ //--- Check if the Buy button is active
      level_SL = string(Bid-100*_Point); //--- Set initial Stop Loss 100 points below Bid for Buy
      level_TP = string(Bid+100*_Point); //--- Set initial Take Profit 100 points above Bid for Buy
   }
   else if (GetState(BTN_SELL)==true){ //--- Check if the Sell button is active
      level_SL = string(Ask+100*_Point); //--- Set initial Stop Loss 100 points above Ask for Sell
      level_TP = string(Ask-100*_Point); //--- Set initial Take Profit 100 points below Ask for Sell
   }
   
   createHL(HL_SL,0,double(level_SL),clrRed); //--- Create a red Stop Loss line at the calculated level
   createHL(HL_TP,0,double(level_TP),clrGreen); //--- Create a green Take Profit line at the calculated level

   CreateBtn(BTN_SL,110,135,110,23,"SL: "+GetValue(HL_SL),13,clrRed,clrWhite,clrRed); //--- Make a button showing the Stop Loss level
   CreateBtn(BTN_TP,220,135,110,23,"TP: "+GetValue(HL_TP),13,clrGreen,clrWhite,clrGreen); //--- Make a button showing the Take Profit level
   
   CreateBtn(BTN_SL1M,110,158,27,20,"-",17,clrBlack,clrWhite,clrGray); //--- Make a button to slightly lower Stop Loss
   CreateBtn(BTN_SL2M,137,158,27,20,"--",17,clrBlack,clrWhite,clrGray); //--- Make a button to greatly lower Stop Loss
   CreateBtn(BTN_SL2P,164,158,27,20,"++",17,clrBlack,clrWhite,clrGray); //--- Make a button to greatly raise Stop Loss
   CreateBtn(BTN_SL1P,191,158,27,20,"+",17,clrBlack,clrWhite,clrGray); //--- Make a button to slightly raise Stop Loss
   
   CreateBtn(BTN_TP1P,222,158,27,20,"+",17,clrBlack,clrWhite,clrGray); //--- Make a button to slightly raise Take Profit
   CreateBtn(BTN_TP2P,249,158,27,20,"++",17,clrBlack,clrWhite,clrGray); //--- Make a button to greatly raise Take Profit
   CreateBtn(BTN_TP2M,276,158,27,20,"--",17,clrBlack,clrWhite,clrGray); //--- Make a button to greatly lower Take Profit
   CreateBtn(BTN_TP1M,303,158,27,20,"-",17,clrBlack,clrWhite,clrGray); //--- Make a button to slightly lower Take Profit
   
   CreateBtn(BTN_YES,110,178,70,30,CharToString(254),20,clrWhite,clrDarkGreen,clrWhite,"Wingdings"); //--- Make a green checkmark button to confirm the trade
   CreateBtn(BTN_NO,260,178,70,30,CharToString(253),20,clrWhite,clrDarkRed,clrWhite,"Wingdings"); //--- Make a red X button to cancel the trade
   CreateBtn(BTN_IDLE,180,183,80,25,CharToString(40),20,clrWhite,clrBlack,clrWhite,"Wingdings"); //--- Make a neutral button between Yes and No
}

在这里,我们定义了“CreateTradeLevels”函数来设置我们的交易级别。在该函数中,我们使用 NormalizeDouble 函数配合 SymbolInfoDouble 将“Ask”设置为“SYMBOL_ASK”,将“Bid”设置为“SYMBOL_BID”,并通过 _Digits 进行精度调整,同时将“level_SL”和“level_TP”声明为字符串类型。如果“GetState”显示“BTN_BUY”为真,我们将“level_SL”设置为“Bid-100_Point”,将“level_TP”设置为“Bid+100_Point”;但如果“BTN_SELL”为真,我们将“level_SL”设置为“Ask+100_Point”,将“level_TP”设置为“AsK-100_Point”。

我们使用“createHL”函数,在“clrRed”颜色下绘制位于“double(level_SL)”价格的“HL_SL”线,在“clrGreen”颜色下绘制位于“double(level_TP)”价格的“HL_TP”线。然后,我们使用“CreateBtn”函数创建多个按钮:例如,文本为“GetValue(HL_SL)”的“BTN_SL”按钮,文本为“GetValue(HL_TP)”的“BTN_TP”按钮,以及带有“-”和“+”等符号的调整按钮“BTN_SL1M”、“BTN_SL2M”、“BTN_SL2P”、“BTN_SL1P”、“BTN_TP1P”、“BTN_TP2P”、“BTN_TP2M”和“BTN_TP1M”。此外,还使用 CharToString 在“Wingdings”字体中创建了用于确认、取消和中立选项的“BTN_YES”、“BTN_NO”和“BTN_IDLE”按钮。通过这个函数,我们可以在买入或卖出按钮被点击时调用它,以初始化交易级别的设置。

if (!isHaveTradeLevels){ //--- Check if trade levels aren't already on the chart
   CreateTradeLevels(); //--- Add Stop Loss and Take Profit levels and controls to the chart
   isHaveTradeLevels = true; //--- Mark that trade levels are now present
}

在这里,我们设置了一个检查,通过“!isHaveTradeLevels”测试“isHaveTradeLevels”是否为假。当为假时,我们使用“CreateTradeLevels”函数在图表上放置止损和止盈控件,然后将“isHaveTradeLevels”更新为真,以表示它们现在是活动状态。编译完成后,我们得到了以下结果。

TRADE LEVELS

接下来,我们需要让这些交易按钮“活”起来,使它们能够响应并执行相关的功能。以下是我们实现这一点的方法。

if (tradeInAction){ //--- Continue if a trade setup is active

   // SL SLOW/FAST BUTTONS
   if (GetState(BTN_SL1M)){ //--- Check if the small Stop Loss decrease button is clicked
      ObjectSetDouble(0,HL_SL,OBJPROP_PRICE,GetValueHL(HL_SL)-slow_pts*_Point); //--- Move the Stop Loss down by a small amount
      ObjectSetInteger(0,BTN_SL1M,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_SL2M)){ //--- Check if the large Stop Loss decrease button is clicked
      ObjectSetDouble(0,HL_SL,OBJPROP_PRICE,GetValueHL(HL_SL)-fast_pts*_Point); //--- Move the Stop Loss down by a large amount
      ObjectSetInteger(0,BTN_SL2M,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_SL1P)){ //--- Check if the small Stop Loss increase button is clicked
      ObjectSetDouble(0,HL_SL,OBJPROP_PRICE,GetValueHL(HL_SL)+slow_pts*_Point); //--- Move the Stop Loss up by a small amount
      ObjectSetInteger(0,BTN_SL1P,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_SL2P)){ //--- Check if the large Stop Loss increase button is clicked
      ObjectSetDouble(0,HL_SL,OBJPROP_PRICE,GetValueHL(HL_SL)+fast_pts*_Point); //--- Move the Stop Loss up by a large amount
      ObjectSetInteger(0,BTN_SL2P,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   
   // TP SLOW/FAST BUTTONS
   if (GetState(BTN_TP1M)){ //--- Check if the small Take Profit decrease button is clicked
      ObjectSetDouble(0,HL_TP,OBJPROP_PRICE,GetValueHL(HL_TP)-slow_pts*_Point); //--- Move the Take Profit down by a small amount
      ObjectSetInteger(0,BTN_TP1M,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_TP2M)){ //--- Check if the large Take Profit decrease button is clicked
      ObjectSetDouble(0,HL_TP,OBJPROP_PRICE,GetValueHL(HL_TP)-fast_pts*_Point); //--- Move the Take Profit down by a large amount
      ObjectSetInteger(0,BTN_TP2M,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_TP1P)){ //--- Check if the small Take Profit increase button is clicked
      ObjectSetDouble(0,HL_TP,OBJPROP_PRICE,GetValueHL(HL_TP)+slow_pts*_Point); //--- Move the Take Profit up by a small amount
      ObjectSetInteger(0,BTN_TP1P,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }
   if (GetState(BTN_TP2P)){ //--- Check if the large Take Profit increase button is clicked
      ObjectSetDouble(0,HL_TP,OBJPROP_PRICE,GetValueHL(HL_TP)+fast_pts*_Point); //--- Move the Take Profit up by a large amount
      ObjectSetInteger(0,BTN_TP2P,OBJPROP_STATE,false); //--- Turn off the button press state
      ChartRedraw(0); //--- Refresh the chart to show the change
   }

}

在这里,当“tradeInAction”为真时,我们在工具包中管理止损和止盈的调整。我们使用“GetState”函数来检查“BTN_SL1M”、“BTN_SL2M”、“BTN_SL1P”或“BTN_SL2P”等按钮是否被点击,然后通过 ObjectSetDouble 函数、配合“OBJPROP_PRICE”属性和“GetValueHL”函数获取的当前值,以“slow_pts_Point”或“fast_pts_Point”的幅度来调整“HL_SL”线。之后,我们使用 ObjectSetInteger 函数将 OBJPROP_STATE 重置为假,并使用 ChartRedraw 函数更新图表。对于“HL_TP”的调整,我们以类似的方式处理“BTN_TP1M”、“BTN_TP2M”、“BTN_TP1P”或“BTN_TP2P”这些按钮。最后,一旦设置完成,我们就可以确认其位置并开立相应的持仓,然后清理掉交易设置面板。但首先,我们需要一个函数来删除这个设置面板。

//+------------------------------------------------------------------+
//| Delete objects function                                          |
//+------------------------------------------------------------------+
void DeleteObjects_SLTP(){
   ObjectDelete(0,HL_SL); //--- Remove the Stop Loss line from the chart
   ObjectDelete(0,HL_TP); //--- Remove the Take Profit line from the chart
   ObjectDelete(0,BTN_SL); //--- Remove the Stop Loss display button
   ObjectDelete(0,BTN_TP); //--- Remove the Take Profit display button
   ObjectDelete(0,BTN_SL1M); //--- Remove the small Stop Loss decrease button
   ObjectDelete(0,BTN_SL2M); //--- Remove the large Stop Loss decrease button
   ObjectDelete(0,BTN_SL1P); //--- Remove the small Stop Loss increase button
   ObjectDelete(0,BTN_SL2P); //--- Remove the large Stop Loss increase button
   ObjectDelete(0,BTN_TP1P); //--- Remove the small Take Profit increase button
   ObjectDelete(0,BTN_TP2P); //--- Remove the large Take Profit increase button
   ObjectDelete(0,BTN_TP2M); //--- Remove the large Take Profit decrease button
   ObjectDelete(0,BTN_TP1M); //--- Remove the small Take Profit decrease button
   ObjectDelete(0,BTN_YES); //--- Remove the confirm trade button
   ObjectDelete(0,BTN_NO); //--- Remove the cancel trade button
   ObjectDelete(0,BTN_IDLE); //--- Remove the idle button
   
   ChartRedraw(0); //--- Refresh the chart to show all objects removed
}

在这里,我们通过“DeleteObjects_SLTP”函数来处理工具包中的清理工作。在该函数中,我们使用 ObjectDelete 函数从图表上移除“HL_SL”、“HL_TP”、“BTN_SL”、“BTN_TP”、“BTN_SL1M”、“BTN_SL2M”、“BTN_SL1P”、“BTN_SL2P”、“BTN_TP1P”、“BTN_TP2P”、“BTN_TP2M”、“BTN_TP1M”、“BTN_YES”、“BTN_NO”和“BTN_IDLE”这些对象,然后使用 ChartRedraw 函数并以“0”为窗口ID来刷新图表,显示所有内容已被清除。现在,我们可以在下单程序中使用这个函数了。

// BUY ORDER PLACEMENT
if (GetState(BTN_BUY) && GetState(BTN_YES)){ //--- Check if both Buy and Yes buttons are clicked
   obj_Trade.Buy(double(GetValue(BTN_LOT)),_Symbol,Ask,GetValueHL(HL_SL),GetValueHL(HL_TP)); //--- Place a Buy order with set lot size, SL, and TP
   DeleteObjects_SLTP(); //--- Remove all trade level objects from the chart
   isHaveTradeLevels = false; //--- Mark that trade levels are no longer present
   ObjectSetInteger(0,BTN_YES,OBJPROP_STATE,false); //--- Turn off the Yes button press state
   ObjectSetInteger(0,BTN_BUY,OBJPROP_STATE,false); //--- Turn off the Buy button press state
   tradeInAction = false; //--- Mark the trade setup as complete
   ChartRedraw(0); //--- Refresh the chart to reflect changes
}
// SELL ORDER PLACEMENT
else if (GetState(BTN_SELL) && GetState(BTN_YES)){ //--- Check if both Sell and Yes buttons are clicked
   obj_Trade.Sell(double(GetValue(BTN_LOT)),_Symbol,Bid,GetValueHL(HL_SL),GetValueHL(HL_TP)); //--- Place a Sell order with set lot size, SL, and TP
   DeleteObjects_SLTP(); //--- Remove all trade level objects from the chart
   isHaveTradeLevels = false; //--- Mark that trade levels are no longer present
   ObjectSetInteger(0,BTN_YES,OBJPROP_STATE,false); //--- Turn off the Yes button press state
   ObjectSetInteger(0,BTN_SELL,OBJPROP_STATE,false); //--- Turn off the Sell button press state
   tradeInAction = false; //--- Mark the trade setup as complete
   ChartRedraw(0); //--- Refresh the chart to reflect changes
}
else if (GetState(BTN_NO)){ //--- Check if the No button is clicked to cancel
   DeleteObjects_SLTP(); //--- Remove all trade level objects from the chart
   isHaveTradeLevels = false; //--- Mark that trade levels are no longer present
   ObjectSetInteger(0,BTN_NO,OBJPROP_STATE,false); //--- Turn off the No button press state
   ObjectSetInteger(0,BTN_BUY,OBJPROP_STATE,false); //--- Turn off the Buy button press state
   ObjectSetInteger(0,BTN_SELL,OBJPROP_STATE,false); //--- Turn off the Sell button press state
   tradeInAction = false; //--- Mark the trade setup as canceled
   ChartRedraw(0); //--- Refresh the chart to reflect changes
}

我们在策略测试器中的工具包里执行交易。我们使用“GetState”函数检查“BTN_BUY”和“BTN_YES”是否为真,如果是,则使用“obj_Trade.Buy”方法,并传入“double(GetValue(BTN_LOT))”(手数)、_Symbol(交易品种)、“Ask”(价格)、“GetValueHL(HL_SL)”(止损)和“GetValueHL(HL_TP)”(止盈)来下达买单。或者,如果“BTN_SELL”和“BTN_YES”为真,我们则使用“obj_Trade.Sell”方法,并将价格替换为“Bid”。在任何一种情况下,我们都使用“DeleteObjects_SLTP”函数来清除对象,将“isHaveTradeLevels”和“tradeInAction”设置为假,使用 ObjectSetInteger 函数将“BTN_YES”、“BTN_BUY”或“BTN_SELL”的 OBJPROP_STATE 状态重置为假,并使用 ChartRedraw 函数来更新图表。但如果“BTN_NO”为真,我们则通过类似地清除对象和重置状态来取消操作。同样地,我们按如下方式处理交易手数增加或减少的按钮。

if (GetState(BTN_P)==true){ //--- Check if the lot size increase button is clicked
   double newLot = (double)GetValue(BTN_LOT); //--- Get the current lot size as a number
   double lotStep = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); //--- Get the minimum lot size change allowed
   newLot += lotStep; //--- Increase the lot size by one step
   newLot = NormalizeDouble(newLot,2); //--- Round the new lot size to 2 decimal places
   newLot = newLot > 0.1 ? lotStep : newLot; //--- Ensure lot size doesn't exceed 0.1, otherwise reset to step
   ObjectSetString(0,BTN_LOT,OBJPROP_TEXT,string(newLot)); //--- Update the lot size display with the new value
   ObjectSetInteger(0,BTN_P,OBJPROP_STATE,false); //--- Turn off the increase button press state
   ChartRedraw(0); //--- Refresh the chart to show the new lot size
}
if (GetState(BTN_M)==true){ //--- Check if the lot size decrease button is clicked
   double newLot = (double)GetValue(BTN_LOT); //--- Get the current lot size as a number
   double lotStep = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); //--- Get the minimum lot size change allowed
   newLot -= lotStep; //--- Decrease the lot size by one step
   newLot = NormalizeDouble(newLot,2); //--- Round the new lot size to 2 decimal places
   newLot = newLot < lotStep ? lotStep : newLot; //--- Ensure lot size doesn't go below minimum, otherwise set to step
   ObjectSetString(0,BTN_LOT,OBJPROP_TEXT,string(newLot)); //--- Update the lot size display with the new value
   ObjectSetInteger(0,BTN_M,OBJPROP_STATE,false); //--- Turn off the decrease button press state
   ChartRedraw(0); //--- Refresh the chart to show the new lot size
}

在这里,我们从增加手数开始调整。我们使用“GetState”函数检查“BTN_P”是否为真,然后使用“GetValue”从“BTN_LOT”获取值来设置“newLot”,使用 SymbolInfoDoubleSYMBOL_VOLUME_STEP 获取“lotStep”(最小手数变化量),将“lotStep”加到“newLot”上,并使用 NormalizeDouble 将其四舍五入到2位小数,如果结果超过0.1,则将其限制为0.1。之后,使用 ObjectSetString 更新“BTN_LOT”的“OBJPROP_TEXT”属性,使用“ObjectSetInteger”将“BTN_P”的“OBJPROP_STATE”重置为假,最后通过 ChartRedraw 刷新图表。

对于减少手数,我们使用“GetState”检查“BTN_M”,以相同的方式获取“newLot”后从中减去“lotStep”,确保其值不小于“lotStep”,并应用相同的 ObjectSetString、“ObjectSetInteger”和“ChartRedraw”函数步骤来更新“BTN_LOT”并重置“BTN_M”的状态。至于“恐慌”按钮,我们需要定义一个函数,在它被点击时关闭所有开仓。

//+------------------------------------------------------------------+
//| Close all positions function                                     |
//+------------------------------------------------------------------+
void closeAllPositions(){
   for (int i=PositionsTotal()-1; i>=0; i--){ //--- Loop through all open positions, starting from the last one
      ulong ticket = PositionGetTicket(i); //--- Get the ticket number of the current position
      if (ticket > 0){ //--- Check if the ticket is valid
         if (PositionSelectByTicket(ticket)){ //--- Select the position by its ticket number
            if (PositionGetString(POSITION_SYMBOL)==_Symbol){ //--- Check if the position is for the current chart symbol
               obj_Trade.PositionClose(ticket); //--- Close the selected position
            }
         }
      }
   }
}

我们通过“closeAllPositions”函数来处理关闭所有持仓。在该函数中,我们使用 PositionsTotal 函数从最后一个持仓(索引为总数减1)开始向下循环到0,使用 PositionGetTicket 函数获取每个索引“i”对应的“ticket”(订单号)。如果“ticket”有效,我们使用 PositionSelectByTicket 选中该持仓,然后使用 PositionGetString 检查 POSITION_SYMBOL(交易品种)是否与 _Symbol(当前图表品种)匹配,匹配则使用“obj_Trade.PositionClose”方法通过“ticket”关闭该持仓。然后,我们可以在“恐慌”按钮被点击时调用此函数来关闭所有持仓。

if (GetState(BTN_CLOSE)==true){ //--- Check if the close all positions button is clicked
   closeAllPositions(); //--- Close all open trades
   ObjectSetInteger(0,BTN_CLOSE,OBJPROP_STATE,false); //--- Turn off the close button press state
   ChartRedraw(0); //--- Refresh the chart to reflect closed positions
}

为了管理关闭所有交易的功能,我们使用“GetState”函数检查“BTN_CLOSE”是否为真。如果是,我们使用“closeAllPositions”函数关闭所有开仓,然后使用 ObjectSetInteger 函数将“BTN_CLOSE”的 OBJPROP_STATE 设置为假,并使用 ChartRedraw 函数(以“0”为参数)来更新图表。在编译并运行程序后,我们得到以下结果。

FINAL OUTCOME

从图中我们可以看到,我们设置了交易级别,并且可以动态开仓,实现了我们的目标。现在剩下的是对程序进行彻底的测试,这将在接下来的部分中讨论。


实战回测:使用工具包

我们在 MetaTrader 5的策略测试器 中测试我们的工具包:加载程序,选择我们的设置,然后启动它——观看下面的 图形交换格式 (GIF) 图像,可以看到买入、卖出和调整按钮以闪电般的速度运行。点击买入或卖出,微调止损、止盈和手数,然后用“是”确认或用“否”取消,“恐慌”按钮随时准备快速关闭所有交易。如下所示。

回测图


结论

总之,我们打造了一个手动回测工具包,它将手动控制与MQL5中 策略测试器 的速度相结合,简化了我们测试交易策略的方式。我们已经展示了如何设计它、编码它,以及如何通过按钮来调整交易——所有这些都为快速、精确的模拟量身定制。您可以根据自己的需求调整此工具包,并用它来增强您的回测体验。

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

最近评论 | 前往讨论 (6)
Allan Munene Mutiiria
Allan Munene Mutiiria | 16 4月 2025 在 14:31
Mogulh Chilyalya Kiti #:
不同的时间框架

您好。目前只有一个时间框架。也许在不久的将来可以试试。

Blessing Dumbura
Blessing Dumbura | 16 4月 2025 在 15:47
谢谢,这是一个有用的工具
Allan Munene Mutiiria
Allan Munene Mutiiria | 16 4月 2025 在 18:26
Blessing Dumbura #:
谢谢,这是一个有用的工具

当然,欢迎并感谢您的反馈。

Dontrace
Dontrace | 7 5月 2025 在 11:08

想知道如何将 mq4 指标转换为 mq5 指标

zigooo
zigooo | 17 6月 2025 在 17:16
我试着修改它,使它可以根据 EA 输入参数,用 lotsize 和 tp/sl 快速打开订单。它在真实市场上可以工作,但在回溯测试模式下却不能工作。解决方案是什么?
交易中的神经网络:具有预测编码的混合交易框架(StockFormer) 交易中的神经网络:具有预测编码的混合交易框架(StockFormer)
在本文中,我们将讨论混合交易系统 StockFormer,其结合了预测编码和强化学习(RL)算法。该框架用到 3 个变换器分支,集成了多样化多头注意力(DMH-Attn)机制,改进了原版的注意力模块,采用多头前馈模块,能够捕捉不同子空间中的多元化时间序列形态。
市场模拟(第五部分):创建 C_Orders 类(二) 市场模拟(第五部分):创建 C_Orders 类(二)
在本文中,我将解释 Chart Trade 如何与 EA 交易一起处理平仓请求,以关闭用户的所有未平仓头寸。这听起来简单,但你需要知道如何应对一些复杂情况。
市场模拟(第六部分):将信息从 MetaTrader 5 传输到 Excel 市场模拟(第六部分):将信息从 MetaTrader 5 传输到 Excel
许多人,尤其是非程序员,发现在 MetaTrader 5 和其他程序之间传输信息非常困难。其中一个程序就是 Excel。许多人使用 Excel 作为管理和维护风险控制的一种方式。这是一个优秀的程序,易于学习,即使对于那些不是 VBA 程序员的人来说也是如此。在这里,我们将看看如何在 MetaTrader 5 和 Excel 之间建立连接(一种非常简单的方法)。
探索达瓦斯箱体突破策略中的高级机器学习技术 探索达瓦斯箱体突破策略中的高级机器学习技术
达瓦斯箱体突破策略由尼古拉斯·达瓦斯(Nicolas Darvas)提出,是一种技术交易方法:当股价突破预设的"箱体"区间上沿时,视为潜在买入信号,表明强劲的上升动能。本文将以该策略为例,探讨三种高级机器学习技术的应用。其中包括:利用机器学习模型直接生成交易信号(而非仅过滤交易);采用连续型信号(而非离散型信号);使用基于不同时间框架训练的模型进行交易验证。