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

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

第 2/3 篇

◍ 用按钮接管图表交互的底层铺垫

想在 MT5 图表上用鼠标点一下就触发交易或切换状态,第一步不是写交易逻辑,而是先把界面对象建起来。下面这段初始化与建按钮的代码,就是给后续「半自动策略」打地基:它把交易类、状态开关和三个可调参数先声明好,再封装一个通用的按钮绘制函数。 参数层面给了三个入口:init_lot 默认 0.03 手,slow_pts 默认 10 点,fast_pts 默认 100 点。慢线点数和快线点数差距拉到 10 倍,意味着后续快慢通道的灵敏度分层会非常明显,适合在黄金或欧美这种点值差异大的品种上做分离测试。 CreateBtn 这个函数把坐标、尺寸、文字、颜色全参数化了,默认字号 13、白字黑底黑边、Calibri 字体。你直接抄进 EA,调用时传 xD/yD 就能把按钮钉在左上角,不用每次手写十几行 ObjectSetInteger。外汇和贵金属杠杆高,这种半自动脚本只是辅助,实盘前务必在策略测试器跑至少 3 个月 tick 数据验证。

MQL5 / C++
class="macro">#include <Trade/Trade.mqh> class=class="str">"cmt">//--- Bring in the Trade library needed for trading functions
CTrade obj_Trade; class=class="str">"cmt">//--- Create a trading object to handle trade operations
class="type">bool tradeInAction = false; class=class="str">"cmt">//--- Track whether a trade setup is currently active
class="type">bool isHaveTradeLevels = false; class=class="str">"cmt">//--- Track whether Stop Loss and Take Profit levels are shown
input class="type">class="kw">double init_lot = class="num">0.03;
input class="type">int slow_pts = class="num">10;
input class="type">int fast_pts = class="num">100;
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Create button function                                            |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void CreateBtn(class="type">class="kw">string objName,class="type">int xD,class="type">int yD,class="type">int xS,class="type">int yS,class="type">class="kw">string txt,
                class="type">int fs=class="num">13,class="type">class="kw">color clrTxt=clrWhite,class="type">class="kw">color clrBg=clrBlack,
                class="type">class="kw">color clrBd=clrBlack,class="type">class="kw">string font="Calibri"){
   ObjectCreate(class="num">0,objName,OBJ_BUTTON,class="num">0,class="num">0,class="num">0); class=class="str">"cmt">//--- Create a new button object on the chart
   ObjectSetInteger(class="num">0,objName,OBJPROP_XDISTANCE, xD); class=class="str">"cmt">//--- Set the button&class="macro">#x27;s horizontal position
   ObjectSetInteger(class="num">0,objName,OBJPROP_YDISTANCE, yD); class=class="str">"cmt">//--- Set the button&class="macro">#x27;s vertical position
   ObjectSetInteger(class="num">0,objName,OBJPROP_XSIZE, xS); class=class="str">"cmt">//--- Set the button&class="macro">#x27;s width
   ObjectSetInteger(class="num">0,objName,OBJPROP_YSIZE, yS); class=class="str">"cmt">//--- Set the button&class="macro">#x27;s height
   ObjectSetInteger(class="num">0,objName,OBJPROP_CORNER, CORNER_LEFT_UPPER); class=class="str">"cmt">//--- Position the button from the top-left corner
   ObjectSetString(class="num">0,objName,OBJPROP_TEXT, txt); class=class="str">"cmt">//--- Set the text displayed on the button
   ObjectSetInteger(class="num">0,objName,OBJPROP_FONTSIZE, fs); class=class="str">"cmt">//--- Set the font size of the button text
   ObjectSetInteger(class="num">0,objName,OBJPROP_COLOR, clrTxt); class=class="str">"cmt">//--- Set the class="type">class="kw">color of the button text
   ObjectSetInteger(class="num">0,objName,OBJPROP_BGCOLOR, clrBg); class=class="str">"cmt">//--- Set the background class="type">class="kw">color of the button
   ObjectSetInteger(class="num">0,objName,OBJPROP_BORDER_COLOR,clrBd); class=class="str">"cmt">//--- Set the border class="type">class="kw">color of the button
   ObjectSetString(class="num">0,objName,OBJPROP_FONT,font); class=class="str">"cmt">//--- Set the font style of the button text
   ChartRedraw(class="num">0); class=class="str">"cmt">//--- Refresh the chart to show the new button
}

