外汇EA编写教程:用于一组指标信号的朴素贝叶斯分类器

无论我们喜欢与否, 统计在交易中都扮演着重要的角色。从充满数字的基本面消息开始, 至交易报告或测试报告结束, 我们不能没有统计指标。与此同时, 有关统计在进行交易决策方面的论文仍然是最具争议的命题之一。行情是否随机, 报价是否稳定, 概率方法是否适用于分析?这可以无限期地争论。很容易就能在互联网以及 mql5.com 网站上找到各种观点的素材和讨论, 严格的科学计算和令人印象深刻的图表。然而, 交易者通常感兴趣的是应用方面 — 如何在交易终端中实际运作。本文尝试展示一种务实方法, 通过一组技术指标进行交易决策的概率模型。少于理论, 精于实践。

思路是从概率论的角度评估各种指标的潜力, 并测试指标委员会的能力, 以便提升交易系统的胜率百分比。

这需要创建一个处理任意指标信号的框架, 和一个简单的智能系统进行测试。

建议将标准指标作为操作指标, 尽管框架允许包括和分析其它自定义指标。

但是在设计和实现算法之前, 还需要一点理论。

条件概率模型的简介

文章的标题提到了 朴素贝叶斯分类器。它基于著名的 贝叶斯公式, 本文会进行简要的研究。它之所以被称为 “朴素”, 在于公式以随机变量的独立性为必要假设。指标的独立性将在后面讨论, 但现在 — 是公式本身。

   (1)

其中 H 是系统内部状态的假设 (在我们的案例中是行情状态和交易系统的假设), E 是观察事件 (在我们的案例中是指标的信号), 和它们的概率描述:

  • P(H) 是先验概率, 从观测历史已知, 状态 H 的概率;
  • P(E) 是事件 E 的总概率, 考虑了所有存在的假设, 其中几个通常存在 (应当注意的是, 假设必须是不相交的, 即系统中一次只能有一个状态; 链接是为那些想深入研究理论的人所提供);
  • P(E|H) 是假设 (状态) H 为真时, 事件 E 的发生概率;
  • P(H|E) 是当观察事件 E 时, 假设 (状态) H 的后验概率。

考察一个简单的交易系统为例。行情状态诸如走势向上 (买入), 走势向下 (卖出) 和横向波动 (等待) 通常视为假设 H。指标信号描述的行情可能状态可作为事件 E。

对于特定指标的信号, 可依据历史数据轻松地从公式 (1) 的右侧计算概率, 随后找出行情最可能的状态 P(H|E)。

然而, 计算需要对假设和收集统计数据的方法进行更明确的定义, 这将作为获取概率的依据。

首先, 假设交易依据柱线 (而非即时报价) 进行的。交易绩效可以通过盈利, 盈利因子或其它特征来评估。但为了简单起见, 我们将使用入场的盈亏比率。这便直接将系统的评估与成功交易 (使用信号) 的概率联系起来。

我们还将限制我们的交易系统, 不使用止盈和止损价位, 无需尾随停止以及修改手数。所有这些参数都可以引入到模型中, 但是它们会使概率的计算复杂化, 并将其转化为多维分布。交易系统的唯一参数是以柱线为单位的持仓时间。换言之, 一旦使用指标在选择的方向上入场, 则在预定时间之后自动执行离场。这种做法的好处在于它强调了在报价增长或下降基础上所做假设的正确性或虚假性。假设以这种纯粹的形式进行测试, 无需保障和铺垫。

为了完满地简化这个话题, 我们将做出两个激进的举动。

上面提到的 “买入”, “卖出” 和 “等待” 通常被视为交易假设。忽略 “等待” 将大大减少计算, 而阐述不会缺失一般性。似乎这种简化会对获得的结果的适用性产生负面影响, 至少部分如此。然而, 如果您注意到, 即便如此简化仍需阅读大量的材料, 那么, 您也许会同意首先取得操作模型是一件好事, 然后逐步补充细节。那些想要构建更复杂模型从而研究概率密度的人可在互联网上找到相应的成果 (包括英语, 如 合并财务的推理方法技术指标, 其中描述了一种混合概率决策系统)。

最后, 第二个也是最后一个激进的举措是将 “买入” 和 “卖出” 的状态结合在一起, 但具有普遍意义 — “入场”。一款指标的不同指向信号, 通常以相似的方式对称使用。例如, 根据指标的超买状态变成卖出信号, 而超卖状态 — 为买入信号。

换言之, 假设 H 现在是两个方向 (买入或卖出) 中成功的入场。

在这些条件下, 公式 (1) 右侧的概率可以根据选定的报价历史计算如下。

由于可以在任何柱线上成功入场 — 其中一个方向变得有利可图 (此处忽略点差, 因为 D1 被选为操作时间帧, 更详细的描述如下)。

P(E) = 带指标信号的柱线数 / 柱线总数

P(E|H) = 盈利交易方向匹配的指标信号柱线数 / 柱线总数

可以使用简化后获得的公式, 依据历史数据计算所选指标的信号指向成功交易条件的概率。

   (2)

其中 Nok 是正确信号的数量, Ntotal 是信号的总数。

用于计算任意指标概率的框架将会稍后实现。如我们将看到的, 这个概率通常接近 0.5, 有必要进行一些研究, 以便找到稳定超过 0.5 的条件。然而, 数值较大的指标是罕见的。对于主要研究的标准指标, 概率在 0.51-0.55 的范围内变化。很明显, 这些数值太小, 更像是 “盈亏平衡”, 而不能令资金稳定地增长。

