在本文中,我将使用数学和编程来深入研究这些策略,并评估它们的盈利能力。这篇文章同时包含了数学和实践部分。在数学部分,我将提供用于计算策略预期收益的公式和许多交易者未考虑的其他重要参数。在实践部分,我将开发一个简单的网格和马丁格尔的EA交易,并比较方程与实际情况。这篇文章对初学者特别有用,因为这些策略通常是他们最先遇到的。盲目地相信它们可能会导致失望和浪费时间,就像我在过去发生的那样。如果我不懂数学,也许我还会相信它们。但如果你正确看待这些策略,它们仍然有其道理。这就是我要证明的。
要了解这两种策略的流行程度,我们应该看看所有外汇新手都想要什么。大多数新手交易者都是数学家和梦想家,他们认为自己的智力可以帮助自己迅速而轻松地致富。我曾经也是这样一个梦想家。这两种策略都以图形的形式出现,在策略测试器中不断上升。在报价历史的许多部分上,测试人员甚至可以在没有任何过滤器的情况下显示类似圣杯的结果。在任何领域的活动和任何业务,都有一个危险,更了解实情的人能够通过你的付出而使自己更加富有。外汇交易也不例外。外汇有很多这样的欺骗行为,这两种策略是最具说明性和流行的证据。当你第一次使用这些策略时,你会发现它们适用于所有具有难以置信的利润因子和预期收益的货币对,甚至可以应对任何点差,所以这个算法似乎已经超越了市场。这是因为它们建立在纯粹的数学基础上,没有逻辑。即使过了这么多年,我还是想找到一种算法,让我无论价格走向如何都能获得利润。数学家一般都是有趣的人,他们能够用正确的方程式证明任何事情,不管这在现实中是否正确。)) 一般来说,这两种策略利用盈亏平衡的假象来说服您使用它们。它们都适用于任何货币对和任何时期,或者更确切地说,制造一种假象,让你相信它们的简单性和效率。钻研这些策略,你迟早会意识到你什么都不知道。) 然而,这一阶段是必要的,因为这是开始理性思考和了解市场的真实性质以及你真正需要使用什么策略的唯一途径。
创建订单网格的目的是在任何市场上盈利。不管是下跌还是上涨,如果市场有一个清晰可见的波动,那么根据这个想法,网格都会使用一个聪明的订单打开系统打开订单,这样总的来说,这些订单在某个时候获得了足够的利润,可以一次关闭所有订单。让我在下面的图片中展示这一点:
这里我分别展示了上涨和下跌市场的两种选择。根据网格策略,无论我们得到哪种选择,我们都应该赢。那些使用网格的人总是使用挂单,因为它们是以最好的价格触发的。这是真的,但我相信市场订单也不会更糟,只要你能在入场时控制点差和滑点。另外,你可以稍微推迟入场。不过,在这种策略下,限价订单效果更好。我们有一个相对于下订单而言的出发点,在这一点之上,我们用“s”步长设置买入挂单,而在它之下,我们设置卖出挂单。如果价格达到它们,它们就变成市场订单。该图像根据特定的价格情况显示未结订单。在这里显示限价单是没有意义的,因为它们一直保持在同一水平上无限地上下波动。只有未结的实际订单对我们来说才是重要的,因为它们的利润或亏损分别加起来等于总利润或亏损。为了保证利润,有些订单应该比其他订单大K倍,即我们应该提供K=a/d,其中K>=K0。当达到K0时,所有网格订单的总利润超过零阈值。对于MetaTrader 4,我们还可以用同样简单的方式计算头寸或订单的当前利润。否则,我们将得到一种情况,当价格立即向某个方向移动,我们获取利润后,价格移动“n”点向上或向下。严格地说,这个比率可以计算,但可以很容易地手动选择。可能的计算如下:
把所有订单的损失或利润加起来,我们可以看出这些总和是算术级数。有一个方程描述算术级数的和(使用它的第一项和最后一项),它在这里被应用。
考虑到Pr Ls=0,求解该方程得到“a”,从而可以计算K0=a/d。此外,利用这些方程可以定义可以打开买入和卖出仓位的交易周期的利润因素以及预期收益。
这些方程计算的是某个交易周期的利润系数和预期收益,而不是整个图表。如果我们的图表在周期的终点结束,利润因素将是正的。一个周期是一个独立的网格。建立网格,尽可能地使用,仓位被关闭而一个新的网格建立起来,如果存款无限,这也是一个无尽的过程。这就是它在余额曲线上的大致形状:
在这里,我提供了一个网格机器人余额图的简化表示。有几个周期,存款能够支撑住,而图表会上升。但这不可避免地以一个循环结束,即存款不足,我们所有可见的利润都归经纪商所有。在数学方面,这被认为是一个未完成的循环。未完成的周期总是无利可图的,它的损失会覆盖在工作到最后的周期中赚取的所有利润。由于继续网格所需的订单量不足,周期也可能未完成。所有经纪商都对终端或特定货币对中同时打开的订单数量施加限制,网格不能无限期地构建。即使我们假设我们能够做到这一点,我们最终还是会得到上述结果。在文章的最后,我将从数学的角度简要解释为什么会发生这种情况。
就像网格一样,马丁格尔背后的想法也是不管市场走向如何都要赢。它是建立在同样的对永恒利润的幻想之上。如果我们打开一个订单,结果是获利的,我们就简单地再交易。一旦出现亏损,我们就将下一订单的手数相对于亏损头寸增加n倍。如果我们的订单是获利的,我们只需关闭它,并把手数重置为初始值。如果订单再次亏损,则重复上一步,同时相对于周期内所有失败头寸的手数总和增加手数“n”倍。重复,直到我们再次达成一笔可以获利的交易。周期中的最后一笔交易总是有利可图的,它的利润总是弥补失去交易的损失。这就是图如何开始由循环组成。假设图以最后一个循环结束,我们得到一个正的期望值和利润因子。如何以及在何处开立这些订单并不重要。最好,这些订单应该有固定的利润和损失,或只是在固定的止损水平关闭。下面是马丁格尔机器人余额图的样子:
正如我们所看到的,它非常类似于网格余额图,因为马丁格尔是循环工作的,就像网格一样。唯一的区别是,它总是打开一个订单,然后等到关闭后再打开下一个订单。就像在网格的情况下一样,存款迟早会不足以完成周期,所有订单都会关闭,存款也会消失。为确保盈利周期,上一笔交易的利润应弥补上一笔交易的亏损:
这里的利润是以你的帐户货币单位计算的,而不是以点数计算的,因为系统处理的是大量的数据。特定订单的手数是使用递归计算的:
其中“K”是周期所需的利润系数。点差、佣金和隔夜息在这里没有考虑,但我认为这并不重要。如果需要的话,可以很容易地修改方程,尽管我看不出这有什么意义。马丁格尔方程类似于网格方程。SL和TP是订单获得的亏损和期望利润。我们可以通过求解以下简单方程得到定义:K=(L[i]* TP[i])/Sum(1,i-1)(L[j]*SL[j]).
为了测试上述假设,让我们用MQL5语言编写一个简单的网格EA和一个简单的马丁格尔EA来测试它们并查看结果。我将从网格开始。首先,在我们的模板中添加几个方便的类来处理仓位:
#include <Trade\PositionInfo.mqh> #include <Trade\Trade.mqh> CPositionInfo m_position=CPositionInfo();// trade position object CTrade m_trade=CTrade(); // trading object
默认情况下,MetaTrader 5中始终存在这两个库,因此不会出现编译问题。
接下来,让我们描述所有必要的输入参数:
///grid variables input int MaxChannelSizePoints=500;//Max Of a+d input int MinMoveToClose=100;//Mininum Move input int GridStepPoints=20;//Grid Step In Points input int BarsI=999;//Bars To Start Calculate input double KClose=3.5;//Asymmetry /// ////////minimum trading implementation input int SlippageMaxOpen=15; //Slippage For Open In Points input double Lot=0.01;//Lot input int MagicC=679034;//Magic /////////
第一个块实现了所有必需的网格参数,而第二个块实现了以其最简单的形式进行固定手数交易的能力。
在启动EA时,我们需要验证和恢复上一个会话中的网格参数,以防操作被错误地终止。此功能是可选的,但最好提前实现:
void DimensionAllMQL5Values()////////////////////////////// { ArrayResize(Time,BarsI,0); ArrayResize(High,BarsI,0); ArrayResize(Low,BarsI,0); } void CalcAllMQL5Values()/////////////////////////////////// { ArraySetAsSeries(High,false); ArraySetAsSeries(Low,false); ArraySetAsSeries(Time,false); CopyHigh(_Symbol,_Period,0,BarsI,High); CopyLow(_Symbol,_Period,0,BarsI,Low); CopyTime(_Symbol,_Period,0,BarsI,Time); ArraySetAsSeries(High,true); ArraySetAsSeries(Low,true); ArraySetAsSeries(Time,true); }
此代码是实现预定义数组以完成初始分析所必需的。我们以后不需要这些数组,我们将仅在初始计算时使用它们。
恢复按以下方式执行:
void RestoreGrid()//recover the grid if the robot is restarted { DimensionAllMQL5Values(); CalcAllMQL5Values(); bool ord=PositionSelect(Symbol()); if ( ord && int(PositionGetInteger(POSITION_MAGIC)) == MagicC ) { GridStartTime=datetime(PositionGetInteger(POSITION_TIME)); GridStartPrice=double(PositionGetDouble(POSITION_PRICE_OPEN)); GridUpPrice=GridStartPrice; GridDownPrice=GridStartPrice; for(int i=0;i<BarsI;i++) { if ( High[i] > GridUpPrice ) GridUpPrice=High[i]; if ( Low[i] < GridDownPrice ) GridDownPrice=Low[i]; if ( Time[i] < GridStartTime ) break; } bCanUpdate=true; bTryedAlready=false; } }
为了跟踪当前的网格状态,我们需要额外的变量来显示网格存在期间的上下价格,以及起始网格价格和设置时间。
datetime GridStartTime;//grid construction time double GridStartPrice;//grid starting price double GridUpPrice;//upper price within the corridor double GridDownPrice;//lower price within the corridor
我们还需要两个布尔变量来跟踪或更新价格变动期间的网格变量,以及在第一次尝试失败时关闭网格的其他尝试。
bool bCanUpdate;//whether it is possible to update the grid bool bTryedAlready;//whether there was an attempt to close a position
在开发过程中创建和更新网格参数如下所示:
void CreateNewGrid()//create a new grid { SymbolInfoTick(Symbol(),LastTick); GridStartTime=TimeCurrent(); GridStartPrice=LastTick.bid; GridUpPrice=GridStartPrice; GridDownPrice=GridStartPrice; double SummUp=LastTick.ask+double(GridStepPoints)*_Point; double SummDown=LastTick.bid-double(GridStepPoints)*_Point; while ( SummUp <= LastTick.ask+double(MaxChannelSizePoints)*_Point ) { m_trade.BuyStop(Lot,SummUp,Symbol()); SummUp+=double(GridStepPoints)*_Point; } while ( SummDown >= LastTick.bid-double(MaxChannelSizePoints)*_Point ) { m_trade.SellStop(Lot,SummDown,Symbol()); SummDown-=double(GridStepPoints)*_Point; } } void UpdateGrid()//update the grid parameters { SymbolInfoTick(Symbol(),LastTick); if ( LastTick.bid > GridUpPrice ) GridUpPrice=LastTick.bid; if ( LastTick.bid < GridDownPrice ) GridDownPrice=LastTick.bid; }
用于关闭头寸和清除剩余限价订单的函数,以及检测关闭网格条件的函数:
void ClosePosition()//close a position by a symbol { bool ord; ord=PositionSelect(Symbol()); if ( ord && int(PositionGetInteger(POSITION_MAGIC)) == MagicC ) { if(m_position.SelectByIndex(0)) m_trade.PositionClose(m_position.Ticket()); } } void CleanLimitOrders()//clear limit orders { int orders=OrdersTotal(); for(int i=0;i<orders;i++) { ulong ticket=OrderGetTicket(i); if(ticket!=0) { m_trade.OrderDelete(ticket); } } } bool bCanClose()//closure condition { if ( GridStartPrice == GridUpPrice && (GridStartPrice-GridDownPrice)/_Point >= MinMoveToClose ) return true; if ( GridStartPrice == GridDownPrice && (GridUpPrice-GridStartPrice)/_Point >= MinMoveToClose ) return true; if ( GridStartPrice != GridUpPrice && GridStartPrice != GridDownPrice && (GridStartPrice-GridDownPrice)/(GridUpPrice-GridStartPrice) >= KClose && (GridStartPrice-GridDownPrice)/_Point >= MinMoveToClose ) return true; if ( GridStartPrice != GridDownPrice && GridStartPrice != GridUpPrice && (GridUpPrice-GridStartPrice)/(GridStartPrice-GridDownPrice) >= KClose && (GridUpPrice-GridStartPrice)/_Point >= MinMoveToClose ) return true; /* if ( GridUpPrice >= GridStartPrice+MaxChannelSizePoints*_Point //|| GridDownPrice <= GridStartPrice-MaxChannelSizePoints*_Point ) return true; */ return false; }
我已经注释掉了关闭条件函数中的最后一个条件。它关闭网格以防价格超出网格。你可以随意使用它,它不会改变任何东西。现在我们只需要编写主要的交易函数:
void Trade()//the main function where all actions are performed { bool ord=PositionSelect(Symbol()); if ( bCanUpdate ) UpdateGrid(); if ( ord && bCanClose() )//if there is a position and the closing condition is met { ClosePosition(); CleanLimitOrders(); bCanUpdate=false; bTryedAlready=true; } if ( bTryedAlready ) ClosePosition(); if ( !bCanUpdate && !ord ) { CleanLimitOrders(); CreateNewGrid(); bCanUpdate=true; bTryedAlready=false; } }
另外,让我们定义在何处、调用什么以及在初始化EA时执行什么操作:
int OnInit() { m_trade.SetExpertMagicNumber(MagicC);//set the magic number for positions RestoreGrid();//restore the grid if present return(INIT_SUCCEEDED); } void OnTick() { Trade(); }
我们已经开发了网格EA,现在让我们测试一下它的行为:
如你所见,关于无法获利周期的假设已经得到证实。一开始,网格运行得很好,但后来出现了一个时刻,网格不够,导致一个亏损周期,摧毁了所有的利润。趋势市场通常表现出良好的业绩,而亏损主要发生在横盘市场。总的结果总是亏损,因为我们还有点差。
现在我们已经处理了网格,让我们继续讨论马丁格尔EA。它的代码会简单得多。为了处理仓位,我们将使用网格EA中应用的库。再一次显示代码是没有意义的。让我们马上考虑输入参数:
input int SLE=100;//Stop Loss Points input int TPE=300;//Take Profit Points input int SlippageMaxOpen=15; //Slippage For Open In Points input double Lot=0.01;//Start Lot input int MagicC=679034;//Magic input int HistoryDaysLoadI=10;//History Deals Window Days
为了更简单,我选择了一种系统,在这种系统中,头寸严格地通过止损或获利了结。最后一个变量允许我们避免不断加载整个订单历史,而是只加载必要的阶段(纯粹用于优化)。我相信,其他变量是不言自明的。
这个EA只有两个函数:
double CalcLot()//calculate the lot { bool ord; double TotalLot=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 ) { TotalLot+=HistoryDealGetDouble(ticket,DEAL_VOLUME); } else { break; } } } return TotalLot == 0 ? Lot: TotalLot; } void Trade()//the main function where all actions are performed { bool ord=PositionSelect(Symbol()); SymbolInfoTick(Symbol(),LastTick); if ( !ord ) { if ( MathRand() > 32767.0/2.0 ) { m_trade.Buy(CalcLot(),_Symbol,LastTick.ask,LastTick.bid-double(SLE)*_Point,LastTick.ask+double(TPE)*_Point); } else { m_trade.Sell(CalcLot(),_Symbol,LastTick.ask,LastTick.ask+double(SLE)*_Point,LastTick.bid-double(TPE)*_Point); } } }
第一个函数是在查看交易历史后,计算用于开仓的最终手数。如果最后一笔交易是没有获利的,那么下一笔交易将等于前一笔亏损交易到第一笔盈利交易的总和。如果最后一笔交易获利了,则将手数重置为其起始价值。在主函数中,我们在不同方向上随机打开具有固定限价级别的订单,而使用第一个函数计算交易量。为了让所有这些都正常工作,我们需要在初始化中为EA分配一个幻数,同时在OnTick处理程序中调用main函数,就像在网格中一样。
这就结束了最简单的马丁格尔的开发。现在,让我们测试并查看结果:
这种情况与网格相似,我们可以看到周期。马丁格尔EA在损失周期开始之前是起作用的,保证金不足以开启下一个仓位的时候,亏损就发生了。就像网格一样,它在某些地方有效,在某些地方无效,但总是以失败告终。既然我们已经探讨了这两种策略,现在是时候得出数学结论,引导我们找到比仅仅理解这两种策略更重要的答案了。
为什么我相信网格和马丁格尔背后的统一数学原理如此重要?如果我们彻底理解了它,我们最终可以告别一系列永远不会给我们带来利润的想法,尽管我们可能希望相信它们。至少,我们将了解哪些条件有助于这些策略的执行。此外,我们将了解纯马丁格尔和网格为什么是亏损的策略。
让我们设想一下,任何策略都由无限多个最简单的策略组成。当开启一个订单时,就激活其中一个。我们将假定这些订单是以固定的损益结清的。让我们把它们与C[i]和Lot[i]数组相对应,这些数组的大小相等,趋于无穷大。假设每种策略所使用的手数总是不同的。另外,让我们介绍一下触发这些策略的概率。PC[i], 当然,这些事件形成了一个完整的组,因此 Sum(0,n)(PC[i])=1。这些事件的所有结果形成了新的事件空间 S[i],T[i],分别表示得到的损失和利润。这些事件有自己的条件概率PS[i],PT[i],当然,这也构成了一个完整的组。下面提供了图形表示:
现在让我们考虑一下这个列表中的任何一个策略,并计算它的预期收益。
如果我们不知道开仓点的价格方向,我们可以说M[i]=0,其中M[i]是某些策略的预期收益数组。换言之,如果我们不知道价格走向,只要交易数量趋于无穷大,无论我们如何交易,我们都会得到0。
一般预期收益方程如下所示:
我们知道,当n趋于无穷大时,所有M[i]趋于零,这意味着在策略数为有限的情况下,我们和的所有项趋于0,而交易数为无限。这反过来意味着总体预期收益 M0 仍然等于0。如果我们进一步想一想,结果是这样的有限策略集的无限集也等于零,因为将无限多个零相加得到0。在网格的情况下,手数大小在任何地方都是相同的,而在马丁格尔的情况下,手数大小是不同的,但这种差异不会以任何方式影响最终的期望。这两种策略都可以用这个一般方程来描述,甚至不用考虑组合学,一切都很简单明了。
因为这些策略可以用这个方程来描述,所以它适用于任何策略。这意味着所有涉及变化和操纵交易量的策略,以及任何复杂的订单开启/关闭系统,在不知道开盘和收盘交易时的大致运动方向或至少一些辅助市场参数的情况下,都注定要失败。没有正确的预测,我们所有的努力都是浪费时间和金钱。
如果你知道市场即将朝某个方向移动,或者发生这种事件的概率很高,同时也存在缺口的风险,那么这个网格可能很有用。缺口和网格混合不太好。这是因为订单是按一定的步长下的,下一个报价点可能会越过所有订单,并远远超出网格。当然,这是一个罕见的情况,但它不可避免地降低了系统效率。网格大小应设置为等于或略小于预测的移动,而我们不需要知道移动的方向,而只需要知道其近似值。以下是在趋势检测算法成功的情况下余额图的外观:
事先定义一个趋势并不总是可能的。这通常会导致周期性的损失(上面的红色标记),而且下降幅度也相当大。该网格可用于那些具有检测大运动的方法的人。在这种情况下,网格可以基于自定义信号。我还没有处理这个问题,所以如果有人有很好的算法检测强烈的波动,请随意分享你的经验。
现在我们来看看马丁格尔。如果我们有任何预期收益为“0”的信号,但已知损失序列是这样的,即对于一系列中的一定数量的损失,盈利交易的概率接近于1,那么这个信号可以用于马丁格尔。余额图如下所示:
但我个人认为马丁格尔在任何情况下都是危险的。我相信,要达到我所描述的这些条件几乎是不可能的,尽管在网格的情况下,一切似乎都更容易,而且最重要的是,更清晰。
在本文中,我试图尽可能清晰地描述这两种策略,突出它们的相似性、优点和缺点。我相信,所有的解释即使是初学者也很容易理解。这篇文章主要是为新手交易者准备的,但是得出的结论比简单评估策略及其应用的局限性要重要得多。这里提供的总体数学结论允许每个人在开发自己的交易系统时有效地利用自己的时间。这篇文章本身并没有给理解市场物理带来任何新的东西,但我认为它可以防止许多交易者为了自己的利益而轻率地利用这些原则。
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程