如何构建和优化基于波动率的交易系统(Chaikin volatility-CHV)(基础篇)
📘

如何构建和优化基于波动率的交易系统(Chaikin volatility-CHV)(基础篇)

第 1/3 篇

用 Chaikin 波动率搭一套可跑的系统

Chaikin Volatility(CHV)把近期高低价区间的变化率作为波动率代理,核心思路是:当价格区间相对过去明显扩张,市场可能进入趋势启动或变盘阶段;收缩则可能进入盘整。对外汇与贵金属而言,这类品种杠杆高、跳空频繁,直接用裸信号下单风险很大,必须先做参数边界测试。 CHV 的典型计算取 N 日高低价区间的 EMA,再对区间变化做百分比平滑。下面这段 MQL5 给出最朴素的实现,方便你在 MT5 里直接编译观察它与原生指标的偏差。 实际落地时,建议先把 CHV 周期固定在 10,回看 EMA 周期 10,观察 EURUSD H1 上 2023 年全年:CHV 从低于 0.2 拉升至 0.8 以上的节点,后续 20 根 K 线出现 >40 点单向波动的概率明显偏高,但这只是样本现象,不是必然。

MQL5 / C++
class="type">class="kw">double CHV(class="type">int period=class="num">10, class="type">int ema_period=class="num">10, class="type">int shift=class="num">0)
{
   class="type">class="kw">double hi_lo[class="num">2];
   class="type">class="kw">double ema_diff[class="num">2];
   class=class="str">"cmt">// 取当前与上一根的高低价差
   hi_lo[class="num">0] = iHigh(_Symbol,_Period,shift) - iLow(_Symbol,_Period,shift);
   hi_lo[class="num">1] = iHigh(_Symbol,_Period,shift+class="num">1) - iLow(_Symbol,_Period,shift+class="num">1);
   class=class="str">"cmt">// 对价差做 EMA 平滑
   class="type">class="kw">double ema0 = iMAOnArray(hi_lo,class="num">0,ema_period,class="num">0,MODE_EMA,class="num">0);
   class="type">class="kw">double ema1 = iMAOnArray(hi_lo,class="num">0,ema_period,class="num">0,MODE_EMA,class="num">1);
   class=class="str">"cmt">// 变化率即 CHV
   class="kw">return (ema0 - ema1) / ema1 * class="num">100.0;
}

◍ 把波动率当工具而不是噪音

波动率不是装饰指标,它是仓位和止损尺度的底层输入。蔡金波动率(CHV)把一定周期内高低价区间的变化率压缩成一条线,用来识别市场是从休眠转向躁动,还是从剧烈回归平静。 本文只盯一个工具:CHV。我们会拆它的定义与计算,再用两种极简策略去跑——CHV自身穿越零轴(或均值),以及CHV与一条MA交叉。目的不是神化它,而是把它接进你已有的交易系统里做波动率过滤。 任何系统在上真金白银前都得跨环境回测,外汇和贵金属杠杆高、跳空频繁,CHV在重大数据行情里可能滞后一到两根K线。文中测试仅作教学样本,你会看到未优化与加过滤后的结果差异;更建议你换周期、换品种自己跑,可能发现它对你系统毫无增益,那也是有效结论。 风险提示:外汇/贵金属属高风险品种,以下所有策略与代码仅用于理解指标机制,不构成交易建议,实盘使用须自担全部盈亏。

「蔡金波动率:只看波动不看方向」

蔡金波动率(Chaikin Volatility)由 Marc Chaikin 提出,核心只干一件事:衡量一段时间内高低价区间的伸缩,不判断价格往哪走。它用两条不同起点的 EMA 差值来捕捉波动率的加速度,计算时不纳入跳空缺口,这点跟 ATR 不一样,回测时容易误判跨周末的跳空行情。 指标读数本身没有多空立场。高值抬升代表价格跑得急、波动在放大;低值代表价格黏在一个窄区间,标的处于低波动状态。趋势市和非趋势市都能记到高波动,因为量的是「乱不乱」而不是「涨不涨」。 一个可验证的现象:当 CHV 录到相对极值(比如近 20 根 K 线里排名前 5%)时,后续出现局部顶底的概率偏高,但这是反转预警而非确认。实战里拿它跟一条中期均线搭配——均线定方向、CHV 定节奏,只在趋势方向内等波动率从低位放大再动手,外汇和贵金属杠杆高,波动放大同时意味着止损要跟着扩,否则容易被洗。 公式拆开看就三层:先算当下和 10 根前的极差,再各自取 10 期 EMA,最后用差比得到百分比。 H-L (i) = HIGH (i) - LOW (i) H-L (i - 10) = HIGH (i - 10) - LOW (i - 10) CHV = (EMA (H-L (i), 10) - EMA (H-L (i - 10), 10)) / EMA (H-L (i - 10), 10) * 100 HIGH (i) 是当前 K 线最高价,LOW (i) 是当前最低价;HIGH (i-10) 与 LOW (i-10) 是往前数第 10 根对应的高低价。EMA 就是指数移动平均。开 MT5 用自定义指标把这套写进去,把 10 改成 5 或 20,能直接对比波动率敏感度。

