
情绪分析与深度学习在交易策略中的应用以及使用Python进行回测
概述
将深度学习和情绪分析融入MetaTrader 5(MQL5)的交易策略代表了算法交易领域的一项前沿性的进步。深度学习是机器学习的一个子集,涉及具有多层结构的神经网络,能够从庞大且复杂的数据集中学习和进行预测。另一方面,情绪分析是一种自然语言处理(NLP)技术,用于确定文本背后的情感或情绪基调。通过运用这些技术,交易者可以改进他们的决策过程并提高交易收益。
对于本文,我们将使用DLL shell32.dll将Python集成到MQL5中,该DLL在Windows上执行我们所需的操作。通过安装Python并通过shell32.dll运行它,我们将能够从MQL5的EA中启动Python脚本。一共有两个Python脚本:一个用于运行TensorFlow训练的ONNX模型,另一个使用库从互联网上获取新闻,阅读标题,并使用人工智能量化媒体情绪。这是一种可能的解决方案,但获取股票或交易品种的情绪方法有很多,来源也各不相同。一旦获得了模型和情绪值,如果两者一致,EA就会执行订单。
我们能否在Python中进行测试,以了解结合情绪分析和深度学习的结果?答案是肯定的,我们将继续研究代码。
使用Python进行深度学习和情绪分析的策略回测
为了执行此策略的回测,我们将使用以下库。我将以我的另一篇文章为起点。当然,在此我也会提供必要的解释。
我们将使用以下库:import ccxt import pandas as pd import numpy as np import onnx import onnxruntime as ort import matplotlib.pyplot as plt from sklearn.metrics import mean_absolute_error, mean_squared_error, r2_score, mean_absolute_percentage_error from sklearn.model_selection import TimeSeriesSplit from sklearn.preprocessing import MinMaxScaler import requests from datetime import datetime, timedelta import nltk from nltk.sentiment import SentimentIntensityAnalyzer from newsapi import NewsApiClient
首先,我们确保nltk是最新版本。
nltk.download('vader_lexicon')
nltk(Natural Language Toolkit,自然语言工具包)是一个用于处理人类语言数据(文本)的库。它提供了易于使用的接口来访问50多个语料库和词汇资源,如WordNet,以及一系列文本处理库,用于分类、分词、词干提取、词性标注、解析和语义推理,还有针对工业级NLP库的封装。
读者必须将Python回测脚本进行适配,以指定从哪里获取数据、新闻源以及ONNX模型的数据。
我们将使用以下方法来获取情绪分析:
def get_news_sentiment(symbol, api_key, date): try: newsapi = NewsApiClient(api_key=api_key) # Obtener noticias relacionadas con el símbolo para la fecha específica end_date = date + timedelta(days=1) articles = newsapi.get_everything(q=symbol, from_param=date.strftime('%Y-%m-%d'), to=end_date.strftime('%Y-%m-%d'), language='en', sort_by='relevancy', page_size=10) sia = SentimentIntensityAnalyzer() sentiments = [] for article in articles['articles']: text = article.get('title', '') if article.get('description'): text += ' ' + article['description'] if text: sentiment = sia.polarity_scores(text) sentiments.append(sentiment['compound']) avg_sentiment = np.mean(sentiments) if sentiments else 0 return avg_sentiment except Exception as e: print(f"Error al obtener el sentimiento para {symbol} en la fecha {date}: {e}") return 0
对于回测,我们将使用news-api作为新闻源,因为他们的免费API允许我们获取过去1个月的新闻。如果您需要更多数据,可以购买订阅服务。
剩下的代码将用于从ONNX模型获取预测结果,以预测下一个收盘价。我们只需将情绪分析结果与深度学习预测结果进行比较,如果两者得出的结论相同,则将创建订单。具体如下:
investment_df = comparison_df.copy() investment_df['price_direction'] = np.where(investment_df['prediction'].shift(-1) > investment_df['prediction'], 1, -1) investment_df['sentiment_direction'] = np.where(investment_df['sentiment'] > 0, 1, -1) investment_df['position'] = np.where(investment_df['price_direction'] == investment_df['sentiment_direction'], investment_df['price_direction'], 0) investment_df['strategy_returns'] = investment_df['position'] * (investment_df['actual'].shift(-1) - investment_df['actual']) / investment_df['actual'] investment_df['buy_and_hold_returns'] = (investment_df['actual'].shift(-1) - investment_df['actual']) / investment_df['actual']
该代码首先创建了一个名为comparison_df的副本,并将其命名为investment_df。然后,它添加了一个名为price_direction的新列,该列的值根据下一个预测是否高于当前预测来设定,如果高于则取值为1,否则取值为-1。接着,它添加了另一个名为sentiment_direction的列,该列的值根据情绪是否积极来设定,如果积极则取值为1,消极则取值为-1。之后,它添加了一个名为position的列,该列的值在price_direction与sentiment_direction相匹配时取price_direction的值,否则取0。代码接着计算strategy_returns,方法是将position与实际值从一行到下一行的相对变化相乘。最后,它计算了buy_and_hold_returns,即不考虑仓位时实际值从一行到下一行的相对变化。
回测结果如下所示:
Datos normalizados guardados en 'binance_data_normalized.csv' Sentimientos diarios guardados en 'daily_sentiments.csv' Predicciones y sentimiento guardados en 'predicted_data_with_sentiment.csv' Mean Absolute Error (MAE): 30.66908467315391 Root Mean Squared Error (RMSE): 36.99641752814565 R-squared (R2): 0.9257591918098058 Mean Absolute Percentage Error (MAPE): 0.00870572230484879 Gráfica guardada como 'ETH_USDT_price_prediction.png' Gráfica de residuales guardada como 'ETH_USDT_residuals.png' Correlation between actual and predicted prices: 0.9752007459642241 Gráfica de estrategia de inversión guardada como 'ETH_USDT_investment_strategy.png' Gráfica de drawdown guardada como 'ETH_USDT_drawdown.png' Sharpe Ratio: 9.41431958149606 Sortino Ratio: 11800588386323879936.0000 Número de rendimientos totales: 28 Número de rendimientos en exceso: 28 Número de rendimientos negativos: 19 Media de rendimientos en exceso: 0.005037 Desviación estándar de rendimientos negativos: 0.000000 Sortino Ratio: nan Beta: 0.33875104783408166 Alpha: 0.006981197358213854 Cross-Validation MAE: 1270.7809910146143 ± 527.5746657573876 SMA Mean Absolute Error (MAE): 344.3737716856061 SMA Mean Absolute Error (MAE): 344.3737716856061 SMA Root Mean Squared Error (RMSE): 483.0396130996611 SMA R-squared (R2): 0.5813550203375846 Gráfica de predicción SMA guardada como 'ETH_USDT_sma_price_prediction.png' Gráfica de precio, predicción y sentimiento guardada como 'ETH_USDT_price_prediction_sentiment.png' Gráfica de drawdown guardada como 'ETH_USDT_drawdown.png' Maximum Drawdown: 0.00%
结果显示,预测价格与实际价格之间的相关性非常好。R²(决定系数),这是衡量模型预测准确性的一个指标,也表现良好。夏普比率(Sharpe Ratio)高于5,非常优秀,索提诺比率(Sortino Ratio)也同样出色。除此之外,其他结果也通过图表进行了展示。
策略与持有对比的图表如下所示:
其他图表,如预测价格与实际价格对比
以及,实际价格、预测价格和情绪分析
结果显示,该策略盈利很好,因此我们现在正在利用这一优势来创建一个EA。
这个EA应该包含两个Python脚本,一个用于情绪分析,另一个用于深度学习模型,并且它们应该被合并到EA中以实现功能。
ONNX模型
数据获取、训练和ONNX模型代码与我们在之前文章中使用的保持一致。因此,我将继续探讨用于情绪分析的Python代码。
使用Python进行情绪分析
我们将使用requests和TextBlob库来获取外汇新闻并进行情绪分析,同时使用csv库来读取和写入数据。此外,还将使用datetime和time库。
import requests from textblob import TextBlob import csv from datetime import datetime import time from time import sleep
这个脚本的初衷是在启动时先延迟几秒钟(以确保脚本的下一部分能够正常运行)。脚本的第二部分将读取我们要使用的API密钥。在这个案例中,我们将使用Marketaux API,它提供了一系列免费的新闻和免费呼叫。还有其他选项,如News API、Alpha Vantage或Finhub,其中有一些是付费的,但提供了更多新闻,包括历史新闻,允许在MT5(MetaTrader 5)中对策略进行回测。如前所述,由于Marketaux有一个免费的API来获取每日新闻,因此我们将暂时使用这个免费API。如果我们想使用其他来源,则需要调整代码。
以下是脚本可能的创建方案:
以下是从EA输入中读取API密钥的函数:
api_file_path = 'C:/Users/jsgas/AppData/Roaming/MetaQuotes/Terminal/24F345EB9F291441AFE537834F9D8A19/MQL5/Files/Files/api.txt' print(api_file_path) def read_api_from_file(): try: with open(api_file_path, 'r', encoding='utf-16') 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('\ufeff', '') # Remove BOM character if present print(f"API after stripping whitespace: {api}") time.sleep(5) return api except FileNotFoundError: print(f"El archivo {api_file_path} no existe.") time.sleep(5) return None # Configuración de la API de Marketaux api=read_api_from_file() MARKETAUX_API_KEY = api
在读取新闻之前,我们需要知道要读取什么内容。为此,该Python脚本将从EA创建的一个文本文件中读取信息,这样Python脚本就知道要读取什么内容,或者要研究哪个交易品种并获取其相关新闻。同时,脚本还需要知道在EA中输入了哪个API密钥,以及今天是哪一天,以便模型能够完成处理并获取对应日期的新闻。
此外,它还必须能够写入txt或csv文件,以便将情绪分析的结果作为EA的输入。
def read_symbol_from_file(): try: with open(symbol_file_path, 'r', encoding='utf-16') 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('\ufeff', '') # Remove BOM character if present print(f"Symbol after stripping whitespace: {symbol}") return symbol except FileNotFoundError: print(f"El archivo {symbol_file_path} no existe.") return None
def save_sentiment_to_txt(average_sentiment, file_path='C:/Users/jsgas/AppData/Roaming/MetaQuotes/Terminal/24F345EB9F291441AFE537834F9D8A19/MQL5/Files/Files/'+str(symbol)+'sentiment.txt'): with open(file_path, 'w') 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, 1): print(f"{i}. {title}") print(f" {description[:100]}...") # Primeros 100 caracteres de la descripción print(f"\nTipo de cambio actual: {current_rate if current_rate else 'No disponible'}") # Calcular el sentimiento promedio sentiment_scores = [TextBlob(title + " " + description).sentiment.polarity for title, description in news] average_sentiment = sum(sentiment_scores) / len(sentiment_scores) if sentiment_scores else 0 print(f"Sentimiento promedio: {average_sentiment:.2f}") # Guardar resultados en CSV #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 'sentiment.txt'") else: print("No se pudieron obtener noticias de Forex.") else: print("No se pudo obtener el símbolo del archivo.")
交易者必须根据研究内容(外汇、股票或加密货币)调整整个脚本。
EA
我们必须像如下包含shell32.dll来运行python脚本
#include <WinUser32.mqh> #import "shell32.dll" int ShellExecuteW(int hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd); #import
我们必须将python脚本添加到File文件夹中
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";
以及python脚本的输入和输出的所有路径,
// 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 = "_";
我们必须输入Marketaux api密钥
input string api_key = "mWpORHgs3GdjqNZkxZwnXmrFLYmG5jhAbVrF"; // MARKETAUX_API_KEY www.marketaux.com
我们可以从这里获取,具体如下:
我不为marketaux工作,所以您可以使用任何其他新闻来源,或者你想要/需要的订阅。
您必须设置一个magic编码,这样一来订单就不会混淆
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; request.tp = takeProfit; request.magic = Magic_Number; request.comment = "Buy Order"; // Enviar la solicitud de comercio if(!OrderSend(request,result)) { Print("Error al abrir orden de compra: ", result.retcode);
最后一段代码是关于如何下单,您也可以使用CTrade的交易来下单。
我将写入一个文件(用作.py脚本中的输入):
void WriteToFile(string filePath, string data) { Print("Intentando abrir el archivo: ", filePath); // Abre el archivo en modo de escritura, crea el archivo si no existe int fileHandle = FileOpen(filePath, FILE_WRITE | FILE_TXT); if(fileHandle != INVALID_HANDLE) { // Escribe los datos en el archivo FileWriteString(fileHandle, data); FileClose(fileHandle); // Cierra el archivo Print("Archivo escrito exitosamente: ", filePath); } else { Print("Error al abrir el archivo ", filePath, ". Código de error: ", GetLastError()); } }
还将在文件中写入交易品种、时间范围和当前日期:
void WriteSymbolAndTimeframe() { // Obtén el símbolo actual currentSymbol = Symbol(); // Obtén el período de tiempo del gráfico actual string currentTimeframe = GetTimeframeString(Period()); currentTime = TimeToString(TimeCurrent(), TIME_DATE); // Escribe cada dato en su respectivo archivo WriteToFile(filePathSymbol, currentSymbol); WriteToFile(filePathTimeframe, currentTimeframe); WriteToFile(filePathTime, currentTime); WriteToFile(filePathApi,api_key); Sleep(10000); // Puedes ajustar o eliminar esto según sea necesario }
函数WriteSymbolAndTimeframe执行以下任务:
- 首先,它获取当前的交易品种并将其存储在currentSymbol中。
- 然后,它使用GetTimeframeString(Period())获取当前图表的时间框架(作为字符串)并将其存储在currentTimeframe中。
- 还使用 TimeToString(TimeCurrent(), TIME_DATE)以特定格式获取当前时间,并将其存储在 currentTime 中。
- 接下来,将每个值写入到它们各自的文件中:
- 将currentSymbol写入filePathSymbol文件中
- 将currentTimeframe写入filePathTimeframe文件中
- 将currentTime写入filePathTime文件中
- 将api_key写入filePathApi文件中
- 最后,使用Sleep(10000)将该函数暂停10秒,这个暂停时间可以根据需要调整或移除。
我们可以通过以下方式启动这些脚本:
void OnTimer() { datetime currentTime2 = TimeCurrent(); // Verifica si ha pasado el intervalo para el primer script if(currentTime2 - lastExecutionTime1 >= interval1) { // Escribe los datos necesarios antes de ejecutar el script WriteSymbolAndTimeframe(); // Ejecuta el primer script de Python int result = ShellExecuteW(0, "open", "cmd.exe", "/c python \"" + script1 + "\"", "", 1); if(result > 32) Print("Script 1 iniciado exitosamente"); else Print("Error al iniciar Script 1. Código de error: ", result); lastExecutionTime1 = currentTime2; }
函数OnTimer会定期执行,并完成以下任务:
- 首先,获取当前时间并将其存储在currentTime2中。
- 然后,检查第一个脚本从上次执行以来经过的时间(lastExecutionTime1)是否大于或等于一个预定义的时间间隔(interval1)。
- 如果条件满足,它将通过调用WriteSymbolAndTimeframe函数来写入必要的数据。
- 接下来,它执行第一个Python脚本,通过ShellExecuteW运行一个命令来打开cmd.exe,并运行由script1指定的Python脚本。
- 如果脚本执行成功(结果大于32),将打印一条成功消息;否则,将打印一条包含相应错误代码的错误消息。
- 最后,将lastExecutionTime1更新为当前时间(currentTime2)。
我们可以使用以下函数来读取文件:
string ReadFile(string file_name) { string result = ""; int handle = FileOpen(file_name, FILE_READ|FILE_TXT|FILE_ANSI); // Use FILE_ANSI for plain text if(handle != INVALID_HANDLE) { int file_size = FileSize(handle); // Get the size of the file result = FileReadString(handle, file_size); // Read the whole file content FileClose(handle); } else { Print("Error opening file: ", file_name); } return result; }
用代码定义了一个名为ReadFile的函数,该函数接受一个文件名作为参数,并返回文件内容作为字符串。首先,它初始化一个空字符串result。然后,它尝试使用FileOpen通过读取权限和纯文本的模式打开文件。如果文件句柄有效,它使用FileSize获取文件大小,使用FileReadString将整个文件内容读入result,然后使用FileClose关闭文件。如果文件句柄无效,它会打印一个包含文件名的错误消息。最后,它返回包含文件内容的result。
通过修改这个条件,我们可以添加一个额外的情绪因素:
if(ExtPredictedClass==PRICE_DOWN && Sentiment_number<0) signal=ORDER_TYPE_SELL; // sell condition else { if(ExtPredictedClass==PRICE_UP && Sentiment_number>0) signal=ORDER_TYPE_BUY; // buy condition else Print("No order possible"); }
在这个情况下,情情绪从10变为-10,其中0表示中性信号。您可以根据需要调整该策略。
其余的代码是基于文章如何在MQL5中使用ONNX模型中的简单EA,并做了一些修改。
这还不是一个完全成型的EA,而只是一个简单的示例,展示了如何使用Python和MQL5创建一个结合情绪分析和深度学习的EA。您在这个智能交易系统上投入的时间越多,遇到的错误和问题就会越少。这是一个前沿的案例研究,并且回测结果显示出了良好的前景。我希望您觉得这篇文章有所帮助,如果有人能够获取到良好的新闻样本或者让这个系统运行一段时间,请分享结果。为了测试这个策略,您应该使用一个模拟账户。
结论
综上所述,将深度学习和情绪分析集成到MetaTrader 5(MQL5)交易策略中,展示了目前算法交易的先进水平。通过利用DLL shell32.dll接口上的Python脚本,我们可以无缝执行复杂的模型并获得有价值的情绪数据,从而有助交易决策和收益。所概述的过程包括使用Python获取和分析新闻情绪,运行ONNX模型进行价格预测,并在两个指标一致时执行交易。
回测结果表明了该策略的潜在盈利能力,这由良好的相关性指标、高的R平方值以及优秀的夏普比率和索提诺比率所体现。这些发现表明,将情绪分析与深度学习相结合可以显著提高交易信号的准确性和整体策略的性能。
展望未来,开发一个功能完善的EA涉及各种组件的精细集成,包括用于情感分析的Python脚本和用于价格预测的ONNX模型。通过不断精炼这些元素并使策略适应不同的市场和数据源,交易者可以构建一个强大且有效的交易工具。
这项研究为那些对探索机器学习、情绪分析和算法交易的融合感兴趣的人提供了基础,提供了一条通往更加明智且可能更有益的交易决策的道路。
本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/15225
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
本文由网站的一位用户撰写,反映了他们的个人观点。MetaQuotes Ltd 不对所提供信息的准确性负责,也不对因使用所述解决方案、策略或建议而产生的任何后果负责。



嗨,哈维尔,没错。我继续查看并实验了代码。在这个模型中,我们创建了一个神经网络,根据收盘历史预测以太坊的价格,对吗?-> 该模型本身的表现似乎比抛硬币好不了多少,但我期待将情绪数据添加到该模型中。还是我误解了该模型的目的?
我目前正在处理将其保存为 ONNX 模型时遇到的问题。我想,这对学习很有帮助。
谢谢您的帮助。一旦我成功实现了这个功能,我将与您分享。
我刚刚在交易逻辑中加入了情感因素。
我刚刚在交易逻辑中加入了情感因素。
我没有您的资源和能力,也没有您的钱,但请用您的模型帮助我。
您好,您应该找一台 CPU 或 Gpu 来计算模型,您可以使用文章中的模型(但要搜索时间框架的有效性)(一个符号的 1 天时间框架模型可以维持 3 - 6 个月)......或者您可以发布一个自由职业者的帖子,让别人为您制作模型。(每个符号都必须有自己的正确时间框架模型)。
您好、
您是用这种机型进行交易的吗?如果是,请告诉我它的性能。
谢谢。