基于马尔可夫状态转移矩阵的神经网络自学习型EA(基础篇)
📘

基于马尔可夫状态转移矩阵的神经网络自学习型EA(基础篇)

第 1/3 篇

「马尔可夫链加神经网的自进化EA雏形」

这套EA的思路不是写死一套指标规则,而是把马尔可夫状态转移矩阵、神经网络模式识别和轻量对冲逻辑揉在一起,让程序在跑的过程中自己调权重。市场被切成分层状态,状态之间按概率跳转,网络负责从价格序列里抓非线性特征。 原作者在 MT5 环境里做了实证:年化约 28.7%,最大回撤 14.2%,夏普 1.65,盈利单占比 62.3%。数字看着漂亮,但外汇和贵金属本身就是高杠杆高风险品种,这种回测表现不等于未来能复制,拿真金白银前先在策略测试器里跑 own 数据。 值得关注的是,它在横盘和低波动、以及急拉急跌两种极端里都勉强站得住,说明状态切换机制确实在起作用,而不是靠某一段行情吃饭。

◍ 把市场状态塞进马尔可夫的无记忆框里

预测下一步行情到底要回看多少历史?马尔可夫链给的答案是:只要当前状态定义得够厚,过去可以全扔掉。它的数学约束写成条件概率就是——未来只挂接在当下,不认来路。 公式里 P(X_t+1 = j | X_t = i) = P_ij 这句话,表面看和技术分析“历史会重演”唱反调,其实分歧只在“状态”二字怎么写。我们把状态做成多维画像:趋势方向与强度用 ATR 量,波动率特征、价格相对关键水平的位移全编进去,过去的有效信息就压缩进了这一帧。 这么一来,3×3 的转移概率矩阵 P(i,j) 不再是抽象数学,而是某交易品种的“市场基因”。每个格子是从状态 i 滑到 j 的概率,扫一遍就能定位哪些转移更常发生。 处理这张矩阵我们用经典 MLP:输入层 9 个神经元吃下 3×3 每个元素,隐藏层 40 个 ReLU 神经元做非线性提炼,输出层 2 个神经元给涨跌概率。MT5 上跑一遍,你能直接比对网络输出和矩阵原值,验证它是否挖到了统计看不出的微弱关联。外汇与贵金属波动受事件驱动,这类概率模型仅作辅助,实盘仍属高风险。

MQL5 / C++
P(X_t+<span class="number">class="num">1</span> = j | X_t = i, X_t-<span class="number">class="num">1</span> = i_t-<span class="number">class="num">1</span>, ..., X_0 = i_0) = P(X_t+<span class="number">class="num">1</span> = j | X_t = i) = P_ij

把波动率标准化成三种市况

判定当前市况是整套 EA 的底座。核心思路是用「当日收盘价变动」除以 ATR(14) 做标准化,让阈值随波动率伸缩,避免在低波动期误判、高波动期迟钝。 原文给出的 GetMarketState 用 D1 周期:若收盘价差 > 0.5*ATR 判为上涨,< -0.5*ATR 判为下跌,否则横盘。这样同一套逻辑在 EURUSD 平静期与剧烈行情期都不用手动改参数。 状态只分三种:FLAT(0)、UPTREND(1)、DOWNTREND(2)。后续转移矩阵和神经网络输入都基于这个序列,相当于把连续价格压成离散标签。 ATR 句柄在 OnInit 里用 iATR(_Symbol, PERIOD_D1, 14) 创建,失败则退出初始化;ATR_Period 设为 14 是默认观察窗口,可改但建议先按原文跑通。 外汇与贵金属属高杠杆品种,标准化阈值仅为概率参考,实盘前请在 MT5 策略测试器用历史数据验证再上真金。