为了解决这个问题, 需要使用多个指标而不仅仅是一个指标。这个决定本身就不是新的, 它被大多数的交易者所使用。但是, 概率理论允许对不同组合的指标效率进行定量分析, 并评估潜在的影响。

公式 (1) 对应三个指标 (A, B, C) 的情况, 将如下所示:

  (3)

应该采用方便算法计算的形式。幸运地是, 贝叶斯理论可应用在许多行业, 因此可以为我们的案例找到现成的食谱。

特别是, 有这样的一个领域 朴素贝叶斯垃圾过滤。没有必要彻底研究它。仅有基本概念是相关的。如果一份文档 (例如, 电子邮件) 当中包含某些特征单词, 则它被标记为垃圾。一般单词在语言中出现的概率, 以及在垃圾中发现的概率是已知的。类似地, 我们已知指标信号的一般概率及其 “命中率” 的百分比。换句话说, 为了使垃圾处理理论完全融入我们的概率交易理论中, 用 “成功交易” 来代替 “垃圾” 假设, 以及 “指标信号” 来代替 “单词” 事件就足够了。

那么公式 (3) 可以通过独立指标的概率以下列方式进行扩展 (见上面的计算):

   (4)

根据公式 (2) 分别计算每个指标的 P(H|A), P(H|B), P(H|C)。

当然, 在必要时将公式 (4) 扩展到任意数量的指标是很容易地。为了得出指标数量如何影响正确交易决策的概率的想法, 假设所有指标具有相同的概率值:

则公式 (4) 变为:

   (5)

其中 N 是指标数量。

N 的各种数值的函数图形显示在如图例 1 当中。

带有不同数量随机变量的联合概率的外貌

图例. 1. 带有不同数量随机变量的联合概率的外貌

因此, 在 p = 0.51, 我们得到 P(3) = 0.53, 这是不利的; 但在 p = 0.55, P(3) = 0.65, 这是一个明显的改善。

指标的独立性

上面研究的公式是基于所分析的随机过程的独立性假设, 其为这种情况下的指标信号。但遇到这种条件了吗?

显然, 某些指标, 包括许多来自标准的指标, 有很多共通之处。作为直观的图解, 图例 2 显示了一些内置指标。

一组相似的标准指标

图例. 2. 一组相似的标准指标

很容易看出, 相同周期的随机指标和 WPR 指标, 在最后一个窗口中相互交叠, 彼此几乎重复。这并不令人惊奇, 因为它们的公式是等价的。

在屏幕截图中的稍高处, MACD 和动量振荡器指标相同, 针对移动均线类型进行了修正。此外, 由于都是基于移动平均线 (MA) 来绘制的, 所以它们不能被称为独立于 MA 本身。

RSI, RVI, CCI 也是强相关的。应当注意的是, 几乎所有的标准振荡器都是相似的, 相关系数接近 1。

特别是 ATR 和 StdDev, 波动指标之间显著巧合。

在形成交易系统的一套指标时, 应考虑所有这些因素, 因为所依据指标的委员会的真实效果在实际中将远远低于理论期望。

顺便说一下, 当训练神经网络时也会发生相同的情形。交易者们经常用它们来尝试处理来自众多自愿选择的指标数据。然而, 喂养依赖载体作为输入显著降低了训练的效率, 因为网络的计算能力被浪费。所分析数据的容量也许看起来很大, 但其中包含的信息是重复的, 没有意义。

针对这个问题的严格方法需要计算指标和最小成对数值形成的集合之间的相关性。这是一个单独且大范围的研究。有兴趣的人可以在互联网上找到相关文章。在此, 我们所遵循的一般思路将会基于上述观察结果。例如, 其中一组也许如下所示: 随机, ATR, AC (加速/减速) 或 WPR, 布林带, 动量。

应当注意的是, 加速/减速 (AC) 指标本质上是振荡器的派生体。为什么它适合纳入该组?

我们以简化的形式来表示报价序列 (或从它们派生的振荡器) 作为周期性振荡, 例如余弦或正弦。这些函数的导数等于:

   (6)

这些函数与其导数的相关性为零。

    (7)

所以, 通常考虑使用指标的一阶导数作为额外独立指标的良好候选者。

另一方面, 二阶导数在这种振荡处理中是一个可疑的候选者, 因为得到原始信号副本的机会很高。

为了总结有关指标独立性的讨论, 有意将参照不同周期计算的指标副本视为独立的。

可假设答案取决于周期的比率。轻微的差异显然掩盖了指标的依赖性, 因此需要明显的差异。这与经典方法有部分一致, 例如 Elder 的三联屏方法, 其时间帧通常与不同时间周期的分析指标有相当于 5 倍的差别。

应当注意的是, 在所研究的系统中, 不仅指标值事实上应是独立变量, 且交易信号应由它们直接产生。然而, 对于大多数相同类型的指标 (例如振荡器), 交易信号产生的原理是相似的。所以, 时间序列的强弱依赖性等价于信号的强弱依赖性。

设计

所以, 我们已掌握了这个理论, 如今已准备好去了解怎样以及如何编码。

指标的交易信号统计将在特殊的智能系统中收集。为令智能系统能够根据任意指标的数值进行交易, 有必要实现一个框架 (本质上是一个 mqh 头文件), 它通过输入参数获取所用指标和信号生成方法的描述。举例, 在设置中应有一个选项可以设置两条不同周期的移动平均线, 并且当快速均线穿越慢速均线时分别产生买入和卖出信号。

EA 将由柱线开盘明确控制, 并以开盘价进行交易。这不是真正的智能系统, 而是计算概率和测试假设的工具。其重点是快速检查, 因为指标集合有无穷多个选项。。

