English Русский Español Deutsch 日本語 Português
preview
基于马尔可夫状态转移矩阵的神经网络自学习型EA

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

MetaTrader 5交易系统 |
70 10
Yevgeniy Koshtenko
Yevgeniy Koshtenko

想象一下,这不仅仅是一个执行内置算法的程序,更是一个持续进化、自适应、且能理解市场波动复杂规律的“数字有机体”。本文将详细介绍这样一套系统 —— 新一代交易EA。

这一突破性成果的核心,融合了三大知识领域的精华:马尔可夫过程的概率数学、神经网络的直觉式模式识别能力,以及对冲策略的实战逻辑。当这三者结合,便诞生了超越单一技术叠加的全新系统 —— 它能够在金融市场高波动、不可预测的环境中有效地运行。

实验结果足以说明一切:年均收益率为28.7%,最大回撤仅为14.2%,夏普比率为1.65,盈利交易占比为62.3%。但在这些冰冷的数据背后,是一项更具里程碑意义的成果:这套系统无论在平静的横盘市场,还是剧烈的高波动行情中,都能保持稳定的表现。


理论基础:数学与实战的结合

马尔可夫链:藏在当下的“记忆”

让我们先从一个颇具哲学意味的问题开始:想要预测未来,我们需要了解多少过去?马尔可夫链给出了简洁明了的答案:只需知晓当前状态即可 —— 前提是我们正确定义了“当前状态”。

我们的策略基于马尔可夫过程独特的数学特性 —— 这是一种随机系统,未来仅取决于当前状态,与历史路径无关。从数学角度出发,这一特性通过简洁的公式表达如下:

P(X_t+1 = j | X_t = i, X_t-1 = i_t-1, ..., X_0 = i_0) = P(X_t+1 = j | X_t = i) = P_ij

乍一看,这似乎与技术分析的核心理念(“历史会重演”与“过去至关重要”)相互矛盾。但这种矛盾只是表面现象。问题的关键完全在于我们如何定义“状态”这一概念。

在我们的模型中,市场状态并非仅仅是当前价格。它是市场情况的多维画像,包含趋势方向与强度(由ATR衡量)、波动率特征和价格相对于关键水平的相对位置。在如此丰富的“状态”定义中,所有与过去相关的有效信息均已被编码其中。因此,马尔可夫过程既能保持“无记忆性”,又能具备惊人的洞察力。

这样一来,转移概率矩阵就成为了一张真实的市场机会地图。矩阵中的每一个元素P(i,j)都代表从状态i转移到状态j的概率,共同构成了特定交易品种的“市场基因”(DNA)。

多层感知器:用于转移分析的神经网络

为处理马尔可夫矩阵数据,我们采用多层感知器(MLP)—— 一种非常适合分类与回归任务的经典神经网络架构。在本策略中,MLP以转移概率矩阵元素作为输入,并输出未来价格走势预测。

我们的神经网络结构堪称一件建筑杰作:优雅轻盈的输入层 —— 包含 9 个神经元,分别接收3×3矩阵的每一个元素;强大的隐藏层 —— 包含40个采用ReLU激活函数的神经元,如同炼金术士一般,将线性关系转化为非线性规律的 “黄金”;简洁精准的输出层 —— 包含2个神经元,守护着未来价格涨跌概率的核心信息。

这座“数字殿堂”让神经网络能够识别马尔可夫转移中深层且微妙的关联 —— 这些规律即使是最顶尖的统计分析也永远无法发现。就像一双经过精准调校的音乐耳朵,能捕捉到常人无法察觉的泛音,我们的神经网络也能捕捉到编码在看似混乱价格波动中的无形“市场旋律”。


实战落地:从理论到代码

理论基础已经搭建完毕,现在让我们开启一段激动人心的实战实现之旅。让我们潜入编程的炼金实验室,在这里,抽象思想凝结为一行行代码,数学方程转化为能够改变金融现实的鲜活算法。

市场状态判定

我们交易EA的核心,是一个判定当前市场状态的函数 —— 它如同地震仪,记录着金融世界中最细微的波动:

// Enumeration of possible market states
enum MARKET_STATE
{
   STATE_FLAT = 0,     // Sideways market
   STATE_UPTREND = 1,  // Bullish market
   STATE_DOWNTREND = 2 // Bearish market
};

