English Русский Deutsch 日本語
preview
MQL5自优化智能交易系统(第十三部分):基于矩阵分解浅谈控制理论

MQL5自优化智能交易系统(第十三部分):基于矩阵分解浅谈控制理论

MetaTrader 5 — 示例 |
37 2
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana

金融市场往往难以提前规划和预判。投资者情绪十分脆弱,会随全球形势与当下热点事件快速转变。因此,在历史数据中看起来盈利的策略,放到实时市场中常常会失效。

交易领域存在多种理论可以解释这类现象。但核心一点:交易程序一旦开发部署完成,其运行逻辑就固定下来,若无人工干预则无法修改。这意味着策略会一再重复同样的错误,既无法从失败中获益,也无法从过往失误中学习。

图 1:金融市场中部署交易程序的常规交易架构

针对这个长期存在的难题,人们提出过许多解决方案。其中一个极具潜力的方案来自控制理论。控制理论主要研究如何校正动态、混沌环境下系统的运行行为,目标是让系统重新朝着设定目标运行。

我们周期性地把策略的绩效数据反馈至反馈控制器;控制器会记录并观测策略和市场之间的相互作用,以此估算策略行为与市场结果之间的关联关系。控制器会挖掘和盈利 / 亏损交易强相关的主导模式。如果能够找到这类规律并完成学习,理论上,即便面对混乱且持续变化的市场,反馈控制器也能调整、引导交易系统的运行状态,实现盈利。 

这实际上会改变原有策略的部署架构,也就是图 1 里的原理图。在图 2 中,本文介绍控制理论文献里的常用符号表示法:市场输入信号记为 (M),策略输出记为 (S)。

图 2:我们可以用简化符号重新定义交易程序,M 代表市场输入,S 代表策略输出

在前面介绍矩阵分解的文章里,我们主要搭建回归与分类模型,用来预测未来价格水平或是技术指标的变化。而本文将聚焦控制理论,具体来说是闭环反馈控制器。数值驱动型交易程序的相关讨论中,这个方向常常被忽略,但它的基础理论完全可以适配交易者的需求。

本文目标:验证反馈控制器能够对交易系统实现精细调控。合理配置后,控制器可以校正系统,并使其持续朝着盈利交易的方向运行。

我们以基准版本交易策略作为起点:策略使用两条移动平均线,一条基于最高价、一条基于最低价。两条均线周期相同,构成一个均线通道。当价格向上突破通道,开多单;价格向下跌破通道,则开空单。这个基准版本作为盈利基准,我们期望加入反馈控制器后能够超越该基准。图 3 展示该策略在欧元兑美元(EURUSD)日线图上的运行效果。

图 3:交易策略在 EURUSD 日线图表上的可视化演示

反馈控制器会先观测 90 天的策略运行表现,之后才允许介入策略。也就是说,前 90 天控制器不会向系统输出任何指令,只采集观测数据。请注意:这个 90 天观测周期属于可调参数,合适的参数值无法预先确定。本文选取 90 天是人为设定,假设该周期契合主导外汇市场金融机构的业务周期。读者可以自行调试该参数。

在这 90 天内,控制器记录多项关键变量:市场报价、账户余额、账户净值、指标数值、开仓类型。这些观测数据存入一个矩阵,称为快照。之后训练线性模型,学习快照随时间演变的规律。简单来讲,用前 89 组快照映射后续 89 组快照,让系统学习策略状态随时间的演变过程。

90 天观测期结束后,这个线性系统整合全部系统状态,预测下一笔交易将盈利还是亏损。如果预测下一笔交易会亏损,系统直接暂停交易,直到预判市场环境转好再恢复。

所以,反馈控制器的任务是观测系统输出 (S),学习一个全新的控制函数 (F),通过它改变系统行为 (FS),引导系统重回盈利状态,让策略不再单纯由市场 (M) 直接驱动。90 天观测周期结束后,策略不再仅由市场输入(M)单独驱动,而是同时受到市场输入与反馈控制器调节信号的共同作用(FS + M)。

