English Русский Español Deutsch 日本語
preview
您应当知道的 MQL5 向导技术(第 47 部分):配合时态差异的强化学习

您应当知道的 MQL5 向导技术(第 47 部分):配合时态差异的强化学习

MetaTrader 5交易系统 |
27 0
Stephen Njuki
Stephen Njuki

概述

强化学习中的时态间差异(TD)简介,是理解 TD 如何与其它诸如蒙特卡洛(Monte Carlo)、Q-学习和 SARSA、等算法区分开来的门户。本文旨在通过强调 TD 学习的独特能力,来揭示 TD 学习的复杂性,即基于来自局次的部分信息,增量更新估值的能力,而非像蒙特卡洛方法中那样等待局次完成。这种区别令 TD 学习成为一种强大的工具,尤其是处于环境动态、且需及时更新学习政策的情况下。

上一篇强化学习文章中,我们研究了蒙特卡洛算法,其每局执行单次更新之前,会在多个周期内收集奖励信息。然而,时态差异(TD)就是从部分和不完整的情节中学习,就像我们之前在此处此处讨论的 Q-学习和 SARSA 算法一样。

以下是 TD、Q-学习和 SARSA 之间主要区别的摘要表格。

回顾一下,依政策更新“状态-行动”对的公式,举例 SARSA 如下:

TD 学习

Q-学习

SARSA

数值类型

状态值 V(s)

动作值 Q(s,a)

动作值 Q(s,a)

学习方式

未来状态值的估测

无政策

依政策

政策类型

不依特定政策

学习最优政策

学习当前行为政策

更新目标

下一个状态值 V(s′)

最大 Q(s′,a′)

实际 Q(s′,a′)

探索

往往需要单独政策

假设智顾寻求最优

更保守

行为

向下一个状态的值移动

贪婪;有利最优路径

遵循实际探索路径

回顾一下我们涵盖的一些行话,依政策更新意味着正在更新的“状态-行动”对是当前对,不一定是最优对、或具有最高 Q-值的对。如果我们要更新具有最高 Q-值的“状态-动作”对,那么这将是无政策方式。我们利用 MQL5 执行这些更新,如下所示:

//+------------------------------------------------------------------+
// Update using On-policy
//+------------------------------------------------------------------+
void Cql::SetOnPolicy(double Reward, vector &E)
{  Action(E);
//where 'act' index 1 represents the current Q_SA-action from Q_SA-Map
   double _sa = Q_SA[transition_act][e_row[1]][e_col[1]];
   double _v = Q_V[e_row[1]][e_col[1]];
   if(THIS.use_markov)
   {  int _old_index = GetMarkov(e_row[1], e_col[1]);
      int _new_index = GetMarkov(e_row[0], e_col[0]);
      _sa *= markov[_old_index][_new_index];
      _v *= markov[_old_index][_new_index];
   }
   for (int i = 0; i < THIS.actions; i++)
   {  Q_SA[i][e_row[1]][e_col[1]] += THIS.alpha * ((Reward + (THIS.gamma * _sa)) - Q_SA[i][e_row[1]][e_col[1]]);
   }
   Q_V[e_row[1]][e_col[1]] += THIS.alpha * ((Reward + (THIS.gamma * _v)) - Q_V[e_row[1]][e_col[1]]);
}
//+------------------------------------------------------------------+
// Update using Off-policy
//+------------------------------------------------------------------+
void Cql::SetOffPolicy(double Reward, vector &E)
{  Action(E);
//where 'act' index 0 represents highest valued Q_SA-action from Q_SA-Map
//as determined from Action() function above.
   double _sa = Q_SA[transition_act][e_row[0]][e_col[0]];
   double _v = Q_V[e_row[0]][e_col[0]];
   if(THIS.use_markov)
   {  int _old_index = GetMarkov(e_row[1], e_col[1]);
      int _new_index = GetMarkov(e_row[0], e_col[0]);
      _sa *= markov[_old_index][_new_index];
      _v *= markov[_old_index][_new_index];
   }
   for (int i = 0; i < THIS.actions; i++)
   {  Q_SA[i][e_row[0]][e_col[0]] += THIS.alpha * ((Reward + (THIS.gamma * _sa)) - Q_SA[i][e_row[0]][e_col[0]]);
   }
   Q_V[e_row[0]][e_col[0]] += THIS.alpha * ((Reward + (THIS.gamma * _v)) - Q_V[e_row[0]][e_col[0]]);
}

