English Русский Deutsch 日本語
preview
价格行为分析工具包开发(第三十六部分):实现Python直接读取MetaTrader 5行情数据流

价格行为分析工具包开发(第三十六部分):实现Python直接读取MetaTrader 5行情数据流

MetaTrader 5交易系统 |
53 0
Christian Benjamin
Christian Benjamin

内容


引言

我们的前一篇文章演示了如何通过简易的MQL5脚本,将历史K线数据传输至 Python、完成特征工程、训练机器学习模型,再把交易信号回传给MetaTrader执行;不再需要手动导出CSV文件、借助Excel进行分析,也规避了版本管理方面的各类问题。交易者由此获得一套端到端处理流程:将原始分钟K线数据转化为基于统计逻辑的入场信号,同时配套动态计算的止损(SL)与止盈(TP)价位。

该系统解决了算法交易中的三大痛点:

  1. 数据碎片化:不再需要复制粘贴CSV文件,也无需处理复杂的表格公式,MetaTrader 5图表可以直接与Python通信。
  2. 分析结果滞后:自动化完成特征工程与模型推理,实现实时信号输出,从被动应对行情转变为基于实时数据的主动交易。
  3. 风险管理不一致回测与实盘均采用基于ATR计算的止损止盈,保证每一笔交易遵循随波动率动态调整的交易规则,维护策略的竞争优势。

然而,依靠EA向Python推送数据会带来延迟以及系统复杂度问题。新版本充分利用Python作为MetaTrader 5客户端的能力,调用MetaTrader 5官方库直接获取和更新数据。该方法不再依赖EA定时器等待;Python可以按需读取数据,高效写入Parquet存储,并且以异步方式执行高负载计算任务。

在上述基础之上,我们的增强版Python‑MQL5混合工具具备更强大的功能:

  • Python端:通过官方原生库实时接入MetaTrader 5行情数据;高级特征工程(例如尖峰Z分数、MACD差值、ATR通道、Prophet趋势差值);具备时序感知的梯度提升模型流水线,采用滚动窗口持续重训练;全部能力通过轻量Flask API对外提供服务。
  • MQL5端:一套健壮的REST轮询EA,自带重试机制;图表仪表盘展示信号、置信度、连接状态;用箭头标记入场出场点位;可选择在严格风控规则下自动执行订单。

我们的第一篇文章仅提供概念验证,而这套面向生产环境的框架大幅降低部署时间,加快策略反馈闭环,让您依托数据,更有信心、更精准地开展交易。让我们开始深入实践!



系统架构概述

下面是MetaTrader 5Python服务之间数据、信号流转的高层流程说明,同时介绍各个组件的核心职责。


MetaTrader 5终端

MetaTrader 5终端作为主要行情接口与图表平台。它存放所选品种的实时与历史K线数据,并为EA提供运行环境。EA借助内置的WebRequest()功能定时获取最新K线数据,并将收到的交易信号、止损/止盈线、开仓/平仓箭头直接绘制在图表上。MetaTrader 5终端负责(开启条件下的)订单提交、本地绘图对象管理(面板、箭头、文本标签),以及向用户可视化展示系统输出结果。

Python行情数据源

不再依靠EA主动推送K线数据,Python 行情数据源组件调用MetaTrader 5官方Python库,按需拉取历史以及实时分钟级OHLCK线数据。初始化阶段创建压缩的Parquet数据存储,保存多日历史价格行为数据,后续不断追加新生成的 K 线。该架构不再依赖MQL5定时器周期,Python服务可以随机、快速读取完整历史行情,供回测与实时推理共同使用。

特征工程

原始K线加载至内存或磁盘后,特征工程层将原始数据转换为对机器学习有统计意义的输入特征。计算标准化尖峰Z分数、MACD柱差值、14周期RSI、用于衡量波动率的14周期ATR,以及基于EMA的动态包络通道。同时调用Facebook的Prophet库估算分钟级别趋势差值,捕捉行情属于均值回归还是趋势行情。整套自动化流水线保证实时数据与历史数据采用完全一致的处理逻辑,维持模型的可靠性。

机器学习模型

