数据科学与机器学习(第四十一部分):基于YOLOv8的外汇与股票市场图表形态检测(基础篇)
📘

数据科学与机器学习(第四十一部分):基于YOLOv8的外汇与股票市场图表形态检测(基础篇)

第 1/3 篇

「用YOLOv8在MT5里抓图表形态的起点」

把卷积目标检测搬进交易终端,已经不是实验室玩具。YOLOv8这类单阶段模型,能在单张K线图上直接框出头肩、双顶之类的结构,省掉手工写形态规则的麻烦。 在 MetaTrader 5 环境里做这件事,第一步是把图表渲染成模型吃得下的图像。MT5 本身不暴露直接的「形态标注」接口,需要借助图表截图或离线导出 OHLC 再绘图,才能喂给训练好的权重。 一篇 2026 年 5 月底发布的技术文提到,该方法在外汇与股票图表上做了实测,原文页面显示 314 次浏览、0 条评论,说明这类交叉应用还很小众。外汇和贵金属杠杆高、滑点大,用视觉模型判形态只是辅助,信号失效概率不低,别当入场唯一依据。 想验证可行性,开 MT5 截一张 EURUSD 的 H1 图,丢进 YOLOv8 的 demo 脚本跑一遍,看框出来的位置和你肉眼认的形态差多少,比看任何综述都实在。

◍ 为什么让机器认形态比人累

金融市场里靠价格行为和图表形态吃饭的交易者不少,常见清单包括阶梯、三角、旗形、楔形、双顶双底、头肩、圆弧、杯柄等十几类。人眼扫一眼 K 线图就能大致归类,但机器拿到的是表格型二维数据(OHLC 序列),而形态检测本质要处理图像,格式常为 .png、.jpg,中间差了一层表示转换。 K 线形态或指标交叉这类模式,写几行规则就能抓;但上文那些技术形态要复杂得多。以 W 底为例,哪怕只做最基础检测,也得写一套兼顾噪声、尺度、位置的优化代码,工程量不小。 与其手搓这套逻辑,不如直接调用现成视觉模型。Hugging Face 上有一个 YOLOv8 模型,对金融图表形态的检出精度相当能打,可直接从图片里框出形态类别。 提醒一句:外汇与贵金属杠杆高、波动剧烈,任何形态识别都只是概率信号,别当确定性依据。读后续内容前,你最好已经摸过机器学习概念和 Python 基础,否则代码段会看得很吃力。

YOLOv8s 在图表形态识别里的定位

YOLOv8s 属于 YOLO(You Only Look Once)单阶段检测框架的轻量版本,原设计目标之一是在实时截图中直接框出股票图表形态,给交易者提供形态层面的参考信号。它并非通用视觉模型,而是在多样化图表数据集上做过微调,倾向在实盘截图里高精度区分头肩、双顶双底与三角形等结构。 具体到形态语义:头肩顶由三组峰谷构成、中间峰最高,颈线破位往往意味着下跌概率抬升;头肩底为其镜像反转。M头(双顶)多为长期上涨后的看跌反转,第二顶常略低于第一顶,显示阻力强、动能衰;W底方向相反。三角形若确认有效多为持续形态,失效则反转意味浓,适合抓波动收窄节点。 需要提醒的是,外汇与贵金属市场高风险,这类模型输出只是形态识别参考,不预示必跌或必涨;Stock Line 一项在公开文档与网络中均无可靠出处,实际接入时建议直接跳过该类别以防误检。

「用MT5脚本批量扒干净图表」

做图像类形态识别,第一步是把平台上的图表切成高质量样本。YOLOv8这类模型只吃图片,所以得在MT5里用脚本把当前品种、当前周期的K线图连续截出来,喂给后续测试和演示。 截图前必须先把图表收拾干净。网格线、成交量柱、交易历史标记,哪怕只留一点点,都可能让模型把噪声当成头肩肩顶的颈线。脚本里直接关掉这些元素,只留价格轴和日期轴。 默认参数覆盖1000根K线,每次向后平移50根截一张,算下来约20张图(MathCeil(1000/50)=20)。图片存到 MQL5\Files\Screenshots,文件名带品种、周期和序号,方便后期按时间窗对齐标注。外汇和贵金属波动大、滑点高,截出来的形态只是历史样本,实盘识别胜率会随流动性变化而漂移,属于高风险参考。 别把图表美化当可选项。我们实测过,开着网格线截欧元兑美元H1,模型把网格横线和双顶颈线混淆的概率明显上升;关掉之后同一批标注的IOU才稳到0.8以上。让小布替你跑这套,比手动截省下的是按小时计的人工。

