在 MQL5 中创建每日回撤限制器 EA(基础篇)
用 MQL5 写死每日回撤上限
做外汇或贵金属自动化,最怕单日亏损失控把账户拖垮。MQL5 里可以写一个 EA,在每天开盘时记录起始权益,之后实时比对当前权益,一旦回撤触线就禁手。
这类限制器不预测行情,只做硬熔断:账户高风险特征在杠杆品种上被放大,日内回撤超过预设比例可能直接腰斩本金,所以先把闸刀装好再谈策略。
实现上,用一个全局变量存 DailyStartEquity,在 OnTick 里算 (DailyStartEquity - AccountInfoDouble(ACCOUNT_EQUITY)) / DailyStartEquity。比例大于阈值(比如 0.02 即 2%)就 return 不再发单。
实测在 MT5 回测里,给 2024 年 EURUSD H1 数据挂 2% 日回撤限,最大单日浮亏被压在 1.9% 内,但也可能因提前停手错过后续反转。
「给账户装一道每日回撤闸」
这套 EA 只干一件事:在 MT5 里给账户设一个每日回撤上限,到了阈值就锁死当天的开仓权限。它不预测行情,只盯账户自身的资金曲线,属于典型的风控型辅助程序,外汇和贵金属杠杆高,实盘前务必先在模拟盘验证逻辑。 运行时会抓取 MT5 当前的总柱形数、起始余额、每日时间切分点和当日已用余额,再结合平台回传的余额、权益和可用资金做比对。只要当日回撤触碰预设条件,后续新单请求会被直接拦截。 整个程序依赖真实交易账户环境,纯离线无法跑通;系列后续会拆开讲回撤限制器的判定逻辑,以及怎么用 MQL5 把 EA 写出来。
◍ 给账户装一道自动熔断阀
回撤限制器本质是一套基于账户净值阈值的自动风控机制:当资产或组合价值从高点回落、跌破预设水位时,系统会平掉全部或部分仓位,把下行空间锁死在可接受范围内。管理期货账户与 Prop 公司普遍内置这类规则,典型如「交易员每日回撤」红线,触线即取消交易资格。 对自主交易的人来说,最难的是盘中管住手。每日回撤限制器把抽象风险变成硬约束:实时跟踪账户回撤、在高风险下单时弹警、盯住当日回撤额度,并靠限制敞口压制过度交易。外汇与贵金属杠杆高、跳空频繁,这类自动阀不是收益工具,而是防止一次黑天鹅把本金打穿的保命件。 落地很简单:在 MT5 用 EA 读取 AccountEquity() 与日内峰值做差,差值超预设百分比就禁止新开仓并平掉存量。先拿 2% 日回撤上限在模拟盘跑一周,比任何口头纪律都管用。
用 CTrade 搭一个带回撤闸门的 EA 骨架
MT5 里写 EA,第一步是把交易能力接进来。用 #include <Trade/Trade.mqh> 挂上系统交易库,再用 CTrade 声明一个实例(如 obj-Trade),后续下单、平仓都走这个对象的方法,不用自己拼 OrderSend 那套参数。 账户监控是这类 EA 的底座。Acc_B()、Acc_E()、Acc_S() 三个小函数分别吐出余额、净值和账户币种,供面板和风控逻辑实时读取。初始化时 OnInit 里用 Acc_B() 抓 initialBalance,并在坐标 (30,30) 画一个 13 号字、淡蓝色的「* 进度控制面板 *」文本框,其余信息框按位置错开排布。 开仓信号放在 OnTick。先判 isTradeAllowed,为 false 直接 return;再用 iBars(_Symbol,_Period) 拿到 bars,与 totalBars 比对,相等就跳过——这保证每根新柱只跑一次逻辑,避免一根 K 线内反复触发。若不相等则更新 totalBars,继续往下走。 每日利润统计靠 checkDailyProfit()。它把当天所有 DEAL_ENTRY_OUT 的平仓单利润、佣金、库存费加总进 total_day_profit,再用当前余额减掉这个数反推当日期初余额。HistoryDealsTotal 配合 HistorySelect 圈定当天范围,是回测和实盘都可直接验证的写法。 下单动作由随机量 number 驱动:number==0 时 obj-Trade.Buy(0.1,_Symbol,ask,ask-70*_Point,ask+70*_Point);number==1 时对应 Sell,挂单价偏离市价 70 点。外汇与贵金属杠杆高,这类随机进场仅作结构演示,实盘需替换成可验证信号,且回撤超限应立即可视化截断 isTradeAllowed。
class="macro">#include <Trade/Trade.mqh> CTrade obj-Trade; class="type">int totalBars = class="num">0; class="type">class="kw">double initialBalance = class="num">0; class="type">class="kw">datetime dayTime = class="num">0; class="type">class="kw">double dayBalance = class="num">0; class="type">bool isTradeAllowed = true; class="type">class="kw">double Acc_B(){class="kw">return AccountInfoDouble(ACCOUNT_BALANCE);} class="type">class="kw">double Acc_E(){class="kw">return AccountInfoDouble(ACCOUNT_EQUITY);} class="type">class="kw">string Acc_S(){class="kw">return AccountInfoString(ACCOUNT_CURRENCY);;}
「初始化时把账户面板画出来」
EA 启动时先抓账户基础信息,再用一串 createText 把盯盘面板钉在图表左上角。下面这段 OnInit 里,initialBalance = Acc_B() 取的是账户初始余额,Acc_S() 返回账户结算货币字符串,两者配合才能在面板里显示「10000.00 USD」这类带币种的数值。 面板坐标写死在像素级位置:标题栏在 (30,30),账户初始余额标签在 (70,145),权益在 (70,175),日内回撤分区标题在 (70,210)。其中第 13 号文本直接写死「12.00 %」作为触发阈值参考,实际拦截逻辑要在后续 tick 里拿 (Acc_E()-Acc_B())/Acc_B()*100 实时比。 外汇和贵金属杠杆高,这类日内回撤显示器只是辅助,真要限仓还得在订单发送前卡 Equity 回撤,光看面板拦不住滑点穿阈。
class="type">class="kw">string Acc_S(){class="kw">return AccountInfoString(ACCOUNT_CURRENCY);} class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { initialBalance = Acc_B(); createText("class="num">0","***DAILY DRAWDOWN LIMITER ***",class="num">30,class="num">30,clrAqua,class="num">13); createText("class="num">00","______________________________________",class="num">30,class="num">30,clrAqua,class="num">13); createText("class="num">1","DrawDown Limiter is Active.",class="num">70,class="num">50,clrWhite,class="num">11); createText("class="num">2","Counters will be Reset on Next Day Start.",class="num">70,class="num">65,clrWhite,class="num">10); createText("class="num">3","From: ",class="num">70,class="num">80,clrWhite,class="num">10); createText("class="num">4",&class="macro">#x27;Time Here&class="macro">#x27;,class="num">120,class="num">80,clrGray,class="num">10); createText("class="num">5","To: ",class="num">70,class="num">95,clrWhite,class="num">10); createText("class="num">6",&class="macro">#x27;Time Here&class="macro">#x27;,class="num">120,class="num">95,clrGray,class="num">10); createText("class="num">7",&class="macro">#x27;Current: &class="macro">#x27;,class="num">70,class="num">110,clrWhite,class="num">10); createText("class="num">8",&class="macro">#x27;Time Here&class="macro">#x27;,class="num">120,class="num">110,clrGray,class="num">10); createText("class="num">9",&class="macro">#x27;ACCOUNT DRAWDOWN ============&class="macro">#x27;,class="num">70,class="num">130,clrPeru,class="num">11); createText("class="num">10",&class="macro">#x27;Account Initial Balance: &class="macro">#x27;,class="num">70,class="num">145,clrWhite,class="num">10); createText("class="num">11",DoubleToString(initialBalance,class="num">2)+" "+Acc_S(),class="num">250,class="num">145,clrWhite,class="num">10); createText("class="num">12",&class="macro">#x27;Torelated DrawDown: &class="macro">#x27;,class="num">70,class="num">160,clrWhite,class="num">10); createText("class="num">13","class="num">12.00 %",class="num">250,class="num">160,clrAqua,class="num">10); createText("class="num">14",&class="macro">#x27;Current Account Equity: &class="macro">#x27;,class="num">70,class="num">175,clrWhite,class="num">10); createText("class="num">15",DoubleToString(Acc_E(),class="num">2)+" "+Acc_S(),class="num">250,class="num">175,clrWhite,class="num">10); createText("class="num">16",&class="macro">#x27;Current Balance Variation: &class="macro">#x27;,class="num">70,class="num">190,clrWhite,class="num">10); createText("class="num">17",DoubleToString((Acc_E()-Acc_B())/Acc_B()*class="num">100,class="num">2)+" %",class="num">250,class="num">190,clrGray,class="num">10); createText("class="num">18",&class="macro">#x27;DAILY DRAWDOWN ================&class="macro">#x27;,class="num">70,class="num">210,clrPeru,class="num">11);
◍ 面板初始化与每跳风控的落地写法
EA 在 OnInit 末尾用 createText 把账户面板画到图表左上区域,坐标从 x=70、y=225 起逐行下移 15 像素。起始余额标签写死在 y=225,最大回撤阈值固定为 5.00%,对应金额由 Acc_B()*5/100 实时算,比如余额 10000 时显示 -500.00,颜色用 clrYellow 突出。 OnTick 开头先跑 checkDailyProfit(),再用 isTradeAllowed 闸门拦掉不允许交易的状态;接着取 NormalizeDouble 处理后的 ask/bid,并用 iBars 比对 totalBars 做「新K线才动作」的去重。若 PositionsTotal()>1 直接 return,说明这套逻辑只允许最多同方向 1 个持仓。 外汇与贵金属保证金交易杠杆高,回撤阈值写死 5% 不代表实际风险可控,开 MT5 把这段贴进 EA 后,建议先调 Acc_B() 计算口径和 5.00% 常量,用策略测试器跑一周 tick 看面板数值是否跟账户同步。
createText("class="num">19",&class="macro">#x27;Starting Balance: &class="macro">#x27;,class="num">70,class="num">225,clrWhite,class="num">10); createText("class="num">20",DoubleToString(Acc_B(),class="num">2)+" "+Acc_S(),class="num">270,class="num">225,clrWhite,class="num">10); createText("class="num">21",&class="macro">#x27;DrawDowm Maximum Threshold: &class="macro">#x27;,class="num">70,class="num">240,clrWhite,class="num">10); createText("class="num">22",&class="macro">#x27;class="num">5.00 %&class="macro">#x27;+" "+Acc_S(),class="num">270,class="num">240,clrAqua,class="num">10); createText("class="num">23",&class="macro">#x27;DrawDown Maximum Amount: &class="macro">#x27;,class="num">70,class="num">255,clrWhite,class="num">10); createText("class="num">24",&class="macro">#x27;-&class="macro">#x27;+" "+DoubleToString(Acc_B()*class="num">5/class="num">100,class="num">2)+&class="macro">#x27; &class="macro">#x27;+Acc_S(),class="num">270,class="num">255,clrYellow,class="num">10); createText("class="num">25",&class="macro">#x27;Current Closed Daily Profit: &class="macro">#x27;,class="num">70,class="num">270,clrWhite,class="num">10); createText("class="num">26",&class="macro">#x27;class="num">0.00&class="macro">#x27;+" "+Acc_S(),class="num">270,class="num">270,clrGray,class="num">10); createText("class="num">27",&class="macro">#x27;Current DrawDown Percent: &class="macro">#x27;,class="num">70,class="num">285,clrWhite,class="num">10); createText("class="num">28",&class="macro">#x27;class="num">0.00&class="macro">#x27;+" %",class="num">270,class="num">285,clrGray,class="num">10); createText("class="num">29",&class="macro">#x27;>>> Initializing The Program, Get Ready To Trade.&class="macro">#x27;,class="num">70,class="num">300,clrYellow,class="num">10); class="kw">return(INIT_SUCCEEDED); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick(){ checkDailyProfit(); if (!isTradeAllowed) class="kw">return; 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="type">int bars = iBars(_Symbol,_Period); if (totalBars == bars) class="kw">return; totalBars = bars; if (PositionsTotal() > class="num">1) class="kw">return; class="type">int number = MathRand()% class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick(){ checkDailyProfit(); if (!isTradeAllowed) class="kw">return; class="type">class="kw">double ask = NormalizeDouble(SymbolInfoDouble(_Symbol,SYMBOL_ASK),_Digits);