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

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

第 1/2 篇

「用 Python 给 MT5 情绪策略跑回测」

把情绪分析叠进交易逻辑,再靠深度学习挑特征,这条路线在 MT5 上已经有了可落地的雏形。原作者在 2025 年 1 月 15 日贴出的示例帖,累计浏览 1548、评论 14,说明这类交叉验证的需求并不小众。 外汇与贵金属自带高杠杆与跳空风险,任何基于情绪模型的信号都只是概率倾斜,不能直接当方向圣旨。想验证它,第一步是把 MT5 的历史报价导出来,交给 Python 做离线回测,而不是只在图表上肉眼翻。 具体做法通常是用 MetaTrader 5 的 Python 包拉取 symbol 的 OHLCV,再接入你自己的情绪打分序列,用 pandas 对齐时间轴后算信号命中率。跑通一次,你就能知道这套情绪特征在哪些时段是负贡献。

用 shell32 把 Python 塞进 MT5 的实战路径

把深度学习和情绪分析接进 MetaTrader 5,本质上是在 EA 外面挂一层 Python 运行时。深度学习用多层神经网络从复杂数据里提特征,情绪分析走 NLP 判定文本基调,两者叠加,决策输入会比单纯看价格序列多一个维度。外汇和贵金属杠杆高、跳空频繁,这种额外信号只能当概率权重,不能直接当方向保证。 具体落地不靠 MQL5 原生 ML,而是借 Windows 的 shell32.dll 拉起 Python。装好 Python 后,EA 通过 shell32 调起两个脚本:一个加载 TensorFlow 训好的 ONNX 模型做推断,另一个抓新闻标题用 AI 打情绪分。两个输出若同向,EA 才发单——这是一种可跑的通路,不是唯一解,情绪源和模型架构都能换。 先在 Python 侧验证合起来有没有用,再搬进 MT5 才划算。下一段我们直接看代码怎么串。

◍ 把情绪信号和深度学习预测叠一层过滤

回测这层用 Python 搭,核心不是单看模型,而是让情绪分析和 ONNX 深度学习预测互相确认。新闻源选了 news-api 的免费接口,能拉最近 1 个月的新闻;要更长历史就得买订阅。读者自己改脚本时,必须显式指定数据来源、新闻源以及 ONNX 模型的输入路径,否则跑不起来。 处理流程上,代码先复制 comparison_df 为 investment_df,加 price_direction 列:下一期预测价高于当期预测取 1,否则 -1;再加 sentiment_direction 列:情绪积极取 1,消极取 -1。position 列在两者方向一致时取该方向值,不一致就置 0。strategy_returns 用 position 乘实际价格逐行收益率,buy_and_hold_returns 则是不带仓位滤镜的裸持有收益。 回测出来的数字值得盯一下:预测价与实际价的相关系数很高,R² 表现良好,夏普比率高于 5,索提诺比率也同样出色。外汇和贵金属属高风险品种,这种统计优势只代表历史样本内的概率倾向,实盘可能衰减。 下面这段是情绪抓取的实现骨架,用 nltk 的 VADER 做 compound 打分,读者可直接抄去改 symbol 和 api_key 验证。

