请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  量化策略 帖子:3226071 新帖:215

开发和分析交易系统的最佳方法

随心所致发表于:2 月 1 日 19:24回复(1)

介绍

当前,外汇交易越来越普遍,新的信号和交易系统不断涌现。这是不可避免的,因为一直有很多人想快速轻松地赚钱。随着全球互联网基础设施的扩张,这个市场只会变得更大。这个网站就是一个例证。它包含了最发达的生态系统,具有许多功能,允许用户购买产品或服务,以及通过贡献网站开发来赚钱。在本文中,我将重点介绍选择产品和信号时要使用的标准,以及开发和应用自己的交易系统时要遵循的规则。

我的哲学

让我告诉你们一些关于我的动机和我构建交易系统的原则。我已经开发了多个不同的EA,主要是在 MetaTrader 4 上,不过现在是时候习惯 MetaTrader 5 了。一些系统已经被改编为 MetaTrader 5 版本了。

当然,就功能而言,MetaTrader 5 把它的前身远远甩在后面了。我认为要了解市场,首先要把理论和实践结合起来。MetaTrader 4 提供了足够好的必要实践。

我试着尽可能简单和快速地开发,然后看看结果。如果结果类似于一个模式,我会尝试添加过滤器,增加信号强度并重新检查结果。如果改进了,我会努力深入研究这个过程,找出改进或恶化背后的原因。

此外,同时有必要开发不同的简单的系统来与之协同工作。几个月来对一个系统进行修补,试图使它变得完美,这无疑是一种失败的方法。一些交易员还认为,系统的输入参数和其他复杂性越多越好,)事实上,恰恰相反。

我开发的一些 EA 包含 2000 到 3000 行代码,却没有产生任何结果,而一些由20 到 50 行组成的工作代码可以在多个货币的整个历史上工作。)你对市场的了解质量是这里成功的主要因素。有才华的程序员不是那些能够开发出最复杂的系统的人,而是那些理解他们的目标并知道他们真正需要为系统添加什么的人。

这种方法的另一个优点是输出系统越简单,就越容易修复和修改它。另一个有趣的特性是,您开发的EA最终可能遵循完全不同的逻辑,这有时需要几年才能理解。

在下一个知识层次上,你开始了解市场,你开发的几乎所有机器人都变得尽可能简单,同时尽可能盈利和稳定。我现在正处于这个层次。我知道哪些有效,哪些无效,以及需要解决的关键问题。达到这个水平后,你会意识到在交易社区中流行的99%的信息完全是无稽之谈,包括烛形模式和指标。其他一些信息,如趋势和横盘,可能看起来相对可信,但诀窍在于它可以用不同的方式来解释。所以,如果你亏了,交易“专家”总是会说你错过了什么,或者你的趋势线设置不正确。

与其浪费时间,我们应该开始思考到底是什么在驾驭着价格。另一个必要的先决条件是数学知识和在分析结果、发现合理规律和理解其背后的物理现象时应用数学的能力。只有理论与实践相结合,才能实现这一目标。最后,这完全取决于你开发和测试的交易系统的数量。不要使用别人的代码,从头开始创建自己的代码。另外,不要认为你能很快找到圣杯。我想我能找到它好几年了,但想并不意味着知道。


信号,EA交易,指标

所有这些工具都是为了帮助外汇交易者和订阅者而设计的,如果使用得当,它们中的每一个都是有益的。在这方面,我认为,对每一种工具都有正确的认识是非常重要的,这样才能对整个局势形成正确的看法。最重要的是要明白,任何服务只意味着给你一个工具,而不是一个解决方案。了解价格标签背后的真正含义是非常重要的。就我个人而言,我对这些问题已经有了很长时间的了解,我相信这会对某些人有所帮助。另一个原因是我想分享一下我对开发定制交易系统的想法。

信号

信号在真实账户和演示账户上交易。信号允许监控你自己的交易账户和其他交易者的账户,以及订阅信号和复制其他交易者的交易。就我个人而言,我不建议订阅平均持仓时间少于一分钟的信号。事实是,这样的短线交易不是套利就是剥头皮交易。在这两种情况下,这样的策略对ping都非常敏感,即使10毫秒也足以让一个仓位以错误的方式打开。平均交易持续时间越长,您的交易与原始交易的差异就越小。

下面让我举例说明坏信号和好信号:

安全的信号:


这是安全的,因为净值线是尽可能接近余额线的,意味着它的特点是没有持久的交易,仓位是轮流打开和关闭的,没有马丁格尔和网格。很明显,信号是稳定的。它非常类似于一条直线,其特点是存款的负担相对较小,盈利交易的比例较高。

下一个信号:


信号也很好,但大的绿色向下峰值表明系统使用了马丁格尔。测试开始时出现了几个峰值,这些峰值可能会破坏所获得的全部利润。不能保证这种情况不会再次发生。结果可能是整个账户存款都会消失。尽管如此,市场上有很多信号可供选择,你可以找到相当好的样本。

EA 交易

没有人会在市场上发布一个稳定和盈利的EA,除非考虑到出售这个EA将带来比开发者使用它更多的利润。另一种可能的选择是,开发者对系统不太确定,不想冒险投入大量资金。相反,他或她用小型基金交易,以降低风险。为了购买一个EA交易,你将需要一大笔资金。例如,如果你租了一个EA交易,初始存款100美元,取得了200美元收益,但是租金用了200美元,那么你还是什么都没有得到。

预计利润应至少比EA价格高出几倍。此外,请记住,没有人能保证EA的业绩在必要的时期内保持盈利。多货币性质是EA的另一个优势。如果它至少在几个货币对上起作用,那么它应用了市场物理学,大大增加了获利的机会。作为一个例子,我将以实际测试的形式在表格中展示我最近一个系统的成绩。

首先,我将展示关于主要货币对过去10年历史的通用EA测试表:


所有这些结果都是通过使用单个设置文件实现的,没有对任何特定货币对进行调整,也没有进行优化。

以下是表中提供的选项之一:


