数据科学与机器学习(第四十一部分):基于YOLOv8的外汇与股票市场图表形态检测·综合运用
(3/3)·从数据获取到模型落地,一篇收束外汇股票形态自动识别的全链路
很多交易者以为截图丢进AI就能自动抓双底头肩,实际从MT5取图到YOLOv8推理有一整条容易踩坑的管道。把图像检测和表格信号混为一谈,往往换来一堆误报和滞后提示。
◍ 用定时器把图表调成干净的截屏底子
想在 MT5 里自动抓图做价格行为复盘,第一步是把图表环境收拾干净:关掉网格、成交量、交易历史,只留价格轴与日期轴。下面这段 EA 初始化用 EventSetTimer 挂了一个 60 秒周期的心跳,失败就直接 INIT_FAILED 退出,成功才继续洗图表。 洗图逻辑集中在 showBars():CHART_SHOW_GRID、CHART_SHOW_VOLUMES、CHART_SHOW_TRADE_HISTORY 全设 false,背景白、前景黑,阳线用 Tomato、阴线用 LightSeaGreen,并强制 CHART_SCALE=3 保证截屏缩放一致。 外汇与贵金属波动剧烈、杠杆高风险大,自动截屏只是辅助记录,不能据此直接下单。把 chart_scale 和 timer_seconds 两个 input 改小,能拿到更高密度但更挤的 K 线图,自己按屏幕分辨率和观察周期调。
input class="type">uint chart_scale = class="num">3; input class="type">uint timer_seconds = class="num">60; class="type">int chart_width, chart_height; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">int OnInit() { class=class="str">"cmt">//--- if (!EventSetTimer(timer_seconds)) { printf("%s failed to set the timer, Error = %d",__FUNCTION__,GetLastError()); class="kw">return INIT_FAILED; } showBars(true); class=class="str">"cmt">//--- class="kw">return(INIT_SUCCEEDED); } class="type">void showBars(class="type">bool show=true) { class=class="str">"cmt">//--- Cleaning the chart 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, true); class=class="str">"cmt">// prevent scrolling ChartSetInteger(class="num">0, CHART_SHIFT, true); if (ChartGetInteger(class="num">0, CHART_SCALE) != chart_scale) ChartSetInteger(class="num">0, CHART_SCALE, chart_scale); if (show) { ChartSetInteger(class="num">0, CHART_COLOR_BACKGROUND, clrWhite); ChartSetInteger(class="num">0, CHART_COLOR_FOREGROUND, clrBlack); ChartSetInteger(class="num">0, CHART_COLOR_CHART_UP, clrTomato); ChartSetInteger(class="num">0, CHART_COLOR_CANDLE_BULL, clrTomato); ChartSetInteger(class="num">0, CHART_COLOR_CHART_DOWN, clrLightSeaGreen); ChartSetInteger(class="num">0, CHART_COLOR_CANDLE_BEAR, clrLightSeaGreen); ChartSetInteger(class="num">0, CHART_SHOW_ASK_LINE, true); ChartSetInteger(class="num">0, CHART_SHOW_BID_LINE, true);
「把 BMP 铺成 MT5 图表底图」
在切换图表配色分支结束后,立刻调用 ChartRedraw() 刷新画面,否则新配色不会实时生效。这一步和后面的背景图注入是两套独立逻辑:前者改系统色,后者用位图对象压在底层。 要用自定义图片当背景,核心函数是 chartBackGroundSet()。它先以 OBJ_BITMAP_LABEL 类型建一个名为 background-img 的对象,失败就打印错误并返回 false,方便你直接在专家日志里抓异常。 图片路径通过 OBJPROP_BMPFILE 绑定,X/YDISTANCE 设 0 把图钉在左上角,XSIZE/YSIZE 用传入的 width、height 拉伸铺满。最后 OBJPROP_BACK 置 true 且 ZORDER 设 -1,把图层压到最底,K 线和轴线仍能盖在上面。 外汇与贵金属杠杆高、滑点大,这类视觉改造只影响主观读取,不改变任何成交逻辑,别误以为换了背景就能过滤风险。
class="type">bool chartBackGroundSet(class="type">class="kw">string filename, class="type">int width, class="type">int height) { class="type">class="kw">string obj_name = "background-img"; if(!ObjectCreate(class="num">0,obj_name,OBJ_BITMAP_LABEL,class="num">0,class="num">0,class="num">0)) { printf("%s failed to create a bitmap in the chart window! Error = %s",__FUNCTION__,ErrorDescription(GetLastError())); class="kw">return(class="kw">false); } class=class="str">"cmt">//--- set the path to the image file if(!ObjectSetString(class="num">0,obj_name,OBJPROP_BMPFILE, filename)) { printf("%s failed to load the image! Error = %s",__FUNCTION__,ErrorDescription(GetLastError())); class="kw">return(class="kw">false); } class=class="str">"cmt">//--- Position the image to cover the entire chart ObjectSetInteger(class="num">0, obj_name, OBJPROP_XDISTANCE, class="num">0); ObjectSetInteger(class="num">0, obj_name, OBJPROP_YDISTANCE, class="num">0); ObjectSetInteger(class="num">0, obj_name, OBJPROP_XSIZE, width); ObjectSetInteger(class="num">0, obj_name, OBJPROP_YSIZE, height); class=class="str">"cmt">//--- Send the image to the background ObjectSetInteger(class="num">0, obj_name, OBJPROP_BACK, true); ObjectSetInteger(class="num">0, obj_name, OBJPROP_ZORDER, -class="num">1); class=class="str">"cmt">//--- Make sure the object is visible
定时抓图后把识别结果铺回图表
这段逻辑跑在 OnTimer 里,靠定时器触发而不是每次 tick,能明显降低 CPU 占用。先调 showBars(true) 把 K 线显出来,再 ObjectsDeleteAll 清两遍(参数 0,0 那次顺手清掉子窗口对象),避免旧标注残影混进截图。 takeScreenShot() 成功就 Print 当前 TimeCurrent() 并 Sleep(100),给文件落盘留缓冲。随后用 ChartGetInteger 抓 CHART_WIDTH_IN_PIXELS / CHART_HEIGHT_IN_PIXELS,实测这两个值随窗口缩放实时变,不写死尺寸才能对齐后续贴图。 文件名按 Symbol、周期、日、星期几、小时拼到 \Files\YOLOv8 Images\ 下,用 StringReplace 去掉前缀做存在性校验——文件没生成就直接 return,防止把空图设成背景。最后 chartBackGroundSet 把带检测框的图铺上,再 showBars(false) 隐藏原生 K 线,视觉上只剩 AI 标注层。外汇与贵金属波动剧烈,这类自动截图识别方案仅作辅助,实盘信号仍需人工复核。
ObjectSetInteger(class="num">0, obj_name, OBJPROP_SELECTABLE, class="kw">false); ObjectSetInteger(class="num">0, obj_name, OBJPROP_HIDDEN, true); class=class="str">"cmt">//--- Redraw the chart to see changes ChartRedraw(class="num">0); class=class="str">"cmt">//--- class="kw">return true; } class="type">void OnTimer(class="type">void) { class=class="str">"cmt">//--- showBars(true); class=class="str">"cmt">//explicitly show the bars class=class="str">"cmt">// Clear the objects before taking a screenshot ObjectsDeleteAll(class="num">0); ObjectsDeleteAll(class="num">0,class="num">0); if (takeScreenShot()) { Print("Screen shot taken: ",TimeCurrent()); Sleep(class="num">100); } chart_width = (class="type">int)ChartGetInteger(class="num">0, CHART_WIDTH_IN_PIXELS); chart_height = (class="type">int)ChartGetInteger(class="num">0, CHART_HEIGHT_IN_PIXELS); class=class="str">"cmt">//--- Take screenshot class="type">MqlDateTime time_struct; TimeToStruct(TimeLocal(), time_struct); class="type">class="kw">string filename = StringFormat("\\Files\\YOLOv8 Images\\%s.%s.%d.%d.%d.bmp",Symbol(),EnumToString(Period()),time_struct.day, time_struct.day_of_week, time_struct.hour); class="type">class="kw">string fileshort_name = filename; class="type">bool checkfile = class="kw">false; if (StringReplace(fileshort_name, "\\Files\\","")>class="num">0) checkfile = true; class=class="str">"cmt">//If the parent folder was removed we can proceed to check if a file exists before drawing an object if (checkfile) class="kw">while (!FileIsExist(fileshort_name)) { printf("%s not found",fileshort_name); class="kw">return; } class=class="str">"cmt">//--- Set the image with patterns detected to a chart if (!chartBackGroundSet(filename, chart_width, chart_height)) class="kw">return; showBars(class="kw">false); }
◍ 把工具请下神坛
YOLOv8 在图表形态识别上的表现确实能打,但别把它当成自动印钞机。实测里它对头肩、双顶这类常见形态能标出不少,但漏检和误检都不少,作者本人也坦言早期自己手搓类似模型效果很差。 当前这版模型只吐出人眼可看的标注图,不直接发交易指令,手动交易者拿它当副驾更现实。真要接程序化,得用 Python 与 MT5 通信,把 predict 的输出以文本或 JSON 推过去——附件里的 deploy.py 就是干这个持续部署的。 外汇和贵金属杠杆高、滑点狠,任何模型辅助都只是概率倾斜,仓位和止损得自己扛。开源仓库里 Experts\YOLOv8 EA.mq5 截完图就把预测图丢回图表,跑一遍就知道它哪类形态靠不住。