纺锤形图表指标·综合运用
🧵

纺锤形图表指标·综合运用

(3/3)· 把量价纺锤图吃透后,你会发现传统蜡烛图漏掉了成交权重这一层信息

案例拆解新手友好 第 3/3 篇
很多人把纺锤图当成蜡烛图的换皮,其实它多出的 VWMA 和 VR 两个维度直接改写了形态含义。只看开盘高低不看成交量加权,很容易把缩量反弹误判成趋势确认。这篇把绘图底层和策略用法一次讲清。

◍ 图形对象属性与指标迭代的落地写法

在 MT5 自定义指标里,用 ObjectSetInteger 给对象铺属性时,有几行直接决定对象会不会碍事。OBJPROP_HIDDEN 设 true 能把对象名从图表隐藏,OBJPROP_SELECTABLE 设 false 则禁止鼠标拖拽,配合 OBJPROP_BACK 设 false 让标签浮在前端,这三行组合能做出「看得见但碰不到」的标注层。 Y 轴偏移用 OBJPROP_YOFFSET 接变量 shift_y,X 轴偏移 OBJPROP_XOFFSET 写死 0,锚点 OBJPROP_ANCHOR 和时空坐标 OBJPROP_TIME / OBJPROP_PRICE 分开用 SetInteger 与 SetDouble 传,价格坐标必须是 double 类型,否则编译报隐式转换警告。 OnCalculate 里有个硬门槛:rates_total 小于 long_period 就直接 Alert 并返回 0,意味着你的 VR 或 VWMA 周期若大于已加载历史根数,指标不画一根线。实际调试时把 long_period 设成 200,而账户历史只拉了 100 根,就会弹「history data is not loaded」且缓冲区全空。 主循环从 position=prev_calculated-1 起步,若 position 仍小于 long_period 则强行拉到 long_period,避免前段未初始化数据参与。循环内把 high/low 塞进 Buff_up/Buff_down,再按 open 与 close 的大小关系给 Buff_color_up_down 填 0、1、2,分别对应上涨、下跌和 Doji——这三档编号直接喂给直方图着色缓冲区。