此选项确认这不仅仅是一个随机表。此外,它还演示了使用自动交易系统可以实现的结果。在市场上很难找到这样的EA,所以我决定展示我自己的结果。这个EA还没有出现在我的产品中。在交易中使用了固定的1手交易量。

指标

我对指标的态度大多是消极的。我相信,它们更适合用于手工交易。虽然可以将它们集成到一个代码中,但它们的工作速度非常慢,而且通常表示价格略有变化。在报价窗口中绘制指示时,这是很自然的。对于单独窗口中的指标,它们大多计算一个函数或执行循环计算,即后面的数值是从以前的数值计算出来的。在这两种情况下,这些工具的效率都受到严重限制。

你不应该指望一个指标交叉的形式的信号。这只是一个心理时刻,仅此而已。我认为最有用的指标是检测市场几何结构(模式)的指标。它们应该有声音信号。不管怎样,交易者应该始终做出关于遵循或不遵循某个模式的最终决定。

如果有可能开发一个能够正确检测入口点的指标,那么基于它的EA早就创建了。我测试的所有指标(包括我开发的指标)都没有显示入场点,基于它们的EA结果是无用的。它们主要是作为澄清的辅助工具,老实说,也可以用来安抚交易者的情绪,交易者希望对入场和退出都保持信心。

下面是我的指标示例:


该指标说明了一种非标准做法。它定义简单的数学函数,在图表上查找它们,并在检测到它们时通知您。同样,这个指标只是一个工具,旨在节省您的时间。这些指标在人工交易中可能很有用,但最终决定权总是你自己。

在简要介绍了投资和交易的其他特性之后,现在是时候从数学的角度来研究测试和开发,以获得更深入的理解。

优化搜索背后的数学

如果开发的最终目标是获得满足我们需求的n个系统,那么第一个问题是系统应该有多复杂才能最大限度地提高我们的工作效率?这里,效率是指初始原型在我定义的原型标准范围内的概率。换言之,原型应该在初始阶段就已经提供了某些参数,同时工作于某一个或几个货币对。就我个人而言,我一直认为多货币性是一个强制性的标准。因此,这一切归结为某种依赖性,这种依赖性无法可靠地确定,但可以通过实验粗略地探讨:

  • Ps=Ps(L)

其中,L 工作代码的行数。换句话说,所创建系统的第一个参数在可接受范围内的概率直接取决于我们编写的代码量。您可能认为系统中的代码行数越多越好,但情况并非总是如此。请记住:

  • T=K*L

代码行数越多,完成开发所需的时间就越多。但我们最优先考虑的不是代码的行数,而是它们的效率以及我们在一个时间单位内可以开发多少个工作系统。此参数影响所有其他参数:

  • E= Ps/T
  • E --> Max

换句话说,我们应该尽可能地使E参数最大化。主要有两种方法:提高市场知识的质量和寻找最佳的原型代码大小。定义原型的大小,以及市场知识和应用它的能力提供了一定的E0,它可以作为你工作效率的一个指标。此外,我们还得到了开发一个系统所需的平均时间。

开发之后总是要进行测试和修改。这些过程也有其自身的概率,在我们的例子中,是成功修改+优化的概率。它们不可分割。未经测试,不可能进行修正和修改。因此,这一切都归结为“测试-分析-修改”的几个重复周期。最终,我们要么抛弃这个系统,要么改进它,并将它留给自己应用或销售。

另外,请记住,优化您的开发和初始测试方法并不能保证您至少会找到一个这样的系统。您所能指望的就是获得期望结果的最大机会,并且您需要知道您有多少时间用于开发和初始测试。利用伯努利方程,我们可以得出: 

  • Pa=Sum(m0...m...n)[C(n,m)*Pow(Ps ,m)*Pow(1-Ps ,n-m)]
  • m0 - 满意原型的最小数量
  • Pa — 从“n”开发周期中获得至少“m0”或更多的概率
  • n — 搜索工作原型的最大可用循环数

我使用公式中的和,因为我也对那些令人满意的原型数量超过我需要的选项感到满意。我稍后将提供的等式应用了相同的思想。

千万不要高估你的能力,另外,不要期望有很多好的原型。即使你找到了他们,也没人能保证它们会按照你想要的方式工作。你可以依靠许多简单的原型,也可以依靠更复杂的原型,选择权在你。我依赖简单的。

在修改和测试的同时,还有一些参数可以而且应该最大化。我将从在测试方案中找到可接受选项的概率开始。从最初选择的原型中寻找可接受结果的整个过程最终合并到一个测试修改优化过程中。这是一个可以用伯努利方程描述的间歇过程:

  • Pf= Sum(1...m...n)[C(n,m)*Pow(Po,m)*Pow(1-Po,n-m)]
  • Po — 在迭代过程中获得可接受结果的概率
  • n — 最大可用搜索周期数

我假设在寻找可接受的结果时,我们至少需要一个令人满意的事件。换句话说,我们有一定的时间进行测试和修改,最终转化为可用的迭代次数(测试或修改),以便在原型中找到比初始结果更多的东西,这个结果应该适合我们。获取Pf不是强制性的,因为合理使用时间更重要:

  • Ef= Pf/m
  • Ef--> Max

在现阶段,重要的是保持编辑和测试之间的平衡,并确定某个参数如何影响最终结果,以及是否有任何意义的进一步测试。测试和改进总是需要大致相同的时间,所以“m”是一系列测试,你应该用自己的直觉或经验来定义。这里没有具体的指导方针或建议。当然,遵循标准算法是可能的,在标准算法中,你需要“n”个循环并看到结果,但在许多情况下,可以减少迭代次数,例如,在初始阶段放弃系统,或者相反,决定它是有效和灵活的。我总是坚持这种方法,因为它节省时间。时间是我们拥有的最宝贵的东西。基于前面的发现,我们可以创建一个更完整和通用的开发效率度量,考虑到您使用的是“m”测试,而不是平均使用完整的“n”测试:

  • ET= ( Ps*Pf)/(TSumm)
  • TSumm = T + m*Tm
  • Tm — 平均周期时间 (测试-修改-优化)