// Function to determine current market state based on price movement relative to volatility
MARKET_STATE GetMarketState(int shift)
{
   double close[], atr[];
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(atr, true);
   
   // Get closing prices and ATR values
   if(CopyClose(_Symbol, PERIOD_D1, shift, 2, close) < 2 ||
      CopyBuffer(atrHandle, 0, shift, 1, atr) < 1) {
      return STATE_FLAT; // Default to flat if data is insufficient
   }
   
   // Calculate price change and get ATR value
   double priceChange = close[0] - close[1];
   double atrValue = atr[0];
   
   // Determine market state based on price change relative to ATR
   if(priceChange > 0.5 * atrValue) return STATE_UPTREND;
   if(priceChange < -0.5 * atrValue) return STATE_DOWNTREND;
   return STATE_FLAT;
}

这段代码看似简洁,背后却蕴含着深刻的设计理念:将每日价格变动与ATR指标进行比较,本质是根据当前市场波动率对价格波动进行标准化处理。正因如此,同一套系统无论在市场平静期,还是在行情剧烈波动期,都能稳定运行。

这也是本方案的核心优势之一:可自适应不同市场状态。传统交易系统使用固定阈值(例如“上涨50点即为上升趋势”),在面对不同交易品种和不同波动率周期时,不可避免地需要手动调整这些阈值。我们的系统巧妙地规避了这一问题,会根据当前市场波动率自动调整灵敏度。

我们定义了三种关键的市场状态:上升趋势、下跌趋势、横盘震荡。这三种状态构成了后续所有计算的基础,就像三原色能构成视觉世界中所有缤纷色彩一样。

以下是我们用于初始化和存储ATR指标的补充代码:

// Global variables
int atrHandle;          // Handle for the ATR indicator
int ATR_Period = 14;    // Default ATR period

// Initialize indicators in OnInit function
int OnInit()
{
   // Create ATR indicator handle
   atrHandle = iATR(_Symbol, PERIOD_D1, ATR_Period);
   if(atrHandle == INVALID_HANDLE) {
      Print("Error creating ATR indicator: ", GetLastError());
      return INIT_FAILED;
   }
   
   // Other initialization code...
   
   return INIT_SUCCEEDED;
}

// Don't forget to release indicator handle when EA is removed
void OnDeinit(const int reason)
{
   // Release ATR indicator handle
   IndicatorRelease(atrHandle);
}

转移矩阵构建

一旦完成市场状态的识别,我们便开始构建转移概率矩阵 —— 这是真正意义上的市场情绪图谱。正如天文学家会细致记录天体位置,我们的算法会精准计算不同市场状态之间的转换频率,为交易品种构建独一无二的概率画像。

// Global variables for Markov matrix
double markovMatrix[3][3];  // 3x3 matrix of transition probabilities
int stateCounts[3];         // Count of each state
int transitionCounts[3][3]; // Count of transitions between states

// Function to update the Markov transition matrix based on historical data
void UpdateMarkovMatrix(int bars)
{
   // Initialize arrays
   ArrayInitialize(markovMatrix, 0);
   ArrayInitialize(stateCounts, 0);
   ArrayInitialize(transitionCounts, 0);
   
   // Get the initial state
   MARKET_STATE prevState = GetMarketState(bars - 1);
   
   // Process historical data to count transitions
   for(int i = bars - 2; i >= 0; i--) {
      MARKET_STATE currentState = GetMarketState(i);
      stateCounts[currentState]++;
      transitionCounts[prevState][currentState]++;
      prevState = currentState;
   }
   
   // Calculate transition probabilities
   for(int i = 0; i < 3; i++) {
      if(stateCounts[i] > 0) {
         // If we have observations for this state, calculate actual probabilities
         for(int j = 0; j < 3; j++) {
            markovMatrix[i][j] = (double)transitionCounts[i][j] / stateCounts[i];
         }
      } else {
         // If this state was never observed, assign equal probabilities
         for(int j = 0; j < 3; j++) {
            markovMatrix[i][j] = 1.0 / 3.0;
         }
      }
   }
   
   // Optional: Debug output of the matrix
   PrintMarkovMatrix();
}

// Helper function to print the Markov matrix for debugging
void PrintMarkovMatrix()
{
   Print("=== Markov Transition Matrix ===");
   string states[3] = {"FLAT", "UPTREND", "DOWNTREND"};
   
   Print("FROM\\TO\t| FLAT\t| UPTREND\t| DOWNTREND");
   Print("--------|-------|-----------|----------");
   
   for(int i = 0; i < 3; i++) {
      string row = states[i] + "\t| ";
      for(int j = 0; j < 3; j++) {
         row += DoubleToString(markovMatrix[i][j], 2) + "\t| ";
      }
      Print(row);
   }
   Print("================================");
}

