基于马尔可夫状态转移矩阵的神经网络自学习型EA·进阶篇
「按周期推算次日涨跌标签」
训练神经网络前,得先把「未来价格相对变化」算出来当监督信号。代码用 Period() 判断当前图表周期,再按一天 24 小时反推每根 K 线对应的 barsPerDay:M1 是 24*60=1440 根,M5 是 288 根,H1 是 24 根,H4 仅 6 根,D1 就是 1 根。 拿到 barsPerDay 后,若索引 i 往后推 barsPerDay 根不越界,就用 main_close[i] 减 main_close[i+barsPerDay],得到 future_price_change。这个值大于 0 标 buy_signal,小于 0 标 sell_signal,再写进 xy 矩阵的输出位(INPUT_SIZE+0 和 +1)。外汇与贵金属波动剧烈,这种标签只反映历史样本里的方向偏移,对实盘只是概率倾向。 网络结构写死为 {INPUT_SIZE, 40, OUTPUT_SIZE} 三层,用 MLPTrainLBFGS 以学习率 0.001、迭代上限 100 跑训练;info 为负会打印错误码并退出。训练成功只说明样本拟合完成,不代表未来能稳定盈利,开 MT5 把这段塞进 EA 回测才能看清过拟合程度。
class=class="str">"cmt">// Calculate target timeframe for prediction based on current timeframe class="type">int barsPerDay = class="num">0; class="kw">switch(Period()) { case PERIOD_M1: barsPerDay = class="num">24 * class="num">60; class="kw">break; case PERIOD_M5: barsPerDay = class="num">24 * class="num">12; class="kw">break; case PERIOD_M15: barsPerDay = class="num">24 * class="num">4; class="kw">break; case PERIOD_M30: barsPerDay = class="num">24 * class="num">2; class="kw">break; case PERIOD_H1: barsPerDay = class="num">24; class="kw">break; case PERIOD_H4: barsPerDay = class="num">6; class="kw">break; case PERIOD_D1: barsPerDay = class="num">1; class="kw">break; class="kw">default: barsPerDay = class="num">24; class="kw">break; } class=class="str">"cmt">// Calculate future price change for target value class="type">class="kw">double future_price_change = class="num">0; if(i + barsPerDay < bars) { future_price_change = main_close[i] - main_close[i + barsPerDay]; } class=class="str">"cmt">// Determine target signals based on future price movement class="type">bool buy_signal = future_price_change > class="num">0; class="type">bool sell_signal = future_price_change < class="num">0; class=class="str">"cmt">// Set output layer target values xy.Set(i, INPUT_SIZE + class="num">0, buy_signal ? class="num">1.0 : class="num">0.0); xy.Set(i, INPUT_SIZE + class="num">1, sell_signal ? class="num">1.0 : class="num">0.0); } class=class="str">"cmt">// Initialize neural network if not done already if(mlp.GetNeuronCount() == class="num">0) { class="type">int network_structure[] = {INPUT_SIZE, class="num">40, OUTPUT_SIZE}; mlp.Create(network_structure, class="num">3); } class=class="str">"cmt">// Train neural network class="kw">using L-BFGS algorithm class="type">int info = class="num">0; CMLPReportShell report; CAlglib::MLPTrainLBFGS(mlp, xy, samples, class="num">0.001, class="num">5, class="num">0.01, class="num">100, info, report); if(info < class="num">0) { Print("Training error, code: ", info); class="kw">return false; } class=class="str">"cmt">// Update last training time and log success lastTrainingTime = TimeCurrent(); Print("Training completed successfully. Used ", samples, " examples of Markov matrix"); class="kw">return true; } class=class="str">"cmt">// Function to get prediction from trained neural network class="type">bool GetPrediction(class="type">class="kw">double &buySignal, class="type">class="kw">double &sellSignal) { class=class="str">"cmt">// Check if neural network is trained if(mlp.GetNeuronCount() == class="num">0) {
神经网络信号如何驱动下单
预测函数里先判断模型是否训练过,未训练直接返回 false 并打印提示,避免空模型瞎跑。若距上次训练已超过 48*60*60 秒(即 48 小时),则触发重训练,保证马尔可夫特征不严重过时。 输入向量用 3x3 的 Markov 矩阵铺平,调用 UpdateMarkovMatrix(100) 表示基于最近 100 根 K 线更新状态转移概率,再交给 CAlglib::MLPProcess 得到 output[0] 买信号、output[1] 卖信号。 全局参数里 LotSize=0.01、MaxPositions=5、TakeProfit=100 点、PriceDistance=50 点,属于轻仓多单网格的底子。OnTick 中若 GetPrediction 失败直接 return;优先 CheckProfitClosure 平盈利单,再算总持仓,达到 5 单上限就退出。 实际触发 BUY 需同时满足:buySignal>0.55、BUY 类持仓未达上限、且 tick.ask 与 lastBuyPrice 差值大于 50*_Point,防止同价密集加仓。外汇与贵金属杠杆高,这类网格在单边行情中可能连续浮亏,务必在 MT5 策略测试器用历史数据验过再上。
Print("Neural network not trained yet"); class="kw">return false; } class=class="str">"cmt">// Check if we need to retrain(every class="num">48 hours) class="type">class="kw">datetime currentTime = TimeCurrent(); if(currentTime - lastTrainingTime > class="num">48 * class="num">60 * class="num">60) { Print("Retraining neural network(class="num">48 hours passed)"); if(!TrainAdvancedMLP()) { class="kw">return false; } } class=class="str">"cmt">// Prepare input vector with current Markov matrix class="type">class="kw">double input[INPUT_SIZE], output[OUTPUT_SIZE]; UpdateMarkovMatrix(class="num">100); class="type">int idx = class="num">0; for(class="type">int i = class="num">0; i < class="num">3; i++) { for(class="type">int j = class="num">0; j < class="num">3; j++) { input[idx++] = markovMatrix[i][j]; } } class=class="str">"cmt">// Get prediction from neural network CAlglib::MLPProcess(mlp, input, output); class=class="str">"cmt">// Return prediction values buySignal = output[class="num">0]; sellSignal = output[class="num">1]; class="kw">return true; } class=class="str">"cmt">// Global variables for position management class="type">class="kw">double lastBuyPrice = class="num">0; class=class="str">"cmt">// Price of last buy order class="type">class="kw">double lastSellPrice = class="num">0; class=class="str">"cmt">// Price of last sell order class="type">class="kw">double LotSize = class="num">0.01; class=class="str">"cmt">// Trading volume class="type">int MaxPositions = class="num">5; class=class="str">"cmt">// Maximum allowed positions class="type">class="kw">double TakeProfit = class="num">100; class=class="str">"cmt">// Target profit in points class="type">class="kw">double PriceDistance = class="num">50; class=class="str">"cmt">// Minimum distance between positions CTrade trade; class=class="str">"cmt">// Trading object class=class="str">"cmt">// Main trading function called on each tick class="type">void OnTick() { class=class="str">"cmt">// Get prediction from neural network class="type">class="kw">double buySignal = class="num">0, sellSignal = class="num">0; if(!GetPrediction(buySignal, sellSignal)) { class="kw">return; class=class="str">"cmt">// Exit if prediction fails } class=class="str">"cmt">// Process closing of profitable positions first CheckProfitClosure(); class=class="str">"cmt">// Check if maximum positions limit is reached class="type">int totalPositions = CountOpenPositions(); if(totalPositions >= MaxPositions) class="kw">return; class=class="str">"cmt">// Get current market prices class="type">MqlTick tick; if(!SymbolInfoTick(_Symbol, tick)) class="kw">return; class=class="str">"cmt">// Open BUY position if: class=class="str">"cmt">// class="num">1. Buy signal is strong enough(threshold class="num">0.55) class=class="str">"cmt">// class="num">2. We haven&class="macro">#x27;t reached max positions for BUY class=class="str">"cmt">// class="num">3. Price is far enough from the last buy to avoid clustering if(buySignal > class="num">0.55 && CountPositionsByType(POSITION_TYPE_BUY) < MaxPositions && (lastBuyPrice == class="num">0 || MathAbs(tick.ask - lastBuyPrice) > PriceDistance*_Point)) {
◍ 信号触发与止盈平仓的执行骨架
这段逻辑把 MLP 模型给出的 buySignal / sellSignal 转成实际下单动作,并用价格距离过滤重复开仓。卖单与买单对称:当 sellSignal 大于 0.55、当前卖单数量低于 MaxPositions,且最新 bid 与上次卖价偏差超过 PriceDistance*_Point 时才允许新开仓,避免同价位密集加仓。 平仓端由 CheckProfitClosure 接管:倒序遍历 PositionsTotal,只处理当前图表品种。多头盈利判定为 currentPrice - openPrice > TakeProfit*_Point,空头则为 openPrice - currentPrice 的同阈值比较,达标即调用 trade.PositionClose 平仓。 CountOpenPositions 提供基础计数,但开仓判断实际依赖 CountPositionsByType 按多空分类。外汇与贵金属杠杆高,TakeProfit 与 PriceDistance 若设太小,在滑点大的时段可能频繁触发或平不掉,建议在 MT5 策略测试器用真实点差回测确认。
if(trade.Buy(LotSize, _Symbol, tick.ask, class="num">0, class="num">0, "MLP_Buy")) { lastBuyPrice = tick.ask; Print("Opened BUY position based on MLP signal: ", buySignal); } class=class="str">"cmt">// Open SELL position with similar logic if(sellSignal > class="num">0.55 && CountPositionsByType(POSITION_TYPE_SELL) < MaxPositions && (lastSellPrice == class="num">0 || MathAbs(tick.bid - lastSellPrice) > PriceDistance*_Point)) { if(trade.Sell(LotSize, _Symbol, tick.bid, class="num">0, class="num">0, "MLP_Sell")) { lastSellPrice = tick.bid; Print("Opened SELL position based on MLP signal: ", sellSignal); } } class=class="str">"cmt">// Function to check and close profitable positions class="type">void CheckProfitClosure() { class="type">int total = PositionsTotal(); for(class="type">int i = total - class="num">1; i >= class="num">0; i--) { class="type">ulong ticket = PositionGetTicket(i); if(ticket <= class="num">0) class="kw">continue; if(!PositionSelectByTicket(ticket)) class="kw">continue; class=class="str">"cmt">// Skip positions of other symbols if(PositionGetString(POSITION_SYMBOL) != _Symbol) class="kw">continue; class="type">class="kw">double openPrice = PositionGetDouble(POSITION_PRICE_OPEN); class="type">class="kw">double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT); class="type">ENUM_POSITION_TYPE posType = (class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); class=class="str">"cmt">// Check if position has reached target profit class="type">bool closePosition = false; if(posType == POSITION_TYPE_BUY) { closePosition = (currentPrice - openPrice) > TakeProfit*_Point; } else if(posType == POSITION_TYPE_SELL) { closePosition = (openPrice - currentPrice) > TakeProfit*_Point; } class=class="str">"cmt">// Close the position if profit target is reached if(closePosition) { trade.PositionClose(ticket); Print("Closed position ", ticket, " with profit"); } } } class=class="str">"cmt">// Helper function to count all open positions for the current symbol class="type">int CountOpenPositions() { class="type">int count = class="num">0; class="type">int total = PositionsTotal(); for(class="type">int i = class="num">0; i < total; i++) { class="type">ulong ticket = PositionGetTicket(i); if(ticket <= class="num">0) class="kw">continue; if(!PositionSelectByTicket(ticket)) class="kw">continue;
「用两个函数锁定当前品种持仓数」
在 EA 里做仓位管控,第一步是搞清楚「现在这个图表品种到底挂了多少单」。下面这段 MQL5 把统计拆成两个层次:先数总持仓,再按买卖方向分别数,避免在主循环里反复调接口拖慢执行。 CountPositions 只认 _Symbol,遍历 PositionsTotal 返回的持仓数,用 PositionGetString(POSITION_SYMBOL) 做比对,匹配就 count++。实盘里如果同时跑多个品种 EA,这个函数能保证你只动自己图表上的单。 CountPositionsByType 多传一个 ENUM_POSITION_TYPE 参数,在符号匹配的基础上再用 PositionGetInteger(POSITION_TYPE) 过滤 BUY 或 SELL。这样你在加仓逻辑里可以直接写「若 CountPositionsByType(POSITION_TYPE_BUY) >= 3 则不再开多」,规则清晰且可回测。 外汇与贵金属杠杆高,持仓计数只是风控一环,实际批量平仓前建议先在策略测试器用历史数据跑一遍,确认计数逻辑在跳空、部分成交等异常场景下不漏单。
class="type">int CountPositions() { class="type">int count = class="num">0; class="type">int total = PositionsTotal(); for(class="type">int i = class="num">0; i < total; i++) { class="type">ulong ticket = PositionGetTicket(i); if(ticket <= class="num">0) class="kw">continue; if(!PositionSelectByTicket(ticket)) class="kw">continue; if(PositionGetString(POSITION_SYMBOL) == _Symbol) { count++; } } class="kw">return count; } class=class="str">"cmt">// Helper function to count positions by type(BUY or SELL) class="type">int CountPositionsByType(class="type">ENUM_POSITION_TYPE type) { class="type">int count = class="num">0; class="type">int total = PositionsTotal(); for(class="type">int i = class="num">0; i < total; i++) { class="type">ulong ticket = PositionGetTicket(i); if(ticket <= class="num">0) class="kw">continue; if(!PositionSelectByTicket(ticket)) class="kw">continue; if(PositionGetString(POSITION_SYMBOL) == _Symbol && (class="type">ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == type) { count++; } } class="kw">return count; }
多货币回测里真正站得住的数字
把 EA 基础架构跑起来后,我们用 2017–2025 年的多货币对历史数据做了一次完整回测。高流动性品种里,EURUSD 与 GBPUSD 的表现最靠前,其余交叉盘差异明显,不单列。 以 EURUSD 为例,固定参数 LotSize=0.01、MaxPositions=5、ATR_Period=14,总成交 182,524 笔。年均收益 66.7%,最大回撤 11%,夏普 1.3,盈利交易占比 44.7%,盈利因子 1.2,恢复因子 7.64。胜率不到一半却靠盈亏比补回来,这点对算法系统不算稀奇,但回撤压在百分之十几算克制。
- 年 3 月美元急拉那段,不少趋势类算法回撤炸裂,这套 EA 靠神经网络重训练加对冲,净值没破前高但也收了正收益。外汇和贵金属杠杆高,历史稳定性不代表未来不失效,参数直接搬进 MT5 前先开策略测试器跑一遍自己的样本外数据。
它自适应状态识别在趋势和震荡里都能跑,不像多数策略二选一。真要验证,把上面三组参数录进输入变量,用 2025 年数据做 walk-forward,看恢复因子会不会掉到 3 以下。