class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert initialization function                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit(){

一键面板背后的按钮与状态读取

EA 初始化收尾阶段用 CreateBtn 一口气铺了 6 个图形按钮:加手数(150,45 起,40×25,Wingdings 字符 217 即上箭头)、手数显示(190,45,60×25,初始仓位文字)、减手数(250,45,字符 218 下箭头)、BUY(110,70,绿底)、SELL(220,70,红底)、以及 110,100 处跨 220 宽的全平紧急键「PANIC BUTTON (X)」。坐标和尺寸都是像素级写死,想在 MT5 里改布局直接调前四个数值即可。 return(INIT_SUCCEEDED) 之前必须把按钮建完,否则面板缺控件,OnTick 里的状态判断会读到空对象。下方三个小函数才是面板能「活」的关键:GetState 用 ObjectGetInteger 读 OBJPROP_STATE 判断按钮是否被按,GetValue 读 OBJPROP_TEXT 拿显示文字,GetValueHL 读 OBJPROP_PRICE 取水平线价格。 OnTick 里先 NormalizeDouble 把 Ask/Bid 对齐 _Digits,避免点差计算出现多余小数位。随后只要 BTN_BUY 或 BTN_SELL 的 STATE 为真,就把 tradeInAction 置 true,交易逻辑才被激活。外汇与贵金属波动剧烈,这类一键发单面板务必先在策略测试器跑熟,实盘误触可能造成快速亏损。

MQL5 / C++
  CreateBtn(BTN_P,class="num">150,class="num">45,class="num">40,class="num">25,CharToString(class="num">217),class="num">15,clrBlack,clrWhite,clrBlack,"Wingdings"); class=class="str">"cmt">//--- Make the button to increase lot size with an up arrow
  CreateBtn(BTN_LOT,class="num">190,class="num">45,class="num">60,class="num">25,class="type">class="kw">string(init_lot),class="num">12,clrWhite,clrGray,clrBlack); class=class="str">"cmt">//--- Make the button showing the current lot size
  CreateBtn(BTN_M,class="num">250,class="num">45,class="num">40,class="num">25,CharToString(class="num">218),class="num">15,clrBlack,clrWhite,clrBlack,"Wingdings"); class=class="str">"cmt">//--- Make the button to decrease lot size with a down arrow
  CreateBtn(BTN_BUY,class="num">110,class="num">70,class="num">110,class="num">30,"BUY",class="num">15,clrWhite,clrGreen,clrBlack); class=class="str">"cmt">//--- Make the Buy button with a green background
  CreateBtn(BTN_SELL,class="num">220,class="num">70,class="num">110,class="num">30,"SELL",class="num">15,clrWhite,clrRed,clrBlack); class=class="str">"cmt">//--- Make the Sell button with a red background
  CreateBtn(BTN_CLOSE,class="num">110,class="num">100,class="num">220,class="num">30,"PANIC BUTTON(X)",class="num">15,clrWhite,clrBlack,clrBlack); class=class="str">"cmt">//--- Make the emergency button to close all trades

    class="kw">return(INIT_SUCCEEDED); class=class="str">"cmt">//--- Tell the system the EA start up successfully
}

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

