情绪分析与深度学习在交易策略中的应用以及使用Python进行回测·进阶篇
📘

情绪分析与深度学习在交易策略中的应用以及使用Python进行回测·进阶篇

第 2/2 篇

◍ 用 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 的实际值:

MQL5 / C++
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.」之类提示,方便你定位是源断还是路径错。

MQL5 / C++
    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 仅示例性质,实盘前务必用模拟账户验证,且回测良好前景不代表未来概率稳定。

MQL5 / C++
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_READFILE_TXTFILE_ANSI 开文件,用 FileSize 取长、FileReadString 一把读回全部内容,句柄无效时留空串,调用方需自行判断长度再解析。
MQL5 / C++
  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 跑一遍配对率。

MQL5 / C++
  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 里的样例在策略测试器跑一遍,确认情绪因子接入后夏普不降,再谈实盘。

常见问题

用 Python 脚本抓取舆情并计算均值,写进 CSV 或 JSON 落盘;EA 侧定时读该文件即可,不必每次联网。
在 MQL 侧用 ShellExecuteW 异步唤起 Python 进程,脚本跑完写结果文件,EA 轮询读取,互不阻塞。
可以。小布盯盘的 AIGC 已内置舆情与价格行为融合诊断,打开对应品种页就能看到情绪值与预测类别的合并视图。
别直接全仓跟。先用历史回测看两类数据错位频率,仅在情绪与模型方向一致时给小仓位,外汇贵金属高风险需控杠杆。
只传 OHLC、指标缓冲区和公开图表状态,不传账号与订单明细;本地回环端口通信,对外零暴露。