我们修改和修订的函数中包括更新,添加了一个新的 “Q_V” 对象,我们已将其表示为矩阵,以便清晰地映射到相应的环境状态,但我们可以很容易地将其当作向量,因为环境状态坐标能映射至单一索引整数。旧的 Q-映射更名为 “Q_SA”。新命名方式与新对象跟踪 Q-映射数值相一致,这些值与动作无关,而这恰是 TD 所关注的,将旧的 Q-Map 重命名为 Q_SA 则强调了其“状态-动作”对数值,其在函数被调用时都会被更新。如上所示我们的 MQL5 实现派生自以下 TD 公式(既可依政策、亦或无政策):

其中:

  • V (s) :当前状态 s 的值
  • V (s′):下一个状态 s′的值
  • α:学习率(控制我们调整当前值的程度)
  • r:采取动作后所获奖励
  • γ:折扣系数(判定未来奖励的重要性)
  • 该公式基于收到的奖励和下一个状态 V (s′) 的估值,更新状态 V (s) 的估值

回顾一下,依政策更新“状态-行动”对的公式,举例 SARSA 如下:

其中:

  • Q (s, a) :当前“状态-动作”对 (s, a) 的 Q-值
  • Q (s′, a′):下一个"状态-动作"对 (s′, a′) 的 Q-值,其中 a′ 是当前政策在下一个状态 s′ 中选择的动作
  • α:学习率
  • r:采取行动后所获奖励
  • γ:折扣因子

同样,无政策更新的公式如下所示:

其中:

  • Q (s, a) :当前“状态-动作”对 (s, a) 的 Q-值
  • max a′ ​Q (s′, a′) :所有可能的动作 a' 上下一个状态 s′ 的最大 Q-值(假设将采取 s′ 中的最佳动作)
  • α:学习率
  • r:采取行动后所获奖励
  • γ:折扣因子

从最后两个公式中可清晰看出,特定于动作的更新,为选择下一个动作提供了一些输入。不过,对于 TD 更新,由于它聚合了所有动作的数值,并简单地将它们分配给各自的环境状态,因此该过程对所要选择的动作的影响未能很好地定义。

这就是为什么在使用 TD 时,会伴随一个附加模型(在我们的例子中是政策神经网络),有必要为参与者指导选择下一个动作。该模型能够采取多种形式,但就我们的目的而言,它只是一个大小为 3-9-3 的 3 层 MLP,其中输出层为每个可能的 3 个动作提供概率分布,在我们的实例中仍然是买入、卖出、和持有。因此,该 MLP 将是一个分类器,而非一个回归器。我们在自定义信号类接口中示意该类的声明代码,如下所示:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CSignalTD   : public CExpertSignal
{
protected:

   int                           m_actions;           // LetMarkov possible actions
   int                           m_environments;      // Environments, per matrix axis
   int                           m_scale;             // Environments, row-to-col scale
   bool                          m_use_markov;        // Use Markov
   double                        m_epsilon;           // Epsilon
   bool                          m_policy;            // On Policy
   
public:
   void                          CSignalTD(void);
   void                          ~CSignalTD(void);

   //--- methods of setting adjustable parameters
   void                          QL_Scale(int value)
   {  m_scale = value;
   }
   void                          QL_Markov(bool value)
   {  m_use_markov = value;
   }
   void                          QL_Epsilon(bool value)
   {  m_epsilon = value;
   }
   void                          QL_Policy(bool value)
   {  m_policy = value;
   }

   //--- method of verification of arch
   virtual bool      ValidationSettings(void);
   //--- method of creating the indicator and timeseries
   virtual bool      InitIndicators(CIndicators *indicators);
   //--- methods of checking if the market models are formed
   virtual int       LongCondition(void);
   virtual int       ShortCondition(void);

protected:
   int               GetOutput(Cql *QL, CNeuralNetwork &PN);
   Sql               RL;
   Cql               *QL_BUY, *QL_SELL;
   CNeuralNetwork    POLICY_NETWORK_BUY,POLICY_NETWORK_SELL;
};