由于Pf和Ps概率的事件是不一致的,我们有权比较一个新的事件空间,其中一个新的事件正在寻找原型。比较之后是一个成功的改进,基本上意味着一个可接受的交易系统已经找到。因此,我们将概率相乘,得到给定事件的概率。

但我们也知道,每个检测到的选项都需要“T”原型开发时间和“m*Tm”改进时间。与一个完整的开发周期相关的成功概率越大,该方法就越有效。在许多方面,最终时间取决于测试时间。起初,我开发的是基于tick的机器人,但我建议大家开发基于柱形的原型。首先,您将加快您的测试几倍,其次,您将远离报价点,不会浪费时间在测试真实报价点会大大加快您的开发。现在是时候分析最重要的策略标准了。

主要策略参数

有一定的定量值来评价战略的质量,反映了战略的某些参数。有些值比其他值更重要,但一般来说,它们能够说明战略的一切。有以下定量策略参数:

  • Expected payoff (预期收益)
  • Profit factor(利润因子)
  • Absolute drawdown(绝对回撤)
  • Relative drawdown(相对回撤)
  • Maximum drawdown(最大回撤)
  • Recovery factor(恢复因子)
  • Percentage of profitable trades(获利交易百分比)

Expected payoff (预期收益)

预期收益是EA或人工交易系统中最重要的参数。如果你没有合理的预期回报,你就没有成功的机会,除非你运气好。预期收益可以分为两种类型:按点数和按存款货币。

  • M_Points=( Sum(1,n)(PrPoints[i]) - Sum(1,n)(LsPoints[i]) )/n
  • M=( Sum(1,n)(Pr[i]) - Sum(1,n)(Ls[i]) )/n

其中:

  • PrPoints[i] — 如果第i笔交易是盈利的,那么这是以点数表示的盈利额
  • LsPoints[i] — 如果第1笔交易是亏损的,那么这是以点数表示的亏损额
  • Pr[i] — 如果第i笔交易是获利的,则这是以存款货币表示的利润额
  • Ls[i] — 如果第i笔交易是亏损的,那么这是以存款货币表示的亏损额
  • n — 交易数量

我在这里要描述的所有值,并不是100%准确地描述一个策略,因为,为了准确地确定它们,需要n趋于无穷大。但是,即使是中等数量的交易,所有这些值都可以告诉我们很多关于策略的信息,并且具有非常高的可靠性。

为保证策略的盈利能力,这个值应大于零。同时,我们要记住,点差、佣金和隔夜息减少了部分利润。

  • PrPoints[i]=PrPoints_0[i] - (SpreadOpen[i]/2 + SpreadClose[i]/2) - (OrderComission[i]/OrderLots[i])/TickValue + (OrderSwap[i]/OrderLots[i])/TickValue
  • Pr[i]=Pr_0[i] - (SpreadOpen[i]/2 + SpreadClose[i]/2)*OrderLots[i])*TickValue - OrderComission[i] + OrderSwap[i]

这里需要特别提及TickValue,这是我们使用1手交易量时,1个点的存款钱数。此值对于所有货币对都不同,因此取最小价格变化产生整数值。事实上,这是策略中最重要的参数,因为点差本质上是经纪商预期收益的一部分。因此,我们要高度重视。如果预期收益仅略高于点差,则所有其他策略参数都无关紧要。

Profit factor(利润因子)

利润因子是你战略中第二重要的参数。如果你已经获得了一个好的预期收益,这是你应该关注的下一个参数,因为它反映了你的信号质量,或者换句话说,预测质量。交易的总利润越大,总损失越小,参数值就越大。

  • PrFactor = Sum(1,n)(Pr[i]) / Sum(1,n)(Ls[i])

如我们所见,如果方程的分母变为零,那么利润系数就无法计算,或者,形式上,可以假设分母从论点的正部分趋于零时,这就是极限。极限是正无穷大。这意味着,如果没有亏损的交易,该指标将变为无穷大;如果没有获利的交易,该指标将变为0。所以,这个值的范围是 [0,+无穷],平均值是 1. 凡是超过它的都是有利可图的,凡是少于它的都是亏损的。

接下来的两个参数反映了相同的情况,但方式略有不同。

Maximum drawdown(最大回撤)

如果我们假设先有一个高点,那么最大回撤就是相邻两个净值高点和低点之间的最大差值。如果我们将峰值数组价格设置为PriceExtremum[i],则最大回撤为:

  • MaximumDrawdown = MaxOf( PriceExtremum[i+1]- PriceExtremum[i] )

如果你有两个或两个以上的策略具有相同的最终利润,选择那个最大回撤最小的。下一个参数反映了无需进行此假设:

Recovery factor(恢复因子)

此参数与上一个参数几乎相同,只是利润存在于公式中:

  • RecoveryFactor = TotalProfit/MaximumDrawdown

我相信,这个参数的信息量更大。恢复因子越高,策略越稳定。例如,参数越高,策略就越适合使用马丁格尔,因为损失的顺序变得越小,我们可以开始增加手数,以防损失,而不用担心失去存款。

Percentage of profitable trades(获利交易百分比)

此参数仅在平均获利交易的价值非常接近或等于平均亏损交易的价值时才相关。我们习惯了50%的水平,所有超过它的值都表明该策略是盈利的,否则就是亏损的。个人推荐使用 60%. 我相信,这个水平是非常重要的,特别是对于手动交易系统,因为这个参数对任何交易者都有很大的影响。但这个参数并没有揭示利润因素,此外,此参数并不总是提供信息。

  • WinPercent= (100*NProfit)/(NProfit + NLoss)

Absolute drawdown(绝对回撤)

如果您的策略意味着您的存款承受沉重的负担,或者平衡曲线不类似于直线,而是一个混乱的混乱,只有轻微的利润倾向,或者至少在未来有一些先决条件的情况下,这个参数是很重要的。在某些人看来,绝对回撤能够表明最低存款额,使我们能够从破产中节省资金,但这是一种错觉,我不建议任何人坚持这个想法。该值等于测试或交易段内的起始余额和余额图上的底点之间的差值:

  • AbsoluteDrawdown = StartBalance - MinimumBalance