D1 将用作省缺的操作时间帧。当然, 没有什么可以阻止您使用任意其它时间帧进行分析。不过, D1 最不容易受到随机噪声的影响, 并且存在于若干年份中的规律性分析更符合概率方法的特异性。此外, D1 的交易策略通常可以忽略点差, 从而抵消系统的 “等待” 状态。然而, 对于日内交易, 无法做出这样的假设, 有必要计算更多假设数量的概率。

如早前所述, EA 将根据指标信号开仓, 并在预定时间后平仓。为此目的, 引入相应的输入参数。其省缺值为 5 天。这是 D1 时间帧的一个特征周期, 它被用于众多也使用 D1 进行交易的研究。

EA 和框架将是跨平台的, 也就是说, 它们将能够在 MetaTrader 4 和 MetaTrader 5 中编译和运行。该功能将由公开的包装头文件提供, 这些文件允许在 MetaTrader 5 环境中使用 MetaTrader 4 的 MQL API。甚或, 在某些情况下将会条件编译: 代码的特定部分将封装在 #ifdef __MQL4__ 和 #ifdef __MQL5__ 预处理程序指令中。

以 MQL 实现

指标框架

处理指标信号的框架总览将从研究所需指标类型开始。最明显的枚举包括所有内置指标, 以及自定义指标的 iCustom 项目。利用框架的输入参数选择指标所需的枚举。

enum IndicatorType
{
  iCustom,

  iAC,
  iAD,
  tADX_period_price,
  tAlligator_jawP_jawS_teethP_teethS_lipsP_lipsS_method_price,
  iAO,
  iATR_period,
  tBands_period_deviation_shift_price,
  iBearsPower_period_price,
  iBullsPower_period_price,
  iBWMFI,
  iCCI_period_price,
  iDeMarker_period,
  tEnvelopes_period_method_shift_price_deviation,
  iForce_period_method_price,
  dFractals,
  dGator_jawP_jawS_teethP_teethS_lipsP_lipsS_method_price,
  fIchimoku_tenkan_kijun_senkou,
  iMomentum_period_price,
  iMFI_period,
  iMA_period_shift_method_price,
  dMACD_fast_slow_signal_price,
  iOBV_price,
  iOsMA_fast_slow_signal_price,
  iRSI_period_price,
  dRVI_period,
  iSAR_step_maximum,
  iStdDev_period_shift_method_price,
  dStochastic_K_D_slowing_method_price,
  iWPR_period

};

每个内置指标的名称包含一个带有指标本身参数信息的后缀。元素的第一个字符表示可用缓存区的数量, 例如: i — 一个缓冲区, d — 两个, t — 三个。这只是给用户的一个提示。如果他指定了不正确的参数数量或不存在的缓冲区索引, 框架将在日志里输出错误。

自然地, 对于每个指标, 不仅需要指定输入参数中的类型, 还要指定实际参数作为字符串, 缓冲区编号和开始读取数据的柱线编号。

还有用于产生信号的指标值。理论上, 可以有任意数量的不同信号, 但主要的变体将汇总在另一个枚举当中。

enum SignalCondition
{
  Disabled,
  NotEmptyIndicatorX,
  SignOfValueIndicatorX,
  IndicatorXcrossesIndicatorY,
  IndicatorXcrossesLevelX,
  IndicatorXrelatesToIndicatorY,
  IndicatorXrelatesToLevelX
};

因此, 形成信号的条件:

  • 指标值不为空;
  • 指标值带有所需的符号 (正或负);
  • 指标穿越另一个指标 (这里应当注意的是, 当描述信号时, 必须提供设置 2 个指标的能力);
  • 指标穿越某些等级 (此处很明显, 必须进入等级的一个区域);
  • 指标相对于另一个指标以所需的方式定位 (例如, 高于或低于);
  • 指标相对于给定等级以所需方式定位;

第一个元素 — ‘禁用’ — 允许禁止产生信号的任何条件。我们将提供若干相同的输入参数组, 用来描述信号, 省缺情况下每个信号均被禁用。

从以前的枚举项目的名称可以很容易地猜出, 需要以某种方式为数值设置所需的符号以及彼此的相对行数位置。为此目的将添加另一个枚举。

enum UpZeroDown
{
  EqualOrNone,
  UpSideOrAboveOrPositve,
  DownSideOrBelowOrNegative,
  NotEqual
};

EqualOrNone 允许检查:

  • SignOfValueIndicatorX 组合的空值
  • 与 IndicatorXrelatesToLevelX 组合的级别相等

UpSideOrAboveOrPositve 允许检查:

  • 上穿 IndicatorXcrossesIndicatorY
  • SignOfValueIndicatorX 数值为正
  • 上穿 IndicatorXcrossesLevelX 的等级
  • 如果 X 和 Y 是相同的指标, 在连续柱线上 IndicatorXrelatesToIndicatorY 的指标值增长
  • 如果 X 和 Y 是不同的指标, relatedToIndicatorY 的 X 高于 Y 的位置
  • 一个指标的位置高于 IndicatorXrelatesToLevelX 的一个等级

DownSideOrBelowOrNegative 允许检查:

  • 下穿 IndicatorXcrossesIndicatorY
  • SignOfValueIndicatorX 数值为负
  • 上穿 IndicatorXcrossesLevelX 的等级
  • 如果 X 和 Y 是相同的指标, 在连续柱线上 IndicatorXrelatesToIndicatorY 的指标值下降
  • 如果 X 和 Y 是不同的指标, relatedToIndicatorY 的 X 低于 Y 的位置
  • 一个指标的位置低于 IndicatorXrelatesToLevelX 的一个等级