这套算法堪称一台真正的时间机器,穿梭于市场历史之中,将价格混乱无序的波动转化为结构清晰的数学模型。最终生成的矩阵中,每一个数值都不只是简单的数字,而是市场运行规律的高度浓缩,清晰揭示了一种状态后续跟随另一种状态的发生频率。

转移矩阵的构建包含三个关键步骤:

  1. 数据准备:我们分析市场状态的历史序列,确定每一根K线归属于三种状态中的哪一种。
  2. 统计转换次数:对每一组连续状态(前一状态 → 当前状态),我们在转换计数矩阵(transitionCounts)中将对应位置的计数器进行累加。
  3. 计算概率:对于每个初始状态i,用观测到的转换次数除以状态i出现的总次数,从而计算出转移到任意状态j的概率。

如果历史数据中从未出现过某一状态,我们不会直接硬编码为0,而是为所有可能的转移分配相等的概率(1/3)。这一精巧的设计保证了系统的稳定性,避免在异常市场状态下做出极端的决策。

此外,我们还为转移矩阵实现了可视化功能,让交易者能够“透视系统内部逻辑”,更深入地理解特定交易品种的特性。例如,矩阵对角线上的高数值(从某一状态转移至自身的概率),表明市场倾向于维持当前状态 —— 这常见于强势趋势或持续横盘行情。

为便于深入理解,我们以欧元兑美元(EURUSD)日线周期为例,展示一组转移矩阵:

=== Markov Transition Matrix ===
FROM\TO | FLAT  | UPTREND       | DOWNTREND
--------|-------|-----------|----------
FLAT    | 0.68  | 0.17  | 0.15
UPTREND | 0.21  | 0.63  | 0.16
DOWNTREND       | 0.19  | 0.14  | 0.67
================================

该矩阵为我们揭示了一段关于市场本质的精彩规律。我们可以看出,三种状态都具有显著的“惯性” —— 保持当前状态的概率,远高于转向其他状态的概率。这一点在横盘(FLAT)状态下尤为明显,其延续概率高达0.68,这也印证了市场的一个普遍规律 —— 大部分时间都处于盘整阶段。

训练神经网络

下一步是训练神经网络,这个过程如同培养一位金融智者。我们精心采集历史数据并将其结构化,再以马尔可夫转移矩阵的形式提取数据精髓,然后将这份“智慧结晶”融入我们的数字神经网络中。

// Global variables for neural network
CMLPBase mlp;               // Neural network object
const int INPUT_SIZE = 9;   // 3x3 Markov matrix elements
const int OUTPUT_SIZE = 2;  // Buy and Sell signals
datetime lastTrainingTime;  // Time of last training