系统核心是梯度提升分类器,封装在带标准化缩放的scikit‑learn流水线中。模型在K线滚动窗口数据集上训练,使用TimeSeriesSplit时间序列拆分避免未来信息泄露,使用RandomizedSearchCV随机搜索交叉验证完成超参优化。标签生成方式:向前看未来十分钟价格走势,按照可配置阈值,将行情划分为做多、做空、观望三类标签。训练完成的模型序列化保存为model.pkl文件,保证回测与实盘环境都可以低延迟加载并执行推理。

Flask API

Flask API充当Python数据科学生态和MQL5 EA之间的桥梁。对外提供/analyze接口,接收包含近期收盘价与时间戳的JSON请求;执行特征流水线并调用已加载模型,输出各类分类概率;返回精简JSON报文,包含交易信号、止损、止盈以及conf(置信度)。这套轻量REST接口可以容器化,部署在任意服务器上,将Python算力与MetaTrader的运行环境解耦,便于扩展扩容。

MQL5 EA

在客户端侧,MQL5 EA只专注于用户交互与交易执行工作。EA定时轮询Flask API,解析返回的JSON数据,将每一条信号同时记录到“专家”选项卡以及本地CSV文件中,并更新图表仪表盘,展示当前信号、置信度、连接状态与时间戳。当收到有效的做多、做空或者平仓信号时,EA会绘制箭头标记以及止损止盈线,如果EnableTrading交易开关开启,则通过CTrade类执行开仓或平仓。将全部数据科学计算交由Python处理,EA本身保持轻量、响应迅速,同时便于维护。


Python后端详细解析

后端的底层是一套稳健的数据采集流水线,基于MetaTrader 5 官方Python库实现。服务首次运行时会执行“初始化引导”,拉取最近若干天的分钟级OHLCK线数据,并写入压缩的Parquet文件中。Parquet列式存储格式搭配Zstandard压缩算法,对时间序列片段的读取速度极快,同时磁盘占用很低。后续会采用增量更新机制,仅追加新生成的K线,避免重复下载,保证实盘推理与回测使用同一份最新的权威数据源。

import datetime as dt
import pandas as pd
import MetaTrader5 as mt5
from pathlib import Path

PARQUET_FILE = "hist.parquet.zst"
DAYS_TO_PULL = 60
UTC = dt.timezone.utc

def bootstrap():
    """Fetch last DAYS_TO_PULL days of M1 bars and write to Parquet."""
    now = dt.datetime.utcnow()
    start = now - dt.timedelta(days=DAYS_TO_PULL)
    mt5.initialize()
    mt5.symbol_select("Boom 300 Index", True)
    bars = mt5.copy_rates_range("Boom 300 Index", mt5.TIMEFRAME_M1,
                                start.replace(tzinfo=UTC),
                                now.replace(tzinfo=UTC))
    df = pd.DataFrame(bars)
    df['time'] = pd.to_datetime(df['time'], unit='s')
    df.set_index('time').to_parquet(PARQUET_FILE, compression='zstd')

def append_new_bars():
    """Append only the newest bars since last timestamp."""
    df = pd.read_parquet(PARQUET_FILE)
    last = df.index[-1]
    now = dt.datetime.utcnow()
    new = mt5.copy_rates_range("Boom 300 Index", mt5.TIMEFRAME_M1,
                               last.replace(tzinfo=UTC) + dt.timedelta(minutes=1),
                               now.replace(tzinfo=UTC))
    if new:
        new_df = pd.DataFrame(new)
        new_df['time'] = pd.to_datetime(new_df['time'], unit='s')
        merged = pd.concat([df, new_df.set_index('time')])
        merged[~merged.index.duplicated()].to_parquet(PARQUET_FILE,
                                                      compression='zstd')

原始K线数据准备就绪后,流水线会计算一组特征,用于捕捉动量、波动率以及极端行情波动。对价格一阶差分做归一化处理,除以其20根K线滚动标准差,得到"z-spike"(Z尖峰分数),以此识别突发的价格暴涨暴跌。MACD柱差值与14周期RSI分别用于量化趋势状态与超买/超卖水平,14周期ATR用于衡量当前市场波动率。以20周期EMA构建包络通道(EMA×0.997和EMA×1.003),可以跟随市场状态的切换自适应变化。最后,Facebook的Prophet库读取全部收盘价序列,预测分钟级别趋势差值,捕捉更加精细的、和时间相关的季节性与趋势漂移。

import numpy as np
import pandas as pd
import ta
from prophet import Prophet

