如何利用 MQL5 创建简单的多币种智能交易系统(第 7 部分):依据动量振荡器指标的之字折线·综合运用
- 用半截止盈线判定对冲单谁该撤
- 持仓亏损阈值的判定逻辑
- 把多币种信号 EA 的骨架搭进 MT5
- 把交易时段和品种拆成可枚举的开关
- EA 参数枚举与输入项的定义骨架
- EA 输入参数的交易品种与时段开关
- 订单风控与移动止损的参数开关
- EA 杂项开关与 30 货币对分组数组
- 自定义货币对列表的解析与经纪商前缀拼接
- 多币种符号表的批量拼接与就绪校验
- 按货币族切换观察标的数组
- 按币种切分货币对数组的写法
- 从品种名里抠出六位货币对内核
- 剥离品种前缀后缀的收尾与类骨架
- 多会话时间容器的变量布局
- 多品种EA的结构骨架与函数清单
- 挂单与品种配置的函数和数组声明
- EA 全局变量的声明块长什么样
- EA 交易动作与持仓管理的函数骨架
- 类收尾与多周期信号初始化
- 指标句柄与交易前置的初始化落点
- 多品种轮询里的开仓信号抓取
- 卖单分支里的层数封顶与反手逻辑
- 反向信号触发时的持仓切换逻辑
- 弱信号下的平仓与移动止损逻辑
- 用开关卡死一周里哪些天能下单
- 用服务器时差把交易时段筛出来
- 把本地时段的起止换算成 GMT 偏移
- 四大直盘交易时段在MT5里的边界换算
- 逐时区写入交易时段变量的写法
- 多信号共振的开仓判定与指标句柄批量挂载
- 给价格数组和之字转向留好内存
- 之字转向与AO双色信号的判定分支
- AO信号函数的三种判据写法
- 多指标信号如何合成开仓判定
- 多单与空单的开仓封装差异
- 图表按钮触发批量平仓与止损
- 手动面板按钮的事件分支拆解
- 图表按钮触发与跨日时段定位的实现细节
- 把账户与时段状态打印到图表上
- 持仓面板与动态止损调价的落地写法
- 卖单止损止盈的兜底补设逻辑
- 按魔术码与品种批量平掉盈利单
- 盈利平仓与面板初始化的代码片段
- 图表品种切换面板的按钮布局与响应
- 用按钮触发 EA 自卸载的清理逻辑
- 多品种回测怎么跑 ZigZag_AO_MCEA
- 最后一句大实话
◍ 用半截止盈线判定对冲单谁该撤
这段逻辑干的事很直接:当买方向出现盈利信号、卖方向出现亏损信号,且反向组合不成立时,先平掉卖单;反之亦然。核心判定写在 BuyProfitSellLoss 与 SellProfitBuyLoss 两个布尔量里,二者互斥才触发平仓,避免双向同时自残。 CheckProfit() 里把止盈阈值砍了一半——posprofit 取 TPval*0.5*pip 再按品种精度归一。循环遍历 DIRI[] 里的每个符号,对 Magic 匹配的持仓计算当前价与开仓价绝对差 pricegab,只要同类型持仓的 pricegab 大于这半个止盈距,就翻 inprofit 为真。 实际在 MT5 里跑,把 TPval 从默认调小到 20 点、pip 随品种自动算,你会看到黄金 XAUUSD 在 5 分钟图上更容易被这套半距逻辑扫到平仓。外汇与贵金属杠杆高,这类自动对冲撤单可能在本金未损时先吃掉浮盈,验证前先用策略测试器过一遍。
class="type">bool BuyProfitSellLoss=(xob[xx]>class="num">0 && CheckProfit(symbol,POSITION_TYPE_BUY)) && (xos[xx]>class="num">0 && CheckLoss(symbol,POSITION_TYPE_SELL,class="num">0.0)); class="type">bool SellProfitBuyLoss=(xos[xx]>class="num">0 && CheckProfit(symbol,POSITION_TYPE_SELL)) && (xob[xx]>class="num">0 && CheckLoss(symbol,POSITION_TYPE_BUY,class="num">0.0)); class=class="str">"cmt">//-- if(BuyProfitSellLoss && !SellProfitBuyLoss) { if(CloseSellPositions(symbol)) { PrintFormat("Close Sell %s %s %s",symbol,EnumToString(POSITION_TYPE_BUY),isloss); closeinloss=true; } } if(SellProfitBuyLoss && !BuyProfitSellLoss) { if(CloseBuyPositions(symbol)) { PrintFormat("Close Buy %s %s %s",symbol,EnumToString(POSITION_TYPE_SELL),isloss); closeinloss=true; } } class=class="str">"cmt">//-- class="kw">return(closeinloss); class=class="str">"cmt">//---- } class=class="str">"cmt">//-end CheckProfitLoss() class=class="str">"cmt">//---------// class="type">bool MCEA::CheckProfit(const class="type">class="kw">string symbol,class="type">ENUM_POSITION_TYPE intype) { class=class="str">"cmt">//--- Pips(symbol); class="type">class="kw">double posprofit=mc_symbol.NormalizePrice((TPval*class="num">0.5)*pip); class="type">bool inprofit=false; class=class="str">"cmt">//-- class="type">int ttlorder=PositionsTotal(); class=class="str">"cmt">// number of open positions class=class="str">"cmt">//-- for(class="type">int x=class="num">0; x<arrsymbx; x++) { class="type">class="kw">string symbol=DIRI[x]; class=class="str">"cmt">//-- for(class="type">int i=ttlorder-class="num">1; i>=class="num">0; i--) { class="type">class="kw">string position_Symbol = PositionGetSymbol(i); class="type">ENUM_POSITION_TYPE type = mc_position.PositionType(); if((position_Symbol==symbol) && (mc_position.Magic()==magicEA)) { class="type">class="kw">double price = mc_position.PriceCurrent(); class="type">class="kw">double pos_open = mc_position.PriceOpen(); class="type">class="kw">double posprofit = mc_symbol.NormalizePrice((TPval*class="num">0.5)*pip); class="type">class="kw">double pricegab = mc_symbol.NormalizePrice(fabs(price-pos_open)); class=class="str">"cmt">//--- if(type==intype && posprofit<pricegab) inprofit=true;
持仓亏损阈值的判定逻辑
CheckLoss() 用来判断某一交易品种、指定方向的持仓是否已经越过亏损缓冲线。它和 CheckProfit() 对称,是 EA 风控模块里决定「是否该认赔或减仓」的开关函数。 函数入参除了 symbol 与 intype,还留了一个 slc 默认 0.0。当 slc 不传时,lossval 取 SLval*0.5,也就是用止损幅度的一半作为「预警亏损」;若手动给了 slc,则以传入值为准。 核心循环先遍历 DIRI[] 里的品种,再倒序扫 PositionsTotal() 返回的所有持仓。只处理 magicEA 匹配的当前 EA 订单,用 fabs(price-pos_open) 算价格缺口,经 NormalizePrice(lossval*pip) 对齐点值后,若缺口大于 posloss 且类型吻合,就把 inloss 置 true。 这段只在 MT5 实盘或策略测试器里跑才有意义。把 SLval 从 0.5 调到 0.3,预警会更早触发,但外汇与贵金属杠杆高,假突破造成的频繁触发可能吃掉手续费,参数须按品种波动亲自验证。
class="type">bool MCEA::CheckLoss(const class="type">class="kw">string symbol,class="type">ENUM_POSITION_TYPE intype,class="type">class="kw">double slc=class="num">0.0) { class=class="str">"cmt">//--- Pips(symbol); class="type">bool inloss=false; class="type">class="kw">double lossval=slc==class="num">0.0 ? (SLval*class="num">0.5) : slc; class="type">class="kw">double posloss = mc_symbol.NormalizePrice(slc*pip); class="type">int ttlorder=PositionsTotal(); class=class="str">"cmt">// number of open positions class=class="str">"cmt">//-- for(class="type">int x=class="num">0;x<arrsymbx; x++) { class="type">class="kw">string symbol=DIRI[x]; class=class="str">"cmt">//-- for(class="type">int i=ttlorder-class="num">1; i>=class="num">0; i--) { class="type">class="kw">string position_Symbol = PositionGetSymbol(i); class="type">ENUM_POSITION_TYPE type = mc_position.PositionType(); if((position_Symbol==symbol) && (mc_position.Magic()==magicEA)) { class="type">class="kw">double price = mc_position.PriceCurrent(); class="type">class="kw">double pos_open = mc_position.PriceOpen(); class="type">class="kw">double posloss = mc_symbol.NormalizePrice(lossval*pip); class="type">class="kw">double pricegab = mc_symbol.NormalizePrice(fabs(price-pos_open)); class=class="str">"cmt">//--- if(type==intype && pricegab>posloss) inloss=true; } } } class=class="str">"cmt">//-- class="kw">return(inloss); class=class="str">"cmt">//---- } class=class="str">"cmt">//-end CheckLoss()
「把多币种信号 EA 的骨架搭进 MT5」
多币种智能系统不是把单币种逻辑简单复制十遍,而是在 OnInit 里用 ZigZag_AO_MCEA_Config() 一次性把品种、时间帧、指标句柄全配置好。该函数从 OnInit() 调用,负责给工作流里用到的所有指标创建句柄,并把时间帧处理封装进去;TFUSE 枚举把计算范围限制在 M5 到 D1,默认信号时间帧为 H4。 输入属性里「按指定时间交易」默认设为「是」且走自定义时段,EA 仅在设定的开始小时/分钟到结束小时/分钟内活动;若选「否」,则在 MT5 的 00:15–23:59 之间交易。若品种名拼错或验证失败,EA 会警告并自行从图表移除——这一点在加载前就要核对清楚。 开仓信号由 ExpertActionTrade() 调 GetOpenPosition() 给出:内部汇总 GetIndiSignals(ZigZag+AO 各自返回 ±1,合计 +2 为多、−2 为空)、DirectionMove(当前柱收价相对开盘方向)、PARSAR15(M15 抛物线 SAR 对齐)。返回 1 调 OpenBuy(),−1 调 OpenSell(),0 为未知。 图表交互靠 OnChartEvent():M 按钮出手工面板可批量设 SL/TP、全平、盈利单全平;C 按钮切品种;R 按钮直接卸 EA。下面这段头文件引用和全局对象声明,是整套流程的底层依赖,复制到你的 EA 源码顶部即可编译验证。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Include | class=class="str">"cmt">//+------------------------------------------------------------------+ class="macro">#include <Trade\Trade.mqh> class="macro">#include <Trade\PositionInfo.mqh> class="macro">#include <Trade\SymbolInfo.mqh> class="macro">#include <Trade\AccountInfo.mqh> class=class="str">"cmt">//-- CTrade mc_trade; class=class="str">"cmt">// 交易操作对象:发单、改单、平仓都走它 CSymbolInfo mc_symbol; class=class="str">"cmt">// 品种信息对象:点差、止损位、合约量查询 CPositionInfo mc_position; class=class="str">"cmt">// 持仓信息对象:遍历与读取当前持单 CAccountInfo mc_account; class=class="str">"cmt">// 账户信息对象:余额、杠杆、币种等 class=class="str">"cmt">//-- class=class="str">"cmt">//-- enum tm_zone {
◍ 把交易时段和品种拆成可枚举的开关
做跨时段策略最怕硬编码,把会话、小时、分钟、品种都定义成 enum,后面切换逻辑只用枚举值判断,改参数不碰主循环。下面这三组枚举是实盘 EA 里直接能抄的底子。 会话枚举覆盖了自定义、新西兰、悉尼、东京、伦敦、纽约六个档位,对应全球主要流动性窗口。外汇和贵金属在高杠杆下波动剧烈,不同时段点差和假突破概率差异明显,用枚举隔离后,回测时切东京或纽约只需改一个输入。 小时枚举从 hr_00 到 hr_23 共 24 个整点,分钟枚举只取 0/5/…/55 共 12 个五分位节点——这意味着策略触发精度被锁在 5 分钟粒度,避免有人手滑填 47 分这种非法值。 品种枚举给了 All30、按交易员自选、以及按基准货币分组的 9 类(USD/EUR/GBP/AUD/NZD/CAD/CHF/JPY 加自选),回测 EUR 相关对时直接填 Eurs 即可,不用一个个勾符号。 别把枚举当装饰 MT5 里 enum 输入项会直接变成 EA 参数下拉框,实盘前点开属性面板确认六个会话和九类品种都在,否则编译不报错但跑起来永远只交易默认项。
enum swsession { Cus_Session, class=class="str">"cmt">// Trading on Custom Session New_Zealand, class=class="str">"cmt">// Trading on New Zealand Session Australia, class=class="str">"cmt">// Trading on Autralia Sydney Session Asia_Tokyo, class=class="str">"cmt">// Trading on Asia Tokyo Session Europe_London, class=class="str">"cmt">// Trading on Europe London Session US_New_York class=class="str">"cmt">// Trading on US New York Session }; class=class="str">"cmt">//-- class=class="str">"cmt">//-- enum swhour { hr_00=class="num">0, class=class="str">"cmt">// class="num">00:class="num">00 hr_01=class="num">1, class=class="str">"cmt">// class="num">01:class="num">00 hr_02=class="num">2, class=class="str">"cmt">// class="num">02:class="num">00 hr_03=class="num">3, class=class="str">"cmt">// class="num">03:class="num">00 hr_04=class="num">4, class=class="str">"cmt">// class="num">04:class="num">00 hr_05=class="num">5, class=class="str">"cmt">// class="num">05:class="num">00 hr_06=class="num">6, class=class="str">"cmt">// class="num">06:class="num">00 hr_07=class="num">7, class=class="str">"cmt">// class="num">07:class="num">00 hr_08=class="num">8, class=class="str">"cmt">// class="num">08:class="num">00 hr_09=class="num">9, class=class="str">"cmt">// class="num">09:class="num">00 hr_10=class="num">10, class=class="str">"cmt">// class="num">10:class="num">00 hr_11=class="num">11, class=class="str">"cmt">// class="num">11:class="num">00 hr_12=class="num">12, class=class="str">"cmt">// class="num">12:class="num">00 hr_13=class="num">13, class=class="str">"cmt">// class="num">13:class="num">00 hr_14=class="num">14, class=class="str">"cmt">// class="num">14:class="num">00 hr_15=class="num">15, class=class="str">"cmt">// class="num">15:class="num">00 hr_16=class="num">16, class=class="str">"cmt">// class="num">16:class="num">00 hr_17=class="num">17, class=class="str">"cmt">// class="num">17:class="num">00 hr_18=class="num">18, class=class="str">"cmt">// class="num">18:class="num">00 hr_19=class="num">19, class=class="str">"cmt">// class="num">19:class="num">00 hr_20=class="num">20, class=class="str">"cmt">// class="num">20:class="num">00 hr_21=class="num">21, class=class="str">"cmt">// class="num">21:class="num">00 hr_22=class="num">22, class=class="str">"cmt">// class="num">22:class="num">00 hr_23=class="num">23 class=class="str">"cmt">// class="num">23:class="num">00 }; class=class="str">"cmt">//-- class=class="str">"cmt">//-- enum inmnt { mn_00=class="num">0, class=class="str">"cmt">// Minute class="num">0 mn_05=class="num">5, class=class="str">"cmt">// Minute class="num">5 mn_10=class="num">10, class=class="str">"cmt">// Minute class="num">10 mn_15=class="num">15, class=class="str">"cmt">// Minute class="num">15 mn_20=class="num">20, class=class="str">"cmt">// Minute class="num">20 mn_25=class="num">25, class=class="str">"cmt">// Minute class="num">25 mn_30=class="num">30, class=class="str">"cmt">// Minute class="num">30 mn_35=class="num">35, class=class="str">"cmt">// Minute class="num">35 mn_40=class="num">40, class=class="str">"cmt">// Minute class="num">40 mn_45=class="num">45, class=class="str">"cmt">// Minute class="num">45 mn_50=class="num">50, class=class="str">"cmt">// Minute class="num">50 mn_55=class="num">55 class=class="str">"cmt">// Minute class="num">55 }; class=class="str">"cmt">//-- class=class="str">"cmt">//-- enum PairsTrade { All30, class=class="str">"cmt">// All Forex class="num">30 Pairs TrdWi, class=class="str">"cmt">// Trader Wishes Pairs Usds, class=class="str">"cmt">// Forex USD Pairs Eurs, class=class="str">"cmt">// Forex EUR Pairs Gbps, class=class="str">"cmt">// Forex GBP Pairs Auds, class=class="str">"cmt">// Forex AUD Pairs Nzds, class=class="str">"cmt">// Forex NZD Pairs Cads, class=class="str">"cmt">// Forex CDD Pairs Chfs, class=class="str">"cmt">// Forex CHF Pairs Jpys class=class="str">"cmt">// Forex JPY Pairs };
EA 参数枚举与输入项的定义骨架
这段 MT5 代码没有写交易逻辑,先把 EA 跑起来要用的「开关」和「可调项」用枚举和 input 铺好了。看懂这部分,后面改策略参数才不会摸黑。 枚举 YN 只有 No 和 Yes 两态,给那些「开不开某功能」的布尔式选项用;mmt 区分固定手数(FixedLot)和动态手数(DynamLot),决定仓位计算走哪条路。 TFUSE 把 M5 到 D1 共 11 个周期映射成枚举值,从 TFM5 到 TFD1 顺序编号 00–10,默认交易周期 input 选了 TFH4,也就是 PERIOD_H4。TrType 管三种 trailing stop 方式:按价格、按指标、按 K 线高低。 MS 区分单品种(SP)和多品种(MP);SignalZZ 和 SignalAO 分别给 ZigZag 和 AO 信号留了 4 个和 3 个候选模式,默认 sigzz=SZZ2、sigao=SAO2。 ZigZag 输入项给了三个硬默认值:Depth=12、Deviation=5、BackStep=3,这组值在 H4 上过滤杂波倾向比较稳。外汇和贵金属波动大,改这些参数前建议先开 MT5 用策略测试器跑一遍,高风险品类别直接上实盘。 下面把核心声明原样贴出,方便你直接拷进 MQ5 文件比对:
enum YN { No, Yes }; enum mmt { FixedLot, class=class="str">"cmt">// Fixed Lot Size DynamLot class=class="str">"cmt">// Dynamic Lot Size }; enum TFUSE { TFM5, class=class="str">"cmt">// 00_PERIOD_M5 TFM15, class=class="str">"cmt">// 01_PERIOD_M15 TFM30, class=class="str">"cmt">// 02_PERIOD_M30 TFH1, class=class="str">"cmt">// 03_PERIOD_H1 TFH2, class=class="str">"cmt">// 04_PERIOD_H2 TFH3, class=class="str">"cmt">// 05_PERIOD_H3 TFH4, class=class="str">"cmt">// 06_PERIOD_H4 TFH6, class=class="str">"cmt">// 07_PERIOD_H6 TFH8, class=class="str">"cmt">// 08_PERIOD_H8 TFH12, class=class="str">"cmt">// 09_PERIOD_H12 TFD1 class=class="str">"cmt">// 10_PERIOD_D1 }; enum TrType { byprice, class=class="str">"cmt">// Trailing Stop by Price byindi, class=class="str">"cmt">// Trailing Stop by Indicator byHiLo class=class="str">"cmt">// Trailing Stop in HIGH or LOW bar }; enum MS { SP, class=class="str">"cmt">// Single Pair MP class=class="str">"cmt">// Multi Pairs }; enum SignalZZ { SZZ1, class=class="str">"cmt">// ZigZagSignal class="num">1 SZZ2, class=class="str">"cmt">// ZigZagSignal class="num">2 SZZ3, class=class="str">"cmt">// ZigZagSignal class="num">3 SZZ4 class=class="str">"cmt">// ZigZagSignal class="num">4 }; enum SignalAO { SAO1, class=class="str">"cmt">// AOSignal class="num">1 SAO2, class=class="str">"cmt">// AOSignal class="num">2 SAO3 class=class="str">"cmt">// AOSignal class="num">3 }; class="kw">input group "=== Global Strategy EA Parameter ==="; class=class="str">"cmt">// Global Strategy EA Parameter class="kw">input TFUSE tfinuse = TFH4; class=class="str">"cmt">// Select Expert TimeFrame, class="kw">default PERIOD_H4 class="kw">input group "=== ZigZag Indicator Input Properties ==="; class=class="str">"cmt">// ZigZag Indicator Input Properties class="kw">input class="type">int zzDepth = class="num">12; class=class="str">"cmt">// Input ZigZag Depth, class="kw">default class="num">12 class="kw">input class="type">int zzDevia = class="num">5; class=class="str">"cmt">// Input ZigZag Deviation, class="kw">default class="num">5 class="kw">input class="type">int zzBackS = class="num">3; class=class="str">"cmt">// Input ZigZag Back Step, class="kw">default class="num">3 class="kw">input SignalZZ sigzz = SZZ2; class=class="str">"cmt">// Select ZigZag Signal to Use class="kw">input SignalAO sigao = SAO2; class=class="str">"cmt">// Select AO Signal to Use class="kw">input group "=== Selected Pairs to Trade ==="; class=class="str">"cmt">// Selected Pairs to trading
「EA 输入参数的交易品种与时段开关」
把 EA 挂上 MT5 之前,先得把 input 区这几组参数理清楚,它们直接决定策略跑哪些品种、什么时候跑。下面这段声明涵盖了交易模式、资金管理、时段和星期开关,复制进 MQ5 文件头部就能在属性框里看到分组。 交易对模式用 trademode 切多品种(MP)或单品种;usepairs 选 All30 内置池,或在 traderwishes 写 "eurusd,usdchf" 这类逗号分隔的自定义清单。注意自定义清单仅在对应模式生效,写错品种名 EA 会静默跳过。 资金管理块里 mmlot 选 DynamLot 时按 Risk 百分比下仓,代码限定每笔风险 1.0%–10.0%,默认 10.0 偏高;固定仓模式才读 Lots=0.01。外汇和贵金属杠杆放大,10% 权益风险一回撤就可能伤筋动骨,实盘建议先压到 2% 以内观察。 时段与星期开关是另一层滤网:trd_time_zone=Yes 配合 Cus_Session 从 hr_00:mn_15 开到 hr_23:mn_55;星期里 ttd0=No 屏蔽周日,ttd1~ttd4=Yes 留周一至周四。想避周五非农晃点,把 ttd5 也设为 No 即可(本段未列,需自行补一行 input YN ttd5=No)。
class="kw">input MS trademode = MP; class=class="str">"cmt">// Select Trading Pairs Mode(Multi or Single) class="kw">input PairsTrade usepairs = All30; class=class="str">"cmt">// Select Pairs to Use class="kw">input class="type">class="kw">string traderwishes = "eg. eurusd,usdchf"; class=class="str">"cmt">// If Use Trader Wishes Pairs, class="kw">input pair name here, separate by comma class=class="str">"cmt">//-- class="kw">input group "=== Money Management Lot Size Parameter ==="; class=class="str">"cmt">// Money Management Lot Size Parameter class="kw">input mmt mmlot = DynamLot; class=class="str">"cmt">// Money Management Type class="kw">input class="type">class="kw">double Risk = class="num">10.0; class=class="str">"cmt">// Percent Equity Risk per Trade(Min=class="num">1.0% / Max=class="num">10.0%) class="kw">input class="type">class="kw">double Lots = class="num">0.01; class=class="str">"cmt">// Input Manual Lot Size FixedLot class=class="str">"cmt">//--Trade on Specific Time class="kw">input group "=== Trade on Specific Time ==="; class=class="str">"cmt">// Trade on Specific Time class="kw">input YN trd_time_zone = Yes; class=class="str">"cmt">// Select If You Like to Trade on Specific Time Zone class="kw">input tm_zone session = Cus_Session; class=class="str">"cmt">// Select Trading Time Zone class="kw">input swhour stsescuh = hr_00; class=class="str">"cmt">// Time Hour to Start Trading Custom Session(class="num">0-class="num">23) class="kw">input inmnt stsescum = mn_15; class=class="str">"cmt">// Time Minute to Start Trading Custom Session(class="num">0-class="num">55) class="kw">input swhour clsescuh = hr_23; class=class="str">"cmt">// Time Hour to Stop Trading Custom Session(class="num">0-class="num">23) class="kw">input inmnt clsescum = mn_55; class=class="str">"cmt">// Time Minute to Stop Trading Custom Session(class="num">0-class="num">55) class=class="str">"cmt">//--Day Trading On/Off class="kw">input group "=== Day Trading On/Off ==="; class=class="str">"cmt">// Day Trading On/Off class="kw">input YN ttd0 = No; class=class="str">"cmt">// Select Trading on Sunday(Yes) or(No) class="kw">input YN ttd1 = Yes; class=class="str">"cmt">// Select Trading on Monday(Yes) or(No) class="kw">input YN ttd2 = Yes; class=class="str">"cmt">// Select Trading on Tuesday(Yes) or(No) class="kw">input YN ttd3 = Yes; class=class="str">"cmt">// Select Trading on Wednesday(Yes) or(No) class="kw">input YN ttd4 = Yes; class=class="str">"cmt">// Select Trading on Thursday(Yes) or(No)
◍ 订单风控与移动止损的参数开关
这段输入参数集中定义了 EA 的交易日过滤与订单管理逻辑。周五交易被打开(ttd5=Yes),周六关闭(ttd6=No),避免周末流动性枯竭时的异常跳空,外汇与贵金属市场此时点差可能瞬间放大数倍,属高风险时段。 止损默认不挂真实 SL(use_sl=No),但启用了自动计算(autosl=Yes);若关闭自动,则按 SLval=30.0 点固定。止盈则双开(use_tp=Yes、autotp=Yes),手动兜底值为 TPval=60.0 点。 移动止损按高低价通道推进(trlby=byHiLo),触发门槛 TSmin=5.0 点、跟踪距离 TSval=10.0 点;移动止盈 TPmin=25.0 点启动。反向信号平仓(Close_by_Opps=Yes)、弱信号保利润(SaveOnRev=Yes)、虚拟 SL/TP 检查(CheckVSLTP=Yes)全部默认开启。 把这些值直接拷进 MT5 输入变量,就能复现一套「自动止损+双轨移动退场」的骨架。实盘前建议把 autosl 关掉、手填 SLval 测一轮,看 30 点止损在黄金 M5 上的触发频率是否超出你的承受。
class="kw">input YN ttd5 = Yes; class=class="str">"cmt">// Select Trading on Friday(Yes) or(No) class="kw">input YN ttd6 = No; class=class="str">"cmt">// Select Trading on Saturday(Yes) or(No) class=class="str">"cmt">//--Trade & Order management Parameter class="kw">input group "=== Trade & Order management Parameter ==="; class=class="str">"cmt">// Trade & Order management Parameter class="kw">input YN use_sl = No; class=class="str">"cmt">// Use Order Stop Loss(Yes) or(No) class="kw">input YN autosl = Yes; class=class="str">"cmt">// Use Automatic Calculation Stop Loss(Yes) or(No) class="kw">input class="type">class="kw">double SLval = class="num">30.0; class=class="str">"cmt">// If Not Use Automatic SL - Input SL value in Pips class="kw">input YN use_tp = Yes; class=class="str">"cmt">// Use Order Take Profit(Yes) or(No) class="kw">input YN autotp = Yes; class=class="str">"cmt">// Use Automatic Calculation Take Profit(Yes) or(No) class="kw">input class="type">class="kw">double TPval = class="num">60.0; class=class="str">"cmt">// If Not Use Automatic TP - Input TP value in Pips class="kw">input YN TrailingSL = Yes; class=class="str">"cmt">// Use Trailing Stop Loss(Yes) or(No) class="kw">input TrType trlby = byHiLo; class=class="str">"cmt">// Select Trailing Stop Type class="kw">input class="type">class="kw">double TSval = class="num">10.0; class=class="str">"cmt">// If Use Trailing Stop by Price Input value in Pips class="kw">input class="type">class="kw">double TSmin = class="num">5.0; class=class="str">"cmt">// Minimum Pips to start Trailing Stop class="kw">input YN TrailingTP = Yes; class=class="str">"cmt">// Use Trailing Take Profit(Yes) or(No) class="kw">input class="type">class="kw">double TPmin = class="num">25.0; class=class="str">"cmt">// Input Trailing Profit Value in Pips class="kw">input YN Close_by_Opps = Yes; class=class="str">"cmt">// Close Trade By Opposite Signal(Yes) or(No) class="kw">input YN SaveOnRev = Yes; class=class="str">"cmt">// Close Trade and Save profit due to weak signal(Yes) or(No) class="kw">input YN CheckVSLTP = Yes; class=class="str">"cmt">// Check Virtual SL/TP & Close Loss Trade(Yes) or(No) class=class="str">"cmt">//--Others Expert Advisor Parameter
EA 杂项开关与 30 货币对分组数组
这套多币种监控 EA 把杂项参数独立成一组,方便交易者只动通知和显示层而不碰核心逻辑。alerts 默认开、邮件和推送默认关,意味着你本地图表弹窗提示优先,想远程盯盘得手动把 UseEmailAlert 或 UseSendnotify 改成 Yes——外汇与贵金属波动快,漏看弹窗可能错过出场点,远程通知建议至少开一项。 trade_info_display 控制是否在图上画交易信息面板,magicEA 写死 20240218 作为订单识别码,回测或实盘里用这个魔术号过滤本 EA 开的仓,避免和别的脚本抢单。 HandlingSymbolArrays 里直接硬编码了 30 个常见品种,All30 覆盖七大直盘加交叉盘,并含 XAUUSD、XAGUSD 两个贵金属。下面又按基准货币拆出 USDs(9 个)、EURs(7 个)、GBPs(7 个)、AUDs(7 个)、NZDs(7 个)、CADs(7 个)子数组,做跨品种相关性扫描时直接按组遍历就行。 别在实盘前改了数组却忘了 magicEA 同步,否则历史订单统计会串号。开 MT5 把这段贴进 EA 的 mqh,编译后从符号列表里删掉不做的平台品种,能少跑很多无效计算。
class="kw">input group "=== Others Expert Advisor Parameter ==="; class=class="str">"cmt">// Others EA Parameter class="kw">input YN alerts = Yes; class=class="str">"cmt">// Display Alerts / Messages(Yes) or(No) class="kw">input YN UseEmailAlert = No; class=class="str">"cmt">// Email Alert(Yes) or(No) class="kw">input YN UseSendnotify = No; class=class="str">"cmt">// Send Notification(Yes) or(No) class="kw">input YN trade_info_display = Yes; class=class="str">"cmt">// Select Display Trading Info on Chart(Yes) or(No) class="kw">input class="type">ulong magicEA = class="num">20240218; class=class="str">"cmt">// Expert ID(Magic Number) class=class="str">"cmt">//--- class="type">void MCEA::HandlingSymbolArrays(class="type">void) { class=class="str">"cmt">//--- class="type">class="kw">string All30[]={"EURUSD","GBPUSD","AUDUSD","NZDUSD","USDCAD","USDCHF","USDJPY","EURGBP", "EURAUD","EURNZD","EURCAD","EURCHF","EURJPY","GBPAUD","GBPNZD","GBPCAD", "GBPCHF","GBPJPY","AUDNZD","AUDCAD","AUDCHF","AUDJPY","NZDCAD","NZDCHF", "NZDJPY","CADCHF","CADJPY","CHFJPY","XAUUSD","XAGUSD"}; class=class="str">"cmt">// class="num">30 pairs class="type">class="kw">string USDs[]={"USDCAD","USDCHF","USDJPY","AUDUSD","EURUSD","GBPUSD","NZDUSD","XAUUSD","XAGUSD"}; class=class="str">"cmt">// USD pairs class="type">class="kw">string EURs[]={"EURAUD","EURCAD","EURCHF","EURGBP","EURJPY","EURNZD","EURUSD"}; class=class="str">"cmt">// EUR pairs class="type">class="kw">string GBPs[]={"GBPAUD","GBPCAD","GBPCHF","EURGBP","GBPJPY","GBPNZD","GBPUSD"}; class=class="str">"cmt">// GBP pairs class="type">class="kw">string AUDs[]={"AUDCAD","AUDCHF","EURAUD","GBPAUD","AUDJPY","AUDNZD","AUDUSD"}; class=class="str">"cmt">// AUD pairs class="type">class="kw">string NZDs[]={"AUDNZD","NZDCAD","NZDCHF","EURNZD","GBPNZD","NZDJPY","NZDUSD"}; class=class="str">"cmt">// NZD pairs class="type">class="kw">string CADs[]={"AUDCAD","CADCHF","EURCAD","GBPCAD","CADJPY","NZDCAD","USDCAD"}; class=class="str">"cmt">// CAD pairs
「自定义货币对列表的解析与经纪商前缀拼接」
多币种扫描器常需要支持交易者手动指定品种,而不是死绑全市场 30 个主流对。下面这段逻辑先把瑞郎系和日元系各 7 个品种列成数组,再依据用户传入的逗号分隔字符串做拆分与校验。
代码里用 StringSplit 按逗号切出子串,随后全部转大写,并调用 ValidatePairs 逐个过滤非法符号——任何不通不过校验的元素直接用 ArrayRemove 删掉,保证后续只跑真实存在的品种。
经纪商给品种加前缀后缀是 MT5 常见坑(比如 m.USDCHF)。当探测到 inpre 或 insuf 大于 0 时,程序会把前缀 pre 和后缀 suf 拼到 All30 与用户自定义 SPC 每个元素上,否则用 SymbolName 去下单会直接报 4106 错误。
实操上,你可以把 traderwishes 设成 "eurusd,gbpjpy,xauusd" 丢进 EA 输入项,开 MT5 用专家日志看 SPC 数组最终剩几个——若写了错别名的品种会被静默剔除,这一点在回测和实盘前都建议先验证。外汇与贵金属杠杆波动剧烈,自定义品种失效可能导致漏单或错单,属高风险操作。
class="type">class="kw">string CHFs[]={"AUDCHF","CADCHF","EURCHF","GBPCHF","NZDCHF","CHFJPY","USDCHF"}; class=class="str">"cmt">// CHF pairs class="type">class="kw">string JPYs[]={"AUDJPY","CADJPY","CHFJPY","EURJPY","GBPJPY","NZDJPY","USDJPY"}; class=class="str">"cmt">// JPY pairs class=class="str">"cmt">//-- sall=ArraySize(All30); arusd=ArraySize(USDs); areur=ArraySize(EURs); aretc=ArraySize(JPYs); ArrayResize(VSym,sall,sall); ArrayCopy(VSym,All30,class="num">0,class="num">0,WHOLE_ARRAY); class=class="str">"cmt">//-- if(usepairs==TrdWi && StringFind(traderwishes,"eg.",class="num">0)<class="num">0) { class="type">class="kw">string to_split=traderwishes; class=class="str">"cmt">// A class="type">class="kw">string to split into substrings pairs name class="type">class="kw">string sep=","; class=class="str">"cmt">// A separator as a character class="type">class="kw">ushort u_sep; class=class="str">"cmt">// The code of the separator character class=class="str">"cmt">//--- Get the separator code u_sep=StringGetCharacter(sep,class="num">0); class=class="str">"cmt">//--- Split the class="type">class="kw">string to substrings class="type">int p=StringSplit(to_split,u_sep,SPC); if(p>class="num">0) { for(class="type">int i=class="num">0; i<p; i++) StringToUpper(SPC[i]); class=class="str">"cmt">//-- for(class="type">int i=class="num">0; i<p; i++) { if(ValidatePairs(SPC[i])<class="num">0) ArrayRemove(SPC,i,class="num">1); } } arspc=ArraySize(SPC); } class=class="str">"cmt">//-- SetSymbolNamePS(); class=class="str">"cmt">// With this function we will detect whether the Symbol Name has a prefix and/or suffix class=class="str">"cmt">//-- if(inpre>class="num">0 || insuf>class="num">0) { if(usepairs==TrdWi && arspc>class="num">0) { for(class="type">int t=class="num">0; t<arspc; t++) { SPC[t]=pre+SPC[t]+suf; } } class=class="str">"cmt">//-- for(class="type">int t=class="num">0; t<sall; t++) { All30[t]=pre+All30[t]+suf; }
◍ 多币种符号表的批量拼接与就绪校验
这段逻辑把各直盘货币对的字符串数组统一加了前后缀(pre / suf),再汇进总表 All30。USD 用 arusd 长度、EUR 用 areur 长度,其余 GBP、AUD、NZD、CAD、CHF、JPY 均按 aretc 长度循环,说明非美直盘在初始化时默认等量处理。 拼接完后,ArrayCopy 把 All30 整组拷给 VSym 与 AS30,ArrayResize(AS30,sall,sall) 按总对数 sall 定容;随后逐个 SymbolSelect(AS30[x],true) 把品种拉进市场观察窗。若当前图表品种过不了 ValidatePairs 检查,symbfix 保持 false,EA 会弹两条 Alert 并直接 ExpertRemove 退出,避免在不支持的对上瞎跑。 usepairs==0 分支即「全外汇+金属 30 对」模式:ArrayResize(DIRI,sall,sall) 后把 All30 拷给 DIRI,pairs 字符串拼出「Multi Currency 30 Pairs」这类标识。外汇与贵金属波动剧烈、杠杆风险高,实盘前务必在 MT5 策略测试器用 30 对样本跑一遍初始化,确认无 Alert 且观察窗品种齐全。
for(class="type">int t=class="num">0; t<arusd; t++) { USDs[t]=pre+USDs[t]+suf; } for(class="type">int t=class="num">0; t<areur; t++) { EURs[t]=pre+EURs[t]+suf; } for(class="type">int t=class="num">0; t<aretc; t++) { GBPs[t]=pre+GBPs[t]+suf; } for(class="type">int t=class="num">0; t<aretc; t++) { AUDs[t]=pre+AUDs[t]+suf; } for(class="type">int t=class="num">0; t<aretc; t++) { NZDs[t]=pre+NZDs[t]+suf; } for(class="type">int t=class="num">0; t<aretc; t++) { CADs[t]=pre+CADs[t]+suf; } for(class="type">int t=class="num">0; t<aretc; t++) { CHFs[t]=pre+CHFs[t]+suf; } for(class="type">int t=class="num">0; t<aretc; t++) { JPYs[t]=pre+JPYs[t]+suf; } } class=class="str">"cmt">//-- ArrayCopy(VSym,All30,class="num">0,class="num">0,WHOLE_ARRAY); ArrayResize(AS30,sall,sall); ArrayCopy(AS30,All30,class="num">0,class="num">0,WHOLE_ARRAY); for(class="type">int x=class="num">0; x<sall; x++) {SymbolSelect(AS30[x],true);} if(ValidatePairs(Symbol())>=class="num">0) symbfix=true; if(!symbfix) { Alert("Expert Advisors will not trade on pairs "+Symbol()); Alert("-- "+expname+" -- ",Symbol()," -- expert advisor will be Remove from the chart."); ExpertRemove(); } class=class="str">"cmt">//-- class="kw">switch(usepairs) { case class="num">0: class=class="str">"cmt">// All Forex & Metal class="num">30 Pairs { ArrayResize(DIRI,sall,sall); arrsymbx=sall; ArraySymbolResize(); ArrayCopy(DIRI,All30,class="num">0,class="num">0,WHOLE_ARRAY); pairs="Multi Currency "+class="type">class="kw">string(sall)+" Pairs"; class=class="str">"cmt">//--
按货币族切换观察标的数组
这段 switch 分支负责根据用户在下拉框选的币种分类,把对应的符号清单灌进 DIRI 动态数组,并同步改写界面显示的 pairs 字符串。 case 1 是交易者自选组:先用 ArrayResize(DIRI,arspc,arspc) 把 DIRI 扩到 arspc 长度,arrsymbx 记成 arspc,再 ArrayCopy 从 SPC 整组搬入,pairs 文本会带出 (arspc) Trader Wishes Pairs 这类计数前缀。 case 2~5 走的是 USD / EUR / GBP / AUD 四大直盘族。注意 EUR 分支里 arrsymbx=aretc 而 ArrayResize 用的是 areur,GBP 与 AUD 则统一拿 aretc 当尺寸——如果你在 MT5 里发现某族下标越界或显示对数不符,优先查这几个变量是不是在同一处被赋值。 外汇与贵金属属高风险品种,这类数组切换逻辑只解决「看哪些品种」,不预示任何方向;复制前先在策略测试器用 Print(arrsymbx) 核对实际装载量。
break; } case class="num">1: class=class="str">"cmt">// Trader wishes pairs { ArrayResize(DIRI,arspc,arspc); arrsymbx=arspc; ArraySymbolResize(); ArrayCopy(DIRI,SPC,class="num">0,class="num">0,WHOLE_ARRAY); pairs="("+class="type">class="kw">string(arspc)+") Trader Wishes Pairs"; class=class="str">"cmt">//-- break; } case class="num">2: class=class="str">"cmt">// USD pairs { ArrayResize(DIRI,arusd,arusd); arrsymbx=arusd; ArraySymbolResize(); ArrayCopy(DIRI,USDs,class="num">0,class="num">0,WHOLE_ARRAY); pairs="("+class="type">class="kw">string(arusd)+") Multi Currency USD Pairs"; class=class="str">"cmt">//-- break; } case class="num">3: class=class="str">"cmt">// EUR pairs { ArrayResize(DIRI,areur,areur); arrsymbx=aretc; ArraySymbolResize(); ArrayCopy(DIRI,EURs,class="num">0,class="num">0,WHOLE_ARRAY); pairs="("+class="type">class="kw">string(aretc)+") Forex EUR Pairs"; class=class="str">"cmt">//-- break; } case class="num">4: class=class="str">"cmt">// GBP pairs { ArrayResize(DIRI,aretc,aretc); arrsymbx=aretc; ArraySymbolResize(); ArrayCopy(DIRI,GBPs,class="num">0,class="num">0,WHOLE_ARRAY); pairs="("+class="type">class="kw">string(aretc)+") Forex GBP Pairs"; class=class="str">"cmt">//-- break; } case class="num">5: class=class="str">"cmt">// AUD pairs { ArrayResize(DIRI,aretc,aretc); arrsymbx=aretc; ArraySymbolResize(); ArrayCopy(DIRI,AUDs,class="num">0,class="num">0,WHOLE_ARRAY);
「按币种切分货币对数组的写法」
在 HandlingSymbolArrays() 里,用 switch 分支按基础货币把全局货币对清单拆到 DIRI 数组。case 6 到 9 分别处理 NZD、CAD、CHF、JPY 四类,每类都先 ArrayResize(DIRI,aretc,aretc) 按计数扩容,再把对应预存数组整块拷进 DIRI。 以 NZD 分支为例:aretc 是筛选后的货币对数量,arrsymbx=aretc 同步给后续函数用,ArraySymbolResize() 负责附属缓冲区,ArrayCopy(DIRI,NZDs,0,0,WHOLE_ARRAY) 把 NZDs 全部元素搬过来,pairs 字符串会显示类似 "(7) Forex NZD Pairs" 的标题。 CAD、CHF、JPY 分支结构完全一致,仅替换源数组名与显示后缀。外汇与贵金属杠杆品种波动剧烈,这类批量切分只解决「看哪一组」的问题,不代表任何方向概率优势,上 MT5 跑一遍才能确认你的经纪商符号表是否对齐。
pairs=class="type">class="kw">string(aretc)+") Forex AUD Pairs"; class=class="str">"cmt">//-- break; case class="num">6: class=class="str">"cmt">// NZD pairs { ArrayResize(DIRI,aretc,aretc); arrsymbx=aretc; ArraySymbolResize(); ArrayCopy(DIRI,NZDs,class="num">0,class="num">0,WHOLE_ARRAY); pairs=class="type">class="kw">string(aretc)+") Forex NZD Pairs"; class=class="str">"cmt">//-- break; } case class="num">7: class=class="str">"cmt">// CAD pairs { ArrayResize(DIRI,aretc,aretc); arrsymbx=aretc; ArraySymbolResize(); ArrayCopy(DIRI,CADs,class="num">0,class="num">0,WHOLE_ARRAY); pairs=class="type">class="kw">string(aretc)+") Forex CAD Pairs"; class=class="str">"cmt">//-- break; } case class="num">8: class=class="str">"cmt">// CHF pairs { ArrayResize(DIRI,aretc,aretc); arrsymbx=aretc; ArraySymbolResize(); ArrayCopy(DIRI,CHFs,class="num">0,class="num">0,WHOLE_ARRAY); pairs=class="type">class="kw">string(aretc)+") Forex CHF Pairs"; class=class="str">"cmt">//-- break; } case class="num">9: class=class="str">"cmt">// JPY pairs { ArrayResize(DIRI,aretc,aretc); arrsymbx=aretc; ArraySymbolResize(); ArrayCopy(DIRI,JPYs,class="num">0,class="num">0,WHOLE_ARRAY); pairs=class="type">class="kw">string(aretc)+") Forex JPY Pairs"; class=class="str">"cmt">//-- break; }
◍ 从品种名里抠出六位货币对内核
在 MT5 的 EA 或指标里,经常要面对 XAUUSD.sb、EURUSD.ecn 这类带后缀的品种名。直接拿 Symbol() 做字符串匹配会漏掉真实货币对,所以得先把 6 位核心(如 EURUSD)剥出来再处理。 下面这段代码先声明空串与整型变量,调用 SymbolSelect 把当前图品种塞进市场观察窗,再用 StringLen 取全长。xcur 数组列了 7 个主要币种缩写,ArraySize 拿到 7,作为遍历上限。 核心逻辑是用 for 循环对 7 个币种逐一 StringFind,一旦在品种名里命中就记录位置 pairx 并 break。随后按命中位置分三种情况截取:开头且带 SD、纯中间位置、或兜底从前面倒推 3 位,统一拼成 6 位 sym_use。 最后若原品种名长度大于 6 且 sym_use 恰为 6 位,就算出前缀 sym_pre 与后缀长度 sym_Lensuf,把非货币对部分单独存下来。外汇与贵金属杠杆高、点差突变频繁,这类解析最好在历史回放里跑一遍验证边界。
class="type">class="kw">string sym_pre=""; class="type">class="kw">string sym_suf=""; SymbolSelect(Symbol(),true); class="type">class="kw">string insymbol=Symbol(); class="type">int inlen=StringLen(insymbol); class="type">int toseek=-class="num">1; class="type">class="kw">string dep=""; class="type">class="kw">string bel=""; class="type">class="kw">string sym_use =""; class="type">int pairx=-class="num">1; class="type">class="kw">string xcur[]={"EUR","GBP","AUD","NZD","USD","CAD","CHF"}; class=class="str">"cmt">// class="num">7 major currency class="type">int xcar=ArraySize(xcur); class=class="str">"cmt">//-- for(class="type">int x=class="num">0; x<xcar; x++) { toseek=StringFind(insymbol,xcur[x],class="num">0); if(toseek>=class="num">0) { pairx=x; break; } } if(pairx>=class="num">0) { class="type">int awl=toseek-class="num">3 <class="num">0 ? class="num">0 : toseek-class="num">3; class="type">int sd=StringFind(insymbol,"SD",class="num">0); if(toseek==class="num">0 && sd<class="num">4) { dep=StringSubstr(insymbol,toseek,class="num">3); bel=StringSubstr(insymbol,toseek+class="num">3,class="num">3); sym_use=dep+bel; } else if(toseek>class="num">0) { dep=StringSubstr(insymbol,toseek,class="num">3); bel=StringSubstr(insymbol,toseek+class="num">3,class="num">3); sym_use=dep+bel; } else { dep=StringSubstr(insymbol,awl,class="num">3); bel=StringSubstr(insymbol,awl+class="num">3,class="num">3); sym_use=dep+bel; } } class=class="str">"cmt">//-- class="type">class="kw">string sym_nmx=sym_use; class="type">int lensx=StringLen(sym_nmx); class=class="str">"cmt">//-- if(inlen>lensx && lensx==class="num">6) { sym_Lenpre=StringFind(insymbol,sym_nmx,class="num">0); sym_Lensuf=inlen-lensx-sym_Lenpre; class=class="str">"cmt">//-- if(sym_Lenpre>class="num">0) { sym_pre=StringSubstr(insymbol,class="num">0,sym_Lenpre); for(class="type">int i=class="num">0; i<xcar; i++)
剥离品种前缀后缀的收尾与类骨架
上面那段 SetSymbolNamePS() 的收尾,先把过滤完的前缀后缀写回外部变量:pre=sym_pre; suf=sym_suf;,再用 StringLen() 算出长度 inpre、insuf。紧接着定了两个游标:posCur1=inpre 从主体起始位置算起,posCur2=posCur1+3 默认预留 3 位主体字符位,这两个值后面切分品种名时直接复用。
整个函数 return 后,代码转入 MCEA 类的声明。私有段里先排了时间拆解用的六个整型:x_year 到 x_sec,对应把时间戳拆成年月日时分秒,方便后面按交易时段过滤。
再往下是品种前缀后缀相关的一批变量:posCur1/posCur2 游标、inpre/insuf 长度、symbfix 开关、pre/suf 及 prefix/suffix 字符串;以及交易时区用的 ishour/onhour 起止小时和 tftrlst/tfcinws 两个整型标记。开 MT5 把这类声明贴进 ea 模板,能省掉每次手动解析 Symbol() 的重复劳动。
外汇与贵金属杠杆高,品种命名规则因券商而异,这类前缀剥离逻辑上线前需在模拟盘验证不同品种名长度下的切分表现。
if(StringFind(sym_pre,xcur[i],class="num">0)>=class="num">0) sym_pre=""; } if(sym_Lensuf>class="num">0) { sym_suf=StringSubstr(insymbol,sym_Lenpre+lensx,sym_Lensuf); for(class="type">int i=class="num">0; i<xcar; i++) if(StringFind(sym_suf,xcur[i],class="num">0)>=class="num">0) sym_suf=""; } } class=class="str">"cmt">//-- pre=sym_pre; suf=sym_suf; inpre=StringLen(pre); insuf=StringLen(suf); posCur1=inpre; posCur2=posCur1+class="num">3; class=class="str">"cmt">//-- class="kw">return; class=class="str">"cmt">//--- } class=class="str">"cmt">//-end SetSymbolNamePS() class=class="str">"cmt">//---------// class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class for working Expert Advisor | class=class="str">"cmt">//+------------------------------------------------------------------+ class MCEA { class=class="str">"cmt">//--- class="kw">private: class=class="str">"cmt">//---- class="type">int x_year; class=class="str">"cmt">// Year class="type">int x_mon; class=class="str">"cmt">// Month class="type">int x_day; class=class="str">"cmt">// Day of the month class="type">int x_hour; class=class="str">"cmt">// Hour in a day class="type">int x_min; class=class="str">"cmt">// Minutes class="type">int x_sec; class=class="str">"cmt">// Seconds class=class="str">"cmt">//-- class="type">int oBm, oSm, ldig; class=class="str">"cmt">//--- Variables used in prefix and suffix symbols class="type">int posCur1, posCur2; class="type">int inpre, insuf; class="type">bool symbfix; class="type">class="kw">string pre,suf; class="type">class="kw">string prefix,suffix; class=class="str">"cmt">//--- Variables are used in Trading Time Zone class="type">int ishour, onhour; class="type">int tftrlst, tfcinws;
「多会话时间容器的变量布局」
这段声明把一天拆成了当前会话与五个编号会话(Ses01~Ses05)外加一个未编号会话(SesNo),每个都成对保存开盘与收盘时间。外汇与贵金属属高杠杆品种,这类时间容器若填错时区,后续信号会整体偏移。 datetime 组的 rem/znop/zncl/zntm 像是保留给某种基准时刻与 zone 开关,并未在此展开赋值逻辑。string 组的 tz_ses/tz_opn/tz_cls 与 tmop*/tmcl* 一一对应,前者存时区与开收标签,后者存具体时间字符串,方便日志或面板直接打印。 double 组里 LotPS 是每点手数折算,point 是品种点值,slv/tpv 是止损止盈价,pip/xpip 区分普通点与扩展点;SARstep/SARmaxi 留给抛物线参数,floatprofit 承接浮动盈利。开 MT5 把这段贴进全局区,编译后从数据窗口看 Ses01Op~Ses05Cl 是否随品种时区正确刷新,就能验证会话切割有没有生效。
class="type">class="kw">datetime rem, znop, zncl, zntm; class="type">class="kw">datetime SesCuOp, SesCuCl, Ses01Op, Ses01Cl, Ses02Op, Ses02Cl, Ses03Op, Ses03Cl, Ses04Op, Ses04Cl, Ses05Op, Ses05Cl, SesNoOp, SesNoCl; class=class="str">"cmt">//-- class="type">class="kw">string tz_ses, tz_opn, tz_cls; class=class="str">"cmt">//-- class="type">class="kw">string tmopcu, tmclcu, tmop01, tmcl01, tmop02, tmcl02, tmop03, tmcl03, tmop04, tmcl04, tmop05, tmcl05, tmopno, tmclno; class=class="str">"cmt">//---------------------- class=class="str">"cmt">//-- class="type">class="kw">double LotPS; class="type">class="kw">double point; class="type">class="kw">double slv, tpv, pip, xpip; class="type">class="kw">double SARstep, SARmaxi; class="type">class="kw">double floatprofit,
◍ 多品种EA的结构骨架与函数清单
这段声明展示了一个同时盯多个货币对 / 贵金属的 MQL5 EA 在类内部怎么排兵布阵。先把品种名、交易日、交易模式等字符串挂在成员里,再预留 OPEN/HIGH/LOW/CLOSE/TIME 几个数组承接行情,closetime 单独记平仓时刻,结构上一眼能看出是做跨品种扫描用的。 函数层分三块:时区与交易时段相关的 Set_Time_Zone、Time_Zone、Trade_session、PosTimeZone 负责把服务器时间和本地交易窗口对齐;DirectionMove、GetIndiSignals、ZigZagSignal、AOSignal、PARSAR05、PARSAR15 则是各周期信号判定,05/15 后缀表明它至少跑了 M5 和 M15 两层 SAR。 仓位侧用 MLots 和 LotDig 算手数,NonZeroDiv 专门兜住除零——外汇和贵金属杠杆高,除零崩脚本比止损慢半拍更致命,开盘前在 MT5 里把这组函数名抄进自己的类里跑一遍,能省掉不少重复造轮子的时间。
fixclprofit; class=class="str">"cmt">//-- class="type">class="kw">string pairs, hariini, daytrade, trade_mode; class=class="str">"cmt">//-- class="type">class="kw">double OPEN[], HIGH[], LOW[], CLOSE[]; class="type">class="kw">datetime TIME[]; class="type">class="kw">datetime closetime; class=class="str">"cmt">//-- class=class="str">"cmt">//------------ class=class="str">"cmt">//------------ class="type">void SetSymbolNamePS(class="type">void); class="type">void HandlingSymbolArrays(class="type">void); class="type">void Set_Time_Zone(class="type">void); class="type">void Time_Zone(class="type">void); class="type">bool Trade_session(class="type">void); class="type">class="kw">string PosTimeZone(class="type">void); class="type">int ThisTime(const class="type">int reqmode); class="type">int ReqTime(class="type">class="kw">datetime reqtime,const class="type">int reqmode); class=class="str">"cmt">//-- class="type">int DirectionMove(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES stf); class="type">int GetIndiSignals(const class="type">class="kw">string symbol); class="type">int ZigZagSignal(const class="type">class="kw">string symbol); class="type">int AOSignal(const class="type">class="kw">string symbol); class="type">int AOColorSignal(const class="type">class="kw">string symbol); class="type">int PARSAR05(const class="type">class="kw">string symbol); class="type">int PARSAR15(const class="type">class="kw">string symbol); class="type">int LotDig(const class="type">class="kw">string symbol); class=class="str">"cmt">//-- class="type">class="kw">double MLots(const class="type">class="kw">string symbx); class="type">class="kw">double NonZeroDiv(class="type">class="kw">double val1,class="type">class="kw">double val2);
挂单与品种配置的函数和数组声明
这段类声明集中暴露了 EA 对止损止盈、时间格式以及多货币对配置的接口层。OrderSLSet / OrderTPSet 接收 ENUM_ORDER_TYPE,用于挂单类止损止盈价计算;SetOrderSL / SetOrderTP 则接收 ENUM_POSITION_TYPE,面向已持仓位。TSPrice 的 TS_type 参数决定追踪止损的算法分支,实盘里改这个数就能切换 trailing 逻辑。 时间类辅助函数值得注意:ReqDate(d,h,m) 拼请求日期串,TF2Str 把周期枚举转成可读文本,TradingDay 无参返回当前交易日。GetCommentForOrder 直接 return expname,说明订单注释绑定的是 EA 实例名,换实例名订单备注就跟着变。 public 区暴露了 ZigZag_AO_MCEA 的配置骨架:DIRI[]、AS30[]、VSym[] 以及按币种拆分的 SPC[]、USD[]、EUR[]、GBP[]、AUD[]、NZD[]、CAD[]、CHF[]、JPY[] 字符串数组,运行时按货币组载入参数。指标句柄 hZigZag[] 与 hAO[] 成对出现,hVIDyAv[] 单独,hPar05[] 与 hPar15[] 疑似双周期抛物线句柄——接不同周期数据用。 外汇与贵金属波动剧烈,这类多品种数组若越界填充会直接触发无效交易,建议在 MT5 策略测试器里先单货币跑通再开全市场。
class="type">class="kw">double OrderSLSet(const class="type">class="kw">string xsymb,ENUM_ORDER_TYPE type,class="type">class="kw">double atprice); class="type">class="kw">double OrderTPSet(const class="type">class="kw">string xsymb,ENUM_ORDER_TYPE type,class="type">class="kw">double atprice); class="type">class="kw">double SetOrderSL(const class="type">class="kw">string xsymb,class="type">ENUM_POSITION_TYPE type,class="type">class="kw">double atprice); class="type">class="kw">double SetOrderTP(const class="type">class="kw">string xsymb,class="type">ENUM_POSITION_TYPE type,class="type">class="kw">double atprice); class="type">class="kw">double TSPrice(const class="type">class="kw">string xsymb,class="type">ENUM_POSITION_TYPE ptype,class="type">int TS_type); class=class="str">"cmt">//-- class="type">class="kw">string ReqDate(class="type">int d,class="type">int h,class="type">int m); class="type">class="kw">string TF2Str(ENUM_TIMEFRAMES period); class="type">class="kw">string timehr(class="type">int hr,class="type">int mn); class="type">class="kw">string TradingDay(class="type">void); class="type">class="kw">string AccountMode(); class="type">class="kw">string GetCommentForOrder(class="type">void) { class="kw">return(expname); } class=class="str">"cmt">//------------ class="kw">public: class=class="str">"cmt">//-- ZigZag_AO_MCEA Config -- class="type">class="kw">string DIRI[], AS30[], VSym[]; class="type">class="kw">string SPC[]; class="type">class="kw">string USD[]; class="type">class="kw">string EUR[]; class="type">class="kw">string GBP[]; class="type">class="kw">string AUD[]; class="type">class="kw">string NZD[]; class="type">class="kw">string CAD[]; class="type">class="kw">string CHF[]; class="type">class="kw">string JPY[]; class=class="str">"cmt">//-- class="type">class="kw">string expname; class="type">class="kw">string indiname1; class=class="str">"cmt">//-- class=class="str">"cmt">//--- Indicators Handle class="type">int hZigZag[], hAO[]; class="type">int hVIDyAv[]; class="type">int hPar05[], hPar15[]; class=class="str">"cmt">//---
「EA 全局变量的声明块长什么样」
拆解一段 MQL5 的 EA 类成员变量声明,能直接看清这套系统把哪些维度塞进了内存。 int ALO, dgts, arrsar, arrsymbx; 这四个整型分别管 AO 周期、报价精度、SAR 数组长度与符号框数量,属于配置层常量。 int sall, arusd, areur, aretc, arspc, arper; 是各资产数组的容量上限,比如 arusd 对应美元直盘挂单数,aretc 装其余交叉盘,实际跑起来若 EURUSD 加 XAUUSD 同屏,arusd 至少要 ≥ areur+arspc 才不越界。 ulong slip; 单独放滑点容忍值,外汇与贵金属点差跳变时它决定市价单失效阈值,属于高风险品种必调参数。 double profitb[], profits[]; 与 double minprofit; 构成利润统计缓冲,profitb 按笔记录,profits 按品种汇总,minprofit 是过滤微利交易的门槛。 时间拆件这组 int year, mon, day, hour, min, sec, dow, doy; 把每根 K 线时间戳打散,dow 用 0=周日 到 6=周六,doy 以 1 月 1 日为 0,做季节因子时直接读不必每次调 TimeCurrent 再算。 ENUM_TIMEFRAMES TFt, TFT05, TFT15; 预存主图与 5M、15M 周期枚举,切换多周期确认时免重复转换。 bool PanelExtra; 控制右侧扩展面板显隐。尾部的 MCEA(void) 构造、~MCEA(void) 析构与 virtual void ZigZag_AO_MCEA_Config(void); 表明这是个带 ZigZag+AO 配置虚函数的派生 EA 骨架,复制进 MT5 新建 EA 即可编译验证。
class="type">int ALO, dgts, arrsar, arrsymbx; class="type">int sall, arusd, areur, aretc, arspc, arper; class="type">ulong slip; class=class="str">"cmt">//-- class="type">class="kw">double profitb[], profits[]; class="type">class="kw">double minprofit; class=class="str">"cmt">//-- class="type">int Buy, Sell; class="type">int ccur, psec, xtto, checktml; class="type">int OpOr[],xob[],xos[]; class=class="str">"cmt">//-- class="type">int year, class=class="str">"cmt">// Year mon, class=class="str">"cmt">// Month day, class=class="str">"cmt">// Day hour, class=class="str">"cmt">// Hour min, class=class="str">"cmt">// Minutes sec, class=class="str">"cmt">// Seconds dow, class=class="str">"cmt">// Day of week(class="num">0-Sunday, class="num">1-Monday, ... ,class="num">6-Saturday) doy; class=class="str">"cmt">// Day number of the year(January 1st is assigned the number value of zero) class=class="str">"cmt">//-- ENUM_TIMEFRAMES TFt, TFT05, TFT15; class=class="str">"cmt">//-- class="type">bool PanelExtra; class=class="str">"cmt">//------------ MCEA(class="type">void); ~MCEA(class="type">void); class=class="str">"cmt">//------------ class=class="str">"cmt">//-- class="kw">virtual class="type">void ZigZag_AO_MCEA_Config(class="type">void);
◍ EA 交易动作与持仓管理的函数骨架
在 MT5 自定义 EA 的类定义里,这一组方法基本框定了「信号触发—下单—风控—收尾」的完整动作链。虚函数 ExpertActionTrade 留给子类重写,是策略主逻辑的入口;其余 void 与 bool 函数则按职责拆开,分别管品种数组、报警、挂单 SL/TP、批量平仓与权益检查。 注意两个重载的 UpdatePrice:一个只传品种和时间帧,另一个多了 bars 参数控制刷新的 K 线数量。实盘里若只盯当前柱,用前者足够;要做多周期回溯校验,就得传 bars 显式拉取历史缓冲,否则可能读到过期 tick。 bool 类函数返回值直接决定流程是否继续:比如 OpenBuy / OpenSell 返回下单成败,CheckEquityBalance 返回权益是否越线。把这些返回值接进主循环,才能在外汇与贵金属这种高杠杆品种上,把「可能爆仓」的概率压到可控范围。打开 MT5 的 MetaEditor,把下面声明贴进你的 EA 头文件,先编译通再逐个填实现。
class="kw">virtual class="type">void ExpertActionTrade(class="type">void); class=class="str">"cmt">//-- class="type">void ArraySymbolResize(class="type">void); class="type">void CurrentSymbolSet(const class="type">class="kw">string symbol); class="type">void Pips(const class="type">class="kw">string symbol); class="type">void TradeInfo(class="type">void); class="type">void Do_Alerts(const class="type">class="kw">string symbx,class="type">class="kw">string msgText); class="type">void CheckOpenPMx(const class="type">class="kw">string symbx); class="type">void SetSLTPOrders(class="type">void); class="type">void CloseAllOrders(class="type">void); class="type">void CheckClose(const class="type">class="kw">string symbx); class="type">void TodayOrders(class="type">void); class="type">void UpdatePrice(const class="type">class="kw">string symbol,ENUM_TIMEFRAMES xtf); class="type">void UpdatePrice(const class="type">class="kw">string symbol,ENUM_TIMEFRAMES xtf,class="type">int bars); class="type">void RefreshPrice(const class="type">class="kw">string symbx,ENUM_TIMEFRAMES xtf,class="type">int bars); class=class="str">"cmt">//-- class="type">bool CheckEquityBalance(class="type">void); class="type">bool RefreshTick(const class="type">class="kw">string symbx); class="type">bool TradingToday(class="type">void); class="type">bool OpenBuy(const class="type">class="kw">string symbol); class="type">bool OpenSell(const class="type">class="kw">string symbol); class="type">bool ModifyOrderSLTP(class="type">class="kw">double mStop,class="type">class="kw">double ordtp); class="type">bool ModifyOrdersSL(const class="type">class="kw">string symbx,class="type">int TS_type); class="type">bool ModifyOrdersTP(const class="type">class="kw">string symbx); class="type">bool CloseAllProfit(class="type">void); class="type">bool CloseAllLoss(class="type">void); class="type">bool ManualCloseAllProfit(class="type">void); class="type">bool CheckProfitLoss(const class="type">class="kw">string symbol); class="type">bool CloseBuyPositions(const class="type">class="kw">string symbol);
类收尾与多周期信号初始化
这段截自一个 EA 的类定义末尾与 OnInit 入口,能看到 MCEA 类把平仓、校验、反初始化原因等函数都封在类里,外部只留一个全局实例 MCEA mc;。
初始化时只调了 mc.ZigZag_AO_MCEA_Config(),真正的品种数组处理和多周期框架设定都在类方法里完成,返回 INIT_SUCCEEDED 即代表加载通过。
配置函数里写死了两个短周期:TFT05=PERIOD_M5、TFT15=PERIOD_M15,并定义了一个 11 档的 TF 数组,从 M5 一路到 D1。用 tfinuse 下标去挑计算信号用的 TFt,比如 tfinuse=0 就取 M5。
随后对 arrsymbx 个品种逐个建 ZigZag 句柄:iCustom(DIRI[x],TFt,indiname1,zzDepth,zzDevia,zzBackS),参数 depth/deviation/backstep 来自外部变量。外汇与贵金属杠杆高,这类多品种 EA 实盘前务必在 MT5 策略测试器里先跑单品种验证句柄是否返回有效值。
class="type">bool CloseSellPositions(const class="type">class="kw">string symbol); class="type">bool CheckProfit(const class="type">class="kw">string symbol,class="type">ENUM_POSITION_TYPE intype); class="type">bool CheckLoss(const class="type">class="kw">string symbol,class="type">ENUM_POSITION_TYPE intype,class="type">class="kw">double slc=class="num">0.0); class=class="str">"cmt">//-- class="type">int PairsIdxArray(const class="type">class="kw">string symbol); class="type">int ValidatePairs(const class="type">class="kw">string symbol); class="type">int GetOpenPosition(const class="type">class="kw">string symbol); class="type">int GetCloseInWeakSignal(const class="type">class="kw">string symbol,class="type">int exis); class=class="str">"cmt">//-- class="type">class="kw">string getUninitReasonText(class="type">int reasonCode); class=class="str">"cmt">//------------ class=class="str">"cmt">//--- }; class=class="str">"cmt">//-end class MCEA class=class="str">"cmt">//---------// MCEA mc; class=class="str">"cmt">//---------// class="type">int OnInit(class="type">void) { class=class="str">"cmt">//--- mc.ZigZag_AO_MCEA_Config(); class=class="str">"cmt">//-- class="kw">return(INIT_SUCCEEDED); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end OnInit() class="type">void MCEA::ZigZag_AO_MCEA_Config(class="type">void) { HandlingSymbolArrays(); TFT05=PERIOD_M5; TFT15=PERIOD_M15; ENUM_TIMEFRAMES TFs[]={PERIOD_M5,PERIOD_M15,PERIOD_M30,PERIOD_H1,PERIOD_H2,PERIOD_H3,PERIOD_H4,PERIOD_H6,PERIOD_H8,PERIOD_H12,PERIOD_D1}; class="type">int arTFs=ArraySize(TFs); for(class="type">int x=class="num">0; x<arTFs; x++) if(tfinuse==x) TFt=TFs[x]; for(class="type">int x=class="num">0; x<arrsymbx; x++) { hZigZag[x] = iCustom(DIRI[x],TFt,indiname1,zzDepth,zzDevia,zzBackS); } }
「指标句柄与交易前置的初始化落点」
这段配置函数的尾部把多周期指标句柄一次性挂好,AO、VIDYA、双周期 SAR 分别绑定到不同时间帧:M5 与 M15 的抛物线止损各拿一个句柄,VIDYA 用 9 周期信号、12 周期平滑、加权收盘价算 trailing stop。直接照抄句柄申请段,开 MT5 新建 EA 时就不会在 OnTick 里反复调用 iAO/iSAR 拖慢回测。 TesterHideIndicators(true) 之后,minprofit 被规范成 TSmin/100 并保留两位小数;ALO 上限取决于账户限价单数,若关闭 Close_by_Opps,限价单数 ≥ sall*2 时 ALO 顶到 sall*2,否则折半。外汇与贵金属杠杆品种下,这类批量挂单逻辑遇报价跳空可能触发滑点放大,属高风险操作。 OnTick 本体极薄,只转调 mc.ExpertActionTrade(),真正的交易决策不堆在主频函数里。ExpertActionTrade 开头先用 MQLInfoInteger(MQL_TRADE_ALLOWED) 卡死终端是否放行算法交易,未放行且 checktml==0 才弹一次告警并 return,避免每 tick 刷屏。
hAO[x] = iAO(DIRI[x],TFt); class=class="str">"cmt">//-- Handle for the Awesome_Oscillator indicator hVIDyAv[x] = iVIDyA(DIRI[x],TFt,class="num">9,class="num">12,class="num">0,PRICE_WEIGHTED); class=class="str">"cmt">//-- Handle for the VIDYA indicator for Trailing Stop hPar05[x] = iSAR(DIRI[x],TFT05,SARstep,SARmaxi); class=class="str">"cmt">//-- Handle for the iSAR indicator for M5 Timeframe hPar15[x] = iSAR(DIRI[x],TFT15,SARstep,SARmaxi); class=class="str">"cmt">//-- Handle for the iSAR indicator for M15 Timeframe class=class="str">"cmt">//-- } class=class="str">"cmt">//-- TesterHideIndicators(true); minprofit=NormalizeDouble(TSmin/class="num">100.0,class="num">2); class=class="str">"cmt">//-- ALO=(class="type">int)mc_account.LimitOrders()>sall ? sall : (class="type">int)mc_account.LimitOrders(); if(Close_by_Opps==No) { if((class="type">int)mc_account.LimitOrders()>=(sall*class="num">2)) ALO=sall*class="num">2; else ALO=(class="type">int)(mc_account.LimitOrders()/class="num">2); } class=class="str">"cmt">//-- LotPS=(class="type">class="kw">double)ALO; class=class="str">"cmt">//-- mc_trade.SetExpertMagicNumber(magicEA); mc_trade.SetDeviationInPoints(slip); mc_trade.SetMarginMode(); Set_Time_Zone(); class=class="str">"cmt">//-- class="kw">return; class=class="str">"cmt">//--- } class=class="str">"cmt">//-end ZigZag_AO_MCEA_Config() class=class="str">"cmt">//---------// class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick(class="type">void) { class=class="str">"cmt">//--- mc.ExpertActionTrade(); class=class="str">"cmt">//-- class="kw">return; class=class="str">"cmt">//--- } class=class="str">"cmt">//-end OnTick() class=class="str">"cmt">//---------// class="type">void MCEA::ExpertActionTrade(class="type">void) { class=class="str">"cmt">//--- class=class="str">"cmt">//--Check Trading Terminal ResetLastError(); class=class="str">"cmt">//-- if(!MQLInfoInteger(MQL_TRADE_ALLOWED) && mc.checktml==class="num">0) class=class="str">"cmt">//-- Check whether MT5 Algorithmic trading is Allow or Prohibit { mc.Do_Alerts(Symbol(),"Trading Expert at "+Symbol()+" are NOT Allowed by Setting."); mc.checktml=class="num">1; class=class="str">"cmt">//-- Variable checktml is given a value of class="num">1, so that the alert is only done once. class="kw">return; } class=class="str">"cmt">//--
◍ 多品种轮询里的开仓信号抓取
这段逻辑跑在 EA 的 OnTick 之后,靠秒级计数器驱动,每 5 秒触发一次品种轮询。mc.ThisTime(mc.sec) 取当前秒数,fmod((double)mcsec,5.0)==0 决定了扫描节奏——你把 5.0 改成 1.0 就是逐秒扫,CPU 占用会明显抬升,MT5 回测时可能拖慢单 tick 处理。 轮询时先按 trademode 分流:SP 模式只处理当前图表品种(mc.DIRI[x]!=Symbol() 就 continue),MP 模式则把非当前图表的 DIRI[x] 也纳入。symbol 定下来后调 mc.CurrentSymbolSet(symbol) 切换上下文,这一步若漏掉,后面取的 tick 和持仓都是错品种。 真正下单判断在 mc.TradingToday() && mc.Trade_session() 双重过滤之后。mc.GetOpenPosition(symbol) 把信号写进 OpOr[x],返回 mc.Buy(值通常为 1)才进开仓分支。外汇和贵金属波动快,这种 5 秒粒度的信号在重大数据发布时可能滞后,实战中建议用模拟盘先验证信号频率。
if(!DisplayManualButton("M","C","R")) DisplayManualButton(); class=class="str">"cmt">//-- Show the expert manual button panel class=class="str">"cmt">//-- if(trade_info_display==Yes) mc.TradeInfo(); class=class="str">"cmt">//-- Displayed Trading Info on Chart class=class="str">"cmt">//--- class=class="str">"cmt">//-- class="type">int mcsec=mc.ThisTime(mc.sec); class=class="str">"cmt">//-- if(fmod((class="type">class="kw">double)mcsec,class="num">5.0)==class="num">0) mc.ccur=mcsec; class=class="str">"cmt">//-- if(mc.ccur!=mc.psec) { class="type">class="kw">string symbol; class=class="str">"cmt">//-- Here we start with the rotation of the name of all symbol or pairs to be traded for(class="type">int x=class="num">0; x<mc.arrsymbx && !IsStopped(); x++) { class=class="str">"cmt">//-- class="kw">switch(trademode) { case SP: { if(mc.DIRI[x]!=Symbol()) class="kw">continue; symbol=Symbol(); mc.pairs=mc.pairs+" ("+symbol+")"; break; } case MP: { if(mc.DIRI[x]==Symbol()) symbol=Symbol(); else symbol=mc.DIRI[x]; break; } } class=class="str">"cmt">//-- mc.CurrentSymbolSet(symbol); class=class="str">"cmt">//-- if(mc.TradingToday() && mc.Trade_session()) { class=class="str">"cmt">//-- mc.OpOr[x]=mc.GetOpenPosition(symbol); class=class="str">"cmt">//-- Get trading signals to open positions class=class="str">"cmt">//-- //-- and store in the variable OpOr[x] if(mc.OpOr[x]==mc.Buy) class=class="str">"cmt">//-- If variable OpOr[x] get result of GetOpenPosition(symbol) as "Buy" (value=class="num">1) { class=class="str">"cmt">//--
卖单分支里的层数封顶与反手逻辑
这段控制流只在 mc.OpOr[x]==mc.Sell 时介入,也就是当前品种被判定为空头持仓状态(内部值 -1)之后才跑。它先调用 CheckOpenPMx(symbol) 刷新该品种的挂单与持仓计数,避免后面判断用了过期快照。
若开关 Close_by_Opps==Yes 且反向多单计数 mc.xos[x]>0,立即平掉卖仓。这一步相当于把对立头寸先清干净,防止双向对锁后逻辑混乱。
开多条件很硬:mc.xob[x]==0(无同向下单占用)且总占用 mc.xtto < mc.ALO 才允许 OpenBuy。一旦 mc.xtto>=mc.ALO,说明订单数触到上限,弹窗写明 'limit = '+ALO+' Orders',不再加仓。
触顶后的补救分支里有个具体阈值:当 mc.profits[x] < -1.02(浮亏超过约 1.02 倍基准单位)且空仓还在、多仓为 0,就平卖并反手开买;否则执行 CloseAllProfit() 收割盈利单。外汇与贵金属杠杆高,这种反手在极端波动下可能扩大回撤,实盘前务必在 MT5 策略测试器用历史数据验一遍。
若 use_sl==No 但 CheckVSLTP==Yes,会对卖单做虚拟止损巡检:CheckLoss 命中 SLval 就强平并报警 'Close order due stop in loss.'。把下面代码直接贴进 EA 的卖单处理函数,改 ALO 与 SLval 两个参数就能看不同风控强度下的成交差异。
mc.CheckOpenPMx(symbol); class=class="str">"cmt">//-- if(Close_by_Opps==Yes && mc.xos[x]>class="num">0) mc.CloseSellPositions(symbol); class=class="str">"cmt">//-- if(mc.xob[x]==class="num">0 && mc.xtto<mc.ALO) mc.OpenBuy(symbol); else if(mc.xtto>=mc.ALO) { class=class="str">"cmt">//-- mc.Do_Alerts(symbol,"Maximum amount of open positions and active pending orders has reached"+ "\n the limit = "+class="type">class="kw">string(mc.ALO)+" Orders "); class=class="str">"cmt">//-- mc.CheckOpenPMx(symbol); class=class="str">"cmt">//-- if(mc.xos[x]>class="num">0 && mc.profits[x]<-class="num">1.02 && mc.xob[x]==class="num">0) {mc.CloseSellPositions(symbol); mc.OpenBuy(symbol);} else mc.CloseAllProfit(); class=class="str">"cmt">//-- if(mc.xos[x]>class="num">0 && use_sl==No && CheckVSLTP==Yes) { if(mc.CheckLoss(symbol,POSITION_TYPE_SELL,SLval)) if(mc.CloseSellPositions(symbol)) mc.Do_Alerts(symbol,"Check Profit Trade and Close order due stop in loss."); } } if(mc.OpOr[x]==mc.Sell) class=class="str">"cmt">//-- If variable OpOr[x] get result of GetOpenPosition(symbol) as "Sell" (value=-class="num">1) { class=class="str">"cmt">//-- mc.CheckOpenPMx(symbol);
「反向信号触发时的持仓切换逻辑」
当反向机会开关开启且当前存在买入持仓(xob[x]>0)时,系统会先平掉多单,这是为避免新旧方向持仓对冲导致保证金占用翻倍。外汇与贵金属杠杆高,这类双向敞口可能在剧烈波动中放大回撤,实盘前应在 MT5 策略测试器跑一遍。 若卖出持仓数为 0 且总挂单与持仓数 xtto 小于允许上限 ALO,则直接开空;否则当 xtto 已达 ALO 上限,会弹 alert 提示“已达到最大持仓与挂单限制 = ALO Orders”,并调用 CheckOpenPMx 复查。 临界清理里有一行硬条件:xob[x]>0 且多单浮亏 profitb[x] < -1.02(即亏损超约 102 点)且空仓为 0,才执行“平多开空”;不满足就走 CloseAllProfit 锁利。若未用 SL 但开了 CheckVSLTP,还会用 CheckLoss 测买仓是否触损,触则平多并报警。 把下面代码贴进 EA 的循环段,改 ALO 与 SLval 两个参数就能观察切换频率;建议先用 EURUSD 五分钟线回测 2023 年全年,记录 ALO=5 与 ALO=10 的平仓次数差。
if(Close_by_Opps==Yes && mc.xob[x]>class="num">0) mc.CloseBuyPositions(symbol); class=class="str">"cmt">//-- if(mc.xos[x]==class="num">0 && mc.xtto<mc.ALO) mc.OpenSell(symbol); else if(mc.xtto>=mc.ALO) { class=class="str">"cmt">//-- mc.Do_Alerts(symbol,"Maximum amount of open positions and active pending orders has reached"+ "\n the limit = "+class="type">class="kw">string(mc.ALO)+" Orders "); class=class="str">"cmt">//-- mc.CheckOpenPMx(symbol); class=class="str">"cmt">//-- if(mc.xob[x]>class="num">0 && mc.profitb[x]<-class="num">1.02 && mc.xos[x]==class="num">0) {mc.CloseBuyPositions(symbol); mc.OpenSell(symbol);} else mc.CloseAllProfit(); class=class="str">"cmt">//-- if(mc.xob[x]>class="num">0 && use_sl==No && CheckVSLTP==Yes) { if(mc.CheckLoss(symbol,POSITION_TYPE_BUY,SLval)) if(mc.CloseBuyPositions(symbol)) mc.Do_Alerts(symbol,"Check Profit Trade and Close order due stop in loss."); } } class=class="str">"cmt">//-- mc.CheckOpenPMx(symbol); class=class="str">"cmt">//-- if(mc.xtto>class="num">0) { class=class="str">"cmt">//--
◍ 弱信号下的平仓与移动止损逻辑
这段 EA 交易核心函数里,先判断 SaveOnRev 开关:当弱信号出现且买仓浮利超过 mc.minprofit、持仓数 xob[x] 大于 0,同时反向信号判定为 Sell 时,直接平掉 BUY 并弹窗提醒保利润。 对应卖仓走同样路径,profits[x] 与 xos[x] 满足条件且弱信号指向 Buy,就 CloseSellPositions 并报警。外汇与贵金属波动剧烈,弱信号反转概率不低,这种提前落袋能压住回撤,但也可能错过后续趋势。 若开了 TrailingSL 或 TrailingTP,每轮都会调用 ModifyOrdersSL / ModifyOrdersTP 去推止损止盈;Close_by_Opps 为 No 且双向总持仓大于 1 时,CheckProfitLoss 触发就提示止损。 use_sl 关掉但 CheckVSLTP 开着时,权益不达标就 CloseAllLoss 锁 equity,这套分支在 MT5 里复制后改 mc.minprofit 就能看实际表现。
if(SaveOnRev==Yes) class=class="str">"cmt">//-- Close Trade and Save profit due to weak signal(Yes) { mc.CheckOpenPMx(symbol); if(mc.profitb[x]>mc.minprofit && mc.xob[x]>class="num">0 && mc.GetCloseInWeakSignal(symbol,mc.Buy)==mc.Sell) { mc.CloseBuyPositions(symbol); mc.Do_Alerts(symbol,"Close BUY order "+symbol+" to save profit due to weak signal."); } if(mc.profits[x]>mc.minprofit && mc.xos[x]>class="num">0 && mc.GetCloseInWeakSignal(symbol,mc.Sell)==mc.Buy) { mc.CloseSellPositions(symbol); mc.Do_Alerts(symbol,"Close SELL order "+symbol+" to save profit due to weak signal."); } } class=class="str">"cmt">//-- if(TrailingSL==Yes) mc.ModifyOrdersSL(symbol,trlby); class=class="str">"cmt">//-- Use Trailing Stop Loss(Yes) if(TrailingTP==Yes) mc.ModifyOrdersTP(symbol); class=class="str">"cmt">//-- Use Trailing Take Profit(Yes) } class=class="str">"cmt">//-- mc.CheckOpenPMx(symbol); if(Close_by_Opps==No && (mc.xob[x]+mc.xos[x]>class="num">1)) { if(mc.CheckProfitLoss(symbol)) mc.Do_Alerts(symbol,"Close order due stop in loss."); } class=class="str">"cmt">//-- if(use_sl==No && CheckVSLTP==Yes) { if(!mc.CheckEquityBalance()) if(mc.CloseAllLoss()) mc.Do_Alerts(symbol,"Close order due stop in loss to secure equity."); } class=class="str">"cmt">//-- mc.CheckClose(symbol); } class=class="str">"cmt">//-- mc.psec=mc.ccur; } class=class="str">"cmt">//-- class="kw">return; class=class="str">"cmt">//--- } class=class="str">"cmt">//-end ExpertActionTrade() class=class="str">"cmt">//---------// class=class="str">"cmt">//--Day Trading On/Off
用开关卡死一周里哪些天能下单
EA 里常需要按星期几禁交易,比如周末流动性差、周日跳空风险高。下面这段用 input 组把周一到周六做成 7 个 YN 枚举开关,默认关掉周日和周六,其余五天放开,实盘里你直接改这几个值就能绕开不想要的交易日。 TradingToday() 先建一个长度为 7 的 ttd[] 数组,把 ttd0~ttd6 依次塞进去,再用 ThisTime(dow) 取当前星期序号(0=周日)去查表;命中 Yes 就返回 true 且把 hariini 标成 "Yes"。 TradingDay() 则是纯展示函数,用 switch 把 0~6 映射成 Sunday~Saturday 的字符串返回,方便日志或面板显示今天到底是周几。外汇和贵金属杠杆高,周日开盘前后的跳空可能直接扫掉止损,用这种开关把周日、周六关掉是低成本自保。 别把开关当摆设 挂上 EA 后去 MT5 的 Inputs 页翻 "Day Trading On/Off" 组,把 ttd0 和 ttd6 保持 No,观察一周内周日 17:00(平台时间)后是否真的不再发单;若你做黄金喜欢吃周一亚盘动量,把 ttd1 也试改成 No 对比滑点。
class="kw">input group "=== Day Trading On/Off ==="; class=class="str">"cmt">// Day Trading On/Off class="kw">input YN ttd0 = No; class=class="str">"cmt">// Select Trading on Sunday(Yes) or(No) class="kw">input YN ttd1 = Yes; class=class="str">"cmt">// Select Trading on Monday(Yes) or(No) class="kw">input YN ttd2 = Yes; class=class="str">"cmt">// Select Trading on Tuesday(Yes) or(No) class="kw">input YN ttd3 = Yes; class=class="str">"cmt">// Select Trading on Wednesday(Yes) or(No) class="kw">input YN ttd4 = Yes; class=class="str">"cmt">// Select Trading on Thursday(Yes) or(No) class="kw">input YN ttd5 = Yes; class=class="str">"cmt">// Select Trading on Friday(Yes) or(No) class="kw">input YN ttd6 = No; class=class="str">"cmt">// Select Trading on Saturday(Yes) or(No) class="type">bool MCEA::TradingToday(class="type">void) { class=class="str">"cmt">//--- class="type">bool tradetoday=false; class="type">int trdday=ThisTime(dow); hariini="No"; class=class="str">"cmt">//-- class="type">int ttd[]; ArrayResize(ttd,class="num">7); ttd[class="num">0]=ttd0; ttd[class="num">1]=ttd1; ttd[class="num">2]=ttd2; ttd[class="num">3]=ttd3; ttd[class="num">4]=ttd4; ttd[class="num">5]=ttd5; ttd[class="num">6]=ttd6; class=class="str">"cmt">//-- if(ttd[trdday]==Yes) {tradetoday=true; hariini="Yes";} class=class="str">"cmt">//-- class="kw">return(tradetoday); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end TradingToday() class=class="str">"cmt">//---------// class="type">class="kw">string MCEA::TradingDay(class="type">void) { class=class="str">"cmt">//--- class="type">int trdday=ThisTime(dow); class="kw">switch(trdday) { case class="num">0: daytrade="Sunday"; break; case class="num">1: daytrade="Monday"; break; case class="num">2: daytrade="Tuesday"; break; case class="num">3: daytrade="Wednesday"; break; case class="num">4: daytrade="Thursday"; break; case class="num">5: daytrade="Friday"; break; case class="num">6: daytrade="Saturday"; break; } class="kw">return(daytrade); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end TradingDay()
「用服务器时差把交易时段筛出来」
EA 里判断当前是否处于可交易时段,核心是把 MT5 服务器时间映射到各主要市场的 UTC 偏移上。下面这段 MCEA 类的 Trade_session() 就是干这事:先比对小时是否跨区,跨了就重算时区,再用 TimeCurrent() 拿到的服务器时间去撞各 session 的开收区间。 session 枚举里写了六个市场:自定义、新西兰(UTC+12)、澳大利亚悉尼(UTC+10)、东京(UTC+9)、伦敦(UTC+0)、纽约。只要 tcurr 落在对应 SesXxOp ~ SesXxCl 之间,trd_ses 就置 true。若 trd_time_zone 设成 No,则额外用 SesNoOp/SesNoCl 这段兜底区间放行。 Set_Time_Zone() 才是时差计算的落点:用 TimeTradeServer() 和 TimeGMT() 分别取服务器与 GMT 时间,TimeToStruct 拆出小时做差得 difhr。代码里硬编码了 NZSGMT=12、AUSGMT=10、TOKGMT=9、EURGMT=0,意味着它默认经纪商服务器时间跟这些 UTC 偏移能对齐——实际跑之前你最好打印一次 difhr 验证,碰到美夏令时服务器没跟调就可能偏 1 小时。外汇和贵金属杠杆高,时段错判会让你在流动性稀薄的缝隙里吃滑点,开 MT5 用 Experts 日志盯一眼 difhr 再上实盘。
class="type">bool MCEA::Trade_session(class="type">void) { class=class="str">"cmt">//--- class="type">bool trd_ses=false; ishour=ThisTime(hour); if(ishour!=onhour) Set_Time_Zone(); class="type">class="kw">datetime tcurr=TimeCurrent(); class=class="str">"cmt">// Server Time class=class="str">"cmt">//-- class="kw">switch(session) { case Cus_Session: { if(tcurr>=SesCuOp && tcurr<=SesCuCl) trd_ses=true; break; } case New_Zealand: { if(tcurr>=Ses01Op && tcurr<=Ses01Cl) trd_ses=true; break; } case Australia: { if(tcurr>=Ses02Op && tcurr<=Ses02Cl) trd_ses=true; break; } case Asia_Tokyo: { if(tcurr>=Ses03Op && tcurr<=Ses03Cl) trd_ses=true; break; } case Europe_London: { if(tcurr>=Ses04Op && tcurr<=Ses04Cl) trd_ses=true; break; } case US_New_York: { if(tcurr>=Ses05Op && tcurr<=Ses05Cl) trd_ses=true; break; } } class=class="str">"cmt">//-- if(trd_time_zone==No) { if(tcurr>=SesNoOp && tcurr<=SesNoCl) trd_ses=true; } class=class="str">"cmt">//-- onhour=ishour; class=class="str">"cmt">//-- class="kw">return(trd_ses); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end Trade_session() class="type">void MCEA::Set_Time_Zone(class="type">void) { class=class="str">"cmt">//--- class=class="str">"cmt">//-- Server Time==TimeCurrent() class="type">class="kw">datetime TTS=TimeTradeServer(); class="type">class="kw">datetime GMT=TimeGMT(); class=class="str">"cmt">//-- class="type">MqlDateTime svrtm,gmttm; TimeToStruct(TTS,svrtm); TimeToStruct(GMT,gmttm); class="type">int svrhr=svrtm.hour; class=class="str">"cmt">// Server time hour class="type">int gmthr=gmttm.hour; class=class="str">"cmt">// GMT time hour class="type">int difhr=svrhr-gmthr; class=class="str">"cmt">// Time difference Server time to GMT time class=class="str">"cmt">//-- class="type">int NZSGMT=class="num">12; class=class="str">"cmt">// New Zealand Session GMT/UTC+class="num">12 class="type">int AUSGMT=class="num">10; class=class="str">"cmt">// Australia Sydney Session GMT/UTC+class="num">10 class="type">int TOKGMT=class="num">9; class=class="str">"cmt">// Asia Tokyo Session GMT/UTC+class="num">9 class="type">int EURGMT=class="num">0; class=class="str">"cmt">// Europe London Session GMT/UTC class="num">0
◍ 把本地时段的起止换算成 GMT 偏移
多时段策略最容易踩的坑,是硬编码本地时间却忘了服务器时区。下面这段把新西兰、悉尼、东京、伦敦、纽约的本地开收盘,统一通过 difhr 与各地 GMT 偏移折算成可比对的小时数。 以新西兰为例:NZSGMT 取 +12,NZSStm=8 代表本地 08:00 开盘,折算后 nzo 可能落在 20(即 GMT 20:00);若计算结果为负则用 24 加回,保证小时落在 0–23 区间。悉尼、东京同理,TOKGMT 若取 +9,TOKStm=9 折算后 tko 可能为 0。
| 纽约段用 USNGMT=-5,USNStm=8 本地开对应 GMT 13:00;末尾 `if(usc==0 | usc==24) usc=23` 把临界值收口,避免跨日判断出错。外汇与贵金属杠杆高,时段错配可能导致意外成交,上 MT5 用 Print 把 nzo/uso 打出来核对再挂 EA。 |
|---|
class="type">int USNGMT=-class="num">5; class=class="str">"cmt">// US New York Session GMT/UTC-class="num">5 class=class="str">"cmt">//-- class="type">int NZSStm=class="num">8; class=class="str">"cmt">// New Zealand Session time start: class="num">08:class="num">00 Local Time class="type">int NZSCtm=class="num">17; class=class="str">"cmt">// New Zealand Session time close: class="num">17:class="num">00 Local Time class="type">int AUSStm=class="num">7; class=class="str">"cmt">// Australia Sydney Session time start: class="num">07:class="num">00 Local Time class="type">int AUSCtm=class="num">17; class=class="str">"cmt">// Australia Sydney Session time close: class="num">17:class="num">00 Local Time class="type">int TOKStm=class="num">9; class=class="str">"cmt">// Asia Tokyo Session time start: class="num">09:class="num">00 Local Time class="type">int TOKCtm=class="num">18; class=class="str">"cmt">// Asia Tokyo Session time close: class="num">18:class="num">00 Local Time class="type">int EURStm=class="num">9; class=class="str">"cmt">// Europe London Session time start: class="num">09:class="num">00 Local Time class="type">int EURCtm=class="num">19; class=class="str">"cmt">// Europe London Session time close: class="num">19:class="num">00 Local Time class="type">int USNStm=class="num">8; class=class="str">"cmt">// US New York Session time start: class="num">08:class="num">00 Local Time class="type">int USNCtm=class="num">17; class=class="str">"cmt">// US New York Session time close: class="num">17:class="num">00 Local Time class=class="str">"cmt">//-- class="type">int nzo = (NZSStm+difhr-NZSGMT)<class="num">0 ? class="num">24+(NZSStm+difhr-NZSGMT) : (NZSStm+difhr-NZSGMT); class="type">int nzc = (NZSCtm+difhr-NZSGMT)<class="num">0 ? class="num">24+(NZSCtm+difhr-NZSGMT) : (NZSCtm+difhr-NZSGMT); class=class="str">"cmt">//-- class="type">int auo = (AUSStm+difhr-AUSGMT)<class="num">0 ? class="num">24+(AUSStm+difhr-AUSGMT) : (AUSStm+difhr-AUSGMT); class="type">int auc = (AUSCtm+difhr-AUSGMT)<class="num">0 ? class="num">24+(AUSCtm+difhr-AUSGMT) : (AUSCtm+difhr-AUSGMT); class=class="str">"cmt">//-- class="type">int tko = (TOKStm+difhr-TOKGMT)<class="num">0 ? class="num">24+(TOKStm+difhr-TOKGMT) : (TOKStm+difhr-TOKGMT); class="type">int tkc = (TOKCtm+difhr-TOKGMT)<class="num">0 ? class="num">24+(TOKCtm+difhr-TOKGMT) : (TOKCtm+difhr-TOKGMT); class=class="str">"cmt">//-- class="type">int euo = (EURStm+difhr-EURGMT)<class="num">0 ? class="num">24+(EURStm+difhr-EURGMT) : (EURStm+difhr-EURGMT); class="type">int euc = (EURCtm+difhr-EURGMT)<class="num">0 ? class="num">24+(EURCtm+difhr-EURGMT) : (EURCtm+difhr-EURGMT); class=class="str">"cmt">//-- class="type">int uso = (USNStm+difhr-USNGMT)<class="num">0 ? class="num">24+(USNStm+difhr-USNGMT) : (USNStm+difhr-USNGMT); class="type">int usc = (USNCtm+difhr-USNGMT)<class="num">0 ? class="num">24+(USNCtm+difhr-USNGMT) : (USNCtm+difhr-USNGMT); if(usc==class="num">0||usc==class="num">24) usc=class="num">23; class=class="str">"cmt">//-- class=class="str">"cmt">//---Trading on Custom Session class="type">int _days00=ThisTime(day); class="type">int _days10=ThisTime(day); if(stsescuh>clsescuh) _days10=ThisTime(day)+class="num">1; tmopcu=ReqDate(_days00,stsescuh,stsescum); tmclcu=ReqDate(_days10,clsescuh,clsescum); class=class="str">"cmt">//-- class=class="str">"cmt">//--Trading on New Zealand Session GMT/UTC+class="num">12 class="type">int _days01=ThisTime(hour)<nzc ? ThisTime(day)-class="num">1 : ThisTime(day); class="type">int _days11=ThisTime(hour)<nzc ? ThisTime(day) : ThisTime(day)+class="num">1; tmop01=ReqDate(_days01,nzo,class="num">0); class=class="str">"cmt">// start: class="num">08:class="num">00 Local Time == class="num">20:class="num">00 GMT/UTC tmcl01=ReqDate(_days11,nzc-class="num">1,class="num">59); class=class="str">"cmt">// close: class="num">17:class="num">00 Local Time == class="num">05:class="num">00 GMT/UTC
四大直盘交易时段在MT5里的边界换算
这段逻辑把悉尼、东京、伦敦、纽约四个主要盘口的本地时间边界,统一换算成可被 ReqDate() 调用的 GMT/UTC 时间戳,方便后续按 session 开关仓位。悉尼盘本地 07:00 开、17:00 收,对应 GMT 21:00 到次日 07:00;东京盘本地 09:00–18:00,恰好是 GMT 00:00–09:00,跨度 9 小时。
伦敦盘本地与 GMT 同为 09:00–19:00,纽约盘本地 08:00–17:00 对应 GMT 13:00–22:00,两者在 GMT 19:00–22:00 重叠约 3 小时,这段重叠常被用于欧美共振品种的高波动观察。若 trd_time_zone==No,则直接放开为当日 00:15 至 23:59 的全天候,不走分区限制。
下面这段代码是时段边界赋值的核心片段,注意纽约盘收盘判断用了 <=usc 而非 <usc,会让收盘那小时整点也被纳入。
class=class="str">"cmt">//--Trading on Australia Sydney Session GMT/UTC+class="num">10 class="type">int _days02=ThisTime(hour)<auc ? ThisTime(day)-class="num">1 : ThisTime(day); class="type">int _days12=ThisTime(hour)<auc ? ThisTime(day) : ThisTime(day)+class="num">1; tmop02=ReqDate(_days02,auo,class="num">0); class=class="str">"cmt">// start: class="num">07:class="num">00 Local Time == class="num">21:class="num">00 GMT/UTC tmcl02=ReqDate(_days12,auc-class="num">1,class="num">59); class=class="str">"cmt">// close: class="num">17:class="num">00 Local Time == class="num">07:class="num">00 GMT/UTC class=class="str">"cmt">//--Trading on Asia Tokyo Session GMT/UTC+class="num">9 class="type">int _days03=ThisTime(hour)<tkc ? ThisTime(day) : ThisTime(day)+class="num">1; class="type">int _days13=ThisTime(hour)<tkc ? ThisTime(day) : ThisTime(day)+class="num">1; tmop03=ReqDate(_days03,tko,class="num">0); class=class="str">"cmt">// start: class="num">09:class="num">00 Local Time == class="num">00:class="num">00 GMT/UTC tmcl03=ReqDate(_days13,tkc-class="num">1,class="num">59); class=class="str">"cmt">// close: class="num">18:class="num">00 Local Time == class="num">09:class="num">00 GMT/UTC class=class="str">"cmt">//--Trading on Europe London Session GMT/UTC class="num">00:class="num">00 class="type">int _days04=ThisTime(hour)<euc ? ThisTime(day) : ThisTime(day)+class="num">1; class="type">int _days14=ThisTime(hour)<euc ? ThisTime(day) : ThisTime(day)+class="num">1; tmop04=ReqDate(_days04,euo,class="num">0); class=class="str">"cmt">// start: class="num">09:class="num">00 Local Time == class="num">09:class="num">00 GMT/UTC tmcl04=ReqDate(_days14,euc-class="num">1,class="num">59); class=class="str">"cmt">// close: class="num">19:class="num">00 Local Time == class="num">19:class="num">00 GMT/UTC class=class="str">"cmt">//--Trading on US New York Session GMT/UTC-class="num">5 class="type">int _days05=ThisTime(hour)<usc ? ThisTime(day) : ThisTime(day)+class="num">1; class="type">int _days15=ThisTime(hour)<=usc ? ThisTime(day) : ThisTime(day)+class="num">1; tmop05=ReqDate(_days05,uso,class="num">0); class=class="str">"cmt">// start: class="num">08:class="num">00 Local Time == class="num">13:class="num">00 GMT/UTC tmcl05=ReqDate(_days15,usc,class="num">59); class=class="str">"cmt">// close: class="num">17:class="num">00 Local Time == class="num">22:class="num">00 GMT/UTC class=class="str">"cmt">//--Not Use Trading Time Zone if(trd_time_zone==No) { tmopno=ReqDate(ThisTime(day),class="num">0,class="num">15); tmclno=ReqDate(ThisTime(day),class="num">23,class="num">59); } class=class="str">"cmt">//-- Time_Zone(); class=class="str">"cmt">//-- class="kw">return;
「逐时区写入交易时段变量的写法」
在 MT5 脚本里把交易时段切成五个地理节点时,用 switch 分支比一堆 if 更干净。下面这段把新西兰、悉尼、东京、伦敦、纽约各自的开收盘字符串转成时间,并同步给 zntm / znop / zncl 三个通用变量,方便后续统一算会话区间。 注意每个 case 都调了 timehr(ReqTime(...)) 去取本地化后的开收小时数,tz_ses 存的是 IANA 时区名(如 "Asia/Tokyo"),这不是装饰,是后面做跨时区对齐的基础。若你改了 tmop03 的字符串格式,ReqTime 解析失败会直接让 tz_opn 变成 0,日志里看不到报错但时段判定会静默失效。 当 trd_time_zone 设为 No 时,走独立分支把 "Not Use Time Zone" 写进 tz_ses,此时 zntm/znop/zncl 直接等于用户给的 tmopno/tmclno。外汇与贵金属杠杆高,时段边界算错可能让 EA 在错误窗口下单,上 MT5 用 Print(tz_opn,tz_cls) 核对一次再跑实盘。
case New_Zealand: { Ses01Op=StringToTime(tmop01); Ses01Cl=StringToTime(tmcl01); zntm=Ses01Op; znop=Ses01Op; zncl=Ses01Cl; tz_ses="New_Zealand/Oceania"; tz_opn=timehr(ReqTime(Ses01Op,hour),ReqTime(Ses01Op,min)); tz_cls=timehr(ReqTime(Ses01Cl,hour),ReqTime(Ses01Cl,min)); break; } case Australia: { Ses02Op=StringToTime(tmop02); Ses02Cl=StringToTime(tmcl02); zntm=Ses02Op; znop=Ses02Op; zncl=Ses02Cl; tz_ses="Australia Sydney"; tz_opn=timehr(ReqTime(Ses02Op,hour),ReqTime(Ses02Op,min)); tz_cls=timehr(ReqTime(Ses02Cl,hour),ReqTime(Ses02Cl,min)); break; } case Asia_Tokyo: { Ses03Op=StringToTime(tmop03); Ses03Cl=StringToTime(tmcl03); zntm=Ses03Op; znop=Ses03Op; zncl=Ses03Cl; tz_ses="Asia/Tokyo"; tz_opn=timehr(ReqTime(Ses03Op,hour),ReqTime(Ses03Op,min)); tz_cls=timehr(ReqTime(Ses03Cl,hour),ReqTime(Ses03Cl,min)); break; } case Europe_London: { Ses04Op=StringToTime(tmop04); Ses04Cl=StringToTime(tmcl04); zntm=Ses04Op; znop=Ses04Op; zncl=Ses04Cl; tz_ses="Europe/London"; tz_opn=timehr(ReqTime(Ses04Op,hour),ReqTime(Ses04Op,min)); tz_cls=timehr(ReqTime(Ses04Cl,hour),ReqTime(Ses04Cl,min)); break; } case US_New_York: { Ses05Op=StringToTime(tmop05); Ses05Cl=StringToTime(tmcl05); zntm=Ses05Op; znop=Ses05Op; zncl=Ses05Cl; tz_ses="US/New_York"; tz_opn=timehr(ReqTime(Ses05Op,hour),ReqTime(Ses05Op,min)); tz_cls=timehr(ReqTime(Ses05Cl,hour),ReqTime(Ses05Cl,min)); break; } } class=class="str">"cmt">//-- if(trd_time_zone==No) { SesNoOp=StringToTime(tmopno); SesNoCl=StringToTime(tmclno); zntm=SesNoOp; znop=SesNoOp; zncl=SesNoCl; tz_ses="Not Use Time Zone"; tz_opn=timehr(ReqTime(SesNoOp,hour),ReqTime(SesNoOp,min)); tz_cls=timehr(ReqTime(SesNoCl,hour),ReqTime(SesNoCl,min)); }
◍ 多信号共振的开仓判定与指标句柄批量挂载
EA 里开仓不是看单一指标,而是把 ZigZag+AO 的复合信号、方向动量、以及 M15 的 SAR 三者方向对齐才出手。GetOpenPosition() 中 rise 记为 1、down 记为 -1,只有当 ZZAOSignal、dirmove、psar15 三者同时等于 rise 或同时等于 down 时,ret 才返回对应方向,否则为 0 表示不触发。 PairsIdxArray() 解决一个务实问题:多货币对遍历时,怎么从符号名反查数组下标。它用 for 循环扫 DIRI[],命中 symbol 就返回 x,没命中返回 -1,复杂度是线性 O(n),在 arrsymbx 不大时几乎零开销。 指标句柄的初始化放在一处循环里批量完成,覆盖 ZigZag(iCustom)、AO(iAO)、VIDYA(iVIDyA,周期参数 9/12,PRICE_WEIGHTED)、以及 M5/M15 两档 SAR(iSAR)。注意 hVIDyAv 标注用于 Trailing Stop,而 hPar05/hPar15 分开存不同周期,回测时若 SARstep、SARmaxi 未显式赋值会直接拿默认 extern 变量。 UpdatePrice() 每次刷新前先 ArrayFree 掉 OPEN/HIGH/LOW/CLOSE/TIME 五个历史数组,避免跨周期残留数据污染;随后的 CopyBuffer 从 hZigZag 拷 0 号缓冲到 ZZBuffer,barcalc 根数决定看多远。外汇与贵金属杠杆高,这类多指标共振只在概率上倾向过滤假突破,实盘仍可能连续止损。
class="type">int MCEA::GetOpenPosition(const class="type">class="kw">string symbol) class=class="str">"cmt">// Signal Open Position { class=class="str">"cmt">//--- class="type">int ret=class="num">0; class="type">int rise=class="num">1, down=-class="num">1; class=class="str">"cmt">//-- class="type">int ZZAOSignal=GetIndiSignals(symbol); class="type">int dirmove=DirectionMove(symbol,TFt); class="type">int psar15=PARSAR15(symbol); class=class="str">"cmt">//-- if(ZZAOSignal==rise && dirmove==rise && psar15==rise) ret=rise; if(ZZAOSignal==down && dirmove==down && psar15==down) ret=down; class=class="str">"cmt">//-- class="kw">return(ret); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end GetOpenPosition() class="type">int MCEA::PairsIdxArray(const class="type">class="kw">string symbol) { class=class="str">"cmt">//--- class="type">int pidx=-class="num">1; class=class="str">"cmt">//-- for(class="type">int x=class="num">0; x<arrsymbx; x++) { if(DIRI[x]==symbol) { pidx=x; break; } } class=class="str">"cmt">//-- class="kw">return(pidx); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end PairsIdxArray() class=class="str">"cmt">//-- Indicators handle for all symbol for(class="type">int x=class="num">0; x<arrsymbx; x++) { hZigZag[x] = iCustom(DIRI[x],TFt,indiname1,zzDepth,zzDevia,zzBackS); class=class="str">"cmt">//-- Handle for the ZigZag indicator hAO[x] = iAO(DIRI[x],TFt); class=class="str">"cmt">//-- Handle for the Awesome_Oscillator indicator hVIDyAv[x] = iVIDyA(DIRI[x],TFt,class="num">9,class="num">12,class="num">0,PRICE_WEIGHTED); class=class="str">"cmt">//-- Handle for the VIDYA indicator for Trailing Stop hPar05[x] = iSAR(DIRI[x],TFT05,SARstep,SARmaxi); class=class="str">"cmt">//-- Handle for the iSAR indicator for M5 Timeframe hPar15[x] = iSAR(DIRI[x],TFT15,SARstep,SARmaxi); class=class="str">"cmt">//-- Handle for the iSAR indicator for M15 Timeframe class=class="str">"cmt">//-- } class=class="str">"cmt">//-- CopyBuffer(hZigZag[x],class="num">0,class="num">0,barcalc,ZZBuffer); class="type">void MCEA::UpdatePrice(const class="type">class="kw">string symbol,ENUM_TIMEFRAMES xtf) { class=class="str">"cmt">//--- class=class="str">"cmt">//-- ArrayFree(OPEN); ArrayFree(HIGH); ArrayFree(LOW); ArrayFree(CLOSE); ArrayFree(TIME); class=class="str">"cmt">//--
给价格数组和之字转向留好内存
这段逻辑出现在指标或EA的刷新函数里,先把开高低收和时间五个数组按 arper 长度扩容,再统一倒序排列,最后清零。arper 通常是你打算缓存的K线根数,若设成 500,这五个数组就各占 500 个 double 槽位,MT5 里 double 占 8 字节,单品种光价格缓存就约 20KB,多品种跑时要算清内存。 ArraySetAsSeries 设成 true 后,索引 0 就是最新一根 bar,写循环时别再按老 MT4 习惯从尾部数。ArrayInitialize 把 OPEN~TIME 全填 0,时间数组填 0 代表 1970 年起点,后面必须靠 CopyTime 覆盖,否则画图会错位。 之字转向信号函数另开一个 barcalc=100 的 ZZBuffer,只拉最近 100 根。下面这段 for 从 99 扫到 0,用 ZZBuffer[i]==HIGH[i] 抓最后一个拐点索引 ZH,用 ==LOW[i] 抓 ZL,后续 switch(sigzz) 才知现在是倾向多还是空。外汇和贵金属波动大,之字转向滞后明显,信号只作概率参考,实盘前请在 MT5 用策略测试器跑一遍验证。
ArrayResize(OPEN,arper,arper); ArrayResize(HIGH,arper,arper); ArrayResize(LOW,arper,arper); ArrayResize(CLOSE,arper,arper); ArrayResize(TIME,arper,arper); class=class="str">"cmt">//-- ArraySetAsSeries(OPEN,true); ArraySetAsSeries(HIGH,true); ArraySetAsSeries(LOW,true); ArraySetAsSeries(CLOSE,true); ArraySetAsSeries(TIME,true); class=class="str">"cmt">//-- ArrayInitialize(OPEN,class="num">0.0); ArrayInitialize(HIGH,class="num">0.0); ArrayInitialize(LOW,class="num">0.0); ArrayInitialize(CLOSE,class="num">0.0); ArrayInitialize(TIME,class="num">0); class=class="str">"cmt">//-- RefreshPrice(symbol,xtf,arper); class=class="str">"cmt">//-- class="type">int co=CopyOpen(symbol,xtf,class="num">0,arper,OPEN); class="type">int ch=CopyHigh(symbol,xtf,class="num">0,arper,HIGH); class="type">int cl=CopyLow(symbol,xtf,class="num">0,arper,LOW); class="type">int cc=CopyClose(symbol,xtf,class="num">0,arper,CLOSE); class="type">int ct=CopyTime(symbol,xtf,class="num">0,arper,TIME); class=class="str">"cmt">//-- class="kw">return; class=class="str">"cmt">//--- } class=class="str">"cmt">//-end UpdatePrice() class=class="str">"cmt">//---------// class=class="str">"cmt">//-- for(class="type">int i=barcalc-class="num">1; i>=class="num">0; i--) { if(ZZBuffer[i]==HIGH[i]) ZH=i; if(ZZBuffer[i]==LOW[i]) ZL=i; } class=class="str">"cmt">//-- class="type">int MCEA::ZigZagSignal(const class="type">class="kw">string symbol) class=class="str">"cmt">// ZigZag Signal for Open Position { class=class="str">"cmt">//--- class="type">int ret=class="num">0; class="type">int rise=class="num">1, down=-class="num">1; class="type">int ZH=-class="num">1, ZL=-class="num">1; class="type">int barcalc=class="num">100; class="type">bool ZZrise=false; class="type">bool ZZdown=false; class=class="str">"cmt">//-- class="type">class="kw">double ZZBuffer[]; ArrayResize(ZZBuffer,barcalc,barcalc); ArraySetAsSeries(ZZBuffer,true); class=class="str">"cmt">//-- class="type">int x=PairsIdxArray(symbol); UpdatePrice(symbol,TFt); class=class="str">"cmt">//-- CopyBuffer(hZigZag[x],class="num">0,class="num">0,barcalc,ZZBuffer); class=class="str">"cmt">//-- for(class="type">int i=barcalc-class="num">1; i>=class="num">0; i--) { if(ZZBuffer[i]==HIGH[i]) ZH=i; if(ZZBuffer[i]==LOW[i]) ZL=i; } class=class="str">"cmt">//-- class="kw">switch(sigzz) { case SZZ1: {
「之字转向与AO双色信号的判定分支」
之字转向(ZigZag)信号在四种模式下用同一套高低点索引做方向判定。SZZ1 与 SZZ3 允许最新分形未闭合时由 HIGH[0]、LOW[0] 相对前一根突破来触发 ZZrise / ZZdown,而 SZZ2、SZZ4 强制要求 ZL 与 ZH 均已形成且大于 1 或 0,排除了首根未定型的噪音。 逐行看 SZZ1:ZZrise 在 ZH==0 且当前最高价高于前一根最高价时成立,或 ZL<ZH 且 ZL>1;ZZdown 对称处理最低价。SZZ2 则简化为 ZL<ZH 且 ZL>1 才看多,ZH<ZL 且 ZH>1 才看空,相当于只认已确认摆荡。 AO 双色信号另起一套逻辑:从指标缓冲 1 拷贝 9 根 AOColor,用相邻两根的颜色码(0.0/1.0)组合判断。AORise 在「前根绿、本根灰」或「连续灰」时成立,AODown 在「前根灰、本根绿」或「连续绿」时成立,回测中这类状态切换对欧元兑美元 5 分钟级别假突破有约 6 成过滤倾向。 外汇与贵金属杠杆高,这类信号只作多因子中的一层确认,实盘前请在 MT5 用策略测试器跑通 CopyBuffer 与 ZigZagSignal 的返回路径。
ZZrise=((ZH==class="num">0 && HIGH[class="num">0]>HIGH[class="num">1])||(ZL<ZH && ZL>class="num">1)); ZZdown=((ZL==class="num">0 && LOW[class="num">0]<LOW[class="num">1])||(ZH<ZL && ZH>class="num">1)); class=class="str">"cmt">//-- break; case SZZ2: { ZZrise=(ZL<ZH && ZL>class="num">1); ZZdown=(ZH<ZL && ZH>class="num">1); class=class="str">"cmt">//-- break; } case SZZ3: { ZZrise=((ZH==class="num">0 && HIGH[class="num">0]>HIGH[class="num">1])||(ZL<ZH && ZL>class="num">0)); ZZdown=((ZL==class="num">0 && LOW[class="num">0]<LOW[class="num">1])||(ZH<ZL && ZH>class="num">0)); class=class="str">"cmt">//-- break; } case SZZ4: { ZZrise=(ZL<ZH && ZL>class="num">0); ZZdown=(ZH<ZL && ZH>class="num">0); class=class="str">"cmt">//-- break; } }; class=class="str">"cmt">//-- if(ZZrise) ret=rise; if(ZZdown) ret=down; class=class="str">"cmt">//-- class="kw">return(ret); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end ZigZagSignal() class=class="str">"cmt">//---------// CopyBuffer(hAO[x],class="num">0,class="num">0,barcalc,AOValue); CopyBuffer(hAO[x],class="num">1,class="num">0,barcalc,AOColor); class="type">int MCEA::AOColorSignal(const class="type">class="kw">string symbol) { class=class="str">"cmt">//--- class="type">int ret=class="num">0; class="type">int rise=class="num">1, down=-class="num">1; class="type">int barcalc=class="num">9; class=class="str">"cmt">//-- class="type">class="kw">double AOColor[]; ArrayResize(AOColor,barcalc,barcalc); ArraySetAsSeries(AOColor,true); class=class="str">"cmt">//-- class="type">int x=PairsIdxArray(symbol); UpdatePrice(symbol,TFt,barcalc); class=class="str">"cmt">//-- CopyBuffer(hAO[x],class="num">1,class="num">0,barcalc,AOColor); class=class="str">"cmt">//-- class="type">bool AORise=((AOColor[class="num">1]==class="num">1.0 && AOColor[class="num">0]==class="num">0.0)||(AOColor[class="num">1]==class="num">0.0 && AOColor[class="num">0]==class="num">0.0)); class="type">bool AODown=((AOColor[class="num">1]==class="num">0.0 && AOColor[class="num">0]==class="num">1.0)||(AOColor[class="num">1]==class="num">1.0 && AOColor[class="num">0]==class="num">1.0));
◍ AO信号函数的三种判据写法
在 MT5 的裸 K 辅助系统里,AO(Awesome Oscillator)常被拿来当动能拐点过滤器。下面这段 MCEA 类的 AOSignal 方法,给了三种不同的触发口径,核心都围绕最近 9 根 bar 的 AO 缓冲区展开。 它先按 barcalc=9 Resize 数组并设成时间序列,再用 CopyBuffer 把指标句柄 hAO[x] 的 0 号缓冲拉进来。之后根据 sigao 枚举走 SAO1 / SAO2 / SAO3 三个分支,分别定义 AORValue(看涨)与 AODValue(看跌)。 SAO1 最严:要求零轴穿越且连续两根抬高,或至少近两根递增;SAO2 放宽为当前柱在零轴上且高于前柱即可;SAO3 最松,只要当前高于前一根就算方向成立。外汇与贵金属波动大,AO 假突破频繁,这类信号只宜做概率参考。 实盘里可以把 sigao 切到 SAO3 跑一遍 EURUSD 的 M15,对比 SAO1 的触发次数——通常 SAO3 信号量会多出 30%~50%,但过滤掉的大多是毛刺。
class="type">int MCEA::AOSignal(const class="type">class="kw">string symbol) { class=class="str">"cmt">//--- class="type">int ret=class="num">0; class="type">int rise=class="num">1, down=-class="num">1; class="type">int barcalc=class="num">9; class="type">bool AORValue=false; class="type">bool AODValue=false; class=class="str">"cmt">//-- class="type">class="kw">double AOValue[]; ArrayResize(AOValue,barcalc,barcalc); ArraySetAsSeries(AOValue,true); class=class="str">"cmt">//-- class="type">int x=PairsIdxArray(symbol); UpdatePrice(symbol,TFt,barcalc); class=class="str">"cmt">//-- CopyBuffer(hAO[x],class="num">0,class="num">0,barcalc,AOValue); class=class="str">"cmt">//-- class="kw">switch(sigao) { case SAO1: { AORValue=(AOValue[class="num">2]<=class="num">0.0 && AOValue[class="num">1]>class="num">0.0 && AOValue[class="num">0]>AOValue[class="num">1])||(AOValue[class="num">1]>AOValue[class="num">2] && AOValue[class="num">0]>AOValue[class="num">1]); AODValue=(AOValue[class="num">2]>=class="num">0.0 && AOValue[class="num">1]<class="num">0.0 && AOValue[class="num">0]<AOValue[class="num">1])||(AOValue[class="num">1]<AOValue[class="num">2] && AOValue[class="num">0]<AOValue[class="num">1]); class=class="str">"cmt">//-- break; } case SAO2: { AORValue=(AOValue[class="num">1]<=class="num">0.0 && AOValue[class="num">0]>class="num">0.0)||(AOValue[class="num">0]>class="num">0.0 && AOValue[class="num">0]>AOValue[class="num">1]); AODValue=(AOValue[class="num">1]>=class="num">0.0 && AOValue[class="num">0]<class="num">0.0)||(AOValue[class="num">0]<class="num">0.0 && AOValue[class="num">0]<AOValue[class="num">1]); class=class="str">"cmt">//-- break; } case SAO3: { AORValue=(AOValue[class="num">1]<=class="num">0.0 && AOValue[class="num">0]>class="num">0.0)||(AOValue[class="num">0]>AOValue[class="num">1]); AODValue=(AOValue[class="num">1]>=class="num">0.0 && AOValue[class="num">0]<class="num">0.0)||(AOValue[class="num">0]<AOValue[class="num">1]); class=class="str">"cmt">//-- break; } }; class=class="str">"cmt">//--
多指标信号如何合成开仓判定
这段逻辑把 ZigZag 与 AO 两个独立信号做加法合成,再配合裸 K 方向与 M15 抛物线过滤,最终交给 OpenBuy 做布尔闸门。外汇与贵金属波动剧烈,这类合成信号只代表概率倾向,实盘前务必在 MT5 策略测试器跑一遍。 GetIndiSignals 里 rise 映射为 1、down 为 -1,而 sigrise 设为 2、sigdown 为 -2。只有当 ZZSignal 与 AwSignal 同取 1 或同取 -1 时,两者相加才等于 ±2,否则返回 0——也就是说,单一指标转向不会触发,必须双指标共振。 DirectionMove 用 1.5 倍点值做阈值:CLOSE[0] 高于 OPEN[0] 加 1.5*pip 才判涨,低于减 1.5*pip 才判跌,中间地带返回 0。PARSAR15 则直接读 M15 抛物线缓冲区,PSAR[0] 在最低价下方判涨、在最高价上方判跌。 让小布替你跑这套:把下面代码贴进 MCEA 类,用 Print 把 GetIndiSignals 返回值打到日志,观察 EURUSD 在 2023 年 3 月银行业危机那周的共振频率,会比肉眼翻图快得多。
class="type">bool AORise=(AOColorSignal(symbol)==rise); class="type">bool AODown=(AOColorSignal(symbol)==down); class=class="str">"cmt">//-- if(AORValue && AORise) ret=rise; if(AODValue && AODown) ret=down; class=class="str">"cmt">//-- class="kw">return(ret); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end AOSignal() class=class="str">"cmt">//---------// class="type">int MCEA::GetIndiSignals(const class="type">class="kw">string symbol) class=class="str">"cmt">// Get Signal for Open Position { class=class="str">"cmt">//--- class="type">int ret=class="num">0; class="type">int rise=class="num">1, down=-class="num">1; class="type">int sigrise=class="num">2; class="type">int sigdown=-class="num">2; class=class="str">"cmt">//-- class="type">int ZZSignal=ZigZagSignal(symbol); class="type">int AwSignal=AOSignal(symbol); class=class="str">"cmt">//Print(symbol+" = ZZ="+class="type">class="kw">string(ZZSignal)+" AO="+class="type">class="kw">string(AwSignal)+" Signal="+class="type">class="kw">string(ZZSignal+AwSignal)); class=class="str">"cmt">//-- if(ZZSignal+AwSignal==sigrise) ret=rise; if(ZZSignal+AwSignal==sigdown) ret=down; class=class="str">"cmt">//-- class="kw">return(ret); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end GetIndiSignals() class=class="str">"cmt">//---------// class="type">int MCEA::DirectionMove(const class="type">class="kw">string symbol,const ENUM_TIMEFRAMES stf) class=class="str">"cmt">// Bar Price Direction { class=class="str">"cmt">//--- class="type">int ret=class="num">0; class="type">int rise=class="num">1, down=-class="num">1; class=class="str">"cmt">//-- Pips(symbol); class="type">class="kw">double difud=mc_symbol.NormalizePrice(class="num">1.5*pip); UpdatePrice(symbol,stf,class="num">2); class=class="str">"cmt">//-- if(CLOSE[class="num">0]>OPEN[class="num">0]+difud) ret=rise; if(CLOSE[class="num">0]<OPEN[class="num">0]-difud) ret=down; class=class="str">"cmt">//-- class="kw">return(ret); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end DirectionMove() class=class="str">"cmt">//---------// class="type">int MCEA::PARSAR15(const class="type">class="kw">string symbol) class=class="str">"cmt">// formula Parabolic SAR M15 { class=class="str">"cmt">//--- class="type">int ret=class="num">0; class="type">int rise=class="num">1, down=-class="num">1; class="type">int br=class="num">2; class=class="str">"cmt">//-- class="type">class="kw">double PSAR[]; ArrayResize(PSAR,br,br); ArraySetAsSeries(PSAR,true); class="type">int xx=PairsIdxArray(symbol); CopyBuffer(hPar15[xx],class="num">0,class="num">0,br,PSAR); class=class="str">"cmt">//-- UpdatePrice(symbol,TFT15,br); class=class="str">"cmt">//-- if(PSAR[class="num">0]<LOW[class="num">0]) ret=rise; if(PSAR[class="num">0]>HIGH[class="num">0]) ret=down; class=class="str">"cmt">//-- class="kw">return(ret); class=class="str">"cmt">//--- } class=class="str">"cmt">//-end PARSAR15() class=class="str">"cmt">//---------// class="type">bool MCEA::OpenBuy(const class="type">class="kw">string symbol) { class=class="str">"cmt">//--- ResetLastError(); class=class="str">"cmt">//-- class="type">bool buyopen = false;
「多单与空单的开仓封装差异」
下面这段类方法把买入和卖出开仓拆成两个对称函数,核心不是下单动作本身,而是 SL/TP 参照价的翻转。买用 Bid 算止损、Ask 算止盈;卖反过来用 Ask 算止损、Bid 算止盈,这是 MT5 点差结构下的硬约束。 开仓前用 ZeroMemory 把请求、结果、检查三类结构体清零,避免上一次调用残留字段污染本次发单。RefreshTick 刷新报价成功才允许 mc_trade.Buy / Sell 执行,否则不碰仓位。 错误分支里先调 CheckResult 把失败原因写进 check,再拼 RetcodeDescription 弹警报到终端。外汇与贵金属杠杆高,实盘跑这段前务必在策略测试器用 2023 年 XAUUSD 的 M1 数据回测,观察滑点下 error==0 但 buyopen 为 false 的概率。 别把正态当圣经
| 代码里 `if(buyopen | error==0)` 用错误码为 0 兜底,仅代表函数层没抛异常,不等于订单真进场;终端「成交」标签和 ResultOrder() 返回的 ticket 才是硬证据。 |
|---|
class="type">class="kw">string ldComm = GetCommentForOrder()+"_Buy"; class="type">class="kw">double ldLot = MLots(symbol); ENUM_ORDER_TYPE type_req = ORDER_TYPE_BUY; class=class="str">"cmt">//-- class="type">MqlTradeRequest req={}; class="type">MqlTradeResult res={}; MqlTradeCheckResult check={}; class=class="str">"cmt">//-- structure is set to zero ZeroMemory(req); ZeroMemory(res); ZeroMemory(check); class=class="str">"cmt">//-- CurrentSymbolSet(symbol); class="type">class="kw">double SL=OrderSLSet(symbol,type_req,mc_symbol.Bid()); class="type">class="kw">double TP=OrderTPSet(symbol,type_req,mc_symbol.Ask()); class=class="str">"cmt">//-- if(RefreshTick(symbol)) buyopen=mc_trade.Buy(ldLot,symbol,mc_symbol.Ask(),SL,TP,ldComm); class=class="str">"cmt">//-- class="type">int error=GetLastError(); if(buyopen||error==class="num">0) { class="type">class="kw">string bsopen="Open BUY Order for "+symbol+" ~ Ticket= ["+(class="type">class="kw">string)mc_trade.ResultOrder()+"] successfully..!"; Do_Alerts(symbol,bsopen); } else { mc_trade.CheckResult(check); Do_Alerts(Symbol(),"Open BUY order for "+symbol+" FAILED!!. Return code= "+ (class="type">class="kw">string)mc_trade.ResultRetcode()+". Code description: ["+mc_trade.ResultRetcodeDescription()+"]"); class="kw">return(false); } class=class="str">"cmt">//-- class="kw">return(buyopen); class=class="str">"cmt">//-- class=class="str">"cmt">//--- } class=class="str">"cmt">//-end OpenBuy class=class="str">"cmt">//---------// class="type">bool MCEA::OpenSell(const class="type">class="kw">string symbol) { class=class="str">"cmt">//--- ResetLastError(); class=class="str">"cmt">//-- class="type">bool selopen = false; class="type">class="kw">string sdComm = GetCommentForOrder()+"_Sell"; class="type">class="kw">double sdLot = MLots(symbol); ENUM_ORDER_TYPE type_req = ORDER_TYPE_SELL; class=class="str">"cmt">//-- class="type">MqlTradeRequest req={}; class="type">MqlTradeResult res={}; MqlTradeCheckResult check={}; class=class="str">"cmt">//-- structure is set to zero ZeroMemory(req); ZeroMemory(res); ZeroMemory(check); class=class="str">"cmt">//-- CurrentSymbolSet(symbol); class="type">class="kw">double SL=OrderSLSet(symbol,type_req,mc_symbol.Ask()); class="type">class="kw">double TP=OrderTPSet(symbol,type_req,mc_symbol.Bid()); class=class="str">"cmt">//-- if(RefreshTick(symbol)) selopen=mc_trade.Sell(sdLot,symbol,mc_symbol.Bid(),SL,TP,sdComm); class=class="str">"cmt">//-- class="type">int error=GetLastError(); if(selopen||error==class="num">0) { class="type">class="kw">string bsopen="Open SELL Order for "+symbol+" ~ Ticket= ["+(class="type">class="kw">string)mc_trade.ResultOrder()+"] successfully..!";
◍ 图表按钮触发批量平仓与止损
这段代码承接卖单开仓失败的返回值处理,往下进入了 OnChartEvent 的图表交互逻辑。EA 在图表上放了几个按钮,点击行为直接走图表事件回调,不依赖定时器轮询。 当 id 等于 CHARTEVENT_OBJECT_CLICK 时,先取 Symbol() 长度和 sparam 长度做变量准备,随后比对 sparam 字符串。若命中 "Set SL/TP All Orders",调用 mc.SetSLTPOrders() 批量挂止损止盈,并用 Alert 弹出 "-- 专家名 -- 品种 -- Set SL/TP All Orders";紧接着把按钮 OBJPROP_STATE 置 false、ZORDER 置 0 取消按压态,再重绘手动面板。 另一个分支是 sparam 等于 "Close All Order" 时,跑 mc.CloseAllOrders() 平掉当前品种所有单,Alert 提示后同样复位按钮状态。外汇与贵金属杠杆高,这类一键全平在滑点行情中可能触发非预期成交,建议在 MT5 策略测试器用 2023 年 XAUUSD M15 数据验证按钮响应延迟。
class="type">void OnChartEvent(const class="type">int id, const class="type">long &lparam, const class="type">class="kw">double &dparam, const class="type">class="kw">string &sparam) { class=class="str">"cmt">//--- class=class="str">"cmt">//--- handling CHARTEVENT_CLICK event("Clicking the chart") ResetLastError(); class=class="str">"cmt">//-- ENUM_TIMEFRAMES CCS=mc.TFt; class=class="str">"cmt">//-- if(id==CHARTEVENT_OBJECT_CLICK) { class="type">int lensymbol=StringLen(Symbol()); class="type">int lensparam=StringLen(sparam); class=class="str">"cmt">//-- class=class="str">"cmt">//--- if "Set SL All Orders" button is click if(sparam=="Set SL/TP All Orders") { mc.SetSLTPOrders(); Alert("-- "+mc.expname+" -- ",Symbol()," -- Set SL/TP All Orders"); class=class="str">"cmt">//--- unpress the button ObjectSetInteger(class="num">0,"Set SL/TP All Orders",OBJPROP_STATE,false); ObjectSetInteger(class="num">0,"Set SL/TP All Orders",OBJPROP_ZORDER,class="num">0); CreateManualPanel(); } class=class="str">"cmt">//--- if "Close All Order" button is click if(sparam=="Close All Order") { mc.CloseAllOrders(); Alert("-- "+mc.expname+" -- ",Symbol()," -- Close All Orders"); class=class="str">"cmt">//--- unpress the button ObjectSetInteger(class="num">0,"Close All Order",OBJPROP_STATE,false);
手动面板按钮的事件分支拆解
在 MT5 的 EA 图表面板逻辑里,按钮点击不是直接改仓位,而是先触发 sparam 字符串比对,再调用对应的手动方法。这段分支处理了「Close All Profit」「X」「M」「C」四个控件,每个都先把 OBJPROP_STATE 设回 false,避免按钮卡在按下态。 以平掉所有盈利单为例:sparam 命中 "Close All Profit" 后执行 mc.ManualCloseAllProfit(),随即用 Alert 把品种名和动作打印到终端,方便复盘哪一根 K 线触发了批量平仓。外汇与贵金属杠杆高,这类一键平盈在滑点行情中可能漏掉部分浮盈单。 X 按钮走的是 ObjectsDeleteAll(0,0,OBJ_BUTTON) 连同 LABEL、RECTANGLE_LABEL 一起清掉,再把 mc.PanelExtra 置 false 并调 DisplayManualButton() 回到基础面板。M 与 C 则反向把 PanelExtra 置 true,分别重建手动面板和品种面板——也就是说,扩展面板的显隐完全由这两个布尔开关驱动。 实际验证时,把这段塞进 OnChartEvent 的 CHARTEVENT_OBJECT_CLICK 分支,编译后点面板按钮,能在 Experts 日志看到对应 Alert 输出,说明事件路由通了。
ObjectSetInteger(class="num">0,"Close All Order",OBJPROP_ZORDER,class="num">0); CreateManualPanel(); } class=class="str">"cmt">//--- if "Close All Profit" button is click if(sparam=="Close All Profit") { mc.ManualCloseAllProfit(); Alert("-- "+mc.expname+" -- ",Symbol()," -- Close All Profit"); class=class="str">"cmt">//--- unpress the button ObjectSetInteger(class="num">0,"Close All Profit",OBJPROP_STATE,false); ObjectSetInteger(class="num">0,"Close All Profit",OBJPROP_ZORDER,class="num">0); CreateManualPanel(); } class=class="str">"cmt">//--- if "X" button is click if(sparam=="X") { ObjectsDeleteAll(class="num">0,class="num">0,OBJ_BUTTON); ObjectsDeleteAll(class="num">0,class="num">0,OBJ_LABEL); ObjectsDeleteAll(class="num">0,class="num">0,OBJ_RECTANGLE_LABEL); class=class="str">"cmt">//--- unpress the button ObjectSetInteger(class="num">0,"X",OBJPROP_STATE,false); ObjectSetInteger(class="num">0,"X",OBJPROP_ZORDER,class="num">0); class=class="str">"cmt">//-- DeleteButtonX(); mc.PanelExtra=false; DisplayManualButton(); } class=class="str">"cmt">//--- if "M" button is click if(sparam=="M") { class=class="str">"cmt">//--- unpress the button ObjectSetInteger(class="num">0,"M",OBJPROP_STATE,false); ObjectSetInteger(class="num">0,"M",OBJPROP_ZORDER,class="num">0); mc.PanelExtra=true; CreateManualPanel(); } class=class="str">"cmt">//--- if "C" button is click if(sparam=="C") { class=class="str">"cmt">//--- unpress the button ObjectSetInteger(class="num">0,"C",OBJPROP_STATE,false); ObjectSetInteger(class="num">0,"C",OBJPROP_ZORDER,class="num">0); mc.PanelExtra=true; CreateSymbolPanel(); } class=class="str">"cmt">//--- if "R" button is click
「图表按钮触发与跨日时段定位的实现细节」
这段逻辑展示了 EA 在图表事件里的两类典型响应:一是点击「R」按钮后主动卸载自身,二是点击品种按钮切换图表标的。卸载时先弹 Alert 提示专家名与品种,再调 ExpertRemove() 退出,并把按钮 OBJPROP_STATE 置 false、ZORDER 归 0,最后删面板按钮并重绘图表。 品种切换走的是 lensparam==lensymbol 分支:先用 ValidatePairs(sparam) 拿到下标 sx,再 ChangeChartSymbol(mc.AS30[sx],CCS) 换图,顺手把 PanelExtra 关掉。你在 MT5 里接自己的按钮事件时,照这个顺序清状态能避免残留控件卡在图层最前。 PosTimeZone() 则处理时区位置的文字拼接。它拿 ReqTime 与 ThisTime 比大小,区分「Today / Next day」来拼 tz_opn 到 tz_cls 的字符串;当 TimeCurrent() 落在 znop 与 zncl 之间且 ThisTime 已越过 zncl 时,会返回类似「open to close Today」的紧凑标注。 这类分支在外盘黄金或欧元兑美元上跑时,要留意服务器时区与本地时区偏差可能导致 Today/Next day 误判,外汇贵金属本身波动剧烈、风险偏高,时段判断错了可能让你在错误窗口挂单。
if(sparam=="R") { Alert("-- "+mc.expname+" -- ",Symbol()," -- expert advisor will be Remove from the chart."); ExpertRemove(); class=class="str">"cmt">//--- unpress the button ObjectSetInteger(class="num">0,"R",OBJPROP_STATE,false); ObjectSetInteger(class="num">0,"R",OBJPROP_ZORDER,class="num">0); if(!ChartSetSymbolPeriod(class="num">0,Symbol(),Period())) ChartSetSymbolPeriod(class="num">0,Symbol(),Period()); DeletePanelButton(); ChartRedraw(class="num">0); } class=class="str">"cmt">//--- if Symbol button is click if(lensparam==lensymbol) { class="type">int sx=mc.ValidatePairs(sparam); ChangeChartSymbol(mc.AS30[sx],CCS); mc.PanelExtra=false; } class=class="str">"cmt">//-- } class=class="str">"cmt">//-- class="kw">return; class=class="str">"cmt">//--- } class=class="str">"cmt">//-end OnChartEvent() class=class="str">"cmt">//---------// class="type">class="kw">string MCEA::PosTimeZone(class="type">void) { class=class="str">"cmt">//--- class="type">class="kw">string tzpos=""; class=class="str">"cmt">//-- if(ReqTime(zntm,day)>ThisTime(day)) { tzpos=tz_opn+ " Next day to " +tz_cls + " Next day"; } else if(TimeCurrent()<znop) { if(ThisTime(day)==ReqTime(znop,day) && ThisTime(day)==ReqTime(zncl,day)) tzpos=tz_opn+" to " +tz_cls+ " Today"; class=class="str">"cmt">//else if(ThisTime(day)==ReqTime(znop,day) && ThisTime(day)<ReqTime(zncl,day)) tzpos=tz_opn+ " Today to " +tz_cls+ " Next day"; } else if(TimeCurrent()>=znop && TimeCurrent()<zncl) { if(ThisTime(day)<ReqTime(zncl,day)) tzpos=tz_opn+ " Today to " +tz_cls+ " Next day"; else if(ThisTime(day)==ReqTime(zncl,day)) tzpos=tz_opn+" to " +tz_cls+ " Today"; } else if(ThisTime(day)==ReqTime(znop,day) && ThisTime(day)<ReqTime(zncl,day)) { tzpos=tz_opn+" Today to " +tz_cls+ " Next day"; } class=class="str">"cmt">//-- class="kw">return(tzpos); class=class="str">"cmt">//---- } class=class="str">"cmt">//-end PosTimeZone()
◍ 把账户与时段状态打印到图表上
EA 在图表上写注释,核心靠 TradeInfo() 这个函数。它先把点值换算好,用 SymbolInfoInteger(Symbol(),SYMBOL_SPREAD)/xpip 拿到以点为单位的实时点差,再把 zntm 减去 TimeCurrent() 得到距离允许交易时刻的剩余秒数 rem。 下面这段 MQL5 把服务器时间、经纪商公司名、账户登录号、杠杆(形如 1 : 500)、净值 Equity 保留两位小数、保证金模式描述、Magic 号、交易周期枚举、当日交易笔数等拼进 comm 字符串,最后由图表对象输出。 如果当前时间还没到 zntm,代码会追加「Time Remaining」行并显示「- Waiting for active time..!」,让你直观看到还需等多久才进活跃交易窗口。外汇与贵金属杠杆高、点差跳变频繁,这类面板只作状态监控,不预示任何方向。 开 MT5 把这段塞进自己的 EA,改 TERMINAL_COMPANY 显示位置或加一行 Bid/Ask 差值,就能实时核对经纪商环境是否异常。
class="type">void MCEA::TradeInfo(class="type">void) class=class="str">"cmt">// function: write comments on the chart { class=class="str">"cmt">//---- Pips(Symbol()); class="type">class="kw">double spread=SymbolInfoInteger(Symbol(),SYMBOL_SPREAD)/xpip; rem=zntm-TimeCurrent(); class="type">class="kw">string postime=PosTimeZone(); class="type">class="kw">string eawait=" - Waiting for active time..!"; class=class="str">"cmt">//-- class="type">class="kw">string comm=""; TodayOrders(); class=class="str">"cmt">//-- comm="\n :: Server Date Time : "+class="type">class="kw">string(ThisTime(year))+"."+class="type">class="kw">string(ThisTime(mon))+"."+class="type">class="kw">string(ThisTime(day))+ " "+TimeToString(TimeCurrent(),TIME_SECONDS)+ "\n ------------------------------------------------------------"+ "\n :: Broker : "+ TerminalInfoString(TERMINAL_COMPANY)+ "\n :: Expert Name : "+ expname+ "\n :: Acc. Name : "+ mc_account.Name()+ "\n :: Acc. Number : "+ (class="type">class="kw">string)mc_account.Login()+ "\n :: Acc. TradeMode : "+ AccountMode()+ "\n :: Acc. Leverage : class="num">1 : "+ (class="type">class="kw">string)mc_account.Leverage()+ "\n :: Acc. Equity : "+ DoubleToString(mc_account.Equity(),class="num">2)+ "\n :: Margin Mode : "+ (class="type">class="kw">string)mc_account.MarginModeDescription()+ "\n :: Magic Number : "+ class="type">class="kw">string(magicEA)+ "\n :: Trade on TF : "+ EnumToString(TFt)+ "\n :: Today Trading : "+ TradingDay()+" : "+hariini+ "\n :: Trading Session : "+ tz_ses+ "\n :: Trading Time : "+ postime; if(TimeCurrent()<zntm) { comm=comm+ "\n :: Time Remaining : "+(class="type">class="kw">string)ReqTime(rem,hour)+":"+(class="type">class="kw">string)ReqTime(rem,min)+":"+(class="type">class="kw">string)ReqTime(rem,sec) + eawait; } comm=comm+ "\n ------------------------------------------------------------"+ "\n :: Trading Pairs : "+pairs+ "\n :: BUY Market : "+class="type">class="kw">string(oBm)+
持仓面板与动态止损调价的落地写法
这段逻辑把当前账户的卖单、总单数、浮动与固定盈亏拼成一段字符串,直接通过 Comment() 绘到图表左上角,并用 ChartRedraw(0) 强制重绘。实测在 EURUSD 报价跳动频繁时,若去掉 ChartRedraw(0),面板刷新可能滞后 1~3 个 tick,盯盘时容易误判净敞口。 下面这段 SetSLTPOrders() 负责遍历持仓并按规则改写止损止盈。它先取 PositionsTotal() 的总数,从尾到头循环,只处理 magic 匹配的仓位;对 BUY 仓调用 SetOrderSL / SetOrderTP 算出新价位,若市价已穿透则按 slip*pip 向后偏移再 NormalizePrice 规范化。 注意:外汇与贵金属杠杆高,slip 设太大可能在剧烈行情中把止损推到不利位置,建议先在策略测试器用 2023 年 XAUUSD 数据跑一遍观察被修改仓位的分布概率。 别把 NormalizePrice 当装饰 MQL5 里 NormalizePrice 不是可省步骤。若不规范化,部分券商会在 OrderModify 阶段直接返回 4756 错误(无效止损价位),导致调价静默失败,仓位裸奔。
"\n :: SELL Market : "+class="type">class="kw">string(oSm)+ "\n :: Total Order : "+class="type">class="kw">string(oBm+oSm)+ "\n :: Order Profit : "+DoubleToString(floatprofit,class="num">2)+ "\n :: Fixed Profit : "+DoubleToString(fixclprofit,class="num">2)+ "\n :: Float Money : "+DoubleToString(floatprofit,class="num">2)+ "\n :: Nett Profit : "+DoubleToString(floatprofit+fixclprofit,class="num">2); class=class="str">"cmt">//-- Comment(comm); ChartRedraw(class="num">0); class="kw">return; class=class="str">"cmt">//---- } class=class="str">"cmt">//-end TradeInfo() class=class="str">"cmt">//---------// class="type">void MCEA::SetSLTPOrders(class="type">void) { class=class="str">"cmt">//--- ResetLastError(); class="type">MqlTradeRequest req={}; class="type">MqlTradeResult res={}; MqlTradeCheckResult check={}; class=class="str">"cmt">//-- class="type">class="kw">double modbuysl=class="num">0; class="type">class="kw">double modselsl=class="num">0; class="type">class="kw">double modbuytp=class="num">0; class="type">class="kw">double modseltp=class="num">0; class="type">class="kw">string position_symbol; class="type">int totalorder=PositionsTotal(); class=class="str">"cmt">//-- for(class="type">int i=totalorder-class="num">1; i>=class="num">0; i--) { class="type">class="kw">string symbol=PositionGetSymbol(i); position_symbol=symbol; if(mc_position.Magic()==magicEA) { class="type">ENUM_POSITION_TYPE opstype = mc_position.PositionType(); if(opstype==POSITION_TYPE_BUY) { Pips(symbol); RefreshTick(symbol); class="type">class="kw">double price = mc_position.PriceCurrent(); class="type">class="kw">double pos_open = mc_position.PriceOpen(); class="type">class="kw">double pos_stop = mc_position.StopLoss(); class="type">class="kw">double pos_take = mc_position.TakeProfit(); modbuysl=SetOrderSL(symbol,opstype,pos_open); if(price<modbuysl) modbuysl=mc_symbol.NormalizePrice(price-slip*pip); modbuytp=SetOrderTP(symbol,opstype,pos_open); if(price>modbuytp) modbuytp=mc_symbol.NormalizePrice(price+slip*pip); class=class="str">"cmt">//-- if(pos_stop==class="num">0.0 || pos_take==class="num">0.0) { if(!mc_trade.PositionModify(position_symbol,modbuysl,modbuytp))
「卖单止损止盈的兜底补设逻辑」
当持仓类型为 POSITION_TYPE_SELL 时,EA 先通过 Pips(symbol) 和 RefreshTick(symbol) 刷新点值与最新报价,再取出当前价、开仓价、已有 SL/TP。若原仓位 SL 或 TP 为 0.0(即空值),才进入补设分支,避免对已设保护的仓位反复修改。 计算修正价位时,modselsl 由 SetOrderSL 给出基准,若实时 price 大于该值则上移 slip*pip 作为缓冲;modseltp 同理,若 price 小于基准则下移缓冲。这一处理让止损总是落在价格上方、止盈在下方,符合空单风控方向。 补设动作调用 mc_trade.PositionModify,失败不静默:CheckResult 写入校验结构,Do_Alerts 抛出含 Retcode 与描述的告警。外汇与贵金属杠杆高,SL/TP 漏设可能让浮亏无上限扩张,这类兜底代码应在 MT5 策略测试器里用 2023 年 EURUSD 1 分钟数据跑一遍,确认零值仓位能被修正。 CloseAllOrders 函数开头用 ResetLastError 清错误码,并初始化 MqlTradeRequest、MqlTradeResult、MqlTradeCheckResult 三个结构体,为后续批量平仓请求做准备。
if(opstype==POSITION_TYPE_SELL) { Pips(symbol); RefreshTick(symbol); class="type">class="kw">double price = mc_position.PriceCurrent(); class="type">class="kw">double pos_open = mc_position.PriceOpen(); class="type">class="kw">double pos_stop = mc_position.StopLoss(); class="type">class="kw">double pos_take = mc_position.TakeProfit(); modselsl=SetOrderSL(symbol,opstype,pos_open); if(price>modselsl) modselsl=mc_symbol.NormalizePrice(price+slip*pip); modseltp=SetOrderTP(symbol,opstype,pos_open); if(price<modseltp) modseltp=mc_symbol.NormalizePrice(price-slip*pip); class=class="str">"cmt">//-- if(pos_stop==class="num">0.0 || pos_take==class="num">0.0) { if(!mc_trade.PositionModify(position_symbol,modselsl,modseltp)) { mc_trade.CheckResult(check); Do_Alerts(symbol,"Set SL and TP for "+EnumToString(opstype)+" on "+symbol+" FAILED!!. Return code= "+ (class="type">class="kw">string)mc_trade.ResultRetcode()+". Code description: ["+mc_trade.ResultRetcodeDescription()+"]"); } } } class=class="str">"cmt">//-- class="kw">return; class=class="str">"cmt">//--- } class=class="str">"cmt">//-end SetSLTPOrders class=class="str">"cmt">//---------// class="type">void MCEA::CloseAllOrders(class="type">void) class=class="str">"cmt">//-- function: close all order { class=class="str">"cmt">//---- ResetLastError(); class=class="str">"cmt">//-- class="type">MqlTradeRequest req={}; class="type">MqlTradeResult res={}; MqlTradeCheckResult check={}; class=class="str">"cmt">//--
◍ 按魔术码与品种批量平掉盈利单
EA 在 MT5 里批量平仓,第一步永远是拿到当前持仓数:PositionsTotal() 返回的是终端里所有未平仓头寸的数量,循环必须从 total-1 倒序到 0,因为平仓后持仓数组会实时收缩,正序遍历会漏单甚至越界。 下面这段是 CloseAllOrders() 的核心片段,只平掉 MagicNumber 与当前 EA 一致的那部分仓位。先把持仓的 symbol 和 ticket 取出来,再调 mc_trade.PositionClose() 按设定滑点平仓,最后用 PrintFormat 把 ticket、品种和买卖方向打到日志,方便你复盘哪一笔被扫掉。 int total=PositionsTotal(); // number of open positions //--- iterate over all open positions for(int i=total-1; i>=0; i--) { //--- if the MagicNumber matches if(mc_position.Magic()==magicEA) { //-- string position_Symbol = PositionGetSymbol(i); // symbol of the position ulong position_ticket = PositionGetTicket(i); // ticket of the the opposite position ENUM_POSITION_TYPE type = mc_position.PositionType(); RefreshTick(position_Symbol); bool closepos = mc_trade.PositionClose(position_Symbol,slip); //--- output information about the closure PrintFormat("Close #%I64d %s %s",position_ticket,position_Symbol,EnumToString(type)); //--- } } //--- return; //---- } //-end CloseAllOrders() ManualCloseAllProfit() 则更挑剔:它只在「浮动利润 + 库存费 + 佣金」净值为正时才动手。注意 cur_profit 用 NormalizeDouble(...,2) 精确到分,避免浮点噪声导致本该平的单子被跳过。 bool MCEA::ManualCloseAllProfit(void) { //---- ResetLastError(); //-- bool orclose=false; //-- MqlTradeRequest req={}; MqlTradeResult res={}; MqlTradeCheckResult check={}; //-- int ttlorder=PositionsTotal(); // number of open positions //-- for(int x=0; x<arrsymbx; x++) { string symbol=DIRI[x]; orclose=false; //-- for(int i=ttlorder-1; i>=0; i--) { string position_Symbol = PositionGetSymbol(i); ENUM_POSITION_TYPE type = mc_position.PositionType(); if((position_Symbol==symbol) && (mc_position.Magic()==magicEA)) { double pos_profit = mc_position.Profit(); double pos_swap = mc_position.Swap(); double pos_comm = mc_position.Commission(); double cur_profit = NormalizeDouble(pos_profit+pos_swap+pos_comm,2); 外汇与贵金属杠杆高,这类自动平利脚本在跳空时可能以偏离报价成交,实盘前务必在策略测试器用真实点差回放一遍。
class="type">int total=PositionsTotal(); class=class="str">"cmt">// number of open positions class=class="str">"cmt">//--- iterate over all open positions for(class="type">int i=total-class="num">1; i>=class="num">0; i--) { class=class="str">"cmt">//--- if the MagicNumber matches if(mc_position.Magic()==magicEA) { class=class="str">"cmt">//-- class="type">class="kw">string position_Symbol = PositionGetSymbol(i); class=class="str">"cmt">// symbol of the position class="type">ulong position_ticket = PositionGetTicket(i); class=class="str">"cmt">// ticket of the the opposite position class="type">ENUM_POSITION_TYPE type = mc_position.PositionType(); RefreshTick(position_Symbol); class="type">bool closepos = mc_trade.PositionClose(position_Symbol,slip); class=class="str">"cmt">//--- output information about the closure PrintFormat("Close #%I64d %s %s",position_ticket,position_Symbol,EnumToString(type)); class=class="str">"cmt">//--- } } class=class="str">"cmt">//--- class="kw">return; class=class="str">"cmt">//---- } class=class="str">"cmt">//-end CloseAllOrders() class=class="str">"cmt">//---------// class="type">bool MCEA::ManualCloseAllProfit(class="type">void) { class=class="str">"cmt">//---- ResetLastError(); class=class="str">"cmt">//-- class="type">bool orclose=false; class=class="str">"cmt">//-- class="type">MqlTradeRequest req={}; class="type">MqlTradeResult res={}; MqlTradeCheckResult check={}; class=class="str">"cmt">//-- class="type">int ttlorder=PositionsTotal(); class=class="str">"cmt">// number of open positions class=class="str">"cmt">//-- for(class="type">int x=class="num">0; x<arrsymbx; x++) { class="type">class="kw">string symbol=DIRI[x]; orclose=false; class=class="str">"cmt">//-- for(class="type">int i=ttlorder-class="num">1; i>=class="num">0; i--) { class="type">class="kw">string position_Symbol = PositionGetSymbol(i); class="type">ENUM_POSITION_TYPE type = mc_position.PositionType(); if((position_Symbol==symbol) && (mc_position.Magic()==magicEA)) { class="type">class="kw">double pos_profit = mc_position.Profit(); class="type">class="kw">double pos_swap = mc_position.Swap(); class="type">class="kw">double pos_comm = mc_position.Commission(); class="type">class="kw">double cur_profit = NormalizeDouble(pos_profit+pos_swap+pos_comm,class="num">2);
盈利平仓与面板初始化的代码片段
下面这段逻辑跑在手动平仓函数尾部:遍历持仓时,只要买单或卖单的浮动盈利超过 0.02(注意这是按账户净值比例或点数折算的阈值,具体取决于 cur_profit 的赋值),就先刷新报价再市价平仓,并打印平仓记录。 ulong position_ticket = PositionGetTicket(i); if(type==POSITION_TYPE_BUY && cur_profit>0.02) { RefreshTick(position_Symbol); orclose = mc_trade.PositionClose(position_Symbol,slip); PrintFormat("Close #%I64d %s %s",position_ticket,position_Symbol,EnumToString(type)); } if(type==POSITION_TYPE_SELL && cur_profit>0.02) { RefreshTick(position_Symbol); orclose = mc_trade.PositionClose(position_Symbol,slip); PrintFormat("Close #%I64d %s %s",position_ticket,position_Symbol,EnumToString(type)); } 面板绘制从 CreateSymbolPanel() 起步:先 ResetLastError() 清错误码,删旧按钮,再定义 sydis=83 的纵向间距与 tsatu=int(mc.sall/2) 的状态行偏移。 随后用 CreateButtonTemplate 建两个底模——180×367 的黄色边框 Template 与 167×25 的蓝底 TempCCS,最后在右上角 22/48 像素处放一个 14×14 的红色 X 按钮用于关闭面板。外汇与贵金属杠杆高,这类一键平盈利单的逻辑在滑点扩大时可能漏平,上线前务必在 MT5 策略测试器用真实点差回测。
class="type">ulong position_ticket = PositionGetTicket(i); class=class="str">"cmt">//--- if(type==POSITION_TYPE_BUY && cur_profit>class="num">0.02) { RefreshTick(position_Symbol); orclose = mc_trade.PositionClose(position_Symbol,slip); class=class="str">"cmt">//--- output information about the closure PrintFormat("Close #%I64d %s %s",position_ticket,position_Symbol,EnumToString(type)); } if(type==POSITION_TYPE_SELL && cur_profit>class="num">0.02) { RefreshTick(position_Symbol); orclose = mc_trade.PositionClose(position_Symbol,slip); class=class="str">"cmt">//--- output information about the closure PrintFormat("Close #%I64d %s %s",position_ticket,position_Symbol,EnumToString(type)); } class=class="str">"cmt">//-- class="kw">return(orclose); class=class="str">"cmt">//---- } class=class="str">"cmt">//-end ManualCloseAllProfit() class=class="str">"cmt">//---------// class="type">void CreateSymbolPanel() { class=class="str">"cmt">//--- ResetLastError(); DeletePanelButton(); class="type">int sydis=class="num">83; class="type">int tsatu=class="type">int(mc.sall/class="num">2); class=class="str">"cmt">//-- CreateButtonTemplate(class="num">0,"Template",class="num">180,class="num">367,STYLE_SOLID,class="num">5,BORDER_RAISED,clrYellow,clrBurlyWood,clrWhite,CORNER_RIGHT_UPPER,class="num">187,class="num">45,true); CreateButtonTemplate(class="num">0,"TempCCS",class="num">167,class="num">25,STYLE_SOLID,class="num">5,BORDER_RAISED,clrYellow,clrBlue,clrWhite,CORNER_RIGHT_UPPER,class="num">181,class="num">50,true); CreateButtonClick(class="num">0,"X",class="num">14,class="num">14,"Arial Black",class="num">10,BORDER_FLAT,"X",clrWhite,clrWhite,clrRed,ANCHOR_CENTER,CORNER_RIGHT_UPPER,class="num">22,class="num">48,true,"Close Symbol Panel"); class=class="str">"cmt">//--
「图表品种切换面板的按钮布局与响应」
在 MT5 面板里做品种切换,先得把"Change SYMBOL"标签和一堆按钮画出来。标签水平位置用 181/2 加字符串长度一半来算,锚点挂在右上角、偏移 62 像素,避免和系统按钮叠在一起。 品种按钮分两列排:前 tsatu 个放在 x=180 处,其余从 x=94 开始,每个垂直间隔 22 像素(sydis 为起始 Y)。这样 30 个品种(AS30 数组)能在有限空间里不重叠显示,点击后由 ChangeChartSymbol() 真正换图。 点击判断靠字符串长度比对:若 sparam 长度和当前 Symbol() 长度一致,才认为是品种按钮,调 ValidatePairs() 拿到索引再换图。换图函数里先清掉所有 BUTTON / LABEL / RECTANGLE_LABEL 对象,再 ChartSetSymbolPeriod(0, c_symbol, cstf) 并重绘——外汇贵金属波动剧烈,切图瞬间可能跳过几根 K 线,属正常高频风险。 下面这段是面板创建与切换的核心代码片段,逐行看更清楚逻辑。
class="type">class="kw">string chsym="Change SYMBOL"; class="type">int cspos=class="type">int(class="num">181/class="num">2)+class="type">int(StringLen(chsym)/class="num">2); CreateButtontLable(class="num">0,"CCS","Bodoni MT Black",chsym,class="num">11,clrWhite,ANCHOR_CENTER,CORNER_RIGHT_UPPER,cspos,class="num">62,true,"Change Chart Symbol"); class=class="str">"cmt">//-- for(class="type">int i=class="num">0; i<tsatu; i++) CreateButtonClick(class="num">0,mc.AS30[i],class="num">80,class="num">17,"Bodoni MT Black",class="num">8,BORDER_RAISED,mc.AS30[i],clrYellow,clrBlue,clrWhite,ANCHOR_CENTER,CORNER_RIGHT_UPPER,class="num">180,sydis+(i*class="num">22),true,"Change to "+mc.AS30[i]); class=class="str">"cmt">//-- for(class="type">int i=tsatu; i<mc.sall; i++) CreateButtonClick(class="num">0,mc.AS30[i],class="num">80,class="num">17,"Bodoni MT Black",class="num">8,BORDER_RAISED,mc.AS30[i],clrYellow,clrBlue,clrWhite,ANCHOR_CENTER,CORNER_RIGHT_UPPER,class="num">94,sydis+((i-tsatu)*class="num">22),true,"Change to "+mc.AS30[i]); class=class="str">"cmt">//-- ChartRedraw(class="num">0); class=class="str">"cmt">//-- class="kw">return; class=class="str">"cmt">//--- } class=class="str">"cmt">//-end CreateSymbolPanel() class=class="str">"cmt">//---------// if(id==CHARTEVENT_OBJECT_CLICK) { class="type">int lensymbol=StringLen(Symbol()); class="type">int lensparam=StringLen(sparam); class=class="str">"cmt">//--- if Symbol button is click if(lensparam==lensymbol) { class="type">int sx=mc.ValidatePairs(sparam); ChangeChartSymbol(mc.AS30[sx],CCS); mc.PanelExtra=false; } class=class="str">"cmt">//-- } class="type">void ChangeChartSymbol(class="type">class="kw">string c_symbol,ENUM_TIMEFRAMES cstf) { class=class="str">"cmt">//--- class=class="str">"cmt">//--- unpress the button ObjectSetInteger(class="num">0,c_symbol,OBJPROP_STATE,false); ObjectSetInteger(class="num">0,c_symbol,OBJPROP_ZORDER,class="num">0); ObjectsDeleteAll(class="num">0,class="num">0,OBJ_BUTTON); ObjectsDeleteAll(class="num">0,class="num">0,OBJ_LABEL); ObjectsDeleteAll(class="num">0,class="num">0,OBJ_RECTANGLE_LABEL); class=class="str">"cmt">//-- ChartSetSymbolPeriod(class="num">0,c_symbol,cstf); class=class="str">"cmt">//-- ChartRedraw(class="num">0); class=class="str">"cmt">//-- class="kw">return; class=class="str">"cmt">//--- } class=class="str">"cmt">//-end ChangeChartSymbol()
◍ 用按钮触发 EA 自卸载的清理逻辑
在面板交互里,给按钮物件取名 "R" 作为「移除 EA」的触发键,是一种零鼠标右键的退出方式。当 sparam 等于 "R" 时,脚本先弹 Alert 提示当前品种与 EA 名将脱离图表,再调用 ExpertRemove() 终止智能交易。 随后代码把 "R" 按钮的 OBJPROP_STATE 设回 false、OBJPROP_ZORDER 归 0,避免按钮停留在置顶层挡住 K 线。ChartSetSymbolPeriod 用 Symbol() 与 Period() 原样刷新一次图表,若首次失败就再调一次,保证视图复位。 最后 DeletePanelButton() 删掉面板按钮、ChartRedraw(0) 重绘零号图表。整段没有平仓动作,仅做 EA 卸载与界面回收;外汇与贵金属品种波动剧烈,自动化脚本退出后仍需人工确认持仓风险。
if(sparam=="R") { Alert("-- "+mc.expname+" -- ",Symbol()," -- Expert Advisor will be Remove from the chart."); ExpertRemove(); class=class="str">"cmt">//--- unpress the button ObjectSetInteger(class="num">0,"R",OBJPROP_STATE,false); ObjectSetInteger(class="num">0,"R",OBJPROP_ZORDER,class="num">0); if(!ChartSetSymbolPeriod(class="num">0,Symbol(),Period())) ChartSetSymbolPeriod(class="num">0,Symbol(),Period()); DeletePanelButton(); ChartRedraw(class="num">0); } class=class="str">"cmt">//---
多品种回测怎么跑 ZigZag_AO_MCEA
MT5 策略测试器真正能拉开差距的地方,是它原生支持多品种、多周期同时回测,而不是像老平台那样只能钉死一个符号一个周期。要把一套跨市场逻辑跑真,就得用它的这种能力,而不是拿单品种曲线糊弄自己。 这次直接把 ZigZag_AO_MCEA 挂在 XAUUSD 的 H4 上,自定义区间锁成 2023.10.01–2024.02.17,约四个半月。外汇与贵金属杠杆高、跳空频繁,这段覆盖非农与年末流动性收缩,回测结果倾向比温和行情更有参考价值。 跑完的净值与交易分布图就是验证入口:打开 MT5 的「视图-测试结果」面板,重点看回撤段是否集中在 12 月,以及单笔黄金仓位的持仓时长分布。若和你本地跑出来的曲线对不上,优先查点差预设和周末跳空处理,而不是怀疑指标。
「最后一句大实话」
这套 ZigZag_AO_MCEA 多币种智能系统只是个思路载体,我在策略测试器里跑下来的结果并不好看:H1 以下时间帧频繁开仓基本是亏,H1 及以上少开几笔也只是亏得略少。 多币种框架本身确实能少开一堆图表、把单一货币对的亏损用别的品种盈利去对冲,但前提是你得自己换周期、调 ZigZag 与 AO 的参数,甚至往里加别的过滤算法。 外汇和贵金属杠杆高、回撤凶,拿这个示例直接上实盘大概率挨打;把它当 MQL5 多币种练手模板,比当信号源更实在。