English Русский Español Deutsch 日本語 Português
preview
您应当知道的 MQL5 向导技术(第 19 部分):贝叶斯(Bayesian)推理

您应当知道的 MQL5 向导技术(第 19 部分):贝叶斯(Bayesian)推理

MetaTrader 5交易系统 | 10 二月 2025, 09:55
244 0
Stephen Njuki
Stephen Njuki

概述

我们通过回顾贝叶斯推理来继续开发 MQL5 向导,它是统计学中的一种方法,可据每条馈送的新信息来处理和更新概率。它显然具有广泛的应用潜能,不过,我们的目的是作为交易者,我们全神贯注于它在预测时间序列中扮演的角色。时间序列可供交易者分析的主要是所交易证券的价格,但正如我们将在本文中所见,这些序列能被“扩展”,以便研究替代方案,如证券交易历史。

理论上,贝叶斯推理应能强化任何交易系统的市场适应性,因为任何假设都难免要重新评估。当依据历史数据进行测试,并随后给出前向漫游、或于实盘账户操练时,应当会导致少量的曲线拟合。但这就是理论,在真正实现中可能会毁掉一个好主意,这就是为什么我们将在本文中尝试研究贝叶斯推理的诸多可能实现。

因此,我们的文章按一种简单形式搭构,涵盖了贝叶斯推理的定义;应用示例则涵盖了自定义信号类、资金管理类、和尾随破位类、等等的概括;策略测试报告,以及最后的结论。


定义

贝叶斯推理(BI)由公式 P(H|E) = [P(E|H) * P(H)] / P(E) 托举,其中:

  • H 代表假设,而
  • E 证据,如这般;
  • P(H) 是假设的先验概率,而
  • P(E) 是证据概率,又名边际似然。
  • P(H|E) 和 P(E|H) 分别是上述的条件概率,它们也分别称为后验概率、和似然。

上面的公式尽管简单、且直截了当,但也遇到一点先有鸡还是先有蛋的问题,即我们如何找到: P(E|H)。这是因为它暗示解决方案出自我们上面列出的公式:

P(E|H) = [P(H|E) * P(E)] / P(H)。

然而,这也能重写为 P(E|H) = [P(EH)] / P(H)。这将允许我们在这种状况下做一些手工变通操作,如下所示。


信号类

信号类典型是为智能系统选取建仓位置,无论是多头、亦或空头。它通过添加指标权重来如此行事,且合计数值范围始终在 0 到 100。在使用 BI 时,我们面临着广泛的时间序列选择,不过本文中提供的信号类概括,仅依据收盘价时间序列。

就事而论,为了使用这个时间序列,或任何其它类型,我们首先需要找到一种“系统性”的方式,针对时间序列值进行分类、或聚集。显然这步很重要,因为它不仅归一化我们的时间序列数据,还允许我们在处理其概率时正确识别它。

聚集是无监督的,我们采用一种简陋的方式,根据价格变化类型为每个数据点分配一个聚集。所有正值都会分配一个聚集,零值会有自己的一个聚集,负值也会有自己的。我们曾在系列文章里研究过其它聚集方法,欢迎读者验证这些,然而对于本文,显见聚集不是主要议题,我们已研究过的一些东西都非常初级。

即使配以这种基本的聚集方式,我们明显变得能更好地“识别”数据点,从而估测它们的概率。没有它,由于数据是浮点数,因此每个数据都是唯一的,这实质上相当于唯一的聚集类型,这显然会违背我们获取和计算概率值的目的。我们的简单方式实现,如下源代码:

//+------------------------------------------------------------------+
//| Function to assign cluster for each data point                   |
//+------------------------------------------------------------------+
void CSignalBAYES::SetCluster(matrix &Series)
{  for(int i = 0; i < int(Series.Rows()); i++)
   {  if(Series[i][0] < 0.0)
      {  Series[i][1] = 0.0;
      }
      else if(Series[i][0] == 0.0)
      {  Series[i][1] = 1.0;
      }
      else if(Series[i][0] > 0.0)
      {  Series[i][1] = 2.0;
      }
   }
}