我们的自定义信号类的完整源代码附在本文末尾,旨在利用 MQL5 向导使用它,从而生成智能系统。此处此处有指南,为新读者指导该如何行事。附上我们的源代码后,读者可轻松修改 MLP 设计,不仅是层数,亦可是其大小。尽管,从我们的设计来看,输入层为 3,而 x 轴环境坐标、过渡值或当前 Q_V 矩阵读数、以及环境 y-轴读数作为输入。转换值取自我们在依策略和无政策设置中更新的 Q_V 矩阵,如上面分享的源代码那样。这种转换值的选择由修订后的 Action 函数处理,如下所示:

//+------------------------------------------------------------------+
// Choose an action using epsilon-greedy approach
//+------------------------------------------------------------------+
void Cql::Action(vector &E)
{  int _best_act = 0;
   if (double((rand() % SHORT_MAX) / SHORT_MAX) < THIS.epsilon)
   {  // Explore: Choose random action
      _best_act = (rand() % THIS.actions);
   }
   else
   {  // Exploit: Choose best action
      double _best_value = Q_SA[0][e_row[0]][e_col[0]];
      for (int i = 1; i < THIS.actions; i++)
      {  if (Q_SA[i][e_row[0]][e_col[0]] > _best_value)
         {  _best_value = Q_SA[i][e_row[0]][e_col[0]];
            _best_act = i;
         }
      }
   }
//update last action
   act[1] = act[0];
   act[0] = _best_act;
//markov decision process
   e_row[1] = e_row[0];
   e_col[1] = e_col[0];
   LetMarkov(e_row[1], e_col[1], E);
   int _next_state = 0;
   for (int i = 0; i < int(markov.Cols()); i++)
   {  if(markov[int(E[0])][i] > markov[int(E[0])][_next_state])
      {  _next_state = i;
      }
   }
   int _next_row = 0, _next_col = 0;
   SetMarkov(_next_state, _next_row, _next_col);
   e_row[0] = _next_row;
   e_col[0] = _next_col;
   transition_value = Q_V[_next_row][_next_col];
   policy_history[1][0] = policy_history[0][0];
   policy_history[1][1] = policy_history[0][1];
   policy_history[1][2] = policy_history[0][2];
   policy_history[0][0] = _next_row;
   policy_history[0][1] = transition_value;
   policy_history[0][2] = _next_col;
   transition_act = 1;
   for (int i = 0; i < THIS.actions; i++)
   {  if(Q_SA[i][_next_row][_next_col] > Q_SA[transition_act][_next_row][_next_col])
      {  transition_act = i;
      }
   }
   //if(transition_act!=1)printf(__FUNCSIG__+ " act is : %i ",transition_act);
}

因此,在我们的政政网络中,前面提到的 MLP 是最重要的论点,接下来要选择的合适动作只是当前环境状态、及其 Q-值的函数。这与我们之前一直所用的不同,我们在本文中(附带代码)把马尔可夫决策过程(MDP)中所用的选择合适动作的 Q-Map,重命名为 Q_SA。在所有情况下,我们都已使用无内存 MDP,即计算最近环境状态序列的缓冲区 。这些环境序列,得益于下面再次分享的 LetMarkov 函数,其为我们提供了下一个环境状态的投影。

//+------------------------------------------------------------------+
// Function to update markov matrix
//+------------------------------------------------------------------+
void Cql::LetMarkov(int OldRow, int OldCol, vector &E)  //
{  matrix _transitions;  // Count the transitions
   _transitions.Init(markov.Rows(), markov.Cols());
   _transitions.Fill(0.0);
   vector _states;  // Count the occurrences of each state
   _states.Init(markov.Rows());
   _states.Fill(0.0);
// Count transitions from state i to state ii
   for (int i = 0; i < int(E.Size()) - 1; i++)
   {  int _old_state = int(E[i]);
      int _new_state = int(E[i + 1]);
      _transitions[_old_state][_new_state]++;
      _states[_old_state]++;
   }
// Reset prior values to zero.
   markov.Fill(0.0);
// Compute probabilities by normalizing transition counts
   for (int i = 0; i < int(markov.Rows()); i++)
   {  for (int ii = 0; ii < int(markov.Cols()); ii++)
      {  if (_states[i] > 0)
         {  markov[i][ii] = double(_transitions[i][ii] / _states[i]);
         }
         else
         {  markov[i][ii] = 0.0;  // No transitions from this state
         }
      }
   }
}

