在 MQL5 中创建每日回撤限制器 EA·综合运用
◍ 面板初始化与逐笔触发骨架
EA 启动时先在图表左上区域铺一块日回撤监控面板:从 y=210 到 y=300 按 15 像素行距写死标签,起始余额、5.00% 最大回撤阈值、按余额×5% 算出的蓝色金额线、当日平仓利润与当前回撤百分比都先置 0,最后用蓝字提示「初始化完成,准备交易」并 return INIT_SUCCEEDED。 OnTick 里第一件事是调 checkDailyProfit() 刷新面板数值,随后用 isTradeAllowed 开关拦掉不允许交易的时段;若开关没开直接 return,不取价也不开单。 取价用 NormalizeDouble 包 SymbolInfoDouble 拿 SYMBOL_ASK / SYMBOL_BID 并对齐 _Digits,避免浮点位数乱掉。新K线判定靠 iBars 计数:totalBars 等于当前 bars 就退出,不等才把 totalBars 更新并往下走——这套写法在 M1 上每次新柱最多触发一次逻辑,回测里能明显压低重复计算开销。 外汇与贵金属杠杆品种跳空频繁,5% 回撤线只是阈值参考,实际穿阈速度可能远快于面板刷新节奏,上 MT5 跑之前建议先把 5.00% 改成变量方便调参。
createText("class="num">18","DAILY DRAWDOWN ================",class="num">70,class="num">210,clrPeru,class="num">11); createText("class="num">19","Starting Balance: ",class="num">70,class="num">225,clrBlack,class="num">10); createText("class="num">20",DoubleToString(Acc_B(),class="num">2)+" "+Acc_S(),class="num">270,class="num">225,clrBlack,class="num">10); createText("class="num">21","DrawDown Maximum Threshold: ",class="num">70,class="num">240,clrBlack,class="num">10); createText("class="num">22","class="num">5.00 %",class="num">270,class="num">240,clrBlack,class="num">10); createText("class="num">23","DrawDown Maximum Amount: ",class="num">70,class="num">255,clrBlack,class="num">10); createText("class="num">24","-"+DoubleToString((Acc_B()*class="num">5/class="num">100),class="num">2)+" "+Acc_S(),class="num">270,class="num">255,clrBlue,class="num">10); createText("class="num">25","Current Closed Daily Profit: ",class="num">70,class="num">270,clrBlack,class="num">10); createText("class="num">26","class="num">0.00"+" "+Acc_S(),class="num">270,class="num">270,clrGray,class="num">10); createText("class="num">27","Current DrawDown Percent: ",class="num">70,class="num">285,clrBlack,class="num">10); createText("class="num">28","class="num">0.00 %",class="num">270,class="num">285,clrGray,class="num">10); createText("class="num">29",">>> Initializing The Program, Get Ready To Trade.",class="num">70,class="num">300,clrBlue,class="num">10); class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert deinitialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnDeinit(const class="type">int reason) { class=class="str">"cmt">// Deinitialization code here(if needed) } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { class=class="str">"cmt">// Check daily profit and drawdown checkDailyProfit(); class=class="str">"cmt">// If trading is not allowed, exit function if (!isTradeAllowed) class="kw">return; class=class="str">"cmt">// Get current ask and bid prices class="type">class="kw">double ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits); class="type">class="kw">double bid = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_BID),_Digits); class=class="str">"cmt">// Check for new bar class="type">int bars = iBars(_Symbol,_Period); if (totalBars == bars) class="kw">return; totalBars = bars;
用随机信号测每日盈亏与回撤边界
这段逻辑先卡死持仓数量:当账户里已有超过 1 个持仓时直接 return,保证同一时刻最多只跑一笔单。随后用 MathRand()%2 掷出 0 或 1,等概率随机做多或做空,挂单止损止盈都放在离市价 70 个 _Point 的位置——这本质是用纯随机入场来压力测试风控模块,而不是真拿来交易。 checkDailyProfit() 负责按自然日切割战绩。它取 TimeCurrent() 转成日期字符串,再拼出当天 0 点至次日 0 点的区间,用 HistorySelect 拉出区间内所有平仓记录,把每笔的 DEAL_PROFIT、DEAL_COMMISSION、DEAL_SWAP 加总得到 total_day_Profit。 日收益率的计算口径是:(当日总盈亏 / 期初余额) * 100,其中期初余额 = 当前余额 - 当日总盈亏,结果用 NormalizeDouble 保留两位小数。外汇与贵金属杠杆高,这类随机单若实盘跑,爆仓概率极大,仅适合在策略测试器里验证逻辑。 面板文字用 createText 刷新:标签 "4" 写起始时间、"6" 写结束时间,坐标分别落在 (120,80) 与 (120,95),字号 10、蓝色。你打开 MT5 把这段塞进 EA,改掉 70*_Point 这个间距,就能直观看到不同止损宽度下当日权益曲线的抖动。
class=class="str">"cmt">// If more than one position, exit function if (PositionsTotal() > class="num">1) class="kw">return; class=class="str">"cmt">// Random trade decision class="type">int number = MathRand()%class="num">2; Print(number); if (number == class="num">0){ obj_Trade.Buy(class="num">1,_Symbol,ask,ask-class="num">70*_Point,ask+class="num">70*_Point); } else if (number == class="num">1){ obj_Trade.Sell(class="num">1,_Symbol,bid,bid+class="num">70*_Point,bid-class="num">70*_Point); } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Check daily profit and drawdown | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void checkDailyProfit() { class="type">class="kw">double total_day_Profit = class="num">0.0; class="type">class="kw">datetime end = TimeCurrent(); class="type">class="kw">string sdate = TimeToString(TimeCurrent(), TIME_DATE); class="type">class="kw">datetime start = StringToTime(sdate); class="type">class="kw">datetime to = start + (class="num">1*class="num">24*class="num">60*class="num">60); class=class="str">"cmt">// Reset daily balance and time at start of new day if (dayTime < to){ dayTime = to; dayBalance = Acc_B(); } class=class="str">"cmt">// Calculate total daily profit HistorySelect(start,end); class="type">int TotalDeals = HistoryDealsTotal(); for(class="type">int i = class="num">0; i < TotalDeals; i++){ class="type">class="kw">ulong Ticket = HistoryDealGetTicket(i); if(HistoryDealGetInteger(Ticket,DEAL_ENTRY) == DEAL_ENTRY_OUT){ class="type">class="kw">double Latest_Day_Profit = (HistoryDealGetDouble(Ticket,DEAL_PROFIT) + HistoryDealGetDouble(Ticket,DEAL_COMMISSION) + HistoryDealGetDouble(Ticket,DEAL_SWAP)); total_day_Profit += Latest_Day_Profit; } } class="type">class="kw">double startingBalance = class="num">0.0; startingBalance = AccountInfoDouble(ACCOUNT_BALANCE) - total_day_Profit; class="type">class="kw">string day_profit_in_TextFormat = ""; class="type">class="kw">double daily_Profit_or_Drawdown = NormalizeDouble(((total_day_Profit) * class="num">100/startingBalance),class="num">2); day_profit_in_TextFormat = DoubleToString(daily_Profit_or_Drawdown,class="num">2) + " %"; class=class="str">"cmt">// Update dashboard texts with new data createText("class="num">4",TimeToString(start),class="num">120,class="num">80,clrBlue,class="num">10); createText("class="num">6",TimeToString(to),class="num">120,class="num">95,clrBlue,class="num">10);
「在图表上画出账户与日内盈亏面板」
这段逻辑负责把账户状态直接渲染到 MT5 主图,省去切终端窗口看净值的麻烦。它分别处理了总权益相对初始资金的盈亏着色,以及当日余额与当日保本线(dayBalance)的比较,用颜色区分风险状态。 初始余额显示在坐标 (250,145),字号 10;若当前权益 Acc_E() 高于 initialBalance,权益值与百分比用 clrMediumBlue,低于则用 clrRed,等于用 clrBlack。注意百分比算法是 (Acc_E()-initialBalance)/initialBalance*100,保留两位小数。 当日面板另算一条 5% 回撤警戒线:代码里用 dayBalance*5/100 作为「-X.XX」文本画在 (270,255)。当 Acc_B() 大于 dayBalance 时,当日利润 total_day_Profit 与格式化文本 day_profit_in_TextFormat 着蓝色,否则红色或黑色。外汇与贵金属杠杆高,这类浮亏着色只帮你快速识别状态,不预示后续走势。 直接把下面代码贴进你的 EA 的绘图函数里,改一下坐标就能适配自己的 HUD 布局;开 MT5 用策略测试器跑一遍,看颜色切换是否符合你的风险阈值。
createText("class="num">8",TimeToString(end),class="num">120,class="num">110,clrBlack,class="num">10); createText("class="num">11",DoubleToString(initialBalance,class="num">2)+" "+Acc_S(),class="num">250,class="num">145,clrBlack,class="num">10); if (Acc_E() > initialBalance){ createText("class="num">15",DoubleToString(Acc_E(),class="num">2)+" "+Acc_S(),class="num">250,class="num">175,clrMediumBlue,class="num">10); createText("class="num">17",DoubleToString(((Acc_E()-initialBalance)/initialBalance)*class="num">100,class="num">2)+" %",class="num">250,class="num">190,clrMediumBlue,class="num">10); } else if (Acc_E() < initialBalance){ createText("class="num">15",DoubleToString(Acc_E(),class="num">2)+" "+Acc_S(),class="num">250,class="num">175,clrRed,class="num">10); createText("class="num">17",DoubleToString(((Acc_E()-initialBalance)/initialBalance)*class="num">100,class="num">2)+" %",class="num">250,class="num">190,clrRed,class="num">10); } else if (Acc_E() == initialBalance){ createText("class="num">15",DoubleToString(Acc_E(),class="num">2)+" "+Acc_S(),class="num">250,class="num">175,clrBlack,class="num">10); createText("class="num">17",DoubleToString(((Acc_E()-initialBalance)/initialBalance)*class="num">100,class="num">2)+" %",class="num">250,class="num">190,clrBlack,class="num">10); } createText("class="num">20",DoubleToString(dayBalance,class="num">2)+" "+Acc_S(),class="num">270,class="num">225,clrBlack,class="num">10); createText("class="num">24","-"+DoubleToString((dayBalance*class="num">5/class="num">100),class="num">2)+" "+Acc_S(),class="num">270,class="num">255,clrBlue,class="num">10); if (Acc_B() > dayBalance){ createText("class="num">26",DoubleToString(total_day_Profit,class="num">2)+" "+Acc_S(),class="num">270,class="num">270,clrMediumBlue,class="num">10); createText("class="num">28",day_profit_in_TextFormat,class="num">270,class="num">285,clrMediumBlue,class="num">10); } else if (Acc_B() < dayBalance){ createText("class="num">26",DoubleToString(total_day_Profit,class="num">2)+" "+Acc_S(),class="num">270,class="num">270,clrRed,class="num">10); createText("class="num">28",day_profit_in_TextFormat,class="num">270,class="num">285,clrRed,class="num">10); } else if (Acc_B() == dayBalance){ createText("class="num">26",DoubleToString(total_day_Profit,class="num">2)+" "+Acc_S(),class="num">270,class="num">270,clrBlack,class="num">10); createText("class="num">28",day_profit_in_TextFormat,class="num">270,class="num">285,clrBlack,class="num">10); }
◍ 用浮亏阈值卡死当日交易开关
这段逻辑把「当日回撤」和「账户总回撤」两条红线直接焊死在 EA 里:只要单日浮亏达到 -5.00%,或者账户权益相对初始余额跌穿 -12.00%,图表左上角就会弹出红色字样「Max ThreshHold Hit, Can't Trade.」,同时 isTradeAllowed 被置为 false,后续下单分支全部失效。 反过来若两条线都没碰,标签变蓝字提示可交易,开关重新打开。外汇与贵金属杠杆品种波动剧烈,这类硬截断能避免情绪加仓把账户拖进不可恢复的深坑,但是否触发仍取决于行情概率,不保证避损。 下面这个 createText 是配套的图表标签函数,用 OBJ_LABEL 在左上角画字。逐行看:ObjectCreate 建标签失败就 Print 错误码并返回 false;随后用 ObjectSetInteger 设 X/Y 像素偏移、锚定 CORNER_LEFT_UPPER、字号;ObjectSetString 写文本与颜色;最后 ChartRedraw 刷新。复制进 MT5 把 x=70、y=300 改成你习惯的坐标就能直接用。
class=class="str">"cmt">// Check if drawdown limits are hit and update trading permission if (daily_Profit_or_Drawdown <= -class="num">5.00 ||((Acc_E()-initialBalance)/initialBalance)*class="num">100 < -class="num">12.00){ createText("class="num">29",">>> Max ThreshHold Hit, Can&class="macro">#x27;t Trade.",class="num">70,class="num">300,clrRed,class="num">10); isTradeAllowed = false; } else { createText("class="num">29",">>> Max ThresHold Not Hit, Can Trade.",class="num">70,class="num">300,clrMediumBlue,class="num">10); isTradeAllowed = true; } } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Create text label on the chart | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool createText(class="type">class="kw">string objName, class="type">class="kw">string text, class="type">int x, class="type">int y, class="type">class="kw">color clrTxt,class="type">int fontSize) { ResetLastError(); if (!ObjectCreate(class="num">0,objName,OBJ_LABEL,class="num">0,class="num">0,class="num">0)){ Print(__FUNCTION__,": failed to create the Label! Error code = ", GetLastError()); class="kw">return(false); } ObjectSetInteger(class="num">0,objName,OBJPROP_XDISTANCE, x); ObjectSetInteger(class="num">0,objName,OBJPROP_YDISTANCE, y); ObjectSetInteger(class="num">0,objName,OBJPROP_CORNER, CORNER_LEFT_UPPER); ObjectSetString(class="num">0,objName,OBJPROP_TEXT, text); ObjectSetInteger(class="num">0,objName,OBJPROP_FONTSIZE, fontSize); class=class="str">"cmt">//ObjectSetString(class="num">0,objName,OBJPROP_FONT, "Calibri"); ObjectSetInteger(class="num">0,objName,OBJPROP_COLOR, clrTxt); ChartRedraw(class="num">0); class="kw">return(true); }
一点提醒
把每日回撤上限做成图表文本标签,本质是用 ObjectSetString 把状态、利润和交易权限阈值直接画在窗口上,维护起来比埋在日志里省事。给出的 Daily_Drawdown_Limiter.mq5 示例(9.31 KB)已经把基础框架搭好,读者在 MT5 里加载后改一个阈值参数就能跑。 外汇和贵金属杠杆高、跳空频繁,这类限额策略只能压住单日亏损扩散的概率,拦不住极端行情下的滑点穿透。拿去扩成多品种级联监控前,建议先用历史数据回测不同阈值下的触发频率。 代码是开源底子,真要上实盘先把作者归属和重算逻辑核对清楚,别直接当原创交差。