图 4:反馈控制器部署后,调控交易程序运行逻辑的示意图

我们用五年 EURUSD 日线数据对两套系统做回测。加入反馈控制器后,系统盈利能力提升 82%。基准策略原本盈利 134 美元;增加反馈控制器后,利润提升至 245 美元。除此之外,优化后的系统在获取更高利润的同时,开仓笔数减少:总开仓从 180 笔降至 152 笔,交易频次减少 15%。意味着新系统以更低风险换取更高收益,这是交易程序非常理想的特性。

同时整体风险得到降低。基准策略总亏损为 - 1092 美元;增加反馈控制器后总亏损降至 - 838 美元,而毛利润基本保持不变。这又是一项非常理想的结果。

基准策略的夏普比率为 0.34。接入反馈控制器后,夏普比率提升至 0.68,增幅 100%,在 EURUSD 这类高难度市场中,这个结果相当亮眼。盈利交易占比提升六个百分点,由 53.89% 升至 57.24%。相应的,亏损交易占比同步下降,证明反馈控制器成功学到了区分盈利、亏损交易的核心特征。最后,单笔交易期望收益由 0.75 提升至 1.61,提升幅度 114%。

显而易见,反馈控制器在数值型交易程序中起到关键作用。配置得当的前提下,它可以自主学习、适时干预,避免策略重复踩坑。直接基于快照观测数据搭建控制器的技术称为系统辨识。 

这类算法最初由流体力学领域工程师研发。当年工程师需要为飞机机翼组件设计控制器,但是湍流效应没有显式公式可以预测与校正。自然而然,他们需要找到方法:通过观测系统行为以及产生该行为的输入,学习最优控制输入。 

本文搭建的是系统的线性模型,所以该方法更精确的名称为线性系统辨识。本文展示的结果激励我们进一步拓展反馈控制器的任务范围,并在未来研究非线性系统辨识。下面正式开始。


在 MQL5 中实现

和我们之前绝大多数交易程序一样,首先定义重要的系统参数。对于这套基准策略,只需要一项系统定义:设置统一的均线周期。 

//+------------------------------------------------------------------+
//|                                         Closed Loop Feedback.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System definitions                                               |
//+------------------------------------------------------------------+
#define MA_PERIOD 10

系统设计的下一个重点是全局变量。这套基准策略仅需少量全局变量,关联我们用到的技术指标。具体来说,我们需要两个均线指标句柄,再加一个 ATR 指标句柄,ATR 用于设置止损。每个指标都需要独立的数据缓存数组。

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int    ma_h_handler,ma_l_handler,atr_handler;
double ma_h[],ma_l[],atr[];

本系列文章编写的几乎所有交易程序都会引入依赖库,我们不会从零手写全部代码。本程序调用交易库,外加两个自定义库:一个用于监测新 K 线生成,另一个用来获取买卖价等关键报价信息。 

//+------------------------------------------------------------------+
//| Dependencies                                                     |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <VolatilityDoctor\Time\Time.mqh>
#include <VolatilityDoctor\Trade\TradeInfo.mqh>

CTrade      Trade;
Time        *DailyTimeHandler;
TradeInfo   *TradeInfoHandler;

系统初始化阶段,我们首先实例化自定义类。同时创建各个指标实例。 

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   DailyTimeHandler = new Time(Symbol(),PERIOD_D1);
   TradeInfoHandler = new TradeInfo(Symbol(),PERIOD_D1);
   ma_h_handler = iMA(Symbol(),PERIOD_D1,MA_PERIOD,0,MODE_EMA,PRICE_HIGH);
   ma_l_handler = iMA(Symbol(),PERIOD_D1,MA_PERIOD,0,MODE_EMA,PRICE_LOW);
   atr_handler      = iATR(Symbol(),PERIOD_D1,14);
//---
   return(INIT_SUCCEEDED);
  }

当程序不再运行时,我们会删除之前创建的动态对象,以此高效管理内存,同时释放不再使用的指标,安全地回收系统资源。

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---
   delete DailyTimeHandler;
   delete TradeInfoHandler;
   IndicatorRelease(ma_h_handler);
   IndicatorRelease(ma_l_handler);
  }

