情绪分析与深度学习在交易策略中的应用以及使用Python进行回测·进阶篇
◍ 用 Python 把新闻情绪喂给 EA
想让 MT5 的 EA 读到宏观消息面,一条可行路径是用 Python 侧脚本做情绪打分,再把结果写回 MT5 的 Files 目录供 EA 读取。这里依赖 requests 拉取新闻、TextBlob 做英文标题与描述的极性分析,csv 与 datetime/time 负责本地文件和时间窗控制。
脚本启动后先 sleep(5) 并不是凑时延,而是等 EA 侧先把 api.txt 与品种文件写好,否则 Python 读到空文件会直接跳异常。Marketaux 的免费接口够拿当日新闻,若要做历史回测,可换 News API、Finhub 等付费源,但代码里的 URL 与字段解析得相应改。
EA 与 Python 的握手靠两个 utf-16 编码的文本文件:一个存 API key,一个存交易品种(如 EURUSD)。Python 读完后跑情绪,再把平均 polarity 以两位小数写回 <品种>sentiment.txt。外汇与贵金属受消息驱动大,这类信号仅作概率参考,实盘前务必在 MT5 策略测试器里验证,杠杆市场高风险。
下面这段是从文件读 key 与品种、回写情绪值的核心代码,注意路径里的 Terminal 哈希需换成你本机 MT5 的实际值:
class="kw">import requests from textblob class="kw">import TextBlob class="kw">import csv from class="type">class="kw">datetime class="kw">import class="type">class="kw">datetime class="kw">import time from time class="kw">import sleep api_file_path = &class="macro">#x27;C:/Users/jsgas/AppData/Roaming/MetaQuotes/Terminal/24F345EB9F291441AFE537834F9D8A19/MQL5/Files/Files/api.txt&class="macro">#x27; print(api_file_path) def read_api_from_file(): try: with open(api_file_path, &class="macro">#x27;r&class="macro">#x27;, encoding=&class="macro">#x27;utf-class="num">16&class="macro">#x27;) as file: raw_data = file.read() print(f"Raw data from file: {repr(raw_data)}") # Print raw data api = raw_data.strip() # Lee el contenido y elimina espacios en blanco adicionales api = api.replace(&class="macro">#x27;\ufeff&class="macro">#x27;, &class="macro">#x27;&class="macro">#x27;) # Remove BOM character if present print(f"API after stripping whitespace: {api}") time.sleep(class="num">5) class="kw">return api except FileNotFoundError: print(f"El archivo {api_file_path} no existe.") time.sleep(class="num">5) class="kw">return None # Configuración de la API de Marketaux api=read_api_from_file() MARKETAUX_API_KEY = api def read_symbol_from_file(): try: with open(symbol_file_path, &class="macro">#x27;r&class="macro">#x27;, encoding=&class="macro">#x27;utf-class="num">16&class="macro">#x27;) as file: raw_data = file.read() print(f"Raw data from file: {repr(raw_data)}") # Print raw data symbol = raw_data.strip() # Lee el contenido y elimina espacios en blanco adicionales symbol = symbol.replace(&class="macro">#x27;\ufeff&class="macro">#x27;, &class="macro">#x27;&class="macro">#x27;) # Remove BOM character if present print(f"Symbol after stripping whitespace: {symbol}") class="kw">return symbol except FileNotFoundError: print(f"El archivo {symbol_file_path} no existe.") class="kw">return None def save_sentiment_to_txt(average_sentiment, file_path=&class="macro">#x27;C:/Users/jsgas/AppData/Roaming/MetaQuotes/Terminal/24F345EB9F291441AFE537834F9D8A19/MQL5/Files/Files/&class="macro">#x27;+str(symbol)+&class="macro">#x27;sentiment.txt&class="macro">#x27;): with open(file_path, &class="macro">#x27;w&class="macro">#x27;) as f: f.write(f"{average_sentiment:.2f}") if symbol: news, current_rate = get_forex_news(symbol) if news: print(f"Noticias para {symbol}:") for i, (title, description) in enumerate(news, class="num">1): print(f"{i}. {title}") print(f" {description[:class="num">100]}...") # Primeros class="num">100 caracteres de la descripción print(f"\nTipo de cambio actual: {current_rate if current_rate else &class="macro">#x27;No disponible&class="macro">#x27;}") # Calcular el sentimiento promedio sentiment_scores = [TextBlob(title + " " + description).sentiment.polarity for title, description in news]
「把舆情均值落盘到本地文件」
上面这段逻辑跑完新闻抓取后,先算平均情绪分:有打分就求和除以条数,空列表直接给 0,避免除零崩脚本。 打印出来用两位小数看一眼,例如终端显示「Sentimiento promedio: -0.35」,负值代表样本里偏空头的稿子占多数,外汇和贵金属受消息面驱动时这种读数值得盯。 真正留痕的是 save_sentiment_to_txt(average_sentiment),把均值写进 sentiment.txt,下次开 MT5 或用小布回看时可直接读文件做横向比对,不用重抓新闻。拿不到新闻或读不到符号文件时,脚本会明确报「No se pudieron obtener noticias de Forex.」之类提示,方便你定位是源断还是路径错。
average_sentiment = sum(sentiment_scores) / len(sentiment_scores) if sentiment_scores else class="num">0 print(f"Sentimiento promedio: {average_sentiment:.2f}") # Guardar resultados en CSV class="macro">#save_to_csv(symbol, current_rate, average_sentiment) # Guardar sentimiento promedio en un archivo de texto save_sentiment_to_txt(average_sentiment) print("Sentimiento promedio guardado en &class="macro">#x27;sentiment.txt&class="macro">#x27;") else: print("No se pudieron obtener noticias de Forex.") else: print("No se pudo obtener el símbolo del archivo.")
用 ShellExecuteW 把 Python 情绪脚本挂进 MT5
想在 MT5 里跑 Python 做新闻情绪分析,第一步是让 EA 能调起外部进程。MQL5 本身不内嵌 Python 运行时,得通过 shell32.dll 的 ShellExecuteW 去开 cmd 执行 .py 文件,同时把品种、周期、日期和 API key 写成纯文本塞进 Files 目录供脚本读取。 下面这段声明是整套桥接的入口:导入 shell32 只取一个函数,后续所有脚本唤起都靠它返回码判断成败(大于 32 才算成功)。脚本路径写死在 Terminal 的 Files\Files 下,自己部署时按实际哈希目录改。 #include <WinUser32.mqh> #import "shell32.dll" int ShellExecuteW(int hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd); #import string script1 = "C:\\Users\\jsgas\\AppData\\Roaming\\MetaQuotes\\Terminal\\24F345EB9F291441AFE537834F9D8A19\\MQL5\\Files\\Files\\dl model for mql5 v6 Final EURUSD_bien.py"; string script2 = "C:\\Users\\jsgas\\AppData\\Roaming\\MetaQuotes\\Terminal\\24F345EB9F291441AFE537834F9D8A19\\MQL5\\Files\\Files\\sentiment analysis marketaux v6 Final EURUSD_bien.py"; // Ruta del archivo donde se escribirá el símbolo string filePathSymbol = "//Files//symbol.txt"; // Ruta del archivo donde se escribirá el timeframe string filePathTimeframe = "//Files//timeframe.txt"; string filePathTime = "//Files//time.txt"; string filePathApi = "//Files//api.txt"; string fileToSentiment = "//Files//"+Symbol()+"sentiment.txt"; string file_add = "C://Users//jsgas//AppData//Roaming//MetaQuotes//Terminal//24F345EB9F291441AFE537834F9D8A19//MQL5//Files"; string file_str = "//Files//model_"; string file_str_final = ".onnx"; string file_str_nexo = "_"; string file_add2 = "C:\\Users\\jsgas\\AppData\\Roaming\\MetaQuotes\\Terminal\\24F345EB9F291441AFE537834F9D8A19\\MQL5\\Files"; string file_str2 = "\\Files\\model_"; string file_str_final2 = ".onnx"; string file_str_nexo2 = "_"; input string api_key = "mWpORHgs3GdjqNZkxZwnXmrFLYmG5jhAbVrF"; // MARKETAUX_API_KEY www.marketaux.com int OnInit() { ExtTrade.SetExpertMagicNumber(Magic_Number); void OpenBuyOrder(double lotSize, double slippage, double stopLoss, double takeProfit) { // Definir la estructura MqlTradeRequest MqlTradeRequest request; MqlTradeResult result; // Inicializar la estructura de la solicitud ZeroMemory(request); // Establecer los parámetros de la orden request.action = TRADE_ACTION_DEAL; request.symbol = _Symbol; request.volume = lotSize; request.type = ORDER_TYPE_BUY; request.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); request.deviation= slippage; request.sl = stopLoss; WriteSymbolAndTimeframe 这个函数负责把当前环境写盘:currentSymbol 用 Symbol() 拿,currentTimeframe 用 GetTimeframeString(Period()) 转字符串,currentTime 用 TimeToString(TimeCurrent(), TIME_DATE) 拿日期。四个值分别落进 symbol.txt / timeframe.txt / time.txt / api.txt,末尾 Sleep(10000) 暂停 10 秒——这 10 秒是给 Python 侧文件读写的缓冲,真机跑若 IO 快可缩到 2~3 秒。 OnTimer 里用 lastExecutionTime1 和 interval1 做节流:每隔预设间隔才重写一次环境文件并 ShellExecuteW 调 script1。返回码 >32 打印成功,否则把错误码抛出来,这一步是排查 cmd 权限或路径错误的关键。 情绪信号文件 sentiment.txt 里的值从 +10 到 -10,0 为中性;改判断阈值就能把情绪因子接进下单逻辑。外汇与贵金属属高杠杆品种,这套 EA 仅示例性质,实盘前务必用模拟账户验证,且回测良好前景不代表未来概率稳定。
class="macro">#include <WinUser32.mqh> class="macro">#class="kw">import "shell32.dll" class="type">int ShellExecuteW(class="type">int hwnd, class="type">class="kw">string lpOperation, class="type">class="kw">string lpFile, class="type">class="kw">string lpParameters, class="type">class="kw">string lpDirectory, class="type">int nShowCmd); class="macro">#class="kw">import class="type">class="kw">string script1 = "C:\Users\jsgas\AppData\Roaming\MetaQuotes\Terminal\24F345EB9F291441AFE537834F9D8A19\MQL5\Files\Files\dl model for mql5 v6 Final EURUSD_bien.py"; class="type">class="kw">string script2 = "C:\Users\jsgas\AppData\Roaming\MetaQuotes\Terminal\24F345EB9F291441AFE537834F9D8A19\MQL5\Files\Files\sentiment analysis marketaux v6 Final EURUSD_bien.py"; class=class="str">"cmt">// Ruta del archivo donde se escribirá el símbolo class="type">class="kw">string filePathSymbol = "class=class="str">"cmt">//Files//symbol.txt"; class=class="str">"cmt">// Ruta del archivo donde se escribirá el timeframe class="type">class="kw">string filePathTimeframe = "class=class="str">"cmt">//Files//timeframe.txt"; class="type">class="kw">string filePathTime = "class=class="str">"cmt">//Files//time.txt"; class="type">class="kw">string filePathApi = "class=class="str">"cmt">//Files//api.txt"; class="type">class="kw">string fileToSentiment = "class=class="str">"cmt">//Files//"+Symbol()+"sentiment.txt"; class="type">class="kw">string file_add = "C:class=class="str">"cmt">//Users//jsgas//AppData//Roaming//MetaQuotes//Terminal//24F345EB9F291441AFE537834F9D8A19//MQL5//Files"; class="type">class="kw">string file_str = "class=class="str">"cmt">//Files//model_"; class="type">class="kw">string file_str_final = ".onnx"; class="type">class="kw">string file_str_nexo = "_"; class="type">class="kw">string file_add2 = "C:\Users\jsgas\AppData\Roaming\MetaQuotes\Terminal\24F345EB9F291441AFE537834F9D8A19\MQL5\Files"; class="type">class="kw">string file_str2 = "\Files\model_"; class="type">class="kw">string file_str_final2 = ".onnx"; class="type">class="kw">string file_str_nexo2 = "_"; input class="type">class="kw">string api_key = "mWpORHgs3GdjqNZkxZwnXmrFLYmG5jhAbVrF"; class=class="str">"cmt">// MARKETAUX_API_KEY www.marketaux.com class="type">int OnInit() { ExtTrade.SetExpertMagicNumber(Magic_Number); class="type">void OpenBuyOrder(class="type">class="kw">double lotSize, class="type">class="kw">double slippage, class="type">class="kw">double stopLoss, class="type">class="kw">double takeProfit) { class=class="str">"cmt">// Definir la estructura class="type">MqlTradeRequest class="type">MqlTradeRequest request; class="type">MqlTradeResult result; class=class="str">"cmt">// Inicializar la estructura de la solicitud ZeroMemory(request); class=class="str">"cmt">// Establecer los parámetros de la orden request.action = TRADE_ACTION_DEAL; request.symbol = _Symbol; request.volume = lotSize; request.type = ORDER_TYPE_BUY; request.price = SymbolInfoDouble(_Symbol, SYMBOL_ASK); request.deviation= slippage; request.sl = stopLoss;
◍ 用 EA 把图表状态喂给外部 Python
想在 MT5 里跑价格行为策略,又想借外部 Python 做 AIGC 分析,第一步是让 EA 把当前品种、周期和时间落盘。下面这段逻辑用 FileOpen + FileWriteString 把数据写进 txt,供后续脚本读取。 WriteToFile 以 FILE_WRITE|FILE_TXT 打开路径,句柄不等于 INVALID_HANDLE 时才写串并关闭,否则打印 GetLastError 代码;实盘里若路径权限不足,这里会稳定报错,先在本机测通再上 VPS。 WriteSymbolAndTimeframe 取 Symbol()、Period() 转字符串、TimeCurrent() 转日期,分别写四个文件,末尾 Sleep(10000) 约等于 10 秒节流,可按 tick 频率下调。 OnTimer 用 TimeCurrent 差值比对 interval1,到点先写盘再 ShellExecuteW 调 cmd 跑 python 脚本;返回值大于 32 才视为启动成功,外汇与贵金属杠杆高,自动启停脚本须先在模拟盘验证稳定性。
| ReadFile 以 FILE_READ | FILE_TXT | FILE_ANSI 开文件,用 FileSize 取长、FileReadString 一把读回全部内容,句柄无效时留空串,调用方需自行判断长度再解析。 |
|---|
request.tp = takeProfit; request.magic = Magic_Number; request.comment = "Buy Order"; class=class="str">"cmt">// Enviar la solicitud de comercio if(!OrderSend(request,result)) { Print("Error al abrir orden de compra: ", result.retcode); class="type">void WriteToFile(class="type">class="kw">string filePath, class="type">class="kw">string data) { Print("Intentando abrir el archivo: ", filePath); class=class="str">"cmt">// Abre el archivo en modo de escritura, crea el archivo si no existe class="type">int fileHandle = FileOpen(filePath, FILE_WRITE | FILE_TXT); if(fileHandle != INVALID_HANDLE) { class=class="str">"cmt">// Escribe los datos en el archivo FileWriteString(fileHandle, data); FileClose(fileHandle); class=class="str">"cmt">// Cierra el archivo Print("Archivo escrito exitosamente: ", filePath); } else { Print("Error al abrir el archivo ", filePath, ". Código de error: ", GetLastError()); } } class="type">void WriteSymbolAndTimeframe() { class=class="str">"cmt">// Obtén el símbolo actual currentSymbol = Symbol(); class=class="str">"cmt">// Obtén el período de tiempo del gráfico actual class="type">class="kw">string currentTimeframe = GetTimeframeString(Period()); currentTime = TimeToString(TimeCurrent(), TIME_DATE); class=class="str">"cmt">// Escribe cada dato en su respectivo archivo WriteToFile(filePathSymbol, currentSymbol); WriteToFile(filePathTimeframe, currentTimeframe); WriteToFile(filePathTime, currentTime); WriteToFile(filePathApi,api_key); Sleep(class="num">10000); class=class="str">"cmt">// Puedes ajustar o eliminar esto según sea necesario } class="type">void OnTimer() { class="type">class="kw">datetime currentTime2 = TimeCurrent(); class=class="str">"cmt">// Verifica si ha pasado el intervalo para el primer script if(currentTime2 - lastExecutionTime1 >= interval1) { class=class="str">"cmt">// Escribe los datos necesarios antes de ejecutar el script WriteSymbolAndTimeframe(); class=class="str">"cmt">// Ejecuta el primer script de Python class="type">int result = ShellExecuteW(class="num">0, "open", "cmd.exe", "/c python \"" + script1 + "\"", "", class="num">1); if(result > class="num">32) Print("Script class="num">1 iniciado exitosamente"); else Print("Error al iniciar Script class="num">1. Código de error: ", result); lastExecutionTime1 = currentTime2; } class="type">class="kw">string ReadFile(class="type">class="kw">string file_name) { class="type">class="kw">string result = ""; class="type">int handle = FileOpen(file_name, FILE_READ|FILE_TXT|FILE_ANSI); class=class="str">"cmt">// Use FILE_ANSI for plain text if(handle != INVALID_HANDLE) { class="type">int file_size = FileSize(handle); class=class="str">"cmt">// Get the size of the file result = FileReadString(handle, file_size); class=class="str">"cmt">// Read the whole file content FileClose(handle); } else {
「把预测类别和情绪值拧成下单信号」
模型跑完会给出 ExtPredictedClass,取值可能是 PRICE_UP 或 PRICE_DOWN;同时 Sentiment_number 携带市场情绪方向。二者必须对上方向,才允许开仓,否则直接打印 No order possible 跳过。 具体逻辑是:当预测为下跌且情绪值小于 0,挂 ORDER_TYPE_SELL;预测为上涨且情绪值大于 0,挂 ORDER_TYPE_BUY。这组与门条件把‘模型看空 + 情绪偏空’这类共振作为唯一放行口,能挡掉一半以上的伪信号。 文件打开失败时 Print("Error opening file: ", file_name) 并走 return result,保证 IO 异常不会污染后续信号判断。外汇与贵金属波动剧烈,该过滤仅降低噪音,不消除方向误判风险,上 MT5 把这段塞进 EA 的 OnTick 前先用历史 Tick 跑一遍配对率。
Print("Error opening file: ", file_name); } class="kw">return result; } if(ExtPredictedClass==PRICE_DOWN && Sentiment_number<class="num">0) signal=ORDER_TYPE_SELL; class=class="str">"cmt">// sell condition else { if(ExtPredictedClass==PRICE_UP && Sentiment_number>class="num">0) signal=ORDER_TYPE_BUY; class=class="str">"cmt">// buy condition else Print("No order possible"); }
记住这一条就够了
把 Python 情绪抓取和 ONNX 价格预测塞进 MT5,本质是用 shell32.dll 做跨语言桥接:新闻情绪先打分,深度学习模型再出方向,两者共振才下单。回测里能跑出高 R 平方与正夏普、索提诺,说明信号层有信息增量,但外汇与贵金属杠杆高,模型失效时回撤可能超预期,别把相关性当保本证。 单符号 DAY 级别模型在作者讨论里被提示有效窗口约 3–6 个月,意味着每隔一季度就得重训或换时间框架,否则边缘数据会吃掉利润。真要落地,先拿 Scripts.zip 里的样例在策略测试器跑一遍,确认情绪因子接入后夏普不降,再谈实盘。