研究烛条分析技术(第三部分):用于形态操作的函数库·进阶篇
(2/3)· 从研究结论到可复用代码,解决形态分析只能看不能用的断层问题
◍ 烛条结构与形态评分的结构骨架
在 MT5 里做价格行为识别,第一步是把单根烛条和形态评分拆成可复用的结构。下面这段结构定义把每根 K 线的方向、实体长度、归类,以及三段式趋势评分都固化下来,后续直接填数据即可。 单个烛条用 CANDLE_SET 描述:bool m_bull 标记是否为涨势烛条,double m_bodysize 记录实体大小,TYPE_CANDLESTICK m_type 给烛条定类。实盘里实体大小若小于平均值的 0.3 倍,倾向视为犹豫线,不单独作为信号。 RATING_SET 则把上升趋势拆成 m_a_uptrend / m_b_uptrend / m_c_uptrend 三段计数,下降趋势对应 m_a_dntrend 等三项。这种三段切分方便你按近、中、远周期分别加权,避免单看一个斜率被噪音带偏。 类对外暴露 K1/K2/K3 三个权重系数,写法上是典型的 setter/getter 重载:传参即设值,无参调用即取值。开 MT5 新建 EA 时,把这三组 struct 原样贴进头文件,就能直接接着写识别逻辑。
class="type">bool m_bull; class=class="str">"cmt">// 涨势烛条 class="type">class="kw">double m_bodysize; class=class="str">"cmt">// 实体大小 TYPE_CANDLESTICK m_type; class=class="str">"cmt">// 烛条类型 }; class=class="str">"cmt">//--- 形态效率评估属性 class="kw">struct RATING_SET { class="type">int m_a_uptrend; class="type">int m_b_uptrend; class="type">int m_c_uptrend; class="type">int m_a_dntrend; class="type">int m_b_dntrend; class="type">int m_c_dntrend; }; class="kw">public: CPattern(class="type">void); ~CPattern(class="type">void); class=class="str">"cmt">//--- 设置并返回权重系数 class="type">void K1(class="kw">const class="type">class="kw">double k1) { m_k1=k1; } class="type">class="kw">double K1(class="type">void) { class="kw">return(m_k1); } class="type">void K2(class="kw">const class="type">class="kw">double k2) { m_k2=k2; } class="type">class="kw">double K2(class="type">void) { class="kw">return(m_k2); } class="type">void K3(class="kw">const class="type">class="kw">double k3) { m_k3=k3; } class="type">class="kw">double K3(class="type">void) { class="kw">return(m_k3); } class=class="str">"cmt">//--- 设置并返回趋势阈值
烛条识别类的参数读写接口
在 MT5 自定义指标或 EA 里封装价格行为识别逻辑时,通常会用一个类统一管理各类烛条形态的判定系数。下面这组方法就是典型的 getter/setter 模式:用带参函数写入阈值,用无参函数读回当前值。 Threshold(int) 控制整体识别灵敏度的整数门槛,返回时用 Threshold(void) 取回 m_threshold_value。Long_coef / Short_coef / Doji_coef 分别管长体、短体、十字星三类形态的比例系数,都是 double 类型,写入即覆盖成员变量。 剩下几个也同构:Maribozu_coef 对应光头光脚烛,Spin_coef 对应尖顶( spinning top ),Hummer_coef1 与 Hummer_coef2 则给锤子线用了两个独立系数(例如实体占比上限与影线倍率下限)。把这些接口直接贴进你的 .mqh 头文件就能编译,外汇与贵金属波动大、滑点难测,参数改完务必在策略测试器用至少 3 个月 tick 数据回测验证。
class="type">void Threshold(class="kw">const class="type">int threshold) { m_threshold_value=threshold; } class="type">int Threshold(class="type">void) { class="kw">return(m_threshold_value); } class=class="str">"cmt">//--- 设置并返回长体烛条设置系数 class="type">void Long_coef(class="kw">const class="type">class="kw">double long_coef) { m_long_coef=long_coef; } class="type">class="kw">double Long_coef(class="type">void) { class="kw">return(m_long_coef); } class=class="str">"cmt">//--- 设置并返回短体烛条设置系数 class="type">void Short_coef(class="kw">const class="type">class="kw">double short_coef) { m_short_coef=short_coef; } class="type">class="kw">double Short_coef(class="type">void) { class="kw">return(m_short_coef); } class=class="str">"cmt">//--- 设置并返回十字星烛条设置系数 class="type">void Doji_coef(class="kw">const class="type">class="kw">double doji_coef) { m_doji_coef=doji_coef; } class="type">class="kw">double Doji_coef(class="type">void) { class="kw">return(m_doji_coef); } class=class="str">"cmt">//--- 设置并返回 marubozu 烛条设置系数 class="type">void Maribozu_coef(class="kw">const class="type">class="kw">double maribozu_coef) { m_maribozu_coef=maribozu_coef; } class="type">class="kw">double Maribozu_coef(class="type">void) { class="kw">return(m_maribozu_coef); } class=class="str">"cmt">//--- 设置并返回尖顶烛条设置系数 class="type">void Spin_coef(class="kw">const class="type">class="kw">double spin_coef) { m_spin_coef=spin_coef; } class="type">class="kw">double Spin_coef(class="type">void) { class="kw">return(m_spin_coef); } class=class="str">"cmt">//--- 设置并返回锤子烛条设置系数 class="type">void Hummer_coef1(class="kw">const class="type">class="kw">double hummer_coef1) { m_hummer_coef1=hummer_coef1; } class="type">void Hummer_coef2(class="kw">const class="type">class="kw">double hummer_coef2) { m_hummer_coef2=hummer_coef2; }
「锤形线系数的存取与默认权重」
在形态识别类里,锤形线(Hummer)用了两个系数做过滤:coef1 默认 0.1、coef2 默认 2,分别通过 Hummer_coef1() 和 Hummer_coef2() 读出当前值。这两个数是后续判定实体占比与下影线长度比重的基准,直接决定信号触发松紧。 采样范围 Range 默认给到 8000 根烛条,TrendPeriod 默认 5 根用于趋势上下文判断。构造函数里把 m_k1=1、m_k2=0.5、m_k3=0.25 以及 m_doji_coef=0.04、m_maribozu_coef=0.01 等一揽子权重一次性初始化,开 MT5 把这段贴进类定义,改 m_hummer_coef2 从 2 调到 3,能明显看到锤形信号变稀。 外汇与贵金属波动大、杠杆高,这类形态系数只是概率过滤,不等于方向确认,实盘前务必在策略测试器跑通再上真仓。
class="type">class="kw">double Hummer_coef1(class="type">void) { class="kw">return(m_hummer_coef1); } class="type">class="kw">double Hummer_coef2(class="type">void) { class="kw">return(m_hummer_coef2); } class=class="str">"cmt">//--- 设置并返回预设参数的采样范围 class="type">void Range(class="kw">const class="type">int range_total) { m_range_total=range_total; } class="type">int Range(class="type">void) { class="kw">return(m_range_total); } class=class="str">"cmt">//--- 设置并返回计算趋势所需的烛条数量 class="type">void TrendPeriod(class="kw">const class="type">int period) { m_trend_period=period; } class="type">int TrendPeriod(class="type">void) { class="kw">return(m_trend_period); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 构造函数 class=class="str">"cmt">//+------------------------------------------------------------------+ CPattern::CPattern(class="type">void) : m_k1(class="num">1), m_k2(class="num">0.5), m_k3(class="num">0.25), m_threshold_value(class="num">100), m_long_coef(class="num">1.3), m_short_coef(class="num">0.5), m_doji_coef(class="num">0.04), m_maribozu_coef(class="num">0.01), m_spin_coef(class="num">1), m_hummer_coef1(class="num">0.1), m_hummer_coef2(class="num">2), m_range_total(class="num">8000), m_trend_period(class="num">5) { }
◍ 识别单根烛条形态类型的接口
在价格行为分析里,先把每根烛条归到固定形态类别,后续组合信号才有意义。CPattern 类暴露的 CandleType() 就是干这件事的:给定品种、周期和偏移索引,它返回 TYPE_CANDLESTICK 枚举里的一个值,可能是锤子、十字星、Marubozu 等 9 类之一,未识别时给 CAND_NONE。 对外方法只做薄封装,真正计算在私有 GetCandleType() 里完成。它先用 CopyRates 取 shift 往前 aver_period 根数据,用这aver_period根收盘均值判断趋势方向(UPPER/DOWN/FLAT),再算实体绝对值和上下影线长度,填进 CANDLE_STRUCTURE 结构。 注意 aver_period 来自类成员 m_trend_period,默认值多少直接决定“趋势背景”的灵敏度。若你在 EURUSD 的 M15 上跑,把 m_trend_period 从 20 调到 5,短均线的分类会更贴近近期波动,但外汇和贵金属杠杆高、滑点大,误判概率会随之上升,建议开 MT5 用不同周期实测分类差异。 代码逐行拆解:函数声明取 symbol/timeframe/shift 三参数;枚举列出 9 种形态常量;CandleType 先调 GetCandleType 填结构,成功返 m_type 否则 CAND_NONE;GetCandleType 内 CopyRates 取量不足即返 false,随后用 rt[aver_period] 这根算 OHLC,循环累加前 aver_period 根收盘得均值为趋势基准,bull 看开收大小,影线按多空分开算。
TYPE_CANDLESTICK CandleType(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,class="kw">const class="type">int shift); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 烛条类型 | class=class="str">"cmt">//+------------------------------------------------------------------+ enum TYPE_CANDLESTICK { CAND_NONE, class=class="str">"cmt">// 未定义 CAND_MARIBOZU, class=class="str">"cmt">// Marubozu CAND_DOJI, class=class="str">"cmt">// 十字星 CAND_SPIN_TOP, class=class="str">"cmt">// 尖顶 CAND_HAMMER, class=class="str">"cmt">// 锤子 CAND_INVERT_HAMMER, class=class="str">"cmt">// 倒锤子 CAND_LONG, class=class="str">"cmt">// 长体 CAND_SHORT class=class="str">"cmt">// 短体 }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 返回所选烛条的类型 | class=class="str">"cmt">//+------------------------------------------------------------------+ TYPE_CANDLESTICK CPattern::CandleType(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,class="kw">const class="type">int shift) { CANDLE_STRUCTURE res; if(GetCandleType(symbol,timeframe,res,shift)) class="kw">return(res.m_type); class="kw">return(CAND_NONE); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 烛条类型识别 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CPattern::GetCandleType(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,CANDLE_STRUCTURE &res,class="kw">const class="type">int shift) { class="type">MqlRates rt[]; class="type">int aver_period=m_trend_period; class="type">class="kw">double aver=class="num">0; SymbolSelect(symbol,true); class="type">int copied=CopyRates(symbol,timeframe,shift,aver_period+class="num">1,rt); class=class="str">"cmt">//--- 获取前一根烛条的详细信息 if(copied<aver_period) class="kw">return(class="kw">false); class=class="str">"cmt">//--- res.m_open=rt[aver_period].open; res.m_high=rt[aver_period].high; res.m_low=rt[aver_period].low; res.m_close=rt[aver_period].close; class=class="str">"cmt">//--- 判断趋势方向 for(class="type">int i=class="num">0;i<aver_period;i++) aver+=rt[i].close; aver/=aver_period; if(aver<res.m_close) res.m_trend=UPPER; if(aver>res.m_close) res.m_trend=DOWN; if(aver==res.m_close) res.m_trend=FLAT; class=class="str">"cmt">//--- 判断它是涨势还是跌势烛条 res.m_bull=res.m_open<res.m_close; class=class="str">"cmt">//--- 获得烛条实体的绝对大小 res.m_bodysize=MathAbs(res.m_open-res.m_close); class=class="str">"cmt">//--- 获取阴影的大小 class="type">class="kw">double shade_low=res.m_close-res.m_low; class="type">class="kw">double shade_high=res.m_high-res.m_open; if(res.m_bull) { shade_low=res.m_open-res.m_low; shade_high=res.m_high-res.m_close; }
用实体均值给烛条贴类型标签
这段逻辑干的事很直接:先算出前 aver_period 根烛条的平均实体绝对值,作为「常态实体」的基准线。具体做法是把每根 rt[i] 的 open 与 close 差取绝对值累加,再除以周期数,sum 就是动态阈值。 贴标签全靠几个系数和这条基准线比大小。长体要求实体大于 sum 乘 m_long_coef;短体则小于 sum 乘 m_short_coef。十字星更苛刻,实体必须小于整根波幅 HL(高减低)乘 m_doji_coef,说明上下影线吞掉了实体。 剩下几种形态叠加了影线约束。marubozu 要求某一侧影线小于实体乘 m_maribozu_coef 且实体大于 0;锤子与倒锤子分别用 shade_low、shade_high 与实体的 m_hummer_coef1 / m_hummer_coef2 比例来区分方向;尖顶则限定在短体前提下两侧影线都超过实体乘 m_spin_coef。 开 MT5 把 aver_period 设成 14、m_long_coef 试 1.5,能直观看到近期趋势中长体烛条被捕获的概率变化。外汇与贵金属波动跳变频繁,这类阈值对滑点敏感,实盘前务必在策略测试器跑一遍历史段验证。
class="type">class="kw">double HL=res.m_high-res.m_low; class=class="str">"cmt">//--- 计算之前烛条的平均实体大小 class="type">class="kw">double sum=class="num">0; for(class="type">int i=class="num">1; i<=aver_period; i++) sum=sum+MathAbs(rt[i].open-rt[i].close); sum=sum/aver_period; class=class="str">"cmt">//--- 判断烛条类型 res.m_type=CAND_NONE; class=class="str">"cmt">//--- 长体 if(res.m_bodysize>sum*m_long_coef) res.m_type=CAND_LONG; class=class="str">"cmt">//--- 短体 if(res.m_bodysize<sum*m_short_coef) res.m_type=CAND_SHORT; class=class="str">"cmt">//--- 十字星 if(res.m_bodysize<HL*m_doji_coef) res.m_type=CAND_DOJI; class=class="str">"cmt">//--- marubozu if((shade_low<res.m_bodysize*m_maribozu_coef || shade_high<res.m_bodysize*m_maribozu_coef) && res.m_bodysize>class="num">0) res.m_type=CAND_MARIBOZU; class=class="str">"cmt">//--- 锤子 if(shade_low>res.m_bodysize*m_hummer_coef2 && shade_high<res.m_bodysize*m_hummer_coef1) res.m_type=CAND_HAMMER; class=class="str">"cmt">//--- 倒锤子 if(shade_low<res.m_bodysize*m_hummer_coef1 && shade_high>res.m_bodysize*m_hummer_coef2) res.m_type=CAND_INVERT_HAMMER; class=class="str">"cmt">//--- 尖顶 if(res.m_type==CAND_SHORT && shade_low>res.m_bodysize*m_spin_coef && shade_high>res.m_bodysize*m_spin_coef) res.m_type=CAND_SPIN_TOP; class=class="str">"cmt">//--- ArrayFree(rt); class="kw">return(true); }
「形态判定的五组重载入口」
PatternType 在类里开了 5 个重载,调用方可以按手头有什么数据选入口:直接丢 TYPE_PATTERN 枚举值、传枚举数组、或者给 1~3 根简单烛条的索引再加 shift 偏移。返回统一是布尔,告诉你在指定品种和时间框架下,shift 位置起是否命中目标形态。 枚举 TYPE_PATTERN 列了 15 个常量,从 NONE 到 DARK_CLOUD_COVER,覆盖锤子、倒锤、看涨吞没、看跌吞没、十字孕线等常见裸 K 结构。实盘里接外汇或贵金属前先确认品种波动特性,这类形态在美盘流动性切换时假信号概率会抬高,属高风险识别。 单形态判定的实现很薄,核心是把 CheckPattern 的结果和传入 pattern 做相等比较。CheckPattern 内部先清零两根烛条结构再往下走,说明多根烛条形态复用同一套结构体,靠 shift 与索引错位去取不同 K 线。 两根与三根烛条的重载要注意索引偏移约定:两根时 index1 取 shift、index2 取 shift+1;三根依此类推。写 EA 时若手滑把 index2 也传成 shift,会拿到相邻两根同一根 K 线的重复数据,形态永远判不出。
class=class="str">"cmt">//--- 识别形态类型 class="type">bool PatternType(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,TYPE_PATTERN pattern,class="type">int shift); class="type">bool PatternType(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,class="type">int index,class="kw">const class="type">int shift); class="type">bool PatternType(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,class="type">int index1,class="type">int index2,class="kw">const class="type">int shift); class="type">bool PatternType(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,class="type">int index1,class="type">int index2,class="type">int index3,class="kw">const class="type">int shift); class=class="str">"cmt">//--- 识别到形态集合 class="type">bool PatternType(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,TYPE_PATTERN &pattern[],class="type">int shift); class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 形态类型 | class=class="str">"cmt">//+------------------------------------------------------------------+ enum TYPE_PATTERN { NONE, HUMMER, INVERT_HUMMER, HANDING_MAN, SHOOTING_STAR, ENGULFING_BULL, ENGULFING_BEAR, HARAMI_BULL, HARAMI_BEAR, HARAMI_CROSS_BULL, HARAMI_CROSS_BEAR, DOJI_STAR_BULL, DOJI_STAR_BEAR, PIERCING_LINE, DARK_CLOUD_COVER }; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 识别预定义的形态 | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">bool CPattern::PatternType(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,TYPE_PATTERN pattern,class="kw">const class="type">int shift) { if(CheckPattern(symbol,timeframe,shift)==pattern) class="kw">return(true); class="kw">return(class="kw">false); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 检查并返回形态类型 | class=class="str">"cmt">//+------------------------------------------------------------------+ TYPE_PATTERN CPattern::CheckPattern(class="kw">const class="type">class="kw">string symbol,class="kw">const ENUM_TIMEFRAMES timeframe,class="type">int shift) { CANDLE_STRUCTURE cand1,cand2; TYPE_PATTERN pattern=NONE; ZeroMemory(cand1); ZeroMemory(cand2);