每当收到新报价时,就会触发 OnTick 事件函数。该函数借助自定义库判断是否生成了一根新的日线 K 线。如果检测到新 K 线,程序就会更新缓存数组里的指标数值,并保存当前收盘价。若当前没有持仓,就执行交易规则判断买卖信号:如果收盘价高于均线通道上沿,则开多单;如果收盘价低于均线通道下沿,则开空单。 

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(DailyTimeHandler.NewCandle())
     {
      CopyBuffer(ma_h_handler,0,0,1,ma_h);
      CopyBuffer(ma_l_handler,0,0,1,ma_l);
      CopyBuffer(atr_handler,0,0,1,atr);

      double c = iClose(Symbol(),PERIOD_D1,0);

      if(PositionsTotal() == 0)
        {
         if(c > ma_h[0])
            Trade.Buy(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetAsk(),(TradeInfoHandler.GetBid()-(atr[0]*2)),(TradeInfoHandler.GetBid()+(atr[0]*2)),"");

         if(c < ma_l[0])
            Trade.Sell(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetBid(),(TradeInfoHandler.GetAsk()+(atr[0]*2)),(TradeInfoHandler.GetAsk()-(atr[0]*2)),"");
        }
     }
  }

最后,所有逻辑执行完毕后,我们取消定义程序开头声明的系统常量,完成资源清理。以上就是这套基准交易系统的完整代码逻辑。

//+------------------------------------------------------------------+
//| Undefine system constants                                        |
//+------------------------------------------------------------------+
#undef MA_PERIOD
//+------------------------------------------------------------------+

接下来我们就可以在历史数据上对程序进行回测。本次测试选取五年的欧元兑美元日线数据,运行这套基准策略。

图 5:基于五年欧元兑美元历史行情数据回测交易程序

为了让测试更加贴近真实场景,我们加入随机延迟。现实市场充满不确定性,引入延迟可以模拟这种不确定性。

图 6:为本轮分析设置贴近真实环境的回测参数

完成回测后,我们可以生成程序完整的绩效报告。本文开篇已经简要介绍了回测的总体结果。从详细报告可以看到,该系统夏普比率偏低,仅为 0.34;并且平均亏损单笔金额大于平均盈利单笔金额。公平来讲,即便后续优化版本,单笔平均亏损依旧略大于单笔盈利。但我们这套闭环反馈系统缩小了二者之间的差距。

图 7:汇总交易程序绩效的详细统计报表

另外,查看基准策略的净值曲线可以发现,整体虽然向上,但稳定性很差,常常连续数月在区间内震荡,盈亏循环往复,策略长时间陷入停滞。这种不稳定表现,正是我们希望引入反馈控制器、修改系统行为所要解决的问题。

图 8:交易程序生成的净值曲线形态不稳定,效果不尽如人意


优化初始回测结果

现在我们开始借助反馈控制器优化原始策略的回测表现。为避免代码冗余,原文省略了没有改动的代码段,重点展示相对于原始版本的修改部分。

可以明显看到,程序需要定义的系统常量变多了。原始版本只需要 1 个常量,升级后的版本一共 4 个。其中新增定义主要涉及三类内容:(1)反馈控制器正式启用前需要采集的总观测样本数量;(2)需要跟踪的特征数量,本例共 12 个特征,用于刻画交易策略运行表现;(3)记录当前持仓类型的向量。

//+------------------------------------------------------------------+
//|                                         Closed Loop Feedback.mq5 |
//|                                               Gamuchirai Ndawana |
//|                    https://www.mql5.com/en/users/gamuchiraindawa |
//+------------------------------------------------------------------+
#property copyright "Gamuchirai Ndawana"
#property link      "https://www.mql5.com/en/users/gamuchiraindawa"
#property version   "1.00"

//+------------------------------------------------------------------+
//| System definitions                                               |
//+------------------------------------------------------------------+
#define MA_PERIOD    10
#define OBSERVATIONS 90
#define FEATURES     12
#define ACCOUNT_STATES 3