class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Expert tick function                                             |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnTick(){
   class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); class=class="str">"cmt">//--- Get the current Ask price and adjust it to the right decimal places
   class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); class=class="str">"cmt">//--- Get the current Bid price and adjust it to the right decimal places
   if (GetState(BTN_BUY)==true || GetState(BTN_SELL)){ class=class="str">"cmt">//--- Check if either the Buy or Sell button is clicked
      tradeInAction = true; class=class="str">"cmt">//--- Set trade setup to active
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+

「用水平线把止损止盈画在图上」

想在 MT5 图表上直观看到挂单的止损止盈位,最轻量的做法是直接画 OBJ_HLINE。下面这段逻辑把创建水平线封装成 createHL,入参带对象名、时间锚点、价格和颜色,只在对象不存在时才建线,避免重复刷新把图弄脏。 createHL 里先用 ObjectFind 判重,返回小于 0 才进 ObjectCreate 建水平线;随后用 ObjectSetInteger 和 ObjectSetDouble 分别写时间、价格、颜色与线型(STYLE_DASHDOTDOT 虚点线),最后 ChartRedraw 强制重绘。红色给止损、绿色给止盈,肉眼一眼能分。 上层 CreateTradeLevels 负责算价:先 NormalizeDouble 取当前 Ask/Bid 并对齐 _Digits,再按按钮状态给 Buy 或 Sell 预设 100 点缓冲——Buy 的 SL 在 Bid 下方 100*_Point、TP 在上方;Sell 反之。100 点只是示例初值,EURUSD 这类品种约 10 美元/标准手盈亏敏感,实战里按波动率调更稳。外汇贵金属杠杆高,画线仅是辅助,不代表任何方向确定性。 把 HL_SL、HL_TP 常量和 BTN_BUY/SELL 的 GetState 接进你自己的面板,编译挂到图表,点买点卖就能看到红绿线自动落位,省去手动拖拽。

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

class="type">void CreateTradeLevels(){
   class="type">class="kw">double Ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); class=class="str">"cmt">//--- Get unnoticed the current Ask price, adjusted for digits
   class="type">class="kw">double Bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); class=class="str">"cmt">//--- Get the current Bid price, adjusted for digits

   class="type">class="kw">string level_SL,level_TP; class=class="str">"cmt">//--- Declare variables to hold SL and TP levels as strings
   if (GetState(BTN_BUY)==true){ class=class="str">"cmt">//--- Check if the Buy button is active
      level_SL = class="type">class="kw">string(Bid-class="num">100*_Point); class=class="str">"cmt">//--- Set initial Stop Loss class="num">100 points below Bid for Buy
      level_TP = class="type">class="kw">string(Bid+class="num">100*_Point); class=class="str">"cmt">//--- Set initial Take Profit class="num">100 points above Bid for Buy
   }
   else if (GetState(BTN_SELL)==true){ class=class="str">"cmt">//--- Check if the Sell button is active
      level_SL = class="type">class="kw">string(Ask+class="num">100*_Point); class=class="str">"cmt">//--- Set initial Stop Loss class="num">100 points above Ask for Sell
      level_TP = class="type">class="kw">string(Ask-class="num">100*_Point); class=class="str">"cmt">//--- Set initial Take Profit class="num">100 points below Ask for Sell
   }

   createHL(HL_SL,class="num">0,class="type">class="kw">double(level_SL),clrRed); class=class="str">"cmt">//--- Create a red Stop Loss line at the calculated level
   createHL(HL_TP,class="num">0,class="type">class="kw">double(level_TP),clrGreen); class=class="str">"cmt">//--- Create a green Take Profit line at the calculated level
}

◍ 在图表上铺止损止盈与微调按钮

这段逻辑负责把交易面板直接画到 MT5 图表上:先放两个主按钮显示当前 SL、TP 数值,红底白字对应止损、绿底白字对应止盈,坐标分别固定在 (110,135) 与 (220,135),宽 110 高 23,字号 13。 紧接着在第二行 y=158 处排了 8 个 27×20 的小方块,用来微调挂单位置:SL 侧四个分别是 -、--、++、+,TP 侧四个是 +、++、--、-,每个都用 clrGray 边框、17 号字,点一下就可能让止损或止盈偏移一个或几个步进。 最底下一行 y=178 放了确认与取消:绿色对勾用 Wingdings 字符 254,红色叉用 253,中间还夹一个字符 40 的中性钮。整套按钮靠 CreateBtn() 批量生成,若 isHaveTradeLevels 为 false 才调用 CreateTradeLevels() 补建,避免重复叠加。 外汇与贵金属波动剧烈,这类一键改 SL/TP 的面板只降低操作延迟,不预示胜率,实盘前务必在策略测试器里跑一遍坐标冲突。

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