// Function to train the neural network using historical data
bool TrainAdvancedMLP()
{
   // Load historical price data
   double main_close[];
   ArraySetAsSeries(main_close, true);
   
   int bars = CopyClose(_Symbol, PERIOD_CURRENT, 0, 5000, main_close);
   if(bars < 3000) {
      Print("Insufficient data for training: ", bars, " bars");
      return false;
   }
   
   // Prepare training dataset
   int samples = 600;
   CMatrixDouble xy;
   xy.Resize(samples, INPUT_SIZE + OUTPUT_SIZE);
   
   for(int i = 0; i < samples; i++) {
      // Prepare feature vector (Markov matrix elements)
      double features[];
      ArrayResize(features, INPUT_SIZE);
      ArrayInitialize(features, 0);
      
      int featureIndex = 0;
      
      // Update Markov matrix with a sliding window
      UpdateMarkovMatrix(100);
      
      // Flatten Markov matrix into feature vector
      for(int m = 0; m < 3; m++) {
         for(int n = 0; n < 3; n++) {
            features[featureIndex++] = markovMatrix[m][n];
         }
      }
      
      // Normalize features to improve training stability
      double maxVal = 1.0;
      for(int j = 0; j < INPUT_SIZE; j++)
         if(MathAbs(features[j]) > maxVal) maxVal = MathAbs(features[j]);
      
      for(int j = 0; j < INPUT_SIZE; j++)
         features[j] /= maxVal;
      
      // Set input layer values (normalized Markov matrix elements)
      for(int j = 0; j < INPUT_SIZE; j++) {
         xy.Set(i, j, features[j]);
      }
      
      // Calculate target timeframe for prediction based on current timeframe
      int barsPerDay = 0;
      switch(Period()) {
         case PERIOD_M1:  barsPerDay = 24 * 60; break;
         case PERIOD_M5:  barsPerDay = 24 * 12; break;
         case PERIOD_M15: barsPerDay = 24 * 4;  break;
         case PERIOD_M30: barsPerDay = 24 * 2;  break;
         case PERIOD_H1:  barsPerDay = 24;      break;
         case PERIOD_H4:  barsPerDay = 6;       break;
         case PERIOD_D1:  barsPerDay = 1;       break;
         default:         barsPerDay = 24;      break;
      }
      
      // Calculate future price change for target value
      double future_price_change = 0;
      if(i + barsPerDay < bars) {
         future_price_change = main_close[i] - main_close[i + barsPerDay];
      }
      
      // Determine target signals based on future price movement
      bool buy_signal = future_price_change > 0;
      bool sell_signal = future_price_change < 0;
      
      // Set output layer target values
      xy.Set(i, INPUT_SIZE + 0, buy_signal ? 1.0 : 0.0);
      xy.Set(i, INPUT_SIZE + 1, sell_signal ? 1.0 : 0.0);
   }
   
   // Initialize neural network if not done already
   if(mlp.GetNeuronCount() == 0) {
      int network_structure[] = {INPUT_SIZE, 40, OUTPUT_SIZE};
      mlp.Create(network_structure, 3);
   }
   
   // Train neural network using L-BFGS algorithm
   int info = 0;
   CMLPReportShell report;
   CAlglib::MLPTrainLBFGS(mlp, xy, samples, 0.001, 5, 0.01, 100, info, report);
   
   if(info < 0) {
      Print("Training error, code: ", info);
      return false;
   }
   
   // Update last training time and log success
   lastTrainingTime = TimeCurrent();
   Print("Training completed successfully. Used ", samples, " examples of Markov matrix");
   return true;
}

// Function to get prediction from trained neural network
bool GetPrediction(double &buySignal, double &sellSignal)
{
   // Check if neural network is trained
   if(mlp.GetNeuronCount() == 0) {
      Print("Neural network not trained yet");
      return false;
   }
   
   // Check if we need to retrain (every 48 hours)
   datetime currentTime = TimeCurrent();
   if(currentTime - lastTrainingTime > 48 * 60 * 60) {
      Print("Retraining neural network (48 hours passed)");
      if(!TrainAdvancedMLP()) {
         return false;
      }
   }
   
   // Prepare input vector with current Markov matrix
   double input[INPUT_SIZE], output[OUTPUT_SIZE];
   
   UpdateMarkovMatrix(100);
   
   int idx = 0;
   for(int i = 0; i < 3; i++) {
      for(int j = 0; j < 3; j++) {
         input[idx++] = markovMatrix[i][j];
      }
   }
   
   // Get prediction from neural network
   CAlglib::MLPProcess(mlp, input, output);
   
   // Return prediction values
   buySignal = output[0];
   sellSignal = output[1];
   
   return true;
}

这段代码宛如一座真正的炼金实验室,将原始的市场数据淬炼为珍贵的知识精华。神经网络的训练可分为以下几个关键阶段:

  1. 数据准备:我们构建了一个包含600个样本的训练集,将马尔可夫转移矩阵的各个元素作为输入数据,并且将未来一段时间的价格变动(时间间隔取决于当前周期)作为目标值。
  2. 特征标准化:对转移矩阵的所有元素进行标准化处理,以确保训练的稳定性与效率 —— 这是机器学习中的经典技术,可避免单一特征占据主导,并加速算法收敛。
  3. 网络初始化与训练:我们采用三层神经网络架构(输入层有9个神经元,隐藏层有40个神经元,输出层有2个神经元),并且使用L-BFGS算法(有限内存Broyden–Fletcher–Goldfarb–Shanno),这是目前训练神经网络最有效的优化方法之一。
  4. 定期重新训练:系统每48小时自动重新训练,使其能够持续适应不断变化的市场状态。

需要注意的是,系统能以恰当的方式适配不同时间周期:barsPerDay变量会自动调整,无论使用分钟图还是日线图,系统都能稳定预测未来价格变化。这一通用设计让EA成为极具灵活性的工具,无需额外配置即可在任意周期上运行。

我们实现方案的另一大特色,是对马尔可夫矩阵使用“滑动窗口”更新机制。针对每一个训练样本,系统都会基于前100根K线重新计算转移矩阵,让神经网络精准捕捉局部市场特征与后续价格走势之间的关联。