一旦我们“识别出”数据点,那我们就可以继续计算上面公式方程中定义的后验概率。然而,如此行事,我们需要一个特定的聚集类型作为我们的假设。这个聚集对于多头仓位和空头仓位必然是唯一的,因此我们为每笔仓位都自定义输入参数,可用作每种情况下识别要用到的聚集类型的索引。它们分别标记为 “m_cluster_long” 和 “m_cluster_short”。

故此,为了得到后验概率,需要将该聚集索引与“识别出”、或已聚类时间序列一起作为输入。我们计算后验概率的函数,是获取仓位类型聚类发生给定当前聚集类型的概率。由于我们正在提供一连串最近的数据点,每个数据点的聚集索引都是矩阵格式,因此我们本质上将零索引数据点作为当前聚集。

为了解决上述潜在的先有鸡还是先有蛋的情况,我们在计算 P(E|H)

遵从第一性原理。如上解释,由于 H 由相应的仓位索引表示,因此证据 E 是当前聚集或输入序列中零索引处的聚集类型。故此,我们的后验概率是找出给定仓位聚集类型接下来出现给定值的似然,因为最新证据(索引为 0 的聚集)已经发生。

因此,为了找到 P(E|H) 逆反,我们重新访问输入序列,并列举仓位索引 H 何时出现,及后随的零索引 E(证据)。这也是一个概率,故我们首先列举空间,即找到 H 出现次数,然后在该空间内查找证据索引连续跟随的次数。

这明显意味着我们的输入序列具有足够的长度,这要服从于所参考聚集类型的数量。在我们这个非常简单的示例中,我们有 3 种聚集类型(考虑到零价格变化必然很少发生,实际上是 2 种),这样就能配合输入序列少于 50 个的情况工作。不过,一个可能的选项是更冒险的聚类方式,即采用 5/6 或更多的聚集类型,那么输入序列的默认大小需要足够大,以便捕获所有出现的聚集类型,从而我们的后验函数正常工作。后验函数的清单如下:

//+------------------------------------------------------------------+
//| Function to calculate the posterior probability for each cluster |
//+------------------------------------------------------------------+
double CSignalBAYES::GetPosterior(int Type, matrix &Series)
{  double _eh_sum = 0.0, _eh = 0.0, _e = 0.0, _h = 0.0;
   for(int i = 0; i < int(Series.Rows()); i++)
   {  if(Type == Series[i][1])
      {  _h += 1.0;
         if(i != 0)
         {  _eh_sum += 1.0;
            if(Series[i][1] == Series[i - 1][1])
            {  _eh += 1.0;
            }
         }
      }
      if(i != 0 && Series[0][1] == Series[i][1])
      {  _e += 1.0;
      }
   }
   _h /= double(Series.Rows() - 1);
   _e /= double(Series.Rows() - 1);
   if(_eh_sum > 0.0)
   {  _eh /= _eh_sum;
   }
   double _posterior = 0.0;
   if(_e > 0.0)
   {  _posterior += ((_eh * _h) / _e);
   }
   return(_posterior);
}