Relative drawdown(相对回撤)

我认为,这个参数比一个交易系统的盈利能力或稳定性信息更丰富,因为它考虑了总利润。最大回撤和恢复系数之间的差值大致相同:

  • 100*AbsoluteDrawdown/EndProfit

这个值是以百分比来衡量的,它显示了绝对回撤对最终利润的影响程度。这一因子的信息价值也非常有限。这个值越小,策略就越好。

其它策略参数

以下是一些不太重要的策略参数,然而,它们不应被低估。

  • Maximum profit(最大利润)
  • Maximum loss (最大亏损)
  • Average profit (平均利润)
  • Average loss (平均亏损)
  • Maximum consecutive wins (最大连续获胜)
  • Maximum consecutive losses (最大连续亏损)
  • Average consecutive wins (平均连续获胜)
  • Average consecutive losses (平均连续亏损)

Maximum profit(最大利润)

利润最大的交易。此参数可以是存款货币或点数。这完全取决于我们为什么需要这个参数。它的重要性相当抽象,下一个参数也是如此。

Maximum loss (最大亏损)

最大亏损的交易。

Average profit (平均利润)

所有交易的利润之和除以交易数量。仅当预期收益和利润系数为正时,此参数才提供信息。

  • MiddleProfit= Sum(Pr[i])/n;

Average loss (平均亏损)

所有交易的损失总额除以交易数量:

  • MiddleLoss= Sum(Ls[i])/n;

Maximum consecutive wins (最大连续获胜)

这个参数非常有用,因为如果它受到上述条件的限制,那么它非常适合使用反马丁格尔。连续赢的次数越少,反马丁格尔的效果越好。

Maximum consecutive losses (最大连续亏损)

与前面的参数不同,限制当前参数允许使用正马丁格尔。如果你设法限制了任何信号的这个值,那么你就可以使用马丁格尔快速而安全地赚钱,而不用担心丢失存款。

Average consecutive wins (平均连续获胜)

该参数几乎与成功交易的百分比相同。事实上,它以略有不同的方式反映了交易系统几乎相同的指标。

  • MiddleWins= Sum(Wins[i])/n

其中 Wins[i] 是指定半波的长度,如果我们将整个余额图分为上升和下降两部分,突出显示上升部分,并计算其中的交易数量,以及上升半程的数量(n),就可以计算参数本身,它只不过是某些半波的算术平均值。

Average consecutive losses (平均连续亏损)

参数同上,唯一的区别是它考虑了负半波:

  • MiddleLosses= Sum(Loss[i])/n

我的额外增加项

我相信,还有一个参数可以提供更完整的策略评估:

  • Linearity factor (线性系数)

它反映了余额曲线与连接余额图起点和终点的直线之间的偏差。在进行固定手数交易时,余额图越像一条直线,系统就越好。这给了未来更高的高效率的机会。此参数在进行固定手数交易时特别有用。市场波动是不断变化的,这意味着平均烛形大小(整体市场动态)也在变化。如果我们理解了这一点,那么我们就可以说,为什么某些策略的预期收益在测试结束时会减少,或者相反,图是非常弯曲的,不能保证斜率角以及预期收益会保持稳定。

  • LinearFactor = MaxDeviation/EndBalance
  • MaxDeviaton = Max(MathAbs(Balance[i]-AverageLine))
  • AverageLine=StartBalance+K*i
  • K=(EndBalance-StartBalance)/n
  • n - 测试中的交易数量

MetaTrader 4和MetaTrader 5策略测试程序中缺少此类参数的原因非常简单。为了计算这些参数,您总是需要运行两次,因为它们具有最终利润。参数越低,策略越好。如果你愿意,你可以用%来衡量。

下图提供了一些解释:


在测试或审查系统时,需要关注哪些指标?

我相信,只有几个基本的系统参数值得关注:

  • Expected payoff in points (预期收益点数)
  • Profit factor (利润因子)或者它的同等替代
  • Maximum drawdown(最大回撤)
  • Maximum consecutive losses (最大连续亏损)
  • Linearity factor (线性系数)

其他参数很难用作系统或市场的附加信息。无论如何,我还没有找到能够应用它们的算法,甚至没有使用它们的先决条件。我相信,把看上去简单得多的事情复杂化是没有意义的。这会带来误解,分散人们对重要事情的注意力。

我将尽可能简单地描述如何选择这些标准:

为了使该策略有利可图,有必要提供以点数为单位的预期收益,以超过以点数为单位的点差+佣金+隔夜息。预期收益最好是平均点差+佣金+隔夜息的两倍。然后补偿损失,得到大于等于参数模的利润。

在我们确信策略的盈利能力之后,我们应该注意预测质量,即利润因子。这个指标超过1越多,预测质量越好。预测质量越好,参数对其余所有参数的影响就越大。如果可以忽略预期收益,利润因子将是最重要的参数。它直接或间接地影响所有其他参数。

在获得适当的盈利能力后,是时候考虑最大的回撤了。此参数定义在当前存款阶段确保最大盈利能力所需的最低存款。根据最大回撤,我们可以选择初始存款,以确保最大的年利润率,而不必担心失去我们的存款。如果我们确定最大连续损失或最大回撤有极值,就有可能在信号中应用马丁格尔而不必担心停损出局。

线性因子确保这是一个规律性的结果,而不是一个随机的结果。图形越像一条直线,这就越可能是一个真正的规律性或市场特征。

测试时间也是被低估的参数之一。测试段越长,盈利结果越相似,系统反映全局模式的概率就越高,而不是某个随机结果。

余额和净值