随着程序功能扩展,全局变量也更加复杂。现在需要矩阵存储状态快照、向量保存线性模型的预测结果,还有布尔标记,用来判断系统何时从观测阶段切换到实盘预测交易阶段。

//+------------------------------------------------------------------+
//| Global variables                                                 |
//+------------------------------------------------------------------+
int    ma_h_handler,ma_l_handler,atr_handler,scenes,b_matrix_scenes;
double ma_h[],ma_l[],atr[];
matrix snapshots,OB_SIGMA,OB_VT,OB_U,b_vector,b_matrix;
vector S,prediction;
vector account_state;
bool predict,permission;

系统初始化阶段会执行多项预处理工作。前面几步和之前一致,完成技术指标的创建。之后初始化快照矩阵,12 行 90 列,矩阵所有元素初始置1。permission 标记初始值为 true,表示系统一开始被允许交易但当 90 天观测周期结束后,该标记会被置为 false,同时 predict 标记设为 true。自此系统不再无条件执行交易;每一笔操作,都必须先获取线性模型的授权。

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---
   DailyTimeHandler  = new Time(Symbol(),PERIOD_D1);
   TradeInfoHandler  = new TradeInfo(Symbol(),PERIOD_D1);
   ma_h_handler      = iMA(Symbol(),PERIOD_D1,MA_PERIOD,0,MODE_EMA,PRICE_HIGH);
   ma_l_handler      = iMA(Symbol(),PERIOD_D1,MA_PERIOD,0,MODE_EMA,PRICE_LOW);
   atr_handler       = iATR(Symbol(),PERIOD_D1,14);
   snapshots         = matrix::Ones(FEATURES,OBSERVATIONS);
   scenes            = 0;
   b_matrix_scenes   = 0;
   account_state     = vector::Zeros(3);
   b_matrix          = matrix::Zeros(1,1);
   prediction        = vector::Zeros(2);
   predict           = false;
   permission        = true;
//---
   return(INIT_SUCCEEDED);
  }

OnTick 事件处理函数相较于基准系统做了大幅修改。一项核心改动:账户状态现在保存到一个包含 3 个元素的向量中。该向量采用独热编码(one-hot encoding):若持有多单,则第一个元素置 1;若持有空单,则第二个元素置 1;若无持仓,则第三个元素置 1。这种方式以结构化形式向线性模型提供分类信息。

交易逻辑本身保持不变:如果收盘价高于均线通道上沿,则考虑开多;如果收盘价低于均线通道下沿,则考虑开空。前 90 天内,predict 标记为 false,系统拥有无条件交易权限。90 天之后,所有交易决策都需要经过线性模型校验。一旦采集够所需数量的观测样本,快照矩阵会调整尺寸以容纳新数据;同时,我们构建好的线性辨识系统会在下单前对交易决策进行筛选过滤。 

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
//---
   if(DailyTimeHandler.NewCandle())
     {
      CopyBuffer(ma_h_handler,0,0,1,ma_h);
      CopyBuffer(ma_l_handler,0,0,1,ma_l);
      CopyBuffer(atr_handler,0,0,1,atr);
      double c = iClose(Symbol(),PERIOD_D1,0);

      if(PositionsTotal() == 0)
        {
         account_state = vector::Zeros(ACCOUNT_STATES);

         if(c > ma_h[0])
           {
            if(!predict)
              {
               if(permission)
                  Trade.Buy(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetAsk(),(TradeInfoHandler.GetBid()-(atr[0]*2)),(TradeInfoHandler.GetBid()+(atr[0]*2)),"");
              }

            account_state[0] = 1;
           }

         else
            if(c < ma_l[0])
              {
               if(!predict)
                 {
                  if(permission)
                     Trade.Sell(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetBid(),(TradeInfoHandler.GetAsk()+(atr[0]*2)),(TradeInfoHandler.GetAsk()-(atr[0]*2)),"");
                 }

               account_state[1] = 1;
              }

            else
              {
               account_state[2] = 1;
              }
        }

      if(scenes < OBSERVATIONS)
        {
         take_snapshots();
        }

      else
        {
         matrix temp;
         temp.Assign(snapshots);
         snapshots = matrix::Ones(FEATURES,scenes+1);

         //--- The first row is the intercept and must be full of ones
         for(int i=0;i<FEATURES;i++)
            snapshots.Row(temp.Row(i),i);

         take_snapshots();
         fit_snapshots();

         predict = true;
         permission = false;
        }
      scenes++;
     }
  }