幸好 MDP 判定下一个状态的过程仍靠 TD 执行,然此处的区别在于我们不能单独用这些投影自身的下一个状态坐标来判定下一个动作。以前,当我们用到 Q_SA 时,需要读取最高概率的动作,为下一个状态坐标加权,以便知晓参与者假定行事。不过,现在,我们的等效矩阵,Q_V 只为我们提供任何状态坐标的数值,但判定参与者的下一个动作在强化学习循环中至关重要。

因此,我们引入了一个政策网络,一个 MLP,出于我们的目的,我们简单地将其设计为 3-9-3,具有 9 个输出的单隐藏层,接受前面提到的两个环境坐标、及其 Q-值共 3 个输入,并输出一个 3 个向量,旨在捕获 3 种可能动作的概率分布:买入、 卖出、及持有,在向量中得分最高的数值则是推荐的动作。


TD 的宏观益处和目的

TD 的 Q-值更新频率高于蒙特卡洛,其在快速变化和流动的金融市场,优势显而易见。然而,从优势的立场来看,它与 SARSA 或 Q-学习有何不同?我们尝试通过查看一些日常示例来回答这个问题。

根据定义,TD 和 SARSA/Q-学习之间的主要区别在于,TD 更侧重基于数值的学习,其中仅依赖和更新状态值,而其它两种算法则侧重“状态-动作”配对,来执行类似的更新。


场景 A

假设仓库中有一个库存管理系统,该系统纯粹保持跟踪多种产品的库存水平。该系统的目标只是管理库存水平,并确保任何产品没有库存不足、或过剩的情况。

在这种状况下,TD 将比 SARSA 或 Q-学习更有优势,因为它专注于状态值,而非“状态-动作”对。这是因为在这种情况下,系统可能只需要预测每个状态的“数值”(例如,总体库存水平),而无需评估每个特定动作(例如,每个 SKU 的订购)。因此,在这种状况下,无需政策网络 MLP,TD 学习就能更新状态(库存水平)的数值函数,且无需计算每种产品的每笔可能的订购决策。

甚至,库存管理或许会有逐渐的变化,取代反馈不同奖励的突然“状态-行动”对。由于 TD 学习应对增量反馈,令其适用于平滑过渡的环境,其中了解整体状态比知晓每个“状态-动作”成果更重要。最后,具有若干动作、和大型“状态-动作”空间的环境,例如更复杂的库存管理(我们需要将动作规程映射到每个库存水平状态)、Q-学习和 SARSA 尽管适用,但必然会产生计算成本,而 TD 从未遇到过成本问题,因其整体应用程序更简单。

 

场景 B

研究一个智能建筑系统,它可以调整供暖、通风、和空调(HVAC)设置,以便最大限度地减少能源消耗,同时保持居住者的舒适度。故其目标是平衡短期回报、与长期目标一致,分别为降低能源费用、保持建筑物处于最佳温度、和空气品质。

在这种情况下,TD 将比 SARSA 或 Q-学习更适合,因为能耗水平、或用户舒适度是绝对指标,没有任何动作与它们挂钩,亦不依赖它们的数值。在这种特殊情况下,为了平衡短期和长期奖励,可以训练两个强化学习周期,并行预测两者。TD 的增量每周期更新(而非我们在蒙特卡洛看到的每局次更新),也使其成为这种智能建筑系统的理想选择,因为温度、占用率、及空气品量等环境条件会逐渐变化。这令 TD 能够提供无缝的调整机制。

最后,如上所述,SARSA 或 Q-学习等效项将遭遇额外的计算和内存需求,因为需要采取特定措施来“补救”环境状态值的任何不足或过度。  

 

场景 C

交通流量预测和控制系统,目标是通过预测交通流量、并相应调整交通灯信号,来最大限度地降低多个十字路口的拥堵,这为我们提供了第三个可能的 TD 用例。相比 SARSA 或 Q-学习,TD 在这种状况下也具有优势,因为主要关注的是理解和预测整体交通状态(如拥堵水平),而非优化每个单独的交通信号动作。TD 学习令系统能够学习交通状态的总体“值”,而非每个特定信号变化的影响。

