纺锤形图表指标·综合运用
(3/3)· 把量价纺锤图吃透后,你会发现传统蜡烛图漏掉了成交权重这一层信息
◍ 图形对象属性与指标迭代的落地写法
在 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——这三档编号直接喂给直方图着色缓冲区。
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 后价格延续的概率倾向偏弱反转。
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 更新节奏,能验证「非首根不报新柱」的边界处理是否如预期。外汇与贵金属波动剧烈,指标仅作概率参考,实盘须自担高风险。
}
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 或按钮回调里调用,避免残留图形干扰下一轮价格行为判断。
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加载指标对照验证。
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,代表每根都尝试买入——这显然只是占位,真跑之前得按你的价格行为规则改。
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;
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 行是手数和止损点。从 p1 到 p26 共 26 个 char 输入,每个对应一根 K 线的动作预设,默认全是 1(买)。
把这份代码贴进 MT5 的 EA 输入区,先别接实盘,把 p1~p26 改成 4(不操作),只观察指标绘制和幻数隔离是否符合预期。
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 模式在流动性差时可能直接拒单,建议先在策略测试器跑一遍看回码分布。
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 测一下,能直观看到平仓成功与否的返回差异。
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 的参数映射,就能直接验证多空分流是否如预期触发。外汇与贵金属杠杆高,信号分流仅描述概率倾向,不代表胜率保证。
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 形参,减少栈开销。
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 用历史数据跑一遍这三个函数,确认状态码分布是否符合你的形态定义。
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 或脚印图的新想法,私信过去未必被实现,但对方明确说会认真考虑——这或许是小众价格行为工具继续存活的唯一路径。