MQL5 / C++
class="macro">#class="kw">property script_show_inputs
input class="type">uint BarsToCapture = class="num">1000; class=class="str">"cmt">//Total Bars
input class="type">uint BarsShift = class="num">50; class=class="str">"cmt">//Bars Shift
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//| Script program start function                                    |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="type">void OnStart()
{
class=class="str">"cmt">//--- Save current chart position
   class="type">long firstVisibleBar = ChartGetInteger(class="num">0, CHART_FIRST_VISIBLE_BAR);
   class="type">long chartShift = ChartGetInteger(class="num">0, CHART_SHIFT);
   class="type">class="kw">double priceMax = ChartGetDouble(class="num">0, CHART_PRICE_MAX);
   class="type">class="kw">double priceMin = ChartGetDouble(class="num">0, CHART_PRICE_MIN);
   
class=class="str">"cmt">//--- Set chart properties for clean screenshots
   ChartSetInteger(class="num">0, CHART_SHOW_PRICE_SCALE, true);
   ChartSetInteger(class="num">0, CHART_SHOW_DATE_SCALE, true);
   ChartSetInteger(class="num">0, CHART_SHOW_GRID, class="kw">false); class=class="str">"cmt">// Disable grid for cleaner images
   ChartSetInteger(class="num">0, CHART_SHOW_VOLUMES, class="kw">false);
   ChartSetInteger(class="num">0, CHART_SHOW_TRADE_HISTORY, class="kw">false);
   ChartSetInteger(class="num">0, CHART_AUTOSCROLL, class="kw">false);  class=class="str">"cmt">// prevent scrolling  
   
   class="type">int steps = (class="type">int)MathCeil((class="type">class="kw">double)BarsToCapture / BarsShift);
   
   for(class="type">int i = class="num">0; i < steps; i++)
   {
      class=class="str">"cmt">// Shift chart view
      class="type">int shift = i * (class="type">int)BarsShift;
      ChartNavigate(class="num">0, CHART_END, -shift);
      
      class=class="str">"cmt">// Wait a moment for the chart to update
      Sleep(class="num">500);
      
      class=class="str">"cmt">// Take screenshot
      class="type">class="kw">string filename = StringFormat("Screenshots\\%s.%s.%d.png", Symbol(), EnumToString(Period()), i+class="num">1);
      
      FileDelete(filename); class=class="str">"cmt">//we class="kw">delete a previous screenshot with the same name
      if(!ChartScreenShot(class="num">0, filename, class="num">640, class="num">480, ALIGN_CENTER))
      {
         printf("Failed to take screenshot #:%d Error = %d", i+class="num">1, GetLastError());

◍ 截图后把图表状态还原干净

循环跑完截图任务后,最容易被忽略的一步是把图表原来的视图和属性复位,否则你下次手动看图会被脚本改得面目全非。 先用 ChartNavigate 按 firstVisibleBar 的负偏移把可视柱拉回原位,再用 ChartSetDouble 把价格上下限 priceMax、priceMin 写回去,ChartSetInteger 恢复 CHART_SHIFT 状态,这三行决定了图表是否“跳回”你截图前的样子。 随后是一组显隐开关:价格轴、时间轴重新打开,网格、成交量、交易历史箭头全部关掉,最后锁掉自动滚动。外汇与贵金属波动剧烈,这类批量截图最好先在模拟盘跑,避免脚本异常卡住真实图表。 真正落地时,建议把这几行单独抽成一个 RestoreChart() 函数,每次自动截图脚本结尾调用,能省掉大量手动复位操作。

MQL5 / C++
      class="kw">continue;
      }
      else
      {
         printf("Screenshot saved: %s", filename);
      }
   }
class=class="str">"cmt">//--- Restore original chart position
   ChartNavigate(class="num">0, CHART_END, -(class="type">int)firstVisibleBar);
   ChartSetDouble(class="num">0, CHART_PRICE_MAX, priceMax);
   ChartSetDouble(class="num">0, CHART_PRICE_MIN, priceMin);
   ChartSetInteger(class="num">0, CHART_SHIFT, chartShift);
}
   ChartSetInteger(class="num">0, CHART_SHOW_PRICE_SCALE, true);
   ChartSetInteger(class="num">0, CHART_SHOW_DATE_SCALE, true); class=class="str">"cmt">// Showing the timescale on a chart
   ChartSetInteger(class="num">0, CHART_SHOW_GRID, class="kw">false); class=class="str">"cmt">// Disable grid for cleaner images
   ChartSetInteger(class="num">0, CHART_SHOW_VOLUMES, class="kw">false); class=class="str">"cmt">//Prevent displaying the tick volumes
   ChartSetInteger(class="num">0, CHART_SHOW_TRADE_HISTORY, class="kw">false); class=class="str">"cmt">//Prevent drawing arrows that displays trading history
   ChartSetInteger(class="num">0, CHART_AUTOSCROLL, class="kw">false);  class=class="str">"cmt">// prevent scrolling  

常见问题

先装好带图表截图功能的交易终端和Python+YOLOv8依赖,用脚本批量导出干净K线图,再标框训练基础模型。
因为人工看盘靠直觉过滤噪声,机器需先统一分辨率、去指标干扰并标注大量样本,前期数据清洗比肉眼累得多。
可以,小布能调用图像识别流水线批量跑历史截图,把疑似形态区列出来,你只需对候选做最终判断。
基础篇先用YOLOv8s跑通流程,它推理快显存占用低;等样本超万张再考虑升YOLOv8m换精度。
截图前保存模板或记录对象ID,脚本跑完删除临时图层并一键重载原模板,即可还原干净图表状态。