流量本身也是动态的,且全天不断变化,这会影响 TD 增量的更新 MO。一种适应性很强的算法,不像蒙特卡洛那样,在执行更新之前要等待局次完成。减少 TD 的计算和内存过载也适用于这种情况,特别是如果考虑到,即使在相对较小的城市中,交通枢纽如何交织和相互关联。在实现交通流量预测系统时,计算和内存成本肯定是一个重要因素,而 TD 将在解决这个问题方面大有帮助。


场景 D

与我们迄今为止讲述的强化学习中的其它算法相比,制造业中的预测性维护,提出了我们对 TD 作为首选算法的最终用例。研究这样一个案例,制造工厂旨在预测机器何时需要维护,从而避免停机。就像智能建筑系统一样,该系统需要平衡短期奖励(通过延迟检查来节省维护成本)、与长期收益(防止故障)。TD 学习对此很适合,因为它能随时间推移,基于部分反馈更新机器的整体健康值,而不像 SARSA 或 Q-学习那样需跟踪特定动作(维修或更换)。

TD 也是一种更好的算法,因为机器老化是逐渐发生的,并且 TD 能基于增量传感器数据轻松地持续更新健康值,而不必长时间等待,这可能会遇到额外的风险。此外,在拥有多台机器的工厂中,TD 学习将能够很好地扩展,因为机器数量会减少或增加,而它只专注于跟踪每台机器的状态/健康状况,且不需要存储和更新工厂中每台机器的特定“状态-动作”对。


这些是金融交易和市场之外的几种情况,故现在我们来研究如何在自定义信号类里具体应用。


运用 TD 算法构筑自定义信号类

我们为实现 TD 而构建的自定义信号类依赖两个额外的类。由于它是一种强化学习算法,因此其中一个类是 CQL 类。我们已在之前的所有文章中用到、或引用了该类;不过,下面再次分享其接口:

//+------------------------------------------------------------------+
//| Q_SA-Learning Class Interface.                                      |
//+------------------------------------------------------------------+
class Cql
{
protected:
   matrix            markov;
   void              LetMarkov(int OldRow, int OldCol, vector &E);

   vector            acts;
   matrix            environment;
   matrix            Q_SA[];
   matrix            Q_V;

public:
   void              Action(vector &E);
   void              Environment(vector &E_Row, vector &E_Col, vector &E);

   void              SetOffPolicy(double Reward, vector &E);
   void              SetOnPolicy(double Reward, vector &E);

   double            GetReward(double MaxProfit, double MaxLoss, double Float);
   vector            SetTarget(vector &Rewards, vector &TargetOutput);

   void              SetMarkov(int Index, int &Row, int &Col);
   int               GetMarkov(int Row, int Col);


   Sql               THIS;

   int               act[2];

   int               e_row[2];
   int               e_col[2];

   int               transition_act;
   double            transition_value;
   
   matrix            policy_history;

   vector            Q_Loss()
   {  vector _loss;
      _loss.Init(THIS.actions);
      _loss.Fill(0.0);
      for(int i = 0; i < THIS.actions; i++)
      {  _loss[i] = Q_SA[e_row[0]][e_col[0]][i];
      }
      return(_loss);
   }


   void              Cql(Sql &RL)
   {  //
      if(RL.actions > 0 && RL.environments > 0)
      {  policy_history.Init(2,2+1);
         policy_history.Fill(0.0);
         acts.Init(RL.actions);
         ArrayResize(Q_SA, RL.actions);
         for(int i = 0; i < RL.actions; i++)
         {  acts[i] = i + 1;
            Q_SA[i].Init(RL.environments, RL.environments);
         }
         Q_V.Init(RL.environments, RL.environments);
         environment.Init(RL.environments, RL.environments);
         for(int i = 0; i < RL.environments; i++)
         {  for(int ii = 0; ii < RL.environments; ii++)
            {  environment[i][ii] = ii + (i * RL.environments) + 1;
            }
         }
         markov.Init(RL.environments * RL.environments, RL.environments * RL.environments);
         markov.Fill(0.0);
         THIS = RL;
         ArrayFill(e_row, 0, 2, 0);
         ArrayFill(e_col, 0, 2, 0);
         ArrayFill(act, 0, 2, 1);
         transition_act = 1;
      }
   };
   void              ~Cql(void) {};
};

