价格行为分析工具包开发(第 32 部分):基于 Python 的 K 线识别引擎(二) 使用 TA-Lib 进行检测·综合运用
把信号坐标算准再丢给绘图层
这段逻辑干的事很直接:根据每根 K 线的 Signal 字段,决定箭头画在哪儿。看涨信号就把标记点放到 Low 下方 price_rng*0.005 的位置,用绿色 "^";看跌则放到 High 上方同比例处,用红色 "v"。price_rng 通常是该品种近期高低差,0.005 这个系数控制箭头离影线的距离,调大一点在黄金这种点差大的品种上更不容易和蜡烛重叠。 绘图时用 mpf.make_addplot 生成散点层,非信号时刻一律填 np.nan,保证图上只出现该出现的箭头。markersize=80 在 1 分钟周期 EURUSD 上观感合适,切到 XAUUSD 日线建议降到 50 左右,否则箭头会盖住半根蜡烛。 图例用手写 Line2D 塞进 axes[0].legend,分别标出买卖蜡烛和买卖信号四种样式,loc='upper left' 对多数横盘品种不挡价格。最后 savefig 用 dpi=100 存本地 PNG,返回前端的是文件名而非图像二进制,接口负载更小;若你本地跑,把 path 改成桌面绝对路径就能直接看图。外汇与贵金属波动剧烈,信号仅代表形态识别结果,实际入场仍倾向结合多周期确认。
if row["Signal"] == "bullish": y, marker, class="type">color = row["Low"] - price_rng*class="num">0.005, "^", "green" else: y, marker, class="type">color = row["High"] + price_rng*class="num">0.005, "v", "red" adds.append(mpf.make_addplot( [y if i == tstamp else np.nan for i in df.index], type="scatter", marker=marker, markersize=class="num">80, class="type">color=class="type">color )) mc = make_marketcolors(up=&class="macro">#x27;green&class="macro">#x27;, down=&class="macro">#x27;red&class="macro">#x27;, edge=&class="macro">#x27;inherit&class="macro">#x27;, wick=&class="macro">#x27;inherit&class="macro">#x27;) style = make_mpf_style(marketcolors=mc, base_mpf_style=&class="macro">#x27;class="kw">default&class="macro">#x27;) fig, axes = mpf.plot(df, type="candle", style=style, title=f"{symbol} Patterns", addplot=adds, volume=False, returnfig=True, tight_layout=True) axes[class="num">0].legend(handles=[ Line2D([class="num">0],[class="num">0],class="type">color=&class="macro">#x27;green&class="macro">#x27;,lw=class="num">4,label=&class="macro">#x27;Buy Candle&class="macro">#x27;), Line2D([class="num">0],[class="num">0],class="type">color=&class="macro">#x27;red&class="macro">#x27;,lw=class="num">4,label=&class="macro">#x27;Sell Candle&class="macro">#x27;), Line2D([class="num">0],[class="num">0],marker=&class="macro">#x27;^&class="macro">#x27;,linestyle=&class="macro">#x27;None&class="macro">#x27;,class="type">color=&class="macro">#x27;green&class="macro">#x27;,markersize=class="num">12,label=&class="macro">#x27;Buy Signal&class="macro">#x27;), Line2D([class="num">0],[class="num">0],marker=&class="macro">#x27;v&class="macro">#x27;,linestyle=&class="macro">#x27;None&class="macro">#x27;,class="type">color=&class="macro">#x27;red&class="macro">#x27;,markersize=class="num">12,label=&class="macro">#x27;Sell Signal&class="macro">#x27;) ], loc=&class="macro">#x27;upper left&class="macro">#x27;, frameon=True) fname = f"pattern_chart_{uuid.uuid4().hex[:class="num">8]}.png" fig.savefig(path := os.path.join(os.path.dirname(__file__), fname), dpi=class="num">100) plt.close(fig) app.logger.info(f"Chart saved: {fname}") class="kw">return jsonify( patterns=[p or "None" for p in detected[::-class="num">1]], signals =[s or "none" for s in signals [::-class="num">1]], log=[], chart=fname )
「实盘日志里能挖出什么信号」
把 EA 挂在 Step Index 的 M15 周期上跑,智能交易选项卡会逐根 K 线推送 JSON:先发 symbol、timeframe 和时间戳数组,再收回形态名与图表路径。每根新 K 线收盘即刷新,同时弹出行情警报,延迟通常在 1 秒内。 从一段真实日志看,2025.07.15 22:56:50 客户端发出 39 根 K 线的数据,服务端回 200 并识别到 39 个形态:含 CDLENGULFING 4 次、CDLHARAMI 6 次、CDLHIKKAKE 5 次,另有 9 个位置标为 None。同秒警报触发——05:45 一根 M15 收出 bullish CDLENGULFING。 K 线图本身用 Python 的 mplfinance 画:绿实体为阳线、红实体为阴线;下方绿标是买入信号、上方红标是卖出信号。Crash 1000 指数同样流程跑通,信号出现后行情有概率延续,也可能快速反转,外汇与指数衍生品波动剧烈,验证时请先用模拟盘。 想自己复现,直接看下面这段日志原文,重点盯 [CSLAB] JSON-OUT 和 HTTP 200 RESP 两行,比对本地 EA 输出是否缺字段。
<span class="number">class="num">2025.07</span><span class="number">.class="num">15</span> <span class="number">class="num">22</span>:<span class="number">class="num">56</span>:<span class="number">class="num">50.294</span> Candlestick Label <span class="number">class="num">2</span> (Step Index,M15) [CSLAB] JSON-OUT: {<span class="class="type">class="kw">string">"symbol"</span>:<span class="class="type">class="kw">string">"Step Index"</span>,<span class="class="type">class="kw">string">"timeframe"</span>:<span class="number">class="num">0</span>,<span class="class="type">class="kw">string">"time"</span>:[<span class="number">class="num">1752558300</span>,<span class="number">class="num">1752559200</span>, <span class="number">class="num">1752560100</span>,<span class="number">class="num">1752561000</span>,<span class="number">class="num">1752561900</span>,<span class="number">class="num">1752562800</span>,<span class="number">class="num">1752563700</span>,<span class="number">class="num">1752564600</span>,<span class="number">class="num">1752565500</span>,<span class="number">class="num">1752566400</span>,<span class="number">class="num">1752567300</span>,<span class="number">class="num">1752568200</span>, <span class="number">class="num">1752569100</span>,<span class="number">class="num">1752570000</span>,<span class="number">class="num">1752570900</span>,<span class="number">class="num">1752571800</span>,<span class="number">class="num">1752572700</span>,<span class="number">class="num">1752573600</span>,<span class="number">class="num">1752574500</span>,<span class="number">class="num">1752575400</span>,<span class="number">class="num">1752576300</span>,<span class="number">class="num">1752577200</span>,<span class="number">class="num">1752578100</span>,<span class="number">class="num">1752579000</span>, <span class="number">class="num">1752579900</span>,<span class="number">class="num">1752580800</span>,<span class="number">class="num">1752581700</span>,<span class="number">class="num">1752582600</span>,<span class="number">class="num">1752583500</span>,<span class="number">class="num">1752584400</span>,<span class="number">class="num">1752585300</span>,<span class="number">class="num">1752586200</span>,<span class="number">class="num">1752587100</span>,<span class="number">class="num">1752588000</span>,<span class="number">class="num">1752588900</span>,<span class="number">class="num">1752589800</span>, <span class="number">class="num">1752590700</span>,<span class="number">class="num">1752591600</span>,<span class="number">class="num">1752592500</span>,<span class="number">class="num">1752593400</span>,<span class="number">class="num">17525943</span> <span class="number">class="num">2025.07</span><span class="number">.class="num">15</span> <span class="number">class="num">22</span>:<span class="number">class="num">56</span>:<span class="number">class="num">50.804</span> Candlestick Label <span class="number">class="num">2</span> (Step Index,M15) [CSLAB] HTTP <span class="number">class="num">200</span> RESP: {<span class="class="type">class="kw">string">"chart"</span>:<span class="class="type">class="kw">string">"pattern_chart_e06f7a61.png"</span>,<span class="class="type">class="kw">string">"log"</span>:[],<span class="class="type">class="kw">string">"patterns"</span>: [<span class="class="type">class="kw">string">"CDLENGULFING"</span>,<span class="class="type">class="kw">string">"CDLHARAMI"</span>,<span class="class="type">class="kw">string">"None"</span>,<span class="class="type">class="kw">string">"CDLCLOSINGMARUBOZU"</span>,<span class="class="type">class="kw">string">"CDLHARAMI"</span>,<span class="class="type">class="kw">string">"CDLHIKKAKE"</span>,<span class="class="type">class="kw">string">"CDLENGULFING"</span>,<span class="class="type">class="kw">string">"CDLHIGHWAVE"</span>,<span class="class="type">class="kw">string">"None"</span>,<span class="class="type">class="kw">string">"CDLLONGLINE"</span>, <span class="class="type">class="kw">string">"CDLHIGHWAVE"</span>,<span class="class="type">class="kw">string">"CDLSHORTLINE"</span>,<span class="class="type">class="kw">string">"CDLSHORTLINE"</span>,<span class="class="type">class="kw">string">"CDLHIKKAKE"</span>,<span class="class="type">class="kw">string">"CDLHARAMI"</span>,<span class="class="type">class="kw">string">"CDLENGULFING"</span>,<span class="class="type">class="kw">string">"CDLDOJI"</span>,<span class="class="type">class="kw">string">"None"</span>,<span class="class="type">class="kw">string">"None"</span>,<span class="class="type">class="kw">string">"None"</span>,<span class="class="type">class="kw">string">"CDLCLOSINGMARUBOZU"</span>, <span class="class="type">class="kw">string">"None"</span>,<span class="class="type">class="kw">string">"CDLBELTHOLD"</span>,<span class="class="type">class="kw">string">"None"</span>,<span class="class="type">class="kw">string">"CDLHARAMI"</span>,<span class="class="type">class="kw">string">"CDLBELTHOLD"</span>,<span class="class="type">class="kw">string">"CDLHIKKAKE"</span>,<span class="class="type">class="kw">string">"CDLENGULFING"</span>,<span class="class="type">class="kw">string">"CDLHIKKAKE"</span>,<span class="class="type">class="kw">string">"CDLHIKKAKE"</span>,<span class="class="type">class="kw">string">"None"</span>,<span class="class="type">class="kw">string">"CDLDOJI"</span>,<span class="class="type">class="kw">string">"CDLHAMMER"</span>, <span class="class="type">class="kw">string">"CDLHARAMI"</span>,<span class="class="type">class="kw">string">"CDLENGULFING"</span> <span class="number">class="num">2025.07</span><span class="number">.class="num">15</span> <span class="number">class="num">22</span>:<span class="number">class="num">56</span>:<span class="number">class="num">50.804</span> Candlestick Label <span class="number">class="num">2</span> (Step Index,M15) <span class="number">class="num">2025.07</span><span class="number">.class="num">15</span> <span class="number">class="num">22</span>:<span class="number">class="num">56</span>:<span class="number">class="num">50.804</span> Candlestick Label <span class="number">class="num">2</span> (Step Index,M15) <span style="background-class="type">color:rgb(class="num">222, class="num">188, class="num">133);">Alert: Step Index PERIOD_CURRENT bullish pattern <span class="class="type">class="kw">string">&class="macro">#x27;CDLENGULFING&class="macro">#x27;</span> at <span class="number">class="num">2025.07</span><span class="number">.class="num">15</span> <span class="number">class="num">05</span>:<span class="number">class="num">45</span></span> <span class="number">class="num">2025</span>-<span class="number">class="num">07</span>-<span class="number">class="num">15</span> <span class="number">class="num">22</span>:<span class="number">class="num">54</span>:<span class="number">class="num">52</span>,<span class="number">class="num">246</span> INFO Received /patterns request <span class="number">class="num">2025</span>-<span class="number">class="num">07</span>-<span class="number">class="num">15</span> <span class="number">class="num">22</span>:<span class="number">class="num">54</span>:<span class="number">class="num">52</span>,<span class="number">class="num">250</span> INFO Loaded <span class="number">class="num">60</span> bars <span class="keyword">for</span> Step Index
◍ 日志里跑出来的形态识别链路
把 MT5 专家日志和本地 HTTP 服务日志叠在一起看,能还原出一套 K 线形态识别的实际跑通路径。上面这段混合日志来自 Crash 1000 Index 的 M15 图表,指标先通过 [CSLAB] JSON-OUT 把 40 根以上 K 线的 Unix 时间戳(如 1752561000 对应 2025.07.15 某根)打包发到本地 127.0.0.1 的 /patterns 接口,接口回 200 后返回图表文件名与逐根形态数组。 返回的 patterns 数组里,CDLBELTHOLD 出现了 14 次,None 占 8 次,CDLENGULFING 与 CDLHARAMI 各 2 次,说明该合成指数在 M15 上更倾向走出实体主导的 Belt Hold 而非复杂反转。最后一条 Alert 在 23:30:02 触发,标记 06:30 这根出现 bearish 的 CDLCLOSINGMARUBOZU——注意这是收盘吞噬阳线,偏空含义,但 Crash 1000 Index 属高波动合成品种,外汇贵金属交易者借镜时须明白这类极端品种的信号噪声比远低于主流货币对。 验证方法很直接:在 MT5 装一个带 HTTP 回调的 Candlestick Label 类指标,把日志级别开到 INFO,观察 /patterns 请求间隔。本例两次 POST 相差约 35 秒,说明重算周期或定时器设置在 30~40 秒一刷,你可以照这个节奏调自己的采样频率。
class="num">2025-class="num">07-class="num">15 class="num">22:class="num">54:class="num">52,class="num">808 INFO Chart saved: pattern_chart_a77f2eec.png class="num">2025-class="num">07-class="num">15 class="num">22:class="num">54:class="num">52,class="num">810 INFO class="num">127.0.class="num">0.1 - - [class="num">15/Jul/class="num">2025 class="num">22:class="num">54:class="num">52] "POST /patterns HTTP/class="num">1.1" class="num">200 - class="num">2025-class="num">07-class="num">15 class="num">22:class="num">55:class="num">00,class="num">040 INFO Received /patterns request class="num">2025.07.class="num">15 class="num">23:class="num">30:class="num">00.299 Candlestick Label class="num">2 (Crash class="num">1000 Index,M15) [CSLAB] JSON-OUT: {"symbol":"Crash class="num">1000 Index","timeframe":class="num">0,"time":[class="num">1752561000,class="num">1752561900, class="num">1752562800,class="num">1752563700,class="num">1752564600,class="num">1752565500,class="num">1752566400,class="num">1752567300,class="num">1752568200,class="num">1752569100,class="num">1752570000,class="num">1752570900,class="num">1752571800,class="num">1752572700,class="num">1752573600,class="num">1752574500,class="num">1752575400 ,class="num">1752576300,class="num">1752577200,class="num">1752578100,class="num">1752579000,class="num">1752579900,class="num">1752580800,class="num">1752581700,class="num">1752582600,class="num">1752583500,class="num">1752584400,class="num">1752585300,class="num">1752586200,class="num">1752587100,class="num">1752588000,class="num">1752588900, class="num">1752589800,class="num">1752590700,class="num">1752591600,class="num">1752592500,class="num">1752593400,class="num">1752594300,class="num">1752595200,class="num">1752596100,class="num">17 class="num">2025.07.class="num">15 class="num">23:class="num">30:class="num">02.749 Candlestick Label class="num">2 (Crash class="num">1000 Index,M15) [CSLAB] HTTP class="num">200 RESP: {"chart":"pattern_chart_ebd72b47.png","log":[],"patterns": ["CDLCLOSINGMARUBOZU","CDLBELTHOLD","CDLBELTHOLD","None","CDLBELTHOLD","CDLHIKKAKE","CDLENGULFING","CDLBELTHOLD","CDLHARAMI","CDLENGULFING","None","CDLBELTHOLD", "None","CDLBELTHOLD","CDLBELTHOLD","CDLBELTHOLD","None","CDLBELTHOLD","CDLBELTHOLD","None","CDLBELTHOLD","CDLBELTHOLD","CDLBELTHOLD","CDLDOJI","CDLBELTHOLD","None", "CDLSHORTLINE","CDLSHORTLINE","None","CDLENGULFING","CDLHARAMI","CDLCLOSINGMARUBOZU","None","CDLMATCHINGL class="num">2025.07.class="num">15 class="num">23:class="num">30:class="num">02.749 Candlestick Label class="num">2 (Crash class="num">1000 Index,M15) class="num">2025.07.class="num">15 class="num">23:class="num">30:class="num">02.769 Candlestick Label class="num">2 (Crash class="num">1000 Index,M15) Alert: Crash class="num">1000 Index PERIOD_CURRENT bearish pattern &class="macro">#x27;CDLCLOSINGMARUBOZU&class="macro">#x27; at class="num">2025.07.class="num">15 class="num">06:class="num">30 class="num">2025-class="num">07-class="num">15 class="num">23:class="num">30:class="num">02,class="num">719 INFO Chart saved: pattern_chart_ebd72b47.png class="num">2025-class="num">07-class="num">15 class="num">23:class="num">30:class="num">02,class="num">735 INFO class="num">127.0.class="num">0.1 - - [class="num">15/Jul/class="num">2025 class="num">23:class="num">30:class="num">02] "POST /patterns HTTP/class="num">1.1" class="num">200 -
别急着下结论
这套混合架构把 MT5 的实时行情、图表对象与警报推送,和 Flask 后端的 TA-Lib 形态计算、mplfinance 可视化切得很干净:EA 只传 60 根 K 线的 OHLC 与时间戳 JSON,后端回吐标注图,EA 侧资源占用压到很低,分析延迟落在亚秒级。 模块拆开后,形态规则、信号过滤、警报逻辑可以各自改而不互相拖垮;用 Docker 封装或挂到带认证的 API 网关后,可移植和安全性还能再提一档。外汇与贵金属波动剧烈,这类自动化流程只降低看盘成本,不消除爆仓风险。 真要落地,先拿自己常做的品种跑通 candlesticks.py 与 Candlestick_Label_2.mq5,叠加一个自定义指标看看标注是否对齐,再考虑并进更大的分析面板。