def engineer(df: pd.DataFrame) -> pd.DataFrame:
    df = df.copy()
    # z-spike (20-bar rolling std)
    df['r'] = df['close'].diff()
    df['z_spike'] = df['r'] / (df['r'].rolling(20).std() + 1e-9)

    # MACD histogram diff, RSI, ATR
    df['macd'] = ta.trend.macd_diff(df['close'])
    df['rsi']  = ta.momentum.rsi(df['close'], window=14)
    df['atr']  = ta.volatility.average_true_range(df['high'],
                                                  df['low'],
                                                  df['close'],
                                                  window=14)

    # EMA envelopes
    ema = df['close'].ewm(span=20).mean()
    df['env_low'] = ema * 0.997
    df['env_up']  = ema * 1.003

    # Prophet trend delta (minute-level)
    if len(df) > 200:
        m = Prophet(daily_seasonality=False, weekly_seasonality=False)
        m.fit(pd.DataFrame({'ds': df.index, 'y': df['close']}))
        df['delta'] = m.predict(m.make_future_dataframe(periods=0,
                                                        freq='min'))['yhat'] - df['close']
    else:
        df['delta'] = 0.0

    return df.dropna()

我们将预测任务设定为三分类问题:如果未来10分钟价格上涨幅度超过设定阈值,则标记为“BUY”(做多);如果下跌幅度超过该阈值,则标记为“SELL”(做空);其余情况标记为“WAIT”(观望)。标签生成完成后,特征与标签按照滚动时间窗口进行切分,用于模型训练。使用scikit‑learn流水线先对全部特征做标准化处理,再拟合GradientBoostingClassifier(梯度提升分类器)。超参数(学习率、树的数量、树最大深度)通过RandomizedSearchCV随机搜索交叉验证完成优化,并且采用TimeSeriesSplit时间序列拆分的交叉验证策略,杜绝未来信息泄露问题。最优模型被序列化保存为model.pkl文件,可直接加载,实现低延迟推理预测。

import numpy as np
import joblib
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler
from sklearn.ensemble import GradientBoostingClassifier
from sklearn.model_selection import TimeSeriesSplit, RandomizedSearchCV

LOOKAHEAD_MIN   = 10
LABEL_THRESHOLD = 0.0015
FEATS = ['z_spike','macd','rsi','atr','env_low','env_up','delta']

def label_and_train(df: pd.DataFrame):
    # Look-ahead return
    chg = (df['close'].shift(-LOOKAHEAD_MIN) - df['close']) / df['close']
    df['label'] = np.where(chg > LABEL_THRESHOLD, 1,
                   np.where(chg < -LABEL_THRESHOLD, 2, 0))

    X = df[FEATS].dropna()
    y = df.loc[X.index, 'label']

    pipe = Pipeline([
        ('scaler', StandardScaler()),
        ('gb', GradientBoostingClassifier(random_state=42))
    ])
    param_dist = {
        'gb__learning_rate': [0.01, 0.05, 0.1],
        'gb__n_estimators': [300, 500, 700],
        'gb__max_depth': [2, 3, 4]
    }
    cv = TimeSeriesSplit(n_splits=5)
    rs = RandomizedSearchCV(pipe, param_dist, n_iter=12,
                            cv=cv, scoring='roc_auc_ovr',
                            n_jobs=-1, random_state=42)
    rs.fit(X, y)
    joblib.dump(rs.best_estimator_, 'model.pkl')
为打通Python与MetaTrader之间的数据交互,我们对外开放唯一的Flask接口:/analyze。客户端发送JSON请求报文,包含交易品种、一组收盘价数组以及对应的UNIX时间戳。该接口对传入报文复现整套特征工程流水线,加载预训练模型,计算各类别的预测概率,选出置信度最高的交易信号,并依据ATR特征动态计算止损与止盈价位。接口返回精简的JSON对象:
from flask import Flask, request, jsonify
import joblib
import pandas as pd

app = Flask(__name__)
model = joblib.load('model.pkl')