这是主要的强化学习类,其依政策和无政策更新的一些类定义已在上面分享。此外,该类通常足够为自定义信号类实现强化学习,不过对于 TD,因为我们不使用或仅依赖环境值来制定交易决策,而是选择像过去一样继续预测合适的动作,我们就需要一个模型来进行这些预测。

这就是为什么要用附加类 CNeuralNetwork 类将该模型定义为神经网络、或 MLP。类似地,它的类接口分享如下:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
class CNeuralNetwork
{
protected:
   
   matrix               TransposeToCol(vector &V);
   matrix               TransposeToRow(vector &V);
   
public:
   CLayer               *layers[];
   double               m_learning_rate;
   ENUM_LOSS_FUNCTION   m_loss;
   void                 AddDenseLayer(ulong Neurons, ENUM_ACTIVATION_FUNCTION AF = AF_RELU, ulong LastNeurons = 0)
   {  ArrayResize(layers, layers.Size() + 1);
      layers[layers.Size() - 1] = new CLayer(Neurons, AF);
      if(LastNeurons  != 0)
      {  layers[layers.Size() - 1].AddWeights(LastNeurons);
      }
      else if(layers.Size() - 1 > 0)
      {  layers[layers.Size() - 1].AddWeights(layers[layers.Size() - 2].activations.Size());
      }
   };
   void                 Init(double LearningRate, ENUM_LOSS_FUNCTION LF)
   {  m_loss = LF;
      m_learning_rate = LearningRate;
   };
   
   vector               Forward(vector& Data);
   void                 Backward(vector& LabelAnswer);
   
   void                 CNeuralNetwork(){};
   void                 ~CNeuralNetwork()
   {  if(layers.Size() > 0)
      {  for(int i = 0; i < int(layers.Size()); i++)
         {  delete layers[i];
         }
      }
   };
};

我们针对执行相似函数的最后一个 CMLP 类进行了一些明显的修改。总体着重于效率;不过,这仍是一个测试版,即使就我们的目的而言,它已能给我们一些成果。除了大部分在反向传播(Backward)函数中的效率变化之外,在工作进度中,我们还引入了一个层类,并改变了网络的构造方式。自定义信号类的初始化现在如下所示: 

//+------------------------------------------------------------------+
//| Constructor                                                      |
//+------------------------------------------------------------------+
void CSignalTD::CSignalTD(void) :    m_scale(5),
   m_use_markov(true),
   m_policy(true)

{
//--- initialization of protected data
   m_used_series = USE_SERIES_OPEN + USE_SERIES_HIGH + USE_SERIES_LOW + USE_SERIES_CLOSE + USE_SERIES_SPREAD + USE_SERIES_TIME;
   //
   RL.actions  = 3;//buy, sell, do nothing
   RL.environments = 3;//bullish, bearish, flat
   RL.use_markov = m_use_markov;
   RL.epsilon = m_epsilon;
   QL_BUY = new Cql(RL);
   QL_SELL = new Cql(RL);
   //
   POLICY_NETWORK_BUY.AddDenseLayer(9, AF_SIGMOID, 3);
   POLICY_NETWORK_BUY.AddDenseLayer(3, AF_SOFTMAX);
   POLICY_NETWORK_BUY.Init(0.0004,LOSS_BCE);
   //
   POLICY_NETWORK_SELL.AddDenseLayer(9, AF_SIGMOID, 3);
   POLICY_NETWORK_SELL.AddDenseLayer(3, AF_SOFTMAX);
   POLICY_NETWORK_SELL.Init(0.0004,LOSS_BCE);
}

