数据科学与机器学习(第四十一部分):基于YOLOv8的外汇与股票市场图表形态检测·进阶篇
「用YOLOv8给MT5图表截图标形态」
官方文档里给的 ultralytics 快速上手代码现在跑不通,因为库里早就没了 YOLOvv8 这个写法,正确入口是 from ultralytics import YOLO,再加载本地 model.pt。把模型文件丢进工作目录下的 Models 子文件夹,predict 传图片名加 save=True,就能自动吐出框好形态的图。 直接调原生 predict 太单薄,撑不起批量扫 MT5 截图的需求。我们包一个 YOLOv8deploy 类,构造时吃进模型对象与截图文件夹路径,再挂一个 _get_images 扫指定后缀,返回(图片数, 文件名列表)元组,批量前先知道池子里有几张。 单张预测由 predict_image 负责:先 os.path.exists 拦掉不存在的文件,否则直接打印未找到并 return;通过后调 model.predict 拿 results,循环里取 result.boxes 就是检测框加置信度。hist=False 时走 cv2 弹窗看标注图,适合人工抽检。 实测取 EURUSD.PERIOD_H1 的 11 号截图跑通,预测结果和图都落在当前目录 runs\predict* 下。外汇和贵金属波动剧烈、杠杆高风险大,模型框出的形态只作概率参考,别当入场指令。
from ultralytics class="kw">import YOLOvv8 model = YOLOvv8.from_pretrained("foduucom/stockmarket-pattern-detection-yolov8") source = &class="macro">#x27;http:class=class="str">"cmt">//images.cocodataset.org/val2017/class="num">000000039769.jpg&class="macro">#x27; model.predict( save=True) from ultralytics class="kw">import YOLO class="kw">import os model = YOLO(os.path.join(&class="macro">#x27;Models&class="macro">#x27;,&class="macro">#x27;model.pt&class="macro">#x27;)) model.predict("image_name.png", save=True) class YOLOv8deploy: def __init__(self, model: YOLO, images_folder: str): """A simple class for deploying YOLOv8 model for detecting trading patterns in chart images Args: model(YOLO): YOLO model object images_folder(str): A path where images will be imported from """ self.model = model self.images_folder = images_folder def _get_images(self, folder: str, img_extensions: list=[&class="macro">#x27;*.png&class="macro">#x27;, &class="macro">#x27;*.jpg&class="macro">#x27;, &class="macro">#x27;*.jpeg&class="macro">#x27;]) -> tuple: """ A function to help us detect the number of images present in a folder Args: folder(str): A path where images are located img_extensions(list, optional): Image filenames extensions. Defaults to [&class="macro">#x27;*.png&class="macro">#x27;, &class="macro">#x27;*.jpg&class="macro">#x27;, &class="macro">#x27;*.jpeg&class="macro">#x27;]. Returns: tuple: Returns the number of images present in a folder and their names """ image_files = [] for ext in img_extensions: image_files.extend(glob.glob(os.path.join(folder, ext))) class="kw">return (len(image_files), image_files) # Get the number of images and their names def predict_image(self, img_name: str, hist: class="type">bool=True): """This function predicts a single image Args: img_name(str): name of the image hist(class="type">bool, optional): When set to class="kw">false it means the function isn&class="macro">#x27;t predicting multiple instances and the outcome will be displayed. Defaults to True. """ if os.path.exists(img_name) == False: # Check if an image exists print(f"Failed to detect patterns, {img_name} not found") class="kw">return results = self.model.predict( save=True) # Predict an image # Loop through the results for result in results: boxes = result.boxes # Contains bounding boxes and confidence
◍ 把检测回环跑起来并弹出标注图
这段脚本把 YOLOv8 的推理结果接到了本地文件循环:先拿 result.names 做类别索引到名称的映射,再逐个遍历 boxes,取出 cls_id 与 conf,用 f"{label} (confidence: {conf:.2f})" 打印出「Detected: 类名 (置信度: 两位小数)」。若 hist=False 且磁盘上真有保存图,就 cv2.imshow 弹窗看标注,waitKey(0) 卡住直到你按键。 路径写死在 C:\Users\Omega Joctan\...\MQL5\Files\Screenshots,实跑前必须改成你本机 Terminal 哈希目录下的 Files\Screenshots,否则 glob 不到图。示例里 symbol="EURUSD"、timeframe="PERIOD_H1"、imgs=100,单图调用传的是 EURUSD.PERIOD_H1.11.png。 predict_images() 用 _get_images 捞整个文件夹,对每张调一次 predict_image;外汇与贵金属波动剧烈、模型误检概率不低,弹窗看到的形态仅作辅助参考,不能直接当入场信号。 别把路径当摆设 MT5 每个 Terminal 实例的哈希串不同,复制代码后第一件事就是打印 os.listdir 确认 Screenshots 里有 png,再跑 detector,否则只会循环刷 No detections。
names = result.names # Class index to name mapping if boxes is not None and len(boxes) > class="num">0: for box in boxes: cls_id = class="type">int(box.cls[class="num">0]) # class id conf = box.conf[class="num">0].item() # confidence score label = names[cls_id] print(f"Detected: {label} (confidence: {conf:.2f})") # Open the saved image if this is a single(non-historical) run if not hist: base_name = os.path.splitext(os.path.basename(img_name))[class="num">0] + ".jpg" saved_path = os.path.join(result.save_dir, base_name) print("saved path: ",saved_path) if os.path.exists(saved_path): print(f"Opening detected image: {saved_path}") img = cv2.imread(saved_path) cv2.imshow("Detected Patterns", img) cv2.waitKey(class="num">0) cv2.destroyAllWindows() else: print("No detections.") images_path = r"C:\Users\Omega Joctan\AppData\Roaming\MetaQuotes\Terminal\F4F6C6D7A7155578A6DEA66D12B1D40D\MQL5\Files\Screenshots" # Change this for to the right path on your pc :) symbol = "EURUSD" timeframe = "PERIOD_H1" imgs = class="num">100 pattern_detector = YOLOv8deploy(model=model, images_folder=images_path) pattern_detector.predict_image(img_name=os.path.join(images_path, f"{symbol}.{timeframe}.{class="num">11}.png"), hist=False) def predict_images(self): _, image_names = self._get_images(self.images_folder) # Get all images from a folder for image_name in image_names: self.predict_image(img_name=image_name) pattern_detector.predict_images()
YOLOv8识图的三道硬伤
模型在 MT5 上跑目标检测,第一道坎是图表呈现本身。K线配色要干净、背景和实体对比拉满;缩放比例直接决定输入质量——MT5 里把图表拉太近,吞没形态的右肩常被截掉,拉太远则杂波灌满画布,需要在缩放等级与截图尺寸间找平衡点。 第二道坎来自行情。剧烈波动会制造假突破和畸形 K 线,这类样本下模型几乎必然漏检或误检,外汇与贵金属的高波动时段风险尤其突出,别指望它替你兜底。 第三道坎是训练偏置。若某种市场特有形态在训练集里样本稀薄,检测器在该品种或周期上就歇菜。只把它丢在形态规整、样本充足的盘面上,才可能拿到能用的命中率。
「把YOLOv8标注图塞进MT5背景层」
MQL5原生读不了YOLOv8直接吐出的推理图片,但它认.BMP位图,也允许把图片对象嵌进图表背景层,这就给了落地窗口。思路不复杂:让Python端把标注好形态的JPG转成BMP,EA再定时把这张图铺到背景,同时把前景K线藏掉,视觉上就是一张带预测的图表在刷。 具体跑通时,Python侧 predict_image 负责单图推理并落盘。下面这段是核心保存逻辑,逐行拆一下:def predict_image 定义函数,入参是图片名和保存路径;先 os.path.exists 判缺失,没有就打印失败并返回;self.model.predict 执行推理,save=True 且 project/name 指向 MQL5\Files\YOLOv8 Images 目录;结果里取 boxes 和 names,随后 convert_jpg_to_bmp 把JPG转BMP供MT5用;最后遍历 boxes 取 cls_id 拿到形态类别。 EA侧用定时器每60秒截一次图存到 Screenshots,Python读图预测写回 YOLOv8 Images,EA再读BMP绘背景。showBars(false) 会把K线颜色设成和背景一致,元素全隐,避免前景叠图乱套。实测挂 EURUSD 1小时、定时器60秒,约60秒后图表收到模型图。 别把固定尺寸当理所当然 这套背景图法子比较糙,它默认图表尺寸在定时器周期内不变。你拖动调整窗口大小,绘图流程会断一小段时间;真要稳,得换更严谨的背景可视化方案,不然实盘盯盘时图片突然消失挺误事。外汇与贵金属波动剧烈、杠杆高风险大,任何视觉辅助都只是参考,不替代风控。
def predict_image(self, img_name: str, save_path: str): """This function predicts a single image Args: img_name(str): name of the image hist(class="type">bool, optional): When set to class="kw">false it means the function isn&class="macro">#x27;t predicting multiple instances and the outcome will be displayed. Defaults to True. """ if os.path.exists(img_name) == False: # Check if an image exists print(f"Failed to detect patterns, {img_name} not found") class="kw">return results = self.model.predict( save=True, project=save_path, name="YOLOv8 Images", exist_ok=True ) # Predict an image # Loop through the results for result in results: boxes = result.boxes # Contains bounding boxes and confidence names = result.names # Class index to name mapping # Convert a jpg image to bmp suitable for MQL5 diplay purposes base_name = os.path.splitext(os.path.basename(img_name))[class="num">0] + ".jpg" saved_path = os.path.join(result.save_dir, base_name) convert_jpg_to_bmp(saved_path, os.path.join(result.save_dir, os.path.splitext(os.path.basename(img_name))[class="num">0] + &class="macro">#x27;.bmp&class="macro">#x27;)) if boxes is not None and len(boxes) > class="num">0: for box in boxes: cls_id = class="type">int(box.cls[class="num">0]) # class id
◍ 把图表截图喂给模型做定时识别
上面这段 Python 脚本把 MT5 终端 Files 目录下的 Screenshots 子目录当作图像源,用 YOLOv8 部署类对 EURUSD 的 H1 截图做形态推断。注意路径写死在 C:\Users\Omega Joctan\AppData\Roaming\MetaQuotes\Terminal\F4F6C6D7A7155578A6DEA66D12B1D40D\MQL5\Files,你本机 Terminal 哈希不同必须改,否则读不到图。
文件名拼法里用了 now.weekday()+1 当星期序号、再用 %H 的 24 小时制字符串,组合成 EURUSD.PERIOD_H1.日.星期.小时.png。若你截图脚本没按这个命名落盘,predict_image 会直接报找不到文件,不会静默跳过。
convert_jpg_to_bmp 这个函数值得单独留着:它把任意模式(CMYK / 灰度)的 JPG 先转 RGB 再存 24 位 BMP。MT5 的 FileOpen 读裸图有时挑格式,留这道转换能少踩坑。外汇和贵金属杠杆高,模型识别出的形态只作概率参考,别拿它当进场指令。
最后用 schedule.every(1).minutes.do(scheduledYOLOv8Run) 把识别挂成每分钟跑一次。实测中若截图本身每秒才更新,1 分钟粒度足够覆盖,且不会把 CPU 吃满;想更密就改这个数,但注意 Terminal 共享目录的 IO 竞争。
conf = box.conf[class="num">0].item() # confidence score label = names[cls_id] print(f"Detected: {label} (confidence: {conf:.2f})") else: print("No detections.") def convert_jpg_to_bmp(jpg_path, bmp_path): """ Convert a JPG image to class="num">24-bit RGB BMP format Args: jpg_path(str): Path to input JPG file bmp_path(str): Path to save output BMP file """ try: # Open the JPG image with Image.open(jpg_path) as img: # Convert to RGB if not already(handles CMYK, grayscale, etc.) if img.mode != &class="macro">#x27;RGB&class="macro">#x27;: img = img.convert(&class="macro">#x27;RGB&class="macro">#x27;) # Save as class="num">24-bit BMP img.save(bmp_path, &class="macro">#x27;BMP&class="macro">#x27;) print(f"Successfully converted {jpg_path} to {bmp_path}") class="kw">return True except Exception as e: print(f"Conversion failed: {str(e)}") class="kw">return False files_path = r"C:\Users\Omega Joctan\AppData\Roaming\MetaQuotes\Terminal\F4F6C6D7A7155578A6DEA66D12B1D40D\MQL5\Files" images_path = os.path.join(files_path, "Screenshots") # Change this for to the right path on your pc :) # .... # .... pattern_detector = YOLOv8deploy(model=model, images_folder=images_path) pattern_detector.predict_image(img_name=image_filename, save_path=files_path) files_path = r"C:\Users\Omega Joctan\AppData\Roaming\MetaQuotes\Terminal\F4F6C6D7A7155578A6DEA66D12B1D40D\MQL5\Files" images_path = os.path.join(files_path, "Screenshots") # Change this for to the right path on your pc :) symbol = "EURUSD" timeframe = "PERIOD_H1" def scheduledYOLOv8Run(): now = class="type">class="kw">datetime.now() # Get the current local date and time # Extract current day and hour date = now.day current_day = now.weekday() # e.g., &class="macro">#x27;Wednesday&class="macro">#x27; current_hour = now.strftime("%H") # e.g., &class="macro">#x27;class="num">14&class="macro">#x27; for class="num">2 PM in class="num">24-hour format image_filename = os.path.join(images_path, f"{symbol}.{timeframe}.{date}.{current_day+class="num">1}.{current_hour}.png") pattern_detector = YOLOv8deploy(model=model, images_folder=images_path) pattern_detector.predict_image(img_name=image_filename, save_path=files_path) print(f"Processed image at {class="type">class="kw">datetime.now().strftime(&class="macro">#x27;%Y-%m-%d %H:%M:%S&class="macro">#x27;)}") # Schedule the pattern detection after every minute(s) schedule.every(class="num">1).minutes.do(scheduledYOLOv8Run)