对于那些看不到这些值之间差异的初学者来说,这个主题非常重要。净值是当前的浮动利润,而余额是当前的固定利润。交易者通常关注的是余额线,而不是净值。在良好的交易系统中,净值位于尽可能靠近余额线的位置。这表明该策略不使用网格、马丁格尔或金字塔。我并不是说这些方法不起作用,但它们需要一个好的信号。我在以前的文章中对此进行了详细阐述。在 MetaTrader 5 策略测试器和 MQL5 网站中,余额线显示为深蓝色,而净值线显示为绿色。净值线越接近余额线,如果两条线都上升,策略就越安全。 

真实交易系统参数和可行性

我考虑这个问题是基于我的实践和我开发的实际交易系统。我想保护你不会在那些只显示一张漂亮的图片而别无其它的信号或 EA 上投资,以我微薄的经验来看,经过不到10年历史测试的系统最终出现亏损的风险会增加。如果一个测试段是两年或更短,这一点就更为正确。

我的经验告诉我,在整个历史过程中,能够盈利交易的系统大约有5-50点的预期收益,而利润因子在1.05-3.0之间。我真的很喜欢M5时间段,因为我创建的所有功能系统都是在这个时间段上工作的,如果需要,您也可以在M1上运行它们。这也是一个非常好的图表,可能是因为它包含了每单位时间内数据量最高的最小柱形图(High[i]、Low[i]、Close[i]、Open[i])。这些柱上的点实际上是保存的真正的报价点。图表周期越短,蜡烛中保存的实际报价点就越多。对于许多系统,在测试器中检验系统时,这一点非常重要,除非您在测试中使用真正的报价点。

就我个人而言,我从不使用真实报价点。我所有的EA都是基于柱形关闭的,我不需要担心人工生成的报价点,因为柱形关闭总是一个真正的报价点。我的系统的平均利润系数约为1.2,而预期收益约为15点,因为我使用的时间框架相当小。要找到在高时间框架内工作的东西要困难得多。你可以提高预期收益,但不能提高利润因子。

许多卖家声称EA优化是最重要的,在重新优化之前指出一个特定的预测时间。不要被这些说法所愚弄,因为用任何方法都无法预测EA未来的工作时间。我也建议你忘记“优化”这个词,直到你学会如何使用这个工具。你可以优化任何东西来获得想要的结果,但很可能是随机性的结果。优化需要彻底的方法。我几乎不用这个工具来手动检查一切。人脑是一个比简单的参数枚举更有效的系统。

如何正确测试系统

我使用以下方法来测试自己和他人的系统(尽管在99%的情况下我测试自己的系统):

对于第一次测试,我总是从当前日期中选择最近一年,或者更多。如果我喜欢测试结果,我会多做几个时间段。请记住,综合分析需要至少10年的时间,20年的时间段更好。

如果前几年和后几年都不错的话,可以采取一些更接近中间的部分。如果结果也可以接受,测试全部10年,并评估余额曲线和净值。如果它们像一条直线,那么这个系统就值得关注。这种方法有一个单一的目标-节省时间并最大限度地提高生成系统的质量。

在进行分析之后,是时候确定进一步的行动了。为了进一步增强对该系统的信心,应该在其他几种货币对上进行测试。如果它在那里至少保留了一部分效果,那么它是基于市场物理学的,可以改进。逐个检查主要系统参数并定义它们对结果的影响。在大多数情况下,一个特定参数的影响几乎完全独立于另一个参数。

在达到最佳性能之后,是时候在演示帐户上测试系统了。如果系统在演示帐户上显示了一个可接受的结果,那么就可以在真实帐户上进行尝试。使用柱形的另一个优点是,只要经纪商有能力,演示帐户上的结果与真实帐户上的结果没有差别。我个人推荐 Alp***. 我相信,你能插入缺失的字母。这个经纪商的历史数据没有被歪曲。我不建议测试报价点机器人,因为那里没有保证。仅使用基于柱形开启或关闭的EA。

柱形参数

为了正确理解报价,我们需要定义柱形图或烛形,以及它包含的数据,以及根据柱形图或烛形的外观可以获得哪些数据。柱形(烛形)是一个固定长度的报价历史段,它不保存所有报价,而是保存Open(开盘价)、Close(收盘价)、High(最高价)和Low(最低价)报价,以及更改为 datetime 类型的柱的开启时间。这是自1970年1月1日以来经过的秒数,柱内有六个值:

  • Close[]
  • Open[]
  • Low[]
  • High[]
  • Time[]
  • Volume[]

前四个值保存了柱形的四个报价点(tick),后面是柱开启的时间和交易量。交易量是柱形中的报价点数量。价格数据是最重要的,但是不要忘记时间和交易量。如果你能正确处理这些数据,你就能找到好的信号。柱和烛形的意思是一样的。唯一的区别是相同值的图形表示:


有各种各样的方法分析柱和烛形,我不建议过于严格地应用它们,因为这只是图形数据。它可能只有与指标和其他分析方法结合起来才有帮助。

编写和测试一个简单的基于交易量的EA

让我们考虑开发一个EA,使用交易量和时间区间作为额外的市场数据和交易限制。成交量激增使我们能够发现市场参与者做出的重要决定。限制EA服务器操作时间的能力允许检测交易量非常重要的区域。让我们编写EA并执行几个测试和全面分析,以得出关于系统的结论。系统本身只需要深入研究市场参数并研究它们之间的关系。该系统主要用于显示每个柱的参数都有其自身的权重,并有助于系统的整体质量。EA本身附在文章中,因此每个人都可以在必要时使用和修改它。

让我们从熟知的用于操作仓位的开发库开始:

#include <Trade\PositionInfo.mqh>
#include <Trade\Trade.mqh>
CPositionInfo  m_position=CPositionInfo();// trade position object
CTrade         m_trade=CTrade();          // trading object

它们主要用于简化代码,我相信,我不需要解释他们的操作原理,这个网站有很多关于这方面的资料。

接下来,定义能够切换操作模式的编号列表:

enum MODE_CALCULATE
   {
   MODE_1=0,
   MODE_2=1,
   MODE_3=2,
   MODE_4=3
   };