一旦我们得到后验概率,它就代表了在当前聚集类型(即索引为零处数据点的证据、或聚集类型)下,该仓位的最优聚集类型(无论是 'm_cluster_long' 亦或 'm_cluster_short“)发生的似然。该值范围应在 0.0 到 1.0。对于各自的假设,无论是多头还是空头仓位,理想情况下,返回值必须大于 0.5,不过可由读者探索特殊状况,其中略小的数值亦能产生有趣的结果。

诚然,十进制值必须归一化到由多头条件和空头条件函数输出的标准 0 – 100 范围。为达此目的,我们简单地将其乘以 100.0 即可。多头或空头条件的典型清单如下所列:

//+------------------------------------------------------------------+
//| "Voting" that price will grow.                                   |
//+------------------------------------------------------------------+
int CSignalBAYES::LongCondition(void)
{  int result = 0;
   vector _s_new, _s_old, _s;
   _s_new.CopyRates(m_symbol.Name(), m_period, 8, 0, m_series_size);
   _s_old.CopyRates(m_symbol.Name(), m_period, 8, 1, m_series_size);
   _s = _s_new - _s_old;
   matrix _series;
   _series.Init(_s.Size(), 2);
   for(int i = 0; i < int(_s.Size()); i++)
   {  _series[i][0] = _s[i];
   }
   SetCluster(_series);
   double _cond = GetPosterior(m_long_cluster, _series);
   _cond *= 100.0;
   //printf(__FUNCSIG__ + " cond: %.2f", _cond);
   //return(result);
   if(_cond > 50.0)
   {  result = int(2.0 * (_cond - 50.0));
   }
   return(result);
}

配合该信号类,它能经由 MQL5 向导轻松组装到任何智能系统当中,供 MQL5 向导的萌新读者使用的指南在这里这里


资金管理类

也可以利用 BI 实现自定义资金管理(MM)类。一旦再次开始,我们需要选择一个相应的时间序列作为分析的基础,但正如概述中提到的,我们选择的 MM 将依赖历史交易表现。故此,由于我们的向导组装的智能系统只能交易一个品种,在查询时,所有可供的交易历史,将适用于该智能系统选择的品种。

在利用交易历史时间序列作为分析的基础时,我们将借鉴一个内置的资金管理类,该类是“规模已优化”,其中交易量规模根据最近连续亏损的次数成比例地降低。在我们的例子中,如若我们首选的聚集索引(假设)的似然落在另一个我们称为 “m_condition” 的可优化参数之下,我们将减少规模手数。

故此,我们正在尝试建立东西,实质上是一个理想的聚集索引,在其处我们能按可用保证金的比例使用常规手数。这个聚集索引是净值曲线类型的标识符 (因为只交易一个品种),在其上,我们能按可用保证金的比例自由缩放手数。对“净值曲线类型”的引用有点宽泛,因为我们的聚集遵循我们在信号类中采用的简单格式,那么这里特别指出的是交易结果的类型,即,赢还是输(零利润结果也被分配了一个索引,但不太可能在分析中发挥重要作用)。

这意味着,举例,如果据可用保证金缩放手数的交易结果可盈利,那么我们将检查先前交易结果的顺序,并尝试根据证据(输入序列零索引处的交易结果)建立再次出现盈利交易结果的似然性。

这需要另一个可优化的参数,以概率阈值的形式,衡量重复目标有利条件的似然性,如此这般,若后验结果未达该阈值,则仓位规模会按亏损数量计数值成比例地降低,就如原始 “已优化规模” 资金管理类一样。优化函数的清单如下:

//+------------------------------------------------------------------+
//| Optimizing lot size for open.                                    |
//+------------------------------------------------------------------+
double CMoneyBAYES::Optimize(int Type, double lots)
{  double lot = lots;
//--- calculate number of losses orders without a break
   if(m_decrease_factor > 0)
   {  //--- select history for access
      HistorySelect(0, TimeCurrent());
      //---
      int       orders = HistoryDealsTotal(); // total history deals
      int       losses=0;                    // number of consequent losing orders
      //--
      int      size=0;
      matrix series;
      series.Init(fmin(m_series_size,orders), 2);
      series.Fill(0.0);
      //--
      CDealInfo deal;
      //---
      for(int i = orders - 1; i >= 0; i--)
      {  deal.Ticket(HistoryDealGetTicket(i));
         if(deal.Ticket() == 0)
         {  Print("CMoneySizeOptimized::Optimize: HistoryDealGetTicket failed, no trade history");
            break;
         }
         //--- check symbol
         if(deal.Symbol() != m_symbol.Name())
            continue;
         //--- check profit
         double profit = deal.Profit();
         //--
         series[size][0] = profit;
         size++;
         //--
         if(size >= m_series_size)
            break;
         if(profit<0.0)
            losses++;
      }
      //--
      series.Resize(size,2);
      SetCluster(series);
      double _cond = GetPosterior(Type, series);
      //--
      //---
      if(_cond < m_condition)
         lot = NormalizeDouble(lot - lot * losses / m_decrease_factor, 2);
   }
//--- normalize and check limits


...

//---

...

//---

...

//---
   return(lot);
}


减少因子所有其它参数,和已投入保证金百分比,保持与原始“规模已优化” MM 类相同。

为了与 BI 进行比较,我们可以考虑 凯利(Kelly)准则,其考虑了获胜结果和风险回报率,但兼具长远眼光,不一定按照近期或中期表现来更新分配准则。其公式给出如下 K = W – ((1 - W) / R)

其中:

  • K 是百分比分配
  • W 是胜率 &
  • R 是盈利因子

据报道,这种方式已被投资大师采用,因其在资本配给方面具有长期展望,然而亦可说,仓位理应采取长期方式,而非配给。在操作事务中往往采用长期展望,不过在涉及风险的情况下,短期趋于更关键,这就是为什么执行是一个单独的主题。

故此,BI 相对于凯利准则(KC)的优势能被总结为以下论点:KC 假设市场持续占优,这在横向范围很长的情况下能够成真。忽视交易成本和滑点是另一个类似的反对 KC 的论点,虽然两者都可以长期忽视,但公平地说,大多数市场的设定方式是允许某人代表他人、或用他人的资本进行交易。本质上这意味着需要对这些短期行情保持相当程度的敏感性,因其能够判定交易者或投资者在资本投资中是否仍在委托。


尾随破位类

最后,我们看一下同样使用 BI 实现的自定义尾随类。对此,我们的时间序列必须关注价格柱线范围,因为这始终是波动性的一个很好的代表,是影响应为持仓止损位调整多少的关键衡量值。我们曾使用时间序列数值的变化,如同信号我们使用收盘价的变化,而对于 MM,我们使用交易结果(盈利相较于账户净值水平),这也是账户净值水平的实际变化。当应用于我们的简陋聚集类方法时,变化为我们提供了一组非常基本但可行的指数,这些指数对于这些浮点数据点进行分组十分实用。

智能系统尾随类的一种类似方式应专注于价格柱线范围从高到低的变化。我们以此方式假设,在于我们正在寻找一个聚集索引(在这种尾随状况下,“m_long_cluster” 或 “m_short_cluster” 两者可能相同),如此这般,当在时间序列中它更有可能跟随时,那么我们需要按当前价格柱线范围成比例的额度移动止损。

针对多头和空头持仓,我们已用单独的输入参数,但原则上我们能够仅用一个来调整多头和空头持仓的止损。对于多头持仓,我们的实现清单给出如下:

//+------------------------------------------------------------------+
//| Checking trailing stop and/or profit for long position.          |
//+------------------------------------------------------------------+
bool CTrailingBAYES::CheckTrailingStopLong(CPositionInfo *position,double &sl,double &tp)
  {
//--- check

...

//---
 
...

//---
   sl=EMPTY_VALUE;
   tp=EMPTY_VALUE;
   //
   
   vector _h_new, _h_old, _l_new, _l_old, _s;
   _h_new.CopyRates(m_symbol.Name(), m_period, COPY_RATES_HIGH, 0, m_series_size);
   _h_old.CopyRates(m_symbol.Name(), m_period, COPY_RATES_HIGH, 1, m_series_size);
   _l_new.CopyRates(m_symbol.Name(), m_period, COPY_RATES_LOW, 0, m_series_size);
   _l_old.CopyRates(m_symbol.Name(), m_period, COPY_RATES_LOW, 1, m_series_size);
   _s = (_h_new - _l_new) - (_h_old - _l_old);
   matrix _series;
   _series.Init(_s.Size(), 2);
   for(int i = 0; i < int(_s.Size()); i++)
   {  _series[i][0] = _s[i];
   }
   SetCluster(_series);
   double _cond = GetPosterior(m_long_cluster, _series);
   //
   delta=0.5*(_h_new[0] - _l_new[0]);
   if(_cond>0.5&&price-base>delta)
     {
      sl=price-delta;
     }
//---
   return(sl!=EMPTY_VALUE);
  }

将其与 MQL5 函数库中内置的那些尾随破位类比较,测试和报告部分提供如下。


测试和报告

我们依据 EUR JPY,2022 年的 H4 时间帧执行测试。由于我们已开发了 3 个可在向导智能系统中使用的独立自定义类,我们将按顺序组装 3 个独立的智能交易系统,第一个只有信号类,而资金管理使用固定手数,未用尾随破位;第二个将含有相同的信号类,但增加了我们上面编码的资金管理类,且未用尾随破位;而最后的智能系统将包含我们上面编码的所有 3 个类。有关经由向导组装这些类的指南,请参阅此处

如果我们运行所有三个智能系统,我们会得到以下报告和净值曲线:

r0

c0

仅含 BI 信号类的智能系统报告和净值曲线。


r05

c05

仅含 BI 信号类,和 MM 类的智能系统的报告和净值曲线。


r1

c1

含有 BI 信号类、MM 和尾随类的智能系统的报告和净值曲线。

与其可见,随着 BI 从信号类到 MM 至尾随类的更多搭配,整体性能确实趋向于正相关。这种测试是依据真实报价上完成的,但如常,涵盖较长周期的独立测试是理想的,这是读者应该牢记的事情。作为控制,我们可以优化 3 个使用函数库类的独立智能系统。在所有这些测试运行当中,我们均未用退出价格目标,并仅靠开仓和平仓信号来控制退出。我们挑选出色的振荡器信号类、规模已优化的资金管理类、和移动平均线尾随类,如“控制”智能系统中所用。如上运行类似的测试,会生成以下结果:

cr1

cc1

仅搭配动量振荡器类的智能系统的报告和净值曲线。


cr2

cc2

控制智能系统的报告和净值曲线,含 2 个选定类。


cr3

cc3

控制智能系统的报告和净值曲线,包含所有 3 个选定的类。

我们的控制性能落后于 BI 智能系统,而第三次运行除外。我们选择的替代信号、MM 和尾随类也极大地影响了这个“结果”,不过总体目标是确立我们的 BI 智能系统与 MQL5 函数库中提供的性能是否存在显著差异,答案很明确。


结束语

总而言之,我们已经测试了贝叶斯推理在构建简单智能系统中的作用,将其基本思想并入由 MQL5 向导组装的三款不同支柱类的智能系统当中。我们此处所用方式严格上只是介绍性的,并没有涵盖重要的领域,尤其是与使用更复杂聚集算法、甚或多维数据集有关时。这些都是可以探索的道路,如果在涵盖相宜的历史期间、依赖高品质的即刻报价数据进行正确的测试,则可为人们提供优势。于贝叶斯推理名下能测试更多内容,欢迎读者探索这一点,在于向导组装的智能系统仍是测试原型思路的可靠工具。


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

附加的文件 |
bayes_3.mq5 (7.56 KB)
SignalWZ_19_.mqh (7.89 KB)
TrailingWZ_19.mqh (7.95 KB)
MoneyWZ_19.mqh (9.05 KB)
开发多币种 EA 交易系统(第 15 部分):为真实交易准备 EA 开发多币种 EA 交易系统(第 15 部分):为真实交易准备 EA
当我们逐渐接近获得一个现成的 EA 时,我们需要注意在测试交易策略阶段看似次要的问题,但在转向真实交易时变得重要。
神经网络实践:伪逆 (二) 神经网络实践:伪逆 (二)
由于这些文章本质上是教育性的,并不打算展示特定功能的实现,因此我们在本文中将做一些不同的事情。我们将重点介绍伪逆的因式分解,而不是展示如何应用因式分解来获得矩阵的逆。原因是,如果我们能以一种特殊的方式来获得一般系数,那么展示如何获得一般系数就没有意义了。更好的是,读者可以更深入地理解为什么事情会以这种方式发生。那么,现在让我们来弄清楚为什么随着时间的推移,硬件正在取代软件。
您应当知道的 MQL5 向导技术(第 20 部分):符号回归 您应当知道的 MQL5 向导技术(第 20 部分):符号回归
符号回归是一种回归形式,它从最小、甚或没有假设开始,而底层模型看起来应当映射所研究数据集。尽管它可以通过贝叶斯(Bayesian)方法、或神经网络来实现,但我们看看如何使用遗传算法实现,从而有助于在 MQL5 向导中使用自定义的智能信号类。
神经网络变得简单(第 90 部分):时间序列的频率插值(FITS) 神经网络变得简单(第 90 部分):时间序列的频率插值(FITS)
通过研究 FEDformer 方法,我们打开了时间序列频域表述的大门。在这篇新文章中,我们将继续一开始的主题。我们将研究一种方法,据其我们不仅能进行分析,还可以预测特定区域的后续状态。