@app.route('/analyze', methods=['POST'])
def analyze():
    payload = request.get_json(force=True)
    closes = payload['prices']
    times  = pd.to_datetime(payload['timestamps'], unit='s')
    df = pd.DataFrame({'close': closes}, index=times)
    # duplicate open/high/low for completeness
    df[['open','high','low']] = df[['close']]

    feat = engineer(df).iloc[-1:]
    probs = model.predict_proba(feat[FEATS])[0]
    p_buy, p_sell = probs[1], probs[2]
    signal = ('BUY' if p_buy > 0.55 else
              'SELL' if p_sell > 0.55 else 'WAIT')
    atr = feat['atr']
    entry = feat['close']
    sl = entry - atr if signal=='BUY' else entry + atr
    tp = entry + 2*atr if signal=='BUY' else entry - 2*atr

    return jsonify(signal=signal,
                   sl=round(sl,5),
                   tp=round(tp,5),
                   conf=round(max(p_buy,p_sell),2))

if __name__ == '__main__':
    app.run(port=5000)


MQL5 EA客户端架构剖析

EA的主循环运行在定时器事件(OnTimer)或者新K线检测逻辑中,调用WebRequest()完成HTTP报文的收发。首先,通过CopyRates获取最近N根K线,将MqlRates数组转换为UTF‑8编码的JSON请求体,其中包含交易品种与收盘价序列,并配置所需的HTTP请求头。如果WebRequest()调用失败(返回值≤0),EA则通过GetLastError()捕获错误码,重试计数器累加,记录错误日志,并暂停后续请求,直至重试次数耗尽或者下一次定时器触发。请求成功时(状态码≥200),重置重试计数器,并更新lastStatus连接状态变量。该运行模式保障信号交互稳定异步,不会阻塞图表主线程,也不会因为短暂网络抖动造成程序崩溃。

// In OnTimer() or OnNewBar():
MqlRates rates[];
// Copy the last N bars into `rates`
if(CopyRates(_Symbol, _Period, 0, InpBufferBars, rates) != InpBufferBars)
    return;
ArraySetAsSeries(rates, true);

// Build payload
string payload = "{";
payload += StringFormat("\"symbol\":\"%s\",", _Symbol);
payload += "\"prices\":[";
for(int i=0; i<InpBufferBars; i++)
  {
    payload += DoubleToString(rates[i].close, _digits);
    if(i < InpBufferBars-1) payload += ",";
  }
payload += "]}";

// Send request
string headers = "Content-Type: application/json\r\nAccept: application/json\r\n\r\n";
char  req[], resp[];
int   len = StringToCharArray(payload, req, 0, WHOLE_ARRAY, CP_UTF8);
ArrayResize(req, len);
ArrayResize(resp, 8192);

int status = WebRequest("POST", InpServerURL, headers, "", InpTimeoutMs,
                        req, len, resp, headers);
if(status <= 0)
{
  int err = GetLastError();
  PrintFormat("WebRequest error %d (attempt %d/%d)", err, retryCount+1, MaxRetry);
  ResetLastError();
  retryCount = (retryCount+1) % MaxRetry;
  lastStatus = StringFormat("Err%d", err);
  return;
}
retryCount = 0;
lastStatus = StringFormat("HTTP %d", status);

将合法的JSON 返回报文解析得到交易信号、 止损止盈之后,EA更新图表仪表盘,并绘制新的箭头标记与价位线。仪表盘由一个OBJ_RECTANGLE_LABEL矩形底板以及四个文本标签组成,分别展示交易品种、当前信号、HTTP通信状态以及时间戳。处理交易标记时:先删除带指定前缀的旧绘图对象,再在对应价位新建OBJ_ARROW箭头图形;做多信号使用绿色向上箭头,做空信号使用红色向下箭头,平仓信号使用橙色箭头,各类信号采用不同的箭头编码与颜色加以区分。利用OBJ_HLINE水平直线绘制止损、止盈价位,止损显示为红色,止盈显示为绿色。所有绘图对象都加上图表唯一的名称前缀,在信号更新或者EA反初始化时做清理,保证图表界面整洁,不会堆积大量残留图形。