除了有 2 个 CQL 类分别在买方和卖方处理强化学习外,我们现在还有 2 个政策网络,可再次分别以买方和卖方角度预测动作。GetOutput 函数的运行方式仍与过去的文章大致相同,主要变化是神经网络类的实例当作政策网络输入之一。其新清单如下:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
int CSignalTD::GetOutput(Cql *QL, CNeuralNetwork &PN)
{  int _td_act = 1;
   vector _in, _in_row, _in_row_old, _in_col, _in_col_old;
   if
   (
      _in_row.Init(m_scale) &&
      _in_row.CopyRates(m_symbol.Name(), m_period, 8, 0, m_scale+1) &&
      _in_row.Size() == m_scale+1
      &&
      _in_row_old.Init(m_scale) &&
      _in_row_old.CopyRates(m_symbol.Name(), m_period, 8, 1, m_scale+1) &&
      _in_row_old.Size() == m_scale+1
      &&
      _in_col.Init(m_scale) &&
      _in_col.CopyRates(m_symbol.Name(), m_period, 8, 0, m_scale+1) &&
      _in_col.Size() == m_scale+1
      &&
      _in_col_old.Init(m_scale) &&
      _in_col_old.CopyRates(m_symbol.Name(), m_period, 8, m_scale, m_scale+1) &&
      _in_col_old.Size() == m_scale+1
   )
   {  _in_row -= _in_row_old;
      _in_col -= _in_col_old;
      _in_row.Resize(m_scale);
      _in_col.Resize(m_scale);
      vector _in_e;
      _in_e.Init(m_scale);
      QL.Environment(_in_row, _in_col, _in_e);
      int _row = 0, _col = 0;
      QL.SetMarkov(int(_in_e[m_scale - 1]), _row, _col);
      double _reward_float = _in_row[m_scale - 1];
      double _reward_max = _in_row.Max();
      double _reward_min = _in_row.Min();
      double _reward = QL.GetReward(_reward_max, _reward_min, _reward_float);
      if(m_policy)
      {  QL.SetOnPolicy(_reward, _in_e);
      }
      else if(!m_policy)
      {  QL.SetOffPolicy(_reward, _in_e);
      }
      PN.Forward(QL.policy_history.Row(1));
      vector _label;
      _label.Init(3);
      _label.Fill(0.0);
      if(_in_row[m_scale-1] > 0.0)
      {  _label[0] = 1.0;
      }
      else if(_in_row[m_scale-1] < 0.0)
      {  _label[2] = 1.0;
      }
      else if(_in_row[m_scale-1] == 0.0)
      {  _label[1] = 1.0;
      }
      PN.Backward(_label);
      vector _td_output = PN.Forward(QL.policy_history.Row(0));
      if(_td_output[0] >= _td_output[1] && _td_output[0] >= _td_output[2])
      {  _td_act = 0;
      }
      else if(_td_output[2] >= _td_output[0] && _td_output[2] >= _td_output[1])
      {  _td_act = 2;
      }
   }
   return(_td_act);
}

以政策网络作为其输入之一,现在它就能执行在线或增量学习,因为我们在预测之前不会依据一批数据进行训练,而只依据最新单根柱线数据进行训练。应当只有一次反向传播运行、和一次前向投喂运行,但由于我们需要在进行向后、或反向传播运行之前将当前柱线价格信息加载到神经网络之中,故我们用之前的柱线信息进行一次前向运行,从而加载该信息。我们的 Forward 函数也被修改,返回来自前馈通验的输出分类向量。它的大小为三,如果我们分别遵循索引顺序 0、1、2,则相应索引的每个值都提供了潜在卖出、持有、或买入的“概率”或阈值。

故此,回顾上面提到的内容,我们坚持使用相同的简单强化学习环境,及 3 个状态和 3 个动作的设置。政策更新选择是可优化的,如上一篇讲述蒙特卡洛的强化学习文章。尽管,当于此处执行更新时,它们仅会更新新引入矩阵 Q_V 的 Q-值。这与我们过去在环境映射中为每个动作更新 Q-值所做的工作形成鲜明对比。更新每个动作的数值,对于选择下一个动作是建设性的,因为一旦使用马尔可夫决策过程来判定下一个环境状态的坐标,那么就只需读取具有最高 Q-值的动作,并选它作为我们的下一个动作。

而据 TD,我们的环境矩阵Q_V 不具备含 Q-值的动作,而仅是将数值分配给特定环境。这意味着为了选择、或判定下一步动作规程,会用到一个政策网络(在我们的例子中是 MLP)进行这些预测,其输入将是当前环境的数值(这事实上是该状态下所有适用动作 Q-值的总和)、以及环境状态坐标。如前所述,输出也是 3 种可能动作的“概率分布”,即在给定的当前环境、及其数值下,哪个动作是最好的。

