算法交易中的风险管理器·综合运用
(3/3)·从接口到集成测试,用一套类把日内与投资的回撤关进笼子里
用接口搭起风险管理类的骨架
MQL5 不支持多重继承,所以接口得作为基类 RiskManagerBase 的第一个父类来写,这样能把项目压成一条连贯的继承链,不会卡死扩展。 让 RiskManagerBase 去继承前面定义的 IShortStopLoss 接口,纯虚函数没有字段也没有构造/析构,继承时用 private 还是 public 都无所谓,功能往后甩给 RiskManagerAlgo 子类就行。 最终 RiskManagerAlgo 的继承结构靠封装公共方法给出完整能力,图里能看清层次。在拼算法前,只需补上决策工具去跑一遍风控计算逻辑做验证。 下面这段是基类声明的核心一行,注意冒号后直接接接口名:
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| | class=class="str">"cmt">//| RiskManagerBase | class=class="str">"cmt">//+------------------------------------------------------------------+ class RiskManagerBase:IShortStopLoss class=class="str">"cmt">// the purpose of the class is to control risk in terminal
◍ 用日线分形驱动日内开仓信号
把之前手动风控里的分形逻辑搬进 EA,核心不变:日线图上破上分形则买,下破下分形则卖,仓位只在当日开闭。差别在于信号不再手工填,而是由 CFractalsSignal 类从 CiFractals 指标直接读,顺带能在长历史里回测,不用人工造输入。 类里用 ENUM_TIMEFRAMES 存分析周期,和 EA 运行周期解耦;指标交互交给终端自带的 CiFractals 指针,不必重写分形算法。买卖信号各存一份 TradeInputs 结构,因为上下分形谁先被触没法预知,得同时盯着。 构造时把周期锁在 PERIOD_D1——日线波动够大、出形比周月线频繁,给日内盈利留出空间。Upper() 取分形值传索引 3,即前天的前一天,避开当天刚画又消失的假突破。FrUp 不等于 EMPTY_VALUE 才更新 price,空值不覆写,保证结构里永远是有效分形价。 买入判定三条件:fract_Up.price 初值非零、ask >= fract_Up.price、且该分形当天未被标记 done。日内持仓不加仓,所以同一分形每天只发一次信号。Broker 回执成功后才调 BuyDone() 把 done 置 true。卖出对称,比的是 bid < fract_Dn.price。 Process() 每个 tick 刷一次指标,其实可改为新日线柱事件触发更省资源,但实测额外负担有限就先留着。外汇与贵金属杠杆高,这类信号在历史中胜率随品种和时段波动,实盘前务必在 MT5 策略测试器跑通再上。
ENUM_TIMEFRAMES TF; class=class="str">"cmt">// timeframe used CiFractals *cFractals; class=class="str">"cmt">// fractals class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| TradeInputs | class=class="str">"cmt">//+------------------------------------------------------------------+ class="kw">struct TradeInputs { class="type">class="kw">string symbol; class=class="str">"cmt">// symbol class="type">ENUM_POSITION_TYPE direction; class=class="str">"cmt">// direction class="type">class="kw">double price; class=class="str">"cmt">// price class="type">class="kw">datetime tradedate; class=class="str">"cmt">// date class="type">bool done; class=class="str">"cmt">// trigger flag }; TradeInputs fract_Up, fract_Dn; class=class="str">"cmt">// current signal
「把分形信号封装成可复用类」
在 MT5 里做多周期信号耦合,最怕把分形取值散落在 EA 主逻辑里。把上下分形和买卖判定收进一个 CFractalsSignal 类,后面接别的过滤器时只需调 BuySignal() / SellSignal(),主循环能干净不少。 类里 protected 区挂了 ENUM_TIMEFRAMES TF 和 CiFractals* cFractals 两个核心成员:前者决定取哪根周期的裸分形,后者是标准库的分形指标实例。FrUp、FrDn 两个 double 存最近一次识别到的上/下分形价格,fract_Up、fract_Dn 则记录当前触发状态。 构造函数默认把 TF 写死成 PERIOD_D1,也就是说这个类出厂即看日线分形。若你要做 XAUUSD 的 4H 突破回踩,得把 PERIOD_D1 改成 PERIOD_H4 再编译,否则信号周期不对,黄金的高波动特性会让日线分形滞后非常明显。 new CiFractals() 之后必须过 CheckPointer 判空,并且 cFractals.Create(Symbol(),TF) 要返回 true 才算绑定成功。外汇和贵金属杠杆高、滑点跳空频繁,指标实例创建失败若不被捕获,实盘可能静默漏信号。
class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CFractalsSignal | class=class="str">"cmt">//+------------------------------------------------------------------+ class CFractalsSignal { class="kw">protected: ENUM_TIMEFRAMES TF; class=class="str">"cmt">// 使用的图表周期 CiFractals *cFractals; class=class="str">"cmt">// 分形指标实例 TradeInputs fract_Up, fract_Dn; class=class="str">"cmt">// 当前买卖信号状态 class="type">class="kw">double FrUp; class=class="str">"cmt">// 上方分形价格 class="type">class="kw">double FrDn; class=class="str">"cmt">// 下方分形价格 class="kw">public: CFractalsSignal(class="type">void); class=class="str">"cmt">// 构造函数 ~CFractalsSignal(class="type">void); class=class="str">"cmt">// 析构函数 class="type">void Process(); class=class="str">"cmt">// 启动更新的方法 class="type">bool BuySignal(); class=class="str">"cmt">// 买入信号 class="type">bool SellSignal(); class=class="str">"cmt">// 卖出信号 class="type">void BuyDone(); class=class="str">"cmt">// 买入完成 class="type">void SellDone(); class=class="str">"cmt">// 卖出完成 }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| CFractalsSignal | class=class="str">"cmt">//+------------------------------------------------------------------+ CFractalsSignal::CFractalsSignal(class="type">void) { TF = PERIOD_D1; class=class="str">"cmt">// 使用的图表周期 class=class="str">"cmt">//--- fractal class cFractals=new CiFractals(); class=class="str">"cmt">// 创建分形指标实例 if(CheckPointer(cFractals)==POINTER_INVALID || class=class="str">"cmt">// 若实例未创建 或 !cFractals.Create(Symbol(),TF)) class=class="str">"cmt">// 变体未创建
分形信号类的初始化与回收细节
在自定义分形信号类里,初始化失败时要立刻打印 INIT_FAILED 并中止后续流程,避免空指针读写。紧接着对分形缓冲区做 BufferResize(4),把容量定为 4 根 K 线,再调 Refresh() 拉一次最新数据。 起始状态把 FrUp、FrDn 都置为 EMPTY_VALUE,上下分形价位先留空;fract_Up 与 fract_Dn 的 done 标记设 true、price 设 EMPTY_VALUE,相当于告诉主循环“这一轮还没拿到有效分形”。 析构函数里用 CheckPointer(cFractals)!=POINTER_INVALID 判断实例是否真被建出来,只有合法才 delete,防止重复释放炸 EA。Process() 每次被调也先 Refresh() 再算,保证分形数组不是上一 tick 的残影。 外汇与贵金属波动跳空频繁,这类缓冲区若忘了 Resize 或析构漏删,MT5 日志里可能反复报对象泄漏,实盘前务必在策略测试器跑至少 200 根历史棒验证。
Print("INIT_FAILED"); class=class="str">"cmt">// don&class="macro">#x27;t proceed cFractals.BufferResize(class="num">4); class=class="str">"cmt">// resize fractal buffer cFractals.Refresh(); class=class="str">"cmt">// update class=class="str">"cmt">//--- FrUp = EMPTY_VALUE; class=class="str">"cmt">// leveled upper at start FrDn = EMPTY_VALUE; class=class="str">"cmt">// leveled lower at start fract_Up.done = true; class=class="str">"cmt">// fract_Up.price = EMPTY_VALUE; class=class="str">"cmt">// fract_Dn.done = true; class=class="str">"cmt">// fract_Dn.price = EMPTY_VALUE; class=class="str">"cmt">// } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| ~CFractalsSignal | class=class="str">"cmt">//+------------------------------------------------------------------+ CFractalsSignal::~CFractalsSignal(class="type">void) { class=class="str">"cmt">//--- if(CheckPointer(cFractals)!=POINTER_INVALID) class=class="str">"cmt">// if instance was created, class="kw">delete cFractals; class=class="str">"cmt">// class="kw">delete } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Process | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CFractalsSignal::Process(class="type">void) { class=class="str">"cmt">//--- cFractals.Refresh(); class=class="str">"cmt">// update fractals
◍ 上破分形时的买单信号判定
在 MT5 里用分形做突破信号,核心是先拿到实时卖一价,再比对上方分形位。下面这段逻辑直接取当前品种 SYMBOL_ASK,并调用 cFractals.Upper(3) 读取第三根 K 线的上方分形值。 判定条件分两层:第一层检查分形值是否更新且非 EMPTY_VALUE,避免拿到无效数据;第二层才看 ask 是否大于等于分形价,同时 fract_Up.done 仍为 false,确保同一分形只触发一次。 满足条件即 return true,交给后续下单模块处理。外汇与贵金属杠杆高,分形假突破概率不低,实盘前务必在策略测试器用至少 3 个月 tick 数据回测。 代码逐行拆解: // 取当前品种卖一价(买单成交价) double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); // 取第三根 K 线的上方分形值 FrUp=cFractals.Upper(3); // 若分形值变化且不是空值,说明有新分形 if(FrUp != fract_Up.price && FrUp != EMPTY_VALUE) // 若旧分形有效、卖一价≥分形、且未处理过 if(fract_Up.price != EMPTY_VALUE && ask >= fract_Up.price && fract_Up.done == false) // 返回 true 生成信号 return true;
class="type">class="kw">double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); class=class="str">"cmt">// Buy price FrUp=cFractals.Upper(class="num">3); class=class="str">"cmt">// request the current value if(FrUp != fract_Up.price && class=class="str">"cmt">// if the data has been updated FrUp != EMPTY_VALUE) class=class="str">"cmt">// skip empty value if(fract_Up.price != EMPTY_VALUE && class=class="str">"cmt">// skip zero values ask >= fract_Up.price && class=class="str">"cmt">// if the buy price is greater than or equal to the fractal fract_Up.done == false) class=class="str">"cmt">// the signal has not been processed yet { class="kw">return true; class=class="str">"cmt">// generate a signal to process } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| BuySignal | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CFractalsSignal::BuySignal(class="type">void) { class="type">class="kw">double ask = SymbolInfoDouble(Symbol(),SYMBOL_ASK); class=class="str">"cmt">// Buy price class=class="str">"cmt">//--- check fractals update FrUp=cFractals.Upper(class="num">3); class=class="str">"cmt">// request the current value if(FrUp != fract_Up.price && class=class="str">"cmt">// if the data has been updated FrUp != EMPTY_VALUE) class=class="str">"cmt">// skip empty value { fract_Up.price = FrUp; class=class="str">"cmt">// process the new fractal
「向上分形触发后的信号锁与向下分形读取」
向上分形的成交确认,靠的是一个布尔锁。fract_Up.done 在被 BuyDone() 置为 true 之前,BuySignal() 每次 tick 都会重新评估,避免同一分形被反复发单。 BuySignal() 的核心判断只有三条件并列:分形价不是 EMPTY_VALUE、ask 大于等于分形价、且 done 仍为 false。三者同时满足才 return true,否则直接 false,逻辑极薄但够用。 向下方向走的是另一套:SellSignal() 先取 SYMBOL_BID,再用 cFractals.Lower(3) 抓第 3 根 K 线以下的向下分形。这里 Lower(3) 的参数是偏移量,实战里改这个数就能控制回看深度,外汇与贵金属波动大,参数乱调可能放大假信号风险。 把两段拼起来看,类里用 done 标志位隔离已处理信号,是防止 EA 在 MT5 历史回测里重复触发的常见写法。开 MT5 把 Lower(3) 改成 Lower(5) 跑一遍 EURUSD 的 M15,能直接看到触发频率的变化。
fract_Up.done = false; class=class="str">"cmt">// not processed class=class="str">"cmt">//--- check the signal if(fract_Up.price != EMPTY_VALUE class=class="str">"cmt">// skip zero values ask >= fract_Up.price && class=class="str">"cmt">// if the buy price is greater than or equal to the fractal fract_Up.done == false) class=class="str">"cmt">// the signal has not been processed yet { class="kw">return true; class=class="str">"cmt">// generate a signal to process } class="kw">return false; class=class="str">"cmt">// otherwise false } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| BuyDone | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CFractalsSignal::BuyDone(class="type">void) { fract_Up.done = true; class=class="str">"cmt">// processed } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| SellSignal | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CFractalsSignal::SellSignal(class="type">void) { class="type">class="kw">double bid = SymbolInfoDouble(Symbol(),SYMBOL_BID); class=class="str">"cmt">// bid price class=class="str">"cmt">//--- check fractals update FrDn=cFractals.Lower(class="num">3); class=class="str">"cmt">// request the current value if(FrDn != EMPTY_VALUE class=class="str">"cmt">// skip empty value
下破分形后的卖出信号判定
这段逻辑只做一件事:当下行分形(fract_Dn)的价格被刷新,且当前 bid 价小于等于该分形位、且这个信号还没被处理过,就返回 true 触发卖出信号。外汇与贵金属杠杆高,信号失败概率不低,实盘须自行验证。 代码里先比对 FrDn 与已存 fract_Dn.price,不等说明分形数据更新了,于是写入新价格并把 done 标为 false,代表待处理。随后用 bid <= fract_Dn.price 做穿透判断,三者同时满足才 return true。 SellDone() 函数极简,仅把 fract_Dn.done 置 true,作用是通知系统该信号已成交或已忽略,避免同一条分形在下一 tick 重复发信号。开 MT5 把这段嵌进 EA,用 EURUSD 的 M15 跑一周,观察重复信号次数是否归零。
FrDn != fract_Dn.price) class=class="str">"cmt">// if the data has been updated { fract_Dn.price = FrDn; class=class="str">"cmt">// process the new fractal fract_Dn.done = false; class=class="str">"cmt">// not processed } class=class="str">"cmt">//--- check the signal if(fract_Dn.price != EMPTY_VALUE && class=class="str">"cmt">// skip empty value bid <= fract_Dn.price && class=class="str">"cmt">// if the ask price is less than or equal to the fractal AND fract_Dn.done == false) class=class="str">"cmt">// signal has not been processed { class="kw">return true; class=class="str">"cmt">// generate a signal to process } class="kw">return false; class=class="str">"cmt">// otherwise false } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| SellDone | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void CFractalsSignal::SellDone(class="type">void) { fract_Dn.done = true; class=class="str">"cmt">// processed }
◍ 把风险管理器拼进EA并跑历史回测
集成阶段用 #include 把前面写好的 RiskManagerAlgo.mqh、TradeModel.mqh、CFractalsSignal.mqh 三个自定义类,以及终端标准的 Indicators\BillWilliams.mqh 和 Trade\Trade.mqh 一并挂到主文件头部。新增一个 int 型输入参数 inp_sl_in_int 让用户自己填每笔交易可接受的最小止损点数;正式做全自动系统时,更合理的做法是让止损类或波动率类回算这个值,而不是暴露给用户输入。 指针声明和 OnInit 里的初始化是关键:CTrade 对象用 SetTypeFillingBySymbol(Symbol()) 绑定当前品种,SetDeviationInPoints 写死一个偏大值(不做成输入),再给所有仓位打统一 magic。析构函数里判空后 delete 指针清内存,避免 MT5 EA 反复加载卸载时漏内存。 OnTick 主干先调 RiskManagerBase 的 ContoMonitor() 做风控监控,再跑 SlippageCheck()。本例分形信号类只做演示、不设止损、靠 23:00 强平收尾,所以 SlippageCheck 会放行所有单;真要上线,必须等止损非零才发单给经纪商。 买入检查用 CFractalsSignal.BuySignal() 出信号,且 SpreadMonitor(inp_sl_in_int) 点差合规才以 0.1 标准手下单;Buy() 成功立即 BuyDone() 锁信号,失败写日志留待重试。卖出对称处理,尾盘平仓块直接复用前文代码,想持隔夜就注释掉。 回测用 USDJPY、M15、2023 全年逐 tick、无延迟、1:100 杠杆、1万美金起步。优化只动 inp_riskperday(0.1→1,步0.5)、inp_riskperweek(0.5→3)、inp_riskpermonth(2→8)、inp_plandayprofit(0.1→3)、dayProfitControl(false→true),滑点与点差类参数交用户自调。 最优迭代落在 inp_riskperday=0.6、inp_plandayprofit=3.1:计划盈利是风险成本的 5 倍,即冒 0.6% 本金亏损博 3.1% 收益,分形突破日线形态存在正向动量。最差迭代 inp_riskperday=1.1、inp_plandayprofit=0.1,风险没对准动量,资金曲线大幅回撤。外汇与贵金属属高风险,历史优化不代表未来,开 MT5 用表 3 参数自测 USDJPY 才能验证这套风控是否对你有效。
class="macro">#include <RiskManagerAlgo.mqh> class="macro">#include <Indicators\BillWilliams.mqh> class="macro">#include <Trade\Trade.mqh> class="macro">#include <TradeModel.mqh> class="macro">#include <CFractalsSignal.mqh> input group "RiskManagerAlgoExpert" input class="type">int inp_sl_in_int = class="num">2000; class=class="str">"cmt">// inp_sl_in_int - a stop loss level for a separate trade RiskManagerAlgo *RMA; class=class="str">"cmt">// risk manager CTrade *cTrade; class=class="str">"cmt">// trade
「正文」
CFractalsSignal *cFract; <span class="comment">// fractal</span> <span class="comment">//+------------------------------------------------------------------+</span>
| <span class="comment">// | Expert initialization function | </span> |
|---|
<span class="comment">//+------------------------------------------------------------------+</span> <span class="keyword">int</span> <span class="functions">OnInit</span>() { <span class="comment">//---</span> RMA = <span class="keyword">new</span> RiskManagerAlgo(); <span class="comment">// algorithmic risk manager</span> <span class="comment">//---</span> cFract =<span class="keyword">new</span> CFractalsSignal(); &nb
信号触发前的指针回收与下单链路
在每根 K 线或每次 tick 重算前,先对上轮实例做指针有效性判断再 delete,是避免 MT5 EA 内存泄漏的硬动作。下面这段先把 cFract 与 RMA 两个对象按 CheckPointer 结果清理,再重新拉起风控与分形处理。 if(CheckPointer(cFract)!=POINTER_INVALID) // 若 cFract 实例仍存在 { delete cFract; // 释放旧分形对象 } if(CheckPointer(RMA)!=POINTER_INVALID) // 若 RMA 实例仍存在 { delete RMA; // 释放旧风控对象 } RMA.ContoMonitor(); // 启动资金监控 RMA.SlippageCheck(); // 滑点核查 cFract.Process(); // 跑分形识别 真正的交易触发被压在双重条件后:cFract.BuySignal() 给出分形买点,且 RMA.SpreadMonitor(inp_sl_in_int) 确认点差在容忍带内。此时 cTrade.Buy(0.1) 以 0.1 手试单,成交后 BuyDone() 回写状态并打印 "Buy has been done",失败则打印 "Error: buy"。 外汇与贵金属杠杆品种跳空频繁,这套链路里滑点核查不通过就直接不下单,实盘里能砍掉一批假突破损耗。开 MT5 把 inp_sl_in_int 设成你经纪商常态点差上限,回看最近 200 根 H1 就能看出过滤效果。
if(CheckPointer(cFract)!=POINTER_INVALID) class=class="str">"cmt">// if an instance is found, { class="kw">delete cFract; class=class="str">"cmt">// class="kw">delete } class=class="str">"cmt">//--- if(CheckPointer(RMA)!=POINTER_INVALID) class=class="str">"cmt">// if an instance is found, { class="kw">delete RMA; class=class="str">"cmt">// class="kw">delete } } RMA.ContoMonitor(); class=class="str">"cmt">// run the risk manager RMA.SlippageCheck(); class=class="str">"cmt">// check slippage cFract.Process(); class=class="str">"cmt">// start the fractal process if(cFract.BuySignal() && RMA.SpreadMonitor(inp_sl_in_int)) class=class="str">"cmt">// if there is a buy signal if(cTrade.Buy(class="num">0.1)) class=class="str">"cmt">// if Buy executed, { cFract.BuyDone(); class=class="str">"cmt">// the signal has been processed Print("Buy has been done"); class=class="str">"cmt">// notify } else class=class="str">"cmt">// if Buy not executed, { Print("Error: buy"); class=class="str">"cmt">// notify }
◍ 用时间结构与OnTick串起收盘与信号
在MT5 EA里,先抓当前时间再决定动作,是控制隔夜风险最直接的办法。下面这段把 MqlDateTime 结构和 OnTick 主循环拼在一起,逻辑不复杂但少一步就会漏单。 MqlDateTime time_curr; // 声明当前时间结构 TimeCurrent(time_curr); // 向终端请求当前时间并写入结构 if(time_curr.hour >= 23) // 若小时数已到23点及以后 { RMA.AllOrdersClose(); // 调用风险管理器平掉所有持仓 } Comment(RMA.Message()); // 把风险管理器状态打印到图表左上角 上面这段放在 OnTick 之外或之前做环境准备,真正每跳执行的是 OnTick 函数。它依次跑风险监控、滑点检查、分形处理,再判断买信号与点差条件。 void OnTick() { RMA.ContoMonitor(); // 运行账户风险监控 RMA.SlippageCheck(); // 检查当前滑点是否越界 cFract.Process(); // 启动分形计算流程 if(cFract.BuySignal() && RMA.SpreadMonitor(inp_sl_in_int)) // 分形给买信号且点差监控通过 { if(cTrade.Buy(0.1)) // 以0.1手尝试买入 { cFract.BuyDone(); // 标记该信号已成交处理 Print("Buy has been done"); // 向专家日志输出通知 } } } 实盘里把平仓阈值卡在 hour>=23,意味着每天23:00(服务器时区)后不再留仓,外汇与贵金属杠杆品种跳空概率可能抬升,这一刀切能回避部分隔夜缺口。开MT5把这段代码塞进自己的EA模板,改 inp_sl_in_int 那个点差上限参数,就能看信号过滤松紧对成交频率的影响。
class="type">MqlDateTime time_curr; class=class="str">"cmt">// current time structure TimeCurrent(time_curr); class=class="str">"cmt">// request current time if(time_curr.hour >= class="num">23) class=class="str">"cmt">// if end of day { RMA.AllOrdersClose(); class=class="str">"cmt">// close all positions } Comment(RMA.Message()); class=class="str">"cmt">// display the data state in a comment class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert tick function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnTick() { RMA.ContoMonitor(); class=class="str">"cmt">// run the risk manager RMA.SlippageCheck(); class=class="str">"cmt">// check slippage cFract.Process(); class=class="str">"cmt">// start the fractal process if(cFract.BuySignal() && RMA.SpreadMonitor(inp_sl_in_int)) class=class="str">"cmt">// if there is a buy signal { if(cTrade.Buy(class="num">0.1)) class=class="str">"cmt">// if Buy executed, { cFract.BuyDone(); class=class="str">"cmt">// the signal has been processed Print("Buy has been done"); class=class="str">"cmt">// notify } } }
「信号触发后的下单与收盘逻辑」
这段片段承接分形信号判定的后续动作:当 cFract.BuySignal() 返回真,尝试以 0.1 手买入;成交后调用 BuyDone() 标记信号已处理并打印 "Buy has been done",失败则打印 "Error: buy"。卖单分支完全对称,cFract.SellSignal() 为真时以 0.1 手卖出,成交调 SellDone() 并打印 "Sell has been done",失败打印 "Error: sell"。 时间控制上,用 TimeCurrent() 取当前时间结构,若 time_curr.hour >= 23 就调 RMA.AllOrdersClose() 强平所有持仓,避免隔夜跳空的外汇/贵金属高风险暴露。 循环末尾用 Comment(RMA.Message()) 把风险管理器状态直接刷在图表左上角,方便肉眼盯盘。下面逐行拆一遍核心代码。 别把 23 点平仓当万能钥匙 不同品种流动性在美盘尾段骤降,硬平可能滑点放大;黄金和外汇直盘在该时点点差可能扩张 2~3 倍,建议先在策略测试器跑一周 tick 数据看成交偏差。
else class=class="str">"cmt">// if Buy not executed, { Print("Error: buy"); class=class="str">"cmt">// notify } } if(cFract.SellSignal()) class=class="str">"cmt">// if there is a sell signal { if(cTrade.Sell(class="num">0.1)) class=class="str">"cmt">// if sell executed, { cFract.SellDone(); class=class="str">"cmt">// the signal has been processed Print("Sell has been done"); class=class="str">"cmt">// notify } else class=class="str">"cmt">// if sell failed, { Print("Error: sell"); class=class="str">"cmt">// notify } } class="type">MqlDateTime time_curr; class=class="str">"cmt">// current time structure TimeCurrent(time_curr); class=class="str">"cmt">// request current time if(time_curr.hour >= class="num">23) class=class="str">"cmt">// if end of day { RMA.AllOrdersClose(); class=class="str">"cmt">// close all positions } Comment(RMA.Message()); class=class="str">"cmt">// display the data state in a comment }
画得少,看得清
把前面十二节拆开的模型、信号与风控拼回来,结论其实只有一条:策略赚不赚钱,先看风险有没有被标准化。原文给的 RiskManagerAlgo.mq5 约 10.53 KB、RiskManagerBase.mqh 约 61.49 KB,这些文件就是把「每仓止损 + 风险归一」落进代码的载体,而不是又一套圣杯。 外汇和贵金属杠杆高,黑天鹅一来,没挂止损的盈利策略也可能在几根 K 线内变亏损;反过来,同等仓位下用风险管理器约束敞口,亏损策略的回撤倾向会被压住,存活概率更高。 真要验证,就开 MT5 把 ZIP 里的 mqh 拖进 Include,加载 RiskManagerAlgo 跑历史品种,看权益曲线是不是比裸信号平。少盯几个指标,把风险开关焊死在每笔单子上,比到处找新策略实在。