English Русский Español Deutsch 日本語 Português
preview
您应当知道的 MQL5 向导技术(第 43 部分):依据 SARSA 进行强化学习

您应当知道的 MQL5 向导技术(第 43 部分):依据 SARSA 进行强化学习

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

概述

强化学习(RL)允许交易系统从其环境或市场数据中学习,从而随时间推移提升其交易能力。强化学习能够适应不断变化的市场条件,令其适用于某些动态的金融市场和证券。金融市场是不可预测的,在于它们往往具有高度的不确定性。强化学习擅长在不确定性下制定决策,基于收到的反馈(奖励)不断调整其动作,因而在处理动荡的市场条件时对交易者非常有帮助。

能与其并肩比较的是附加到图表的智能系统,且还会定期依据最近的价格历史记录进行自我优化,从而优调其参数。强化学习的目标是做同样的事情,但不那么大张旗鼓。作为这些系列的一个段落,迄今已考察了强化学习,我们按严格定义意义在训练机器学习中将其当作第三种方式(除了监督学习和无监督学习)。我们尚未考察其能否作为独立模型用于预测。

在本文中这有所变化。我们不仅引入了不同的强化学习算法 SARSA,而且我们寻求在向导汇编的智能系统内实现另一个自定义信号类,作为一个独立的信号模型。当用作信号模型时,强化学习可自动化决策过程,降低了持续人工干预的需求,这反过来(至少在理论上)能允许高频交易、以及实时响应市场波动。此外,通过从其奖励机制获得持续反馈,强化学习模型偏向学习更好地管理风险。这是通过惩罚低回报的高风险行为来实现的,强化学习的净效应最大限度地减少了波动或亏损交易的风险。

不过,原则上,强化学习也是有关平衡探索和开发,即尝试新策略、与运用之前可盈利策略之间的平衡。这要归功于更新 Q-映射 的 “ε-贪婪” 方法,其是跨可能环境状态的动作矩阵,代理人可从中选择动作。

强化学习还有一些其它优点,这些或许看似传闻,但考虑到强化学习在 AI 中的蓬勃发展,它们仍然值得一提。

它可基于历史数据和实时反馈来学习买入或卖出的最佳时机、以及价格点位来帮助优化交易执行,从而提高盈利能力。当作为基本信号模型时,就如本文情况一样,它与另一个信号配对时,就可以达成这一点,故就归结为判定在使用挂单的情况下其作用如何。如果 Q-映射功能,譬如说,限价单、止损单、和市价单的 3 个动作,则可优调已建立、及所熟悉策略的入场点。

不同于传统的线性模型,强化学习有点非常规,大概适合学习和执行复杂的非线性交易策略,而按理这样更能反映现世的市场行为。强化学习是可伸缩的,以便同时处理多个资产类别或策略,具体取决于如何定义其 Q-映射,及随之而来的动作,令其成为跨不同金融工具的投资组合管理、或算法交易的多功能解决方案。最后,它特别适用于实时决策系统,其中不仅包括全自动的智能系统,而且可根据个人的策略和当前设置进行伸缩,甚至可包括部分手工交易和智能系统。


SARSA 概述

SARSA 是 “State-Action-Reward-State-Action” 的首字母缩写,其名称来源于 “Q-映射” 数值的更新方式。这种更新 Q-值的方法与我们早前文章中考察的 Q-学习方式明显不同,因其贴合政策,与我们验证过的非政策方式相悖。事实上,我们在本文中实现的 SARSA,与我们在那篇文章中引入的 Q-学习时所用的雷同,只是 Q-映射 值的更新方式不同。

SARSA 的贴合政策算法意味着它根据已经采取的动作来学习 Q-值,遵循其当前政策,而非基于当前环境状态采取后续动作。它遵循相同政策选择的动作来更新 Q-值。另一方面,Q-学习 是一种非政策算法,这意味着它据下一个环境状态中的最佳动作来更新 Q-值,且与当前政策采取了什么动作无关。它在学习最优政策时独立于代理人的当前动作。我们按政策实现 SARSA 的 Q-值更新,如下所示:

//+------------------------------------------------------------------+
// Update Q-value using On-policy
//+------------------------------------------------------------------+
void Cql::SetOnPolicy(double Reward, vector &E)
{  Action(E);
//where 'act' index 1 represents the current Q-action from Q-Map
   double _action = Q[act[1]][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]);
      _action *= markov[_old_index][_new_index];
   }
   for (int i = 0; i < THIS.actions; i++)
   {  if(i == act[0])
      {  continue;
      }
      Q[i][e_row[1]][e_col[1]] += THIS.alpha * (Reward + (THIS.gamma * _action) - Q[act[0]][e_row[1]][e_col[1]]);
   }
}