因此,这个自定义信号类配合 MQL5 向导一起汇编成一个智能系统,经过短暂的优化后,采用的是品种 GBPUSD 的 2022 年 H1 时间帧,取有利设置之一,为我们提供了以下报告:

r1

c1

这些结果大概说出了我们自定义信号类的潜力,但在不诋毁它们的情况下,始终建议选择任何智能系统设置之后,但在部署到实时环境中之前,交叉验证它。在更长的时间窗口内(与我们此处所做的相比)进行交叉验证和额外的测试努力,留给读者。按照他认可的方式查验。


运用 TD 学习优化和调整信号类

上面的测试运行,我们单纯优化了 epsilon 参数、是否在更新过程中使用马尔可夫权重、以及依政策更新、或无政策更新。当然还有其它典型的非 TD 参数,例如做多和做空条件的开盘价和收盘价阈值、止盈水平,以及以点为单位的入场价格阈值。

然而,在 TD 和强化学习内,我们忽略了相当多的参数,并按它们的预设值一起使用。分别为 alpha 和 gamma 赋值 0.1 和 0.5。这两个参数是政策更新的关键,可以说对信号类的整体性能非常敏感。其它关键参数我们在实现信号类时都略过,仅在政策网络的设置里为其赋予有效的常量值。我们坚持使用 3-9-3 网络,其中每一层的激活函数、以及学习率都是预设的。同时调整时,其中每一个,也可能全部,都会对自定义信号类的结果和性能产生重大影响。


结束语

我们研究了强化学习的时态差异算法,并尝试凸显其用例,令其与我们曾讲述过的其它算法区分开来。我们尚未研究的一个有趣层面,就是强化学习一般会随探索时间的推移而衰减。这背后的论调或争议,在于强化学习模型随时间推移,对于探索新领域、或不断学习的需求大大减少;因此,加以利用将更加重要。当读者定制附加代码,以供进一步使用时,可以考察这些东西。

另一层面可能是令 epsilon 可变,而不单是减少它,这就是上面的衰减所暗示的。其论调是强化学习意味着极其动态、且适应性强,这与依赖静态数据的监督学习模型不同。因此,TD 的强化学习框架可积极应对不断变化的环境。我们在过去的一篇文章中研究过动态学习率方法,争议同样是针对 epsilon,故此衰减假设本身就不能作为令强化学习保持根源的唯一方式。

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

附加的文件 |
Cmlpw.mqh (5.56 KB)
Cql.mqh (11.63 KB)
SignalWZ_47.mqh (9.12 KB)
wz_47.mq5 (6.82 KB)
基于三维反转形态的算法交易 基于三维反转形态的算法交易
在三维K线上探索自动化交易的新世界。基于多维价格K线的交易机器人是什么样的?三维K线中的“黄色”簇群能否预测趋势反转?多维交易是什么样的?
从基础到中级:数组(三) 从基础到中级:数组(三)
在本文中,我们将介绍如何在 MQL5 中使用数组,包括如何使用数组在函数和过程之间传递信息。目的是为您准备在本系列后续材料中演示和解释的内容。因此,我强烈建议您仔细研究本文将展示的内容。
外汇投资组合优化:风险价值理论与马科维茨理论的融合 外汇投资组合优化:风险价值理论与马科维茨理论的融合
外汇市场中的投资组合交易是如何运作的?我们如何将用于优化投资组合权重的马科维茨投资组合理论与用于优化投资组合风险的VaR模型结合起来?我们基于投资组合理论创建一个EA,一方面,我们将获得低风险;另一方面,获得可接受的长期盈利能力。
经典策略重塑(第12部分):欧元兑美元(EURUSD)突破交易策略 经典策略重塑(第12部分):欧元兑美元(EURUSD)突破交易策略
今天,我们将挑战在MQL5中构建一套盈利的突破交易系统。我们选择欧元兑美元(EURUSD)货币对,尝试在H1(1小时)时间框架下捕捉价格的突破行情。初期挑战:系统难以区分假突破与真实趋势的开端,导致亏损较多。我们给系统叠加了多层过滤器,旨在把亏损压到最低,同时把盈利抬到最高。最终,我们成功地让系统实现盈利,并大幅降低假突破带来的风险。