// Panel (rectangle + labels)
void DrawPanel()
{
  const string pid = "SigPanel";
  if(ObjectFind(0, pid) < 0)
    ObjectCreate(0, pid, OBJ_RECTANGLE_LABEL, 0, 0, 0);

  ObjectSetInteger(0, pid, OBJPROP_CORNER, CORNER_LEFT_UPPER);
  ObjectSetInteger(0, pid, OBJPROP_XDISTANCE, PanelX);
  ObjectSetInteger(0, pid, OBJPROP_YDISTANCE, PanelY);
  ObjectSetInteger(0, pid, OBJPROP_XSIZE, PanelW);
  ObjectSetInteger(0, pid, OBJPROP_YSIZE, PanelH);
  ObjectSetInteger(0, pid, OBJPROP_BACK, true);
  ObjectSetInteger(0, pid, OBJPROP_BGCOLOR, PanelBG);
  ObjectSetInteger(0, pid, OBJPROP_COLOR, PanelBorder);

  string lines[4] = {
    StringFormat("Symbol : %s", _Symbol),
    StringFormat("Signal : %s", lastSignal),
    StringFormat("Status : %s", lastStatus),
    StringFormat("Time   : %s", TimeToString(TimeLocal(), TIME_MINUTES))
  };

  for(int i=0; i<4; i++)
  {
    string lbl = pid + "_L" + IntegerToString(i);
    if(ObjectFind(0, lbl) < 0)
      ObjectCreate(0, lbl, OBJ_LABEL, 0, 0, 0);
    ObjectSetInteger(0, lbl, OBJPROP_CORNER, CORNER_LEFT_UPPER);
    ObjectSetInteger(0, lbl, OBJPROP_XDISTANCE, PanelX + 6);
    ObjectSetInteger(0, lbl, OBJPROP_YDISTANCE, PanelY + 4 + i*(TxtSize+2));
    ObjectSetString(0, lbl, OBJPROP_TEXT, lines[i]);
    ObjectSetInteger(0, lbl, OBJPROP_FONTSIZE, TxtSize);
    ObjectSetInteger(0, lbl, OBJPROP_COLOR, TxtColor);
  }
}

// Arrows & SL/TP lines
void ActOnSignal(ESignal code, double sl, double tp)
{
  // remove old arrows/lines
  for(int i=ObjectsTotal(0)-1; i>=0; i--)
    if(StringFind(ObjectName(0,i), objPrefix) == 0)
      ObjectDelete(0, ObjectName(0,i));

  // arrow
  int    arrCode = (code==SIG_BUY ? 233 : code==SIG_SELL ? 234 : 158);
  color  clr     = (code==SIG_BUY ? clrLime : code==SIG_SELL ? clrRed : clrOrange);
  string name    = objPrefix + "Arr_" + TimeToString(TimeCurrent(), TIME_SECONDS);
  ObjectCreate(0, name, OBJ_ARROW, 0, TimeCurrent(), SymbolInfoDouble(_Symbol, SYMBOL_BID));
  ObjectSetInteger(0, name, OBJPROP_ARROWCODE, arrCode);
  ObjectSetInteger(0, name, OBJPROP_COLOR, clr);

  // SL line
  if(sl > 0)
  {
    string sln = objPrefix + "SL_" + name;
    ObjectCreate(0, sln, OBJ_HLINE, 0, 0, sl);
    ObjectSetInteger(0, sln, OBJPROP_COLOR, clrRed);
  }

  // TP line
  if(tp > 0)
  {
    string tpn = objPrefix + "TP_" + name;
    ObjectCreate(0, tpn, OBJ_HLINE, 0, 0, tp);
    ObjectSetInteger(0, tpn, OBJPROP_COLOR, clrLime);
  }
}

实际的订单提交操作受EnableTrading开关控制,您可以便捷地在仅可视化模式和实盘执行模式之间切换。执行任意市价订单之前,EA调用PositionSelect(_Symbol)做校验,避免生成同方向重复持仓。对于做多信号,调用CTrade.Buy(),传入固定手数FixedLots、止损与止盈;做空信号调用CTrade.Sell();平仓信号则执行CTrade.PositionClose()。平仓操作会设置滑点容忍参数(SlippagePoints)。这套精简的带状态逻辑,可以防止意外重复开仓,同时保证所有订单严格遵循预先设定的风控参数。

void ExecuteTrade(ESignal code, double sl, double tp)
{
  if(!EnableTrading) return;

  bool hasPosition = PositionSelect(_Symbol);
  if(code == SIG_BUY && !hasPosition)
    trade.Buy(FixedLots, _Symbol, 0, sl, tp);
  else if(code == SIG_SELL && !hasPosition)
    trade.Sell(FixedLots, _Symbol, 0, sl, tp);
  else if(code == SIG_CLOSE && hasPosition)
    trade.PositionClose(_Symbol, SlippagePoints);
}

安装与配置