SARSA 为下一个状态将采取的实际动作更新规则(状态 → 动作 → 奖励 → 状态 → 动作)。它遵循 “ε-贪婪” 政策进行探索和学习。这与考察过的 Q-学习非常相似,不过当时并不清楚,但现在被带到最前沿的是 “ε-贪婪” 如何选择,因为更新 Q-映射的过程是随机的,故导致非常不一致的结果。如是规则,ε 值越小,随机效应就越小。

在平衡探索与开发方面,SARSA 采取了更平衡的方法,因为它在学习和采取行动时都遵循相同的政策,纸面上这意味着它在一些金融市场等不确定的环境中更安全。另一方面,Q-学习往往更具侵略性,在于它总是从下一个状态寻求最大奖励,这可能令其在波动的环境中更容易做出高风险决策。

因此,SARSA 更适合在探索和开发之间必须维持平衡、且环境有风险、或不平稳的场景,举例,浮现在脑海中的最好场景就是交易任何日元对。相较之,当环境相对稳定时,Q-学习更适合,并且寻找最优策略比管理持续风险更重要。再次,最好的例子可能是 EURCHF 对。

本文中,我们还研究了另一种 RL 算法 “深度-Q-网络”,它在许多方面也与 SARSA 不同。首先,主要区别在于 SARSA 和 Q-学习 一样,如何使用 Q-表格 来存储状态动作值。这将 SARSA 限制在具有较小状态空间的环境之中,因为较大的环境令维护 Q-表格不切实际。然而,正如我们在那篇文章中所见,DQN 利用神经网络来近似每个状态-动作对的 Q-值,令其在具有大型、或连续状态空间的环境中更具可扩展性和有效性。

当涉及到经验回放时,SARSA 不会使用它,因为它会按顺序从每次经验中学习。这可能会导致学习效率低下,因为代理人只从最近的经验中学习。但另一方面,DQN 能实现经验回放,即在训练期间随机缓冲和采样过去的经验。这可大大减少连续经验之间的相关性,从而可能导致更稳定的学习。

SARSA 和 DQN 之间的另一个区别源于目标网络的使用。SARSA 没有这样的概念,因为它基于当前政策在每个步骤中直接更新 Q-值。另一方面,正如我们在 DQN 文章中看到的那样,在主 Q-网络旁侧强制性使用目标网络来稳定学习。搭配 DQN,目标网络会定期更新,从而提供稳定的 Q-值更新,防止学习中出现大幅波动。

可扩展性和复杂性是 DQN 和 SARSA 的另一个深奥变化方式,因为由于 Q-表格大小和策略学习的限制,故 SARSA 最适合于更小、更简单的问题。而 DQN 专为更复杂的高维问题而设计,像是基于图像的任务、或有大量状态的环境中遇到的问题。在这篇关于 SARSA 的文章中,就像我们在 Q-学习文章中那样,为了简洁起见,我们将环境状态限制为 9。

回顾一下,这些是基于 3 个简化的市场状态,即看涨、看跌、和拉扯市场。然后将这些状态中的每一种应用于较短时间帧和较长期时间帧,以便创建一个 3 x 3 矩阵,这意味着 9 种可能的状态。在需要考虑额外参数,例如财经新闻数据、或相关证券价格动作的情况下,这些数据可采用连续值来限制 SARSA 的适用性。

最后,学习速度,体现 SARSA 和 DQN 之间的另一个主要区别,因为 SARSA 在复杂环境中可能会变慢,这是由于顺序更新过程、及神经网络缺乏普适性。然而,由于使用神经网络能在相似状态之间普适化,故 DQN 在大型环境中往往更快,尤其是它与通过经验回放进行批量学习相结合时。


为 SARSA 设置 MQL5 环境

为了实现使用 RL 而非 MLP 或其它机器学习算法作为基本模型的自定义信号类,我们本质上需要简化我们在 RL 概述文章中看到的专注于 Q-学习 的信号类。该文章依赖于 MLP 进行预测,而 RL 受限于在训练期间处理损失函数。当时的 RL 和现在的 SARSA 一样,使用 “ε-贪婪” 方式,指导何时为代理人随机选择最佳动作。

当我们简单地使用 RL 来指导 MLP 的训练过程时,这些经常性随机选择是“可容忍的”,因为训练提供的损失值对 MLP 的整体性能并不那么敏感。然而,既然 RL 是模型而不是另一个 MLP,那么随机动作的选择对整体性能的影响不成比例。