MQL5 / C++
class=class="str">"cmt">// Enumeration of possible market states
enum MARKET_STATE
{
   STATE_FLAT = class="num">0,      class=class="str">"cmt">// Sideways market
   STATE_UPTREND = class="num">1,   class=class="str">"cmt">// Bullish market
   STATE_DOWNTREND = class="num">2  class=class="str">"cmt">// Bearish market
};
class=class="str">"cmt">// Function to determine current market state based on price movement relative to volatility
MARKET_STATE GetMarketState(class="type">int shift)
{
   class="type">class="kw">double close[], atr[];
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(atr, true);
   
   class=class="str">"cmt">// Get closing prices and ATR values
   if(CopyClose(_Symbol, PERIOD_D1, shift, class="num">2, close) < class="num">2 ||
      CopyBuffer(atrHandle, class="num">0, shift, class="num">1, atr) < class="num">1) {
      class="kw">return STATE_FLAT; class=class="str">"cmt">// Default to flat if data is insufficient
   }
   
   class=class="str">"cmt">// Calculate price change and get ATR value
   class="type">class="kw">double priceChange = close[class="num">0] - close[class="num">1];
   class="type">class="kw">double atrValue = atr[class="num">0];
   
   class=class="str">"cmt">// Determine market state based on price change relative to ATR
   if(priceChange > class="num">0.5 * atrValue) class="kw">return STATE_UPTREND;
   if(priceChange < -class="num">0.5 * atrValue) class="kw">return STATE_DOWNTREND;
   class="kw">return STATE_FLAT;
}
class=class="str">"cmt">// Global variables
class="type">int atrHandle;            class=class="str">"cmt">// Handle for the ATR indicator
class="type">int ATR_Period = class="num">14;     class=class="str">"cmt">// Default ATR period
class=class="str">"cmt">// Initialize indicators in OnInit function
class="type">int OnInit()
{
   class=class="str">"cmt">// Create ATR indicator handle
   atrHandle = iATR(_Symbol, PERIOD_D1, ATR_Period);
   if(atrHandle == INVALID_HANDLE) {

「马尔可夫状态矩阵的实盘落地」

EA 初始化阶段若 ATR 句柄创建失败,必须显式释放并返回 INIT_FAILED,否则后续依赖波动率的计算会全部失真。OnDeinit 里调用 IndicatorRelease(atrHandle) 不是可选项,MT5 在移除 EA 时不会自动回收指标句柄,漏写会造成终端内存泄漏。 用 3x3 矩阵刻画市场状态迁移时,核心是把历史 K 线映射成 FLAT / UPTREND / DOWNTREND 三态。UpdateMarkovMatrix 从 bars-1 往前扫,逐根统计 prevState 到 currentState 的跳转次数,再除以该状态总观测数得到转移概率;若某状态在样本内一次没出现,则退化为各 1/3 的均匀先验。 调试时 PrintMarkovMatrix 会把矩阵按「FROM\TO」表格式打到终端,概率统一保留两位。你可以直接把这段代码挂到 EURUSD 的 M15 上跑 500 根,看 UPTREND→DOWNTREND 一格是否显著高于对角线——外汇和贵金属杠杆高,状态翻转只是概率倾向,别拿它当方向保证。

MQL5 / C++
Print("Error creating ATR indicator: ", GetLastError());
class="kw">return INIT_FAILED;
}

class=class="str">"cmt">// Other initialization code...

class="kw">return INIT_SUCCEEDED;
}
class=class="str">"cmt">// Don&class="macro">#x27;t forget to release indicator handle when EA is removed
class="type">void OnDeinit(const class="type">int reason)
{
  class=class="str">"cmt">// Release ATR indicator handle
  IndicatorRelease(atrHandle);
}
class=class="str">"cmt">// Global variables for Markov matrix
class="type">class="kw">double markovMatrix[class="num">3][class="num">3];  class=class="str">"cmt">// 3x3 matrix of transition probabilities
class="type">int stateCounts[class="num">3];        class=class="str">"cmt">// Count of each state
class="type">int transitionCounts[class="num">3][class="num">3]; class=class="str">"cmt">// Count of transitions between states
class=class="str">"cmt">// Function to update the Markov transition matrix based on historical data
class="type">void UpdateMarkovMatrix(class="type">int bars)
{
  class=class="str">"cmt">// Initialize arrays
  ArrayInitialize(markovMatrix, class="num">0);
  ArrayInitialize(stateCounts, class="num">0);
  ArrayInitialize(transitionCounts, class="num">0);

  class=class="str">"cmt">// Get the initial state
  MARKET_STATE prevState = GetMarketState(bars - class="num">1);

  class=class="str">"cmt">// Process historical data to count transitions
  for(class="type">int i = bars - class="num">2; i >= class="num">0; i--) {
    MARKET_STATE currentState = GetMarketState(i);
    stateCounts[currentState]++;
    transitionCounts[prevState][currentState]++;
    prevState = currentState;
  }

  class=class="str">"cmt">// Calculate transition probabilities
  for(class="type">int i = class="num">0; i < class="num">3; i++) {
    if(stateCounts[i] > class="num">0) {
      class=class="str">"cmt">// If we have observations for this state, calculate actual probabilities
      for(class="type">int j = class="num">0; j < class="num">3; j++) {
        markovMatrix[i][j] = (class="type">class="kw">double)transitionCounts[i][j] / stateCounts[i];
      }
    } else {
      class=class="str">"cmt">// If this state was never observed, assign equal probabilities
      for(class="type">int j = class="num">0; j < class="num">3; j++) {
        markovMatrix[i][j] = class="num">1.0 / class="num">3.0;
      }
    }
  }

  class=class="str">"cmt">// Optional: Debug output of the matrix
  PrintMarkovMatrix();
}
class=class="str">"cmt">// Helper function to print the Markov matrix for debugging
class="type">void PrintMarkovMatrix()
{
  Print("=== Markov Transition Matrix ===");
  class="type">class="kw">string states[class="num">3] = {"FLAT", "UPTREND", "DOWNTREND"};