NotEqual 允许检查:

  • 不等于 IndicatorXrelatesToLevelX 的一个等级 (数值)

当一个信号被触发时, 它必须被处理。为此, 我们来定义一个特殊的枚举。

enum SignalType
{
  Alert,
  Buy,
  Sell,
  CloseBuy,
  CloseSell,
  CloseAll,
  BuyAndCloseSell,
  SellAndCloseBuy,
  ModifyStopLoss,
  ModifyTakeProfit,
  ProceedToNextCondition
};

此处是信号处理的主要动作: 消息输出, 买入, 卖出, 所有已开订单平仓(买入, 卖出或两者同时), 自卖出持仓翻转为买入, 自买入持仓翻转为卖出, 修改止损或止盈价位, 并转移到下一个条件 (信号) 的检查。最后一点允许信号链检查 (例如, 检查主缓存区是否穿越信号线, 如果是, 检查是否发生在某一级别之上或之下)。

可以从中看到, 动作列表不包含放置挂单。这不在本操作的广度之内。有兴趣的人可以扩展框架。

使用所有这些提供的枚举, 可以描述用于设置工作指标的某些属性组。一个组看起来如下:

input IndicatorType Indicator1Selector = iCustom;             // ·     选择器
input string Indicator1Name = "";                             // ·     名称
input string Parameter1List = "" /*1.0,value:t,value:t*/;     // ·     参数
input string Indicator1Buffer = "";                           // ·     缓存区
input int Indicator1Bar = 1;                                  // ·     柱线

当指标选择器设置为 iCustom 时, Indicator1Name 参数设计用于设置自定义指标的名称。

Parameter1List 参数允许将指标参数设置为逗号分隔的字符串。每个输入参数的类型将被自动检测, 例如: 11.0 — 双精度, 11 — 整数, 2015.01.01 20:00 — 日期, true/false — 布尔, “text” — 字符串。某些参数(例如移动平均线的类型或价格类型) 可以不用数字设置, 而是不带引号的字符串 (sma, ema, smma, lwma, close, open, high, low, median, typical, weighted, lowhigh, closeclose)。

Indicator1Buffer 是没有引号的缓存区的编号或名称。支持的缓存区名称: main, signal, upper, lower, jaw, teeth, lips, tenkan, kijun, senkouA, senkouB, chikou, +di, -di。

Indicator1Bar — 柱线编号, 省缺为 1。

一旦定义了所有指标, 就可将它们用作形成信号的基础, 即用于触发事件的条件。每个信号由一组输入参数定义。

input string __SIGNAL_A = "";
input SignalCondition ConditionA = Disabled;                      // ·     条件 A
input string IndicatorA1 = "";                                    // ·     信号 A 的指标 X
input string IndicatorA2 = "";                                    // ·     信号 A 的指标Y
input double LevelA1 = 0;                                         // ·     信号 A 的等级 X
input double LevelA2 = 0;                                         // ·     信号 A 的等级 Y
input UpZeroDown DirectionA = EqualOrNone;                        // ·     方向或符号 A
input SignalType ExecutionA = Alert;                              // ·     动作 A

可以在 __SIGNAL_ 参数中为每个信号设置一个标识符。

‘Condition’ 选择检查信号的条件。接下来, 设置一或两个指标, 以及一或两个要使用的级别值 (第二个级别为以后保留, 不会在本实验中用到)。’Indicator’ 参数中的指标即可以是来自相应属性组的指标编号, 亦或指标原型的形式:

indicatorName@buffer(param1,param2,…)[bar]

此条目格式能够快速确定使用的指标, 无需使用属性组的详细描述。例如,

iMA@0(1,0,sma,high)[1]

工作中的智能系统在每根当前柱线上取编号 1 的柱线的最高价 (最新的完整柱线, 已知的最终最高价) 返回。

因此, 指标设置即可在专用属性组中 (用于随后引用来自信号的编号), 也可直接在 ‘Indicator’ 参数 (X 或 Y) 里的信号中。在不同信号中使用相同的指标, 或在一个信号内作为 X 和 Y 时, 第一种方法很方便。

‘Direction’ 参数指定触发条件的方向或数值符号。当信号被触发时, 根据 “Execution” 执行相应的动作。

接下来就是在其基础上判断指标和信号的例子。

在框架中的当前定义中, 指标不可超过 20 个参数, 指标属性的专用组最大数量为 6 (但是如之前所述, 指标可以直接在信号中附加设置), 以及最多 6 个信号。所有这些可以在源代码中修改。IndicatN.mqh 文件附在文章末尾。

此文件还实现了几个类, 其中包含用于解析指标参数, 调用它们, 检查条件以及将检查结果返回给调用代码 (此即智能系统) 的所有逻辑。

特别是, 通过上述 SignalType 枚举传递执行某些操作的指令, 使用简单的公有 TradeSignals, 其中包含与枚举项相对应的布尔字段:

class TradeSignals
{
  public:
    bool alert;
    bool buy;
    bool sell;
    bool buyExit;
    bool sellExit;
    bool ModifySL;
    bool ModifyTP;
    
    int index;
    double value;
    
    string message;
  
    TradeSignals(): alert(false), buy(false), sell(false), buyExit(false), sellExit(false), ModifySL(false), ModifyTP(false), value(EMPTY_VALUE), message(""){}
};

当满足所需条件时, 字段设置为 true。例如, 如果选择了 CloseAll 操作, 则在 TradeSignals 对象中设置 buyExit 和 sellExit 标志。

‘index’ 字段包括触发条件的序列号。