if (!isHaveTradeLevels){ class=class="str">"cmt">//--- Check if trade levels aren&class="macro">#x27;t already on the chart
  CreateTradeLevels(); class=class="str">"cmt">//--- Add Stop Loss and Take Profit levels and controls to the chart

用按钮微调止损止盈的实时位移

当 tradeInAction 为真,说明当前图表上已经挂好了待成交或已激活的 trade levels,这时通过面板按钮直接拖 SL/TP 比手动拉横线更快。代码里把止损下移、上移和止盈下移、上移都拆成了「慢速」「快速」两档,对应 slow_pts 与 fast_pts 两个步长倍数,实盘里通常慢速设 1 倍点差、快速设 5 倍点差,触感差别很明显。 每一段逻辑都是先 GetState 读按钮是否被点下,再用 ObjectSetDouble 改 HL_SL 或 HL_TP 的 OBJPROP_PRICE,偏移量就是 GetValueHL 取到的当前价加减 N*_Point。改完立刻 ObjectSetInteger 把按钮 STATE 归 false,并 ChartRedraw(0) 强制重绘,否则 MT5 子窗口里的水平线不会马上跳。 外汇和贵金属杠杆高,SL 被快速按钮误推太远可能让单笔风险从 1% 飙到 5% 以上,建议把 fast_pts 锁死在账户风险阈值内再编译。

MQL5 / C++
isHaveTradeLevels = true; class=class="str">"cmt">//--- Mark that trade levels are now present
}
if (tradeInAction){ class=class="str">"cmt">//--- Continue if a trade setup is active
   class=class="str">"cmt">// SL SLOW/FAST BUTTONS
   if (GetState(BTN_SL1M)){ class=class="str">"cmt">//--- Check if the small Stop Loss decrease button is clicked
        ObjectSetDouble(class="num">0,HL_SL,OBJPROP_PRICE,GetValueHL(HL_SL)-slow_pts*_Point); class=class="str">"cmt">//--- Move the Stop Loss down by a small amount
        ObjectSetInteger(class="num">0,BTN_SL1M,OBJPROP_STATE,false); class=class="str">"cmt">//--- Turn off the button press state
        ChartRedraw(class="num">0); class=class="str">"cmt">//--- Refresh the chart to show the change
   }
   if (GetState(BTN_SL2M)){ class=class="str">"cmt">//--- Check if the large Stop Loss decrease button is clicked
        ObjectSetDouble(class="num">0,HL_SL,OBJPROP_PRICE,GetValueHL(HL_SL)-fast_pts*_Point); class=class="str">"cmt">//--- Move the Stop Loss down by a large amount
        ObjectSetInteger(class="num">0,BTN_SL2M,OBJPROP_STATE,false); class=class="str">"cmt">//--- Turn off the button press state
        ChartRedraw(class="num">0); class=class="str">"cmt">//--- Refresh the chart to show the change
   }
   if (GetState(BTN_SL1P)){ class=class="str">"cmt">//--- Check if the small Stop Loss increase button is clicked
        ObjectSetDouble(class="num">0,HL_SL,OBJPROP_PRICE,GetValueHL(HL_SL)+slow_pts*_Point); class=class="str">"cmt">//--- Move the Stop Loss up by a small amount
        ObjectSetInteger(class="num">0,BTN_SL1P,OBJPROP_STATE,false); class=class="str">"cmt">//--- Turn off the button press state
        ChartRedraw(class="num">0); class=class="str">"cmt">//--- Refresh the chart to show the change
   }
   if (GetState(BTN_SL2P)){ class=class="str">"cmt">//--- Check if the large Stop Loss increase button is clicked
        ObjectSetDouble(class="num">0,HL_SL,OBJPROP_PRICE,GetValueHL(HL_SL)+fast_pts*_Point); class=class="str">"cmt">//--- Move the Stop Loss up by a large amount
        ObjectSetInteger(class="num">0,BTN_SL2P,OBJPROP_STATE,false); class=class="str">"cmt">//--- Turn off the button press state
        ChartRedraw(class="num">0); class=class="str">"cmt">//--- Refresh the chart to show the change
   }

   class=class="str">"cmt">// TP SLOW/FAST BUTTONS
   if (GetState(BTN_TP1M)){ class=class="str">"cmt">//--- Check if the small Take Profit decrease button is clicked
        ObjectSetDouble(class="num">0,HL_TP,OBJPROP_PRICE,GetValueHL(HL_TP)-slow_pts*_Point); class=class="str">"cmt">//--- Move the Take Profit down by a small amount
        ObjectSetInteger(class="num">0,BTN_TP1M,OBJPROP_STATE,false); class=class="str">"cmt">//--- Turn off the button press state
        ChartRedraw(class="num">0); class=class="str">"cmt">//--- Refresh the chart to show the change
   }
   if (GetState(BTN_TP2M)){ class=class="str">"cmt">//--- Check if the large Take Profit decrease button is clicked
        ObjectSetDouble(class="num">0,HL_TP,OBJPROP_PRICE,GetValueHL(HL_TP)-fast_pts*_Point); class=class="str">"cmt">//--- Move the Take Profit down by a large amount
        ObjectSetInteger(class="num">0,BTN_TP2M,OBJPROP_STATE,false); class=class="str">"cmt">//--- Turn off the button press state
        ChartRedraw(class="num">0); class=class="str">"cmt">//--- Refresh the chart to show the change
   }

常见问题

先建好按钮对象类和图表事件回调框架,把鼠标点击坐标映射到按钮区域,才能后续做一键面板。
用水平线对象抓取价格坐标,把线价读进订单逻辑,拖线即改止损止盈,比输入框直观且不易填错。
小布可替你在对应品种页生成止损止盈线与微调面板,你只管拖线和决策,省去手写图表交互代码。
黄金类常设 0.5~1 美元步长,外汇对设 1~2 点,按品种波动调,别一次移太大以免滑点吃肉。
只刷新被改的那条线而非重绘全图,并限制每秒刷新次数,图表对象超 50 个时尤其要这么做。