
如何开发各种类型的追踪止损并将其加入到EA中
内容
引言
延续 之前文章中提及的追踪止损(Trailing Stop)的话题,这里我们将讨论追踪类,以便于创建各种追踪止损算法。基于所创建的类,我们可以创建任何用于改变止损位置的算法:根据当前价格的止损移动、根据指标、根据指定的止损价格等等。阅读完本文后,我们将能够创建并将任何移动止损算法添加到任意EA上。同时,追踪止损的嵌入和使用将变得方便且清晰。
让我们简要地回顾一下追踪止损操作的算法。让我们定义每个追踪止损可采用以下三个运行条件:
- 触发追踪起始点 — 达到该盈利点数时触发追踪止损;
- 追踪步长 — 价格朝盈利方向移动多少点后,下一个止损位置应相应移动;
- 追踪距离 — 当前价格与止损位置之间的距离。
这三个参数可以应用于任何追踪止损策略。在追踪设置中,这些参数可能全部需要。但如果某个参数不需要或已被算法中的某个值替代,那么也可以只需要部分参数。若将止损位置设置在某个指标值处,那么它可以代替“追踪距离”参数。在这种情况下,如果使用该参数,则止损值不会直接使用指标指示的价格,而是根据距离指示价格的点数来设置。
总体而言,上述三个参数在各类追踪止损策略中使用最为广泛,我们将在创建追踪类时予以考虑。
在将止损位置移动到所需价格时,应执行以下检查:
- 止损价格不应比当前价格更接近交易品种的止损级别值(SYMBOL_TRADE_STOPS_LEVEL — 设置止损订单时,从当前收盘价的最小点数偏移);
- 止损价格不应与已设置的价格相同,对于多头仓位应高于当前止损价格,对于空头仓位则应低于当前止损价格;
- 如果追踪算法使用上述参数,则还需要检查这些参数设定的条件。
这些是基本检查。任何追踪算法的工作原理都相同:输入设置止损所需的价格,检查所有必要的限制条件,如果所有检查都通过,则将止损位置移动到指定位置。
以下是简单追踪止损的工作原理:
- 在“触发追踪起始点”参数中,指示达到多少盈利点数时开始追踪,如果不使用该参数,则设为零;
- “追踪步长”参数指定盈利多少点后,应跟随价格上拉止损级别,如果不使用该参数,则设为零;
- “追踪距离”参数设置当前价格与止损位置之间的距离,如果不使用该参数,则设为零;
- 此外,我们可以设置要追踪的仓位的品种和magic编号,或分别设为NULL和-1,以追踪所有品种的任何magic编号的仓位。
当持仓盈利达到指定点数时,便开始使用跟踪止损,并将持仓止损设置在距离当前价格指定的距离处。接下来,当价格朝着持仓盈利方向移动了指定点数(即跟踪步长)后,持仓止损会再次根据价格移动,以保持与价格之间的指定距离。这一过程会一直持续到价格与持仓方向相反变动为止。在这种情况下,止损将保持在已设定的位置。一旦价格达到该止损位置,持仓将因“止损”而获利平仓。因此,追踪止损允许我们在价格反转时通过平仓来保留利润,同时允许价格在指定止损偏移范围内“游走”,如果价格朝着持仓方向移动,则逐渐将止损点跟随价格移动。
我们还可以使用一个从指标中获取的价格来代替“追踪距离”参数。在这种情况下,我们将得到一个基于指标的追踪算法。我们可以传递前一个K线图的某个值(例如,最高价、最低价)——然后我们将得到基于K线图价格的追踪,等等。但是基本的追踪止损算法保持不变。
因此,为了创建追踪止损算法,首先需要创建一个简单的跟踪算法,该算法允许我们向其传递止损头寸所需的价格,并在执行所有必要检查后,将头寸的止损移动到该位置。
在之前的文章中,我们已经探讨了所含的细节,其中包括简单的追踪以及基于各种指标的追踪。这种方法允许我们将追踪止损嵌入到EA中,以追踪当前交易品种的头寸。但它也有其局限性:我们需要在EA中创建一个用于追踪的指标,并将每个指标的句柄发送给追踪函数。追踪函数本身只能处理当前交易品种的头寸。
如果我们使用追踪类,我们可以创建一个追踪类的多个实例,每个实例具有不同的设置,然后这样创建的所有追踪止损都可以根据程序员指定的特定算法在一个EA中同时工作。换句话说,我们创建了一个自定义追踪,用几行代码设置一些列参数后,它便能根据自身的算法进行工作。每个追踪算法都可以在EA中在满足特定条件时启动,从而为追踪止损头寸创建复杂算法。
即使我们为不同的交易品种创建了两个相同的追踪止损,那么对于每个交易品种,都会创建一个单独的追踪,它将根据创建时指定的交易品种的数据运行。这大大简化了追踪算法在程序中的使用:我们只需要用所需的参数创建一个追踪功能块,并在EA中调用它。公平地说,应该指出,在某些算法中,这种方法可能不是最优的,因为每种类型的追踪都会在终端中启动对现有头寸的检索。如果我们制作一个真正通用的算法,该算法为每个追踪止损函数使用相同的终端头寸列表,那么可能会发现其设计和开发的成本对比其需求来说远远不值当。此外,这个话题超出了本文的范围。
因此,首先我们需要编写基础部分:一个简单的跟踪止损类,该类将头寸的止损移动到指定价格,同时执行所有必要的检查。
在之前的文章中,这个简单的基本追踪函数被称为TrailingStopByValue()。在这里,我们将它称为SimpleTrailing。 它将在自定义程序中被充分使用。
与之前的文章一样,我们将所有跟踪类放在一个文件中。然后只需将这个文件连接到EA中。通常,包含文件存储在\MQL5\Include\文件夹或其子文件夹中。
基本追踪止损类
在MetaTrader 5(MQL5)终端的\MQL5\Include\文件夹中,创建一个名为Trailings\的新子文件夹,并在该子文件夹中创建一个名为Trailings.mqh的新类文件。
类名应为CSimpleTrailing,已创建的文件名为Trailings.mqh,同时该类应继承自CObject标准库中的基类。
在MQL向导完成其工作后,我们在\MQL5\Include\Trailings\Trailings.mqh中得到了以下类模板:
//+------------------------------------------------------------------+ //| Trailings.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" class CSimpleTrailing : public CObject { private: public: CSimpleTrailing(); ~CSimpleTrailing(); }; //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CSimpleTrailing::CSimpleTrailing() { } //+------------------------------------------------------------------+ //| | //+------------------------------------------------------------------+ CSimpleTrailing::~CSimpleTrailing() { } //+------------------------------------------------------------------+
将标准库的基类对象包含到所创建的文件中:
//+------------------------------------------------------------------+ //| Trailings.mqh | //| Copyright 2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Object.mqh> //+------------------------------------------------------------------+ //| Class of the position StopLoss simple trailing | //+------------------------------------------------------------------+ class CSimpleTrailing : public CObject
为什么必须从标准库的基类继承所有要创建的追踪类?
这使我们能够轻松地从这些类中创建追踪集合,将它们放入列表中,并在列表中搜索所需的对象等。换句话说,如果我们只是想在未来的EA文件中将这些类的对象作为实例来使用,那么继承自CObject并不是必要的。但是,如果我们想从这些类中构建出功能完备的集合,并利用标准库的所有功能,那么这些类就应该从CObject基类派生出来:
CObject类是MQL5标准库构建的基础类,并为它的所有派生类提供了成为链表元素的能力。
此外,还为在派生类中进一步实现定义了一系列虚拟方法。
接下来,声明三个类的私有方法:
class CSimpleTrailing : public CObject { private: //--- check the criteria for modifying the StopLoss position and return the flag bool CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, MqlTick &tick); //--- modify StopLoss of a position by its ticket bool ModifySL(const ulong ticket, const double stop_loss); //--- return StopLevel in points int StopLevel(void); protected:
CheckCriterion() 方法将返回一个标志,表示所有用于修改止损位置的必要检查都已成功通过(或未通过)。
ModifySL() 方法将按指定值修改持仓的止损。
StopLevel() 方法将返回交易品种 StopLevel 属性的值。
在之前的文章中,所有这些方法都是独立的函数。现在,它们将作为基础追踪类的一部分工作,其他类型追踪止损的一些类也将继承自该类。
类运行所需的其余所有变量都声明为保护型变量:
protected: string m_symbol; // trading symbol long m_magic; // EA ID double m_point; // Symbol Point int m_digits; // Symbol digits int m_offset; // stop distance from price int m_trail_start; // profit in points for launching trailing uint m_trail_step; // trailing step uint m_spread_mlt; // spread multiplier for returning StopLevel value bool m_active; // trailing activity flag //--- calculate and return the StopLoss level of the selected position virtual double GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick); public:
在这里声明的所有变量中,只有“点差乘数(spread multiplier)”需要解释。交易品种的StopLevel值是当前价格到某个距离,在该距离内不能设置止损。如果服务器上StopLevel的值被设置为零,这并不意味着没有StopLevel。相反,这表示止损级别是浮动的,并且通常等于两个点的点差,或者有时是三个点。这都取决于服务器的设置。因此,在这里我们可以自行设置这个乘数。如果StopLevel值使用的是双倍点差,那么m_spread_mlt变量的值就设置为2(默认值)。如果是三倍点差,那么就设置为3,以此类推。
GetStopLossValue() 虚拟方法用于计算和返回一个持仓的止损价格。在不同的追踪止损类型中,止损位的计算方式是不同的。正因如此,这个方法被声明为虚拟的,并且在每个继承自该类的追踪类中,如果其中的止损计算方式与该类方法中将编写的计算方式不同,那么就应该对这个方法进行重写。
在类的公共部分(public),编写用于设置和返回在类的受保护部分(protected)声明的变量值的方法,以及类的构造函数和析构函数:
public: //--- set trailing parameters void SetSymbol(const string symbol) { this.m_symbol = (symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_point =::SymbolInfoDouble(this.m_symbol, SYMBOL_POINT); this.m_digits = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_DIGITS); } void SetMagicNumber(const long magic) { this.m_magic = magic; } void SetStopLossOffset(const int offset) { this.m_offset = offset; } void SetTrailingStart(const int start) { this.m_trail_start = start; } void SetTrailingStep(const uint step) { this.m_trail_step = step; } void SetSpreadMultiplier(const uint value) { this.m_spread_mlt = value; } void SetActive(const bool flag) { this.m_active = flag; } //--- return trailing parameters string Symbol(void) const { return this.m_symbol; } long MagicNumber(void) const { return this.m_magic; } int StopLossOffset(void) const { return this.m_offset; } int TrailingStart(void) const { return this.m_trail_start; } uint TrailingStep(void) const { return this.m_trail_step; } uint SpreadMultiplier(void) const { return this.m_spread_mlt; } bool IsActive(void) const { return this.m_active; } //--- launch trailing with StopLoss offset from the price bool Run(void); //--- constructors CSimpleTrailing() : m_symbol(::Symbol()), m_point(::Point()), m_digits(::Digits()), m_magic(-1), m_trail_start(0), m_trail_step(0), m_offset(0), m_spread_mlt(2) {} CSimpleTrailing(const string symbol, const long magic, const int trailing_start, const uint trailing_step, const int offset); //--- destructor ~CSimpleTrailing() {} };
这个类有两个构造函数:
- 第一个(默认)构造函数使用当前交易品种,并将magic编号设置为-1(表示当前交易品种的所有持仓,无一例外),同时将追踪参数设置为零。
- 第二个构造函数将是参数化的,其形式参数会将交易品种名称、持仓magic编号以及三个追踪参数(起始值、步长和偏移量)传递给构造函数。
让我们来考虑参数化构造函数的实现:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CSimpleTrailing::CSimpleTrailing(const string symbol, const long magic, const int trail_start, const uint trail_step, const int offset) : m_spread_mlt(2) { this.SetSymbol(symbol); this.m_magic = magic; this.m_trail_start= trail_start; this.m_trail_step = trail_step; this.m_offset = offset; }
与默认构造函数不同,在默认构造函数中,初始化字符串将所有变量设置为当前交易品种的值,并将追踪参数设置为零值,而在这里的参数化构造函数中,它会将接收到的值设置到相应的变量中。SetSymbol() 方法会同时设置多个变量的值:
void SetSymbol(const string symbol) { this.m_symbol = (symbol==NULL || symbol=="" ? ::Symbol() : symbol); this.m_point =::SymbolInfoDouble(this.m_symbol, SYMBOL_POINT); this.m_digits = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_DIGITS); }
这两个构造函数中,点差乘数都被设置为2。如有必要,可以使用SetSpreadMultiplier()方法来更改它。
计算并返回所选持仓的止损位置的虚拟方法:
//+------------------------------------------------------------------+ //| Calculate and return the StopLoss level of the selected position | //+------------------------------------------------------------------+ double CSimpleTrailing::GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick) { //--- calculate and return the StopLoss level depending on the position type switch(pos_type) { case POSITION_TYPE_BUY : return(tick.bid - this.m_offset * this.m_point); case POSITION_TYPE_SELL : return(tick.ask + this.m_offset * this.m_point); default : return 0; } }
由于这是一个简单的追踪止损,持仓的止损价位总是保持在当前价位的一个指定距离处。
该方法会返回计算后的持仓的止损价位,然后这些值会在CheckCriterion()方法中进行验证:
//+------------------------------------------------------------------+ //|Check the StopLoss modification criteria and return a flag | //+------------------------------------------------------------------+ bool CSimpleTrailing::CheckCriterion(ENUM_POSITION_TYPE pos_type, double pos_open, double pos_sl, double value_sl, MqlTick &tick) { //--- if the stop position and the stop level for modification are equal or a zero StopLoss is passed, return 'false' if(::NormalizeDouble(pos_sl - value_sl, this.m_digits) == 0 || value_sl==0) return false; //--- trailing variables double trailing_step = this.m_trail_step * this.m_point; // convert the trailing step into price double stop_level = this.StopLevel() * this.m_point; // convert the symbol StopLevel into price int pos_profit_pt = 0; // position profit in points //--- depending on the type of position, check the conditions for modifying StopLoss switch(pos_type) { //--- long position case POSITION_TYPE_BUY : pos_profit_pt = int((tick.bid - pos_open) / this.m_point); // calculate the position profit in points if(tick.bid - stop_level > value_sl // if the StopLoss level is lower than the price with the StopLevel level set down from it (the distance according to StopLevel is maintained) && pos_sl + trailing_step < value_sl // if the StopLoss level exceeds the trailing step set upwards from the current position StopLoss && (this.m_trail_start == 0 || pos_profit_pt > this.m_trail_start) // if we trail at any profit or position profit in points exceeds the trailing start, return 'true' ) return true; break; //--- short position case POSITION_TYPE_SELL : pos_profit_pt = int((pos_open - tick.ask) / this.m_point); // calculate the position profit in points if(tick.ask + stop_level < value_sl // if the StopLoss level is higher than the price with the StopLevel level set upwards from it (the distance according to StopLevel is maintained) && (pos_sl - trailing_step > value_sl || pos_sl == 0) // if the StopLoss level is below the trailing step set downwards from the current StopLoss or a position has no StopLoss && (this.m_trail_start == 0 || pos_profit_pt > this.m_trail_start) // if we trail at any profit or position profit in points exceeds the trailing start, return 'true' ) return true; break; //--- return 'false' by default default: break; } //--- conditions are not met - return 'false' return false; }
如果传递给方法的止损(StopLoss)值满足所有过滤器的所有标准,该方法返回true,并且使用ModifySL()方法修改持仓的止损价:
//+------------------------------------------------------------------+ //| Modify StopLoss of a position by ticket | //+------------------------------------------------------------------+ bool CSimpleTrailing::ModifySL(const ulong ticket, const double stop_loss) { //--- if the EA stop flag is set, report this in the journal and return 'false' if(::IsStopped()) { Print("The Expert Advisor is stopped, trading is disabled"); return false; } //--- if failed to select a position by ticket, report this in the journal and return 'false' ::ResetLastError(); if(!::PositionSelectByTicket(ticket)) { ::PrintFormat("%s: Failed to select position by ticket number %I64u. Error %d", __FUNCTION__, ticket, ::GetLastError()); return false; } //--- declare the structures of the trade request and the request result MqlTradeRequest request= {}; MqlTradeResult result = {}; //--- fill in the request structure request.action = TRADE_ACTION_SLTP; request.symbol = ::PositionGetString(POSITION_SYMBOL); request.magic = ::PositionGetInteger(POSITION_MAGIC); request.tp = ::PositionGetDouble(POSITION_TP); request.position = ticket; request.sl = ::NormalizeDouble(stop_loss, this.m_digits); //--- if the trade operation could not be sent, report this to the journal and return 'false' if(!::OrderSend(request, result)) { PrintFormat("%s: OrderSend() failed to modify position #%I64u. Error %d",__FUNCTION__, ticket, ::GetLastError()); return false; } //--- request to change StopLoss position successfully sent return true; }
使用 StopLevel() 方法获取StopLevel值:
//+------------------------------------------------------------------+ //| Return StopLevel in points | //+------------------------------------------------------------------+ int CSimpleTrailing::StopLevel(void) { int spread = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_SPREAD); int stop_level = (int)::SymbolInfoInteger(this.m_symbol, SYMBOL_TRADE_STOPS_LEVEL); return int(stop_level == 0 ? spread * this.m_spread_mlt : stop_level); }
由于止损价位不是一个固定值,而是可以动态变化的,因此每次我们调用该方法时,都会从交易品种属性中请求所需的数值。如果某个交易品种的止损价位被设置为零,那么我们使用交易品种点差乘以2的数值。这是默认值。
在之前的文章中,我们已经将上述讨论的所有三个方法作为独立的函数实现了。现在,这些方法被隐藏在类的私有部分,并在没有外部访问的情况下执行其功能。
主要的追踪方法是Run()方法,它循环选择持仓和将其止损价位移动到计算所得数值:
//+------------------------------------------------------------------+ //| Launch simple trailing with StopLoss offset from the price | //+------------------------------------------------------------------+ bool CSimpleTrailing::Run(void) { //--- if disabled, leave if(!this.m_active) return false; //--- trailing variables MqlTick tick = {}; // price structure bool res = true; // result of modification of all positions //--- check the correctness of the data by symbol if(this.m_point==0) { //--- let's try to get the data again ::ResetLastError(); this.SetSymbol(this.m_symbol); if(this.m_point==0) { ::PrintFormat("%s: Correct data was not received for the %s symbol. Error %d",__FUNCTION__, this.m_symbol, ::GetLastError()); return false; } } //--- in a loop by the total number of open positions int total =::PositionsTotal(); for(int i = total - 1; i >= 0; i--) { //--- get the ticket of the next position ulong pos_ticket =::PositionGetTicket(i); if(pos_ticket == 0) continue; //--- get the symbol and position magic string pos_symbol = ::PositionGetString(POSITION_SYMBOL); long pos_magic = ::PositionGetInteger(POSITION_MAGIC); //--- if the position does not match the filter by symbol and magic number, leave if((this.m_magic != -1 && pos_magic != this.m_magic) || (pos_symbol != this.m_symbol)) continue; //--- if failed to get the prices, move on if(!::SymbolInfoTick(this.m_symbol, tick)) continue; //--- get the position type, its opening price and StopLoss level ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)::PositionGetInteger(POSITION_TYPE); double pos_open =::PositionGetDouble(POSITION_PRICE_OPEN); double pos_sl =::PositionGetDouble(POSITION_SL); //--- get the calculated StopLoss level double value_sl = this.GetStopLossValue(pos_type, tick); //--- if StopLoss modification conditions are suitable, modify the position stop level and add the result to the res variable if(this.CheckCriterion(pos_type, pos_open, pos_sl, value_sl, tick)) res &=this.ModifySL(pos_ticket, value_sl); } //--- at the end of the loop, return the result of modifying each position that matches the "symbol/magic" filter return res; }
该方法会遍历终端活跃持仓列表中的持仓,按照交易品种和magic编号对它们进行排序,并将持仓的止损价位移动到与当前价格计算得出的距离上。每个持仓止损价的修改结果会被添加到res变量中。在所有持仓止损价循环修改结束时,我们要么设置true(如果所有符合交易品种/magic编号筛选条件的持仓止损价都成功修改),要么(如果至少有一个持仓没有成功修改)设置标志位为false。这是为了从外部对止损修改进行额外的控制。
现在,让我们将这个类加入EA中,以测试其功能。
使用终端中\MQL5\Experts\Examples\文件夹下的Moving Average.mq5来测试,并将其保存为MovingAverageWithSimpleTrail.mq5。
将追踪类文件包含到EA中,在设置中输入额外的参数,并创建一个简单追踪类的实例:
//+------------------------------------------------------------------+ //| MovingAverageWithSimpleTrail.mq5 | //| Copyright 2000-2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" #include <Trade\Trade.mqh> #include <Trailings\Trailings.mqh> input group " - Moving Averages Expert Parameters -" input double MaximumRisk = 0.02; // Maximum Risk in percentage input double DecreaseFactor = 3; // Descrease factor input int MovingPeriod = 12; // Moving Average period input int MovingShift = 6; // Moving Average shift input group " - Simple Trailing Parameters -" input int InpTrailingStart = 10; // Trailing start input int InpTrailingStep = 20; // Trailing step in points input int InpTrailingOffset = 30; // Trailing offset in points input bool InpUseTrailing = true; // Use Trailing Stop //--- int ExtHandle=0; bool ExtHedging=false; CTrade ExtTrade; CSimpleTrailing ExtTrailing; // simple trailing class instance #define MA_MAGIC 1234501 //+------------------------------------------------------------------+
在OnInit()程序中设置追踪参数:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit(void) { //--- prepare trade class to control positions if hedging mode is active ExtHedging=((ENUM_ACCOUNT_MARGIN_MODE)AccountInfoInteger(ACCOUNT_MARGIN_MODE)==ACCOUNT_MARGIN_MODE_RETAIL_HEDGING); ExtTrade.SetExpertMagicNumber(MA_MAGIC); ExtTrade.SetMarginMode(); ExtTrade.SetTypeFillingBySymbol(Symbol()); //--- Moving Average indicator ExtHandle=iMA(_Symbol,_Period,MovingPeriod,MovingShift,MODE_SMA,PRICE_CLOSE); if(ExtHandle==INVALID_HANDLE) { printf("Error creating MA indicator"); return(INIT_FAILED); } //--- set trailing parameters ExtTrailing.SetActive(InpUseTrailing); ExtTrailing.SetSymbol(Symbol()); ExtTrailing.SetMagicNumber(MA_MAGIC); ExtTrailing.SetTrailingStart(InpTrailingStart); ExtTrailing.SetTrailingStep(InpTrailingStep); ExtTrailing.SetStopLossOffset(InpTrailingOffset); //--- ok return(INIT_SUCCEEDED); }
在OnTick()中, 运行:
//+------------------------------------------------------------------+ //| Expert tick function | //+------------------------------------------------------------------+ void OnTick(void) { //--- if(SelectPosition()) CheckForClose(); else CheckForOpen(); //--- launch trailing ExtTrailing.Run(); }
这就是需要额外添加到EA文件中的全部内容。让我们对其进行编译,并在EURUSD M15的时间框架上,使用策略测试器在去年时间段上进行一次测试。所有价格变动(ticks)均不考虑交易延迟。
参数设置如下(未启用追踪):
让我们不使用追踪止损来进行测试并看下结果:
现在启用追踪止损:
然后运行相同的测试。
测试结果如下:
结果稍稍好些。很明显,在这些设置下,追踪止损功能轻微地改善了统计数据。
现在,我们可以基于已创建的简单追踪功能来制作任何其他类型的追踪止损了。
让我们实现几个基于指标的追踪类,包括抛物线转向(Parabolic SAR)以及终端中列出的趋势跟踪指标列表中的各种移动平均线。
基于指标的追踪止损类
让我们继续在同一个文件中编写代码。对于基于指标的追踪功能,我们需要创建所需的指标。为此,我们需要指定用于构建数据系列的货币对和时间框架。我们还需要将创建的指标的句柄保存在一个变量中,以便访问该指标。通过句柄接收数据不依赖于指标类型——数据是通过CopyBuffer()函数获得的,同时在调用时指定指标的句柄。
所有从指标获取数据的操作都应该在基于指标的追踪类中设置。在这种情况下,我们必须在处理不同指标的追踪类的每个类中设置所有这些操作。这是可行的,但不是最优的。最好是创建一个基于指标的追踪的基础类,该类将实现通过句柄从指标获取数据的方法,并指定一些对于每个指标都相同的变量(如货币对、时间框架、数据时间序列索引和指标句柄)。追踪类本身将从基类派生。然后,类结构将组织得更加清晰。基础类将提供通过句柄访问指标数据,而派生类将包含所需指标的创建和处理其数据的方法。
让我们继续在Trailings.mqh文件中编写代码。让我们实现一个基于指标的追踪止损基类:
//+------------------------------------------------------------------+ //| Base class of indicator-based trailings | //+------------------------------------------------------------------+ class CTrailingByInd : public CSimpleTrailing { protected: ENUM_TIMEFRAMES m_timeframe; // indicator timeframe int m_handle; // indicator handle uint m_data_index; // indicator data bar string m_timeframe_descr; // timeframe description //--- return indicator data double GetDataInd(void) const { return this.GetDataInd(this.m_data_index); } //--- calculate and return the StopLoss level of the selected position virtual double GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick); public: //--- set parameters void SetTimeframe(const ENUM_TIMEFRAMES timeframe) { this.m_timeframe=(timeframe==PERIOD_CURRENT ? ::Period() : timeframe); this.m_timeframe_descr=::StringSubstr(::EnumToString(this.m_timeframe), 7); } void SetDataIndex(const uint index) { this.m_data_index=index; } //--- return parameters ENUM_TIMEFRAMES Timeframe(void) const { return this.m_timeframe; } uint DataIndex(void) const { return this.m_data_index; } string TimeframeDescription(void) const { return this.m_timeframe_descr; } //--- return indicator data from the specified timeseries index double GetDataInd(const int index) const; //--- constructors CTrailingByInd(void) : CSimpleTrailing(::Symbol(), -1, 0, 0, 0), m_timeframe(::Period()), m_handle(INVALID_HANDLE), m_data_index(1), m_timeframe_descr(::StringSubstr(::EnumToString(::Period()), 7)) {} CTrailingByInd(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const int trail_start, const uint trail_step, const int trail_offset) : CSimpleTrailing(symbol, magic, trail_start, trail_step, trail_offset), m_data_index(1), m_handle(INVALID_HANDLE) { this.SetTimeframe(timeframe); } //--- destructor ~CTrailingByInd(void) { if(this.m_handle!=INVALID_HANDLE) { ::IndicatorRelease(this.m_handle); this.m_handle=INVALID_HANDLE; } } };
该类继承自简单的追踪类,因此它拥有该类的所有功能,以及通过句柄访问指标数据的方法。
在类的受保护部分中,声明了类变量、一个通过句柄从指标接收数据的方法,以及一个用于计算止损(StopLoss)价格的虚方法,该方法重写了父类中同名的方法。
类的公共部分包含了设置和获取指标属性的方法、构造函数和析构函数。
在默认构造函数的初始化字符串中,向父类传递了一个值为-1(表示所有仓位)的magic编号和零个追踪参数。用于计算指标的时间框架是当前图表的时间框架。
在参数化构造函数中,通过构造函数的形参传递了用于计算指标的图表货币对、图表周期以及所有追踪参数。
在类的析构函数中,释放了指标的运算部分,并将存储句柄的变量值设置为INVALID_HANDLE。
以下是从指定时间序列索引返回指标数据的方法:
//+------------------------------------------------------------------+ //| Return indicator data from the specified timeseries index | //+------------------------------------------------------------------+ double CTrailingByInd::GetDataInd(const int index) const { //--- if the handle is invalid, report this and return "empty value" if(this.m_handle==INVALID_HANDLE) { ::PrintFormat("%s: Error. Invalid handle",__FUNCTION__); return EMPTY_VALUE; } //--- get the value of the indicator buffer by the specified index double array[1]; ::ResetLastError(); if(::CopyBuffer(this.m_handle, 0, index, 1, array)!=1) { ::PrintFormat("%s: CopyBuffer() failed. Error %d", __FUNCTION__, ::GetLastError()); return EMPTY_VALUE; } //--- return the received value return array[0]; }
使用CopyBuffer()函数根据指定的索引获取单个指标柱的值,如果成功获取,则返回该值。如果发生错误,则在日志中显示一条消息,并返回一个空值(EMPTY_VALUE)。
CopyBuffer()函数通过指标的句柄处理来自任何指标的数据。因此,这个方法是通用的,并且在每个基于指标的追踪类中都可以不加修改地使用。
计算并返回所选持仓的止损位置的虚拟方法:
//+------------------------------------------------------------------+ //| Calculate and return the StopLoss level of the selected position | //+------------------------------------------------------------------+ double CTrailingByInd::GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick) { //--- get the indicator value as a level for StopLoss double data=this.GetDataInd(); //--- calculate and return the StopLoss level depending on the position type switch(pos_type) { case POSITION_TYPE_BUY : return(data!=EMPTY_VALUE ? data - this.m_offset * this.m_point : tick.bid); case POSITION_TYPE_SELL : return(data!=EMPTY_VALUE ? data + this.m_offset * this.m_point : tick.ask); default : return 0; } }
在这个方法中,也就是在父类中,我们简单地根据价格计算出止损位的偏移量。在同一个类的这个方法中,我们需要从指标接收数据,并将其作为止损价格传递。如果没有从指标接收到数据,该方法将返回当前价格。这将防止我们设置仓位的止损,因为止损水平(StopLevel)不允许我们将止损设置在当前价格。
因此,这个类实现了通过句柄访问指标数据,并根据指标值计算仓位止损价格的功能,并且它的所有方法都将在子类中可用。
现在我们可以基于创建的类来实现基于指标的追踪止损类。
抛物线指标追踪止损类
让我们继续在相同的文件\MQL5\Include\Trailings\Trailings.mqh中编写代码。
像所有其他基于指标的追踪类一样,抛物线SAR追踪类应该从基于指标的追踪类的基类派生:
//+------------------------------------------------------------------+ //| Parabolic SAR position StopLoss trailing class | //+------------------------------------------------------------------+ class CTrailingBySAR : public CTrailingByInd { private: double m_sar_step; // Parabolic SAR Step parameter double m_sar_max; // Parabolic SAR Maximum parameter public: //--- set Parabolic SAR parameters void SetSARStep(const double step) { this.m_sar_step=(step<0.0001 ? 0.0001 : step); } void SetSARMaximum(const double max) { this.m_sar_max =(max <0.0001 ? 0.0001 : max); } //--- return Parabolic SAR parameters double SARStep(void) const { return this.m_sar_step; } double SARMaximum(void) const { return this.m_sar_max; } //--- create Parabolic SAR indicator and return the result bool Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const double sar_step, const double sar_maximum); //--- constructors CTrailingBySAR() : CTrailingByInd(::Symbol(), ::Period(), -1, 0, 0, 0) { this.Initialize(this.m_symbol, this.m_timeframe, 0.02, 0.2); } CTrailingBySAR(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const double sar_step, const double sar_maximum, const int trail_start, const uint trail_step, const int trail_offset); //--- destructor ~CTrailingBySAR(){} };
在类的私有部分,声明用于存储抛物线SAR指标参数值的变量。
公共部分包含用于设置和返回指标属性值的方法。我们声明了用于创建指标的方法,以及两个构造函数——默认构造函数和参数化构造函数。
在默认构造函数中,默认在当前交易品种和图表周期上创建指标,同时将追踪参数值设置为零。
在参数化构造函数中,所有参数都通过构造函数的输入传递,然后根据传递给构造函数的参数创建指标:
//+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CTrailingBySAR::CTrailingBySAR(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const double sar_step, const double sar_maximum, const int trail_start, const uint trail_step, const int trail_offset) : CTrailingByInd(symbol, timeframe, magic, trail_start, trail_step, trail_offset) { this.Initialize(symbol, timeframe, sar_step, sar_maximum); }
Initialize() 方法用于创建抛物线转向(Parabolic SAR)指标,并返回其创建结果。
//+------------------------------------------------------------------+ //| create Parabolic SAR indicator and return the result | //+------------------------------------------------------------------+ bool CTrailingBySAR::Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const double sar_step, const double sar_maximum) { this.SetSymbol(symbol); this.SetTimeframe(timeframe); this.SetSARStep(sar_step); this.SetSARMaximum(sar_maximum); ::ResetLastError(); this.m_handle=::iSAR(this.m_symbol, this.m_timeframe, this.m_sar_step, this.m_sar_max); if(this.m_handle==INVALID_HANDLE) { ::PrintFormat("Failed to create iSAR(%s, %s, %.3f, %.2f) handle. Error %d", this.m_symbol, this.TimeframeDescription(), this.m_sar_step, this.m_sar_max, ::GetLastError()); } return(this.m_handle!=INVALID_HANDLE); }
让我们测试基于Parabolic SAR指标的追踪止损类。
为了进行测试,我们将使用标准交付中的EA文件\MQL5\Experts\Advisors\ExpertMACD.mq5,将其保存为ExpertMACDWithTrailingBySAR.mq5,并添加必要的代码段。
包含跟踪类的文件:添加新的输入变量并声明追踪止损类的实例:
//+------------------------------------------------------------------+ //| ExpertMACD.mq5 | //| Copyright 2000-2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Include | //+------------------------------------------------------------------+ #include <Expert\Expert.mqh> #include <Expert\Signal\SignalMACD.mqh> #include <Expert\Trailing\TrailingNone.mqh> #include <Expert\Money\MoneyNone.mqh> #include <Trailings\Trailings.mqh> //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ //--- inputs for expert input group " - ExpertMACD Parameters -" input string Inp_Expert_Title ="ExpertMACD"; int Expert_MagicNumber =10981; bool Expert_EveryTick =false; //--- inputs for signal input int Inp_Signal_MACD_PeriodFast =12; input int Inp_Signal_MACD_PeriodSlow =24; input int Inp_Signal_MACD_PeriodSignal=9; input int Inp_Signal_MACD_TakeProfit =50; input int Inp_Signal_MACD_StopLoss =20; input group " - Trailing By SAR Parameters -" input ENUM_TIMEFRAMES InpTimeframeSAR = PERIOD_CURRENT; // Parabolic SAR Timeframe input double InpStepSAR = 0.02; // Parabolic SAR Step input double InpMaximumSAR = 0.2; // Parabolic SAR Maximum input int InpTrailingStart = 0; // Trailing Start input uint InpTrailingStep = 0; // Trailing Step input int InpTrailingOffset = 0; // Trailing Offset input bool InpUseTrailing = true; // Use Trailing Stop //+------------------------------------------------------------------+ //| Global expert object | //+------------------------------------------------------------------+ CExpert ExtExpert; CTrailingBySAR ExtTrailing;
在OnInit()函数中,初始化基于抛物线指标的追踪类:
//+------------------------------------------------------------------+ //| Initialization function of the expert | //+------------------------------------------------------------------+ int OnInit(void) { //--- Initializing trailing if(ExtTrailing.Initialize(NULL,PERIOD_CURRENT,InpStepSAR,InpMaximumSAR)) { ExtTrailing.SetMagicNumber(Expert_MagicNumber); ExtTrailing.SetTrailingStart(InpTrailingStart); ExtTrailing.SetTrailingStep(InpTrailingStep); ExtTrailing.SetStopLossOffset(InpTrailingOffset); ExtTrailing.SetActive(InpUseTrailing); } //--- Initializing expert
在OnTick()函数中,运行追踪止损:
//+------------------------------------------------------------------+ //| Function-event handler "tick" | //+------------------------------------------------------------------+ void OnTick(void) { ExtExpert.OnTick(); ExtTrailing.Run(); }
让我们编译这个EA,并使用默认参数在测试器中运行它。
正如我们所见,持仓的止损价格已经正确地根据抛物线转向(Parabolic SAR)指标第一根柱子的值进行了跟踪。
接下来,我们将为终端中呈现的各种移动平均线创建追踪止损类。
移动平均线追踪止损类
这些移动平均线追踪止损类与抛物线转向追踪类的不同之处仅在于输入参数集和Initialize()方法创建的指标类型。Initialize()方法会为每个类创建一个与其对应的移动平均线指标。
基于这点,我们将仅考虑一个类:基于自适应移动平均线的追踪止损类:
//+------------------------------------------------------------------+ //| Adaptive Moving Average position StopLoss trailing class | //+------------------------------------------------------------------+ class CTrailingByAMA : public CTrailingByInd { private: int m_period; // Period AMA parameter int m_fast_ema; // Fast EMA Period parameter int m_slow_ema; // Slow EMA Period parameter int m_shift; // Shift AMA parameter ENUM_APPLIED_PRICE m_price; // Applied Price AMA parameter public: //--- set AMA parameters void SetPeriod(const uint period) { this.m_period=int(period<1 ? 9 : period); } void SetFastEMAPeriod(const uint period) { this.m_fast_ema=int(period<1 ? 2 : period); } void SetSlowEMAPeriod(const uint period) { this.m_slow_ema=int(period<1 ? 30 : period); } void SetShift(const int shift) { this.m_shift = shift; } void SetPrice(const ENUM_APPLIED_PRICE price) { this.m_price = price; } //--- return AMA parameters int Period(void) const { return this.m_period; } int FastEMAPeriod(void) const { return this.m_fast_ema; } int SlowEMAPeriod(void) const { return this.m_slow_ema; } int Shift(void) const { return this.m_shift; } ENUM_APPLIED_PRICE Price(void) const { return this.m_price; } //--- create AMA indicator and return the result bool Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price); //--- constructors CTrailingByAMA() : CTrailingByInd(::Symbol(), ::Period(), -1, 0, 0, 0) { this.Initialize(this.m_symbol, this.m_timeframe, 9, 2, 30, 0, PRICE_CLOSE); } CTrailingByAMA(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price, const int trail_start, const uint trail_step, const int trail_offset); //--- destructor ~CTrailingByAMA(){} }; //+------------------------------------------------------------------+ //| Parametric constructor | //+------------------------------------------------------------------+ CTrailingByAMA::CTrailingByAMA(const string symbol, const ENUM_TIMEFRAMES timeframe, const long magic, const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price, const int trail_start, const uint trail_step, const int trail_offset) : CTrailingByInd(symbol, timeframe, magic, trail_start, trail_step, trail_offset) { this.Initialize(symbol, timeframe, period, fast_ema, slow_ema, shift, price); } //+------------------------------------------------------------------+ //| create AMA indicator and return the result | //+------------------------------------------------------------------+ bool CTrailingByAMA::Initialize(const string symbol, const ENUM_TIMEFRAMES timeframe, const int period, const int fast_ema, const int slow_ema, const int shift, const ENUM_APPLIED_PRICE price) { this.SetSymbol(symbol); this.SetTimeframe(timeframe); this.SetPeriod(period); this.SetFastEMAPeriod(fast_ema); this.SetSlowEMAPeriod(slow_ema); this.SetShift(shift); this.SetPrice(price); ::ResetLastError(); this.m_handle=::iAMA(this.m_symbol, this.m_timeframe, this.m_period, this.m_fast_ema, this.m_slow_ema, this.m_shift, this.m_price); if(this.m_handle==INVALID_HANDLE) { ::PrintFormat("Failed to create iAMA(%s, %s, %d, %d, %d, %s) handle. Error %d", this.m_symbol, this.TimeframeDescription(), this.m_period, this.m_fast_ema, this.m_slow_ema, ::StringSubstr(::EnumToString(this.m_price),6), ::GetLastError()); } return(this.m_handle!=INVALID_HANDLE); }
该类与上述基于抛物线转向(Parabolic SAR)的追踪类完全相同。每个基于移动平均线(MA)的追踪类都具备其特有的一组用于存储指标参数的变量,以及用于设置和返回与这些变量相对应值的方法。所有基于移动平均线的追踪类都是相同的。你可以通过查看下面附带的Trailings.mqh文件中的代码来研究它们。你可以通过加载文末附件ExpertMACDWithTrailingByMA.mq5文件中的测试EA,来测试基于移动平均线的追踪类。
我们已经创建了基于简单追踪方式的类,以及基于终端内提供的标准指标的追踪止损和适用于设置头寸止损位的类。
但这些并不是用我们所呈现的类可以创建的全部追踪止损类型。还存在一些追踪止损类型,它们可以针对持有的多头和空头头寸,分别将其止损移动到特定的价格位置。这种追踪止损的一个例子可以是基于K线最高/最低价格的追踪,或者是基于分形指标(fractal indicator)的追踪。
为了实现这种追踪,我们应该能够为每种持仓类型指定一个单独的止损价。让我们创建这样一个类。
追踪指定的止损价格
要创建一个基于指定值的追踪止损,只需定义变量来代替指标参数,以便为多单和空单设置止损值。并且,我们不再需要从指标中获取数据,而只需根据这些变量中指定的值来计算止损价。在调用追踪的Run()方法时,将从控制程序中直接将这些值设置到变量中。
让我们来实现以下类:
//+------------------------------------------------------------------+ //| Trailing class based on a specified value | //+------------------------------------------------------------------+ class CTrailingByValue : public CSimpleTrailing { protected: double m_value_sl_long; // StopLoss level for long positions double m_value_sl_short; // StopLoss level for short positions //--- calculate and return the StopLoss level of the selected position virtual double GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick); public: //--- return StopLoss level for (2) long and (2) short positions double StopLossValueLong(void) const { return this.m_value_sl_long; } double StopLossValueShort(void) const { return this.m_value_sl_short; } //--- launch trailing with the specified StopLoss offset from the price bool Run(const double value_sl_long, double value_sl_short); //--- constructors CTrailingByValue(void) : CSimpleTrailing(::Symbol(), -1, 0, 0, 0), m_value_sl_long(0), m_value_sl_short(0) {} CTrailingByValue(const string symbol, const long magic, const int trail_start, const uint trail_step, const int trail_offset) : CSimpleTrailing(symbol, magic, trail_start, trail_step, trail_offset), m_value_sl_long(0), m_value_sl_short(0) {} //--- destructor ~CTrailingByValue(void){} };
很明显,这个类与所有基于指标的追踪止损类几乎完全相同。但这里没有Initialize()方法,因为不需要创建任何指标。
在用于计算和返回选定头寸的止损值的虚拟方法中,止损值是根据为多头和空头头寸设定的止损价计算得出的:
//+------------------------------------------------------------------+ //| Calculate and return the StopLoss level of the selected position | //+------------------------------------------------------------------+ double CTrailingByValue::GetStopLossValue(ENUM_POSITION_TYPE pos_type, MqlTick &tick) { //--- calculate and return the StopLoss level depending on the position type switch(pos_type) { case POSITION_TYPE_BUY : return(this.m_value_sl_long - this.m_offset * this.m_point); case POSITION_TYPE_SELL : return(this.m_value_sl_short + this.m_offset * this.m_point); default : return 0; } }
多头和空头头寸的止损值是通过Run()方法的形式参数传递给该类的:
//+------------------------------------------------------------------+ //| Launch trailing with the specified StopLoss offset from the price| //+------------------------------------------------------------------+ bool CTrailingByValue::Run(const double value_sl_long, double value_sl_short) { this.m_value_sl_long =value_sl_long; this.m_value_sl_short=value_sl_short; return CSimpleTrailing::Run(); }
首先,形式参数中传递的值被设置为类的成员变量,然后调用父类的Run()方法。从父类中调用了重新定义的GetStopLossValue()虚拟方法,同时将头寸的止损价设置为在该方法中计算得到的值。
将一个追踪止损关联到EA中
我们之前已经讨论过在测试基于指标的追踪止损时,如何将追踪止损与EA连接起来。现在,让我们来考虑如何根据传递给追踪止损的值(即,K线的最高价和最低价)来连接和启动追踪止损。
将追踪止损功能添加到来自标准指标目录\MQL5\Experts\Advisors\中的ExpertMACD.mq5的EA中。将其保存为ExpertMACDWithTrailingByValue.mq5并进行必要的改进。
将包含追踪止损类的文件包含到EA中,添加追踪止损设置输入参数,以及声明基于值的追踪止损类实例:
//+------------------------------------------------------------------+ //| ExpertMACD.mq5 | //| Copyright 2000-2024, MetaQuotes Ltd. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "Copyright 2000-2024, MetaQuotes Ltd." #property link "https://www.mql5.com" #property version "1.00" //+------------------------------------------------------------------+ //| Include | //+------------------------------------------------------------------+ #include <Expert\Expert.mqh> #include <Expert\Signal\SignalMACD.mqh> #include <Expert\Trailing\TrailingNone.mqh> #include <Expert\Money\MoneyNone.mqh> #include <Trailings\Trailings.mqh> //+------------------------------------------------------------------+ //| Inputs | //+------------------------------------------------------------------+ //--- inputs for expert input group " - ExpertMACD Parameters -" input string Inp_Expert_Title ="ExpertMACD"; int Expert_MagicNumber =10981; bool Expert_EveryTick =false; //--- inputs for signal input int Inp_Signal_MACD_PeriodFast =12; input int Inp_Signal_MACD_PeriodSlow =24; input int Inp_Signal_MACD_PeriodSignal=9; input int Inp_Signal_MACD_TakeProfit =50; input int Inp_Signal_MACD_StopLoss =20; input group " - Trailing By Value Parameters -" input ENUM_TIMEFRAMES InpTimeframe = PERIOD_CURRENT; // Data Rates Timeframe input uint InpDataRatesIndex = 2; // Data Rates Index for StopLoss input uint InpTrailingStep = 0; // Trailing Step input int InpTrailingStart = 0; // Trailing Start input int InpTrailingOffset = 0; // Trailing Offset input bool InpUseTrailing = true; // Use Trailing Stop //+------------------------------------------------------------------+ //| Global expert object | //+------------------------------------------------------------------+ CExpert ExtExpert; CTrailingByValue ExtTrailing;
InpTimeframe输入参数是图表的时间框架,从中获取最高价作为空头头寸的止损价,和最低价作为多头头寸的止损价。
InpDataRatesIndex输入参数是由InpTimeframe确定的时间框架图表上的柱形索引,从中获取要用于设置头寸止损的最高价和最低价。
在EA的OnInit()程序中,初始化追踪止损类的参数:
//+------------------------------------------------------------------+ //| Initialization function of the expert | //+------------------------------------------------------------------+ int OnInit(void) { //--- Initializing trailing ExtTrailing.SetMagicNumber(Expert_MagicNumber); ExtTrailing.SetTrailingStart(InpTrailingStart); ExtTrailing.SetTrailingStep(InpTrailingStep); ExtTrailing.SetStopLossOffset(InpTrailingOffset); ExtTrailing.SetActive(InpUseTrailing); //--- Initializing expert
只有其magic编号与EA相匹配的头寸的止损位才会被追踪。换句话说,我们只追踪由此EA开立的头寸。
在EA的OnTick()程序中,使用在InpDataRatesIndex输入中指定的索引获取柱形数据,并在确认多头和空头头寸的止损价格(柱形的最高价和最低价)时启动追踪:
//+------------------------------------------------------------------+ //| Function-event handler "tick" | //+------------------------------------------------------------------+ void OnTick(void) { ExtExpert.OnTick(); MqlRates rates[1]={}; if(CopyRates(ExtTrailing.Symbol(),InpTimeframe,InpDataRatesIndex,1,rates)) ExtTrailing.Run(rates[0].low,rates[0].high); }
这就是我们需要做的全部工作,以将追踪止损加入到EA中。您可能已经注意到,将不同的追踪止损连加入EA时,唯一的不同之处在于需要声明不同类型的追踪止损实例。加入追踪止损的所有其他步骤几乎完全相同,应该不会引起任何疑问或困惑。
让我们编译EA,并使用默认设置在测试器的可视化模式下运行它:
我们可以看到,止损位置被设置为时间序列2中索引对应的K线的最高价和最低价。
这种追踪止损能在多大程度上提高交易系统的胜率,需要通过独立测试来确定。
此外,每个人都可以使用本文中介绍的类,根据自己的算法创建自定义的追踪止损。这里的研究空间很大,几乎不受限制。
结论
在本文中,我们创建了各种类型的追踪止损类,使我们能够轻松地将追踪止损添加到任何EA中。创建的类也是使用自定义算法实现追踪止损的良好工具集。
示例中仅涵盖了一种处理类对象的方法——创建追踪类对象的实例。这种方法使得程序能够预先确定需要哪种类型的追踪止损以及应该具有哪些参数。对于每个追踪止损,都会在全局区域中创建一个对象。这种方法是可行的——它简单明了。但是,为了在程序执行期间使用“new”对象创建运算符动态创建追踪对象,最好利用标准库提供的创建链接对象列表的功能。为此,所有追踪类都派生自“CObject标准库基对象”。这种方法可以在评论中讨论,因为它超出了当前主题的范围。
文章中介绍的所有类都可以“原封不动”地用于您自己的开发中,或者您可以根据自己的需求和任务进行修改。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/14862