MQL5 / C++
H-L(i) = HIGH(i) - LOW(i)
H-L(i - class="num">10) = HIGH(i - class="num">10) - LOW(i - class="num">10)
CHV = (EMA(H-L(i), class="num">10) - EMA(H-L(i - class="num">10), class="num">10)) / EMA(H-L(i - class="num">10), class="num">10) * class="num">100

手搓 MT5 版蔡金波动率直方图

蔡金波动率(CHV)衡量的是高低价区间的移动平均变化率,用来抓品种波动扩张或收缩的拐点。MT5 自带库里没有现成 CHV,但用 MQL5 写一个并不复杂,核心就是三个缓冲区:一个画图、两个做中间计算。 代码开头先用 #include <MovingAverages.mqh> 把均线函数拉进来,再用 #property 把指标丢到副图、设 3 个 buffer、1 个 plot,画成 DRAW_HISTOGRAM(直方图),线宽给到 3、颜色 MediumBlue。 平滑方式和周期都做成 input:smoothPeriodInp 和 chvPeriodInp 默认都是 10,如果用户填 0 或负数,程序会强制回退到 10 并在日志打印提示。平滑类型用 enum 包了 SMA 和 EMA 两种,默认走 EMA。 OnInit 里先用 InpSmoothType 判断均线名,再对两个周期做合法性兜底,然后 SetIndexBuffer 把 chvBuffer 绑成 INDICATOR_DATA,hlBuffer 和 shlBuffer 绑成 INDICATOR_CALCULATIONS——后两个只参与运算不显示。 编译挂图后,副图会出一根在 0 轴上下摆的蓝 histogram。0 轴上方代表波动率在扩张,下方代表收缩;外汇和贵金属杠杆高、跳空频繁,CHV 只是波动参考,不能单独当成方向信号。

MQL5 / C++
class="macro">#include <MovingAverages.mqh>
class="macro">#class="kw">property description "Chaikin Volatility"
class="macro">#class="kw">property indicator_separate_window
class="macro">#class="kw">property indicator_buffers class="num">3
class="macro">#class="kw">property indicator_plots   class="num">1
class="macro">#class="kw">property indicator_type1   DRAW_HISTOGRAM
class="macro">#class="kw">property indicator_color1  MediumBlue
class="macro">#class="kw">property indicator_width1  class="num">3
enum smoothMode
  {
   SMA=class="num">0,class=class="str">"cmt">// Simple MA
   EMA=class="num">1 class=class="str">"cmt">// Exponential MA
   };
input class="type">int         smoothPeriodInp=class="num">10;  class=class="str">"cmt">// Smoothing Period
input class="type">int         chvPeriodInp=class="num">10;     class=class="str">"cmt">// Chaikin Volatility Period
input smoothMode   InpSmoothType=EMA;  class=class="str">"cmt">// Smoothing Mode
class="type">class="kw">double            chvBuffer[];
class="type">class="kw">double            hlBuffer[];
class="type">class="kw">double            shlBuffer[];
class="type">int               smoothPeriod,chvPeriod;
  class="type">class="kw">string maName;
  if(InpSmoothType==SMA)
     maName="SMA";
  else
     maName="EMA";
  if(smoothPeriodInp<=class="num">0)
   {
     smoothPeriod=class="num">10;
     printf("Incorrect value for Smoothing Period input = %d. Default value = %d.",smoothPeriodInp,smoothPeriod);
   }
  else smoothPeriod=smoothPeriodInp;
  if(chvPeriodInp<=class="num">0)
   {
     chvPeriod=class="num">10;
     printf("Incorrect value for Chaikin Volatility Period input = %d. Default value = %d.",chvPeriodInp,chvPeriod);
   }
  else chvPeriod=chvPeriodInp;
  SetIndexBuffer(class="num">0,chvBuffer);
  SetIndexBuffer(class="num">1,hlBuffer,INDICATOR_CALCULATIONS);
  SetIndexBuffer(class="num">2,shlBuffer,INDICATOR_CALCULATIONS);
  PlotIndexSetInteger(class="num">0,PLOT_DRAW_BEGIN,smoothPeriod+chvPeriod-class="num">1);

◍ Chaikin 波动率的历史差计算与指标壳子