在机器学习中,往往会有一个训练样本和一个测试样本。虽然,作为一种习惯,我并未提供这些,但会提到它们,并邀请读者获取这两个独立的数据集。典型情况下,训练数据集会比测试数据集略大,当我们遵循这种将训练与测试分开的协议时,那么 “ε” 的使用实际上对整个模型具有建设性。Q-映射只是一个矩阵数组,不能由所附代码中的任何函数导出,但这相对直截了当,任何有兴趣进一步独立训练和测试的人都需要在训练后,将该矩阵数组导出为二进制、或 CSV 文件,并在测试时再次读取它。


自定义信号类编码

如上所述,我们的自定义信号类是我们在 Q-学习 算法文章中的简化版,其中一项主要调整是 GetOutput 函数,我们对其进行了修改,如下所示:

//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CSignalSARSA::GetOutput(int &Output, Cql *QL)
{  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) &&
      _in_row.Size() == m_scale
      &&
      _in_row_old.Init(m_scale) &&
      _in_row_old.CopyRates(m_symbol.Name(), m_period, 8, 1, m_scale) &&
      _in_row_old.Size() == m_scale
      &&
      _in_col.Init(m_scale) &&
      _in_col.CopyRates(m_symbol.Name(), m_period, 8, 0, m_scale) &&
      _in_col.Size() == m_scale
      &&
      _in_col_old.Init(m_scale) &&
      _in_col_old.CopyRates(m_symbol.Name(), m_period, 8, m_scale, m_scale) &&
      _in_col_old.Size() == m_scale
   )
   {  _in_row -= _in_row_old;
      _in_col -= _in_col_old;
      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);
      QL.SetOnPolicy(_reward, _in_e);
      Output = QL.transition_act;
   }
}

将该段代码汇编到智能系统之中,请遵循此处此处为新读者提供的指南。汇编的智能系统将用优化的 “epsilon” 值进行测试,因为我们仅为展示智能系统的可用性。在实践中,理想情况下,该数值应以自己对各种环境状态的影响力、及其各自动作的相对重要性知识为指导。


测试和调试基于 SARSA 的信号

为了更好地测试和利用 SARSA 自定义信号,必须该信号类进行一些修改,其中最重要的可能是添加一个函数来导出 Q-映射矩阵数组。这就能开启传统意义上的独立测试和训练。不过,这些交叉验证能力内置在策略测试器当中,前向游走测试可用。

考虑到交替市场状态,诸如绝对价格水平、RSI、或布林带指标值,并将其与这些数据点的交替时间帧交叉,我们也可以对定义环境状态的方式进行额外调整。调试将从检查这些状态变量的捕获和更新方式开始。

奖励函数也应准确反映交易结果。至于本文,我们的代码在每根新柱线上使用有利偏移作为价格范围的百分比。这是因为我们同时在训练 Q-映射,并根据当时的 Q-值 权重制定交易决策。当然这并不理想,在于应该寻求适当的独立测试和训练数据集。但是,奖励量值可能比我们在此处所用的更长久,举例,如果他们研究盈利能力、和智能系统覆盖较长区间的表现。

为了不盲目地追逐智能系统的盈利能力,测试单个状态-动作对,并确保 EA 如预期那般针对市场条件做出正确反应是一个好主意。例如,本文我们的简化版中,在标记看跌短期和长期条件的第一个网格坐标 0 & 0 处检查 Q-值权重,对于代表卖出的动作 0 应具有最高权重,而不应是与看跌行情不一致的“曲线拟合值”。

验证通过 “ε-贪婪” 政策实现的正确探索-开发平衡也至关重要,不过这可以从优化理想的 epsilon 值来获得。然而,这种优化需要在单独的训练数据集上执行,其中 Q-映会备份性能最佳的通验。训练之后,在隔离的数据集上,测试 (前向游走) 可以确认或反驳索用的 epsilon 值。

应在品质合理的数据集上进行回溯训练。策略测试器报告会在通验后指示所用的数据品质,故这始终是训练可靠性的良好代理。不过,如果测试结果优于之前的基准测试,则该训练将涉及在每次通验结束时导出 Q-映射,且导出的 Q-映射 将在后续的反向训练轮次中重复使用,如此这些额外训练通验就能在训练神经网络时充当局次。

在训练过程结束时,最终 Q-映射提供的最令人满意的智能系统性能将用于单次前向测试,以便查看其是否能在尚未“看清”的测试数据集上复现该特定 Q-映射曾经的训练性能。前向游走是策略测试器的原生功能,此处文章可作为新读者的指南。