在这一阶段,系统采集全部相关特征的状态快照:昨日开盘价、最高价、最低价、收盘价,各项技术指标数值、账户信息,以及经过独热编码的账户状态向量。纳入这些变量后,系统能够学习并建立策略与市场之间的映射模型。我们的目标不再仅仅局限于预测未来价格水平,而是想要预测交易账户未来的资金余额。倘若这种关联关系真实存在且能够被模型学到,那么反馈控制器就可以无需人工干预,自主调整策略的运行行为。

//+------------------------------------------------------------------+
//| Record the current state of our system                           |
//+------------------------------------------------------------------+
void take_snapshots(void)
  {
   snapshots[1,scenes] = iOpen(Symbol(),PERIOD_D1,1);
   snapshots[2,scenes] = iHigh(Symbol(),PERIOD_D1,1);
   snapshots[3,scenes] = iLow(Symbol(),PERIOD_D1,1);
   snapshots[4,scenes] = iClose(Symbol(),PERIOD_D1,1);
   snapshots[5,scenes] = AccountInfoDouble(ACCOUNT_BALANCE);
   snapshots[6,scenes] = AccountInfoDouble(ACCOUNT_EQUITY);
   snapshots[7,scenes] = ma_h[0];
   snapshots[8,scenes] = ma_l[0];
   snapshots[9,scenes] = account_state[0];
   snapshots[10,scenes] = account_state[1];
   snapshots[11,scenes] = account_state[2];
  }
//+------------------------------------------------------------------+

接下来我们使用两个矩阵来拟合线性系统:矩阵 X 作为输入,矩阵 y 作为目标输出。这是一个多输出系统,可以同时预测多个结果。矩阵 y 在时间上向后偏移一个步长,也就是输入对应前 89 组快照,输出对应紧随其后的 89 组快照。随着采集到的观测样本不断增加,该循环会通过统计已完成的总场景数,自然适配持续增长的数据集。

一个场景代表两次连续快照之间的时间间隔。本文中我们每天采集一次系统快照,因此场景数量读者可根据自身需求自行调整。我们利用伪逆解法(MQL5 中的`PInv()`函数)求解从 X 映射到 y 的最优系数。本系列前面的文章已经详细讲解过这个函数。如果读者尚不了解 PInv() 函数的重要作用,可以点击此处链接阅读相关内容,PInv() 是我们使用的一个强大工具。

拟合完成后,系统输出快照数据、输入与目标集、学习得到的系数以及模型预测结果。之后我们实时解析这些预测值:如果模型预判账户余额将会增长,则授予交易许可;当考虑开多单,并且模型预判均线的上沿存在上涨动能时,同样授予交易许可;同理,若考虑开空单,且模型预判均线下沿存在下跌动能,也授予交易许可。其余所有情况,系统都禁止交易。

最后,当获得交易许可且当前无持仓时,系统执行对应的交易。该流程执行完毕后,程序打印当前账户余额、预测余额,以及是否获得交易许可。到这里,所有用来改善策略绩效的代码修改就全部完成。我们编写的代码没有直接告诉控制器何时做多、何时做空,而是为控制器定义一套它可以执行的动作集合,控制器根据自身从积累的观测数据中推导出的结论自主做出决策。