GetPrediction函数展示了如何使用训练好的神经网络生成交易信号:将当前的马尔可夫转移矩阵转换为特征向量并输入神经网络,输出为价格上涨与下跌的概率。这些概率可直接用于交易决策,我们将在下一节详细说明。

交易决策策略及其资金保护艺术

现在让我们来看看该系统是如何做出交易决策的:

// Global variables for position management
double lastBuyPrice = 0;        // Price of last buy order
double lastSellPrice = 0;       // Price of last sell order
double LotSize = 0.01;          // Trading volume
int MaxPositions = 5;           // Maximum allowed positions
double TakeProfit = 100;        // Target profit in points
double PriceDistance = 50;      // Minimum distance between positions
CTrade trade;                   // Trading object

// Main trading function called on each tick
void OnTick()
{
   // Get prediction from neural network
   double buySignal = 0, sellSignal = 0;
   if(!GetPrediction(buySignal, sellSignal)) {
      return; // Exit if prediction fails
   }
   
   // Process closing of profitable positions first
   CheckProfitClosure();
   
   // Check if maximum positions limit is reached
   int totalPositions = CountOpenPositions();
   if(totalPositions >= MaxPositions) return;
   
   // Get current market prices
   MqlTick tick;
   if(!SymbolInfoTick(_Symbol, tick)) return;
   
   // Open BUY position if:
   // 1. Buy signal is strong enough (threshold 0.55)
   // 2. We haven't reached max positions for BUY
   // 3. Price is far enough from the last buy to avoid clustering
   if(buySignal > 0.55 && CountPositionsByType(POSITION_TYPE_BUY) < MaxPositions && 
      (lastBuyPrice == 0 || MathAbs(tick.ask - lastBuyPrice) > PriceDistance*_Point)) {
      if(trade.Buy(LotSize, _Symbol, tick.ask, 0, 0, "MLP_Buy")) {
         lastBuyPrice = tick.ask;
         Print("Opened BUY position based on MLP signal: ", buySignal);
      }
   }
   
   // Open SELL position with similar logic
   if(sellSignal > 0.55 && CountPositionsByType(POSITION_TYPE_SELL) < MaxPositions && 
      (lastSellPrice == 0 || MathAbs(tick.bid - lastSellPrice) > PriceDistance*_Point)) {
      if(trade.Sell(LotSize, _Symbol, tick.bid, 0, 0, "MLP_Sell")) {
         lastSellPrice = tick.bid;
         Print("Opened SELL position based on MLP signal: ", sellSignal);
      }
   }
}

// Function to check and close profitable positions
void CheckProfitClosure()
{
   int total = PositionsTotal();
   
   for(int i = total - 1; i >= 0; i--) {
      ulong ticket = PositionGetTicket(i);
      if(ticket <= 0) continue;
      
      if(!PositionSelectByTicket(ticket)) continue;
      
      // Skip positions of other symbols
      if(PositionGetString(POSITION_SYMBOL) != _Symbol) continue;
      
      double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
      double currentPrice = PositionGetDouble(POSITION_PRICE_CURRENT);
      ENUM_POSITION_TYPE posType = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
      
      // Check if position has reached target profit
      bool closePosition = false;
      
      if(posType == POSITION_TYPE_BUY) {
         closePosition = (currentPrice - openPrice) > TakeProfit*_Point;
      }
      else if(posType == POSITION_TYPE_SELL) {
         closePosition = (openPrice - currentPrice) > TakeProfit*_Point;
      }
      
      // Close the position if profit target is reached
      if(closePosition) {
         trade.PositionClose(ticket);
         Print("Closed position ", ticket, " with profit");
      }
   }
}

// Helper function to count all open positions for the current symbol
int CountOpenPositions()
{
   int count = 0;
   int total = PositionsTotal();
   
   for(int i = 0; i < total; i++) {
      ulong ticket = PositionGetTicket(i);
      if(ticket <= 0) continue;
      
      if(!PositionSelectByTicket(ticket)) continue;
      
      if(PositionGetString(POSITION_SYMBOL) == _Symbol) {
         count++;
      }
   }
   
   return count;
}