‘value’ 字段可用于传递自定义数值, 例如: 从指标值获得的新止损价位。

最后, ‘message’ 字段包括给用户的状况描述消息。

所有类的实现细节可以在源代码中找到。它使用辅助的 fmtprnt2.mqh (格式化输出到日志) 和 RubbArray.mqh (“rubber” 数组) 头文件。

应在智能系统代码中使用 #include 指令包含 IndicatN.mqh 框架头文件。结果就是, 一旦经过编译, 可在 EA 的设置对话框中看到含有指标属性的输入参数组:

指标设置

图例. 3. 指标设置

以及信号定义:

交易信号设置

图例. 4. 交易信号设置

屏幕截图显示已预设的数值。一旦我们进入 EA 的概念并开始配置特定的交易策略, 应更仔细地考虑它们。这里还应注意的是, 在设置指标属性时, 可将任何数值参数替换为类型 =var1, =var2 直至 9 的表达式。它们引用框架的设计用于优化的指定同名输入参数 (var1, var2 等)。例如, 一个条目:

iMACD@main(=var4,=var5,=var6,open)[0]

意味着 MACD 的快速, 慢速和信号线的参数可分别通过 var4, var5 和 var6 输入参数进行优化。即使禁用优化, 在单一测试运行期间, 将从框架的指定输入参数读取指标的相应属性值。

测试智能系统

为了方便编码, 我们将所有的交易函数移动到一个特殊的类, 并将它安置为一个独立的 Expert0.mqh 头文件。由于交易系统的测试相当简单, 所以该类仅允许开仓和平仓。

因此, 所有与指标和交易相关的常规操作指令都被移到头文件。

#include <IndicatN.mqh>
#include <Expert0.mqh>

indstats.mq4 文件本身只有几行代码和简单的逻辑。

由于 EA 的扩展名改为 mq5 之后, 应该在 MetaTrader 5 中进行编译和运行, 我们可以添加头文件, 以便将代码移植到新的环境中。

#ifdef __MQL5__
  #include <MarketMQL4.mqh>
  #include <ind4to5.mqh>
  #include <mt4orders.mqh>
#endif

现在, 来看看智能系统的输入参数。

input int ConsistentSignalNumber = 1;
input int Magic = 0;
input float Lot = 0.01f;
input int TradeDuration = 1;

从 Expert0.mqh 文件创建一个 Expert 对象需要 ‘Magic’ 和 ‘Lot’。

Expert e(Magic, Lot);

ConsistentSignalNumber 参数将包含组合交易信号的数量, 以便增加稳健性。

TradeDuration 参数设置以柱线数为单位的持仓时间。如前所述, 交易将根据信号开仓, 并在 5 根柱线 (即 5 天) 后离场, 因为使用的是 D1 时间帧。

OnInit 事件处理程序将初始化指标框架。

int OnInit()
{
  return IndicatN::handleInit();
}

OnTick 处理程序提供的控制基于柱线开盘。

void OnTick()
{
  static datetime lastBar;
  
  if(lastBar != Time[0])
  {
    const RubbArray<TradeSignals> *ts = IndicatN::handleStart();
    ...
    lastBar = Time[0];
  }
}

在新柱线形成时, 再次调用指标框架, 检查所有指标和相关条件。结果位于触发信号的数组中 — TradeSignals 对象。

现在是时候讨论统计积累了。

一旦满足, 框架的每个条件 (事件) 将默认生成带有 “alert” 标志的信号。这将用于计数来自指标的信号数量, 以及系统状态的落实数量, 即买入或卖出时的成功情况。

为了计算统计量, 我们将描述数组。

int bars = 0;                                         // 柱线/抽样的总计数
int bull = 0, bear = 0;                               // 每笔交易类型的柱线/抽样数量
int buy[MAX_SIGNAL_NUM] = {0}, sell[MAX_SIGNAL_NUM] = {0};                     // 无条件信号数组
int buyOnBull[MAX_SIGNAL_NUM] = {0}, sellOnBear[MAX_SIGNAL_NUM] = {0};         // 条件 (成功) 信号数组

在我们的智能柱线交易的案例中, 最后 5 根柱线之后的每根柱线均会潜在地新入场交易。每个这样的段落特征在于报价的上升或下降, 并分别被标记为看涨或看跌。

所有买入和卖出信号将被汇总在 ‘buy’ 和 ‘sell’ 数组中。如果相应的信号与 “看涨” 或 “看跌” 段落相匹配, 则它也会累积在 buyOnBull 或 sellOnBear 数组中, 具体取决于类型。

OnTick 中的以下代码将填充数组。

    const RubbArray<TradeSignals> *ts = IndicatN::handleStart();
    bool up = false, down = false;
    int buySignalCount = 0, sellSignalCount = 0;
    
    for(int i = 0; i < ts.size(); i++)
    {
      // 用于收集统计信息d的警报
      if(ts[i].alert)
      {
        // 在设置事件时, 按照 i 进行列举,
        // 假设 H_xxx 应先至, 在信号 S_xxx 之前,
        // 因为我们在此分配向上或向下的标记
        if(IndicatN::GetSignal(ts[i].index) == "H_BULL")
        {
          bull++;
          buy[ts[i].index]++;
          up = true;
        }
        else if(IndicatN::GetSignal(ts[i].index) == "H_BEAR")
        {
          bear++;
          sell[ts[i].index]++;
          down = true;
        }
        else if(StringFind(IndicatN::GetSignal(ts[i].index), "S_BUY") == 0)
        {
          buy[ts[i].index]++;
          if(up)
          {
            if(PrintDetails) Print("buyOk ", IndicatN::GetSignal(ts[i].index));
            buyOnBull[ts[i].index]++;
          }
        }
        else if(StringFind(IndicatN::GetSignal(ts[i].index), "S_SELL") == 0)
        {
          sell[ts[i].index]++;
          if(down)
          {
            if(PrintDetails) Print("sellOk ", IndicatN::GetSignal(ts[i].index));
            sellOnBear[ts[i].index]++;
          }
        }
        
        if(PrintDetails) Print(ts[i].message);
      }
    }