除了据历史数据集进行交叉验证外,在完全部署之前,还要考虑在实盘账户里进行相同的验证。性能日志,其中不同的 Q-映射在各自的测试器性能旁边列出。进一步可以通过在实时前瞻测试中实现详细日志记录,以便捕捉市场状态、动作、奖励、和 Q-值更新,至少在纸面上有助于跟踪决策缺陷、并调整参数,如学习率、或 ε-衰减(如果需要)。


策略测试器报告

依据 EURJPY,2022 年日线时间帧测试运行,严格来说是为了证明智能系统的可用性,为我们给出以下结果:

r1

c1


在实况交易中研究 SARSA

由于 SARSA 是一种贴合政策算法,因此它在学习过程中直接整合了政策的动作,令其更能适应嘈杂的数据。我们所用的 Q-映射数值方法会依据它们与当前动作的差距成比例更新映射中的所有 Q-值,如上面贴合政策代码所示。更新遵循在动作函数中执行的 “ε-贪婪” 更新,来平衡探索(发现新策略)和开发(使用已知策略),从而帮助模型避免过度拟合市场数据中的短期噪声。代理人选择下一个所用动作是通过马尔可夫(Markov)决策过程进行的,与马尔可夫权重是否将应用于 Q-值更新过程无关,就如我们之前 Q-学习文章的行事。这在 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[0][e_row[0]][e_col[0]];
      for (int i = 1; i < THIS.actions; i++)
      {  if (Q[i][e_row[0]][e_col[0]] > _best_value)
         {  _best_value = Q[i][e_row[0]][e_col[0]];
            _best_act = i;
         }
      }
   }
//update last action
   act[1] = act[0];
   act[0] = _best_act;
//
   int _e_row_new = 0, _e_col_new = 0;
   SetMarkov(int(E[E.Size() - 1]), _e_row_new, _e_col_new);
   e_row[1] = e_row[0];
   e_col[1] = e_col[0];
   e_row[0] = _e_row_new;
   e_col[0] = _e_col_new;
   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);
   transition_act = 0;
   for (int i = 0; i < THIS.actions; i++)
   {  if(Q[i][_next_row][_next_col] > Q[transition_act][_next_row][_next_col])
      {  transition_act = i;
      }
   }
}

经由奖励机制进行时态平滑,通过关注涵盖多次时间迭代的累积奖励,帮助消除短期噪音。这为算法提供了超出数据中噪声之外学习形态的能力。此外,连续-政策-调整 可确保政策基于当前和未来状态。这有助于减轻嘈杂数据的影响,允许算法在获得更多可用数据时进行调整,尤其是在市场条件迅速变化之时。

SARSA 在处理波动性市场方面非常健壮,因为它在选择转换动作方面的固有结构,如上面分享的代码的 Action 函数所示。给定当前环境状态的一对坐标,这两个值需要转换为单一索引,可由 QL 类的马尔可夫矩阵识别。我们调用 'GetMarkov' 函数来实现这一点,其代码如下所列:

//+------------------------------------------------------------------+
// Getting markov index from environment row & col
//+------------------------------------------------------------------+
int Cql::GetMarkov(int Row, int Col)
{  return(Row + (THIS.environments * Col));
}

一旦有了这个索引,我们就能继续从马尔可夫矩阵行(按该索引表示)中读取概率值最高的那一列。这个马尔可夫矩阵特别适合这一点,因为它不像大多数常见指标那样以任何方式缓冲或存储。它是无记忆的,这令它非常适应不确定的环境,如当高波动性之时。故此,从当前环境状态的马尔可夫矩阵行中,我们读出概率最高的列,其索引将为我们提供下一个环境状态的整数。这个整数需要再次分解为两个值,类似于我们开始时的数值,我们称之为行索引和列索引。

一旦我们得到这些代表下一个状态坐标的行和列数值,我们就可以继续从 Q-映射 中读取含有最高 Q-值的动作。该动作的索引将代表我们所说的 “转移动作”。回顾一下,我们面临三种可能的动作,即 0 - 卖出、1 - 什么都不做、和 2 - 买入。此外,环境受限于参考 Q-映射的 3 个“时间帧”上的 3 个状态。

我指出“时间帧”,因为我们实际上仅用一个性时间帧。当然,读者可以修改代码,并进行相应的更改。输入参数 'm_scale' 定义了跟踪 '时间帧' 的变化程度,当获取这两层变化时,我们在映射和定义环境状态时所用,