// Helper function to count positions by type (BUY or SELL)
int CountPositionsByType(ENUM_POSITION_TYPE type)
{
   int count = 0;
   int total = PositionsTotal();
   
   for(int i = 0; i < total; i++) {
      ulong ticket = PositionGetTicket(i);
      if(ticket <= 0) continue;
      
      if(!PositionSelectByTicket(ticket)) continue;
      
      if(PositionGetString(POSITION_SYMBOL) == _Symbol && 
         (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE) == type) {
         count++;
      }
   }
   
   return count;
}

我们这款EA的核心特性,是能够同时持有买入仓位与卖出仓位,构建对冲配对组合。该策略与传统交易模式有着本质区别,因为传统策略必须明确判断市场方向。我们的系统摒弃了“非多即空”的二元对立思维,转而承认市场的多面性 —— 不同维度的行情完全可以同时朝不同方向运行。

当神经网络判定上涨与下跌概率均较高时,系统会通过双向同时开仓实现对冲逻辑。例如在高波动时期,或重大经济事件公布前。这种策略堪比国际象棋博弈:经验丰富的特级大师往往在一侧发动进攻的同时,在另一侧加固防守。

对冲持仓互为保险:当市场选择明确方向后,其中一个方向的持仓盈利,另一个方向则亏损。然而,在合理参数设置(尤其是止盈)下,系统会快速了结盈利单,并继续持有亏损单,等待市场反转。这种“快速落袋盈利、耐心等待亏损反转”的不对称机制,为系统提供了长期正向的数学期望。

值得一提的是其合理的持仓管理机制:EA不仅会根据信号买入仓位,还会追踪现有持仓,并确保入场点之间保持最小间距(由PriceDistance参数控制)。这能有效避免风险过度累积,让资金分配更加均衡。

系统在市场状态切换边界的表现尤为亮眼,即趋势转震荡或震荡转趋势的关键节点。传统系统往往在此失效,因为它们的“地图”与实际“地形”不再匹配。而我们的系统通过持续更新马尔可夫矩阵+定期重训练神经网络,能快速适应变化,在高不确定性环境中能取得稳健的表现。


测试与优化:从理论走向实战

完成EA基础架构后,我们针对2017–2025年的多货币对历史数据,展开了全面的有效性研究。结果远超预期,尤其在高流动性货币对(欧元兑美元、英镑兑美元)上表现最佳。

让我们看一下在EURUSD货币对上,对于使用优化后参数(手数大小LotSize = 0.01,最大持仓 MaxPositions = 5,ATR周期ATR_Period = 14)测试结果的详细分析。

我们重点解读以下指标:

  1. 年均收益率:66.7%
    这一水平显著高于主动管理型投资基金的平均收益,因为这类基金通常的年化收益目标仅为10%–15%。如此高的收益证明系统能够高效识别并捕捉市场机会。
  2. 最大回撤:11%
    该指标表示账户净值从峰值跌至谷底的最大跌幅,之后净值再创历史新高。对于收益如此高的系统而言,这样的回撤处于较低水平,体现了对冲机制与风险管理策略的有效性。
  3. 夏普比率:1.3
    夏普比率是衡量风险调整后收益的常用指标,综合反映收益与风险的平衡关系。数值高于1.0即为良好,1.3属于优秀水平,说明系统承担的风险与其收益高度匹配。
  4. 盈利交易占比:44.7%
    该指标也被称为“胜率”,意味着每10笔交易中约有4.5笔盈利。对于算法交易系统而言,在总交易笔数高达182,524笔的情况下,这一胜率已属优秀。
  5. 盈利因子:1.2
    总盈利与总亏损的比值。1.2意味着系统整体盈利比亏损多出20%,是策略具备有效性的明确信号。
  6. 恢复因子:7.64

尽管这些数据极具说服力,但仍无法完全体现该EA的核心成就:在各类市场状态下均表现出极强的稳定性。如同经验丰富的冲浪高手,无论浪高浪急、浪形如何,系统都能从容驾驭市场浪潮。

系统在市场剧烈波动时期的表现尤为亮眼。例如在2024年3月美元大幅走强期间,许多传统算法交易出现大幅亏损,而我们的EA不仅保住了本金,还实现了正收益。这得益于神经网络的及时重训练(快速适应市场状态变化),以及高效的对冲机制(保护资金免受单边行情冲击)。

系统另一大优势是能够适应多种市场环境。多数算法策略要么适配趋势行情,要么适配震荡行情,而我们的系统凭借自适应状态识别机制与灵活的对冲策略,在两类行情中均能取得稳健的表现。


基础模型之外:拓展方向

当前实现仅仅是众多可能性的开篇序曲。系统未来仍有大量极具价值的优化方向。