在获得触发信号的数组之后, 在一个循环内对其元素进行遍历。启用 ‘alert’ 标志表示收集统计量。

在更深入地分析代码之前, 我们介绍一下信号 (事件) 命名的特殊约定。行情看涨或看跌状态的假设将用 H_BULL 和 H_BEAR 标识符标记。这些事件必须首先使用框架的输入参数进行定义, 先于其它事件 (指标信号)。这是必要的, 以便根据确认的假设设置适当的特征 – 布尔变量 ‘up’ 和 ‘down’。

指标信号必须以 S_BUY 或 S_SELL 标识符开头。

正如可以看到的, 使用激活事件 ts[i].index 编号的引用, 其标识符是通过调用 GetSignal 函数获得的。如果假设得以满足, 则看涨或看跌段落的计数器将会更新。在信号产生的情况下, 每种信号类型均会计算总数, 以及它们成功的索引, 也就是与当前假设相匹配的次数。

请记住, 每根柱线上的 H_BULL 假设或 H_BEAR 假设均为 true。

除了收集统计量之外, EA 应支持信号交易。为了这个目的, 这个循环的实体会补充检查 “买入” 和 “卖出” 标志。

      if(ts[i].buy)
      {
        buySignalCount++;
      }
      else
      if(ts[i].sell)
      {
        sellSignalCount++;
      }

交易功能将在循环后实现。首先, 已开仓位 (如有) 在指定期限后平仓。

    if(e.getLastOrderBar() >= TradeDuration)
    {
      e.closeMarketOrders();
    }

然后根据联合信号进行买入或卖出。

    if(buySignalCount >= ConsistentSignalNumber
    && sellSignalCount >= ConsistentSignalNumber)
    {
      Print("信号冲突");
    }
    else
    if(buySignalCount >= ConsistentSignalNumber)
    {
      e.closeMarketOrders(e.mask(OP_SELL));
      
      if(e.getOrderCount(e.mask(OP_BUY)) == 0)
      {
        e.placeMarketOrder(OP_BUY);
      }
    }
    else
    if(sellSignalCount >= ConsistentSignalNumber)
    {
      e.closeMarketOrders(e.mask(OP_BUY));
      
      if(e.getOrderCount(e.mask(OP_SELL)) == 0)
      {
        e.placeMarketOrder(OP_SELL);
      }
    }

如果买卖信号相互矛盾, 则跳过这样的状态。如果买入和卖出信号的数量等于或大于预定义数量 ConsistentSignalNumber, 则相应开单。

应当注意的是, 为 ConsistentSignalNumber 设置小于已配置信号数量的数值, 可测试交易系统的所有策略组合模式, 或大多数策略模式。在正常操作模式下, EA 将使用交汇点, 而非并联, 因为 ConsistentSignalNumber 必须与信号数量完全相等才能找到联合事件。例如, 配置 3 个信号, ConsistentSignalNumber 设置为 3, 只有当所有三个事件同时发生时, 才会进行交易。如果 ConsistentSignalNumber 设置为 1, 则接收到 3 个信号中的任何 (至少一个) 信号时, 交易开单。

OnDeinit 处理程序将收集到的统计警报或订单历史输出到日志。

智能系统的完整源代码可以在 indstats.mq4 文件中看到。

交易信号设置

所有其它信号必须根据买入或卖出的两个假设进行检查。为此, 请配置 H_BULL 和 H_BEAR 信号及其指标。

为了获得柱线价格, 请使用 iMA 指标, 周期为 1。在 the __INDICATOR_1 组, 设置:

Selector = iMA_period_shift_method_price

Parameters = 1,0,sma,open

Buffer = 0

Bar = 0

在 __INDICATOR_2 组, 除柱线数之外的设置类似: 它应该设置为 5, 在 TradeDuration 参数中设置要用到的柱线数。

换句话说, 智能系统不会在收集统计量模式下进行交易。代之, 它会根据所使用的价格类型分析第 5 到第 0 根柱线之间的报价变化, 以及第 5 或第 6 根柱线的指标信号: 对于基于开盘价的指标, 值可以从第 5 根柱线取得, 而对于所有其它 – 从第 6 根。在统计量收集模式中, 第 5 个柱线是一个虚拟的当前柱线, 随后所有的柱线都提供有关行情看涨或看跌的假设 “未来” 落实情况的信息。

应该说, 在交易模式下, 信号将取自柱线 0 (如果指标基于开盘价) 或柱线 1 (在其它情况下)。如果智能系统没有使用开盘价操作, 而是分析即时报价, 则需要在柱线 0 中检查指标值。

存在这两种模式 — 统计量收集和交易 — 意味着必须创建多个不同工作柱线数量的参数集合。我们将首先收集统计数据, 然后轻松将其转换为真实的交易集。

MA 指标的这两个副本将用于配置假设。在 __SIGNAL_A 组中输入:

__SIGNAL_A = H_BULL

Condition = IndicatorXrelatesToIndicatorY
Indicator X = 1
Indicator Y = 2
Direction or sign = UpSideOrAboveOrPositve
Action = Alert