这样做的目的是期望有几种模式来确定最有效的市场等式。一开始,关于我们想利用什么物理性质应该有一些一般性的想法,但是我们不知道哪个方程对我们的情况最有效。在当前的EA中,我实现了四个等式变体。我们将看看,哪种选择以最好的方式描述了市场。产生太多的模式是没有意义的,如果我们的假设是正确的,我们一定会看到这一点。我通常只做不超过四种模式。

接下来,定义输入参数及其目标:

input MODE_CALCULATE MODEE=MODE_1;//Mode
input int TradeHour=0;//Start Trading Hour
input int TradeMinute=1;//Start Trading Minute
input int TradeHourEnd=23;//End Trading Hour
input int TradeMinuteEnd=59;//End Trading Minute

input bool bWriteValuesE=false;//Log
input int CandlesE=50;//Bars To Analyse
input int Signal=200;//Signal Power
input int PercentE=52;//Percent Signals To One Side

input bool bInvert=false;//Trade Invert

input int SLE=3000;//Stop Loss Points
input int TPE=3000;//Take Profit Points
input double Lot=0.01;//Lot

input int MagicF=15670867;//Magic

操作模式之后是具有四个参数的块,这些参数描述了服务器时间区间,在该区间内我们打开头寸(或交易时段)。虽然看起来很奇怪,但这个值定义了很多。接下来是描述最重要系统变量的操作参数块。如有必要,您可以在日志中写入有关被跟踪值的当前状态的数据,以便在切换模式时能够调整输入。

接下来是我们将要分析的市场部分,以柱数为单位。所有在历史上更加久远的烛形不在计算考虑范围之内。接下来是信号强度,每个模式有不同的标度,这就是我们需要日志的原因。最后一个部件是附加的信号控制元件。信号可能很大,但如果不了解信号的主要部分相对于所有信号的百分比,设置其功率是没有意义的。最后一个变量允许我们反转交易,设定止损和获利,以及定义交易量和所有订单的幻数。

添加以下函数,快速方便地计算所有预定义数组的值:

MqlTick LastTick;//the last tick

double High[];
double Low[];
double Close[];
double Open[];
datetime Time[];
long Volume[];

void DimensionAllMQL5Values()//prepare the arrays
   {
   ArrayResize(Time,CandlesE,0);
   ArrayResize(High,CandlesE,0);
   ArrayResize(Close,CandlesE,0);
   ArrayResize(Open,CandlesE,0);   
   ArrayResize(Low,CandlesE,0);
   ArrayResize(Volume,CandlesE,0);
   }

void CalcAllMQL5Values()//recalculate the arrays
   {
   ArraySetAsSeries(High,false);                        
   ArraySetAsSeries(Low,false);                              
   ArraySetAsSeries(Close,false);                        
   ArraySetAsSeries(Open,false);                                 
   ArraySetAsSeries(Time,false); 
   ArraySetAsSeries(Volume,false);                                   
   CopyHigh(_Symbol,_Period,0,CandlesE,High);
   CopyLow(_Symbol,_Period,0,CandlesE,Low);
   CopyClose(_Symbol,_Period,0,CandlesE,Close);
   CopyOpen(_Symbol,_Period,0,CandlesE,Open);
   CopyTime(_Symbol,_Period,0,CandlesE,Time);
   CopyTickVolume(_Symbol,_Period,0,CandlesE,Volume);
   ArraySetAsSeries(High,true);                        
   ArraySetAsSeries(Low,true);
   ArraySetAsSeries(Close,true);                        
   ArraySetAsSeries(Open,true);                                 
   ArraySetAsSeries(Time,true);
   ArraySetAsSeries(Volume,true);
   }

我在前面的文章中已经提到了这些函数,尽管是简略的形式。此外,这里还有LastTick变量,用于存储从服务器到达的最后一个tick的所有参数的值。在访问数组的时候需要它们,就像在 MQL4 中一样。

主要变量和逻辑放在“静态类”中:

class TickBox
   {
   public:
   static int BarsUp;
   static int BarsDown;
   static double PowerUp;
   static double PowerDown;
   static double PercentUp;
   static double PercentDown;
   static double PercentPowerUp;
   static double PercentPowerDown;

   static void CalculateAll(MODE_CALCULATE MODE0)//calculate all the necessary parameters
      {
      BarsUp=0;
      BarsDown=0;
      PercentUp=0.0;
      PercentDown=0.0;
      PowerUp=0.0;
      PowerDown=0.0;
      if ( MODE0 == MODE_1 )
         {
         for ( int i=0; i<CandlesE; i++ )
            {
            if ( Open[i] < Close[i] )
               {
               BarsUp++;
               PowerUp+=(MathAbs(Open[i] - Close[i])/(High[i] - Low[i]))*Volume[i];
               } 
            if ( Open[i] > Close[i] )
               {
               BarsDown++;
               PowerDown+=(MathAbs(Open[i] - Close[i])/(High[i] - Low[i]))*Volume[i];
               } 
            }
         }
         
      if ( MODE0 == MODE_2 )
         {
         for ( int i=0; i<CandlesE; i++ )
            {
            if ( Open[i] < Close[i] )
               {
               BarsUp++;
               PowerUp+=(MathAbs(Open[i] - Close[i])/_Point)*Volume[i];
               } 
            if ( Open[i] > Close[i] )
               {
               BarsDown++;
               PowerDown+=(MathAbs(Open[i] - Close[i])/-_Point)*Volume[i];
               } 
            }
         }
         
      if ( MODE0 == MODE_3 )
         {
         for ( int i=0; i<CandlesE; i++ )
            {
            if ( Open[i] < Close[i] )
               {
               BarsUp++;
               PowerUp+=(double(CandlesE-i)/double(CandlesE))*(MathAbs(Open[i] - Close[i])/_Point)*Volume[i];
               } 
            if ( Open[i] > Close[i] )
               {
               BarsDown++;
               PowerDown+=(double(CandlesE-i)/double(CandlesE))*(MathAbs(Open[i] - Close[i])/_Point)*Volume[i];
               } 
            }
         }
         
      if ( MODE0 == MODE_4 )
         {
         for ( int i=0; i<CandlesE; i++ )
            {
            if ( Open[i] < Close[i] )
               {
               BarsUp++;
               PowerUp+=(double(CandlesE-i)/double(CandlesE))*(MathAbs(Open[i] - Close[i])/(High[i] - Low[i]))*Volume[i];
               } 
            if ( Open[i] > Close[i] )
               {
               BarsDown++;
               PowerDown+=(double(CandlesE-i)/double(CandlesE))*(MathAbs(Open[i] - Close[i])/(High[i] - Low[i]))*Volume[i];
               } 
            }
         }
         
      if ( BarsUp > 0 && BarsDown > 0 )
         {
         PercentUp=(double(BarsUp)/double(BarsUp+BarsDown))*100.0;
         PercentDown=(double(BarsDown)/double(BarsUp+BarsDown))*100.0;
         PercentPowerUp=(double(PowerUp)/double(PowerUp+PowerDown))*100.0;
         PercentPowerDown=(double(PowerDown)/double(PowerUp+PowerDown))*100.0;
         }         
      }
   };
   int TickBox::BarsUp=0;
   int TickBox::BarsDown=0;
   double TickBox::PowerUp=0;
   double TickBox::PowerDown=0;   
   double TickBox::PercentUp=0;
   double TickBox::PercentDown=0;
   double TickBox::PercentPowerUp=0;
   double TickBox::PercentPowerDown=0;