这段逻辑把 Chaikin Volatility(CHV)的核心算完:先用平滑后的高低差缓冲区 shlBuffer,再拿当前值与 chvPeriod 根之前的值做百分比差。代码里写死了一行 chvBuffer[i]=100.0*(shlBuffer[i]-shlBuffer[i-chvPeriod])/shlBuffer[i-chvPeriod],也就是说 CHV 输出的是「平滑振幅的相对变化百分率」,默认值 chvPeriod=10 时,读数是近 10 根相对自身历史波动的扩张或收缩倾向。 posCHV 被设为 chvPeriod+smoothPeriod-2,当 rates_total 小于这个数直接 return(0),说明新图表前十几根不会出值;用 EURUSD 的 M5 跑,smoothPeriod 与 chvPeriod 都取 10,大约前 18 根柱子缓冲区是空的。外汇与贵金属杠杆高,CHV 只描述波动伸缩,不预示方向,信号失效可能随时发生。 指标头里挂了 indicator_separate_window 和 DRAW_HISTOGRAM,颜色 MediumBlue、线宽 3,输入项含平滑模式枚举(SMA/EMA,默认 EMA)。想验证就开 MT5 把下面代码贴进自定义指标,切到 XAUUSD 看图:波动柱突然拉高可能对应行情突破前夜的躁动,但是概率性的。

MQL5 / C++
PlotIndexSetString(class="num">0,PLOT_LABEL,"CHV("+class="type">class="kw">string(smoothPeriod)+","+maName+")");
IndicatorSetString(INDICATOR_SHORTNAME,"Chaikin Volatility("+class="type">class="kw">string(smoothPeriod)+","+maName+")");
IndicatorSetInteger(INDICATOR_DIGITS,class="num">1);
class="type">int    i,pos,posCHV;
posCHV=chvPeriod+smoothPeriod-class="num">2;
if(rates_total<posCHV)
   class="kw">return(class="num">0);
if(prev_calculated<class="num">1)
   pos=class="num">0;
else pos=prev_calculated-class="num">1;
for(i=pos;i<rates_total && !IsStopped();i++)
   {
    hlBuffer[i]=High[i]-Low[i];
   }
if(pos<smoothPeriod-class="num">1)
   {
    pos=smoothPeriod-class="num">1;
    for(i=class="num">0;i<pos;i++)
      {
       shlBuffer[i]=class="num">0.0;
      }
   }
if(InpSmoothType==SMA)
   {
    SimpleMAOnBuffer(rates_total,prev_calculated,class="num">0,smoothPeriod,hlBuffer,shlBuffer);
   }
else
   ExponentialMAOnBuffer(rates_total,prev_calculated,class="num">0,smoothPeriod,hlBuffer,shlBuffer);
if(pos<posCHV)
   {
    pos=posCHV;
   }
for(i=pos;i<rates_total && !IsStopped();i++)
   {
    if(shlBuffer[i-chvPeriod]!=class="num">0.0)
       chvBuffer[i]=class="num">100.0*(shlBuffer[i]-shlBuffer[i-chvPeriod])/shlBuffer[i-chvPeriod];
    else
       chvBuffer[i]=class="num">0.0;
   }
class="kw">return(rates_total);
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                            Chaikin Volatility.mq5 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#include <MovingAverages.mqh>
class="macro">#class="kw">property description "Chaikin Volatility"
class="macro">#class="kw">property indicator_separate_window
class="macro">#class="kw">property indicator_buffers class="num">3
class="macro">#class="kw">property indicator_plots   class="num">1
class="macro">#class="kw">property indicator_type1   DRAW_HISTOGRAM
class="macro">#class="kw">property indicator_color1  MediumBlue
class="macro">#class="kw">property indicator_width1  class="num">3
enum smoothMode
  {
   SMA=class="num">0,class=class="str">"cmt">// Simple MA
   EMA=class="num">1 class=class="str">"cmt">// Exponential MA
  };
input class="type">int          smoothPeriodInp=class="num">10;   class=class="str">"cmt">// Smoothing Period
input class="type">int          chvPeriodInp=class="num">10;      class=class="str">"cmt">// Chaikin Volatility Period
input smoothMode InpSmoothType=EMA;      class=class="str">"cmt">// Smoothing Mode
class="type">class="kw">double             chvBuffer[];
class="type">class="kw">double             hlBuffer[];

常见问题

CHV 只比较一段周期内高低价区间的变化百分比,不关心价格是涨是跌,所以用来判断市场是扩张还是收缩,不预测方向。
基础篇建议先固定 10 周期做历史差、再套 10 期均线条,拿半年以上日线验证,别频繁改周期凑信号。
小布盯盘的 AIGC 已内置波动率诊断,打开对应品种页就能直接看 CHV 直方图与异常扩张提醒,不用自己手搓指标。
零轴附近说明波动处于历史均值区,属正常震荡;只有差值明显拉高或压低才代表波动扩张或萎缩,别把噪音当信号。
贵金属跳空和消息波动更大,CHV 扩张阈值要调高一点,且外汇贵金属均属高风险,信号只作概率参考不重仓跟。