基于马尔可夫状态转移矩阵的神经网络自学习型EA(基础篇)
「马尔可夫链加神经网的自进化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 上跑一遍,你能直接比对网络输出和矩阵原值,验证它是否挖到了统计看不出的微弱关联。外汇与贵金属波动受事件驱动,这类概率模型仅作辅助,实盘仍属高风险。
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 策略测试器用历史数据验证再上真金。
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 一格是否显著高于对角线——外汇和贵金属杠杆高,状态翻转只是概率倾向,别拿它当方向保证。
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)。外汇与贵金属杠杆高、跳空频繁,这套训练在实盘前务必用策略测试器跑历史回测验证稳定性。
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]); } }