内容简介表
- 简介
- 追踪止损函数的实现方式
- 标准化追踪止损函数CTrailing类
- CTrailing类和策略中其他模块的互相作用
- 追踪止损的一个实际用例一个经典追踪止损函数的例子
- 向CImpulse策略中添加追踪止损
- 向EA设置中添加追踪止损参数
- 基于移动平均线的追踪止损
- 单一头寸的独立追踪止损
- 新版交易引擎的简短修改列表
- 总结
简介
本文是“通用智能交易系统”系列文章中的另一篇,所谓通用智能交易系统是一组特殊的类的集合,它们构成了一个交易引擎使得用户可创建他们自己的策略。在本文前面的部分,我们讨论了交易引擎的各种模型,基于此引擎创建的任何策略的基本功能都可以被扩展。然而,这些模型要么是CStrategy类的一部分,或者是这个类的实现对象。
在本文中,我们继续开发CStrategy交易引擎的功能,并且考虑一个新的可能性:支持追踪止损。和其他CStrategy模块不同,追踪止损算法对于交易引擎来说是外部的。这就是说它的出现和消失不应该影响CStrategy的运作。这一特性可以通过使用一种称为组合的编程技巧来实现,此技术将在本文的后面部分阐述。我们件更严格遵循面向对象变成的原则,继续使用额外的模块或者类来添加新的功能。
追踪止损函数的实现方式
追踪止损是一种算法,它的目标是为了保护头寸免受过多的亏损,而将止损移动到一定的价格水平上。显然,管理一个头寸的止损可以有很多种算法。一种追踪止损管理算法可以作为CStrategy类中的一个独立方法来实现。例如,它可以接收当前头寸作为一个参数并且返回要移动到的当前止损水平:
class CStrategy { public: double TrailingMode1(CPosition* pos); };
然后追踪止损函数作为头寸管理将在包含在策略中:
void CMovingAverage::SupportBuy(const MarketEvent &event,CPosition *pos) { double new_sl = TrailingMode1(pos); pos.StopLossValue(new_sl); }
然而,因为可以有许多追踪止损管理函数,所以将其放在CStrategy类中非常不合适的。相对于EA的基本算法来说,追踪止损是外部的的功能。它不是交易引擎必须的函数,它的目标只是简化交易交易过程。因此,缺少追踪止损函数不应该影响CStrategy或者是一个不使用任何追踪止损策略的功能。另一方面,追踪止损算法的加入不应该使代码可读性变差或者增加基类的代码。这也是为何所有的追踪止损函数都应该被放在独立的类和文件中,在需要的时候同交易策略建立联系。
我们可以在CPosition类中实现追踪止损函数。这种情况下,追踪止损操作看上起是这样的:
void CMovingAverage::SupportBuy(const MarketEvent &event,CPosition *pos) { pos.TrailingMode1(); }
然而,这只会将问题从CStrategy类转移到CPosition类。在这种情况下,CPosition类将含有怪异但是有用的头寸管理算法,这种算法的数量可能会非常多。
除此之外,追踪止损算法还需要配置特定的参数。例如,对于一个经典的追踪止损算法,我们需要确定所达价格极值和想要控制的止损位置之间的距离点数。因此,除了算法本身,我们还需要将运行参数保存在某个地方。如果你将这个数据保存在诸如CPosition或CStrategy的基本类中,那么这些类的内部变量会和大量的追踪止损变量混淆起来,因此会大大增加这些类的复杂度。
对追踪函数进行标准化。CTrailing类
一般来说,最有效的解决办法是最简单的和最成熟的方法。对于追踪止损来说也一样。如果我们将追踪止损想象成一个特殊的类,类中变量存储运行参数并将止损移动算法作为其方法,那么所有以上问题都将得到解决。如果我们将追踪止损作为一个独立的类,那么它的数据成员和方法将不会和基本类CStrategy以及其他重要的基础对象如CPosition相混淆了。
当开发这个类的时候,我们需要解决两个问题。
- 追踪止损类的内部结构。标准化其方法。
- 类和CStrategy引擎的其他模块的互动。
让我们先考虑第一个问题。显然,任何追中止损算法都有其独特的参数集。因此,标准化这个参数集是不可能的。相反,所有的追踪算法都需要一个参数,就是你想改变的头寸的止损位置。头寸用我们已经熟悉的CPosition类代表。另外,每一种追踪止损都需要有一个方法函数,调用它来修改该头寸的止损位。此方法就像一个用于加载追踪止损算法的“按钮”,因此所有类型的追踪止损方法的名称应该一致。
我们已经确定了所有追踪止损类型的两个一般要素,它们可以方便的以一个特殊的基类来表示,我们称其为CTrailing。它将包含设置当前头寸的方法,以及用于修改止损的Modify虚方法,还有一个Copy虚方法,它的用户我们后面再解释:
//+------------------------------------------------------------------+ //| Trailing.mqh | //| Copyright 2016, Vasiliy Sokolov. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2016, Vasiliy Sokolov." #property link "http://www.mql5.com" #include <Object.mqh> #include "..\PositionMT5.mqh" #include "..\Logs.mqh" class CPosition; //+------------------------------------------------------------------+ //| 追踪止损的基类 | //+------------------------------------------------------------------+ class CTrailing : public CObject { protected: CPosition *m_position; // 你要修改止损的头寸 CLog *Log; public: CTrailing(void); void SetPosition(CPosition *position); CPosition *GetPosition(void); virtual bool Modify(void); virtual CTrailing* Copy(void); }; //+------------------------------------------------------------------+ //| 构造函数。接收日志模块 | //+------------------------------------------------------------------+ CTrailing::CTrailing(void) { Log=CLog::GetLog(); } //+------------------------------------------------------------------+ //| 追踪止损方法 | //| 应在派生类中重写 | //+------------------------------------------------------------------+ bool CTrailing::Modify(void) { return false; } //+------------------------------------------------------------------+ //| 返回实例的副本 | //+------------------------------------------------------------------+ CTrailing* CTrailing::Copy(void) { return new CTrailing(); } //+------------------------------------------------------------------+ //| 设置要修改止损的头寸 | //+------------------------------------------------------------------+ void CTrailing::SetPosition(CPosition *position) { m_position=position; } //+------------------------------------------------------------------+ //| 返回要修改止损的头寸 | //+------------------------------------------------------------------+ CPosition *CTrailing::GetPosition(void) { return m_position; } //+------------------------------------------------------------------+
任何追踪止损算法都将派生自这个类。基类不包含具体滑动止损算法的参数。这有助于实现类操作的最大灵活性。尽管事实上,追踪止损功能需要各种参数用于执行,但Modify方法不接受它们中的任何一个。所有参数都在子类中用特殊的方法直接设置。因此,方调用Modify的时候,所有需要的参数都已经准备好。
CTrailing类和策略中其他模块的互相作用
虽然我们现在只有一个CTrading类,但要把它包含在交易引擎的总体架构中已经足够了。我们将在类CPosition中添加这个基类。
class CPosition { public: CTrailing* Trailing; // 追踪止损模块 };
这样的结构看上去直观其自然。这样,头寸就能像控制它自己一样来控制追踪止损了。
void CMovingAverage::SupportBuy(const MarketEvent &event,CPosition *pos) { pos.Trailing.Modify(); }
由于采用标准化的Modify方法,因此使之成为可能,比如为了修改头寸必须准确的了解应该执行哪些步骤。
然而,追踪止损模块的集成还没有结束。上面的例子还需要在用户策略层面的头寸管理方案。我们要重写BuySupport和SellSupport方法并且在EA的逻辑中管理每一个头寸。为了进一步简化头寸管理,我们可以直接在CStrategy类中添加追踪止损模块:
class CStrategy { public: CTrailing* Trailing; // 用于所有头寸的追踪止损模块 };
我们还额外需要一个CStrategy类的CallSupport方法:
//+------------------------------------------------------------------+ //| 调用头寸管理逻辑,如果交易状态不等于 | //| TRADE_WAIT | //+------------------------------------------------------------------+ void CStrategy::CallSupport(const MarketEvent &event) { m_trade_state=m_state.GetTradeState(); if(m_trade_state == TRADE_WAIT)return; SpyEnvironment(); for(int i=ActivePositions.Total()-1; i>=0; i--) { CPosition *pos=ActivePositions.At(i); if(pos.ExpertMagic()!=m_expert_magic)continue; if(pos.Symbol()!=ExpertSymbol())continue; if(CheckPointer(Trailing)!=POINTER_INVALID) { if(CheckPointer(Trailing)==POINTER_INVALID) pos.Trailing=Trailing.Copy(); pos.Trailing.Modify(); if(!pos.IsActive()) continue; } if(pos.Direction()==POSITION_TYPE_BUY) SupportBuy(event,pos); else SupportSell(event,pos); if(m_trade_state==TRADE_STOP && pos.IsActive()) ExitByStopRegim(pos); } }
新的特性用黄色高亮显示。它们非常简单:如果默认设置了滑动止损,但是当前头寸没有,那么当前头寸就会带有滑动止损功能,其止损位置根据追踪逻辑进行修改。然而,我们要考虑到一个非常重要的特点。一个追踪止损的实例被分配给每个头寸,而不是默认追踪止损自身。这一特性有助于在追踪止损逻辑内部避免混淆。想象一下如果同样一个追踪止损实例管理多个头寸。如果在追踪止损逻辑内部计算任何变量并保存它们的话,在下一次调用时逻辑将失效,因为传入的要管理的头寸将改变。这样会导致非常诡异的错误。为了避免这种情况,每一个头寸分配一个独立的追踪止损实例。这一实例在头寸的整个声明周期内保持不变。要生成一个独立的实例,就需要复制默认追踪止损。因为CStrategy类无法指导什么数据和内部变量要被复制,因此复制操作在追踪止损类中执行。要重写CTrailing类的虚函数Copy(),然后返回一个副本自身的CTrailing类的指针。这里是CTrailingClassic的Copy()方法的一个实现样例:
//+------------------------------------------------------------------+ //| 返回一个实例的副本 | //+------------------------------------------------------------------+ CTrailing *CTrailingClassic::Copy(void) { CTrailingClassic *tral=new CTrailingClassic(); tral.SetDiffExtremum(m_diff_extremum); tral.SetStepModify(m_step_modify); tral.SetPosition(m_position); return tral; }
改方法创建了一个CTrailingClassic类型的实例,将其参数设置为当前实例参数,然后返回一个CTrailing类对象的指针。
在开发自定义追踪止损类时记住一条简单的原则:
要默认设置追踪止损,有必要重写基类CTrailing的Copy方法。否则,CStrategy将无法自动管理未平仓头寸。如果你仅打算在BuySupport和SellSupport方法中使用追踪止损,那就无需重写虚方法Copy。
重写Copy方法会复杂化自定义追踪止损算法的开发,但是能够使模块的逻辑更加健壮,避免数据处理错误的出现。
现在CStrategy可以使用追踪止损逻辑来管理头寸了。如果我们将CStrategy::Trailing指针指向自定义策略构造函数中的任一追踪止损算法,它将成为所有属于当前EA头寸的默认追踪止损。因此对于仅使用追踪止损来管理头寸的策略,没有必要重写BuySupport和SellSupport方法。头寸将在CStrategy类中被自动管理。
注意在CallSupport代码中,在调用CTrailing::Modify之后紧跟着检查头寸是否存在。也就是说如果一个头寸在追踪止损修改期间被平仓了,这也没关系,调用重写函数的循环体会被终端,并将搜索下一个头寸。这一特性会带来一个有意思的结果:
任何头寸管理算法实际上都可以被用做追踪止损函数。修改止损就没有必要了。头寸管理算法可以在特定条件下平仓。这一操作是符合预期的,并且CStrategy将正常执行之。
追踪止损的一个实际用例一个经典追踪止损的例子
现在,当基类完成定义并且我们告诉基本策略引擎如何同其协作时,我们就可以创建追踪止损的一个特定实例了。让我们从经典的追踪止损算法开始。它的操作很简单。追踪止损根据新高(多头)和新低(空头)来移动止损位置。如果价格运动返回,止损位置不变。因此,止损距离价格极值一定的距离。这个距离由相应的参数决定。
同时,追踪止损有多个参数。这是可选的。为了避免频繁的修改止损,我们将引入一个额外的限制:新的止损位置同此前止损位置的最小差异等StepModify点。StepModify作为一个独立参数设置。这个参数在交易FORTS时很重要。根据FORTS的交易规则,交易所对于所谓的“低效交易”收取额外的手续费。如果有大量的止损修改而真实交易很少 — 交易所将像交易者收取额外的费用。因此算法必须将这一特性考虑进去。
下面是我们的第一个追踪止损算法。它基于CTrailing类并重写Modify方法:
//+------------------------------------------------------------------+ //| TrailingClassic.mqh | //| Copyright 2016, Vasiliy Sokolov. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2016, Vasiliy Sokolov." #property link "http://www.mql5.com" #include "Trailing.mqh" //+------------------------------------------------------------------+ //| 将追踪止损参数集成在EA的参数列表中 | //| | //+------------------------------------------------------------------+ #ifdef SHOW_TRAILING_CLASSIC_PARAMS input double PointsModify=0.00200; input double StepModify=0.00005; #endif //+------------------------------------------------------------------+ //| 一个经典的追踪止损算法 | //+------------------------------------------------------------------+ class CTrailingClassic : public CTrailing { private: double m_diff_extremum; // 价格到达的极值和止损位置之间的距离点数 double m_step_modify; // 修改止损的最小差异点数 double FindExtremum(CPosition *pos); public: CTrailingClassic(void); void SetDiffExtremum(double points); double GetDiffExtremum(void); void SetStepModify(double points_step); double GetStepModify(void); virtual bool Modify(void); virtual CTrailing *Copy(void); }; //+------------------------------------------------------------------+ //| 构造函数初始化默认参数 | //+------------------------------------------------------------------+ CTrailingClassic::CTrailingClassic(void) : m_diff_extremum(0.0), m_step_modify(0.0) { #ifdef SHOW_TRAILING_CLASSIC_PARAMS m_diff_extremum=PointsModify; m_step_modify=StepModify; #endif } //+------------------------------------------------------------------+ //| 返回实例的副本 | //+------------------------------------------------------------------+ CTrailing *CTrailingClassic::Copy(void) { CTrailingClassic *tral=new CTrailingClassic(); tral.SetDiffExtremum(m_diff_extremum); tral.SetStepModify(m_step_modify); tral.SetPosition(m_position); return tral; } //+------------------------------------------------------------------+ //| 设置价格极值同止损位置的距离点数 | //+------------------------------------------------------------------+ void CTrailingClassic::SetDiffExtremum(double points) { m_diff_extremum=points; } //+------------------------------------------------------------------+ //| 设置修改止损的最小差异点数 | //+------------------------------------------------------------------+ void CTrailingClassic::SetStepModify(double points_step) { m_step_modify=points_step; } //+------------------------------------------------------------------+ //| 返回距离价格极值的点数 | //+------------------------------------------------------------------+ double CTrailingClassic::GetDiffExtremum(void) { return m_diff_extremum; } //+------------------------------------------------------------------+ //| 返回最小修改点数的值 | //+------------------------------------------------------------------+ double CTrailingClassic::GetStepModify(void) { return m_step_modify; } //+------------------------------------------------------------------+ //| 根据经典追踪止损算法按修改止损位置 | //| | //+------------------------------------------------------------------+ bool CTrailingClassic::Modify(void) { if(CheckPointer(m_position)==POINTER_INVALID) { string text="Invalid position for current trailing-stop. Set position with 'SetPosition' method"; CMessage *msg=new CMessage(MESSAGE_WARNING,__FUNCTION__,text); Log.AddMessage(msg); return false; } if(m_diff_extremum<=0.0) { string text="Set points trailing-stop with 'SetDiffExtremum' method"; CMessage *msg=new CMessage(MESSAGE_WARNING,__FUNCTION__,text); Log.AddMessage(msg); return false; } double extremum=FindExtremum(m_position); if(extremum == 0.0)return false; double n_sl = 0.0; if(m_position.Direction()==POSITION_TYPE_BUY) n_sl=extremum-m_diff_extremum; else n_sl=extremum+m_diff_extremum; if(n_sl!=m_position.StopLossValue()) return m_position.StopLossValue(n_sl); return false; } //+------------------------------------------------------------------+ //| 当持有头寸时,返回价格极值 | //| 对于多头头寸,将返回最高价。 | //| 对于空头头寸,返回最低价。 | //| | //+------------------------------------------------------------------+ double CTrailingClassic::FindExtremum(CPosition *pos) { double prices[]; if(pos.Direction()==POSITION_TYPE_BUY) { if(CopyHigh(pos.Symbol(),PERIOD_M1,pos.TimeOpen(),TimeCurrent(),prices)>1) return prices[ArrayMaximum(prices)]; } else { if(CopyLow(pos.Symbol(),PERIOD_M1,pos.TimeOpen(),TimeCurrent(),prices)>1) return prices[ArrayMinimum(prices)]; } return 0.0; } //+------------------------------------------------------------------+
类的基本代码包含在Modify和FindExtremum方法中。EA使用FindExtremum方法查找最高或最低价(取决于头寸类型)。因此,即使在策略重启或者空闲时,止损位置仍旧能够被正确的计算出来。
我们的追踪止损类包含了一些额外的但不晦涩的,以SHOW_TRAILING_CLASSIC_PARAMS宏和一些输入参数“input”形式表示的编程结构。我们将在后续独立的章节“向EA设置中添加追踪止损参数”中讨论这些结构。
向CImpulse策略中添加追踪止损
在之前的文章“通用智能交易系统:使用挂单和对冲设置”中我们第一次引入了CImpulse策略。它简单的交易策略是基于在价格激烈变动时进场交易。所提出的策略使用基于移动平均值的头寸管理方式。当一个柱形的开盘价低于移动平均时,策略将平仓一个多头头寸。当一个柱形的开盘价高于移动平均时,策略平仓一个空头头寸。在此再次描述下之前文章中实现这一逻辑的代码:
//+------------------------------------------------------------------+ //| 根据移动平均管理多头头寸 | //+------------------------------------------------------------------+ void CImpulse::SupportBuy(const MarketEvent &event,CPosition *pos) { if(!IsTrackEvents(event))return; ENUM_ACCOUNT_MARGIN_MODE mode = (ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE); if(mode != ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) { double target = Bid() - Bid()*(m_percent/100.0); if(target < Moving.OutValue(0)) pos.StopLossValue(target); else pos.StopLossValue(0.0); } if(Bid() < Moving.OutValue(0)) pos.CloseAtMarket(); } //+------------------------------------------------------------------+ //| 根据移动平均管理空头头寸 | //+------------------------------------------------------------------+ void CImpulse::SupportSell(const MarketEvent &event,CPosition *pos) { if(!IsTrackEvents(event))return; ENUM_ACCOUNT_MARGIN_MODE mode = (ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE); if(mode != ACCOUNT_MARGIN_MODE_RETAIL_HEDGING) { double target = Ask() + Ask()*(m_percent/100.0); if(target > Moving.OutValue(0)) pos.StopLossValue(target); else pos.StopLossValue(0.0); } if(Ask() > Moving.OutValue(0)) pos.CloseAtMarket(); }
让我们通过使用经典的追踪止损逻辑取代头寸管理规则来使之简化。我们把这段代码从策略中删除,并在策略的构造函数中添加一个经典的追踪止损函数作为默认设置。让我们将这个策略重新命名为:CImpulseTrailingAuto:
//+------------------------------------------------------------------+ //| 策略初始化并在启动时配置追踪止损 | //| | //+------------------------------------------------------------------+ CImpulseTrailing::CImpulseTrailing(void) { CTrailingClassic* classic = new CTrailingClassic(); classic.SetDiffExtremum(0.00100); Trailing = classic; }
现在,根据新的逻辑,头寸基于追踪止损来平仓,平仓位置为距离价格极值0.001。
带有自动追踪止损的CImpulse策略的完整代码在ImpulseTrailingAuto.mqh中提供。
向EA设置中添加追踪止损参数
我们已经创建的追踪止损方法非常的方便。但我们仍旧需要在自定义策略中配置追中止损的参数。我们要简化该过程:例如,某种程度上在EA设置中添加追踪止损参数。问题是如果不使用追踪止损,那么参数仍旧存在在EA的设置中,这会产生歧义。为了避免这种情况的发生,我们可以使用条件编译。让我们向经典追踪止损模块中添加参数以及特殊的条件编译宏SHOW_TRAILING_CLASSIC_PARAMS:
//+------------------------------------------------------------------+ //| TrailingClassic.mqh | //| Copyright 2016, Vasiliy Sokolov. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2016, Vasiliy Sokolov." #property link "http://www.mql5.com" #include "Trailing.mqh" //+------------------------------------------------------------------+ //| 将追踪止损参数集成在EA的参数列表中 | //| | //+------------------------------------------------------------------+ #ifdef SHOW_TRAILING_CLASSIC_PARAMS input double PointsModify = 0.00100; input double StepModify = 0.00005; #endif //+------------------------------------------------------------------+ //| 一个经典的追踪止损算法 | //+------------------------------------------------------------------+ class CTrailingClassic : public CTrailing { ... public: CTrailingClassic(void); ... }; //+------------------------------------------------------------------+ //| 构造函数。初始化参数 | //+------------------------------------------------------------------+ CTrailingClassic::CTrailingClassic(void) : m_diff_extremum(0.0), m_step_modify(0.0) { #ifdef SHOW_TRAILING_CLASSIC_PARAMS m_diff_extremum = PointsModify; m_step_modify = StepModify; #endif }
现在如果SHOW_TRAILING_CLASSIC_PARAMS宏被定义,在编译时追踪止损参数将被集成到EA设置中。
图 1. 动态链接参数PointsModify和StepModify。
当SHOW_TRAILING_CLASSIC_PARAMS宏被注释掉或者不存在时,追踪止损从EA的参数设置中消失:
图 2. 禁用追踪止损参数
SHOW_TRAILING_CLASSIC_PARAMS宏将追踪止损参数添加到EA的设置中,并额外地配置CTrailingClassic,使得在创建时参数被自动地添加到其中。因此,当策略创建这个宏时,已经包含了用户通过EA设置窗口配置的参数了。
基于移动平均线的追踪止损
在本文前述中,如果价格运动到移动平均线之上或之下时,CImpulse策略执行头寸平仓操作。移动平均以CIndMovingAverage指标类的形式出现。CIndMovingAverage类同追踪止损类非常相似。它计算移动平均的值并允许灵活配置指标参数。同追踪止损唯一不同的地方在于它没有头寸管理算法。CIndMovingAverage类没有Modify()方法。另一方面,CTrailing类已经包含了所有需要的方法,除了用于处理移动平均值的算法除外。我们可以使用组合法将这两者的优势结合起来,并创建一个基于这些类的新的追踪止损算法:基于移动平均的追踪止损算法。算法非常简单:它将头寸的止损设置为移动平均值。让我们向Modify方法中添加一个额外的效验:如果当前价格低于(多头头寸)或高于(空头头寸)所计算出来的止损位,那么应该按市价平仓当前头寸。完整的类如下:
//+------------------------------------------------------------------+ //| TrailingMoving.mqh | //| Copyright 2016, Vasiliy Sokolov. | //| http://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2016, Vasiliy Sokolov." #property link "http://www.mql5.com" #include "Trailing.mqh" #include "..\Indicators\MovingAverage.mqh" //+------------------------------------------------------------------+ //| 基于移动平均的追踪止损算法设置头寸的止损为 | //| MA值 | //+------------------------------------------------------------------+ class CTrailingMoving : public CTrailing { public: virtual bool Modify(void); CIndMovingAverage* Moving; virtual CTrailing* Copy(void); }; //+------------------------------------------------------------------+ //| 将头寸的止损设置为MA值 | //+------------------------------------------------------------------+ bool CTrailingMoving::Modify(void) { if(CheckPointer(Moving) == POINTER_INVALID) return false; double value = Moving.OutValue(1); if(m_position.Direction() == POSITION_TYPE_BUY && value > m_position.CurrentPrice()) m_position.CloseAtMarket(); else if(m_position.Direction() == POSITION_TYPE_SELL && value < m_position.CurrentPrice()) m_position.CloseAtMarket(); else if(m_position.StopLossValue() != value) return m_position.StopLossValue(value); return false; } //+------------------------------------------------------------------+ //| 返回CTrailingMoving实例的副本 | //+------------------------------------------------------------------+ CTrailing* CTrailingMoving::Copy(void) { CTrailingMoving* mov = new CTrailingMoving(); mov.Moving = Moving; return mov; }
Modify函数将当前止损位置同一定平均水平做比较。如果两者不相等,函数为头寸设置新的止损位置。使用前一个收盘柱形的移动平均价格,因为当前柱形的移动平均值总是在变化的。此外请注意MovingAverage指标被声明为一个指针。这样使得用户可以连接任何CIndMovingAverage类型的对象到这个追踪止损算法上。
现在让我们在策略测试器中检测这个类的运行结果。这里是一个展示它运作过程的简短视频:
单一头寸的独立追踪止损
我们已经分析了追踪止损的运行机制。通过使用同一的虚拟函数Modify,CStrategy交易引擎能够自动为每个头寸设置追踪止损,并且调用其止损位计算算法。通常这就足够了,但是在某些情况下我们可能需要单独管理每个头寸。这也就是说我们要为每个头寸应用一个追踪止损算法,并互不相同。这类追踪止损特性无法实现统一化或者在交易引擎侧实现,因此应在策略中实现此类控制。可以通过重写BuySupport和SellSupport方法来实现。此外,在这种情况下,没有必要像在自定义策略构造函数中那样,初始化一个默认的追踪止损。
假设CImpulse策略的多头头寸要使用一个基于移动平均线的追踪止损算法。而对于空头头寸,应用一个经典的追踪止损算法。这两种追踪止损算法在之前已经描述过。让我们像下面那样重写BuySupport和SellSupport方法:
//+------------------------------------------------------------------+ //| 根据移动平均管理多头头寸 | //+------------------------------------------------------------------+ void CImpulseTrailing::SupportBuy(const MarketEvent &event,CPosition *pos) { if(!IsTrackEvents(event)) return; if(pos.Trailing == NULL) { CTrailingMoving* trailing = new CTrailingMoving(); trailing.Moving = GetPointer(this.Moving); pos.Trailing = trailing; } pos.Trailing.Modify(); } //+------------------------------------------------------------------+ //| 根据移动平均管理空头头寸 | //+------------------------------------------------------------------+ void CImpulseTrailing::SupportSell(const MarketEvent &event,CPosition *pos) { if(!IsTrackEvents(event)) return; if(pos.Trailing == NULL) { CTrailingClassic* trailing = new CTrailingClassic(); trailing.SetDiffExtremum(0.00100); pos.Trailing = trailing; } pos.Trailing.Modify(); }
请注意基于移动平均的追踪止损,CIndMovingAverage类是一个参数集合。这个类作为Moving对象在策略中存在。在一行代码中,我们指出了用哪个对象来计算止损位置。
在SupportSell方法中,另一种追踪止损类型被应用于新的头寸,它有自己的参数集。使用距离极值价格0.001作为追踪止损距离。
CImpulse策略中每个头寸类型的单独追踪止损方法的完整代码在ImpulseTrailingManual.mqh文件中提供。
新版交易引擎的简短修改列表
自从第一篇关于交易引擎的文章发表以来,CStrategy交易引擎已经发生了巨大的变化。我们为其增加了新的函数和模块来扩展它的交易能力。同时,自从最初的文章发表以来,发布了多个带有各种修改的编译版本。某些改变和老版的CStrategy不兼容,因此我们不得不修改交易引擎。这些修正和扩展导致了不可避免的错误的出现。在这一章中,我们将把修复这些bug后的版本作为交易引擎的最新版。
- 项目中包括一个交易策略面板。由于之前版本的一个bug,考虑到兼容性问题,面板在第三章和第四章中被禁用了。在修复之后,面板再次在第五章中被添加了进来,但是没有正常的运行起来。在交易引擎的第六版中,面板的运作完全恢复了。
- 交易面板包含一个错误:它显示了“BuyOnly”模式两次而不是“SellOnly”。这一bug被修复了。
- 在之前的版本中,改变面板中的交易模式并不能真正改变策略中的交易模式。在第六版中这一bug被修复了。
- 添加了一种新的模式变更处理过程:在SellOnly模式,所有属于本策略的Buy单被平仓并且所有Buy方向的挂单被删除。对于BuyOnly模式也一样:所有Sell方向挂单被删除。当你选择“Stop”模式,所有双边挂单都会被删除。
如果在引擎的操作中发现任何bug请报告。发现的bug将被修复。
总结
本系列文章的第六篇向交易引擎中添加了新的功能。现在交易引擎支持追踪止损。每一个追踪止损都是一个特殊的类,包含了标准化的Modify方法用于修改止损水平。它还包含特定的数据和参数用于配置移动追踪算法。追踪止损可以有两种使用方法。
- 追踪止损操作可以被实现的交易引擎的一个策略中或者启用自动模式。在后一种情况下,默认的追踪止损将被自动地应用到一个头寸上。在这种情况下,无需个性化策略来做控制。
- 个性化策略下的追踪止损管理。在这种情况下个性化策略使用追踪止损函数来管理它自己的头寸。该模式可以实现复杂的控制逻辑 — 例如,对不同的头寸类型使用不同的追踪止损逻辑。