MQL5 / C++
class="kw">import ccxt
class="kw">import pandas as="as" pd
class="kw">import numpy as="as" np
class="kw">import onnx
class="kw">import onnxruntime as ort
class="kw">import matplotlib.pyplot as="as" plt
from sklearn.metrics class="kw">import mean_absolute_error, mean_squared_error, r2_score, mean_absolute_percentage_error
from sklearn.model_selection class="kw">import TimeSeriesSplit
from sklearn.preprocessing class="kw">import MinMaxScaler
class="kw">import requests
from class="type">class="kw">datetime class="kw">import class="type">class="kw">datetime, timedelta
class="kw">import nltk
from nltk.sentiment class="kw">import SentimentIntensityAnalyzer
from newsapi class="kw">import NewsApiClient
nltk.download(&class="macro">#x27;vader_lexicon&class="macro">#x27;)
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=class="num">1)
        articles = newsapi.get_everything(q=symbol,
                                          from_param=date.strftime(&class="macro">#x27;%Y-%m-%d&class="macro">#x27;),
                                          to=end_date.strftime(&class="macro">#x27;%Y-%m-%d&class="macro">#x27;),
                                          language=&class="macro">#x27;en&class="macro">#x27;,
                                          sort_by=&class="macro">#x27;relevancy&class="macro">#x27;,
                                          page_size=class="num">10)
        
        sia = SentimentIntensityAnalyzer()
        
        sentiments = []
        for article in articles[&class="macro">#x27;articles&class="macro">#x27;]:
            text = article.get(&class="macro">#x27;title&class="macro">#x27;, &class="macro">#x27;&class="macro">#x27;)
            if article.get(&class="macro">#x27;description&class="macro">#x27;):
                text += &class="macro">#x27; &class="macro">#x27; + article[&class="macro">#x27;description&class="macro">#x27;]
            
            if text:
                sentiment = sia.polarity_scores(text)
                sentiments.append(sentiment[&class="macro">#x27;compound&class="macro">#x27;])
        
        avg_sentiment = np.mean(sentiments) if sentiments else class="num">0
        class="kw">return avg_sentiment
    except Exception as e:
        print(f"Error al obtener el sentimiento para {symbol} en la fecha {date}: {e}")

「回测跑完先盯这几个数」

上面这段是策略回测收尾时直接吐出来的指标,不是装饰,每一个都能拿去 MT5 里对照自己的 ETH/USDT 复盘。模型对价格的拟合强度看 R2:0.9258,实际与预测价相关系数 0.9752,说明这套预测在样本内跟得相当紧;但交叉验证 MAE 跳到 1270.78±527.57,和样本内 MAE 30.67 差出两个数量级,样本外泛化要打问号。 仓位逻辑那几行值得手抄:用下一期预测值相对本期的变化定价格方向(涨 1 跌 -1),情绪大于 0 才看多,两者同向才持仓、否则空仓。策略收益按 position 乘实际收益率算,和买入持有分开列了出来。 风险数字别漏看:28 个收益样本里负收益 19 个,最大回撤却显示 0.00%,Sortino 出现 nan 和 1.18e19 两种离谱值,说明负收益标准差被算成 0——代码层面有除零隐患,不能直接信这个_ratio。外汇与加密相关品种波动剧烈,高 Sharpe(9.41)在小样本下极易失真,上实盘前务必自己重跑一遍并核对分母。 获取、训练与 ONNX 导出部分和前文一致,这里不重复;接下来会单独拆情绪分析的 Python 实现,看怎么把新闻类情绪转成日频信号接进同一张表。

MQL5 / C++
class="kw">return class="num">0
investment_df = comparison_df.copy()
investment_df[&class="macro">#x27;price_direction&class="macro">#x27;] = np.where(investment_df[&class="macro">#x27;prediction&class="macro">#x27;].shift(-class="num">1) > investment_df[&class="macro">#x27;prediction&class="macro">#x27;], class="num">1, -class="num">1)
investment_df[&class="macro">#x27;sentiment_direction&class="macro">#x27;] = np.where(investment_df[&class="macro">#x27;sentiment&class="macro">#x27;] > class="num">0, class="num">1, -class="num">1)
investment_df[&class="macro">#x27;position&class="macro">#x27;] = np.where(investment_df[&class="macro">#x27;price_direction&class="macro">#x27;] == investment_df[&class="macro">#x27;sentiment_direction&class="macro">#x27;], investment_df[&class="macro">#x27;price_direction&class="macro">#x27;], class="num">0)
investment_df[&class="macro">#x27;strategy_returns&class="macro">#x27;] = investment_df[&class="macro">#x27;position&class="macro">#x27;] * (investment_df[&class="macro">#x27;actual&class="macro">#x27;].shift(-class="num">1) - investment_df[&class="macro">#x27;actual&class="macro">#x27;]) / investment_df[&class="macro">#x27;actual&class="macro">#x27;]
investment_df[&class="macro">#x27;buy_and_hold_returns&class="macro">#x27;] = (investment_df[&class="macro">#x27;actual&class="macro">#x27;].shift(-class="num">1) - investment_df[&class="macro">#x27;actual&class="macro">#x27;]) / investment_df[&class="macro">#x27;actual&class="macro">#x27;]
Datos normalizados guardados en &class="macro">#x27;binance_data_normalized.csv&class="macro">#x27;
Sentimientos diarios guardados en &class="macro">#x27;daily_sentiments.csv&class="macro">#x27;
Predicciones y sentimiento guardados en &class="macro">#x27;predicted_data_with_sentiment.csv&class="macro">#x27;
Mean Absolute Error(MAE): class="num">30.66908467315391
Root Mean Squared Error(RMSE): class="num">36.99641752814565
R-squared(R2): class="num">0.9257591918098058
Mean Absolute Percentage Error(MAPE): class="num">0.00870572230484879
Gráfica guardada como &class="macro">#x27;ETH_USDT_price_prediction.png&class="macro">#x27;
Gráfica de residuales guardada como &class="macro">#x27;ETH_USDT_residuals.png&class="macro">#x27;
Correlation between actual and predicted prices: class="num">0.9752007459642241
Gráfica de estrategia de inversión guardada como &class="macro">#x27;ETH_USDT_investment_strategy.png&class="macro">#x27;
Gráfica de drawdown guardada como &class="macro">#x27;ETH_USDT_drawdown.png&class="macro">#x27;
Sharpe Ratio: class="num">9.41431958149606
Sortino Ratio: class="num">11800588386323879936.0000
Número de rendimientos totales: class="num">28
Número de rendimientos en exceso: class="num">28
Número de rendimientos negativos: class="num">19
Media de rendimientos en exceso: class="num">0.005037
Desviación estándar de rendimientos negativos: class="num">0.000000
Sortino Ratio: nan
Beta: class="num">0.33875104783408166
Alpha: class="num">0.006981197358213854
Cross-Validation MAE: class="num">1270.7809910146143 ± class="num">527.5746657573876
SMA Mean Absolute Error(MAE): class="num">344.3737716856061
SMA Mean Absolute Error(MAE): class="num">344.3737716856061
SMA Root Mean Squared Error(RMSE): class="num">483.0396130996611
SMA R-squared(R2): class="num">0.5813550203375846
Gráfica de predicción SMA guardada como &class="macro">#x27;ETH_USDT_sma_price_prediction.png&class="macro">#x27;
Gráfica de precio, predicción y sentimiento guardada como &class="macro">#x27;ETH_USDT_price_prediction_sentiment.png&class="macro">#x27;
Gráfica de drawdown guardada como &class="macro">#x27;ETH_USDT_drawdown.png&class="macro">#x27;
Maximum Drawdown: class="num">0.00%

常见问题

把情绪信号和成交数据导出成 CSV,用 pandas 按时间对齐后逐根 K 线模拟开平仓,重点算胜率和最大回撤。
可以用系统自带的调度组件写个调用命令,让交易终端定时启动 Python 回测任务,省去人工点开终端的麻烦。
小布可把多源情绪与模型预测做一层共振过滤,只推送同时满足两条件的品种,减少你手动比对的耗时。
优先看净利润、胜率、最大回撤和夏普比率,这四个能快速判断策略在外汇贵金属上是否高风险过载。
正常,双层过滤会剔掉大量弱信号;若日线级别信号过少可放宽情绪阈值,但外汇贵金属波动大需控仓。