  Print("FROM\TO\t| FLAT\t| UPTREND\t| DOWNTREND");
  Print("--------|-------|-----------|----------");

  for(class="type">int i = class="num">0; i < class="num">3; i++) {
    class="type">class="kw">string row = states[i] + "\t| ";
    for(class="type">int j = class="num">0; j < class="num">3; j++) {
      row += DoubleToString(markovMatrix[i][j], class="num">2) + "\t| ";
    }

◍ 把马尔可夫矩阵喂给神经网络

上面那段打印输出的是 3×3 马尔可夫转移矩阵实测值:FLAT 状态留在原地概率 0.68,转 UPTREND 0.17、转 DOWNTREND 0.15;UPTREND 维持概率 0.63;DOWNTREND 维持概率 0.67。这类惯性数据直接压平成一维特征向量,就能当神经网络的输入。 代码里先声明全局 MLP 对象,INPUT_SIZE=9 对应 3×3 矩阵元素,OUTPUT_SIZE=2 对应多空信号。训练函数 TrainAdvancedMLP 从当前品种复制 5000 根 K 线,若不足 3000 根直接返回失败,避免小样本过拟合。 滑窗更新用 UpdateMarkovMatrix(100) 每次重算 100 根窗口的矩阵,再展平、按绝对值最大值归一化。样本数定为 600,CMatrixDouble 尺寸 600×(9+2)。外汇与贵金属杠杆高、跳空频繁,这套训练在实盘前务必用策略测试器跑历史回测验证稳定性。

MQL5 / C++
class=class="str">"cmt">// Global variables for neural network
CMLPBase mlp;            class=class="str">"cmt">// Neural network object
const class="type">int INPUT_SIZE = class="num">9;   class=class="str">"cmt">// 3x3 Markov matrix elements
const class="type">int OUTPUT_SIZE = class="num">2;  class=class="str">"cmt">// Buy and Sell signals
class="type">class="kw">datetime lastTrainingTime;  class=class="str">"cmt">// Time of last training
class=class="str">"cmt">// Function to train the neural network class="kw">using historical data
class="type">bool TrainAdvancedMLP()
{
   class=class="str">"cmt">// Load historical price data
   class="type">class="kw">double main_close[];
   ArraySetAsSeries(main_close, true);
   
   class="type">int bars = CopyClose(_Symbol, PERIOD_CURRENT, class="num">0, class="num">5000, main_close);
   if(bars < class="num">3000) {
      Print("Insufficient data for training: ", bars, " bars");
      class="kw">return false;
   }
   
   class=class="str">"cmt">// Prepare training dataset
   class="type">int samples = class="num">600;
   CMatrixDouble xy;
   xy.Resize(samples, INPUT_SIZE + OUTPUT_SIZE);
   
   for(class="type">int i = class="num">0; i < samples; i++) {
      class=class="str">"cmt">// Prepare feature vector(Markov matrix elements)
      class="type">class="kw">double features[];
      ArrayResize(features, INPUT_SIZE);
      ArrayInitialize(features, class="num">0);
      
      class="type">int featureIndex = class="num">0;
      
      class=class="str">"cmt">// Update Markov matrix with a sliding window
      UpdateMarkovMatrix(class="num">100);
      
      class=class="str">"cmt">// Flatten Markov matrix into feature vector
      for(class="type">int m = class="num">0; m < class="num">3; m++) {
         for(class="type">int n = class="num">0; n < class="num">3; n++) {
            features[featureIndex++] = markovMatrix[m][n];
         }
      }
      
      class=class="str">"cmt">// Normalize features to improve training stability
      class="type">class="kw">double maxVal = class="num">1.0;
      for(class="type">int j = class="num">0; j < INPUT_SIZE; j++)
         if(MathAbs(features[j]) > maxVal) maxVal = MathAbs(features[j]);
      
      for(class="type">int j = class="num">0; j < INPUT_SIZE; j++)
         features[j] /= maxVal;
      
      class=class="str">"cmt">// Set input layer values(normalized Markov matrix elements)
      for(class="type">int j = class="num">0; j < INPUT_SIZE; j++) {
         xy.Set(i, j, features[j]);
      }
}

常见问题

用近200根K线的ATR做z-score标准化,±0.5为震荡,>0.5为趋势,<-0.5为收缩,直接写进EA初始化函数即可。
建议每500根收盘价滚动重算一次矩阵,窗口太短噪声大、太长跟不上regime切换,外汇贵金属高风险需回测确认。
小布可读取你标好的市况标签,自动输出转移概率矩阵并监控神经网络权重漂移,打开品种页就能看诊断。
可能矩阵状态数太少丢信息,先把三种市况拆成含波动率斜率的6态再训练,概率上信号质量会提升。
会,单根跳空常超出历史转移边界,需加波动率熔断层,贵金属避险周更倾向手动降仓。