它的所有函数和变量都是静态的。由于我们不需要实例,因此不提供创建实例。整个计算在类中的单个函数中执行。它定义了看涨和看跌柱的数量,以及信号强度的类似分量,这些分量被归纳为一个公共变量。每个模式的四个方程都在这个函数中实现。

前两个方程变量的实现没有衰减到分析区域的远端,后两个是精确的副本,但是它们实现了衰减。在这种情况下,衰减是线性的。您总是可以进行任何其他衰减,但为简单起见,最好先尝试需要最少计算量的衰减。

计算出每个烛形的一个基本项。第一个方程变量认为烛形中的整个交易量是一个方向或另一个方向的信号。此外,它被认为是多么接近最后的运动是唯一的因素。烛形底部和顶部的灯芯相对于最后的移动量越大,交易量权重就越小。如果需要,您可以计算其他比率。

第二个等式变量以点为单位计算烛形的总变化量。符号取决于烛形的运动方向,此外,该值乘以交易量,因此我们认为交易量是一个权重比,表明柱形运动的可靠性。其余的变量计算上升或下降的条的百分比,以及信号本身的百分比。

接下来,描述主体函数,其中我们将打开和关闭仓位:

void Trade()
   {
   SymbolInfoTick(Symbol(),LastTick);
   MqlDateTime tm;
   TimeToStruct(LastTick.time,tm);
   int MinuteEquivalent=tm.hour*60+tm.min;
   int BorderMinuteStartTrade=HourCorrect(TradeHour)*60+MinuteCorrect(TradeMinute);
   int BorderMinuteEndTrade=HourCorrect(TradeHourEnd)*60+MinuteCorrect(TradeMinuteEnd);
   if ( MathAbs(TickBox::BarsUp-TickBox::BarsDown) >= 1.0 && TickBox::PercentPowerUp >= 50.0 )
      {
      if ( !bInvert ) ClosePosition(POSITION_TYPE_BUY);
      else ClosePosition(POSITION_TYPE_SELL);
      }
      
   if ( MathAbs(TickBox::BarsUp-TickBox::BarsDown) >= 1.0 && TickBox::PercentPowerDown >= 50.0 )
      {
      if ( !bInvert ) ClosePosition(POSITION_TYPE_SELL);
      else ClosePosition(POSITION_TYPE_BUY);
      }
      
     if ( BorderMinuteStartTrade > BorderMinuteEndTrade )
        {
        if ( PositionsTotal() == 0 && !(MinuteEquivalent>=BorderMinuteEndTrade && MinuteEquivalent<= BorderMinuteStartTrade) )
           {
           if ( MathAbs(TickBox::BarsUp-TickBox::BarsDown) >= Signal && TickBox::PercentPowerUp >= PercentE )
              {
              if ( !bInvert ) m_trade.Sell(Lot,_Symbol,LastTick.ask,LastTick.ask+double(SLE)*_Point,LastTick.bid-double(TPE)*_Point);
              else m_trade.Buy(Lot,_Symbol,LastTick.ask,LastTick.bid-double(SLE)*_Point,LastTick.ask+double(TPE)*_Point);
              }
      
           if ( MathAbs(TickBox::BarsUp-TickBox::BarsDown) >= Signal && TickBox::PercentPowerDown >= PercentE )
              {
              if ( !bInvert ) m_trade.Buy(Lot,_Symbol,LastTick.ask,LastTick.bid-double(SLE)*_Point,LastTick.ask+double(TPE)*_Point);
              else m_trade.Sell(Lot,_Symbol,LastTick.ask,LastTick.ask+double(SLE)*_Point,LastTick.bid-double(TPE)*_Point);
              }
           }        
        }
     if ( PositionsTotal() == 0 && BorderMinuteStartTrade <= BorderMinuteEndTrade )
        {
        if ( MinuteEquivalent>=BorderMinuteStartTrade && MinuteEquivalent<= BorderMinuteEndTrade )
           {
           if ( MathAbs(TickBox::BarsUp-TickBox::BarsDown) >= Signal && TickBox::PercentPowerUp >= PercentE )
              {
              if ( !bInvert ) m_trade.Sell(Lot,_Symbol,LastTick.ask,LastTick.ask+double(SLE)*_Point,LastTick.bid-double(TPE)*_Point);
              else m_trade.Buy(Lot,_Symbol,LastTick.ask,LastTick.bid-double(SLE)*_Point,LastTick.ask+double(TPE)*_Point);
              }
      
           if ( MathAbs(TickBox::BarsUp-TickBox::BarsDown) >= Signal && TickBox::PercentPowerDown >= PercentE )
              {
              if ( !bInvert ) m_trade.Buy(Lot,_Symbol,LastTick.ask,LastTick.bid-double(SLE)*_Point,LastTick.ask+double(TPE)*_Point);
              else m_trade.Sell(Lot,_Symbol,LastTick.ask,LastTick.ask+double(SLE)*_Point,LastTick.bid-double(TPE)*_Point);
              }
           }        
        }
   }

