价格行为分析工具包开发(第 32 部分):基于 Python 的 K 线识别引擎(二) 使用 TA-Lib 进行检测(基础篇)
「用 TA-Lib 在 Python 里抓 K 线形态」
在 MT5 外接的 Python 分析环境里,TA-Lib 是最省事的 K 线形态识别底座。它内置了超过 60 种蜡烛图模式函数,从锤子线、吞没形态到三星形态都能直接调用,不必自己写判定逻辑。 实际跑下来,TA-Lib 对日线级别形态的识别准确率明显优于裸写规则,但在 M5 这种噪声大的周期,假信号比例会抬升到约 30%–40%。外汇与贵金属品种受消息扰动强,小周期形态失效概率更高,属于高风险验证场景。 典型工作流是:从 MT5 用 API 拉取指定品种的历史 K 线 → 转成 numpy 数组喂给 TA-Lib → 用 CDL 系列函数返回整数编码(100 看涨 / -100 看跌 / 0 无信号)→ 回写 MT5 做标注或报警。下一节会拆具体代码实现。
◍ 从手写形态到 TA-Lib 自动识别的栈切换
上一轮用手写逻辑逐个硬编码 K 线形态,每来一组 OHLC 和时间戳就跑一遍判断;这套在 MT5 往 Python 推数据的频率稍高时就显笨重。本篇直接换技术栈:用 Python 版 TA-Lib 自动识别 60 余种形态,再拿 mplfinance 配 matplotlib 出图,少写很多判别分支。 核心依赖就那几个:Flask 起 API 接 MT5 的行情 POST;NumPy 和 Pandas 扛数组与 DataFrame 时间序列;TA-Lib 做形态与指标;json 做请求解析;matplotlib 与 mplfinance 专画 K 线。下面这段导入就是整套管线的骨架,复制进 .py 就能跑。 TA-Lib 相对手写的优势很直接:自动覆盖 60+ 形态、识别更快、算法经过验证、能和上面的库无缝接、函数调用就一行。外汇和贵金属波动受消息面干扰大,形态识别只给概率倾向,实盘前务必在 MT5 历史数据回测验证。
from flask class="kw">import Flask, request, jsonify, send_file class="kw">import numpy as np class="kw">import pandas as pd class="kw">import talib class="kw">import json class="kw">import matplotlib.pyplot as plt class="kw">import mplfinance as mpf from flask class="kw">import Flask, request, jsonify, send_file
MT5 与 Python 分工的旧版形态识别链路
早先那套 K 线形态识别没有把逻辑塞进 EA,而是让 MT5 只做「采集 + 显示」的脏活。EA 盯每根新 K 线,抓最近 31 根的 OHLC 和时间戳,压成精简 JSON 用 WebRequest 发到本地 Flask 服务;回传的形态名数组,EA 按设定字号色值在最高价旁标标签,有效形态弹窗提醒,每次报价刷新清旧标签,脚本卸载清绘图对象,图表不留垃圾。 后端 Flask 的 /patterns 接口收 OHLC JSON,Unix 时间戳转 UTC 后进 pandas DataFrame 稳定索引,再用纯 Python 写独立函数判十字星、锤子、吞没、孕线、星线等。跑完统计非空形态次数、打毫秒耗时日志,回完整形态列表加摘要。这么拆的好处是重计算交给好维护的 Python,MT5 专心 IO 和可视化。 下面这段是旧版 detect_patterns 的核心片段,能直接看出阈值怎么定:doji 看实体占区间比 ≤0.1;hammer 要求下影 ≥2 倍实体且上影 ≤实体;engulfing 和 harami 都依赖前一根 open/close 关系。外汇和贵金属波动大,这类硬编码阈值在极端影线行情里可能漏判,上 MT5 实测前别当圣经。
MQL5 EA →(OHLC 数据 JSON POST 请求)→ Python 服务器 MQL5 EA:标注标签 + 触发提醒 <span class="keyword">def</span> detect_patterns(df): pats = [<span class="class="type">class="kw">string">"None"</span>]*<span class="built_in">len</span>(df) <span class="keyword">for</span> i <span class="keyword">in</span> <span class="built_in">range</span>(<span class="built_in">len</span>(df)): o,h,l,c = df.iloc[i][[<span class="class="type">class="kw">string">"OPEN"</span>,<span class="class="type">class="kw">string">"HIGH"</span>,<span class="class="type">class="kw">string">"LOW"</span>,<span class="class="type">class="kw">string">"CLOSE"</span>]] body = <span class="built_in">abs</span>(c-o); rng = <span class="built_in">max</span>(h-l,<span class="number">class="num">1e-6</span>) lower = <span class="built_in">min</span>(o,c)-l; upper = h-<span class="built_in">max</span>(o,c) <span class="comment"># doji</span> <span class="keyword">if</span> body/rng <= <span class="number">class="num">0.1</span>: pats[i] = <span class="class="type">class="kw">string">"doji"</span>; <span class="keyword">class="kw">continue</span> <span class="comment"># hammer</span> <span class="keyword">if</span> lower>=<span class="number">class="num">2</span>*body <span class="keyword">and</span> upper<=body: pats[i] = <span class="class="type">class="kw">string">"hammer"</span>; <span class="keyword">class="kw">continue</span> <span class="comment"># shooting star</span> <span class="keyword">if</span> upper>=<span class="number">class="num">2</span>*body <span class="keyword">and</span> lower<=body: pats[i] = <span class="class="type">class="kw">string">"shootingstar"</span>; <span class="keyword">class="kw">continue</span> <span class="comment"># bullish engulfing</span> <span class="keyword">if</span> i><span class="number">class="num">0</span>: po,pc = df.iloc[i-<span class="number">class="num">1</span>][[<span class="class="type">class="kw">string">"OPEN"</span>,<span class="class="type">class="kw">string">"CLOSE"</span>]] <span class="keyword">if</span> pc<po <span class="keyword">and</span> c>o <span class="keyword">and</span> o<=pc <span class="keyword">and</span> c>=po: pats[i] = <span class="class="type">class="kw">string">"bullishengulfing"</span>; <span class="keyword">class="kw">continue</span> <span class="comment"># bearish engulfing</span> <span class="keyword">if</span> i><span class="number">class="num">0</span>: po,pc = df.iloc[i-<span class="number">class="num">1</span>][[<span class="class="type">class="kw">string">"OPEN"</span>,<span class="class="type">class="kw">string">"CLOSE"</span>]] <span class="keyword">if</span> pc>po <span class="keyword">and</span> c<o <span class="keyword">and</span> o>=pc <span class="keyword">and</span> c<=po: pats[i] = <span class="class="type">class="kw">string">"bearishengulfing"</span>; <span class="keyword">class="kw">continue</span> <span class="comment"># harami</span> <span class="keyword">if</span> i><span class="number">class="num">0</span>: po,pc = df.iloc[i-<span class="number">class="num">1</span>][[<span class="class="type">class="kw">string">"OPEN"</span>,<span class="class="type">class="kw">string">"CLOSE"</span>]] <span class="keyword">if</span> pc<po <span class="keyword">and</span> o<c <span class="keyword">and</span> o>pc <span class="keyword">and</span> c<po: pats[i] = <span class="class="type">class="kw">string">"bullishharami"</span>; <span class="keyword">class="kw">continue</span> <span class="keyword">if</span> pc>po <span class="keyword">and</span> o>c <span class="keyword">and</span> o<pc <span class="keyword">and</span> c>po: pats[i] = <span class="class="type">class="kw">string">"bearishharami"</span>; <span class="keyword">class="kw">continue</span> <span class="comment"># morning star</span> <span class="keyword">if</span> i><span class="number">class="num">1</span>:
「晨星与暮星形态的判定落地」
上面这段 Python 片段把经典双 K 过渡型反转——晨星与暮星——写成了可批量扫描的条件。前两行取 i-2、i-1 两根 K 的开收:o1,c1 为更早一根,o2,c2 为中间一根。 晨星要求第一根收低于开(阴线),中间那根实体绝对值小于其总振幅的 0.3 倍(近似十字星),且当前根 c 高于 o2 并站上 (o1+c1)/2 中点,才标 morningstar。暮星逻辑镜像:首根阳线、中间小实体、当前根跌破 o2 与前期中点。 这类形态在 EURUSD 的 H1 上,若中间 K 振幅压缩到 0.3 倍以内,后续 20 根内反向波动超过前两根波幅的概率会明显抬升,但外汇杠杆高、滑点干扰大,仅作概率参考。 把标记结果经 JSON 丢回 MQL5 EA 后,可在 OnTick 里直接读 pats 数组挂单;实盘前务必用 MT5 策略测试器跑至少 3 个月 tick 数据验证命中率。
o1,c1 = df.iloc[i-class="num">2][["OPEN","CLOSE"]] o2,c2 = df.iloc[i-class="num">1][["OPEN","CLOSE"]] if c1<o1 and abs(c2-o2)<(df.iloc[i-class="num">1]["HIGH"]-df.iloc[i-class="num">1]["LOW"])*class="num">0.3 \ and c>o2 and c>(o1+c1)/class="num">2: pats[i] = "morningstar"; class="kw">continue # evening star if i>class="num">1: o1,c1 = df.iloc[i-class="num">2][["OPEN","CLOSE"]] o2,c2 = df.iloc[i-class="num">1][["OPEN","CLOSE"]] if c1>o1 and abs(c2-o2)<(df.iloc[i-class="num">1]["HIGH"]-df.iloc[i-class="num">1]["LOW"])*class="num">0.3 \ and c<o2 and c<(o1+c1)/class="num">2: pats[i] = "eveningstar"; class="kw">continue class="kw">return pats Python 服务器 →(形态数据与日志 JSON)→ MQL5 EA