如何构建和优化基于波动率的交易系统(Chaikin volatility-CHV)·综合运用
(3/3)·把CHV指标、穿越策略与系统测试串成一条可跑的交易流水线
用 CHV 信号挂买卖单的止损止盈算法
这段逻辑依赖一个自定义指标 Custom_CHV 的输出值 chvVal,当它大于 0 时倾向做多,小于 0 时倾向做空。外汇与贵金属杠杆高,信号失效时回撤可能快速吃掉本金,实盘前务必在 MT5 策略测试器跑历史数据。 做多时先取当前卖价 SymbolInfoDouble(_Symbol,SYMBOL_ASK),止损放在 ask - slPips*_Point,止盈放在 ask + tpPips*_Point。默认 slPips=300、tpPips=600,也就是用 300 点止损博 600 点空间,风险回报比约 1:2。 做空则反过来取买价 SYMBOL_BID,止损在 bid + slPips*_Point,止盈在 bid - tpPips*_Point。注意多空止损都在现价外侧,避免点差瞬间扫损。 chvVal 与 chvValPrev 都做了 NormalizeDouble(...,1) 保留一位小数,复制缓冲时 ArraySetAsSeries(chvInd,true) 让索引 0 是最新柱。这样 chvInd[0] 与 chvInd[1] 直接对应今根与昨根信号。
class="type">class="kw">double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); class="type">class="kw">double slVal = ask - slPips*_Point; class="type">class="kw">double tpVal = ask + tpPips*_Point; trade.Buy(lotSize,_Symbol,ask,slVal,tpVal); } if(chvVal<class="num">0) { class="type">class="kw">double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); class="type">class="kw">double slVal = bid + slPips*_Point; class="type">class="kw">double tpVal = bid - tpPips*_Point; trade.Sell(lotSize,_Symbol,bid,slVal,tpVal); }
◍ 把 CHV 与均线叠进 EA 的初始化和触发
这段逻辑把 Chaikin Volatility(CHV)和一条移动均线塞进同一个 EA,用新 K 线事件驱动交易判定。参数层先暴露平滑周期 10、CHV 周期 10、均线周期 10 与 EMA 模式,止损 300 点、止盈 600 点,仓位写死 1 手——外汇与贵金属杠杆高,固定手数在极端波动下可能直接击穿账户净值。 OnInit 里用 iCustom 挂自编的 Custom_CHV,iMA 取收盘价 EMA,并记下当前 barsTotal。OnTick 只在 barsTotal 小于实时 bars 时跑,也就是每根新柱才处理一次,避免同根 K 线内重复发单。 触发条件是:前一根收盘低于前一根均线、最新收盘上穿均线、且 CHV 当前值大于 0。满足后按实时 ask 计算 sl/tp 并 trade.Buy。CHV>0 代表波动率扩张,和均线拐头共振时,价格向上突破的概率倾向更高,但是否成交仍取决于盘口流动性。 下面这段是可直接贴进 MT5 编辑器验证的源码骨架,注意 iCustom 的参数顺序必须和 Custom_CHV 指标内部 input 对齐,否则 CopyBuffer 取到的会是空序列。
input class="type">int InpSmoothPeriod=class="num">10; class=class="str">"cmt">// Smoothing period input class="type">int InpCHVPeriod=class="num">10; class=class="str">"cmt">// Chaikin Volatility period input smoothMode smoothTypeInp=EMA; class=class="str">"cmt">// Smoothing Mode input class="type">int InpMAPeriod=class="num">10; class=class="str">"cmt">//MA Period input ENUM_MA_METHOD InpMAMode=MODE_EMA; class=class="str">"cmt">// MA Mode input class="type">class="kw">double lotSize=class="num">1; input class="type">class="kw">double slPips = class="num">300; input class="type">class="kw">double tpPips = class="num">600; class="type">int chv; class="type">int ma; class="type">int barsTotal; CTrade trade; class="type">int OnInit() { barsTotal=iBars(_Symbol,PERIOD_CURRENT); chv = iCustom(_Symbol,PERIOD_CURRENT,"Custom_CHV",InpSmoothPeriod,InpCHVPeriod, smoothTypeInp, lotSize, slPips, tpPips); ma=iMA(_Symbol,PERIOD_CURRENT, InpMAPeriod, class="num">0, InpMAMode, PRICE_CLOSE); class="kw">return(INIT_SUCCEEDED); } class="type">void OnDeinit(const class="type">int reason) { Print("EA is removed"); } class="type">void OnTick() { class="type">int bars=iBars(_Symbol,PERIOD_CURRENT); if(barsTotal < bars) { barsTotal=bars; class="type">class="kw">double chvInd[]; class="type">class="kw">double maInd[]; CopyBuffer(chv,class="num">0,class="num">0,class="num">3,chvInd); ArraySetAsSeries(chvInd,true); CopyBuffer(ma,class="num">0,class="num">0,class="num">3,maInd); ArraySetAsSeries(maInd,true); class="type">class="kw">double chvVal = NormalizeDouble(chvInd[class="num">0], class="num">1); class="type">class="kw">double chvValPrev = NormalizeDouble(chvInd[class="num">1], class="num">1); class="type">class="kw">double maVal = NormalizeDouble(maInd[class="num">0], class="num">5); class="type">class="kw">double maValPrev = NormalizeDouble(maInd[class="num">1], class="num">5); class="type">class="kw">double lastClose=iClose(_Symbol,PERIOD_CURRENT,class="num">1); class="type">class="kw">double prevLastClose=iClose(_Symbol,PERIOD_CURRENT,class="num">2); if(prevLastClose<maValPrev && lastClose>maVal && chvVal>class="num">0) { class="type">class="kw">double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); class="type">class="kw">double slVal = ask - slPips*_Point; class="type">class="kw">double tpVal = ask + tpPips*_Point; trade.Buy(lotSize,_Symbol,ask,slVal,tpVal); }
「均线和收盘价交叉的双向触发写法」
这段逻辑把均线穿越和动量方向绑在一起做进出场:前一根收盘在均线上方、当前收盘掉到均线下方、且动量值 chvVal 小于 0,三个条件同时成立才允许 sell;反过来前收在均线下方、当前收站上均线、chvVal 大于 0 才 buy。这样能过滤掉单纯毛刺穿越带来的假信号,但外汇和贵金属杠杆高,信号失效时回撤可能很快扩大,实盘前务必在 MT5 策略测试器里跑一遍。 下方代码先取当前图表均线句柄,用 CopyBuffer 拉 3 根值并把数组设为时间序列,再拿最近两根收盘价做对比。注意 maInd[0] 是当前柱均线、maInd[1] 是上一根,iClose 的 shift 参数 1 是上一根收盘、2 是再前一根,别写反。 [CODE] int ma; ma=iMA(_Symbol,PERIOD_CURRENT, InpMAPeriod, 0, InpMAMode, PRICE_CLOSE); double maInd[]; CopyBuffer(ma,0,0,3,maInd); ArraySetAsSeries(maInd,true); double maVal = NormalizeDouble(maInd[0], 5); double maValPrev = NormalizeDouble(maInd[1], 5); double lastClose=iClose(_Symbol,PERIOD_CURRENT,1); double prevLastClose=iClose(_Symbol,PERIOD_CURRENT,2); if(prevLastClose<maValPrev && lastClose>maVal && chvVal>0) { double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); double slVal = ask - slPips*_Point; double tpVal = ask + tpPips*_Point; trade.Buy(lotSize,_Symbol,ask,slVal,tpVal); } if(prevLastClose>maValPrev && lastClose<maVal && chvVal<0) { double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); double slVal = bid + slPips*_Point; double tpVal = bid - tpPips*_Point; trade.Sell(lotSize,_Symbol,bid,slVal,tpVal); } [/CODE] 逐行拆一下:iMA 拿句柄时 PERIOD_CURRENT 表示用当前周期,InpMAPeriod 和 InpMAMode 是外部输入的周期数与平滑方式。CopyBuffer 第三个参数 0 是从当前柱开始、取 3 个值。ArraySetAsSeries 设 true 后索引 0 才是最新值。NormalizeDouble(...,5) 把均线四舍五入到 5 位小数,XAUUSD 这类报 3 位价的品种也得统一精度才好比大小。 开仓时 sell 用 SYMBOL_BID 做进场价,sl 放在 bid 上方 slPips*_Point、tp 在 bid 下方;buy 反之用 SYMBOL_ASK,sl 在 ask 下方。slPips / tpPips 单位都是点,乘以 _Point 自动适配品种小数位,黄金和欧美不会混。
class="type">int ma; ma=iMA(_Symbol,PERIOD_CURRENT, InpMAPeriod, class="num">0, InpMAMode, PRICE_CLOSE); class="type">class="kw">double maInd[]; CopyBuffer(ma,class="num">0,class="num">0,class="num">3,maInd); ArraySetAsSeries(maInd,true); class="type">class="kw">double maVal = NormalizeDouble(maInd[class="num">0], class="num">5); class="type">class="kw">double maValPrev = NormalizeDouble(maInd[class="num">1], class="num">5); class="type">class="kw">double lastClose=iClose(_Symbol,PERIOD_CURRENT,class="num">1); class="type">class="kw">double prevLastClose=iClose(_Symbol,PERIOD_CURRENT,class="num">2); if(prevLastClose<maValPrev && lastClose>maVal && chvVal>class="num">0) { class="type">class="kw">double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); class="type">class="kw">double slVal = ask - slPips*_Point; class="type">class="kw">double tpVal = ask + tpPips*_Point; trade.Buy(lotSize,_Symbol,ask,slVal,tpVal); } if(prevLastClose>maValPrev && lastClose<maVal && chvVal<class="num">0) { class="type">class="kw">double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); class="type">class="kw">double slVal = bid + slPips*_Point; class="type">class="kw">double tpVal = bid - tpPips*_Point; trade.Sell(lotSize,_Symbol,bid,slVal,tpVal); }
把CHV接进你自己的EA里跑一遍
前面三篇把CHV指标的算法、MQL5自定义实现和两种穿越策略都拆完了,这一节只说落地。你已经能在MT5里挂上Custom_CHV.mq5,再用chvCrossover.mq5或chv_MA_Crossover.mq5跑策略测试器;ZIP里四个文件总大小不到10KB,下载解压直接编译即可。 回测时建议把CHV周期从默认14调到20,欧美盘波动率结构差异明显,参数偏移后信号频率可能下降但假突破概率倾向降低。外汇与贵金属杠杆高,任何基于历史波动率的系统都只代表过去,实盘前必须用策略测试器做样本外验证。 剩下能做的,是把移动平均线周期也一并优化,或者换RSI做过滤。改进没有终点,CHV只是给你一个量化波动的锚,真正边际优势来自你反复跑、反复调的那几轮。