根据信号强度和以百分比表示的质量打开仓位,如果信号变化至少有一点提示,则关闭仓位。

以下函数仅用于关闭在特定方向打开的仓位,它很简单。

void ClosePosition(ENUM_POSITION_TYPE Direction)//close a position by a symbol
   {
   bool ord;
   ord=PositionSelect(Symbol());
   if ( ord && int(PositionGetInteger(POSITION_MAGIC)) == MagicF  && Direction == ENUM_POSITION_TYPE(PositionGetInteger(POSITION_TYPE)) )
      {
      if(m_position.SelectByIndex(0)) m_trade.PositionClose(m_position.Ticket());          
      }
   }

接下来,让我们描述主要事件和操作柱形的函数:

int OnInit()
  {
  m_trade.SetExpertMagicNumber(MagicF);//set the magic number for positions
  DimensionAllMQL5Values();//prepare the predefined arrays
  return(INIT_SUCCEEDED);
  }

datetime Time0;
datetime TimeX[1];
bool bNewBar()
   {
   CopyTime(_Symbol,_Period,0,1,TimeX);
   if ( Time0 < TimeX[0] )
      {
      if (Time0 != 0)
         {
         Time0=TimeX[0];
         return true;
         }
      else
         {
         Time0=TimeX[0];
         return false;
         }
      }
   else return false;
   }

void OnTick()
  {
  if ( bNewBar())//work by bars
     {
     CalcAllMQL5Values();
     TickBox::CalculateAll(MODEE);
     if (bWriteValuesE)
        {
        Print("% Sit in buy = ",TickBox::PercentUp);
        Print("% Sit in sell = ",TickBox::PercentDown);
        Print("Signal = ",MathAbs(TickBox::BarsDown-TickBox::BarsUp));
        Print("% Resistance = ",TickBox::PercentPowerUp);
        Print("% Support = ",TickBox::PercentPowerDown);        
        Print("***************************************************************************");
        }
     Trade();
     } 
  }

在启动EA时,我们需要准备数组并设置它们的大小,以及用于仓位的幻数。然后,描述预测函数来检测柱形图的变化及其工作所需的变量。之后,描述报价点(tick)事件并重新计算预定义数组的值,然后计算系统参数,如果启用了该功能,则添加块将主系统参数写入日志。当然,如果你想的话,你可以用不同方法来做,最主要的是一切都简单明了。

EA背后的想法是,每个条都促成了当前的状态,记住,用两个三个柱来粗略判断接下来会发生什么是不可能的。每个柱都促成了最终的信号,但由于市场物理学原因,它们的权重不同。我将在下一篇文章中详细讨论这一点。

测试 EA

让我们测试EA并尝试检测有前途的功能。我将从去年开始,使用 EURUSD M5。经过10年的运行模式,以及使用不同的时间段,我发现以下结果是可以接受的。用于测试EA的设置附在下面。按照我自己分析系统的规则,我交替地更改了操作模式和服务器时间间隔。因此,我在大约30-60分钟内检测到以下参数。

以下是去年的测试:


接下来,我测试了10年部分中的第一年:


图形不如在最近的阶段上好,但最终的变化仍然保留。看来这里的一切都正常。

在那之后,我取了片段的中间部分并检查了系统在那里的行为:


正如我们所见,这里也有全局模式的迹象,我们只需测试整个时间间隔,看看它在全局范围内的表现:


这张图远不是完美的,但我们可以看到有效的阶段。我们也可以尝试引入过滤器或进行深度优化。特定工具的选择始终是可选项。如果我们对其他对进行测试,那么结果可能会有所不同,但是经过一定的时间,我们很可能会同时找到几个对的最佳参数。如果我们设法掌握并加强物理学,那么结果会更好。

即使在目前的形式,机器人也产生了可接受的结果。交易很少,但EA在多种货币上都是有效的。事件-最简单的代码可以作为开发想法的强大基础。有些系统甚至可以不用修改就可以使用。

当然,获得多货币能力是很好的,但这是通过更多的工作和积极的修改来实现的。在本文中,我不会这样做,而是将此任务留给读者。不要在系统上花费太多时间,试图从中挤出所有的东西。如果成功了,你会立刻看到。如果结果看不出来,那么最好是下结论,改变思路。有时,纠正某些事情是可能的,但情况并非总是如此。

您仍然可以测试它,但很明显,所有的柱形图参数都以不同的方式影响结果,并且在不同的货币对上有具有不同组合的极端参数值。还有一些极端值同时适用于所有对,但是找到它们需要时间和实践。

结论

不要害怕自己编写和测试代码。另外,不要因为你的系统看起来太简单而气馁(看似简单的系统通常才是真正工作的系统)。系统越复杂,自由度就越大,输入参数和不可预测的选项就越多。尽可能避免代码中的复杂性,并使其尽可能简单。

此外,系统越复杂,测试时间就越长。不要开发完全不基于任何东西的系统。不要相信那些说“我已经完成了30笔交易,现在一切都应该正常了”的人。这是一种不专业的做法。特别是,不要基于某些指标编写系统,作为开发人员,这是最愚蠢的事情。

一般来说,要确保你意识到你准备为开发花费多少时间。你应该有一个目标和一个可接受的时间表。最终,你可以缩小你的目标,使之更现实,但你无论如何应该有一个目标。

我看到很多长期的社区成员在图表上设置了数千个指标,试图看到一些东西,这条路是行不通的。首先,你需要确定你是否想要得到某个结果,或者交易只是你的爱好。我相信,有更多有趣的爱好比多年来看枯燥的报价表。外汇交易是一个黑洞,如果你陷入困境,你就再也回不来了。

全部回复

0/140

量化课程

    移动端课程