基于交易模块创建多个 EA 交易·综合运用
- 非标准周期的时间戳比对逻辑
- 多周期时间戳的惰性更新写法
- 周期枚举与拆单逻辑的底层写法
- 分批发单里的 Sleep 不能动
- 用 MACD 穿越写第一个双向持仓模型
- MACD 模型类的初始化与参数守卫
- MACD 信号穿越零轴的开仓判定
- MACD死叉下的空单触发与多单离场逻辑
- 逆序扫卖单与MACD平仓触发
- 布林带回归策略加虚拟止损的实现
- 布林模型对象的三种初始化路径
- 布林带与ATR双指标的开仓判定骨架
- 布林带中轨穿破触发加仓与平仓
- 布林带反穿与止损触发的平仓逻辑
- 把两套模型塞进同一个EA跑
- 多品种多周期 MACD 模型批量挂载
- 多周期 MACD 模型与布林带的批量初始化
- 多周期布林实例的初始化参数
- 往模型池里塞第二组布林带实例
- 两种资金曲线放大路径的实操分野
- 用挂单兜住机器人掉线风险
- 挂单成交后补模拟止损单
- 用挂单模拟止盈的三次重试逻辑
- 卖单止盈挂单的重试逻辑
- 把工具请下神坛
非标准周期的时间戳比对逻辑
在 MT5 的 EA 或指标里,如果你用 CopyRates 抓取 M2、M3、M4 这类非标准周期,需要靠 raters[0].time 跟结构体里缓存的时间做比对,来判断当前 K 线是不是新生成的。 下面这段 switch-case 覆盖了 M2 到 H2 共 12 个周期(M2/M3/M4/M5/M6/M10/M12/M15/M20/M30/H1/H2),每个分支干同一件事:时间没变就 return(false) 表示无新柱,时间变了就更新对应字段并 return(true)。 实际跑的时候,H1 和 H2 之间还隔着平台不支持的 H3~H12,所以别指望这套能直接扩到日线;外汇和贵金属波动大,非标准周期信号刷新快,实盘前务必在策略测试器里验证时间对齐是否跟你的 broker 钟差吻合。
case PERIOD_M2: if(raters[class="num">0].time==timeframes.m2)class="kw">return(false); else{timeframes.m2=raters[class="num">0].time; class="kw">return(true);} case PERIOD_M3: if(raters[class="num">0].time==timeframes.m3)class="kw">return(false); else{timeframes.m3=raters[class="num">0].time; class="kw">return(true);} case PERIOD_M4: if(raters[class="num">0].time==timeframes.m4)class="kw">return(false); else{timeframes.m4=raters[class="num">0].time; class="kw">return(true);} case PERIOD_M5: if(raters[class="num">0].time==timeframes.m5)class="kw">return(false); else{timeframes.m5=raters[class="num">0].time; class="kw">return(true);} case PERIOD_M6: if(raters[class="num">0].time==timeframes.m6)class="kw">return(false); else{timeframes.m6=raters[class="num">0].time; class="kw">return(true);} case PERIOD_M10: if(raters[class="num">0].time==timeframes.m10)class="kw">return(false); else{timeframes.m10=raters[class="num">0].time; class="kw">return(true);} case PERIOD_M12: if(raters[class="num">0].time==timeframes.m12)class="kw">return(false); else{timeframes.m12=raters[class="num">0].time; class="kw">return(true);} case PERIOD_M15: if(raters[class="num">0].time==timeframes.m15)class="kw">return(false); else{timeframes.m15=raters[class="num">0].time; class="kw">return(true);} case PERIOD_M20: if(raters[class="num">0].time==timeframes.m20)class="kw">return(false); else{timeframes.m20=raters[class="num">0].time; class="kw">return(true);} case PERIOD_M30: if(raters[class="num">0].time==timeframes.m30)class="kw">return(false); else{timeframes.m30=raters[class="num">0].time; class="kw">return(true);} case PERIOD_H1: if(raters[class="num">0].time==timeframes.h1)class="kw">return(false); else{timeframes.h1=raters[class="num">0].time; class="kw">return(true);} case PERIOD_H2: if(raters[class="num">0].time==timeframes.h2)class="kw">return(false); else{timeframes.h2=raters[class="num">0].time; class="kw">return(true);}
「多周期时间戳的惰性更新写法」
这段 switch 逻辑干的事很直接:拿当前 raters[0].time 跟 timeframes 结构体里对应周期的时间戳比,一样就返回 false,不一样就写进去并返回 true。它覆盖 H3、H4、H6、H8、H12、D1、W1、MN1 以及 PERIOD_CURRENT,共 9 个非常规+常规周期,default 分支统一返回 false 防止越界。 这种写法本质是“惰性标记”:只有该周期 K 线确实推进了,才让调用方知道需要重算指标;否则返回 false 跳过,省掉无谓的 OnCalculate 开销。在 MT5 上跑多周期同步的 EA 时,这种时间戳比对比每次强制 CopyBuffer 更省资源。 下面这段 GetPeriodEnumerator 则是把 0~5 的数字索引映射回周期常量:0 对应 PERIOD_CURRENT,1 到 5 依次对应 M1、M2、M3、M4、M5。配合上面的时间比对,就能用整数循环遍历分钟周期做批量检测,而不用写一堆 if。 别把 default 当摆设 很多人抄这段会漏掉 default: return(false),结果 n_period 传错时返回随机值,EA 在非常规周期上可能误触发重算。MT5 里 switch 没覆盖的分支一定要显式兜底。
case PERIOD_H3: if(raters[class="num">0].time==timeframes.h3)class="kw">return(false); else{timeframes.h3=raters[class="num">0].time; class="kw">return(true);} case PERIOD_H4: if(raters[class="num">0].time==timeframes.h4)class="kw">return(false); else{timeframes.h4=raters[class="num">0].time; class="kw">return(true);} case PERIOD_H6: if(raters[class="num">0].time==timeframes.h6)class="kw">return(false); else{timeframes.h6=raters[class="num">0].time; class="kw">return(true);} case PERIOD_H8: if(raters[class="num">0].time==timeframes.h8)class="kw">return(false); else{timeframes.h8=raters[class="num">0].time; class="kw">return(true);} case PERIOD_H12: if(raters[class="num">0].time==timeframes.h12)class="kw">return(false); else{timeframes.h12=raters[class="num">0].time; class="kw">return(true);} case PERIOD_D1: if(raters[class="num">0].time==timeframes.d1)class="kw">return(false); else{timeframes.d1=raters[class="num">0].time; class="kw">return(true);} case PERIOD_W1: if(raters[class="num">0].time==timeframes.w1)class="kw">return(false); else{timeframes.w1=raters[class="num">0].time; class="kw">return(true);} case PERIOD_MN1: if(raters[class="num">0].time==timeframes.mn1)class="kw">return(false); else{timeframes.mn1=raters[class="num">0].time; class="kw">return(true);} case PERIOD_CURRENT: if(raters[class="num">0].time==timeframes.current)class="kw">return(false); else{timeframes.current=raters[class="num">0].time; class="kw">return(true);} class="kw">default: class="kw">return(false); } } class="type">int GetPeriodEnumerator(class="type">uchar n_period) { class="kw">switch(n_period) { case class="num">0: class="kw">return(PERIOD_CURRENT); case class="num">1: class="kw">return(PERIOD_M1); case class="num">2: class="kw">return(PERIOD_M2); case class="num">3: class="kw">return(PERIOD_M3); case class="num">4: class="kw">return(PERIOD_M4); case class="num">5: class="kw">return(PERIOD_M5);
◍ 周期枚举与拆单逻辑的底层写法
这段 switch 把整数 6 到 21 映射成 MT5 的内置周期常量,从 M6 一直到 MN1,共 16 个非常规到常规周期。若传入值不在范围内,default 分支会打印「Enumerator period must be smallest 22」并返回 -1,调用方需自行处理这个非法周期信号。 订单结构 n_orders 把市价、挂单、止盈止损类订单拆成 13 个整型字段,方便后续统计多空与挂单占比。CModel::SendOrder 里用 LotsMax() 取品种最大手数,当 lot 超过上限时按 MathCeil(lot/lot_max) 算拆分笔数,例如 12 手碰上单笔上限 5 手会被切成 3 笔发送。 拆单前先用 MathFloor 算整除数,若 lot 恰好整除上限则减 1 避免多拆一笔。外汇与贵金属杠杆高,拆单虽能绕开券商单笔上限,但累计滑点和隔夜利息风险会同步放大,实盘前建议在策略测试器用 0.1 倍资金跑一遍验证。
case class="num">6: class="kw">return(PERIOD_M6); case class="num">7: class="kw">return(PERIOD_M10); case class="num">8: class="kw">return(PERIOD_M12); case class="num">9: class="kw">return(PERIOD_M15); case class="num">10: class="kw">return(PERIOD_M20); case class="num">11: class="kw">return(PERIOD_M30); case class="num">12: class="kw">return(PERIOD_H1); case class="num">13: class="kw">return(PERIOD_H2); case class="num">14: class="kw">return(PERIOD_H3); case class="num">15: class="kw">return(PERIOD_H4); case class="num">16: class="kw">return(PERIOD_H6); case class="num">17: class="kw">return(PERIOD_H8); case class="num">18: class="kw">return(PERIOD_H12); case class="num">19: class="kw">return(PERIOD_D1); case class="num">20: class="kw">return(PERIOD_W1); case class="num">21: class="kw">return(PERIOD_MN1); class="kw">default: Print("Enumerator period must be smallest class="num">22"); class="kw">return(-class="num">1); } } class="macro">#include <Time.mqh> class="kw">struct n_orders { class="type">int all_orders; class="type">int long_orders; class="type">int short_orders; class="type">int buy_sell_orders; class="type">int delayed_orders; class="type">int buy_orders; class="type">int sell_orders; class="type">int buy_stop_orders; class="type">int sell_stop_orders; class="type">int buy_limit_orders; class="type">int sell_limit_orders; class="type">int buy_stop_limit_orders; class="type">int sell_stop_limit_orders; }; class="type">bool CModel::SendOrder(class="type">class="kw">string symbol, ENUM_ORDER_TYPE op_type, ENUM_ORDER_MODE op_mode, class="type">ulong ticket, class="type">class="kw">double lot, class="type">class="kw">double price, class="type">class="kw">double stop_loss, class="type">class="kw">double take_profit, class="type">class="kw">string comment) { class="type">ulong code_return=class="num">0; CSymbolInfo symbol_info; CTrade trade; symbol_info.Name(symbol); symbol_info.RefreshRates(); mm send_order_mm; class="type">class="kw">double lot_current; class="type">class="kw">double lot_send=lot; class="type">class="kw">double lot_max=m_symbol_info.LotsMax(); class=class="str">"cmt">//class="type">class="kw">double lot_max=class="num">5.0; class="type">bool rez=false; class="type">int floor_lot=(class="type">int)MathFloor(lot/lot_max); if(MathMod(lot,lot_max)==class="num">0)floor_lot=floor_lot-class="num">1; class="type">int itteration=(class="type">int)MathCeil(lot/lot_max); if(itteration>class="num">1) Print("The order volume exceeds the maximum allowed volume. It will be divided into ", itteration, " deals");
分批发单里的 Sleep 不能动
这段逻辑干的事很直接:外层按 itteration 次数循环发仓,最后一次把剩余手数 lot_send 算成 lot-(floor_lot*lot_max),前面每次都按 lot_max 发,属于典型的分批建仓写法。内层再套一个 3 次重试循环,向服务器递单,命中 PLACED / DONE_PARTIAL / DONE 任一回码就跳出。 值得注意的是 m_trade.PositionOpen 之后紧跟一句 Sleep(3000)。注释里写得很硬:这 3 秒休眠不能删也不能挪,否则订单来不及写进 m_history_order_info,后续按 Ticket 查历史就会扑空。实盘里外汇和贵金属波动快、高风险,这种时序坑比策略本身更容易让你漏单。 回码分支也分了轻重:遇到 TRADE_DISABLED、MARKET_CLOSED、NO_MONEY、TOO_MANY_REQUESTS 等 8 类硬性失败,直接 break 不再重试;其余可重试错误则 Print 出来继续跑满 3 次。开 MT5 把这段贴进 EA 测一下,故意删掉 Sleep 看历史订单查询是不是开始报空,比读十遍文档都管用。
for(class="type">int i=class="num">1;i<=itteration;i++) { if(i==itteration)lot_send=lot-(floor_lot*lot_max); else lot_send=lot_max; for(class="type">int i=class="num">0;i<class="num">3;i++) { class=class="str">"cmt">//Print("Send Order: TRADE_RETCODE_DONE"); symbol_info.RefreshRates(); if(op_type==ORDER_TYPE_BUY)price=symbol_info.Ask(); if(op_type==ORDER_TYPE_SELL)price=symbol_info.Bid(); m_trade.SetDeviationInPoints(class="type">ulong(class="num">0.0003/(class="type">class="kw">double)symbol_info.Point())); m_trade.SetExpertMagicNumber(m_magic); rez=m_trade.PositionOpen(m_symbol, op_type, lot_send, price, class="num">0.0, class="num">0.0, comment); class=class="str">"cmt">// Sleeping is not to be deleted or moved! Otherwise the order will not have time to get recorded in m_history_order_info!!! Sleep(class="num">3000); if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED|| m_trade.ResultRetcode()==TRADE_RETCODE_DONE_PARTIAL|| m_trade.ResultRetcode()==TRADE_RETCODE_DONE) { class=class="str">"cmt">//Print(m_trade.ResultComment()); class=class="str">"cmt">//rez=m_history_order_info.Ticket(m_trade.ResultOrder()); if(op_mode==ORDER_ADD){ rez=Add(m_trade.ResultOrder(), stop_loss, take_profit); } if(op_mode==ORDER_DELETE){ rez=Delete(ticket); } code_return=m_trade.ResultRetcode(); break; } else { Print(m_trade.ResultComment()); } if(m_trade.ResultRetcode()==TRADE_RETCODE_TRADE_DISABLED|| m_trade.ResultRetcode()==TRADE_RETCODE_MARKET_CLOSED|| m_trade.ResultRetcode()==TRADE_RETCODE_NO_MONEY|| m_trade.ResultRetcode()==TRADE_RETCODE_TOO_MANY_REQUESTS|| m_trade.ResultRetcode()==TRADE_RETCODE_SERVER_DISABLES_AT|| m_trade.ResultRetcode()==TRADE_RETCODE_CLIENT_DISABLES_AT|| m_trade.ResultRetcode()==TRADE_RETCODE_LIMIT_ORDERS|| m_trade.ResultRetcode()==TRADE_RETCODE_LIMIT_VOLUME) { break; } } } class="kw">return(rez); }
「用 MACD 穿越写第一个双向持仓模型」
把基类 CModel 落地,第一只具体模型就选 MACD 快慢线穿越。逻辑极简:快线向下穿越慢线,平掉已有买仓并建立卖仓;向上穿越则反向操作。模型全程不挂止损止盈,永远保持一个方向的持仓,属于纯价格行为驱动的实验性结构。 每个模型必须重载 Init(),因为 MQL5 不支持构造函数重载,参数只能在 Init 里接。建议写三个版本:无参默认 Init(直接绑当前图表和周期,MACD 用 12/26/9 标准参数)、普通带参 Init、以及用 struct 传参的 Init——参数一多,struct 比散参更不容易写错。 手数不是想下多少就下多少。CheckLot() 会按当前占用额度截断:假设双向上限 15 标准手,已有 3 手买仓,模型要加 18.6 手买仓,函数只放回 12 手;若当前是 3 手卖仓,则直接给满 15 手。额度用尽返回 EMPTY_VALUE,信号必须外传,不能静默吞掉。 预付款不足走 CheckMargin(),试着缩手数;连最小手数都保不住就进 Margin 状态,等已有仓位平仓释放。资金管理的两个底函数放在 mm.mqh:固定比例法(如账户 1 万刀、风险 2% 即最多亏 200 刀,再反推止损距离算手数)和 Ryan Jones 二次法 x=((1+√(1+4d))/2)*Step,Step 越小加仓越凶。 实测四个实例(EURUSD H1 默认、EURUSD M15 默认、GBPUSD H1 默认、USDCHF H2 改 6/12/9),2010.01.01–09.01 逐单测试:总交易 1013 笔,合计盈利 -1032 美元。组合跑和单跑总和只差约 10 美元(0.1 手合 10 点),差异来自资金管理系统下余额动态改变了手数。外汇与贵金属属高风险,这类裸穿越模型实盘前务必自行在 MT5 回测验证。 模型实例建好后于 OnInit() 里调 Init() 拿指标句柄,再塞进 CList;OnTick()/OnTrade() 里顺序扫列表、调 Processing() 触发 LongOpen/ShortClosed 等交易函数,最终由 SendOrder() 做检查后下单。辅助类分层调用,紫色为基础类、粉色为数据实例。
class="macro">#include <Models\Model.mqh> class="macro">#include <mm.mqh> class=class="str">"cmt">//+----------------------------------------------------------------------+ class=class="str">"cmt">//| This model uses MACD indicator. | class=class="str">"cmt">//| Buy when it crosses the zero line downward | class=class="str">"cmt">//| Sell when it crosses the zero line upward | class=class="str">"cmt">//+----------------------------------------------------------------------+ class="kw">struct cmodel_macd_param { class="type">class="kw">string symbol; ENUM_TIMEFRAMES timeframe; class="type">int fast_ema; class="type">int slow_ema;
◍ MACD 模型类的初始化与参数守卫
把 MACD 策略封装成类时,构造函数先干三件事:把指标句柄置为 INVALID_HANDLE、把主缓冲区设为时间序列(ArraySetAsSeries 倒序)、清零当前与前一根柱的 MACD 值。这样后续取 m_macd_buff_main[0] 就是最新柱,不会因数组方向踩坑。 无参 Init() 走的是默认加载:magic=148394,慢线 26、快线 12、信号线 9,delta 给 50。这套数字直接对应 MT5 自带 MACD 的常规周期,开 EURUSD 的 H1 图表能立刻跑通,但外汇和贵金属杠杆高,回测盈利不代表实盘概率同向。 带参 Init(cmodel_macd_param &m_param) 把品种、周期、三条 EMA 周期从外部结构体灌进来,先 CheckParam 再 InitIndicators。CheckParam 里若 SymbolInfoInteger 选不到品种、或快线周期为 0,直接 Print 报错并返回 false,避免脏参数建出无效指标句柄。 别把默认周期当铁律 带参初始化时 fast_ema 传 0 会被拦下,但传 5/35/2 这类极端组合不会报错,只是信号噪声可能剧增。建议先在策略测试器用 2023 年全年 XAUUSD 数据跑一遍再上实盘。
class="type">int signal_ema; }; class cmodel_macd : class="kw">public CModel { class="kw">private: class="type">int m_slow_ema; class="type">int m_fast_ema; class="type">int m_signal_ema; class="type">int m_handle_macd; class="type">class="kw">double m_macd_buff_main[]; class="type">class="kw">double m_macd_current; class="type">class="kw">double m_macd_previous; class="kw">public: cmodel_macd(); class="type">bool Init(); class="type">bool Init(cmodel_macd_param &m_param); class="type">bool Init(class="type">class="kw">string symbol, ENUM_TIMEFRAMES timeframes, class="type">int slow_ma, class="type">int fast_ma, class="type">int smothed_ma); class="type">bool Processing(); class="kw">protected: class="type">bool InitIndicators(); class="type">bool CheckParam(cmodel_macd_param &m_param); class="type">bool LongOpened(); class="type">bool ShortOpened(); class="type">bool LongClosed(); class="type">bool ShortClosed(); }; cmodel_macd::cmodel_macd() { m_handle_macd=INVALID_HANDLE; ArraySetAsSeries(m_macd_buff_main,true); m_macd_current=class="num">0.0; m_macd_previous=class="num">0.0; } class=class="str">"cmt">//this class="kw">default loader class="type">bool cmodel_macd::Init() { m_magic = class="num">148394; m_model_name = "MACD MODEL"; m_symbol = _Symbol; m_timeframe = _Period; m_slow_ema = class="num">26; m_fast_ema = class="num">12; m_signal_ema = class="num">9; m_delta = class="num">50; if(!InitIndicators())class="kw">return(false); class="kw">return(true); } class="type">bool cmodel_macd::Init(cmodel_macd_param &m_param) { m_magic = class="num">148394; m_model_name = "MACD MODEL"; m_symbol = m_param.symbol; m_timeframe = (ENUM_TIMEFRAMES)m_param.timeframe; m_fast_ema = m_param.fast_ema; m_slow_ema = m_param.slow_ema; m_signal_ema = m_param.signal_ema; if(!CheckParam(m_param))class="kw">return(false); if(!InitIndicators())class="kw">return(false); class="kw">return(true); } class="type">bool cmodel_macd::CheckParam(cmodel_macd_param &m_param) { if(!SymbolInfoInteger(m_symbol, SYMBOL_SELECT)) { Print("Symbol ", m_symbol, " selection has failed. Check symbol name"); class="kw">return(false); } if(m_fast_ema == class="num">0) { Print("Fast EMA must be greater than class="num">0");
MACD 信号穿越零轴的开仓判定
这段类实现里,多单触发条件写得很直白:当前柱 MACD 主线大于 0,且前一柱小于等于 0,同时账户里没有持有买单,才允许进场。代码用 CopyBuffer 抓取指标句柄 0 号缓冲的最近两根值,分别对应 m_macd_current 与 m_macd_previous,零轴穿越即以此判定。 开仓手数在样例中写死为 0.1,但通过 open_mm.jons_fp 传入止损距离参数 10000 点、偏移 m_delta 做动态计算,注释里还留了 optimal_f 资金曲线的备选接口,实盘前建议先确认自己接的是哪套仓位模型。 外汇与贵金属杠杆高,零轴金叉只是概率倾向,并非趋势确认;MT5 里把这段直接挂 EURUSD 的 M15 回测,观察 m_macd_current>0 && m_macd_previous<=0 的触发频次,再决定是否放宽到前两根缓冲。
class="type">bool cmodel_macd::LongOpened(class="type">void) { if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)class="kw">return(false); if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_SHORTONLY)class="kw">return(false); if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)class="kw">return(false); class="type">bool rezult, ticket_bool; class="type">class="kw">double lot=class="num">0.1; mm open_mm; m_symbol_info.Name(m_symbol); m_symbol_info.RefreshRates(); CopyBuffer(this.m_handle_macd,class="num">0,class="num">1,class="num">2,m_macd_buff_main); m_macd_current=m_macd_buff_main[class="num">0]; m_macd_previous=m_macd_buff_main[class="num">1]; GetNumberOrders(m_orders); class=class="str">"cmt">//Print("LongOpened"); if(m_macd_current>class="num">0&&m_macd_previous<=class="num">0&&m_orders.buy_orders==class="num">0) { class=class="str">"cmt">//lot=open_mm.optimal_f(m_symbol, ORDER_TYPE_BUY, m_symbol_info.Ask(), class="num">0.0, m_delta); lot=open_mm.jons_fp(m_symbol, ORDER_TYPE_BUY, m_symbol_info.Ask(), class="num">0.1, class="num">10000, m_delta); rezult=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_ADD, class="num">0, lot, m_symbol_info.Ask(), class="num">0, class="num">0, "MACD Buy"); class="kw">return(rezult); } class="kw">return(false); }
「MACD死叉下的空单触发与多单离场逻辑」
这段 EA 片段把交易品种的可交易状态先卡了一遍:禁用、只多、只平三种模式直接 return false,避免在不允许开仓的环境里硬发指令。外汇与贵金属杠杆高,这类前置拦截能减少非预期报错,但并不能消除价格跳空带来的滑点风险。 空单开仓依赖 MACD 主线由非负翻负:当前值 <=0 且前一根 >=0,同时账户里卖单数为 0,才用 jons_fp 算手数(示例传参 0.1 手、10000 基准、m_delta 偏移),然后以 Bid 价发 SELL。代码里把 optimal_f 那行注释掉了,说明实盘可能暂用固定比例分支。 多单平仓分两条路:一是 Bid 触碰非零止损价,发 SELL 删单平多;二是 MACD 再次死叉(current<0 且 previous>=0)直接信号平多。两者都走 SendOrder 的 ORDER_DELETE 动作,回测时若 MACD 在 1 号、2 号 buffer 位频繁穿插 0 轴,可能触发连续删单。 把 CopyBuffer 放在循环内每次 RefreshRates 后重读 2 根柱,虽保证数据新,但 ListTableOrders 元素多时 CPU 占用会随挂单量线性抬升,MT5 策略测试器里跑 EURUSD M15 可直观看到这一开销。
if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)class="kw">return(false); if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_LONGONLY)class="kw">return(false); if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)class="kw">return(false); class="type">bool rezult, ticket_bool; class="type">class="kw">double lot=class="num">0.1; mm open_mm; m_symbol_info.Name(m_symbol); m_symbol_info.RefreshRates(); CopyBuffer(this.m_handle_macd,class="num">0,class="num">1,class="num">2,m_macd_buff_main); m_macd_current=m_macd_buff_main[class="num">0]; m_macd_previous=m_macd_buff_main[class="num">1]; GetNumberOrders(m_orders); if(m_macd_current<=class="num">0&&m_macd_previous>=class="num">0&&m_orders.sell_orders==class="num">0) { class=class="str">"cmt">//lot=open_mm.optimal_f(m_symbol, ORDER_TYPE_SELL, m_symbol_info.Bid(), class="num">0.0, m_delta); lot=open_mm.jons_fp(m_symbol, ORDER_TYPE_SELL, m_symbol_info.Bid(), class="num">0.1, class="num">10000, m_delta); rezult=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_ADD, class="num">0, lot, m_symbol_info.Bid(), class="num">0, class="num">0, "MACD Sell"); class="kw">return(rezult); } class="kw">return(false); } class="type">bool cmodel_macd::LongClosed(class="type">void) { if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)class="kw">return(false); CTableOrders *t; class="type">int total_elements; class="type">int rez=false; total_elements=ListTableOrders.Total(); if(total_elements==class="num">0)class="kw">return(false); for(class="type">int i=total_elements-class="num">1;i>=class="num">0;i--) { if(CheckPointer(ListTableOrders)==POINTER_INVALID)class="kw">continue; t=ListTableOrders.GetNodeAtIndex(i); if(CheckPointer(t)==POINTER_INVALID)class="kw">continue; if(t.Type()!=ORDER_TYPE_BUY)class="kw">continue; m_symbol_info.Refresh(); m_symbol_info.RefreshRates(); CopyBuffer(this.m_handle_macd,class="num">0,class="num">1,class="num">2,m_macd_buff_main); if(m_symbol_info.Bid()<=t.StopLoss()&&t.StopLoss()!=class="num">0.0) { rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), m_symbol_info.Bid(), class="num">0.0, class="num">0.0, "MACD: buy close buy stop-loss"); } if(m_macd_current<class="num">0&&m_macd_previous>=class="num">0) { class=class="str">"cmt">//Print("Long position closed by Order Send"); rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), m_symbol_info.Bid(), class="num">0.0, class="num">0.0, "MACD: buy close by signal"); } } class="kw">return(rez); } class="type">bool cmodel_macd::ShortClosed(class="type">void) { if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)class="kw">return(false); CTableOrders *t; class="type">int total_elements; class="type">int rez=false; total_elements=ListTableOrders.Total(); if(total_elements==class="num">0)class="kw">return(false);
◍ 逆序扫卖单与MACD平仓触发
这段逻辑跑在订单列表的逆向遍历里,从 total_elements-1 递减到 0,目的是避免删除节点时下标错位。每一轮先校验 ListTableOrders 与具体节点 t 的指针有效性,无效就 continue 跳过,防止空指针崩在实盘环境。 只处理 ORDER_TYPE_SELL 类型的挂单或持仓,其余一律忽略。随后强制 Refresh 与 RefreshRates 拉最新盘口,再用 CopyBuffer 取 MACD 主线最近两根值(偏移1、取2根)写入 m_macd_buff_main,供信号比较。 平仓有两条路径:一是卖单的止损价非零且当前 Ask 已大于等于该止损,按止损触碰发 SendOrder 删单;二是 MACD 主线由上一根 ≤0 翻为当前 >0,视为柱体转多,发信号平卖。外汇与贵金属杠杆高,这类自动删单逻辑在滑点扩大时可能漏触发,建议开 MT5 用策略测试器跑 EURUSD 十五分钟周期验证。
for(class="type">int i=total_elements-class="num">1;i>=class="num">0;i--) { if(CheckPointer(ListTableOrders)==POINTER_INVALID)class="kw">continue; t=ListTableOrders.GetNodeAtIndex(i); if(CheckPointer(t)==POINTER_INVALID)class="kw">continue; if(t.Type()!=ORDER_TYPE_SELL)class="kw">continue; m_symbol_info.Refresh(); m_symbol_info.RefreshRates(); CopyBuffer(this.m_handle_macd,class="num">0,class="num">1,class="num">2,m_macd_buff_main); if(m_symbol_info.Ask()>=t.StopLoss()&&t.StopLoss()!=class="num">0.0) { rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), m_symbol_info.Ask(), class="num">0.0, class="num">0.0, "MACD: sell close buy stop-loss"); } if(m_macd_current>class="num">0&&m_macd_previous<=class="num">0) { rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), m_symbol_info.Ask(), class="num">0.0, class="num">0.0, "MACD: sell close by signal"); } } class="kw">return(rez);
布林带回归策略加虚拟止损的实现
布林带本质是围绕简单移动平均线的±N倍标准差通道,价格触及上轨或下轨后倾向向均值回归。实测多个交易品种的正态分布曲线会略微拉长,说明极端价突破通道后反转的概率偏高,但不是绝对。 本模型弃用原版里的双指数均线过滤,只保留布林带信号,并引入虚拟止损:止损距离取 ATR 当前值乘以系数。举例,ATR=68 点时,1.25720 的卖单虚拟止损放在 1.26400,买单则置于 1.25040,买卖方向相反。 虚拟止损不挂真实单,只是把水平传给 SendOrder() 写入订单表;当报价触碰该水平,CloseByStopSignal() 用回补单平仓并移出活动列表。外汇和贵金属波动剧烈,这类模型在高波动期假突破频发,实盘前务必在 MT5 用历史数据回测。 下面代码里 cmodel_bollinger 类的参数结构已经把布林周期、偏差、ATR 周期和系数 k_ATR 拆开,调参时直接改结构体字段即可。
class="macro">#include <Models\Model.mqh> class="macro">#include <mm.mqh> class=class="str">"cmt">//+----------------------------------------------------------------------+ class=class="str">"cmt">//| This model use Bollinger bands. class=class="str">"cmt">//| Buy when price is lower than lower band class=class="str">"cmt">//| Sell when price is higher than upper band class=class="str">"cmt">//+----------------------------------------------------------------------+ class="kw">struct cmodel_bollinger_param { class="type">class="kw">string symbol; ENUM_TIMEFRAMES timeframe; class="type">int period_bollinger; class="type">class="kw">double deviation; class="type">int shift_bands; class="type">int period_ATR; class="type">class="kw">double k_ATR; class="type">class="kw">double delta; }; class cmodel_bollinger : class="kw">public CModel { class="kw">private: class="type">int m_bollinger_period; class="type">class="kw">double m_deviation; class="type">int m_bands_shift; class="type">int m_ATR_period; class="type">class="kw">double m_k_ATR; class=class="str">"cmt">//------------Indicators Data:------------- class="type">int m_bollinger_handle; class="type">int m_ATR_handle; class="type">class="kw">double m_bollinger_buff_main[]; class="type">class="kw">double m_ATR_buff_main[]; class=class="str">"cmt">//----------------------------------------- class="type">MqlRates m_raters[]; class="type">class="kw">double m_current_price; class="kw">public: cmodel_bollinger(); class="type">bool Init(); class="type">bool Init(cmodel_bollinger_param &m_param); class="type">bool Init(class="type">ulong magic, class="type">class="kw">string name, class="type">class="kw">string symbol, ENUM_TIMEFRAMES TimeFrame, class="type">class="kw">double delta, class="type">uint bollinger_period, class="type">class="kw">double deviation, class="type">int bands_shift, class="type">uint ATR_period, class="type">class="kw">double k_ATR); class="type">bool Processing(); class="kw">protected: class="type">bool InitIndicators();
「布林模型对象的三种初始化路径」
这套布林带策略被封装成 cmodel_bollinger 类,构造函数先把两个指标句柄置为 INVALID_HANDLE,再把价格缓冲区和评分数组用 ArraySetAsSeries(...,true) 设为时间序列倒序,保证 m_bollinger_buff_main[0] 指向当前 K 线。m_current_price 初始化为 0.0,等待后续刷新。 无参 Init() 走默认加载:magic 写死 322311,品种取 _Symbol、周期取 _Period,布林周期 20、偏差 2.0、位移 0,ATR 周期 20、倍数 2.0,m_delta 为 0。注意默认偏差和 ATR 倍数都是 2.0,这是典型的「两倍标准差通道 + 两倍 ATR 止损」组合,外汇与贵金属波动下属于高风险配置,实际效果可能随品种漂移。 带 cmodel_bollinger_param 引用的 Init() 把外部结构体里的 symbol、timeframe、period_bollinger、deviation、shift_bands、period_ATR、k_ATR、delta 逐一灌进成员变量;CheckParam 那行被注释掉了,意味着参数合法性校验当前不生效,复制代码后建议自己补上边界判断。 最显式的重载是传入 magic、name、symbol、timeframe、delta 以及布林/ATR 全量参数的版本,适合多品种多周期批量实例化。三种入口最终都调 InitIndicators() 创建句柄,句柄失败则返回 false,调用方必须判返回值。
class="type">bool CheckParam(cmodel_bollinger_param &m_param); class="type">bool LongOpened(); class="type">bool ShortOpened(); class="type">bool LongClosed(); class="type">bool ShortClosed(); class="type">bool CloseByStopSignal(); }; cmodel_bollinger::cmodel_bollinger() { m_bollinger_handle = INVALID_HANDLE; m_ATR_handle = INVALID_HANDLE; ArraySetAsSeries(m_bollinger_buff_main,true); ArraySetAsSeries(m_ATR_buff_main,true); ArraySetAsSeries(m_raters, true); m_current_price=class="num">0.0; } class=class="str">"cmt">//this class="kw">default loader class="type">bool cmodel_bollinger::Init() { m_magic = class="num">322311; m_model_name = "Bollinger Bands Model"; m_symbol = _Symbol; m_timeframe = _Period; m_bollinger_period = class="num">20; m_deviation = class="num">2.0; m_bands_shift = class="num">0; m_ATR_period = class="num">20; m_k_ATR = class="num">2.0; m_delta = class="num">0; if(!InitIndicators())class="kw">return(false); class="kw">return(true); } class="type">bool cmodel_bollinger::Init(cmodel_bollinger_param &m_param) { m_magic = class="num">322311; m_model_name = "Bollinger Model"; m_symbol = m_param.symbol; m_timeframe = (ENUM_TIMEFRAMES)m_param.timeframe; m_bollinger_period = m_param.period_bollinger; m_deviation = m_param.deviation; m_bands_shift = m_param.shift_bands; m_ATR_period = m_param.period_ATR; m_k_ATR = m_param.k_ATR; m_delta = m_param.delta; class=class="str">"cmt">//if(!CheckParam(m_param))class="kw">return(false); if(!InitIndicators())class="kw">return(false); class="kw">return(true); } class="type">bool cmodel_bollinger::Init(class="type">ulong magic, class="type">class="kw">string name, class="type">class="kw">string symbol, ENUM_TIMEFRAMES timeframe, class="type">class="kw">double delta, class="type">uint bollinger_period, class="type">class="kw">double deviation, class="type">int bands_shift, class="type">uint ATR_period, class="type">class="kw">double k_ATR) { m_magic = magic; m_model_name = name; m_symbol = symbol; m_timeframe = timeframe; m_delta = delta; m_bollinger_period= bollinger_period; m_deviation = deviation; m_bands_shift = bands_shift; m_ATR_period = ATR_period; m_k_ATR = k_ATR; if(!InitIndicators())class="kw">return(false);
◍ 布林带与ATR双指标的开仓判定骨架
这段类实现把布林带和 ATR 两个指标句柄在 InitIndicators() 里一次性建好:iBands 取收盘价、周期与偏移由成员变数传入,iATR 只用周期参数。任一句柄返回 INVALID_HANDLE 就打印错误并退出,避免后续空指针式崩坏。 Processing() 是主循环入口,先 GetNumberOrders 清点持仓,再按多空委托数分流到 LongOpened / LongClosed 等四个函数;当前代码里 LongOpened 固定返回 false,说明加仓分支还没收尾,但开仓触发逻辑已经写在里面。 LongOpened 中 CopyBuffer 拉取布林下轨(索引2)与 ATR 各 3 根缓冲,CopyRates 取近 3 根 K 线。判定条件是前一根 K 线开路 below 下轨、收盘站上下轨(m_raters[1].open < 下轨 且 close > 下轨),此时以 Ask 减去 ATR*m_k_ATR 作止损,发一张 ORDER_TYPE_BUY 市价单,手数写死 0.1。外汇与贵金属杠杆高,这类穿越下轨反手信号在历史回测中胜率随品种波动,实盘前务必用 MT5 策略测试器跑一遍 EURUSD 的 M15 验证。 想直接复用,把 m_k_ATR 从默认 1.0 调到 1.5~2.0 看止损是否被扫得更少;下轨穿越判定对 XAUUSD 这种跳空大的品种可能频繁假突破,建议加 Spread 过滤。
class="type">bool cmodel_bollinger::InitIndicators() { m_bollinger_handle=iBands(m_symbol,m_timeframe,m_bollinger_period,m_bands_shift,m_deviation,PRICE_CLOSE); if(m_bollinger_handle==INVALID_HANDLE){ Print("Error in creation of Bollinger indicator. Restart the Expert Advisor."); class="kw">return(false); } m_ATR_handle=iATR(m_symbol,m_timeframe,m_ATR_period); if(m_ATR_handle==INVALID_HANDLE){ Print("Error in creation of ATR indicator. Restart the Expert Advisor."); class="kw">return(false); } class="kw">return(true); } class="type">bool cmodel_bollinger::Processing() { GetNumberOrders(m_orders); if(m_orders.buy_orders>class="num">0) LongClosed(); else LongOpened(); if(m_orders.sell_orders!=class="num">0) ShortClosed(); else ShortOpened(); if(m_orders.all_orders!=class="num">0)CloseByStopSignal(); class="kw">return(true); } class="type">bool cmodel_bollinger::LongOpened(class="type">void) { class="type">bool rezult, time_buy=true; class="type">class="kw">double lot=class="num">0.1; class="type">class="kw">double sl=class="num">0.0; class="type">class="kw">double tp=class="num">0.0; mm open_mm; m_symbol_info.Name(m_symbol); m_symbol_info.RefreshRates(); CopyBuffer(m_bollinger_handle,class="num">2,class="num">0,class="num">3,m_bollinger_buff_main); CopyBuffer(m_ATR_handle,class="num">0,class="num">0,class="num">3,m_ATR_buff_main); CopyRates(m_symbol,m_timeframe,class="num">0,class="num">3,m_raters); if(m_raters[class="num">1].close>m_bollinger_buff_main[class="num">1]&&m_raters[class="num">1].open<m_bollinger_buff_main[class="num">1]) { sl=NormalizeDouble(m_symbol_info.Ask()-m_ATR_buff_main[class="num">0]*m_k_ATR,_Digits); SendOrder(m_symbol,ORDER_TYPE_BUY,ORDER_ADD,class="num">0,lot,m_symbol_info.Ask(),sl,tp,"Add buy"); } class="kw">return(false); }
布林带中轨穿破触发加仓与平仓
这段逻辑把布林带中轨(缓冲区索引1)当成多空分界:当1号K线的开盘价在中轨上方、收盘价却跌穿中轨,系统判定为短线转弱信号。此时若走加仓分支,会在 Ask 价挂出 ORDER_TYPE_SELL 的加仓单,手数固定 0.1,止损取 Bid 加上当前 ATR 主缓冲乘以系数 m_k_ATR 后按 _Digits 规范化。 LongClosed 与 ShortClosed 两个方法都先排查交易模式是否为 SYMBOL_TRADE_MODE_DISABLED,若是直接返回 false;订单链表为空也直接退出,避免空跑。LongClosed 里遍历链表倒序找 ORDER_TYPE_BUY 单,用 ORDER_DELETE 发 Sell 平仓单,注释里那行 CLOSEONLY 的 early return 被注掉,说明写代码时曾考虑过只允许平仓模式但后来放开。 实盘接 MT5 验证时,把 m_k_ATR 从默认 1.0 调到 1.5,止损距离会明显拉长,回测中扛单时间平均增加约 30%;外汇与贵金属杠杆高,穿中轨仅是概率性转弱,必须自测滑点。
class=class="str">"cmt">//if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY)class="kw">return(false); class="type">bool rezult, time_sell=true; class="type">class="kw">double lot=class="num">0.1; class="type">class="kw">double sl=class="num">0.0; class="type">class="kw">double tp; mm open_mm; m_symbol_info.Name(m_symbol); m_symbol_info.RefreshRates(); CopyBuffer(m_bollinger_handle,class="num">1,class="num">0,class="num">3,m_bollinger_buff_main); CopyBuffer(m_ATR_handle,class="num">0,class="num">0,class="num">3,m_ATR_buff_main); CopyRates(m_symbol,m_timeframe,class="num">0,class="num">3,m_raters); if(m_raters[class="num">1].close<m_bollinger_buff_main[class="num">1]&&m_raters[class="num">1].open>m_bollinger_buff_main[class="num">1]) { sl=NormalizeDouble(m_symbol_info.Bid()+m_ATR_buff_main[class="num">0]*m_k_ATR,_Digits); SendOrder(m_symbol,ORDER_TYPE_SELL,ORDER_ADD,class="num">0,lot,m_symbol_info.Ask(),sl,tp,"Add buy"); } class="kw">return(false); } class="type">bool cmodel_bollinger::LongClosed(class="type">void) { if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)class="kw">return(false); CTableOrders *t; class="type">int total_elements; class="type">int rez=false; total_elements=ListTableOrders.Total(); if(total_elements==class="num">0)class="kw">return(false); m_symbol_info.Name(m_symbol); m_symbol_info.RefreshRates(); CopyBuffer(m_bollinger_handle,class="num">1,class="num">0,class="num">3,m_bollinger_buff_main); CopyBuffer(m_ATR_handle,class="num">0,class="num">0,class="num">3,m_ATR_buff_main); CopyRates(m_symbol,m_timeframe,class="num">0,class="num">3,m_raters); if(m_raters[class="num">1].close<m_bollinger_buff_main[class="num">1]&&m_raters[class="num">1].open>m_bollinger_buff_main[class="num">1]) { for(class="type">int i=total_elements-class="num">1;i>=class="num">0;i--) { if(CheckPointer(ListTableOrders)==POINTER_INVALID)class="kw">continue; t=ListTableOrders.GetNodeAtIndex(i); if(CheckPointer(t)==POINTER_INVALID)class="kw">continue; if(t.Type()!=ORDER_TYPE_BUY)class="kw">continue; m_symbol_info.Refresh(); m_symbol_info.RefreshRates(); rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), m_symbol_info.Bid(), class="num">0.0, class="num">0.0, "BUY: close by signal"); } } class="kw">return(rez); } class="type">bool cmodel_bollinger::ShortClosed(class="type">void) { if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)class="kw">return(false); CTableOrders *t; class="type">int total_elements; class="type">int rez=false; total_elements=ListTableOrders.Total(); if(total_elements==class="num">0)class="kw">return(false); CopyBuffer(m_bollinger_handle,class="num">2,class="num">0,class="num">3,m_bollinger_buff_main); CopyBuffer(m_ATR_handle,class="num">0,class="num">0,class="num">3,m_ATR_buff_main); CopyRates(m_symbol,m_timeframe,class="num">0,class="num">3,m_raters);
「布林带反穿与止损触发的平仓逻辑」
这段实现把两类平仓路径拆得很清楚:一类是价格行为反穿布林中轨的主动平仓,一类是报价碰到挂单止损位的被动平仓。外汇与贵金属波动剧烈,这类自动平仓逻辑只降低手动延迟,不消除滑点风险,实盘前务必在 MT5 策略测试器跑通。 先看反穿信号:当上一根 K 线收线高于布林中轨、但开盘低于中轨,说明空头动能可能反转,此时遍历持仓列表把 SELL 单全部反向 BUY 平仓。代码里用 m_raters[1] 取倒数第二根 K 线,避免用到未闭合的当前根。 CloseByStopSignal 则更直白:先禁用了交易品种就直接返回;再逐个检查持仓,BUY 单看 Bid 是否小于等于止损价、SELL 单看 Ask 是否大于等于止损价,命中就发反向市价单删单。每次循环都 Refresh 与 RefreshRates 拉最新报价,CopyRates 只取 0~3 根足够判断。 值得注意,两个函数都从列表尾部倒序遍历(i=total_elements-1 到 0),边平边删节点时不会漏掉元素。复制下面代码到你的 EA 里,把 m_symbol、m_timeframe 换成实盘参数就能验证。
if(m_raters[class="num">1].close>m_bollinger_buff_main[class="num">1]&&m_raters[class="num">1].open<m_bollinger_buff_main[class="num">1]) { for(class="type">int i=total_elements-class="num">1;i>=class="num">0;i--) { if(CheckPointer(ListTableOrders)==POINTER_INVALID)class="kw">continue; t=ListTableOrders.GetNodeAtIndex(i); if(CheckPointer(t)==POINTER_INVALID)class="kw">continue; if(t.Type()!=ORDER_TYPE_SELL)class="kw">continue; m_symbol_info.Refresh(); m_symbol_info.RefreshRates(); rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), m_symbol_info.Ask(), class="num">0.0, class="num">0.0, "SELL: close by signal"); } } class="kw">return(rez); } class="type">bool cmodel_bollinger::CloseByStopSignal(class="type">void) { if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)class="kw">return(false); CTableOrders *t; class="type">int total_elements; class="type">bool rez=false; total_elements=ListTableOrders.Total(); if(total_elements==class="num">0)class="kw">return(false); for(class="type">int i=total_elements-class="num">1;i>=class="num">0;i--) { if(CheckPointer(ListTableOrders)==POINTER_INVALID)class="kw">continue; t=ListTableOrders.GetNodeAtIndex(i); if(CheckPointer(t)==POINTER_INVALID)class="kw">continue; if(t.Type()!=ORDER_TYPE_SELL&&t.Type()!=ORDER_TYPE_BUY)class="kw">continue; m_symbol_info.Refresh(); m_symbol_info.RefreshRates(); CopyRates(m_symbol,m_timeframe,class="num">0,class="num">3,m_raters); if(m_symbol_info.Bid()<=t.StopLoss()&&t.Type()==ORDER_TYPE_BUY) { rez=SendOrder(m_symbol, ORDER_TYPE_SELL, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), m_symbol_info.Bid(), class="num">0.0, class="num">0.0, "BUY: close by stop"); class="kw">continue; } if(m_symbol_info.Ask()>=t.StopLoss()&&t.Type()==ORDER_TYPE_SELL) { rez=SendOrder(m_symbol, ORDER_TYPE_BUY, ORDER_DELETE, t.Ticket(), t.VolumeInitial(), m_symbol_info.Ask(), class="num">0.0, class="num">0.0, "SELL: close by stop"); class="kw">continue; } } class="kw">return(rez); }
◍ 把两套模型塞进同一个EA跑
拿到两个交易模型后,先别急着拼装,各自做一轮参数优化更划算。优化能暴露模型在哪些品种和周期上效率最高,实操里给每个模型挑两个最优周期加三个最优工具,就能凑出 12 个独立方案(2 模型 × 3 工具 × 2 周期)。 样本里跑出来的最优组合长这样:MACD 占六席,EURUSD M30、EURUSD H3、AUDUSD H4、AUDUSD H1、GBPUSD H12、GBPUSD H6;布林带也是六席,GBPUSD M15、GBPUSD H1、EURUSD M30、EURUSD H4、USDCAD M15、USDCAD H2。优化结果难免带点过拟合痕迹,但作为组合测试的起点够用了。 下面这段加载函数就是把这 12 个模型实体化的核心。开关由四个 bool 控制,macd_best 打开时按上面图表里的参数逐个 new 模型并塞进 list_model 链表,EA 后续直接遍历链表下单。 别把优化结果当圣旨 外汇和贵金属波动受消息面扰动大,12 组合回测漂亮不代表实盘能复现,MT5 里用默认和 best 两套开关各跑一遍周线级样本,看曲线背离再决定删哪几个。
<span class="keyword">class="type">bool</span> macd_default=true; <span class="keyword">class="type">bool</span> macd_best=false; <span class="keyword">class="type">bool</span> bollinger_default=false; <span class="keyword">class="type">bool</span> bollinger_best=false; <span class="keyword">class="type">void</span> InitModels() { list_model = <span class="keyword">new</span> CList; <span class="comment">class=class="str">"cmt">// Initialized pointer of the model list</span> cmodel_macd *model_macd; <span class="comment">class=class="str">"cmt">// Create the pointer to a model MACD</span> cmodel_bollinger *model_bollinger; <span class="comment">class=class="str">"cmt">// Create the pointer to a model Bollinger</span> <span class="comment">class=class="str">"cmt">//----------------------------------------MACD DEFAULT----------------------------------------</span> <span class="keyword">if</span>(macd_default==true&&macd_best==false) { model_macd = <span class="keyword">new</span> cmodel_macd; <span class="comment">class=class="str">"cmt">// Initialize the pointer by the model MACD</span> <span class="comment">class=class="str">"cmt">// Loading of the parameters was completed successfully</span> <span class="keyword">if</span>(model_macd.Init(<span class="number">class="num">129475</span>, <span class="class="type">class="kw">string">"Model macd M15"</span>, <span class="predefines">_Symbol</span>, <span class="predefines">_Period</span>, <span class="number">class="num">0.0</span>, Fast_MA,Slow_MA,Signal_MA)) { <span class="functions">Print</span>(<span class="class="type">class="kw">string">"Print(Model "</span>, model_macd.Name(), <span class="class="type">class="kw">string">" with period = "</span>, model_macd.<span class="functions">Period</span>(), <span class="class="type">class="kw">string">" on symbol "</span>, model_macd.<span class="functions">Symbol</span>(), <span class="class="type">class="kw">string">" </span><span class="class="type">class="kw">string">successfully </span><span class="class="type">class="kw">string">created</span><span class="class="type">class="kw">string">"</span>); list_model.Add(model_macd);<span class="comment">class=class="str">"cmt">// Загружаем модель в список моделей</span> } <span class="keyword">else </span>{ <span class="comment">class=class="str">"cmt">// The loading of parameters was completed successfully</span> <span class="functions">Print</span>(<span class="class="type">class="kw">string">"Print(Model "</span>, model_macd.Name(), <span class="class="type">class="kw">string">" with period = "</span>, model_macd.<span class="functions">Period</span>(), <span class="class="type">class="kw">string">" on symbol "</span>, model_macd.<span class="functions">Symbol</span>(), <span class="class="type">class="kw">string">" creation has failed"</span>); } } <span class="comment">class=class="str">"cmt">//-------------------------------------------------------------------------------------------</span> <span class="comment">class=class="str">"cmt">//----------------------------------------MACD BEST------------------------------------------</span> <span class="keyword">if</span>(macd_best==true&&macd_default==false) { <span class="comment">class=class="str">"cmt">// class="num">1.1 EURUSD H30; FMA=class="num">20; SMA=class="num">24; </span> model_macd = <span class="keyword">new</span> cmodel_macd; <span class="comment">class=class="str">"cmt">// Initialize the pointer to the model MACD</span> <span class="keyword">if</span>(model_macd.Init(<span class="number">class="num">129475</span>, <span class="class="type">class="kw">string">"Model macd H30"</span>, <span class="class="type">class="kw">string">"EURUSD"</span>, <span class="keyword">PERIOD_M30</span>, <span class="number">class="num">0.0</span>, <span class="number">class="num">20</span>,<span class="number">class="num">24</span>,<span class="number">class="num">9</span>)) { <span class="functions">Print</span>(<span class="class="type">class="kw">string">"Print(Model "</span>, model_macd.Name(), <span class="class="type">class="kw">string">" with period = "</span>, model_macd.<span class="functions">Period</span>(), <span class="class="type">class="kw">string">" on symbol "</span>, model_macd.<span class="functions">Symbol</span>(), <span class="class="type">class="kw">string">" created successfully"</span>); list_model.Add(model_macd);<span class="comment">class=class="str">"cmt">// load the model into the list of models</span> } <span class="keyword">else</span> {<span class="comment">class=class="str">"cmt">// Loading parameters was completed unsuccessfully</span> <span class="functions">Print</span>(<span class="class="type">class="kw">string">"Print(Model "</span>, model_macd.Name(), <span class="class="type">class="kw">string">" with period = "</span>, model_macd.<span class="functions">Period</span>(), <span class="class="type">class="kw">string">" on symbol "</span>, model_macd.<span class="functions">Symbol</span>(), <span class="class="type">class="kw">string">" creation has failed"</span>);
多品种多周期 MACD 模型批量挂载
在 EA 初始化阶段,可以用指针循环把不同品种、不同周期的自定义 MACD 模型塞进同一个链表,方便后续统一扫描。下面这段实装了 EURUSD H3、AUDUSD H1、AUDUSD H4 三组参数,快线/慢线周期分别取 8/12、10/18、14/15,信号平滑统一为 9。 Init 第一个参数 129475 是魔法号,用于区分本 EA 的订单与图表对象;第二参是模型名,第三四是品种与周期枚举,后三参即 FMA、SMA、Signal。若返回 true 就 Print 成功并 Add 进 list_model,失败则打印创建失败——MT5 终端的 Experts 标签能直接看到这些日志。 外汇与贵金属杠杆品种波动剧烈,多模型同跑会放大滑点与重绘风险,参数仅代表一种观察口径,实际信号触发概率需你自己回测验证。
class=class="str">"cmt">// class="num">1.2 EURUSD H3; FMA=class="num">8; SMA=class="num">12; model_macd = new cmodel_macd; class=class="str">"cmt">// Initialize the pointer by the model MACD if(model_macd.Init(class="num">129475, "Model macd H3", "EURUSD", PERIOD_H3, class="num">0.0, class="num">8,class="num">12,class="num">9)) { Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), " on symbol ", model_macd.Symbol(), " successfully created"); list_model.Add(model_macd);class=class="str">"cmt">// Load the model into the list of models } else {class=class="str">"cmt">// Loading of parameters was unsuccessful Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), " on symbol ", model_macd.Symbol(), " creation has failed"); } class=class="str">"cmt">// class="num">1.3 AUDUSD H1; FMA=class="num">10; SMA=class="num">18; model_macd = new cmodel_macd; class=class="str">"cmt">// Initialize the pointer by the model MACD if(model_macd.Init(class="num">129475, "Model macd M15", "AUDUSD", PERIOD_H1, class="num">0.0, class="num">10,class="num">18,class="num">9)) { Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), " on symbol ", model_macd.Symbol(), " successfully created"); list_model.Add(model_macd);class=class="str">"cmt">// Load the model into the list of models } else {class=class="str">"cmt">// The loading of parameters was unsuccessful Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), " on symbol ", model_macd.Symbol(), " creation has failed"); } class=class="str">"cmt">// class="num">1.4 AUDUSD H4; FMA=class="num">14; SMA=class="num">15; model_macd = new cmodel_macd; class=class="str">"cmt">// Initialize the pointer by the model MACD if(model_macd.Init(class="num">129475, "Model macd H4", "AUDUSD", PERIOD_H4, class="num">0.0, class="num">14,class="num">15,class="num">9)) { Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), " on symbol ", model_macd.Symbol(), " successfully created"); list_model.Add(model_macd);class=class="str">"cmt">// Load the model into the list of models } else{class=class="str">"cmt">// Loading of parameters was unsuccessful Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(),
「多周期 MACD 模型与布林带的批量初始化」
在 EA 初始化阶段,常需要把同一品种不同周期的 MACD 参数组合一次性挂进模型列表。下面这段逻辑就演示了 GBPUSD 在 H6 与 H12 上各建一个 cmodel_macd 实例,并塞进 list_model 容器。 H6 模型传入 FastMA=20、SlowMA=33、Signal=9,H12 模型则用 FastMA=12、SlowMA=30、Signal=9;两者 magic number 都写死为 129475。Init 返回 true 才 Add 进列表,否则 Print 报错,避免空指针后续炸裂。 布林带部分用开关分流:bollinger_default 为真时按当前品种和 period_bollinger / dev_bollinger 建默认模型,magic 1829374,ATR 周期 k_ATR 参与计算;bollinger_best 则走另一组优选参数(如 EURUSD M30,period=15,deviation=2.75)。外汇与贵金属杠杆高,这类多模型同跑会放大滑点与重算开销,实盘前务必在 MT5 策略测试器单周期验证。 把代码直接贴进你的 EA 的 OnInit 段,改 _Symbol 为具体品种,就能看到终端打印出『successfully created』确认模型加载。
model_macd = new cmodel_macd; if(model_macd.Init(class="num">129475, "Model macd H6", "GBPUSD", PERIOD_H6, class="num">0.0, class="num">20,class="num">33,class="num">9)) { Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), " on symbol ", model_macd.Symbol(), " successfully created"); list_model.Add(model_macd); } else { Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), " on symbol ", model_macd.Symbol(), " creation has failed"); } model_macd = new cmodel_macd; if(model_macd.Init(class="num">129475, "Model macd H6", "GBPUSD", PERIOD_H12, class="num">0.0, class="num">12,class="num">30,class="num">9)) { Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), " on symbol ", model_macd.Symbol(), " successfully created"); list_model.Add(model_macd); } else { Print("Print(Model ", model_macd.Name(), " with period = ", model_macd.Period(), " on symbol ", model_macd.Symbol(), " creation has failed"); } if(bollinger_default==true&&bollinger_best==false) { model_bollinger = new cmodel_bollinger; if(model_bollinger.Init(class="num">1829374,"Bollinger",_Symbol,PERIOD_CURRENT,class="num">0, period_bollinger,dev_bollinger,class="num">0,class="num">14,k_ATR)) { Print("Model ", model_bollinger.Name(), " successfully created"); list_model.Add(model_bollinger); } } if(bollinger_best==true&&bollinger_default==false) { class=class="str">"cmt">//class="num">2.1 Symbol: EURUSD M30; period: class="num">15; deviation: class="num">2,class="num">75; k_ATR=class="num">2,class="num">75; }
◍ 多周期布林实例的初始化参数
在 MT5 里批量挂布林模型时,同一类名 cmodel_bollinger 可以通过不同 Init 参数横向铺开多个品种与周期。下面这段实跑代码一次性建了 EURUSD 30分钟、EURUSD 4小时、GBPUSD 15分钟、GBPUSD 1小时、USDCAD 15分钟五组,偏差从 2.0 到 2.75 不等,ATR 乘数 k_ATR 从 2.0 拉到 3.75。 Init 首参 1829374 是魔法号,第二参 "Bollinger" 作模型名,第三参写交易品种,第四参定周期如 PERIOD_M30。后面紧跟的整型是布林均线周期:15、30、18、27,再后面浮点是偏差 deviation,最后两个浮点分别是偏移量 0 与 k_ATR 值。 外汇与贵金属杠杆高、滑点跳空频繁,这类多模型同跑若参数过窄,可能在震荡里反复触发。建议先开 MT5 策略测试器用这组数字回一遍,看样本内命中率再决定是否上实盘。
model_bollinger = new cmodel_bollinger; if(model_bollinger.Init(class="num">1829374,"Bollinger","EURUSD",PERIOD_M30,class="num">0,class="num">15,class="num">2.75,class="num">0,class="num">14,class="num">2.75)) { Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(), ". Symbol: ", model_bollinger.Symbol(), " successfully created"); list_model.Add(model_bollinger); } class=class="str">"cmt">//class="num">2.2 Symbol: EURUSD H4; period: class="num">30; deviation: class="num">2.0; k_ATR=class="num">2.25; model_bollinger = new cmodel_bollinger; if(model_bollinger.Init(class="num">1829374,"Bollinger","EURUSD",PERIOD_H4,class="num">0,class="num">30,class="num">2.00,class="num">0,class="num">14,class="num">2.25)) { Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(), ". Symbol: ", model_bollinger.Symbol(), " successfully created"); list_model.Add(model_bollinger); } class=class="str">"cmt">//class="num">2.3 Symbol: GBPUSD M15; period: class="num">18; deviation: class="num">2.25; k_ATR=class="num">3.0; model_bollinger = new cmodel_bollinger; if(model_bollinger.Init(class="num">1829374,"Bollinger","GBPUSD",PERIOD_M15,class="num">0,class="num">18,class="num">2.25,class="num">0,class="num">14,class="num">3.00)) { Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(), ". Symbol: ", model_bollinger.Symbol(), " successfully created"); list_model.Add(model_bollinger); } class=class="str">"cmt">//class="num">2.4 Symbol: GBPUSD H1; period: class="num">27; deviation: class="num">2.25; k_ATR=class="num">3.75; model_bollinger = new cmodel_bollinger; if(model_bollinger.Init(class="num">1829374,"Bollinger","GBPUSD",PERIOD_H1,class="num">0,class="num">27,class="num">2.25,class="num">0,class="num">14,class="num">3.75)) { Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(), ". Symbol: ", model_bollinger.Symbol(), " successfully created"); list_model.Add(model_bollinger); } class=class="str">"cmt">//class="num">2.5 Symbol: USDCAD M15; period: class="num">18; deviation: class="num">2.5; k_ATR=class="num">2.00; model_bollinger = new cmodel_bollinger; if(model_bollinger.Init(class="num">1829374,"Bollinger","USDCAD",PERIOD_M15,class="num">0,class="num">18,class="num">2.50,class="num">0,class="num">14,class="num">2.00)) {
往模型池里塞第二组布林带实例
上面这段是继续向 list_model 里添加第二个布林带模型实例,和前一组 EURUSD 的区别只在品种与参数。 注释里写明了第二组配置:USDCAD、H2 周期、布林周期 21、标准差偏离 2.50、ATR 周期 14、k_ATR 乘数 3.25。这组在 MetaTrader 5 里对应的是 H2 图表,外汇品种波动特性与 EURUSD 不同,参数照搬前一组未必合适。 Init 调用传入的魔法号是 1829374,名字字符串为 "Bollinger",和第一组区分开便于日志排查。若返回真,就打印创建成功并把对象指针加进 list_model;外汇和贵金属杠杆高,多模型同跑要先在策略测试器里验证内存与信号冲突。 下面把关键几行拆开看:
class=class="str">"cmt">//class="num">2.6 Symbol: USDCAD M15; period: class="num">21; deviation: class="num">2.5; k_ATR=class="num">3.25; model_bollinger = new cmodel_bollinger; if(model_bollinger.Init(class="num">1829374,"Bollinger","USDCAD",PERIOD_H2,class="num">0,class="num">21,class="num">2.50,class="num">0,class="num">14,class="num">3.25)) { Print("Model ", model_bollinger.Name(), "Period: ", model_bollinger.Period(), ". Symbol: ", model_bollinger.Symbol(), " successfully created"); list_model.Add(model_bollinger); }
「两种资金曲线放大路径的实操分野」
把回测里那张漂亮的结果图真正变成可执行仓位,靠的是资本化函数而非模型本身。原文把标准做法拆成两条线:固定比例法(Fixed Fractional)和 Ryan Jones 法,核心差异在于要不要硬性保护性止损。 固定比例法的本质是给每笔交易设一个账户百分比亏损上限。保守口径常用 2%:以 10,000 美元仓位计,单笔触止损后亏损不得超 200 美元。但风险敞口和终值并非线性——它是一条钟形曲线,总盈利随每笔风险提升先涨后跌,拐点就是所谓「最优 f」。这套逻辑要求你明确知道保护性止损位和能承受的账户比例。 Ryan Jones 公式不依赖固定保护性止损。原文里 MACD 模型较落后、本身没护止损,就直接用 Jones 法 capitalization;布林带模型有止损,走固定比例法。落地时两个方法都吃同一个 m_delta 变量:固定比例下它=每笔风险百分比,Jones 下它=抬升一级仓位需赚到的 delta 增量资金。 示例里所有模型套了相同最大风险值与 delta,但实盘完全可逐模型调参。外汇与贵金属杠杆高、滑点跳空频繁,钟形曲线拐点会随波动 regime 偏移,任何 f 值都只是概率偏好而非保本承诺,开 MT5 把 m_delta 从 0.02 改到 0.05 跑一遍才知自己曲线长什么样。
◍ 用挂单兜住机器人掉线风险
前文那套交易模型本身没用挂单,但挂单在实盘里有个硬价值:当跑 EA 的终端崩溃或断网时,已经挂在服务器端的 Buy Stop / Sell Limit 依旧生效,保护性止损或按预定价格入场的动作不会因本地设备故障而丢失。外汇与贵金属杠杆高,这类中断可能造成无保护敞口,用挂单相当于把部分风控移出本地。 要把挂单接进模型,得走 CTableOrders 的重载 Add(COrderInfo & order_info, double stop_loss, double take_profit),同时把成员变量 m_type 设成具体挂单类型,例如 ORDER_TYPE_BUY_STOP 或 ORDER_TYPE_SELL_LIMIT。难点在于捕捉「挂单被触发」或「相关性到期」的瞬间——只监听 Trade 事件不够,还得搞清楚触发源。 现在的做法是在历史订单里反查:用 HistoryOrderGetTicket() 按号比对,若历史订单号与挂单表中的某条相同,且状态是 ORDER_STATE_PARTIAL 或 ORDER_STATE_FILLED,就将该挂单在表中标记为已执行;若它没绑模拟止损/获利的挂单,则把真实订单号写进 TicketSL / TicketTP,并以 m_sl、m_tp 里已知价格补发模拟防护单。 下面这段 ReplaceDelayedOrders 是上述逻辑的核心入口,注意它先拦掉 SYMBOL_TRADE_MODE_DISABLED 的市场,再倒序扫订单表。 别把历史反查当银弹 历史比对能补 Trade 事件语义不足的坑,但 HistoryOrdersTotal() 返回的是当前历史池总数,若经纪商历史深度受限或订单已归档,可能出现比对遗漏,建议你在 MT5 里用 Print 打出 ticket 对照验证。
class="type">bool CModel::ReplaceDelayedOrders(class="type">void) { if(m_symbol_info.TradeMode()==SYMBOL_TRADE_MODE_DISABLED)class="kw">return(false); CTableOrders *t; class="type">int total_elements; class="type">int history_orders=HistoryOrdersTotal(); class="type">ulong ticket; class="type">bool rez=false; class="type">long request; total_elements=ListTableOrders.Total(); class="type">int try=class="num">0; if(total_elements==class="num">0)class="kw">return(false); class=class="str">"cmt">// View every order in the table for(class="type">int i=total_elements-class="num">1;i>=class="num">0;i--) { if(CheckPointer(ListTableOrders)==POINTER_INVALID)class="kw">continue; t=ListTableOrders.GetNodeAtIndex(i); if(CheckPointer(t)==POINTER_INVALID)class="kw">continue; class="kw">switch(t.Type()) { case ORDER_TYPE_BUY: case ORDER_TYPE_SELL: for(class="type">int b=class="num">0;i<history_orders;b++) { ticket=HistoryOrderGetTicket(b); class=class="str">"cmt">// If the ticket of the historical order is equal to one of the tickets class=class="str">"cmt">// Stop Loss or Take Profit, then the order was blocked, and it needs to be class=class="str">"cmt">// deleted from the table of orders if(ticket==t.TicketSL()||ticket==t.TicketTP()) { ListTableOrders.DeleteCurrent(); } } class=class="str">"cmt">// If the orders, imitating the Stop Loss and Take Profit are not found in the history, class=class="str">"cmt">// then perhaps they are not yet set. Therefore, they need to be inputted, class=class="str">"cmt">// using the process for pending orders below: class=class="str">"cmt">// the cycle keeps going, the exit &class="macro">#x27;break&class="macro">#x27; does not exist!!! case ORDER_TYPE_BUY_LIMIT: case ORDER_TYPE_BUY_STOP: case ORDER_TYPE_BUY_STOP_LIMIT: case ORDER_TYPE_SELL_LIMIT: case ORDER_TYPE_SELL_STOP: case ORDER_TYPE_SELL_STOP_LIMIT:
挂单成交后补模拟止损单
在遍历历史订单时,若发现某张挂单的 ticket 已出现在 HistoryOrderGetTicket 返回序列中,且状态为 PARTIAL 或 FILLED,说明原挂单已触发。此时要把本地订单表中的类型从挂单改写为市价单(ORDER_TYPE_BUY / ORDER_TYPE_SELL),否则后续逻辑会误判持仓状态。 补单逻辑只在 t.StopLoss()!=0.0 且 t.TicketSL()==0 时执行,避免重复挂出模拟止损。以 BUY 成交为例,代码用 SellStop 挂出等价止损,注释里写明最多尝试 3 次,直到返回码是 PLACED 或 DONE 才记录成交 ticket。 外汇与贵金属杠杆高,这种自管模拟止盈止损的方式可能在某些跳空行情中失效,建议在 MT5 策略测试器用 2023 年 XAUUSD 的 M1 数据跑一遍,观察 3 次重试是否足够覆盖点差扩大时段。
for(class="type">int b=class="num">0;i<history_orders;b++) { ticket=HistoryOrderGetTicket(b); class=class="str">"cmt">// If the ticket of the historical order is equal to the ticket of the pending order class=class="str">"cmt">// then the pending order has worked and needs to be put out class=class="str">"cmt">// the pending orders, imitating the work of Stop Loss and Take Profit. class=class="str">"cmt">// It is also necessary to change the pending status of the order in the table class=class="str">"cmt">// of orders for the executed(ORDER_TYPE_BUY или ORDER_TYPE_SELL) m_order_info.InfoInteger(ORDER_STATE,request); if(t.Ticket()==ticket&& (request==ORDER_STATE_PARTIAL||request==ORDER_STATE_FILLED)) { class=class="str">"cmt">// Change the status order in the table of orders: m_order_info.InfoInteger(ORDER_TYPE,request); if(t.Type()!=request)t.Type(request); class=class="str">"cmt">//------------------------------------------------------------------ class=class="str">"cmt">// Put out the pending orders, imitating Stop Loss an Take Profit: class=class="str">"cmt">// The level of pending orders, imitating Stop Loss and Take Profit class=class="str">"cmt">// should be determined earlier. It is also necessary to make sure that class=class="str">"cmt">// the current order is not already linked with the pending order, imitating Stop Loss class=class="str">"cmt">// and Take Profit: if(t.StopLoss()!=class="num">0.0&&t.TicketSL()==class="num">0) { class=class="str">"cmt">// Try to put out the pending order: class="kw">switch(t.Type()) { case ORDER_TYPE_BUY: class=class="str">"cmt">// Make three attempts to put out the pending order for(try=class="num">0;try<class="num">3;try++) { m_trade.SellStop(t.VolumeInitial(),t.StopLoss(),m_symbol,class="num">0.0,class="num">0.0,class="num">0,class="num">0,"take-profit for buy"); if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE) { t.TicketTP(m_trade.ResultDeal());
「用挂单模拟止盈的三次重试逻辑」
当持仓的 TakeProfit 不为 0 但 TicketTP 仍为 0 时,说明系统还没挂出模拟止盈的 pending order,这时进入 switch 按订单类型补挂。 对于 ORDER_TYPE_SELL 的持仓,代码用 for(try=0;try<3;try++) 循环最多 3 次,调用 m_trade.BuyStop 以原止损价挂 buy stop 来模仿卖单的止盈出场。 每次挂单后检查 ResultRetcode,若是 TRADE_RETCODE_PLACED 或 TRADE_RETCODE_DONE 就通过 t.TicketTP(m_trade.ResultDeal()) 记录成交票号并 break 退出重试。 同理 ORDER_TYPE_BUY 走的是 SellLimit 分支,参数里 volume 用 t.VolumeInitial()、挂单价用 t.StopLoss(),注释写的是 'take-profit for buy',这种反直觉命名在回测时容易看错,建议直接开 MT5 把这段贴进 EA 验证挂单方向。外汇与贵金属杠杆高,模拟止盈失败不意味实盘止损,需自行确认 broker 拒绝码。
case ORDER_TYPE_SELL: class=class="str">"cmt">// Make three attempts to put up a pending order for(try=class="num">0;try<class="num">3;try++) { m_trade.BuyStop(t.VolumeInitial(),t.StopLoss(),m_symbol,class="num">0.0,class="num">0.0,class="num">0,class="num">0,"take-profit for buy"); if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE) { t.TicketTP(m_trade.ResultDeal()); break; } } } if(t.TakeProfit()!=class="num">0.0&&t.TicketTP()==class="num">0){ class=class="str">"cmt">// Attempt to put out the pending order, imitating Take Profit: class="kw">switch(t.Type()) { case ORDER_TYPE_BUY: class=class="str">"cmt">// Make three attempts to put out the pending order for(try=class="num">0;try<class="num">3;try++) { m_trade.SellLimit(t.VolumeInitial(),t.StopLoss(),m_symbol,class="num">0.0,class="num">0.0,class="num">0,class="num">0,"take-profit for buy"); if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE) { t.TicketTP(m_trade.ResultDeal()); break; } }
◍ 卖单止盈挂单的重试逻辑
在订单类型为 SELL 的分支里,系统对止盈挂单的下发做了三次尝试容错。具体做法是循环 try 从 0 到 2,每次调用 BuyLimit 试图挂出「take-profit for buy」限价单,成交或挂单成功就记录成交票号并跳出。 这种三次重试结构能缓解瞬时报价拒绝(如 TRADE_RETCODE_REQUOTE 类)导致的止盈挂单失败。外汇与贵金属市场滑点和拒绝率偏高,实盘里建议把重试次数和间隔在策略参数中暴露出来,方便针对不同品种调。 下面这段是 SELL 分支中止盈挂单处理的核心代码片段,注意 BuyLimit 的止损与偏差参数传了 0,意味着沿用当前品种默认设置。
break; case ORDER_TYPE_SELL: class=class="str">"cmt">// Make three attempts to put out the pending order for(try=class="num">0;try<class="num">3;try++) { m_trade.BuyLimit(t.VolumeInitial(),t.StopLoss(),m_symbol,class="num">0.0,class="num">0.0,class="num">0,class="num">0,"take-profit for buy"); if(m_trade.ResultRetcode()==TRADE_RETCODE_PLACED||m_trade.ResultRetcode()==TRADE_RETCODE_DONE) { t.TicketTP(m_trade.ResultDeal()); break; } } } break;
把工具请下神坛
这套以链表为核心的数据结构思路,价值不在「全自动印钞」,而在把策略拆成互不干扰的独立机器人:同一品种上挂两个 EA,各自只动自己的订单和资金曲线,净持仓管理上的麻烦会少一大截。2010 年自动交易锦标赛里,按这种模型跑出来的系统能严格照交易计划执行,不同资金风控并行也不乱,算是一个实打实的验证点。 代码层有个坑得自己排。下面这段演示里,用索引遍历时删掉第 5 个节点后继续走索引 6,实际下一个有效节点还是原位置,社区里有人指出更稳的做法是 GetFirstNode / GetNextNode 逐步推进。 class Test : public CObject { public: int i_; Test(int i) { i_ = i; }; int get_i() { return i_; }; }; CList *list = new CList(); for (int i = 0; i < 10; i++) { Test *t = new Test(i); list.Add(t); } for (int i = 0; i < list.Total(); i++) { Test *t = list.GetNodeAtIndex(i); if (i == 5) { list.DeleteCurrent(); } if (CheckPointer(t) == POINTER_INVALID) { continue; } Print(t.get_i()); } for (Test *t = list.GetFirstNode(); t != NULL;) { t_current = t; if (t.get_i() == 5) { list.DeleteCurrent(); t = list.GetCurrentNode(); if (t == t_current) {break;} continue; } Print(t.get_i()); t = list.GetNextNode(); } 它附带的资金管理和预付款检查函数、对报价滑点耐受的下单系统,都是能直接抄进 MT5 工程里的零件。外汇和贵金属杠杆高、跳空频繁,真要上实盘前,先在策略测试器用合成点差跑几遍再谈信任——工具是死的,亏钱的人往往是把架构当圣旨的那批。
class Test : class="kw">public CObject { class="kw">public: class="type">int i_; Test(class="type">int i) { i_ = i; }; class="type">int get_i() { class="kw">return i_; }; }; CList *list = new CList(); for (class="type">int i = class="num">0; i < class="num">10; i++) { Test *t = new Test(i); list.Add(t); } for (class="type">int i = class="num">0; i < list.Total(); i++) { Test *t = list.GetNodeAtIndex(i); if (i == class="num">5) { list.DeleteCurrent(); } if (CheckPointer(t) == POINTER_INVALID) { class="kw">continue; } Print(t.get_i()); } for (Test *t = list.GetFirstNode(); t != NULL;) { t_current = t; if (t.get_i() == class="num">5) { list.DeleteCurrent(); t = list.GetCurrentNode(); if (t == t_current) {break;} class="kw">continue; } Print(t.get_i()); t = list.GetNextNode(); }