在生成实盘信号之前,需要同时准备好Python运行环境与MetaTrader平台。首先,在MetaTrader 5终端所在的同一台电脑上安装Python3.8或更高版本。创建并激活虚拟环境(执行python -m venv venv:Windows系统执行venv\Scripts\activate,macOS/Linux系统执行source venv/bin/activate),然后执行下面命令安装全部依赖库:
pip install MetaTrader 5 pandas numpy ta prophet scikit-learn flask loguru joblib
接下来,配置Windows防火墙,允许python.exeterminal64.exe发起出站HTTP请求。如果生产环境计划使用HTTPS部署,需要将SSL证书安装到受信任的根证书存储区,确保MetaTrader 5可以建立安全连接。

MetaTrader 5端配置步骤:

打开:工具 → 选项 → EA,开启DLL导入权限,将本地 API 地址

(例如:http://127.0.0.1:5000)添加到“允许WebRequest访问下列URL”输入框。这一步白名单配置至关重要 —— 如果不配置,MetaTrader 5会静默丢弃全部POST请求报文。

在项目文件夹中,把Python服务脚本(例如market_ai_engine.py)复制到自定义工作目录。修改脚本头部配置项:设置交易品种(MAIN_SYMBOL)、MetaTrader 5中您的登录信息(LOGIN_ID、PASSWORD、SERVER),以及文件路径(PARQUET_FILE、MODEL_FILE)。如果Flask服务不使用默认端口,您可以在启动服务时通过--port参数指定端口。
部署EA:将编译完成的EA.ex5(或.mq5源码文件)放入MetaTrader 5安装目录下的MQL5 → Experts文件夹中。

重启MetaTrader 5或者刷新导航器,EA就会出现在列表中。将EA拖拽至与Python端配置相同品种的1分钟(M1)图表上。在EA的输入参数中,将InpServerURL设置为 http://127.0.0.1:5000/analyze;设置InpBufferBars(例如60);设置InpPollInterval (例如60秒);设置InpTimeoutMs(例如5000毫秒)。初始阶段保持EnableTrading关闭,只验证信号,不执行真实订单。

最后,请按如下顺序启动Python后端服务:

1. python market_ai_engine.py bootstrap

2. python market_ai_engine.py collect 

3. python market_ai_engine.py train

4. python market_ai_engine.py serve --port 5000

Flask服务正常运行,并MetaTrader中开启自动交易(AutoTrading)后,EA就会开始轮询获取实时信号,在图表上绘制箭头以及止损/止盈线;一旦您确认系统运行无误,EA就会按照预先定义的风控规则执行交易。

故障排查

如果EA没有显示任何数据,或者信号始终返回“WAIT”,请确认已经将API地址添加至MetaTrader 5 EA设置的白名单中。对于HTTP/HTTPS混合环境:本地测试直接使用HTTP (127.0.0.1),生产环境切换为带可信证书的HTTPS。保证服务端与MetaTrader 5终端时钟同步(统一使用UTC时间,或者两者使用相同本地时区),避免K线请求时间错位。最后,确认自动交易已经开启,同时没有全局权限限制,或者没有其他EA干扰阻塞当前EA。


评估与性能结果

想要使用这套Python‑MQL5混合机器学习交易系统,第一步执行下面的命令完成历史数据初始化:

python market_ai_engine.py bootstrap
该命令调用MetaTrade 5官方Python库,直接从MetaTrade 5中下载最近60天的M1(1分钟)K线,生成本地数据集。数据保存为压缩的Parquet文件hist.parquet.zst,该格式读写速度快、存储占用小。除非需要完全重置历史数据,否则该步骤仅需执行一次。
C:\Users\hp\Desktop\Intrusion Trader>python market_ai_engine.py bootstrap
2025-08-04 23:39:23 | INFO | Bootstrapped historical data: 86394 rows

初始化完成之后,执行下面命令持续更新数据集:

python market_ai_engine.py collect
以确保数据集实时保持最新。该数据采集程序可以后台运行;只有当您希望数据流持续更新,且不想每次训练前都重新初始化全量历史数据时,才需要运行该程序。
C:\Users\hp\Desktop\Intrusion Trader>python market_ai_engine.py collect
2025-08-04 23:41:01 | INFO | Appended 2 new bars
2025-08-04 23:42:01 | INFO | Appended 1 new bars
2025-08-04 23:43:01 | INFO | Appended 1 new bars

数据集更新完毕后,下一步进行模型训练。 

python market_ai_engine.py train
该命令会启动完整的机器学习流水线。脚本读取Parquet文件,执行特征工程计算(包括脉冲异常检测、MACD差值、RSI、ATR通道、EMA包络线以及基于Prophet的趋势预测),随后使用随机搜索交叉验证(RandomizedSearchCV)封装的梯度提升分类器(GradientBoostingClassifier),在近期数据的滚动时间窗口上训练预测模型。训练完成后输出序列化模型文件model.pkl,可直接用于推理预测。
C:\Users\hp\Desktop\Intrusion Trader>python market_ai_engine.py train
23:48:44 - cmdstanpy - INFO - Chain [1] start processing
23:51:24 - cmdstanpy - INFO - Chain [1] done processing
2025-08-05 02:59:08 | INFO | Model training complete

想要评估模型在实盘环境下的潜在表现,可以执行以下命令开展回测:

python market_ai_engine.py backtest --days 30
该命令会模拟过去30天内的信号生成与交易结果,计算信号准确率、盈亏比、整体收益率等各项指标。将回测结果保存至CSV文件,方便后续分析,以此判断这套系统的策略表现是否符合您的预期。
C:\Users\hp\Desktop\Intrusion Trader>python market_ai_engine.py backtest --days 30
06:57:33 - cmdstanpy - INFO - Chain [1] start processing
06:58:30 - cmdstanpy - INFO - Chain [1] done processing
2025-08-05 06:59:20 | INFO | Backtest results saved to backtest_results_30d.csv

回测结果

以下是从30天 回测结果中提取的部分指标与交易明细:

累计权益随时间变化曲线图

关键指标汇总如下:

*   **Average Entry Price:** 3099.85
*   **Average Exit Price:** 3096.53
*   **Average PNL (Profit and Loss):** 3.32
*   **Total PNL:** 195.69
*   **Average Cumulative Equity:** 96.34
*   **First Trade Time:** 2025-07-11 14:18:00
*   **Last Trade Time:** 2025-07-27 02:00:00

胜率:

win_rate
72.88135528564453
胜率72.88%。代表大约73%的交易最终实现盈利。

模型训练与验证完成后,可以执行下面命令启动实盘推理:

python market_ai_engine.py serve --port 5000
该命令会启动轻量Flask API服务,监听来自MQL5端EA的请求。当EA向/analyze接口发起轮询请求时,服务端立刻获取最新K线数据,完成特征计算,运行模型推理,以JSON格式返回预测信号,以及对应的止损、止盈和置信度。该服务作为桥梁,打通图表与AI推理引擎。
2025-08-05 12:41:53 | INFO | analyze: signal=%s, sl=%.5f, tp=%.5f
127.0.0.1 - - [05/Aug/2025 12:41:53] "POST /analyze HTTP/1.1" 200 -

在MetaTrader端,EA需要正确配置,用于向Python服务发起轮询。在EA输入参数中填写服务地址(例如http://127.0.0.1:5000/analyze);并且EA需要挂载到和模型训练完全一致的交易品种与周期上,一般为M1(1分钟周期)。全部启动后,EA会定时拉取信号,在图表绘制箭头标记,并且可以选择按照严格风控规则自动执行交易。

2025.08.05 12:41:53.532 trained model (1) (Boom 300 Index,M1)   >>> JSON: {"symbol":"Boom 300 Index","prices":[2701.855,2703.124,
2704.408,2705.493,2705.963,2696.806,2698.278,2699.877,2701.464,2702.788,2691.762,2693.046,2694.263,2695.587,2696.863,2698.
179,2699.775,2701.328,2702.888,2698.471,2699.887,2695.534,2696.952,2698.426,2699.756,2699.552,2700.954,2702.131,2703.571,
2699.549,2700.868,2702.567,2703.798,2705.067,2706.874,2698.084,2699.538,2700.856,2702.227,2703.692,2705.102,2706.188,2707.609,2709.001,
2710.335,2711.716,2712.919,2712.028,2713.529,2715.052,2716.578,2717.
2025.08.05 12:41:53.943 trained model (1) (Boom 300 Index,M1)   <<< HTTP 200 hdr:
2025.08.05 12:41:53.943 trained model (1) (Boom 300 Index,M1)   {"conf":0.43,"signal":"WAIT","sl":2725.04317,"tp":2720.18266}
2025.08.05 12:41:53.943 trained model (1) (Boom 300 Index,M1)   [2025.08.05 12:41:53] Signal → WAIT | SL=2725.04317 | TP=2720.18266 | Conf=0.43


结论

本文中,我们搭建了一套依托Python数据科学能力的MQL5 EA。您现在拥有的整套组件如下:
  • 数据流转通道:EA从MetaTrader 5读取分钟级K线,并将数据写入Parquet文件。
  • 特征工程模块:计算Z‑score脉冲指标、MACD、RSI、ATR、包络通道,还包含基于Prophet得到的趋势差值。
  • 模型训练与推理服务:训练具备时间感知能力的梯度提升模型,通过Flask对外输出预测结果。
  • 图表交互执行层:MQL5接收信号,在图表绘制箭头、止损/止盈线,并且可以自动执行交易。
实现过程中我们处理了各类实际工程问题:包括Flask端JSON报文解析、EA内部HTTP请求限流,也看到了职责分离的设计思想如何让整套代码便于维护与扩展。
您可以尝试替换为其他算法(XGBoost、LSTM,任意模型均可),优化风控规则,或者使用Docker对Python服务做容器化部署,让部署流程更加简洁。基于这套基础框架,您可以继续迭代优化回测效果,进一步打磨您的自动化交易策略。






图表展示器
分析评论
分析大师
分析预测 
波动率导航仪
均值回归信号收割器
信号脉冲 
指标看板 
外部数据流
VWAP
Heikin Ashi   FibVWAP  
RSI背离
抛物线止损与反转指标 (PSAR) 
四分位绘制脚本
侵入探测器
TrendLoom工具  四分位看板 
ZigZag分析仪  相关性探索  市场结构反转检测工具
关联仪表盘  货币强度计 
PAQ分析工具 
双EMA分形突破器
针形柱、吞噬形态与相对强弱指数(RSI)背离
流动性扫单 开盘区间突破工具 暴涨与暴跌拦截 CCI零线EA
K线形态识别 基于TA-Lib的K线形态识别 K线区间工具 MetaTrader 5数据接收器 模型训练与部署 Python库的使用

本文由MetaQuotes Ltd译自英文
原文地址: https://www.mql5.com/en/articles/19065

附加的文件 |
MQL5 交易策略自动化(第二十七部分):创建带可视化反馈的价格行为螃蟹谐波形态 MQL5 交易策略自动化(第二十七部分):创建带可视化反馈的价格行为螃蟹谐波形态
本文将基于 MQL5 开发一套螃蟹谐波形态交易系统。程序借助枢轴点与斐波那契比例识别看涨、看跌螃蟹谐波形态,并按照精准的入场、止损、止盈点位触发交易。同时使用三角形、趋势线等图表对象做可视化展示,完整展示 XABCD 形态结构与各个交易价位。
MQL5自动化交易策略(第二十六部分):构建针形K线均价加仓的多持仓交易系统 MQL5自动化交易策略(第二十六部分):构建针形K线均价加仓的多持仓交易系统
在本文中,我们将在MQL5中开发一套针形K线(Pin Bar)均价加仓系统:识别针形K线形态作为开仓触发信号,采用均价加仓策略实现多持仓管理,并搭配移动止损与盈亏平衡调整功能增强策略。系统内置可自定义参数,同时配备信息面板,可对持仓与盈利情况进行实时监控。
利用深度强化学习优化Ilan智能交易系统 利用深度强化学习优化Ilan智能交易系统
本研究对经典Ilan网格交易策略进行改进,通过在MQL5中集成Q-learning算法,构建出适用于MetaTrader 5平台的自适应版本。本文展示如何定义状态特征、将连续状态空间映射为Q-table可处理的离散值、使用ε-贪婪算法选择操作,以及针对平均持仓收益与平仓事件设计奖励机制。您将实现Q-table的存储与加载功能、通过参数调优优化学习效率,并在策略测试器中对EURUSD(欧元兑美元)/AUDUSD(澳元兑美元)进行测试,以评估稳定性和回撤风险。
从基础到中级:对象事件(一) 从基础到中级:对象事件(一)
在本文中,我们将探讨当图表上的某个对象发生变化时,MetaTrader 5 可以生成的六个事件中的三个。从用户交互的角度来看,这些事件非常有用。这是因为,如果不了解这些事件,在尝试为特定目的管理对象时,我们将不得不付出更多的努力来维护特定的图表配置。