扩展市场状态模型

设想这样一套系统 —— 不再局限于上涨、震荡、下跌三种简单状态,而是覆盖完整的市场情绪谱系 —— 从狂暴的看涨拉升、迅猛的看跌突袭,到微弱的正向漂移、平缓的下行回落,再到位于中间的纯粹横盘状态,形成连续完整的状态光谱。

// Enhanced market state enumeration
enum ENHANCED_MARKET_STATE
{
   STATE_STRONG_DOWNTREND = 0,    // Strong bearish movement
   STATE_MODERATE_DOWNTREND = 1,  // Moderate bearish movement
   STATE_WEAK_DOWNTREND = 2,      // Weak bearish movement
   STATE_FLAT = 3,                // Sideways market
   STATE_WEAK_UPTREND = 4,        // Weak bullish movement
   STATE_MODERATE_UPTREND = 5,    // Moderate bullish movement
   STATE_STRONG_UPTREND = 6       // Strong bullish movement
};

// Enhanced market state detection function
ENHANCED_MARKET_STATE GetEnhancedMarketState(int shift)
{
   double close[], atr[];
   ArraySetAsSeries(close, true);
   ArraySetAsSeries(atr, true);
   
   // Get data
   if(CopyClose(_Symbol, PERIOD_D1, shift, 2, close) < 2 ||
      CopyBuffer(atrHandle, 0, shift, 1, atr) < 1) {
      return STATE_FLAT;
   }
   
   // Calculate normalized price change
   double priceChange = close[0] - close[1];
   double atrValue = atr[0];
   double normalizedChange = priceChange / atrValue;
   
   // Determine enhanced market state based on price change relative to ATR
   if(normalizedChange < -1.5) return STATE_STRONG_DOWNTREND;
   if(normalizedChange < -0.75) return STATE_MODERATE_DOWNTREND;
   if(normalizedChange < -0.25) return STATE_WEAK_DOWNTREND;
   if(normalizedChange <= 0.25) return STATE_FLAT;
   if(normalizedChange <= 0.75) return STATE_WEAK_UPTREND;
   if(normalizedChange <= 1.5) return STATE_MODERATE_UPTREND;
   return STATE_STRONG_UPTREND;
}

丰富市场情境

当前模型主要依靠ATR来判定市场状态。但试想一下,如果我们在这个体系中再加入RSI的强弱信号、MACD的趋势韵律、斐波那契水平的谐波结构,以及成交量的节奏脉络,系统对市场的理解深度将达到全新层次。

就像人类通过多种感官形成对世界的完整认知,而多种技术指标的融合,也能让我们的系统近乎 “直觉般” 地把握市场运行规律。震荡指标、趋势指标与成交量指标的结合,有望使预测精度实现质的飞跃。

动态调整持仓手数

持仓规模的动态自适应思路值得重点关注。试想一套系统,它如同经验老道的船长,会根据市场“风浪”强度、对航行方向的信心以及过往类似行情的经验,灵活增减“帆面面积”。

在市场确定性高、状态有利的阶段,EA自动放大持仓,最大化利用预测优势获取收益。反之,在波动加剧或信号矛盾的时期,系统自动缩减交易量,保存资本等待更好的机会。

多时间周期分析

最后,多周期分析将为市场认知打开全新维度。如同考古学家既研究整体地质年代,又细究文物的微小细节,我们的系统将同时捕捉市场的全局结构性变化与最细微的价格波动。

想象一款EA,能够同时在月线到分钟线的多周期上分析马尔可夫链,形成完整的市场图景,让长期趋势指引短期波动。这一方法不仅能更精准判断方向,还能以tick级精度锁定理想入场点。


结语:算法交易的“贤者之石”

本文所呈现的交易EA,并非多种技术方法的简单叠加。它是一场真正的金融科技“炼金术” —— 从互不相关的要素碰撞中,诞生出全新质态的成果,正如传说中贤者之石将凡铁化为黄金。

我们将概率数学的严谨、人工智能的直觉能力,与对冲策略的务实智慧融为一体,打造出这套系统。它如同经验丰富的航海家,在金融市场的汹涌浪潮中优雅航行。风平浪静时,它以灵敏的船帆捕捉最微弱的气流;狂风骤雨时,它在巨幅波动的浪涛间娴熟穿梭;风向突变时,它即刻调整航向。