SARSA 在当前政策中同时参考动作和奖励,有助于在市场高度波动期间稳定学习过程。这可防止市场突然变化导致的决策极端波动,与 Q-学习 等非政策算法相比,它更适合波动的市场。从上面分享的贴合政策函数代码中所见,我们更新索引为 1 而不是索引 0 的动作值,就像 Q-学习 算法一样。

因为 SARSA 直接评估当前政策所采取的动作,没有太多瞎忙乎,因此在高度波动的环境中,变得更加谨慎,从而避免过于乐观的评估动作价值,这可能会导致在市场意外变化期间做出错误的决策。还有,在动荡的市场中,SARSA 的 “ε-贪婪” 探索令模型能够探索更安全的策略,而不会采取高风险的动作。这降低了在价格剧烈波动期间出现巨大亏损的可能性,同时仍为模型提供发现新盈利策略的机会。自 epsilon 探索更安全策略,允许随机选择动作,而不一定是当前最佳奖励的动作,在动荡的环境中,可能会很快变成令人沮丧的奖励。 

SARSA 朝最优政策的长期趋同,取决于与环境的持续互动。在金融市场中,长期趋势和结构变化是常态,SARSA 的迭代政策评估可确保 Q-值随时间推移趋向收敛,从而反映这些长期形态。 


针对更复杂的策略定制 SARSA

SARSA 中的状态空间聚合可以是一种打破复杂状态空间的方法,尤其是在金融市场中,其中状态空间(价格变动、指标、市场状况、经济日历新闻)可能非常大且连续。状态空间聚合通过将相似状态分组为 “聚合” 或 “抽象” 状态来降低这种复杂性。最粗略的例子是本文的环境状态,它只是 3 个跨即时变化、和较长跨度的变化。然而,在研究一个更加多元化的环境时,也可以更机智地使用它,例如在每个轴上都有 10 年期收益率价值信息、基准利率、PPI、CPI、和失业率的 Q-映射。

由于 Q-映射 是双轴的,因此该信息应可适用于交易外汇对中的每种货币。故此,与其纠结于这些指标中每个指标的单一读数,不如像我们在本文中所做的那样,简单地研究这些量值中的每一个是增加、持平、亦或减少。这些结果之后继续盘判定需要在 Q-映射 矩阵中的每个点分配什么样的索引,就像我们在本文的 Q-映射 中分配索引值一样。


结束语

在本文中,我们研究了另一种强化学习算法 SARSA,值得一提的是,我们已完成了第一个所研究 Q-学习 的 RL 算法、以及随后的 RL 算法的实现,该算法着眼于 “深度-Q-网络”,在选择下一个动作时无需用到马尔可夫决策过程。取而代之,所提供马尔可夫链只是作为权重更新过程的机制。本文已对此进行了更正,并表示歉意,并附上了完整的源代码。

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

附加的文件 |
Cql.mqh (10.46 KB)
SignalWZ_43.mqh (7.47 KB)
wz_43.mq5 (6.6 KB)
MQL5 交易管理面板开发指南(第六部分):交易管理面板(续篇) MQL5 交易管理面板开发指南(第六部分):交易管理面板(续篇)
在本文中,我们对多功能管理面板的“交易面板”进行升级。我们引入一个强大的辅助函数,大幅简化代码,提高可读性、可维护性与运行效率。同时演示如何无缝集成更多按钮,并优化界面,以支持更广泛的交易任务。无论是持仓管理、订单调整,还是简化交互,本文将助您打造稳健且易用的交易管理面板。
将互信息作为渐进特征选择的准则 将互信息作为渐进特征选择的准则
在本文中,我们展示了基于最优预测变量集与目标变量之间互信息渐进特征选择的MQL5实现。
借助成交量精准洞悉交易动态:超越传统OHLC图表 借助成交量精准洞悉交易动态:超越传统OHLC图表
一种将成交量分析与机器学习技术(特别是LSTM神经网络)相结合的算法交易系统。与主要关注价格波动的传统交易方法不同,该系统强调成交量模式及其衍生指标,以预测市场走势。该方法包含三个主要组成部分:成交量衍生指标分析(一阶和二阶导数)、基于LSTM的成交量模式预测,以及传统技术指标。
开发回放系统(第 68 部分):取得正确的时间(一) 开发回放系统(第 68 部分):取得正确的时间(一)
今天,我们将继续努力,让鼠标指针告诉我们在流动性较低期间,一根柱形上还剩下多少时间。尽管乍一看似乎很简单,但实际上这项任务要困难得多。这涉及一些我们必须克服的障碍。因此,为了理解以下部分,您必须很好地理解子系列第一部分的材料。