//+------------------------------------------------------------------+
//| Fit our linear model to our collected snapshots                  |
//+------------------------------------------------------------------+
void fit_snapshots(void)
  {
   matrix X,y;
   X.Reshape(FEATURES,scenes);
   y.Reshape(FEATURES-1,scenes);

   for(int i=0;i<scenes;i++)
     {
      X[0,i] = snapshots[0,i];
      X[1,i] = snapshots[1,i];
      X[2,i] = snapshots[2,i];
      X[3,i] = snapshots[3,i];
      X[4,i] = snapshots[4,i];
      X[5,i] = snapshots[5,i];
      X[6,i] = snapshots[6,i];
      X[7,i] = snapshots[7,i];
      X[8,i] = snapshots[8,i];
      X[9,i] = snapshots[9,i];
      X[10,i] = snapshots[10,i];
      X[11,i] = snapshots[11,i];

      y[0,i] = snapshots[1,i+1];
      y[1,i] = snapshots[2,i+1];
      y[2,i] = snapshots[3,i+1];
      y[3,i] = snapshots[4,i+1];
      y[4,i] = snapshots[5,i+1];
      y[5,i] = snapshots[6,i+1];
      y[6,i] = snapshots[7,i+1];
      y[7,i] = snapshots[8,i+1];
      y[8,i] = snapshots[9,i+1];
      y[9,i] = snapshots[10,i+1];
      y[10,i] = snapshots[11,i+1];
     }

//--- Find optimal solutions
   b_vector = y.MatMul(X.PInv());
   Print("Day Number: ",scenes+1);
   Print("Snapshot");
   Print(snapshots);
   Print("Input");
   Print(X);
   Print("Target");
   Print(y);
   Print("Coefficients");
   Print(b_vector);
   Print("Prediciton");
   Print(y.Col(scenes-1));
   prediction = b_vector.MatMul(snapshots.Col(scenes-1));

   if(prediction[4] > AccountInfoDouble(ACCOUNT_BALANCE))
      permission = true;

   else
      if((account_state[0] == 1) && (prediction[6] > ma_h[0]))
         permission = true;

      else
         if((account_state[1] == 1) && (prediction[7] < ma_l[0]))
            permission = true;

         else
            permission = false;

   if(permission)
     {
      if(PositionsTotal() == 0)
        {
         if(account_state[0] == 1)
            Trade.Buy(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetAsk(),(TradeInfoHandler.GetBid()-(atr[0]*2)),(TradeInfoHandler.GetBid()+(atr[0]*2)),"");

         else
            if(account_state[1] == 1)
               Trade.Sell(TradeInfoHandler.MinVolume(),Symbol(),TradeInfoHandler.GetBid(),(TradeInfoHandler.GetAsk()+(atr[0]*2)),(TradeInfoHandler.GetAsk()-(atr[0]*2)),"");
        }
     }

   Print("Current Balabnce: ",AccountInfoDouble(ACCOUNT_BALANCE)," Predicted Balance: ",prediction[4]," Permission: ",permission);
  }
//+------------------------------------------------------------------+

框架搭建完成后,我们就可以在和之前完全相同的历史行情条件下对程序开展回测。为避免重复,此处不再复述测试参数,但所有参数均与图 6 保持一致。 

图 9:准备对优化后的交易策略开展测试

详细分析结果显示,基准策略与这套带反馈控制器的系统存在显著差异。净利润大幅提升,而总盈利几乎维持不变 —— 这说明控制器能够精准识别并拦截亏损交易,效果值得肯定。夏普比率得到明显提升,盈利单和亏损单的平均盈亏金额基本持平,相对基准策略而言,这是巨大的进步。 

图 10:交易程序的详细回测结果,展示了我们实现的各项优化

净值曲线的波动率有所下降,上涨趋势更强、稳定性更高。这正是我们在交易策略中引入闭环反馈控制器,期望实现的目标。

图 11:优化后的交易策略生成的净值曲线,随时间波动更小,收益更加稳定

我还附上了回测过程中系统采集快照的截图。受截图尺寸限制,12 个特征无法一次性全部展示。但读者可以看到,快照矩阵最后一行显示初始资金为 500 美元,也就是模拟交易策略的启动本金,可参考图 6。程序会持续追踪账户净值与余额如何受市场环境、以及交易策略决策的影响。