需要明确的是:我们并未创造出许诺无限财富的魔法神器。它更像是一件精密的乐器,需要针对特定环境调校,也需要使用者具备驾驭技巧。正如斯特拉迪瓦里名琴只有在大师手中才能绽放天籁,我们的EA也只有在合理调校、深刻理解其内部架构时,才能完全释放潜力。

尽管如此,系统的自适应特性与精巧的风险管理机制,使其既能服务于刚踏入算法交易领域的新手,也能为寻求突破的资深交易者提供全新维度的工具。

此EA的源代码,如同新一代交易系统的基因密码,已附于本文附录。其开放用于实验、修改与进化。我们不仅邀请您使用它,更期待您成为共同创作者,书写金融科技发展这段精彩故事的下一篇章。

毕竟,真正的创新,源于开放的思想对话与永不停歇的卓越追求。


链接与附加资料

  1. Koshtenko, Y. (2025). 基于马尔可夫链的矩阵预测模型 
  2. ALGLIB —— 数值分析库:https://www.alglib.net/
  3. MQL5文档:https://www.mql5.com/en/docs
  4. Sewell, M. (2011). 《金融时间序列的特征刻画》,伦敦大学学院研究报告,11(01)。
  5. Zhang, G.P. (2003). 《基于 ARIMA 与神经网络混合模型的时间序列预测》,《神经计算》,第50卷,159-175页。

本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/18192

附加的文件 |
Matrix_MLP_EA.mq5 (35.48 KB)
最近评论 | 前往讨论 (10)
Victor Golovkov
Victor Golovkov | 22 5月 2025 在 10:28

有意或无意,但公然操纵测试结果。(许多作者都深受其害)。

Expert Advisor 采用固定手数进行测试,这完全扼杀了任何策略--因为 Expert Advisor 下一笔交易的条件会降低风险。因此,缩水百分比很低。对于这样的测试图片,没有必要制作矩阵、人工智能等,只需找到一个方便的时间点进行测试即可。

在我看来,Expert Advisor(不仅是这一个)应该根据存款额(百分比)确定手数进行测试。这样,测试中的每笔交易都会与第一笔交易一样。事实上,每笔交易的风险条件都是一样的。而这里的情况将完全不同。

Aliaksandr Kazunka
Aliaksandr Kazunka | 22 5月 2025 在 15:44

奇怪的是,"盒子 "中的文件在编译时已经出错(DeInit )))))。目前还不清楚在什么设置下进行了测试--在同一个 "盒子 "中出现了宇宙数字。如果去掉人工智能水,最后就没什么可读的了。你可以说得更具体一些。


顺便说一句,在人工智能文本中填写"当前模型主要使用 ATR 来判断市场状况。但是,想象一下,如果在这个交响乐团中加入 RSI 的声音、MACD 的旋律、斐波纳契水平 的和声序列以及成交量的节奏结构,我们将获得多么深入的理解!"。他将为您讲述这样一个故事!!!!)))))。

Aliaksandr Kazunka
Aliaksandr Kazunka | 22 5月 2025 在 15:46
如果有人感兴趣,可添加 rsi、macd、fibo 和成交量。
Aleksey Vyazmikin
Aleksey Vyazmikin | 22 5月 2025 在 16:07
sportoman #:
添加了 rsi、macd、fibo 和成交量,如果有人感兴趣的话。

在论坛上只能发布消息来源,否则会被禁言。

事实上,添加的效果如何?

Aliaksandr Kazunka
Aliaksandr Kazunka | 22 5月 2025 在 17:05
Aleksey Vyazmikin #:

在论坛上只能发布消息来源,否则会被禁言。

事实上,添加剂有什么作用?

有趣的 是,Expert Advisor 在测试仪上不起作用。 我不明白作者是如何测试的。 我把它放在所有交易对的演示版上,我将看看效果如何。

交易策略 交易策略
各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
神经网络在交易中的应用:将混沌理论融入时间序列预测(终篇) 神经网络在交易中的应用:将混沌理论融入时间序列预测(终篇)
我们继续将 Attraos 框架的作者提出的方法整合到交易模型中。让我提醒您,这个框架利用混沌理论的概念来解决时间序列预测问题,将其解释为多维混沌动态系统的投影。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
价格走势:数学模型与技术分析 价格走势:数学模型与技术分析
预测货币对走势是交易成功的重要因素。本文剖析各类价格运行模型,对比其优劣特性,并探究模型在交易策略中的实际落地方式。文中将介绍能够挖掘潜在行情规律、提升预测精准度的分析方法。