__SIGNAL_B 组将会类似配置, 除了方向:

__SIGNAL_B = H_BEAR
Direction or sign = DownSideOrBelowOrNegative

为了测试交易模型的概率, 将采用基于 3 种标准指标的策略:

  • 随机振荡
  • MACD
  • BollingerBands

应预先指出, 所有指标的参数都已经过优化, 其中一些参数有意留作输入参数 var1, var2 等的引用, 来演示框架的这一特征。为在您的提供商的数据上重现正面结果, 每个策略可能需要重新优化。

基于随机振荡的策略 当指标上穿 20 等级时买入, 当它下穿 80 等级时卖出。为此, 定义 __INDICATOR_3 组:

Selector = dStochastic_K_D_slowing_method_price

Parameters = 14,3,3,sma,lowhigh
Buffer = main
Bar = 6

由于指标中使用了最高价和最低价, 因此柱线编号必须取 6 — 在柱线 5 之前最后那根完整的柱线, 在触发信号的情况下开始虚拟交易之处。

买卖信号根据随机指标进行调整。买入组:

__SIGNAL_C = S_BUY stochastic

Condition = IndicatorXcrossesLevelX
Level X = 20
Direction or sign = UpSideOrAboveOrPositve

卖出组:

__SIGNAL_D = S_SELL stochastic

Condition = IndicatorXcrossesLevelX
Level X = 80
Direction or sign = DownSideOrBelowOrNegative

基于 MACD 的策略 当主线上穿信号线时买入, 并在下穿时卖出。

配置 __INDICATOR_4 指标组:

Selector = dMACD_fast_slow_signal_price

Parameters = =var4,=var5,=var6,open
Buffer = signal
Bar = 5

周期 ‘fast’, ‘slow’, ‘signal’ 将从参数 var4, var5, var6 读取, 可用于优化。目前它们分别设置为 6, 21, 6。使用的柱线编号为 5, 因为指标基于开盘价绘制。

由于配置指标的组数量有限, 所以 “main” 缓存区将直接在信号中定义。买入组:

__SIGNAL_E = S_BUY macd

Condition = IndicatorXcrossesIndicatorY
Indicator X = iMACD@main(=var4,=var5,=var6,open)[5]
Indicator Y = 4
Direction or sign = UpSideOrAboveOrPositve

卖出组: 

__SIGNAL_F = S_SELL macd

Condition = IndicatorXcrossesIndicatorY
Indicator X = iMACD@main(=var4,=var5,=var6,open)[5]
Indicator Y = 4
Direction or sign = DownSideOrBelowOrNegative

基于布林带的策略 当前一根柱线的最高价突破向右平移 2 根柱线的指标上边界时买入, 并当前一根柱线的最低价突破向右平移 2 根柱线的指标下边界时卖出。以下是两条指标线的设置。

__INDICATOR_5:

Selector = tBands_period_deviation_shift_price

Parameters = =var1,=var2,2,typical

Buffer = upper
Bar = 5

__INDICATOR_6:

Selector = tBands_period_deviation_shift_price

Parameters = =var1,=var2,2,typical
Buffer = lower
Bar = 5

周期和偏差分别在 var1 和 var2 中指定为 7 和 1。尽管价格类型是典型的, 但是在这两种情况下都可以使用柱线 5, 因为指标向右平移 2 根柱线, 即, 真实计算是依据过去的数据。

最后, 设置信号组如下。

__SIGNAL_G = S_BUY bands

Condition = IndicatorXcrossesIndicatorY
Indicator X = iMA@0(1,0,sma,high)[6]
Indicator Y = 5
Direction or sign = UpSideOrAboveOrPositve

__SIGNAL_H = S_SELL bands

Condition = IndicatorXcrossesIndicatorY
Indicator X = iMA@0(1,0,sma,low)[6]
Indicator Y = 6
Direction or sign = DownSideOrBelowOrNegative

所有设置作为 .set 文件附加在文章末尾。

结果

按指标统计

为了计算概率, 将使用 EURUSD 对的 D1, 2014.01.01-2017.01.01 期间的统计。统计信息收集模式的 EA 设置包含在 indstats-stats-all.set 文件中。

收集的数据被输出到日志。下面是其中一个例子:

: bars=778
: bull=328 bear=449
:    buy:    328      0     30      0     50      0     58      0 
:  buyOk:      0      0     18      0     29      0     30      0 
:   sell:      0    449      0     22      0     49      0     67 
: sellOk:      0      0      0     14      0     28      0     41 
: totals:   0.00   0.00   0.60   0.64   0.58   0.57   0.52   0.61 
: Stats by name:
:  macd=0.576 [57/99]
:  bands=0.568 [71/125]
:  stochastic=0.615 [32/52]

总数为 778 个, 其中 328 个适合成功的 5 天买入交易, 449 个适合成功的 5 天卖出交易。前两列包含假设的计数器 — 相同的 2 个数字, 下两列表示相应的交易策略, 每种交易策略由买入交易列和卖出交易列表示。例如, 随机指标策略产生了 30 个买入信号, 其中 18 个是有利可图的, 22 个卖出信号, 其中 14 个是有利可图的。将成功信号的总数相加并除以产生的信号数量, 结果为每个信号的效率值 (基于历史数据的成功概率)。

  • 随机振荡 — 0.615
  • MACD — 0.576
  • 布林带 — 0.568

测试交易