图 12:持续采集系统运行快照,用来监测并校正策略

输出日志里的快照记录印证了这套运行机制。例如,在第 1645 个交易日,线性模型预判本次交易会发生亏损,于是将交易许可标记置为 false,拦截了这笔交易。 

图 13:线性系统控制器判断市场环境不利,禁止策略开仓

到了第二天,模型预判这笔交易具备盈利潜力,系统便授予交易许可并执行下单。总而言之,这就是优化版程序从启动到运行的完整工作流程。这也说明,预测模型除了常规的价格预测之外,还有大量应用场景。我们可以利用训练得到的模型,挖掘盈利、亏损交易背后可学习的内在规律,以此避免重复犯下过去的错误。

图 14:当市场环境适配策略时,反馈控制器授予交易许可,继续交易



结论

综上,反馈控制器为我们提供了一种构建交易系统的思路,这类系统不仅可以盈利,还具备自适应能力。系统可以从自身的历史绩效中学习,帮助我们避免重复踩坑,即便市场环境持续变化,依然可以保持有效性。本次回测结果在盈利能力、交易效率与风险控制上都取得明显改善,证明控制理论在交易领域具备实际应用价值。展望未来,本次研究激励我们跳出线性模型的局限,探索非线性反馈控制器,以期捕捉市场行为中更复杂的规律。依托这些技术进展,面对充满不确定性的市场,我们可以持续提升交易系统的稳定性与盈利能力。

不过,在离开线性系统的研究之前,我们仍然可以对它做一些有价值的改进。在简单线性系统上衡量每一项潜在优化带来的收益,能够为后续搭建各类非线性系统,提供一套可靠的基准参照。

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

附加的文件 |
最近评论 | 前往讨论 (2)
Majeed Odubela
Majeed Odubela | 20 9月 2025 在 14:12
您未附上 volatilityDoctor/Time..Trade 的头文件。如果没有这两个头文件,就无法对您的提交进行测试。
Gamuchirai Zororo Ndawana
Gamuchirai Zororo Ndawana | 23 9月 2025 在 14:17
Majeed Odubela #:
您未附上 volatilityDoctor/Time..Trade 的头文件。如果没有这两个头文件,就无法对您的提交进行测试。
Majeed,很遗憾听到您遇到这样的情况。

不过,你也必须理解,这篇文章属于一个更大的相关系列,各篇内容相互衔接、层层递进。

您要找的那个类是在较早的一篇文章中从零开始构建并完整附上的。
交易策略 交易策略
各种交易策略的分类都是任意的,下面这种分类强调从交易的基本概念上分类。
市场模拟:MQL5 中的 SQL 入门(五) 市场模拟:MQL5 中的 SQL 入门(五)
在上一篇文章中,我展示了如何添加查询机制。这是必要的,以便在 MQL5 代码中能够充分利用 SQL,并使用 SQL SELECT 查询来检索结果。但还有一个函数需要实现,即 DatabaseReadBind 函数。由于要正确理解这个函数需要稍作详细解释,因此决定不在上一篇文章中介绍,而是在今天的文章中详述。鉴于这个主题内容较多,让我们直接进入下一节。
新手在交易中的10个基本错误 新手在交易中的10个基本错误
新手在交易中会犯的10个基本错误: 在市场刚开始时交易, 获利时不适当地仓促, 在损失的时候追加投资, 从最好的仓位开始平仓, 翻本心理, 最优越的仓位, 用永远买进的规则进行交易, 在第一天就平掉获利的仓位,当发出建一个相反的仓位警示时平仓, 犹豫。
基于协整股票的统计套利(第三部分):数据库搭建 基于协整股票的统计套利(第三部分):数据库搭建
本文提供一个 MQL5 Service(服务)的实现示例,用于保持新建数据库及时更新;该数据库作为数据分析以及一篮子协整股票交易的数据来源。文中详细阐述数据库的设计思路,并整理数据字典以供查阅。配套提供MQL5与Python脚本,用于数据库创建、表结构初始化以及行情数据写入。