内容目录
- 概述
- 部分持仓平仓算法,利用最后一根柱线的走势
- 混合手数变化算法
- 均摊原理
- 交易技术稳固性的直接和间接迹象
- 利用余额和净值波动
- 结束语
概述
作者或公众认为有许多能够盈利的交易技术。 我不会在本文中研究这些技术,因为有各种广泛资源提供关于它们的大量信息。 关于这些方法,我无法提供任何新奇或有趣的东西。 取而代之,我决定撰写本文,作为若干种实用和非标准技术的集合,这些技术在理论上和实践中都很实用。 其中的某些技术您可能很熟悉。 我将尝试覆盖最有趣的方法,并解释为什么它们值得使用。 此外,我将展示这些技术在实战中的适用性。
部分持仓平仓算法,利用最后一根柱线的走势
理论:
当我们开仓,却不能确定价格会往哪个方向移动时,这种方式也许能作为一种实用的交易技术。 智能部分平仓可以弥补点差带来的亏损,甚至可在优化后产生盈利。
我们从下面开始。 假设我们已入场开仓,但不知道在何处离场。 我们从哪个方向入场都没有关系。 无论如何,我们都会在某个时候不得不平仓。 当然,有些玩家可以持有自己的仓位多年。 不过,我们假设机器人应进行密集交易,并提供高频交易。 持仓可以一次性全部离场,或分几个步骤离场,每次部分平仓。 我们知道市场均有横盘结构,这意味着价格趋于返回到当前波浪开始时的一半位置。 换言之,上行走势意味着持续的概率总是小于 50%。 相应地,逆向走势的概率大于 50%。 如果我们整体平仓,我们可能会错过所有波浪,并错失盈利,因为我们不知道未来的波浪幅度。 而部分平仓此刻会有所帮助。 我们能够获得最大的利润,事实上是基于我们不了解未来波浪的性质。
现在我们知道了什么是波浪,并且它们永远不会从市场上消失;我们可以从平仓原则开始,就是说即使入场出错,也应该至少产生一定的利润。 实际上存在这样的机制。
可以有很多种这样的算法,但我只展示其中一种,就是我认为最有效的。 它采用这样的假设,即先前形成的完整烛条走势越大越强,则走势回滚的可能性就越大。 一般来说,此机制的目的是什么? 这很简单。 目的是在减少亏损的同时增加盈利。 脉动实际上也是波浪。 但是这些波浪比通常的经典波浪更有用。 事实是,最接近市场的波浪更为可靠。 脉动越强,持续时间越短,在不久的将来发生回滚的概率越大,且回滚的预期幅度也会越大。 思路是布局该机制,从而当脉动增强时,部分平仓也会更强。 我将在图中展示这一点:
可以利用任何函数来计算需部分平仓的交易量。 我将展示其中的两个。 一个是线性的,第二个是幂:
- { D } - 幂
- { X } - 前一根柱线走势的点数
- { C } - 比例因子
- { Lc(X) = C * Pow(X , D) } - 一个幂函数,计算在当前烛条处需平仓的手数
- { Lc(X) = C * X } - 一个线性函数,计算在当前烛条处需平仓的手数
如果您深入观察,线性函数只是幂函数 D = 1 时的特例,如此我们可以省略它,因为它只是初始思考逻辑的一个示例。 思考开始时总是很简单,但经过深思熟虑之后,我们得到了更多的通用工具。 只是您必须从简单的事情开始。
为了避免直接指定这些系数,然后总担心它们的影响,我们将引入若干个控制参数,可确定这些系数:
- { StartLotsToOnePoint } - 当 X = 1 (输入参数) 时,依据该值平仓
- { PointsForEndLots } - 前一根烛条走势指向盈利方向,则最后持仓平仓速度(输入参数)
- { EndLotsToOnePoint } - 当 X = PointsForEndLots (输入参数)时,依据该值平仓
现在,我们合成一个方程式系统,来计算系数,并得到依据输入参数表达的函数最终形式。 为此目的,应把思路转换为数学表达式:
- { Lc( 1 ) = StartLotsToOnePoint }
- { Lc( PointsForEndLots ) = EndLotsToOnePoint }
如此,我们所有的方程式均已准备就绪。 现在,我们将以扩展形式编写它们,并逐步转换它们来开始求解方程式:
- { C * Pow(1 , D) = StartLotsToOnePoint }
- { C * Pow( PointsForEndLots , D) = EndLotsToOnePoint }
在第一个方程式中,给定任意角度等于自身时为 1,我们就可以立即找到 C:</ s2> </ s0>
- { C = StartLotsToOnePoint }
将第二个方程式的两边均除以 “C”,然后找到方程式两边以“PointsForEndLots” 为底数的对数,我们会得到以下结果:
- { log( PointsForEndLots ) [ Pow( PointsForEndLots , D)] = log( PointsForEndLots ) [ EndLotsToOnePoint / C ] }
考虑到该对数,其底数是相同对数乘以任何幂,我们可以针对所需幂求解方程:
- { D = log( PointsForEndLots ) [ EndLotsToOnePoint / C ] }
我们已发现了第二个未知系数。 但这还不是全部,因为 MQL4 和 MQL5 尚未实现所需任意底数对数的基本函数,而仅有自然算法。 因此,我们需要用自然对数替换对数底数(自然算法是由欧拉数字表达的底数对数)。 语言中缺乏其他对数并无问题,因为任何对数都可以用自然对数来表达。 对于任何稍有了解数学知识的人来说,这是很容易的。 改变了底数之后,我们期望的系数公式如下所示:
- { D = ln( EndLotsToOnePoint / C ) / ln( PointsForEndLots ) }
替换已知的系数 C,得到:
- { D = ln( EndLotsToOnePoint / StartLotsToOnePoint ) / ln( PointsForEndLots ) }
现在,替换函数模板里的两个系数,从而获得最终形式:
- { Lc(X) = StartLotsToOnePoint * Pow( X , ln( EndLotsToOnePoint / StartLotsToOnePoint ) / ln( PointsForEndLots ) ) }
此函数的优点是,角度可以等于 1,也可以大于 1,也可以小于 1。 因此,为了针对任何市场和交易金融产品进行调整,它提供了最大的灵活性。 如果 D = 1,我们得到了一个线性函数。 如果 D > 1,之后调整函数假设:所有波浪均有尺度,且一定振幅的波浪数与振幅成反比(即,如果我们同时计算 M5 和 H1 上的波浪数,那么在相同周期里,H1 上的波浪数减少了 12 倍,这仅仅是因为每小时的烛条数量比 5 分钟的烛条少了 12 倍)。 如果 D < 1,我们期望更高振幅的波浪。 如果 D > 1,我们假设主要是低振幅波浪。
另请注意,您不必采用离散价格序列作为柱线序列 - 您可用即时报价,和任意其它首选的价格区段。 我们在此采用柱线仅仅是因为我们已拥有柱线。
代码:
在代码中,该函数如下所示:
double CalcCloseLots(double orderlots0,double X) { double functionvalue; double correctedlots; if ( X < 0.0 ) return 0.0; functionvalue=StartLotsToOnePoint*MathPow(X ,MathLog(EndLotsToOnePoint/StartLotsToOnePoint)/MathLog(PointsForEndLots)); correctedlots=GetLotAniError(functionvalue); if ( correctedlots > orderlots0 ) return orderlots0; else return correctedlots; }
调整手数的函数,确保手数仅采用正确的数值,以紫色高亮显示(没必要展示其内部代码)。 该函数本身是在绿色高亮显示的运算符下计算的,但它只是更通用函数的一部分,在此处称为:
void PartialCloseType()// close order partially { bool ord; double ValidLot; MqlTick TickS; SymbolInfoTick(_Symbol,TickS); for ( int i=0; i<OrdersTotal(); i++ ) { ord=OrderSelect( i, SELECT_BY_POS, MODE_TRADES ); if ( ord && OrderMagicNumber() == MagicF && OrderSymbol() == _Symbol ) { if ( OrderType() == OP_BUY ) { ValidLot=CalcCloseLots(OrderLots(),(Open[0]-Open[1])/_Point); if ( ValidLot > 0.0 ) ord=OrderClose(OrderTicket(),ValidLot,TickS.bid,MathAbs(SlippageMaxClose),Green); } if ( OrderType() == OP_SELL ) { ValidLot=CalcCloseLots(OrderLots(),(Open[1]-Open[0])/_Point); if ( ValidLot > 0.0 ) ord=OrderClose(OrderTicket(),ValidLot,TickS.ask,MathAbs(SlippageMaxClose),Red); } break; } }
可用 MQL5 编译此代码,因为我们利用了 MT4Orders 函数库。 请注意,这些函数适用于测试。 为了实盘交易,您需要仔细研究并精调它们,同时注意错误和未考虑到的情况。 无论如何,当前版本很适合在策略测试器中进行测试。
测试智能交易系统:
为了演示其操作原理,我为 MetaTrader 4 创建了一个 EA 版本,因为在 MetaTrader 5 中进行测试时,点差可能会掩盖我们想要看到的所有内容。 取而代之的是,我将点差设置为 1,并选择了随机方向开仓:
对于其他货币对,它也能以几乎相同的方式操作。 它也能够在较高的时间帧上工作,但这需要一点耐心,因为需要寻找相应的设置。 不过,测试并不意味着您可以立即在真实帐户中启用该 EA。 它仅是确认该技术在正确运用时会很有用。 无论如何,在盈利这个方面,即使对于这个 EA 来说,也是非常不足的。 但是,结合良好的信号和相应的方法,该方法能够赚取利润。
混合手数变化算法
自最开始市场研究,我就想知道如何将任何系统的利润因子向正方平移。 这意味着,任何不依赖点差的策略,比之其它盈利因子接近一的策略都会获得巨大优势。 为什么确切地需要提高盈利因子? 答案很简单:因为整个系统的回撤,和系统的盈利能力都取决于此变量。 这种技术是存在的,我将向您展示。 但是首先,我们快速遍历一下最著名的那些交易技术,也许利用了很多手法。 以下有许多所谓能提高盈利因子的技术:
- 马丁格尔
- 逆马丁格尔
实际上,沸沸腾腾的观点都归结为两种经常被讨论的方法,但除了失望之外难以带来任何益处。 所有的一切都与一个非常简单的数学原理相关,我在网格和马丁格尔:它们是什么以及如何利用它们?一文中曾讲述过。它并非讲述原理本身,而是所有原理中的共通点。 常见的情况是根据某些条件增减手数。 如果条件与价格形成的性质无关,那么这种系统无疑是难以盈利的。
任何利用手数变化的策略,都有底层的信号。 我们可通过所有订单手数相同来获得它。 这将帮助我们了解底层信号是否与价格形成的性质相关。 在大多数情况下,这样的信号所产生的数学期望为零。 但信号中有一个参数可令我们同时使用正向和逆向马丁格尔。 所需条件是存在大量的低振幅波浪。 如果您能正确运用正向和逆向马丁格尔的组合,则可将它们转变为混合机制,从而把数学期望从零值变为正值。 下图示意了它的外观:
理论:
在此,我们假设有一条特定的初始余额线,在不考虑点差和佣金的情况下,它会始终在初始起始水平附近徘徊,直到有一天您决定停止交易,则最终余额相对于起始水平或正或负,或直到系统慢慢亏损殆尽,资金都浪费在被点差、佣金和隔夜利息。 这意味着任意交易的任何余额线都有几分类似波浪过程,围绕初始余额波动。
有基于此,我们可将整个余额线分为上升和下降区域(该图示意了一个完整波浪的四分之一)。 如果波浪四分之一在增长,则应减少手数(逆马丁格尔); 如果波浪正在下降,则应该增加手数( 马丁格尔)。 无限增加手数的唯一障碍在于任何帐户的最大允许持仓量。 故,即使出现这种情况,保证金也不允许我们开立超额仓位。 因此,手数显然应该在一定的数值走廊内波动,且不应超出其范围。 因此,重要的是波浪的幅度不应太大,最好是较小和不太大的波浪。 如果您仔细查看该示意图和下面的手数变化遮盖面,您将了解到,利用混合变化,与发生波动相对应的线如何获得向上的斜率,这等效于一个正值的数学期望,和盈利因子。
在此情况下如何计算手数? 乍一看,情况似乎并不明朗。 显然,手数可以触及我们的通道边界之一,且永远不会超出。 在这种情况下,我们必须提供一种机制返回到起始手数,以便在平均手数值附近获得稳定波动。 这种系统的明显且唯一的缺点是无法承受大幅波浪。 在低幅走势下,该机制会表现完美。 这个问题可通过一个函数来解决,该函数以这样一种方式来计算下一次的手数,即函数自行将手数推向中间值,若越靠近前一笔平仓订单的手数边界之一,则强烈推荐。 我将在下图中进行演示:
现在,我将利用示意图中的标示来编写适合该任务的函数视图。 但首先,我们要定义输入参数和辅助变量:
控制混合手数变化的输入变量
- { MaxLot } - 最大手数走廊(输入参数)
- { MinLot } - 最小手数走廊(输入参数)
- { LotForMultiplier } - 增加或减少交易量的参考手数
- { ProfitForMultiplier } - 亏损和获利点数,据其增加或减少参考手数
辅助变量
- { MiddleLot = (MaxLot + MinLot)/2 } - 最高和最低手数间的中值
- { h = (MaxLot - MinLot)/2 } - 通道的一半宽度(用于计算)
- { L } - 历史上最后一笔交易的手数
- { X } - 辅助变量
- { OrderProfit , OrderComission , OrderSwap , OrderLots } - 排除了佣金的利润、佣金、计算出的订单隔夜利息、和历史记录中最后一笔订单平仓量(它们均已知)
- { TickSize } - 假设持仓量等于 1 手,且价格朝理想方向移动了 1 个点,持仓递增的利润
- { PointsProfit = ( OrderProfit + OrderComission + OrderSwap ) / ( OrderLots * TickSize ) } - 历史上最后一笔订单的利润,转换为点数
当手数高于或低于中线时的函数
- { L >= MiddleLot ? X = MaxLot - L } - 如果手数处于通道的上部,则 “X” 值是与通道上边界的差值
- { L < MiddleLot ? X = L - MinLot } - 如果手数处于通道的下部,则 “X” 值是与通道下边界的距离
- { L >= MiddleLot & PointsProfit < 0 ? Lo(X) = OrderLots - LotForMultiplier * ( PointsProfit / ProfitForMultiplier ) * ( X / h ) } - 接近通道的上边界时,慢速手数增加
- { L < MiddleLot & PointsProfit >= 0 ? Lo(X) = OrderLots - LotForMultiplier * ( PointsProfit / ProfitForMultiplier ) * ( X / h ) } - 接近通道下边界时,慢速手数减少
- { L >= MiddleLot & PointsProfit >= 0 ? Lo(X) = OrderLots - LotForMultiplier * ( PointsProfit / ProfitForMultiplier ) / ( X / h ) ) } - 加速手数减少
- { L < MiddleLot & PointsProfit < 0 ? Lo(X) = OrderLots - LotForMultiplier * ( PointsProfit / ProfitForMultiplier ) / ( X / h ) } - 加速手数增加
很难理解所有这些关系,因为它们很难阅读。 如果您尝试将所有内容合并到一个连续的函数,那么您将得到一个无比复杂的结构,以至于无法操作。 我将进一步展示代码的样子,如此所有内容都将变得更加清晰。 在此,我展示了 MQL4 样式的代码实现。 如果您利用便捷且著名的 MT4Orders 函数库,则可在 MQL5 中轻松运行该代码:
代码:
double CalcMultiplierLot() { bool ord; double templot=(MaxLot+MinLot)/2.0; for ( int i=OrdersHistoryTotal()-1; i>=0; i-- ) { ord=OrderSelect( i, SELECT_BY_POS, MODE_HISTORY ); if ( ord && OrderSymbol() == CurrentSymbol && OrderMagicNumber() == MagicF ) { double PointsProfit=(OrderProfit()+OrderCommission()+OrderSwap())/(OrderLots()*MarketInfo(CurrentSymbol,MODE_TICKVALUE)); if ( OrderLots() >= (MaxLot+MinLot)/2.0 ) { if ( PointsProfit < 0.0 ) templot=OrderLots()-(LotForMultiplier*(PointsProfit/ProfitForMultiplier))*((MaxLot-OrderLots())/((MaxLot-MinLot)/2.0)); if ( PointsProfit > 0.0 ) templot=OrderLots()-(LotForMultiplier*(PointsProfit/ProfitForMultiplier))/((MaxLot-OrderLots())/((MaxLot-MinLot)/2.0)) ; if ( PointsProfit == 0.0 ) templot=OrderLots(); break; } else { if ( PointsProfit > 0.0 ) templot=OrderLots()-(LotForMultiplier*(PointsProfit/ProfitForMultiplier))*((OrderLots()-MinLot)/((MaxLot-MinLot)/2.0)); if ( PointsProfit < 0.0 ) templot=OrderLots()-(LotForMultiplier*(PointsProfit/ProfitForMultiplier))/((Orderlots()-MinLot)/((MaxLot-MinLot)/2.0)); if ( PointsProfit == 0.0 ) templot=OrderLots(); break; } } } if ( templot <= MinLot ) templot=(MaxLot+MinLot)/2.0; if ( templot >= MaxLot ) templot=(MaxLot+MinLot)/2.0; return templot; }
当您需要控制每笔开单时,该函数库会特别有用。 那么,这就是整个方法。 算法的输入变量以黄色高亮显示。
测试智能交易系统:
该 EA 也适用在 MetaTrader 4,原因与上一个 EA 相同,因为它更容易看到盈利因子的改善:
该智能交易系统里的信号是依次生成的。 买入之后是卖出,卖出之后则是买入。 这样做是为了演示该机制的实质。 在第二个测试中,我们采用固定手数,而在第一个测试中采用的是混合手数变化。 从其可见,只有原信号的余额波动最小的情况下,该系统才能改善盈利因子。 该系统将无法承受大浪。
均摊原理
均摊原理是一种相当有趣的算法。 尽管它像网格和金字塔一样无利可图,但它具有一项非常有趣的功能,可以帮助进行波浪交易。 如果已知走势将持续到某个水平,然后必然要回滚一定的点数,则在这种情况下可以使用马丁格尔均摊。 该技术在马丁格尔中特别有效,因为它可令您减少所需的回滚,同时保持盈利因子大于 1.0。
该原理利用这样的假设:如果价格未如愿朝着我们的持仓方向移动,那么一段时间后,价格无疑会回滚一部分。 这虽非总会发生,但事实上还是有可能,尽管您能够在研究多个品种的波浪性质之后对该算法进行微调。 为了基于这种回调产生盈利,我们需要凭经验对其进行预测或预判。
这也适用于强级别操作,因为当级别被突破后,市场经常会尝试对其进行回踩测试。 如果我们猜对了第一次交易的方向,并且利润达到了期望值,那么我们应平仓,并在看起来更具吸引力的方向上开一笔新仓。 如果价格去往错误方向,则可以通过某些步骤尝试加仓。 在加仓时,我们应当非常小心 - 应该根据预期的回滚,精确完成。 以下是两种可能的示例。
一系列买入订单的示例:
一系列卖出订单的示例:
现在,我将展示如何计算所有订单的盈亏,以及如何根据现有情况,基于所需的回滚来计算下一笔订单的手数。 为了扇骨形持仓的平仓,有必要知道为什么要构建扇骨形,以及哪种条件会令它有用。 我将使用盈利因子。 如果扇骨形为正,则其盈利因子也为正。 若是所有订单的总盈利刚刚变为正数,就把扇骨形平仓是不明智的,因为服务器上的任何价格波动或滑点都可能令盈利向负方向偏移 - 在这种情况下,这种扇骨形似乎毫无用处。 此外,周期的盈利因子为激进的均摊提供了额外调整机会,这一条可在优化自动策略或手动搜索设置时用到。
假设系列中的最大订单数有限制。 然后,假设我们正处于某种未知状态下,此处我们已经开立了 k 笔订单 - 像任何集合或数组一样,它们的索引从 0 开始。 因此,该系列中最后一笔订单的索引为 “k-1”。 当我们开立下一笔订单时,其索引将为 k,订单编号为 “k+1”。 我们将这些思想情况转化为问题的输入数据:
- { MaxOrders } - 系列中允许的最大订单数
- { k = 0 ... 1 ... 2 ... (MaxOrders-1) } - 系列中下一笔订单的索引,如果我们未能从之前的方式法获利,其为我们打算开立的订单
- { ProfitFactorMax } - 允许平仓的盈利因子
- { i = 1 ... 1 ... 2 ... k } - 已经存在的扇骨形订单,和我们要开立的下一笔订单的索引
- { S[i-1] } - 从上一笔订单到当前订单的点数距离
- { X[i-1] } - 从系列的零号订单到当前的点数距离
- { D[k] } - 相对于系列最后一笔订单的开单价,预测所需方向上的回滚
另外,可以说,当我们引用特定订单时,MQL4 和 MQL5 语言会提供以下数据,包含我们计算选项所需的所有数据:
- { P[i] } - 在不排除佣金和隔夜利息的情况下,特定订单的利润
- { С[i] } - 选定订单的佣金
- { S[i] } - 所计算订单每天零点时的滚动隔夜利息
- { L[i] } - 订单交易量
- { Sp[i] } - 开买单时的点差/卖单的当前点差
- { TickSize } - 假设持仓量等于 1 手,并且价格朝期望的方向移动了 1 个点,持仓增加的利润
所有这些数值并非都可在自动交易中实际使用,但它们可以高亮示意计算这些数值的所有可能选项。 上图示意其中一些相关的数值,这些数值可以相对于价格进行描述。 我们从代码中使用的最简单计算方法开始:
如果第一笔订单以正值结果平仓,则无需任何操作。 在我们认为适当的任何时候,我们都可顺着所需方向开新单。 如果价格没有达到目标,就已朝反方向移动,那么我们必须决定价格向亏损方向移动多少点就必须开新单(S[k-one])。 如果价格已达到此水平,那么我们必须决定哪种回滚是合适的 (D [k])。 在那之后,我们必须依据计划的回滚,来判断将要开单的手数;我们得到所需的所有订单的盈利因子大于我们选择的价值 (ProfitFactorMax)。 为此,我们首先需要计算当前持仓将产生哪些利润,以及会产生哪些亏损,最后是它们的合计值。 在编写相应的公式之后,我将在后面解释其目的:
首先,我们引入每笔指定订单的未来利润值。 它等于当前利润加上订单即将盈利的增量,前提是所需的回滚将会发生:
- { j = 0 ... 1 ... 2 ... k-1 }
- { PF[j] = P[j] + С[j] + S[j] + (D[k] * L[j] * TickSize) } - 当所需回滚发生时,指定订单的利润将等于该值
- { PF[k] = { ( D[k] - Spread[k] ) * L[k] * TickSize } - 无法计算此值,因为它包含我们要开仓所需的手数;但是稍后需要用到此表达式
在第一种情况下,P[i] 值是已知的,我们可以利用语言的内置函数来获取它,也可以自行计算。 该计算方法将在本文的末尾附带显示,因为所有内容都可以在标准语言里找到。 出于我们要在系列中开立最后一笔订单,它也需要佣金和隔夜利息,但是这些值只能从以前开立订单中获得。 进而,由于无法断定持仓会否在零点之前移仓,因此无法确定隔夜利息数值。 始终会有不精确的数值。 可以仅选择那些拥有正值隔夜利息的货币对
之后,我们能够利用此预测利润来划分可盈利和亏损的订单,并计算盈利因子:
- { i = 0 ... 1 ... 2 ... k } - 已开单的索引
- { Pr[i] >= 0, Ls[i] >= 0 } - 引入 2 个数组,根据品种接受订单盈亏(“+” 将用于获利,这是计算盈利因子所必需的)
- { PF [i] < 0 ? Pr[i] = 0 & Ls[i] = - PF[i] } - 如果订单利润为负,则将其利润写为 “0”,而利润值应用一个负号写入亏损变量
- { PF [i] > 0 ? Ls[i] = 0 & Pr[i] = PF[i] } - 如果订单获利为正,只需将其写入相应的数组,并在亏损数组中设置零值
数组填充完毕之后,我们可以编写一个公式来计算总盈亏,及其最终结果。
- { SummProfit = Summ[0,k]( PF[i] ) } - 所有获利订单的总利润模块
- { SummLoss = Summ[0,k]( Ls [i] ) } - 所有亏损订单的总亏损模块
现在我们需要编写循环平仓条件:
- { SummLoss > 0 ? SummProfit/SummLoss = ProfitFactorMax }
为了进一步使用该方程式,您需要了解,当扇骨形持仓平仓时,系列中最后一笔订单的利润始终为正。 有基于此,您可以编写:
- { SummProfit = Summ[0,k-1]( PF[j] ) + PF[k] }
将此值代入方程式,并得到以下结果:
- { ( Summ[0,k-1]( PF[j] ) + PF[k] ) / SummLoss = ProfitFactorMax }
现在,通过求解 PF[k] 等式,我们得到:
- { PF[k] = ProfitFactorMax * SummLoss - Summ[0,k-1]( PF[j] ) }
考虑到我们已经有一个 PF[k] 值的公式,我们可在此替换该表达式,并得到:
- { ( D[k] - Spread[k] ) * L[k] * TickSize = ProfitFactorMax * SummLoss - Summ[0,k-1]( PF[j] ) }
现在我们可以相对于 L[k] 求解方程式。 因此,我们最终将获得计算所需开仓量的公式:
- { L[k] = ( ProfitFactorMax * SummLoss - Summ[0,k-1]( PF[j] ) ) / ( ( D[k] - Spread[k] ) * TickSize ) }
这些就是全部了。 现在我们来研究如何在不使用内置函数的情况下计算 P[i] 值。
- { P[i] = ( X[i-1] - Spread[i] ) * L[i] * TickSize + С[j] + S[j] }
计算回滚值
现在我们来看一下回滚计算方法。 我用了两种方式。 方式可以有很多,但我仅提供两种我认为最实用的方式。 回滚是根据价格在错误的方向上走了多少来计算的。 这意味着 D[i] 和 X[i] 可在整个 X 轴的正值方向上以任何函数进行连接:
- { D=D(X) }
- { D[k] = D( X[k-1] ) }
我用两个函数来计算回滚。 The first one is linear. 第二个是幂函数。 第二个函数比较困难,但是更有趣。 此处即为该函数:
- { D = K * X }
- { D = DMin + C * Pow(S , X) }
系数找到后会以高亮显示。 基于这些系数,函数开始发挥不同的作用,并相应地,我们可以根据特定的货币对或时间帧灵活地调整策略。
- { K } - 线性函数的回滚系数(也用作自动系统的输入参数)
- { DMin } - 允许的最小回滚(对于幂函数,D >= DMin)
- { C } - 幂函数伸缩因子
- { S } - 指数的基数
严格来说,可以将 C 添加到度数当中,但我认为以这种形式编写的函数更易读,且易于使用。 另请注意,值 K 和 DMin 是输入参数,因此这些系数对于我们来讲是已知。 目前尚不清楚如何计算剩余的两个系数。 我们现在开始吧。 为了找到 2 个未知值,我们需要至少包含两个方程的一个系统。 系数可以通过求解系统找到。 为了编写此类系统,我们首先需要决定如何控制函数形式。 实际上,我选择了幂函数的当前形式,因为它可以更轻松、更便捷地将回滚平滑降低。 这就是为什么我选择了幂函数。 考虑因素如下:
- { HalfX } - 附加回滚至价格走势的一半变动(控制函数的附加输入参数)
- { D(0) = DMin + K*DMin }
- { D(HalfX) = DMin + K*DMin/2 }
因此,我们获得了所需的方程组,我们将对其进行求解。 换言之,我们把价格走势方向设为与第一笔订单的开单方向相反,其中附加回滚值是开始时的一半。 此附加在开始时具有最大值。 结果就是,我们得到一个函数,它的值不能低于最小回滚,并且当 X 趋于无穷大时,该函数将返回最小回滚。 在数学上,其表述如下:
- { D >= DMin }
- { Lim( X -> +infinity ) = DMin }
现在我们可以开始求解方程组了,但首先我们来重写整个系统:
- { DMin + C * Pow(S , 0) = DMin + K*DMin }
- { DMin + C * Pow(S , HalfX) = DMin + K*DMin/2 }
在第一个方程式中,考虑到任意数字的零次幂都是一这个事实,我们可以立即找到 C。 因此,我们排除了 S 变量。 我们现在要做所有事情的就是求解与 C 变量相关的方程式。
- { С = K * DMin }
现在我们有了 C,我们可以通过简单地替换之前表达式中的 C 变量,来找到剩余的未知 S:
- { Pow(S , HalfX) = 0.5 }
为了抵消度数,我们应该将方程的两个部分都升至 HalfX 的度数倒数。 结果就是,我们得到以下简单表达式,它就是所需的系数:
- { S = Pow(0.5 , 1/HalfX) }
现在我们可以通过代入系数来编写我们自己的幂函数。 我们实现该策略所需的一切均已讲述:
- { D(X) = DMin + K * DMin * Pow( Pow(0.5 , 1/HalfX) , X ) }
这是该函数在代码中的样子:
代码:
double D(double D0,double K0,double H0,double X) { return D0+(D0*K0)*MathPow(MathPow(0.5,1.0/H0),X); }
此处是一些更重要的函数,在 EA 中会用到这些函数来测试理论。 第一个函数判断价格到系列中最近一笔持单的点数距离:
double CalculateTranslation() { bool ord; bool bStartDirection; bool bFind; double ExtremumPrice=0.0; for ( int i=0; i<OrdersTotal(); i++ ) { ord=OrderSelect( i, SELECT_BY_POS, MODE_TRADES ); if ( ord && OrderMagicNumber() == MagicF && OrderSymbol() == Symbol() ) { if ( OrderType() == OP_SELL ) bStartDirection=false; if ( OrderType() == OP_BUY ) bStartDirection=true; ExtremumPrice=OrderOpenPrice(); bFind=true; break; } } for ( int i=0; i<OrdersTotal(); i++ ) { ord=OrderSelect( i, SELECT_BY_POS, MODE_TRADES ); if ( ord && OrderMagicNumber() == MagicF && OrderSymbol() == Symbol() ) { if ( OrderType() == OP_SELL && OrderOpenPrice() > ExtremumPrice ) ExtremumPrice=OrderOpenPrice(); if ( OrderType() == OP_BUY && OrderOpenPrice() < ExtremumPrice ) ExtremumPrice=OrderOpenPrice(); } } if ( bFind ) { if ( bStartDirection ) return (ExtremumPrice-Close[0])/_Point; else return (Close[0]-ExtremumPrice)/_Point; } else return -1.0; }
我们需要该函数,以便系列中的订单之间遵循所需的步长。 此步长将是固定的。 不要忘记在 MQL5 中没有实现 “Close[]” 数组,故我们需要实现它,就像我在之前的文章中所展示的那样。 我认为这个步长很明确。
为了计算当前的 X 和 D,我们将利用以下函数,该函数没有返回值,如同所有其他函数一样。 它将把结果写入全局变量(其好处是最大程度地减少对订单的访问,并避免不必要地调用函数操控历史记录:
double Xx;//shift of X double Dd;//desired rollback of D void CalcXD()//calculate current X and D { bool ord; bool bStartDirection=false; bool bFind=false; double ExtremumPrice=0.0; for ( int i=0; i<OrdersTotal(); i++ ) { ord=OrderSelect( i, SELECT_BY_POS, MODE_TRADES ); if ( ord && OrderMagicNumber() == MagicF && OrderSymbol() == Symbol() ) { if ( OrderType() == OP_SELL ) bStartDirection=false; if ( OrderType() == OP_BUY ) bStartDirection=true; ExtremumPrice=OrderOpenPrice(); bFind=true; break; } } for ( int i=0; i<OrdersTotal(); i++ ) { ord=OrderSelect( i, SELECT_BY_POS, MODE_TRADES ); if ( OrderMagicNumber() == MagicF && OrderSymbol() == Symbol() ) { if ( OrderType() == OP_SELL && OrderOpenPrice() < ExtremumPrice ) ExtremumPrice=OrderOpenPrice(); if ( OrderType() == OP_BUY && OrderOpenPrice() > ExtremumPrice ) ExtremumPrice=OrderOpenPrice(); } } Xx=0.0; Dd=0.0; if ( bFind ) { if ( !bStartDirection ) Xx=(Close[0]-ExtremumPrice)/_Point; if ( bStartDirection ) Xx=(ExtremumPrice-Close[0])/_Point; if ( MODEE==MODE_SINGULARITY ) Dd=D(DE,KE,XE,Xx); else Dd=Xx*KE; } }
该代码与 MT4Orders 函数库完全兼容,因此可以在 MQL5 中进行编译。 这也涉及以后要讨论的函数。 算法的输入变量以黄色高亮显示。
为了计算当前和预期利润,我们将使用三个变量:
double TotalProfitPoints=0.0; double TotalProfit=0; double TotalLoss=0;
返回值将被保存到这些变量当中,而函数本身没有返回值 - 这样我们就能避免每次都执行订单迭代,这会减慢代码操作的速度。
接下来的两个函数,其一将用于平仓条件,其二将用于计算当前持仓的预期利润:
void CalcLP()//calculate losses and profits of all open orders { bool ord; double TempProfit=0.0; TotalProfit=0.0; TotalLoss=0.0; TotalProfitPoints=0.0; for ( int i=0; i<OrdersTotal(); i++ ) { ord=OrderSelect( i, SELECT_BY_POS, MODE_TRADES ); if ( ord && OrderMagicNumber() == MagicF && OrderSymbol() == Symbol() ) { TempProfit=OrderProfit()+OrderCommission()+OrderSwap(); TotalProfitPoints+=(OrderProfit()+OrderCommission()+OrderSwap())/(OrderLots()*SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_VALUE)); if ( TempProfit >= 0.0 ) TotalProfit+=TempProfit; else TotalLoss-=TempProfit; } } } void CalcLPFuture()//calculate losses and profits of all existing orders in the future { bool ord; double TempProfit=0; TotalProfit=0; TotalLoss=0; for ( int i=0; i<OrdersTotal(); i++ ) { ord=OrderSelect( i, SELECT_BY_POS, MODE_TRADES ); if ( ord && OrderMagicNumber() == MagicF && OrderSymbol() == Symbol() ) { TempProfit=OrderProfit()+OrderCommission()+OrderSwap()+(OrderLots()*SymbolInfoDouble(Symbol(),SYMBOL_TRADE_TICK_VALUE)*Dd); if ( TempProfit >= 0.0 ) TotalProfit+=TempProfit; else TotalLoss-=TempProfit; } } }
另外,此处是订单系列平单条件的预测函数:
bool bClose() { CalcLP(); if ( TotalLoss != 0.0 && TotalProfit/TotalLoss >= ProfitFactorMin ) return true; if ( TotalLoss == 0.0 && TotalProfitPoints >= DE*KE ) return true; return false; }
测试智能交易系统:
我已用上述原理为 MetaTrader 5 创建了一款智能交易系统,因为这种方法若能正确应用,可以克服几乎所有的点差、佣金和隔夜利息。 此处是 EA 运用均摊进行测试的结果:
不过,请注意,这仅是一种交易技术,运用时必须非常谨慎。 尽管如此,它非常灵活,能够与多种策略结合使用。 再次提醒,请小心。
交易技术稳固性的直接和间接迹象
在本文的框架内,最有用的应该是研究哪些数学回测变量可以证明所用技术能够强化我们的策略,甚至能扭亏为盈。 这个问题非常重要,因为如果我们错误地解读回测结果,我们可能会排除切实可行的技术,或是采用了错误的技术。 在开发新的交易技术时,我始终遵循两个原则,为最可能的操作检测技术,和最可能拒绝虚假的技术,创造必要且充分的条件:
- 数据样本的大小,以及回测中的交易数量,在进行统计评估时应最大化
- 回测值即使略有变化也能表现出正值变化
在许多情况下,任何正值变化都可能被放大,以令其有利于交易。 这有不可否认的优势:
- 这种交易技术可运用在任何交易金融产品上
- 可将多种技术合并起来变成混合技术
- 如果运用得当,即使存在点差,您盈利的机会也比蒙受损失的机会更大。
现在,我将展示当尝试运用交易技术时应分析哪些变量。 我们以本文讨论的最后一种交易技术为例。 我们从回测量具开始,并看看如何利用它们创建综合量具来检测改进。
首先,请注意以下变量:
- 最终回测利润
- 最大余额回撤
- 最大净值回撤
还有一些其他的、更准确的变量可能会有所帮助,但策略测试器报告中没有这些变量:
- 平均余额回撤
- 平均净值回撤
为什么这些量具可以准确地显示系统的安全性和收益性? 关键是,对于任何回测,都具有以下数学标识,就像在模拟账户或真实账户上进行交易的任何交易系统一样:
- { i = 0 ... 1 ... 2 ... m } - 半波的次数(半波是一个增长的净值或余额区段,后随下跌区段)
- { j = 0 ... 1 ... 2 ... k } - 负半波区段(余额区段在开仓和平仓点处开始和结束,净值区段在其他点处开始和结束)
- { OrderProfit[j]-OrderComission[j]-OrderSwap[j] < 0 ! Lim( m --> +infinity ) [ Summ(i) [Summ(j) [ OrderProfit[j]-OrderComission[j]-OrderSwap[j] ]] / m ] = 扣除佣金点差和隔夜利息后的总利润 } - 回测或交易余额的平均回撤
- { SegmentEquity[j]-SegmentComission[j]-SegmentSwap[j] < 0 ! Lim( m --> +infinity ) [ Summ(i) [Summ(j) [ SegmentEquity[j]-SegmentComission[j]-SegmentSwap[j] ]] / m ] = 扣除佣金点差和隔夜利息后的总利润 } - 回测或交易余额的平均回撤
换言之,如果交易费用为零,则在余额和净值两个项目的平均回撤,包括无终点的回测,或在模拟账户或真实账户上无休止地运用该策略,均趋向于最终余额值。 当然,仅当该策略具有明显的正值利润时,这一条才是正确的。 如果总利润为负值,则可以尝试使用镜像公式,在该公式中,您不应将负值部分累加,而仅应将正值部分累加。 但更便捷的是操控已知的正值回测,并在其中应用一些技术。 不过,这并不意味着整体回测会展示出利润。 您应该在策略表现出获利的情况下找到这样的历史间隔(最好是若干个这样的间隔)。 然后应用一种方式。
我们现在来看看如何运用简单的马丁格尔,来增强其稳定性,并略微将其性能朝盈利方向扭转。 根据上面给出的公式,可以制定这样的综合量具,根据该量具,能够基于几个区域来评估交易技术对整体交易的影响:
综合量具的输入数据:
- { Tp } - 最终回测或交易的利润
- { BdM } - 平均余额回撤
- { EdM } - 平均净值回撤
- { BdMax } - 最大余额回撤
- { EdMax } - 最大净值回撤
回测无法提供的数据以颜色高亮显示。 但是,可以在代码中计算这些数据,并在交易或回测结束时显示它们。 但是我宁愿采用其他可用数据。 这一点是因为,在大多数情况下,均摊回撤越小,来自这两个值的最大回撤就越小。 这些值与概率密切相关,并且一个量具的变更通常会导致另一个量具近似成比例的变化。
综合量具:
- { A = Tp/BdM } - 如果该策略不能预测未来,则等于一;如果它知道如何预测并盈利,则等于大于一(相当于回答有关其获利能力的问题)
- { B = Tp/EdM } - 与前值相同
- { C = Tp / BdMax } - 如果在该量具里有所提高,那么我们可以得出结论,该技术提高了该方法的有效性(降低则意味着负面影响)
- { D = Tp / EdMax } - 与前值相同
可用这 4 个条件中的任何一个。 前两个更准确,但是回测无法提供计算它们所需的数据,因此您必须从输入数据读取。 其他两个则可用回测中的值来计算。 因此,我个人使用最后两个量具,因为它们是可用,且很容易找到。 现在,我们以一个利用终止订单平仓的简单马丁格尔为例,查看该方法的应用。 我们将尝试利用最新的外来方法来增强其变量。
利用余额和净值波动
理论:
实际上,该技术不仅可用在马丁格尔之中,而且可以用在具有足够高频的任何其他交易策略当中。 在此示例中,我将利用基于余额回撤的量具。 因为考虑与余额有关的所有事情都更容易。 我们把余额表分为上升和下降部分。 两个相邻的区段形成一个半波。 随着交易数量趋于无限,半波的数量亦趋于无限。 有限的样本就足以让我们在运用马丁格尔时略有盈利。 下图说明了这个思路:
该图示意已形成的半波,和刚刚开始的半波。 任何余额图都是由这样的半波构成。 这些半波的大小会持续波动,我们可以始终在图表上区分这些半波的分组。 这些半波的大小,在一个波中越小,而在另一个波中越大。 因此,通过逐渐降低手数,我们可以等待,知道当前分组里出现严重回撤的半波。 鉴于在系列中此种严重回撤的数量很少,因此这将增加波浪分组的总体平均量具,结果就是,原始测试的相应性能变量也应增加。
为了实现,我们需要为马丁格尔增添两个额外的输入参数:
- { DealsMinusToBreak } - 前一个周期的亏损交易数量,达到该数量时应将周期的起始手数重置为起始值
- { LotDecrease } - 当交易历史中出现新周期时,降低周期起始手数的步长
这两个参数允许我们为安全的半波分组增加手数;为危险的半波分组减少手数;从理论上讲,应能增加上述性能量具。
以下代码将被添加到马丁格尔 EA. 它计算下一个周期的起始手数,并在必要时将其重置:
代码:
double DecreasedLot=Lot;//lot to decrease double CalcSmartLot()//calculate previous cycle { bool ord; int PrevCycleDeals=0; HistorySelect(TimeCurrent()-HistoryDaysLoadI*86400,TimeCurrent()); for ( int i=HistoryDealsTotal()-1; i>=0; i-- ) { ulong ticket=HistoryDealGetTicket(i); ord=HistoryDealSelect(ticket); if ( ord && HistoryDealGetString(ticket,DEAL_SYMBOL) == _Symbol && HistoryDealGetInteger(ticket,DEAL_MAGIC) == MagicC && HistoryDealGetInteger(ticket,DEAL_ENTRY) == DEAL_ENTRY_OUT ) { if ( HistoryDealGetDouble(ticket,DEAL_PROFIT) > 0 ) { for ( int j=i+1; j>=0; j-- )//found a profitable deal followed by losing (count them) { ticket=HistoryDealGetTicket(j); ord=HistoryDealSelect(ticket); if ( ord && HistoryDealGetString(ticket,DEAL_SYMBOL) == _Symbol && HistoryDealGetInteger(ticket,DEAL_MAGIC) == MagicC && HistoryDealGetInteger(ticket,DEAL_ENTRY) == DEAL_ENTRY_OUT ) { if ( HistoryDealGetDouble(ticket,DEAL_PROFIT) < 0 ) { PrevCycleDeals++; } else { break; } } } break; } else { break; } } } if ( PrevCycleDeals < DealsMinusToBreak ) DecreasedLot-=LotDecrease; else DecreasedLot=Lot; if ( DecreasedLot <= 0.0 ) DecreasedLot=SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); return DecreasedLot; }
输入参数以黄色高亮显示。 请注意,这是一个测试函数,仅适合以此形式用于策略测试器中测试。 然而,就我们来说,执行此假设的第一个粗略和简单的测试就足够了。 本文仅提供理解该思路所需的最少代码。 我不会提供其余的 EA 代码,以免分散读者的注意力,把精力浪费在不必要的思考。 本文撰写期间创建的其他 EA 也是如此。 现在我们来测试通常的马丁格尔,然后开启新模式,并看看它如何影响性能变量。 由于初始信号是常规马丁格尔,其智能交易系统也专为 MetaTrader 5 设计,其可依据不同点差执行相同操作。
测试智能交易系统:
如果您为原始测试计算 D,它的取值则为 1.744。 启用新模式后,此值为 1.758。 盈利能力略微转向正确的方向。 当然,如果我们再进行几次测试,则该值可能会降低,但平均下来,变量应会增加。 严格来说,逻辑足以进行演示。
结束语
在本文中,我尝试收集了最有趣、最实用的技术,或许对自动交易系统的开发人员有所帮助。 经过相应地研究和探索,其中一些技术可以辅助提高利润。 我希望这些资料是有趣且实用的。 可将这些技术当作工具箱,而不是有关如何构建圣杯的指南。 即便是对这种技术的简单了解,也可以令您免于盲目投资或严重亏损。 通过投入更多的时间和精力,您可以尝试创建更有趣、更稳定的系统,超越基于两个指标交汇的传统交易系统。