MQL5 / C++
  ObjectSetInteger(class="num">0,name,OBJPROP_YSIZE,pix_y_a);class=class="str">"cmt">// Y轴可见范围
  ObjectSetInteger(class="num">0,name,OBJPROP_XOFFSET,class="num">0);class=class="str">"cmt">// X轴无偏移
  ObjectSetInteger(class="num">0,name,OBJPROP_YOFFSET,shift_y);class=class="str">"cmt">// 设置Y轴偏移
  ObjectSetInteger(class="num">0,name,OBJPROP_BACK,class="kw">false);class=class="str">"cmt">// 在前端显示
  ObjectSetInteger(class="num">0,name,OBJPROP_SELECTABLE,class="kw">false);class=class="str">"cmt">// 禁用拖拽模式
  ObjectSetInteger(class="num">0,name,OBJPROP_SELECTED,class="kw">false);
  ObjectSetInteger(class="num">0,name,OBJPROP_HIDDEN,true);class=class="str">"cmt">// 隐藏图形对象的名称
  ObjectSetInteger(class="num">0,name,OBJPROP_ANCHOR,anchor);class=class="str">"cmt">// 设置坐标点
  ObjectSetInteger(class="num">0,name,OBJPROP_TIME,time);class=class="str">"cmt">// 设置时间坐标
  ObjectSetDouble(class="num">0,name,OBJPROP_PRICE,price);class=class="str">"cmt">// 设置价格坐标
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 自定义指标迭代函数                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnCalculate(class="kw">const class="type">int rates_total,
                class="kw">const class="type">int prev_calculated,
                class="kw">const class="type">class="kw">datetime &time[],
                class="kw">const class="type">class="kw">double &open[],
                class="kw">const class="type">class="kw">double &high[],
                class="kw">const class="type">class="kw">double &low[],
                class="kw">const class="type">class="kw">double &close[],
                class="kw">const class="type">long &tick_volume[],
                class="kw">const class="type">long &volume[],
                class="kw">const class="type">int &spread[])
  {
class=class="str">"cmt">//--- 检查历史周期数据的可用性
  if(rates_total<long_period)
     {
      Alert("VR or VWMA period is greater than history data or history data is not loaded.");
      class="kw">return(class="num">0);
     }
class=class="str">"cmt">//--- 查询位置
  class="type">int position=prev_calculated-class="num">1;
  if(position<long_period)position=long_period; class=class="str">"cmt">// 改变位置
class=class="str">"cmt">//--- 缓存计算的主循环 
  for(class="type">int i=position; i<rates_total; i++)
    {
     class=class="str">"cmt">//--- 填充直方图缓存
     Buff_up[i]=high[i];
     Buff_down[i]=low[i];
     if(open[i]<close[i])Buff_color_up_down[i]=class="num">0;class=class="str">"cmt">// 上涨柱形
     if(open[i]>close[i])Buff_color_up_down[i]=class="num">1;class=class="str">"cmt">// 下跌柱形
     if(open[i]==close[i])Buff_color_up_down[i]=class="num">2;class=class="str">"cmt">// Doji 柱形
     class=class="str">"cmt">//--- 填充辅助缓存
     Buff_open[i]=open[i];
     Buff_close[i]=close[i];

VR 与 VWMA 的缓冲区填充逻辑

这段循环负责把时间和成交量比率写进指标缓冲区,并为成交量加权移动平均(VWMA)做累加。先记录当前 BAR 的时间戳,再向后取 ext_period_VR 根 K 线求和 tick_volume,除以循环次数得到平均量,当前量比上均值就是 Buff_VR[i],数值大于 1 代表当期量能相对区间均值放大。 成交量加权均价的计算用 switch 分发到不同价格源:0=开盘、1=最高、2=最低、3=收盘、4=四价平均。每种分支都在 ext_period_VWMA 窗口内把「价格×tick_volume」累加到 price_vol,同时把 tick_volume 累加到 vol,最后 Buff_VWMA[i]=price_vol/vol。 外汇与贵金属市场跳空和散户流量扭曲常见,tick_volume 只是券商侧成交笔数代理,不是真实交易所量。用这套代码时建议先在 XAUUSD 的 M15 上把 ext_period_VR 设为 20、ext_period_VWMA 设为 14 跑一遍,观察 Buff_VR 突破 1.5 后价格延续的概率倾向偏弱反转。

MQL5 / C++
Buff_time[i]=class="type">class="kw">double(time[i]);
class=class="str">"cmt">//--- 计算成交量比例
 class="type">class="kw">double mid_vol=class="num">0;
 class="type">int x=class="num">0;
 for(x=i-ext_period_VR; x<=i; x++)
   {
    mid_vol+=class="type">class="kw">double(tick_volume[x]);
   }
 mid_vol/=x;
 Buff_VR[i]=tick_volume[i]/mid_vol; class=class="str">"cmt">// 计算 VR
class=class="str">"cmt">//--- 计算成交量加权移动平均
 class="type">long vol=class="num">0;
 class="type">class="kw">double price_vol=class="num">0;
 x=class="num">0;
 class="kw">switch(type_price_VWMA)
   {
    case class="num">0:
      {
       for(x=i-ext_period_VWMA; x<=i; x++)
         {
          price_vol+=class="type">class="kw">double(open[x]*tick_volume[x]);
          vol+=tick_volume[x];
         }
      }
     class="kw">break;
class=class="str">"cmt">//---
    case class="num">1:
      {
       for(x=i-ext_period_VWMA; x<=i; x++)
         {
          price_vol+=class="type">class="kw">double(high[x]*tick_volume[x]);
          vol+=tick_volume[x];
         }
      }
     class="kw">break;
class=class="str">"cmt">//---
    case class="num">2:
      {
       for(x=i-ext_period_VWMA; x<=i; x++)
         {
          price_vol+=class="type">class="kw">double(low[x]*tick_volume[x]);
          vol+=tick_volume[x];
         }
      }
     class="kw">break;
class=class="str">"cmt">//---
    case class="num">3:
      {
       for(x=i-ext_period_VWMA; x<=i; x++)
         {
          price_vol+=class="type">class="kw">double(close[x]*tick_volume[x]);
          vol+=tick_volume[x];
         }
      }
     class="kw">break;
class=class="str">"cmt">//---
    case class="num">4:
      {
       for(x=i-ext_period_VWMA; x<=i; x++)
         {
          class="type">class="kw">double price=(open[x]+high[x]+low[x]+close[x])/class="num">4;
          price_vol+=class="type">class="kw">double(price*tick_volume[x]);
          vol+=tick_volume[x];
         }

「VWMA 收尾与新 K 线判定逻辑」

在主循环末尾,VWMA 由 price_vol 除以 vol 得到,并写入 Buff_VWMA[i]。若 type_draw==1,三个扩展缓冲直接承接普通缓冲;否则用 ArrayResize 把扩展数组压到 1 个元素并用 ZeroMemory 归零,避免指标窗口残留无效图形对象。 绘图前用 ChartGetDouble 读取主图价格上限,仅当大于 0 且 type_draw==0 才调用 func_drawing 补画,返回 rates_total 作为下次计算的 prev_calculated。 func_new_bar 靠 CopyTime 取最近一根 K 线开盘时间,与静态变量 old_times 比对。首次加载 old_times 为 0 不触发,之后时间变更才返回 true,复制失败时 copied<=0 则恒为 false。 在 MT5 里把这段挂到自定义指标,切到 M1 周期观察 old_times 更新节奏,能验证「非首根不报新柱」的边界处理是否如预期。外汇与贵金属波动剧烈,指标仅作概率参考,实盘须自担高风险。

MQL5 / C++
      }
      class="kw">break;
      }
    Buff_VWMA[i]=price_vol/vol; class=class="str">"cmt">// 计算 VWMA
      class=class="str">"cmt">//---
      if(type_draw==class="num">1)
      {
      Buff_open_ext[i]=Buff_open[i];
      Buff_close_ext[i]=Buff_close[i];
      Buff_VWMA_ext[i]=Buff_VWMA[i];
      }
      else
      {
      class=class="str">"cmt">//--- 减小未使用数组的大小
      ArrayResize(Buff_open_ext,class="num">1);
      ArrayResize(Buff_close_ext,class="num">1);
      ArrayResize(Buff_VWMA_ext,class="num">1);
      class=class="str">"cmt">//--- 未使用数组归零
      ZeroMemory(Buff_open_ext);
      ZeroMemory(Buff_close_ext);
      ZeroMemory(Buff_VWMA_ext);
      }
     }
   end_bar=rates_total;class=class="str">"cmt">// 定义最近柱形的编号
class=class="str">"cmt">//---
   if(ChartGetDouble(class="num">0,CHART_PRICE_MAX,ChartWindowFind())>class="num">0 && type_draw==class="num">0)class=class="str">"cmt">// 检查开始绘图要用的指标窗口可用数据
    {
    func_drawing(func_check_chart(),ext_spin_num,end_bar);
    }
class=class="str">"cmt">//---返回prev_calculated的值用于下次调用
   class="kw">return(rates_total);
   }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| New Bar 函数                                                     |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool func_new_bar(ENUM_TIMEFRAMES period_time)
  {
  class="kw">static class="type">class="kw">datetime old_times; class=class="str">"cmt">// 存储旧值的变量
  class="type">bool res=class="kw">false;             class=class="str">"cmt">// 分析结果变量
  class="type">class="kw">datetime new_time[class="num">1];       class=class="str">"cmt">// 新的柱形开始时间
  class="type">int copied=CopyTime(_Symbol,period_time,class="num">0,class="num">1,new_time); class=class="str">"cmt">// 将最近一个柱形的时间复制到new_time cell中
  if(copied>class="num">0) class=class="str">"cmt">// 复制数据
    {
    if(old_times!=new_time[class="num">0]) class=class="str">"cmt">// 如果保存的柱形开始时间不等于新的柱形开始时间
      {
      if(old_times!=class="num">0) res=true; class=class="str">"cmt">// 如果不是首次加载,true = new bar
      old_times=new_time[class="num">0];     class=class="str">"cmt">// 保存柱形开始时间
      }
    }
  class="kw">return(res);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| OnDeinit                                                        |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnDeinit(class="kw">const class="type">int reason)
  {
class=class="str">"cmt">//--- 删除已使用过的对象
   if(type_draw==class="num">0)
    {

◍ 用循环清掉自旋标记对象

在 MT5 脚本或 EA 的退出阶段,若之前在图上画了多组自旋(spin)分析标记,手动逐个删除不现实。上面这段用 for 循环从 x=0 跑到 ext_spin_num,把每组带 magic_numb 编号的 4 个对象(后缀 1~4)一次性删掉。 对象名拼法是 "SPC" + 魔法码 + 循环序号 + 后缀,比如 magic_numb=888、x=3 时会删 SPC88831 到 SPC88834。ObjectDelete 第一个参数 0 指当前图表,跑之前确认图表 ID 没传错,否则删不到东西。 外汇和贵金属品种波动大、对象刷新频繁,这类清理逻辑建议在 OnDeinit 或按钮回调里调用,避免残留图形干扰下一轮价格行为判断。

MQL5 / C++
for(class="type">int x=class="num">0; x<=ext_spin_num; x++)
  {
   ObjectDelete(class="num">0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"class="num">1");
   ObjectDelete(class="num">0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"class="num">2");
   ObjectDelete(class="num">0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"class="num">3");
   ObjectDelete(class="num">0,"SPC"+IntegerToString(magic_numb)+IntegerToString(x)+"class="num">4");
  }
 }

用单纺锤EA拆解30种形态的决策树

先把策略放一边,直接看EA怎么靠一根纺锤干活。测试EA只取一种纺锤形、不引入VR成交量比例,也就是只跑由单个纺锤构成的那串模式。按方向、实体与引线关系、VWMA位置三个维度切分,列1看涨跌颜色,列2看实体B和引线S长短,列3把VWMA相对High/Low的位置分成上(1)、下(2)、中(3),其中中(3)还能再按VWMA周期内高低价裂出3-3子类,全部组合恰好30种。 这30种样式在EA里各自对应一个函数的返回值。代码把输入参数、30个模式开关、交易量、止损都摊在头部,指标句柄放尾部;OnInit里初始化句柄,开仓平仓各写一个函数调OrderSend并解析结果结构体,另配func_new_bar判新柱。 真正的逻辑全在OnTick:CopyBuffer填缓存→查当前货币对有无持仓→算实体距、引线距及二者比例传给func_one(对应图7列2)。接着func_two、func_three分别落地列3和3-3,再用switch按颜色进决策树,逐层调到func_work给出买/卖/平/无四种动作,最后交给func_send_order和func_delete_position。func_one到func_three只干一件事:把价格形态转成1/2/3的整数喂给switch。 回测设置记一笔可验数据:EURUSD H1,2013.01.01–2015.01.01共2年,止损1000、MetaQuotes-Demo。仅优化「按模式的操作」和VWMA周期(10–500柱)。首轮0.01手、存款10000,净利138.71、回撤2.74%(约28单位);二轮提至0.1手、存款降到1000、切OHLC on M1,742笔交易、最大回撤252美元、净利1407(月均约60,占总投入6%)。外汇与贵金属属高风险,历史回测好不等于实盘能复现。 裸用指标时规则极简:向上箭头买、向下箭头卖,菱形Doji倾向反转。图13里数字1前连续向上箭头,出现蓝菱形后价格转跌到2,红菱形后又反转向上——这种拐点信号可自己开MT5加载指标对照验证。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                                 EASPC.mq5 |
class=class="str">"cmt">//|                                         Azotskiy Aktiniy ICQ:class="num">695710750 |
class=class="str">"cmt">//|                                         [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#class="kw">property copyright "Azotskiy Aktiniy ICQ:class="num">695710750"
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 绘图类型                                                                |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum type_drawing
  {
  spindles=class="num">0,         class=class="str">"cmt">// 纺锤形
  line_histogram=class="num">1, class=class="str">"cmt">// 线和柱状图
  };
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|  价格类型                                                              |
class=class="str">"cmt">//+------------------------------------------------------------------+
enum type_price
  {
  open=class="num">0,   class=class="str">"cmt">// Open
  high=class="num">1,   class=class="str">"cmt">// High
  low=class="num">2,    class=class="str">"cmt">// Low
  close=class="num">3,  class=class="str">"cmt">// Close
  middle=class="num">4, class=class="str">"cmt">// Middle
  };
class=class="str">"cmt">//--- 输入参数

「EA 输入参数的实际含义与执行开关」

这段声明块定义了策略运行前必须暴露给 MT5 用户的外部参数。Magic 数字设成 65758473787389,作用是让 EA 只管理自己下的单,避免和手动单或其他程序冲突。 成交量比例周期 period_VR=10、修正数 correct_VR=4,配合 VWMA 周期 10,意味着指标层默认用最近 10 根 K 线的加权成交量来过滤信号;spindles_num=10 则要求连续出现 10 个纺锤形才触发后续逻辑。 下单量 lot=0.01、止损 stop=1000 点,对外汇和贵金属都属于高杠杆场景下的极端宽止损,实盘前务必按品种波动重算,贵金属跳空可能直接扫掉宽止损。 下面这 26 个 p1~p26 是逐根 K 线的执行模式开关,取值 1 买 / 2 卖 / 3 平仓 / 4 不动。默认全为 1,代表每根都尝试买入——这显然只是占位,真跑之前得按你的价格行为规则改。

MQL5 / C++
class="kw">input class="type">long magic_numb=class="num">65758473787389; class=class="str">"cmt">// Magic 数字
class="kw">input type_drawing type_draw=class="num">1; class=class="str">"cmt">// 指标绘制类型
class="kw">input class="type">int period_VR=class="num">10; class=class="str">"cmt">// 成交量比例周期
class="kw">input class="type">int correct_VR=class="num">4; class=class="str">"cmt">// 成交量比例修正数
class="kw">input class="type">int period_VWMA=class="num">10; class=class="str">"cmt">// 成交量加权移动平均周期
class="kw">input class="type">int spindles_num=class="num">10; class=class="str">"cmt">// 纺锤形数量
class="kw">input type_price type_price_VWMA=class="num">0; class=class="str">"cmt">// 用于绘制成交量加权移动平均的价格类型
 class=class="str">"cmt">// open=class="num">0; high=class="num">1; low=class="num">2; close=class="num">3; middle=class="num">4
class="kw">input class="type">class="kw">double lot=class="num">0.01; class=class="str">"cmt">// 下单量
class="kw">input class="type">int stop=class="num">1000; class=class="str">"cmt">// 止损
class=class="str">"cmt">//---
class="kw">input class="type">char p1=class="num">1; class=class="str">"cmt">// 执行模式 class="num">1-买, class="num">2-卖, class="num">3-平仓, class="num">4-不执行任何操作
class="kw">input class="type">char p2=class="num">1;
class="kw">input class="type">char p3=class="num">1;
class="kw">input class="type">char p4=class="num">1;
class="kw">input class="type">char p5=class="num">1;
class="kw">input class="type">char p6=class="num">1;
class="kw">input class="type">char p7=class="num">1;
class="kw">input class="type">char p8=class="num">1;
class="kw">input class="type">char p9=class="num">1;
class="kw">input class="type">char p10=class="num">1;
class="kw">input class="type">char p11=class="num">1;
class="kw">input class="type">char p12=class="num">1;
class="kw">input class="type">char p13=class="num">1;
class="kw">input class="type">char p14=class="num">1;
class="kw">input class="type">char p15=class="num">1;
class="kw">input class="type">char p16=class="num">1;
class="kw">input class="type">char p17=class="num">1;
class="kw">input class="type">char p18=class="num">1;
class="kw">input class="type">char p19=class="num">1;
class="kw">input class="type">char p20=class="num">1;
class="kw">input class="type">char p21=class="num">1;
class="kw">input class="type">char p22=class="num">1;
class="kw">input class="type">char p23=class="num">1;
class="kw">input class="type">char p24=class="num">1;
class="kw">input class="type">char p25=class="num">1;
class="kw">input class="type">char p26=class="num">1;
逐行拆一下:第 1 行 input long magic_numb=65758473787389; 声明长整型幻数,EA 靠它识别自有持仓。第 2 行 input type_drawing type_draw=1; 选指标画线样式。第 3~5 行分别是 VR 周期、修正项和 VWMA 周期,全默认 10 附近。第 6 行 spindles_num=10 限定纺锤形计数阈值。第 7~8 行 type_price_VWMA=0 指定用开盘价画 VWMA。第 9~10 行是手数和止损点。从 p1p26 共 26 个 char 输入,每个对应一根 K 线的动作预设,默认全是 1(买)。 把这份代码贴进 MT5 的 EA 输入区,先别接实盘,把 p1~p26 改成 4(不操作),只观察指标绘制和幻数隔离是否符合预期。

MQL5 / C++
class="kw">input class="type">long magic_numb=class="num">65758473787389; class=class="str">"cmt">// Magic 数字
class="kw">input type_drawing type_draw=class="num">1; class=class="str">"cmt">// 指标绘制类型
class="kw">input class="type">int period_VR=class="num">10; class=class="str">"cmt">// 成交量比例周期
class="kw">input class="type">int correct_VR=class="num">4; class=class="str">"cmt">// 成交量比例修正数
class="kw">input class="type">int period_VWMA=class="num">10; class=class="str">"cmt">// 成交量加权移动平均周期
class="kw">input class="type">int spindles_num=class="num">10; class=class="str">"cmt">// 纺锤形数量
class="kw">input type_price type_price_VWMA=class="num">0; class=class="str">"cmt">// 用于绘制成交量加权移动平均的价格类型
 class=class="str">"cmt">// open=class="num">0; high=class="num">1; low=class="num">2; close=class="num">3; middle=class="num">4
class="kw">input class="type">class="kw">double lot=class="num">0.01; class=class="str">"cmt">// 下单量
class="kw">input class="type">int stop=class="num">1000; class=class="str">"cmt">// 止损
class=class="str">"cmt">//---
class="kw">input class="type">char p1=class="num">1; class=class="str">"cmt">// 执行模式 class="num">1-买, class="num">2-卖, class="num">3-平仓, class="num">4-不执行任何操作
class="kw">input class="type">char p2=class="num">1;
class="kw">input class="type">char p3=class="num">1;
class="kw">input class="type">char p4=class="num">1;
class="kw">input class="type">char p5=class="num">1;
class="kw">input class="type">char p6=class="num">1;
class="kw">input class="type">char p7=class="num">1;
class="kw">input class="type">char p8=class="num">1;
class="kw">input class="type">char p9=class="num">1;
class="kw">input class="type">char p10=class="num">1;
class="kw">input class="type">char p11=class="num">1;
class="kw">input class="type">char p12=class="num">1;
class="kw">input class="type">char p13=class="num">1;
class="kw">input class="type">char p14=class="num">1;
class="kw">input class="type">char p15=class="num">1;
class="kw">input class="type">char p16=class="num">1;
class="kw">input class="type">char p17=class="num">1;
class="kw">input class="type">char p18=class="num">1;
class="kw">input class="type">char p19=class="num">1;
class="kw">input class="type">char p20=class="num">1;
class="kw">input class="type">char p21=class="num">1;
class="kw">input class="type">char p22=class="num">1;
class="kw">input class="type">char p23=class="num">1;
class="kw">input class="type">char p24=class="num">1;
class="kw">input class="type">char p25=class="num">1;
class="kw">input class="type">char p26=class="num">1;

◍ EA里怎么挂SPC指标并下市价单

这段脚本把 SPC 自定义指标的句柄和一组双精度缓存数组先声明好,Buff_up / Buff_down 各开 3 格存上下坐标,Buff_VWMA_ext 存成交量加权均线,方便后续读柱体状态。p27~p30 四个 input char 默认都是 1,属于预留开关位,实盘前你可以改成 0 关掉某些分支。 OnInit 里只用 iCustom 把 SPC.ex5 挂到当前图表,传了 magic_numb、period_VR、period_VWMA 等参数,返回 INIT_SUCCEEDED 即算初始化过。注意指标文件名写死成 "SPC.ex5",MT5 终端的 MQL5/Indicators 目录下必须存在该编译文件,否则 handle_SPC 会拿到无效句柄。 func_send_order 是市价单发送封装:用 MqlTradeRequest 填好 action=TRADE_ACTION_DEAL、deviation=3、magic=555、type_filling=ORDER_FILLING_FOK。买单价取 SYMBOL_ASK,卖单取 SYMBOL_BID,止损按 _Point*stop 双向偏移;retcode 为 10008 或 10009 才认为成交成功。外汇与贵金属杠杆高,FOK 模式在流动性差时可能直接拒单,建议先在策略测试器跑一遍看回码分布。

MQL5 / C++
class="kw">input class="type">char  p27=class="num">1;
class="kw">input class="type">char  p28=class="num">1;
class="kw">input class="type">char  p29=class="num">1;
class="kw">input class="type">char  p30=class="num">1;
class=class="str">"cmt">//---
class="type">int handle_SPC; class=class="str">"cmt">// 指标句柄
class="type">long position_type; class=class="str">"cmt">// 持仓类型
class=class="str">"cmt">//--- 指标值缓存
class="type">class="kw">double        Buff_up[class="num">3]; class=class="str">"cmt">// 柱状图上方坐标点位的缓存
class="type">class="kw">double        Buff_down[class="num">3]; class=class="str">"cmt">// 直方图下方坐标点缓存
class="type">class="kw">double        Buff_color_up_down[class="num">3]; class=class="str">"cmt">// 直方图的颜色缓存
class="type">class="kw">double        Buff_open_ext[class="num">3]; class=class="str">"cmt">// 开盘价缓存
class="type">class="kw">double        Buff_close_ext[class="num">3]; class=class="str">"cmt">// 收盘价缓存
class="type">class="kw">double        Buff_VWMA_ext[class="num">3]; class=class="str">"cmt">// 成交量加权移动平均缓存
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| EA初始化函数                                                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">int OnInit()
  {
class=class="str">"cmt">//---
   handle_SPC=iCustom(_Symbol,PERIOD_CURRENT,"SPC.ex5",magic_numb,type_draw,period_VR,correct_VR,period_VWMA,spindles_num,type_price_VWMA);
class=class="str">"cmt">//---
   class="kw">return(INIT_SUCCEEDED);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Send Order 函数                                                   |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">bool func_send_order(ENUM_ORDER_TYPE type_order,class=class="str">"cmt">// 下单类型
                     class="type">class="kw">double volume)             class=class="str">"cmt">// 下单量
  {
   class="type">bool x=class="kw">false; class=class="str">"cmt">// 返回变量
class=class="str">"cmt">//--- 声明用于发送订单的变量
   class="type">MqlTradeRequest order_request={class="num">0};
   class="type">MqlTradeResult order_result={class="num">0};
class=class="str">"cmt">//--- 设置用于发送订单的变量
   order_request.action=TRADE_ACTION_DEAL;
   order_request.deviation=class="num">3;
   order_request.magic=class="num">555;
   order_request.symbol=_Symbol;
   order_request.type=type_order;
   order_request.type_filling=ORDER_FILLING_FOK;
   order_request.volume=volume;
   if(type_order==ORDER_TYPE_BUY)
     {
      order_request.price=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
      order_request.sl=order_request.price-(_Point*stop);
     }
   if(type_order==ORDER_TYPE_SELL)
     {
      order_request.price=SymbolInfoDouble(_Symbol,SYMBOL_BID);
      order_request.sl=order_request.price+(_Point*stop);
     }
class=class="str">"cmt">//--- 发送订单
   class="type">bool y=OrderSend(order_request,order_result);
   if(y!=true)Alert("Order sending error.");
class=class="str">"cmt">//--- 检查结果
   if(order_result.retcode==class="num">10008 || order_result.retcode==class="num">10009) x=true;
   class="kw">return(x);
  }

用反手市价单平掉当前持仓

在 MT5 的 EA 逻辑里,平掉一个已开仓位不一定非要走 OrderSend 的 TRADE_ACTION_DEAL 反向手数闭环,上面这段 func_delete_position 就是直接以对手价发市价单覆盖原有头寸。 函数先 PositionSelect(_Symbol) 锁定当前图表品种持仓,把 POSITION_VOLUME 和 POSITION_TYPE 读出来;若是多单就准备发 SELL,空单则发 BUY,相当于用一笔同量反向成交把仓位归零。 订单请求里 deviation 设了 3 点容差,magic=555 方便后续在终端里筛自己的单,type_filling 用了 ORDER_FILLING_FOK——外汇和贵金属流动性瞬变时 FOK 可能直接拒单,属于高风险下的执行选择。 发单后只看 retcode 10008(已成交)或 10009(已成交但部分挂起)才返回 true,其余情况弹 Alert 报错;开 MT5 把这段代码丢进 EA 测一下,能直观看到平仓成功与否的返回差异。

MQL5 / C++
class="type">bool func_delete_position()
  {
   class="type">bool x=class="kw">false;
class=class="str">"cmt">//--- 标记需要操作的订单
   PositionSelect(_Symbol);
   class="type">class="kw">double vol=PositionGetDouble(POSITION_VOLUME);
   class="type">long type=PositionGetInteger(POSITION_TYPE);
   ENUM_ORDER_TYPE type_order;
   if(type==POSITION_TYPE_BUY)type_order=ORDER_TYPE_SELL;
   else type_order=ORDER_TYPE_BUY;
class=class="str">"cmt">//--- 声明用于发送订单的变量
   class="type">MqlTradeRequest order_request={class="num">0};
   class="type">MqlTradeResult order_result={class="num">0};
class=class="str">"cmt">//--- 设置用于发送订单的变量
   order_request.action=TRADE_ACTION_DEAL;
   order_request.deviation=class="num">3;
   order_request.magic=class="num">555;
   order_request.symbol=_Symbol;
   order_request.type=type_order;
   order_request.type_filling=ORDER_FILLING_FOK;
   order_request.volume=vol;
   if(type_order==ORDER_TYPE_BUY)order_request.price=SymbolInfoDouble(_Symbol,SYMBOL_ASK);
   if(type_order==ORDER_TYPE_SELL)order_request.price=SymbolInfoDouble(_Symbol,SYMBOL_BID);
class=class="str">"cmt">//--- 发送订单
   class="type">bool y=OrderSend(order_request,order_result);
   if(y!=true)Alert("Order sending error.");
class=class="str">"cmt">//--- 检查结果
   if(order_result.retcode==class="num">10008 || order_result.retcode==class="num">10009) x=true;
   class="kw">return(x);
  }

「K线形态与持仓状态的信号分流」

这段逻辑先判断当前货币对是否已有持仓:若 else 分支触发,position_type 被置为 -1,代表该品种当前无敞口,后续信号计算不掺入已有仓位偏好。 随后取索引 2 的 K 线(即倒数第三根已完成蜡烛)做实体与影线比:body 取开盘与收盘差的绝对值,shadow 取上下影线差的绝对值;当 shadow 为 0 时强制赋 1,避免后续 body/shadow 除零崩计算。body_shadow 比值越小,实体相对影线越短,形态越倾向十字或纺锤。 三个 func_one / func_two / func_three 分别吃进 body_shadow、影线区间、VWMA 与开收价,返回 char 型分类码。外层再按 Buff_color_up_down[2] 的 0/1(涨跌色)进入两层 switch:第一层定方向,第二层按 afun_1_1 的 1/2/3 把不同参数组(p1~p30)喂给 func_pre_work。也就是说,同一实体比等级在阳线与阴线里走的是完全不同的参数通道。 在 MT5 里把这段接进 EA 的信号例程,改 Buff_color_up_down 的索引或 func_pre_work 的参数映射,就能直接验证多空分流是否如预期触发。外汇与贵金属杠杆高,信号分流仅描述概率倾向,不代表胜率保证。

MQL5 / C++
else
  {
   position_type=-class="num">1; class=class="str">"cmt">// 当前货币对上无持仓
  }
class=class="str">"cmt">//--- 准备要比较的值
class="type">class="kw">double body=Buff_open_ext[class="num">2]-Buff_close_ext[class="num">2];
body=MathAbs(body);
class="type">class="kw">double shadow=Buff_up[class="num">2]-Buff_down[class="num">2];
shadow=MathAbs(shadow);
if(shadow==class="num">0)shadow=class="num">1;class=class="str">"cmt">// 防止除数为0
class="type">class="kw">double body_shadow=body/shadow;
class=class="str">"cmt">//--- 函数返回值变量
class="type">char afun_1_1=func_one(body_shadow);
class="type">char afun_2_1=func_two(Buff_up[class="num">2],Buff_down[class="num">2],Buff_VWMA_ext[class="num">2]);
class="type">char afun_3_1=func_three(Buff_open_ext[class="num">2],Buff_close_ext[class="num">2],Buff_VWMA_ext[class="num">2]);
 class=class="str">"cmt">//---
 class="kw">switch(class="type">int(Buff_color_up_down[class="num">2]))
  {
   case class="num">0:
     {
      class="kw">switch(afun_1_1)
        {
         case class="num">1:
           func_pre_work(afun_2_1,afun_3_1,p1,p2,p3,p4,p5);
           class="kw">break;
         case class="num">2:
           func_pre_work(afun_2_1,afun_3_1,p6,p7,p8,p9,p10);
           class="kw">break;
         case class="num">3:
           func_pre_work(afun_2_1,afun_3_1,p11,p12,p13,p14,p15);
           class="kw">break;
        }
     }
   class="kw">break;
   case class="num">1:
     {
      class="kw">switch(afun_1_1)
        {
         case class="num">1:
           func_pre_work(afun_2_1,afun_3_1,p16,p17,p18,p19,p20);
           class="kw">break;
         case class="num">2:
           func_pre_work(afun_2_1,afun_3_1,p21,p22,p23,p24,p25);
           class="kw">break;
         case class="num">3:
           func_pre_work(afun_2_1,afun_3_1,p26,p27,p28,p29,p30);
           class="kw">break;
        }
     }
   class="kw">break;

◍ 多模式路由下的下单前置逻辑

这段预处理函数把上游两个判定结果 f_2、f_3 和六种形态参数收拢,按分支把具体 pattern 喂给统一的 func_work。f_2 等于 1 或 2 时直接走 pat_1、pat_2;等于 3 时再按 f_3 的 1/2/3 映射到 pat_3_1 至 pat_3_3,嵌套 switch 让三级信号不会互相串线。 func_work 内部只认 pattern:1 为买、2 为卖。进场前先用 position_type!=-1 判断已有持仓,非零则调 func_delete_position 清仓再发单,避免双向加仓。外汇与贵金属杠杆高,这类强制平旧单再开新单的逻辑若 lot 参数过大,回撤可能迅速放大,实盘前应在 MT5 策略测试器用 2023 年 XAUUSD 的 M15 数据跑一遍确认。 把这套路由直接拷进 EA,重点调 f_2、f_3 的上游赋值;若你的形态判定只产出 1~3,可删掉未用到的 pat 形参,减少栈开销。

MQL5 / C++
class="type">void func_pre_work(class="type">char f_2,    class=class="str">"cmt">// 函数Two的返回结果
                     class="type">char f_3,    class=class="str">"cmt">// 函数Three的结果
                     class="type">char pat_1,  class=class="str">"cmt">// 模式 class="num">1
                     class="type">char pat_2,  class=class="str">"cmt">// 模式 class="num">2
                     class="type">char pat_3_1, class=class="str">"cmt">// 模式 3_1
                     class="type">char pat_3_2, class=class="str">"cmt">// 模式 3_2
                     class="type">char pat_3_3) class=class="str">"cmt">// 模式 3_3
  {
   class="kw">switch(f_2)
    {
     case class="num">1: class=class="str">"cmt">//class="num">1
       func_work(pat_1);
       class="kw">break;
     case class="num">2: class=class="str">"cmt">//class="num">2
       func_work(pat_2);
       class="kw">break;
     case class="num">3:
      {
       class="kw">switch(f_3)
        {
         case class="num">1: class=class="str">"cmt">//3_1
           func_work(pat_3_1);
           class="kw">break;
         case class="num">2: class=class="str">"cmt">//3_2
           func_work(pat_3_2);
           class="kw">break;
         case class="num">3: class=class="str">"cmt">//3_3
           func_work(pat_3_3);
           class="kw">break;
        }
      }
     class="kw">break;
    }
  }
class="type">void func_work(class="type">char pattern)
  {
   class="kw">switch(pattern)
    {
     case class="num">1: class=class="str">"cmt">// 买
       if(position_type!=-class="num">1)func_delete_position();
       func_send_order(ORDER_TYPE_BUY,lot);
       class="kw">break;
     case class="num">2: class=class="str">"cmt">// 卖
       if(position_type!=-class="num">1)func_delete_position();
       func_send_order(ORDER_TYPE_SELL,lot);

K线形态判定的三个底层函数

这段逻辑把信号生成拆成了三个独立函数,分别处理实体影线比、VWMA与高低点关系、以及开收盘相对VWMA的位置。三者都返回 char 类型状态码,供上层 switch 分流成开仓、平仓或无操作。 func_one 以 body_shadow_in 为输入,按 1/3、2/3、1 三档切分:≤1/3 返回 1,介于 1/3~2/3 返回 2,2/3~1 返回 3。这意味着实体占全程比越小越倾向归入第一类形态。 func_two 接收 up、down、VWMA 三个价格:VWMA≥up 返回 1(量加权均线压在最高价上方),VWMA≤down 返回 2,其余返回 3。func_three 则看 open 与 close 是否同侧站上或跌破 VWMA,同上为 1、同下为 2、穿越为 3。 外汇与贵金属波动跳空频繁,VWMA 对跳空响应可能滞后,实盘前应在 MT5 用历史数据跑一遍这三个函数,确认状态码分布是否符合你的形态定义。

MQL5 / C++
      class="kw">break;
      case class="num">3: class=class="str">"cmt">// 平仓
         if(position_type!=-class="num">1)func_delete_position();
         class="kw">break;
      case class="num">4: class=class="str">"cmt">// 无操作
         class="kw">break;
   }
}
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 函数 func_one                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">char func_one(class="type">class="kw">double body_shadow_in)
  {
   class="type">char x=class="num">0; class=class="str">"cmt">// 返回变量
   if(body_shadow_in<=(class="type">class="kw">double(class="num">1)/class="type">class="kw">double(class="num">3))) x=class="num">1;
   if(body_shadow_in>(class="type">class="kw">double(class="num">1)/class="type">class="kw">double(class="num">3)) && body_shadow_in<=(class="type">class="kw">double(class="num">2)/class="type">class="kw">double(class="num">3))) x=class="num">2;
   if(body_shadow_in>(class="type">class="kw">double(class="num">2)/class="type">class="kw">double(class="num">3)) && body_shadow_in<=class="num">1) x=class="num">3;
   class="kw">return(x);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 函数 func_two                                                                 |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">char func_two(class="type">class="kw">double up,class=class="str">"cmt">// high [Buff_up]
              class="type">class="kw">double down,class=class="str">"cmt">// low [Buff_down]
              class="type">class="kw">double VWMA) class=class="str">"cmt">// VWMA [Buff_VWMA_ext]
  {
   class="type">char x=class="num">0; class=class="str">"cmt">// 返回变量
   if(VWMA>=up) x=class="num">1;
   if(VWMA<=down) x=class="num">2;
   else x=class="num">3;
   class="kw">return(x);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| 函数 func_three                                                               |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">char func_three(class="type">class="kw">double open,class=class="str">"cmt">// open [Buff_open_ext]
                class="type">class="kw">double close,class=class="str">"cmt">// close [Buff_close_ext]
                class="type">class="kw">double VWMA) class=class="str">"cmt">// VWMA [Buff_VWMA_ext]
  {
   class="type">char x=class="num">0; class=class="str">"cmt">// 返回变量
   if(open>=VWMA && close>=VWMA) x=class="num">1;
   if(open<=VWMA && close<=VWMA) x=class="num">2;
   else x=class="num">3;
   class="kw">return(x);
  }
class=class="str">"cmt">//+------------------------------------------------------------------+

「纺锤图研究仍是一块未垦地」

这套纺锤形图表指标附带了三个可下载文件:easpc.mq5(26.64 KB)、spc.mq5(77.17 KB)与 reporttester.zip(125.3 KB),想在 MT5 里复现的人直接拖进对应目录即可。作者自陈对底层算法了解有限,但强调该主题涉面极宽、远未被挖透,原始 EA 方案虽测出过不错结果,仍值得论坛继续拆改。 从 2016 到 2017 年的几条读者留言看,外界几乎搜不到「如何交易纺锤形图表」的成体系资料,开发者自己给出的站点 ncm.lv 信息也寥寥。外汇与贵金属品种叠加这类小众工具,回测样本薄、实盘滑点大,高风险属性不会因指标新颖而消失。 下一篇作者打算彻底换题,代码还在规划阶段。若你手上有 NinjaTrader 或脚印图的新想法,私信过去未必被实现,但对方明确说会认真考虑——这或许是小众价格行为工具继续存活的唯一路径。

让小布替你跑这套
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到纺锤图的 VWMA 偏离与 VR 异常提示,把重复劳动交给小布,你专注决策。

常见问题

VR 是当前成交量与选定周期均量的比值,宽度变窄说明当下 tick 成交量低于均值,多空参与倾向萎缩,价格运行概率上更偏震荡。
小布盯盘的品种页已内置基于量价维度的 AIGC 诊断,虽不是原生存图,但会给出等效的 VWMA 与 VR 参考,省去手动挂指标的麻烦。
七种内置类型均无透明区域加自定义填充的需求支持,只能用 Bitmap 对象限定区域绘制,代价是内存占用与重绘复杂度上升。
应在 OnCalculate 尾柱锁定判定,仅对已闭合纺锤取 VR 阈值,外汇贵金属波动大、杠杆高,回测需带点差与滑点校验。
Bitmap 对象高频重绘会拖慢终端,建议按可视范围裁剪,关于指标基础结构见《纺锤形图表指标·基础篇》。