为了确保正确计算统计信息, 必须在交易模式下运行 EA。为此, 编辑设置中的柱线编号, 用 0 替换代 5, 并用 1 替换 6。此外, 应该通过将 Action 参数设置为 Buy 和 Sell 而不是 Alert 来启用交易策略。例如, 检查基于随机振荡的交易, 在 __SIGNAL_C (S_BUY stochastic) 组的 Action 参数里将 Alert 替换为 Buy, 并在 __SIGNAL_D (S_SELL stochastic) 组里将 Alert 替换为 Sell。

所有 3 种策略的相应设置分别在文件 indstats-trade-stoch.set, indstats-trade-macd.set, indstats-trade-bands.set 中提供。

使用这些参数集合运行 3 次 EA 生成 3 份交易摘要日志。统计数据在最末端。例如, 以下行获自随机振荡:

: Buys: 18/29 0.62 Sells: 14/22 0.64 Total: 0.63

这些数据表示了真实交易: 29 个买入中有 18 个是有利可图的, 22 个卖出中有 14 个是有利可图的, 信号的总效率是 0.63。

以下提供了基于 MACD 和布林格的策略结果。

: Buys: 29/49 0.59 Sells: 28/49 0.57 Total: 0.58
: Buys: 29/51 0.57 Sells: 34/59 0.58 Totals: 0.57

我们将所有策略的数值汇总到一个列表中。

  • 随机振荡 — 0.63
  • MACD — 0.58
  • 布林带 — 0.57

在此可以看到前一小节中的理论几乎完整对应。事实上, 如果交易信号在 5 根柱线之内, 交易信号可能会有重叠, 在这种情况下不会重复交易, 所以有轻微的区别。

自然地, 可以分析每个单独策略的交易报告。

基于随机振荡指标的策略报告

图例. 5. 基于随机振荡指标的策略报告

基于 MACD 指标的策略报告

图例. 6. 基于 MACD 指标的策略报告

基于布林带指标的策略报告

图例. 7. 基于布林带指标的策略报告

使用公式 (4) 计算所有三个指标的同步信号入场时交易成功的理论概率。

P(H|ABC) = 0.63 * 0.58 * 0.57 / (0.63 * 0.58 * 0.57 + 0.37 * 0.42 * 0.43) = 0.208278 / (0.208278 + 0.066822) = 0.208278 / 0.2751 = 0.757

为了测试这种情形, 必须在操作中包括所有三个信号, 并将 ConsistentSignalNumber 参数的值从 1 改为 3。相应的设置位于 indstats-trade-all.set 文件中。

根据测试器中的交易, 这种系统在实际中的总效率等于 0.75:

: Buys: 4/7 0.57 Sells: 5/5 1.00 Total: 0.75

这是测试报告:

基于 3 个指标的策略组合报告

图例. 8. 基于 3 个指标的策略组合报告

下表分别列出每个指标的交易数字。

盈利,$ 盈利因子 数量 回撤,$
随机振荡 204 2.36 51 41
MACD 159 1.39 98 76
布林带 132 1.29 110 64
总计 68 3.18 12 30

正如从中所见, 由于低频率但更准确的入场, 实现了成功概率的增加。交易数量和利润总额都有所下降, 虽然盈利因子和最大回撤至少提高到 35%, 在有些情况下超过两倍。

结束语

本文研究了概率方法的最简单实现版本, 基于指标信号制定交易决策。利用一款特殊的智能系统来展示, 使用贝叶斯公式增加成功交易概率的理论计算与实际获得的结果相符。

由于信号生成是离散的, 不同指标的信号可能并不一致。指标叠加却不能给出由所有指标确认的共同信号的情况是可能的。此问题的一个可能解决方案是在信号之间引入时间冗余。

在更一般的情况下, 可以根据指标的状态 (而不是信号) 来计算执行交易假设的概率密度。例如, 基于振荡器的特定值检测出的超买或超卖值给出了成功入场的百分比 (概率)。此外, 成功交易的概率显然取决于选定的止损和止盈参数, 手数管理系统和系统的许多其它参数。所有这些都可以从概率论的角度进行分析, 并用于更准确、更复杂的计算交易决策。

文件附加于下:

  • indstats.mq4 (还有 indstats.mq5) — 智能系统。
  • common-includes.zip — 通用头文件存档。
  • additional-mt5-includes.zip — 用于 MetaTrader 5 的附加头文件存档。
  • instats-tester-sets.zip — 用于进行设置的设置文件存档。

本文译自 MetaQuotes Software Corp. 撰写的俄文原文
原文地址: https://www.mql5.com/ru/articles/3264

附加的文件 |

indstats.mq4
(15.63 KB)
common-includes.zip
(17.46 KB)

 

 


MyFxtop迈投-靠谱的外汇跟单社区,免费跟随高手做交易!

 

免责声明:本文系转载自网络,如有侵犯,请联系我们立即删除,另:本文仅代表作者个人观点,与迈投财经无关。其原创性以及文中陈述文字和内容未经本站证实,对本文以及其中全部或者部分内容、文字的真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。

著作权归作者所有。
商业转载请联系作者获得授权,非商业转载请注明出处。

風險提示

MyFxtops邁投所列信息僅供參考,不構成投資建議,也不代表任何形式的推薦或者誘導行為。MyFxtops邁投非外匯經紀商,不接觸妳的任何資金。 MYFXTOPS不保證客戶盈利,不承擔任何責任。從事外彙和差價合約等金融產品的槓桿交易具有高風險,損失有可能超過本金,請量力而行,入市前需充分了解潛在的風險。過去的交易成績並不代表以後的交易成績。依據各地區法律法規,MyFxtops邁投不向中國大陸、美國、加拿大、朝鮮居民提供服務。

邁投公眾號

聯繫我們

客服QQ:981617007
Email: service@myfxtop.com

MyFxtops 邁投