在前一篇文章中,我们向交易类添加了对无效参数的控制。 传递给交易方法的数值有效性得以检查。 如果证实任何参数无效,则从正执行的交易方法携带错误消息返回。 此行为不允许 EA 故意发送无效订单,从而令交易服务器过载。 另一方面,它不能完全控制 EA 行为。 取而代之,我们也许会检查无效值是否可以纠正。 如果可行,那么纠正它们,并将调整后的交易订单发送到服务器当然很合理。
通常,EA 应当能够根据形势采取行动,同时遵循用户定义的交易订单出错的处理逻辑。 因此,当检测到交易订单错误时,我们可以向 EA 提供以下指令:
处理交易订单参数中的错误可能会导致以下几种结果之一:
在本文中,我们将开发交易订单出错处理程序,该程序将检查错误及其来源,并返回错误处理方法:
若服务器端完全禁止了交易,则必须禁用交易操作,或者说 EA 发起交易请求也是无用的。 在此情况下,EA 只能用作分析助手。 有鉴于此,我们需要在首次尝试交易期间设置全局标志,并判断不可能进行交易。
中断交易操作:若出现任何错误,则退出交易方法,而用户能够继续尝试用相同的参数进行交易。
纠正无效参数的工序如下:在检查交易请求的有效性时,我们汇编所有检测到错误的列表。 参数检查方法会遍历列表中的所有错误,并返回相应的交易方法行为代码。 如果错误导致无法进一步交易,则该方法返回退出交易方法的代码,因为即使重发交易订单仍旧不会产生正面效果。 如果错误可以得到修复,则调用纠正相应交易订单值的方法,并返回成功验证的结果。 此外,该方法返回 “等待并重复”,“更新数据并重复” 和 “创建延后请求” 等交易方法行为代码。
这是何意?当行情接近订单停止价位之一或其激活价位,而此刻我们尝试修改停止价位,或删除订单/平仓时,可能需要 “等待并重复” 行为。 如果停止价位的激活价格在交易操作冻结区域之内,则服务器将返回禁止修改订单值。 在这种情况下,只有一个解决方案 — 暂等片刻,且寄希望价格离开该区域。 之后,发送交易请求以便修改订单/持仓参数,或在等待之后删除订单。
如果价格已过时,且在我们处理交易订单期间收到了全新报价,则可能需要 “更新数据并重复” 行为。
“创建延后请求” 行为。 这是何意?
如果仔细查看一下前面的两种处理方法,很明显,在等待时,我们只是在交易方法里等待,直到等待时间结束。 如果我们不必在等待期间分析交易环境,那么这种行为是合理的。 若要在交易方法必须的 “立等” 期内释放程序,我们简单地创建一个延后交易请求,其中包含必要的参数和等待时间,以及重复次数。
在开始之前,我想提醒您,我们已修改了上一篇文章中的交易事件定义:
我已收到一些用户报告,称接收最后一个交易事件时检测到错误。 与文章相关的测试 EA,描述了如何接收交易事件,通过比较先前与当前事件值来获取所发生交易事件的数据。 这足以应对测试函数库的交易事件跟踪目的,因为在撰写有关交易事件的文章时,我并未打算在定制应用程序中使用未完工的函数库版本。 但是事实证明,获取有关交易事件信息的需求很高,因此了解最后所发生事件的确切信息非常重要。
获取交易事件的实现方法也许跳过了一些事件。 例如,如果您一次下了两笔挂单,则程序中不会跟踪第二笔挂单(函数库跟踪所有事件),因为它与倒数第二笔相匹配(“下挂单”),尽管实际上有所区别。
因此,我们将修复此行为。 今天,我们将实现一个简单标志,将所发生事件通知给程序,以便我们能够在程序中查看有关事件的数据。 在下一篇文章中,我们将为同时发生的所有事件创建完整列表,并将其发送到程序,从而完成获取程序中交易事件的操作。 因此,我们不仅可以搜索已发生交易事件,还可以查看同时发生的所有事件,如同帐户和品种集合事件里做到的那样。
因此,在重归交易类的工作之前,我们先完成修改此功能的工序。
由于我们的所有对象实际上都是基于所有函数库对象的基准对象,这些对象具有事件列表以及返回对象发生事件标志的方法,故我们将所有交易事件添加到基准对象事件列表之中,而事件标志可利用 IsEvent() 方法从类中获取。 事件标志由类自动设置。 然而,我们应能够从其他类,及其事件处理程序中设置交易事件已发生的标志。
为此,添加设置基准对象事件标志的方法至 BaseObj.mqh 文件中的 CEventBaseObj 类:
//--- Set/return the occurred event flag to the object data void SetEvent(const bool flag) { this.m_is_event=flag; } bool IsEvent(void) const { return this.m_is_event; }
现在,当一个新事件出现在 CEventsCollection 交易事件集合类中时,我们需要创建事件描述,将其放置在所有对象基类的新事件列表中,并设置新事件标志 。
因此,所有新发生事件的描述都置于品种集合列表里的基类交易事件中。 我们可以在程序中轻松读取该列表,并处理其中的每个事件。
我们针对 EventsCollection.mqh 交易事件类文件进行所有必要的改进。
将两个新方法的定义添加到类的公开部分 —
按其在列表中的索引获取基准事件对象的方法和
返回新事件数量的方法:
//--- Return (1) the last trading event on an account, (2) base event object by index and (3) number of new events ENUM_TRADE_EVENT GetLastTradeEvent(void) const { return this.m_trade_event; } CEventBaseObj *GetTradeEventByIndex(const int index) { return this.GetEvent(index,false); } int GetTradeEventsTotal(void) const { return this.m_list_events.Total(); } //--- Reset the last trading event
该方法按索引返回基准事件对象,然后调用 GetEvent() 基准对象方法,该方法重置标志(false),并检查索引是否超出事件列表的边界,若超出则不纠正返回的事件。 换言之,如果我们传递不存在的索引,则该方法将返回 NULL。 如果我们传递 true给标志值,则该方法返回最后的事件,于此我们并不需要它。
返回新事件的数量的方法简单地返回基准对象列表的大小。
由于交易事件集合类在计时器中不断查看历史和在场订单及持仓的列表,因此我们需要清除基准交易事件的列表,并在 Refresh() 方法中设置列表已排序的标志 :
//+------------------------------------------------------------------+ //| Update the event list | //+------------------------------------------------------------------+ void CEventsCollection::Refresh(CArrayObj* list_history, CArrayObj* list_market, CArrayObj* list_changes, CArrayObj* list_control, const bool is_history_event, const bool is_market_event, const int new_history_orders, const int new_market_pendings, const int new_market_positions, const int new_deals, const double changed_volume) { //--- Exit if the lists are empty if(list_history==NULL || list_market==NULL) return; //--- this.m_is_event=false; this.m_list_events.Clear(); this.m_list_events.Sort(); //--- If the event is in the market environment if(is_market_event) {
在创建新事件的 CreateNewEvent() 的所有方法中,事件发送字符串之后,我们需要将该事件添加到基准事件列表中:
event.SendEvent(); CBaseObj::EventAdd(this.m_trade_event,order.Ticket(),order.Price(),order.Symbol());
这已经在方法清单中设置了,故为节省文章篇幅,于此勿需赘言。 所有内容都可以在附件中找到。
现在,在函数库 CEngine 基准对象类的公开部分中,添加按其在列表中的索引返回基准事件对象的方法和返回新事件数量的方法:
//--- Return (1) the list of order, deal and position events, (2) base trading event object by index and the (3) number of new trading events CArrayObj *GetListAllOrdersEvents(void) { return this.m_events.GetList(); } CEventBaseObj *GetTradeEventByIndex(const int index) { return this.m_events.GetTradeEventByIndex(index); } int GetTradeEventsTotal(void) const { return this.m_events.GetTradeEventsTotal(); } //--- Reset the last trading event
这些方法只是简单地调用上述交易事件集合类的同名方法。
这些都是必要的修改,令您可以跟踪同时发生的所有事件,并捆绑在一起发送到程序。 这些稍后就会看到 — 当测试本文中论述的功能时。
现在我们可以开始进一步完善交易类。
首先,将必要消息的索引添加到 Datas.mqh 文件中:
MSG_LIB_TEXT_REQUEST_REJECTED_DUE, // Request was rejected before sending to the server due to: MSG_LIB_TEXT_INVALID_REQUEST, // Invalid request: MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR, // Insufficient funds for performing a trade MSG_LIB_TEXT_UNSUPPORTED_PRICE_TYPE_IN_REQ, // Unsupported price parameter type in a request MSG_LIB_TEXT_TRADING_DISABLE, // Trading disabled for the EA until the reason is eliminated MSG_LIB_TEXT_TRADING_OPERATION_ABORTED, // Trading operation is interrupted MSG_LIB_TEXT_CORRECTED_TRADE_REQUEST, // Correcting trading request parameters MSG_LIB_TEXT_CREATE_PENDING_REQUEST, // Creating a pending request MSG_LIB_TEXT_NOT_POSSIBILITY_CORRECT_LOT, // Unable to correct a lot };
以及与索引相对应的文本:
{"Запрос отклонён до отправки на сервер по причине:","Request rejected before being sent to server due to:"}, {"Ошибочный запрос:","Invalid request:"}, {"Недостаточно средств для совершения торговой операции","Not enough money to perform trading operation"}, {"Неподдерживаемый тип параметра цены в запросе","Unsupported price parameter type in request"}, {"Торговля отключена для эксперта до устранения причины запрета","Trading for the expert is disabled until this ban is eliminated"}, {"Торговая операция прервана","Trading operation aborted"}, {"Корректировка параметров торгового запроса ...","Correction of trade request parameters ..."}, {"Создание отложенного запроса","Create pending request"}, {"Нет возможности скорректировать лот","Unable to correct the lot"}, };
我们需要定义与交易服务器返回的错误相应的交易请求出错处理方式的枚举,并添加到 Defines.mqh 文件中。
为 EA 设置激活行为,即,添加枚举,描述 EA 可能采取的行为,当检测到交易请求中的错误,或交易服务器返回错误时,激活相应行为:
//+------------------------------------------------------------------+ //| EA behavior when handling errors | //+------------------------------------------------------------------+ enum ENUM_ERROR_HANDLING_BEHAVIOR { ERROR_HANDLING_BEHAVIOR_BREAK, // Abort trading attempt ERROR_HANDLING_BEHAVIOR_CORRECT, // Correct invalid parameters ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST, // Create a pending request }; //+------------------------------------------------------------------+
依据指定的枚举参数之一来处理错误时,可在设置 EA 首选行为。
检查交易请求参数值时,各种错误处理方法都是可能的。 在检查交易订单参数时,为了找出探查到哪些错误,以及这些错误会受哪些交易条件影响,为可能采取的错误处理方法添加指定标记的枚举:
//+------------------------------------------------------------------+ //| Flags indicating the trading request error handling methods | //+------------------------------------------------------------------+ enum ENUM_TRADE_REQUEST_ERR_FLAGS { TRADE_REQUEST_ERR_FLAG_NO_ERROR = 0, // No error TRADE_REQUEST_ERR_FLAG_FATAL_ERROR = 1, // Disable trading for an EA (critical error) - exit TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR = 2, // Library internal error - exit TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST = 4, // Error in the list - handle (ENUM_ERROR_CODE_PROCESSING_METHOD) }; //+------------------------------------------------------------------+
我们添加错误处理行为标志,如此当我们检查交易订单参数后,即可执行:
错误检查方法将返回纠正检测到错误的方式。
为此,针对交易订单错误,以及交易服务器返回的错误,添加可能采取的处理方法枚举:
//+------------------------------------------------------------------+ //| The methods of handling errors and server return codes | //+------------------------------------------------------------------+ enum ENUM_ERROR_CODE_PROCESSING_METHOD { ERROR_CODE_PROCESSING_METHOD_OK, // No errors ERROR_CODE_PROCESSING_METHOD_DISABLE, // Disable trading for the EA ERROR_CODE_PROCESSING_METHOD_EXIT, // Exit the trading method ERROR_CODE_PROCESSING_METHOD_REFRESH, // Update data and repeat ERROR_CODE_PROCESSING_METHOD_WAIT, // Wait and repeat ERROR_CODE_PROCESSING_METHOD_PENDING, // Create a pending request }; //+------------------------------------------------------------------+
处理错误的方法可根据枚举常量的描述轻易理解。
还有,改进基准交易对象。
取决于在图表上构建柱线的方法,交易执行可按要价(Ask)和出价(Bid),或要价(Ask)和最后价(Last)。 当前,基准交易类中仅安排按要价和出价进行交易。 我们加入检查按哪个价格构建图表的功能。 此外,调整我们用来交易的价格。 此外,MQL5 拥有 MqlTradeResult 交易请求结果的结构,以及分别包含错误代码和错误代码描述的 “retcode” 和 “comment” 字段。 如此即可在交易订单发送到服务器后,检查由交易服务器返回的代码。 MQL4 无此功能,因此应利用 GetLastError() 函数读取错误代码,并返回最后的错误代码。 鉴于我们的函数库是一个多平台的函数库,因此对于 MQL4,我们要在发送交易请求至服务器以后,再填充其结构字段。
在检查相对于价格的停止单距离时,我们还要考虑品种规定的可接受停止价位(StopLevel)的最小距离。 如果参数为 SYMBOL_TRADE_STOPS_LEVEL 属性 ID 的 SymbolInfoInteger() 函数返回 StopLevel 数值等于零,这并不意味着停止单无需距价格的最小偏移。 这仅表示该级别是浮动的。 因此,为了调整停止级别距价格的偏移值,我们需要为级别选择 “原位”,或将当前点差乘以某个值作为偏移值。 通常用两倍的点差作为平滑停止位调整。 在交易对象里加入倍数,及其返回和设置方法,以便能够为每个品种的交易对象设置倍数。在 TradeObj.mqh 文件中的 CTradeObj 交易对象基类中加入必要的修改。
在该类的私密部分中,声明两个类成员变量来存储构建图表柱线的价格类型,和用于调整停止单级别的点差倍数:
SActions m_datas; MqlTick m_tick; // Tick structure for receiving prices MqlTradeRequest m_request; // Trade request structure MqlTradeResult m_result; // trade request execution result ENUM_SYMBOL_CHART_MODE m_chart_mode; // Price type for constructing bars ENUM_ACCOUNT_MARGIN_MODE m_margin_mode; // Margin calculation mode ENUM_ORDER_TYPE_FILLING m_type_filling; // Filling policy ENUM_ORDER_TYPE_TIME m_type_expiration; // Order expiration type int m_symbol_expiration_flags; // Flags of order expiration modes for a trading object symbol ulong m_magic; // Magic number string m_symbol; // Symbol string m_comment; // Comment ulong m_deviation; // Slippage in points double m_volume; // Volume datetime m_expiration; // Order expiration time (for ORDER_TIME_SPECIFIED type order) bool m_async_mode; // Flag of asynchronous sending of a trade request ENUM_LOG_LEVEL m_log_level; // Logging level int m_stop_limit; // Distance of placing a StopLimit order in points bool m_use_sound; // The flag of using sounds of the object trading events uint m_multiplier; // The spread multiplier to adjust levels relative to StopLevel
在该类的公开部分里,添加设置点差倍数的方法,和返回倍数值的方法:
public: //--- Constructor CTradeObj(); //--- Set/return the spread multiplier void SetSpreadMultiplier(const uint value) { this.m_multiplier=(value==0 ? 1 : value); } uint SpreadMultiplier(void) const { return this.m_multiplier; } //--- Set default values
当设置点差倍数时,检查传递给该方法的值是否等于零。 如果是,则赋值 1。
另外,在该类的公开部分中,添加两种方法 — 其一设置交易请求错误代码,另一个设置交易请求错误代码的说明:
//--- Set the error code in the last request result void SetResultRetcode(const uint retcode) { this.m_result.retcode=retcode; } void SetResultComment(const string comment) { this.m_result.comment=comment; } //--- Data on the last request result:
在类构造函数中,将默认值 1 赋值给点差倍数:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTradeObj::CTradeObj(void) : m_magic(0), m_deviation(5), m_stop_limit(0), m_expiration(0), m_async_mode(false), m_type_filling(ORDER_FILLING_FOK), m_type_expiration(ORDER_TIME_GTC), m_comment(::MQLInfoString(MQL_PROGRAM_NAME)+" by DoEasy"), m_log_level(LOG_LEVEL_ERROR_MSG) { //--- Margin calculation mode this.m_margin_mode= ( #ifdef __MQL5__ (ENUM_ACCOUNT_MARGIN_MODE)::AccountInfoInteger(ACCOUNT_MARGIN_MODE) #else /* MQL4 */ ACCOUNT_MARGIN_MODE_RETAIL_HEDGING #endif ); //--- Spread multiplier this.m_multiplier=1; //--- Set default sounds and flags of using sounds this.m_use_sound=false; this.InitSounds(); } //+------------------------------------------------------------------+
在 Init() 方法中,定义交易对象参数默认值,设置 m_chart_mode 变量值,该值存储构建柱线的价格:
//+------------------------------------------------------------------+ //| Set default values | //+------------------------------------------------------------------+ void CTradeObj::Init(const string symbol, const ulong magic, const double volume, const ulong deviation, const int stoplimit, const datetime expiration, const bool async_mode, const ENUM_ORDER_TYPE_FILLING type_filling, const ENUM_ORDER_TYPE_TIME type_expiration, ENUM_LOG_LEVEL log_level) { this.SetSymbol(symbol); this.SetMagic(magic); this.SetDeviation(deviation); this.SetVolume(volume); this.SetExpiration(expiration); this.SetTypeFilling(type_filling); this.SetTypeExpiration(type_expiration); this.SetAsyncMode(async_mode); this.SetLogLevel(log_level); this.m_symbol_expiration_flags=(int)::SymbolInfoInteger(this.m_symbol,SYMBOL_EXPIRATION_MODE); this.m_volume=::SymbolInfoDouble(this.m_symbol,SYMBOL_VOLUME_MIN); this.m_chart_mode=#ifdef __MQL5__ (ENUM_SYMBOL_CHART_MODE)::SymbolInfoInteger(this.m_symbol,SYMBOL_CHART_MODE) #else SYMBOL_CHART_MODE_BID #endif ; } //+------------------------------------------------------------------+
在此,对于 MQL5,我们利用参数为 SYMBOL_CHART_MODE ID 的 SymbolInfoInteger() 函数获取数据,而若是 MQL4,柱线则是由出价(Bid)构建的。。
现在,我们应该为每种交易方法加入填充交易服务器反馈结构。
我们以开仓方法为例:
//+------------------------------------------------------------------+ //| Open a position | //+------------------------------------------------------------------+ bool CTradeObj::OpenPosition(const ENUM_POSITION_TYPE type, const double volume, const double sl=0, const double tp=0, const ulong magic=ULONG_MAX, const string comment=NULL, const ulong deviation=ULONG_MAX) { ::ResetLastError(); //--- If failed to get the current prices, write the error code and description, send the message to the journal and return 'false' if(!::SymbolInfoTick(this.m_symbol,this.m_tick)) { this.m_result.retcode=::GetLastError(); this.m_result.comment=CMessage::Text(this.m_result.retcode); if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_NOT_GET_PRICE),CMessage::Text(this.m_result.retcode)); return false; } //--- Clear the structures ::ZeroMemory(this.m_request); ::ZeroMemory(this.m_result); //--- Fill in the request structure this.m_request.action = TRADE_ACTION_DEAL; this.m_request.symbol = this.m_symbol; this.m_request.magic = (magic==ULONG_MAX ? this.m_magic : magic); this.m_request.type = OrderTypeByPositionType(type); this.m_request.price = (type==POSITION_TYPE_BUY ? this.m_tick.ask : (this.m_chart_mode==SYMBOL_CHART_MODE_BID ? this.m_tick.bid : this.m_tick.last)); this.m_request.volume = volume; this.m_request.sl = sl; this.m_request.tp = tp; this.m_request.deviation= (deviation==ULONG_MAX ? this.m_deviation : deviation); this.m_request.comment = (comment==NULL ? this.m_comment : comment); //--- Return the result of sending a request to the server #ifdef __MQL5__ return(!this.m_async_mode ? ::OrderSend(this.m_request,this.m_result) : ::OrderSendAsync(this.m_request,this.m_result)); #else ::ResetLastError(); int ticket=::OrderSend(m_request.symbol,m_request.type,m_request.volume,m_request.price,(int)m_request.deviation,m_request.sl,m_request.tp,m_request.comment,(int)m_request.magic,m_request.expiration,clrNONE); if(ticket!=WRONG_VALUE) { ::SymbolInfoTick(this.m_symbol,this.m_tick); this.m_result.retcode=::GetLastError(); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; this.m_result.deal=ticket; this.m_result.price=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderOpenPrice() : this.m_request.price); this.m_result.volume=(::OrderSelect(ticket,SELECT_BY_TICKET) ? ::OrderLots() : this.m_request.volume); this.m_result.comment=CMessage::Text(this.m_result.retcode); return true; } else { ::SymbolInfoTick(this.m_symbol,this.m_tick); this.m_result.retcode=::GetLastError(); this.m_result.ask=this.m_tick.ask; this.m_result.bid=this.m_tick.bid; this.m_result.comment=CMessage::Text(this.m_result.retcode); return false; } #endif } //+------------------------------------------------------------------+
在此,对于 MQL5,我们 如前返回 OrderSend() 函数的操作结果,而若是 MQL4,我们检查由 MQL4 订单发送函数返回的票据。 如果交易请求执行成功,该函数将返回已开单的票据。 出错则携带 WRONG_VALUE。 因此,确保函数返回的值不是 -1。 如果是,则更新品种报价,以相应的数据填充交易请求结果结构,并返回 true — 函数已成功执行。
如果订单发送函数返回 -1,输出最后的错误代码、当前价格和最后的错误代码定义 至交易请求结果结构。 其余结构字段保留为零。 结果则为,返回 false — 交易订单发送失败。
得益于这样的精益求精,无论发送交易指令的结局如何,我们都可以利用类中方法查看请求结果:
//--- Data on the last request result: //--- Return (1) operation result code, (2) performed deal ticket, (3) placed order ticket, //--- (4) deal volume confirmed by a broker, (5) deal price confirmed by a broker, //--- (6) current market Bid (requote) price, (7) current market Ask (requote) price //--- (8) broker comment to operation (by default, it is filled by the trade server return code description), //--- (9) request ID set by the terminal when sending, (10) external trading system return code uint GetResultRetcode(void) const { return this.m_result.retcode; } ulong GetResultDeal(void) const { return this.m_result.deal; } ulong GetResultOrder(void) const { return this.m_result.order; } double GetResultVolume(void) const { return this.m_result.volume; } double GetResultPrice(void) const { return this.m_result.price; } double GetResultBid(void) const { return this.m_result.bid; } double GetResultAsk(void) const { return this.m_result.ask; } string GetResultComment(void) const { return this.m_result.comment; } uint GetResultRequestID(void) const { return this.m_result.request_id; } uint GetResultRetcodeEXT(void) const { return this.m_result.retcode_external;}
其余的交易方法也以类似的方式最终确定,故没有必要于此研究它们。 您可在下面附带的文件中找到全部所需。
在 Account.mqh 文件的 CAccount 帐户对象类中,改进方法返回开仓或放置挂单所需的保证金:
//+------------------------------------------------------------------+ //| Return the margin required for opening a position | //| or placing a pending order | //+------------------------------------------------------------------+ double CAccount::MarginForAction(const ENUM_ORDER_TYPE action,const string symbol,const double volume,const double price) const { double margin=EMPTY_VALUE; #ifdef __MQL5__ return(!::OrderCalcMargin(ENUM_ORDER_TYPE(action%2),symbol,volume,price,margin) ? EMPTY_VALUE : margin); #else return this.MarginFree()-::AccountFreeMarginCheck(symbol,ENUM_ORDER_TYPE(action%2),volume); #endif } //+------------------------------------------------------------------+
我们在这里所要添加的全部内容就是将传递给该方法的订单类型转换为两个可能的值 — ORDER_TYPE_BUY 或 ORDER_TYPE_SELL,因为 MQL5 和 MQL4 方法在操作时只需要这类的订单。
您可能还记得,将订单类型常量除以 2 的余数总是返回两个值之一:
这恰是我们转换正确订单类型所需要的。
我们在 Trading.mqh 文件的 CTrading 类中已经创建了用于填充交易订单价格参数的自定义结构:
struct SDataPrices { double open; // Open price double limit; // Limit order price double sl; // StopLoss price double tp; // TakeProfit price }; SDataPrices m_req_price; // Trade request prices
不过,MQL 提供了 MqlTradeRequest 结构。 因此,为避免冗余结构,
在类的私密部分中将自定义结构替换为标准结构,以及
声明类成员变量,来存储交易请求中的错误源标志,和
发送交易订单时出错情况下存储 EA 行为的变量:
//+------------------------------------------------------------------+ //| Trading class | //+------------------------------------------------------------------+ class CTrading { private: CAccount *m_account; // Pointer to the current account object CSymbolsCollection *m_symbols; // Pointer to the symbol collection list CMarketCollection *m_market; // Pointer to the list of the collection of market orders and positions CHistoryCollection *m_history; // Pointer to the list of the collection of historical orders and deals CArrayInt m_list_errors; // Error list bool m_is_trade_disable; // Flag disabling trading bool m_use_sound; // The flag of using sounds of the object trading events ENUM_LOG_LEVEL m_log_level; // Logging level MqlTradeRequest m_request; // Trade request prices ENUM_TRADE_REQUEST_ERR_FLAGS m_error_reason_flags; // Flags of error source in a trading method ENUM_ERROR_HANDLING_BEHAVIOR m_err_handling_behavior; // Behavior when handling error //--- Add the error code to the list bool AddErrorCodeToList(const int error_code);
另外,在类的私密部分中,编写返回变量中存储的错误源标志的方法,
返回错误列表中错误代码存在的方法,以及
处理错误时的放置和返回动作的方法:
//--- Return the flag presence in the trading event error reason bool IsPresentErrorFlag(const int code) const { return (this.m_error_reason_flags & code)==code; } //--- Return the error code in the list bool IsPresentErorCode(const int code) { this.m_list_errors.Sort(); return this.m_list_errors.Search(code)>WRONG_VALUE; } //--- Set/return the error handling action void SetErrorHandlingBehavior(const ENUM_ERROR_HANDLING_BEHAVIOR behavior) { this.m_err_handling_behavior=behavior; } ENUM_ERROR_HANDLING_BEHAVIOR ErrorHandlingBehavior(void) const { return this.m_err_handling_behavior; } //--- Check trading limitations
从检查资金不足的方法中删除在日志中显示资金不足消息的代码:
if(money_free<=0 #ifdef __MQL4__ || ::GetLastError()==134 #endif ) { if(this.m_log_level>LOG_LEVEL_NO_MSG) { //--- create a message text string message= ( symbol_obj.Name()+" "+::DoubleToString(volume,symbol_obj.DigitsLot())+" "+ ( order_type>ORDER_TYPE_SELL ? OrderTypeDescription(order_type,false,false) : PositionTypeDescription(PositionTypeByOrderType(order_type)) )+" ("+::DoubleToString(money_free,(int)this.m_account.CurrencyDigits())+")" ); //--- display a journal message if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR),": ",message); this.AddErrorCodeToList(MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR); } return false; }
现在,将从另一个方法里显示资金不足消息。
在当前方法中,简单地添加标志指示,在错误列表中搜索错误,然后将错误代码添加到错误列表:
//+------------------------------------------------------------------+ //| Check if the funds are sufficient | //+------------------------------------------------------------------+ bool CTrading::CheckMoneyFree(const double volume, const double price, const ENUM_ORDER_TYPE order_type, const CSymbol *symbol_obj, const string source_method, const bool mess=true) { ::ResetLastError(); //--- Get the type of a market order by a trading operation type ENUM_ORDER_TYPE action=this.DirectionByActionType((ENUM_ACTION_TYPE)order_type); //--- Get the value of free funds to be left after conducting a trading operation double money_free= ( //--- For MQL5, calculate the difference between free funds and the funds required to conduct a trading operation #ifdef __MQL5__ this.m_account.MarginFree()-this.m_account.MarginForAction(action,symbol_obj.Name(),volume,price) //--- For MQL4, use the operation result of the standard function returning the amount of funds left #else/*__MQL4__*/::AccountFreeMarginCheck(symbol_obj.Name(),action,volume) #endif ); //--- If no free funds are left, inform of that and return 'false' if(money_free<=0 #ifdef __MQL4__ || ::GetLastError()==134 #endif ) { this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR); return false; } //--- Verification successful return true; } //+------------------------------------------------------------------+
声明纠正停止价和挂单价的方法,纠正交易订单交易量的方法,指定如何处理错误的方法和纠正交易订单错误的方法:
//--- Return the correct (1) StopLoss, (2) TakeProfit and (3) order placement price double CorrectStopLoss(const ENUM_ORDER_TYPE order_type, const double price_set, const double stop_loss, const CSymbol *symbol_obj, const uint spread_multiplier=1); double CorrectTakeProfit(const ENUM_ORDER_TYPE order_type, const double price_set, const double take_profit, const CSymbol *symbol_obj, const uint spread_multiplier=1); double CorrectPricePending(const ENUM_ORDER_TYPE order_type, const double price_set, const double price, const CSymbol *symbol_obj, const uint spread_multiplier=1); //--- Return the volume, at which it is possible to open a position double CorrectVolume(const double price, const ENUM_ORDER_TYPE order_type, const CSymbol *symbol_obj, const string source_method); //--- Return the error handling method ENUM_ERROR_CODE_PROCESSING_METHOD ResultProccessingMethod(void); //--- Correct errors ENUM_ERROR_CODE_PROCESSING_METHOD RequestErrorsCorrecting(MqlTradeRequest &request,const ENUM_ORDER_TYPE order_type,const uint spread_multiplier,CSymbol *symbol_obj); public:
为检查限制和错误补充方法规范,并将返回的类型从 'bool' 替换为 ENUM_ERROR_CODE_PROCESSING_METHOD:
//--- Check limitations and errors ENUM_ERROR_CODE_PROCESSING_METHOD CheckErrors(const double volume, const double price, const ENUM_ACTION_TYPE action, const ENUM_ORDER_TYPE order_type, CSymbol *symbol_obj, const CTradeObj *trade_obj, const string source_method, const double limit=0, double sl=0, double tp=0);
该方法现在变得更加完善 — 它立即检查纠正交易订单中错误的可能方法,现在该方法返回处理检测到错误的方式。 以前,它只是返回检查成功标志。
声明设置点差倍数的方法:
//--- Set the following for symbol trading objects: //--- (1) correct filling policy, (2) filling policy, //--- (3) correct order expiration type, (4) order expiration type, //--- (5) magic number, (6) comment, (7) slippage, (8) volume, (9) order expiration date, //--- (10) the flag of asynchronous sending of a trading request, (11) logging level, (12) spread multiplier void SetCorrectTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol=NULL); void SetTypeFilling(const ENUM_ORDER_TYPE_FILLING type=ORDER_FILLING_FOK,const string symbol=NULL); void SetCorrectTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol=NULL); void SetTypeExpiration(const ENUM_ORDER_TYPE_TIME type=ORDER_TIME_GTC,const string symbol=NULL); void SetMagic(const ulong magic,const string symbol=NULL); void SetComment(const string comment,const string symbol=NULL); void SetDeviation(const ulong deviation,const string symbol=NULL); void SetVolume(const double volume=0,const string symbol=NULL); void SetExpiration(const datetime expiration=0,const string symbol=NULL); void SetAsyncMode(const bool mode=false,const string symbol=NULL); void SetLogLevel(const ENUM_LOG_LEVEL log_level=LOG_LEVEL_ERROR_MSG,const string symbol=NULL); void SetSpreadMultiplier(const uint value=1,const string symbol=NULL);
添加设置和返回启用 EA 交易标志的方法:
//--- Set/return the flag enabling sounds void SetUseSounds(const bool flag); bool IsUseSounds(void) const { return this.m_use_sound; } //--- Set/return the flag enabling trading void SetTradingDisableFlag(const bool flag) { this.m_is_trade_disable=flag; } bool IsTradingDisable(void) const { return this.m_is_trade_disable;} //--- Open (1) Buy, (2) Sell position
一旦检测到错误,这可能会阻碍进一步的交易,例如,帐户完全禁止交易。 当检测到此类错误时,即设置此标志,防止进一步发送任何无用的交易订单。
在类构造函数中,重置禁用交易标记,且交易请求为 “纠正参数” 的情况下设置默认 EA 行为:
//+------------------------------------------------------------------+ //| Constructor | //+------------------------------------------------------------------+ CTrading::CTrading() { this.m_list_errors.Clear(); this.m_list_errors.Sort(); this.m_log_level=LOG_LEVEL_ALL_MSG; this.m_is_trade_disable=false; this.m_err_handling_behavior=ERROR_HANDLING_BEHAVIOR_CORRECT; ::ZeroMemory(this.m_request); } //+------------------------------------------------------------------+
然后可以从 EA 设置中指定交易方法出错期间的 EA 行为。 不过,在所有处理程序准备就绪之前,我们将使用自动纠正方法。
当前实现的处理交易服务器返回码的方法仅返回成功标志:
//+------------------------------------------------------------------+ //| Return the error handling method | //+------------------------------------------------------------------+ ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::ResultProccessingMethod(void) { return ERROR_CODE_PROCESSING_METHOD_OK; } //+------------------------------------------------------------------+
为什么? 我们不会在当前文章中研究此方法,我们将在下一篇文章中实现交易服务器返回代码的处理。 不过,该方法已经以简化的形式进行了说明和实现。
实现纠正交易订单中错误的方法:
//+------------------------------------------------------------------+ //| Correct errors | //+------------------------------------------------------------------+ ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::RequestErrorsCorrecting(MqlTradeRequest &request, const ENUM_ORDER_TYPE order_type, const uint spread_multiplier, CSymbol *symbol_obj) { //--- The empty error list means no errors are detected, return success int total=this.m_list_errors.Total(); if(total==0) return ERROR_CODE_PROCESSING_METHOD_OK; //--- In the current implementation, all these codes are temporarily handled by interrupting a trading request if( this.IsPresentErorCode(MSG_LIB_TEXT_ACCOUNT_NOT_TRADE_ENABLED) || // Trading is disabled for the current account this.IsPresentErorCode(MSG_LIB_TEXT_ACCOUNT_EA_NOT_TRADE_ENABLED) || // Trading on the trading server side is disabled for EAs on the current account this.IsPresentErorCode(MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED) || // Trading operations are disabled in the terminal this.IsPresentErorCode(MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED) || // Trading operations are disabled for the EA this.IsPresentErorCode(MSG_SYM_TRADE_MODE_DISABLED) || // Trading on a symbol is disabled this.IsPresentErorCode(MSG_SYM_TRADE_MODE_CLOSEONLY) || // Close only this.IsPresentErorCode(MSG_SYM_MARKET_ORDER_DISABLED) || // Market orders disabled this.IsPresentErorCode(MSG_SYM_LIMIT_ORDER_DISABLED) || // Limit orders are disabled this.IsPresentErorCode(MSG_SYM_STOP_ORDER_DISABLED) || // Stop orders are disabled this.IsPresentErorCode(MSG_SYM_STOP_LIMIT_ORDER_DISABLED) || // StopLimit orders are disabled this.IsPresentErorCode(MSG_SYM_TRADE_MODE_SHORTONLY) || // Only short positions are allowed this.IsPresentErorCode(MSG_SYM_TRADE_MODE_LONGONLY) || // Only long positions are allowed this.IsPresentErorCode(MSG_SYM_CLOSE_BY_ORDER_DISABLED) || // CloseBy orders are disabled this.IsPresentErorCode(MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED) || // Exceeded maximum allowed aggregate volume of orders and positions in one direction this.IsPresentErorCode(MSG_LIB_TEXT_CLOSE_BY_ORDERS_DISABLED) || // Close by is disabled this.IsPresentErorCode(MSG_LIB_TEXT_CLOSE_BY_SYMBOLS_UNEQUAL) || // Symbols of opposite positions are not equal this.IsPresentErorCode(MSG_LIB_TEXT_UNSUPPORTED_PRICE_TYPE_IN_REQ) || // Unsupported price parameter type in a request this.IsPresentErorCode(MSG_LIB_TEXT_TRADING_DISABLE) || // Trading disabled for the EA until the reason is eliminated this.IsPresentErorCode(10006) || // Request rejected this.IsPresentErorCode(10011) || // Request handling error this.IsPresentErorCode(10012) || // Request rejected due to expiration this.IsPresentErorCode(10013) || // Invalid request this.IsPresentErorCode(10017) || // Trading disabled this.IsPresentErorCode(10018) || // Market closed this.IsPresentErorCode(10023) || // Order status changed this.IsPresentErorCode(10025) || // No changes in the request this.IsPresentErorCode(10026) || // Auto trading disabled by server this.IsPresentErorCode(10027) || // Auto trading disabled by client terminal this.IsPresentErorCode(10032) || // Transaction is allowed for live accounts only this.IsPresentErorCode(10033) || // The maximum number of pending orders is reached this.IsPresentErorCode(10034) // You have reached the maximum order and position volume for this symbol ) return ERROR_CODE_PROCESSING_METHOD_EXIT; //--- View the full list of errors and correct trading request parameters for(int i=0;i<total;i++) { int err=this.m_list_errors.At(i); if(err==NULL) continue; switch(err) { //--- Correct an invalid volume and stop levels in a trading request case MSG_LIB_TEXT_REQ_VOL_LESS_MIN_VOLUME : case MSG_LIB_TEXT_REQ_VOL_MORE_MAX_VOLUME : case MSG_LIB_TEXT_INVALID_VOLUME_STEP : request.volume=symbol_obj.NormalizedLot(request.volume); break; case MSG_SYM_SL_ORDER_DISABLED : request.sl=0; break; case MSG_SYM_TP_ORDER_DISABLED : request.tp=0; break; case MSG_LIB_TEXT_PR_LESS_STOP_LEVEL : request.price=this.CorrectPricePending(order_type,request.price,0,symbol_obj,spread_multiplier); break; case MSG_LIB_TEXT_SL_LESS_STOP_LEVEL : request.sl=this.CorrectStopLoss(order_type,request.price,request.sl,symbol_obj,spread_multiplier); break; case MSG_LIB_TEXT_TP_LESS_STOP_LEVEL : request.tp=this.CorrectTakeProfit(order_type,request.price,request.tp,symbol_obj,spread_multiplier); break; //--- If unable to select the position lot, return "abort trading attempt" since the funds are insufficient even for the minimum lot case MSG_LIB_TEXT_NOT_ENOUTH_MONEY_FOR : request.volume=this.CorrectVolume(request.volume,request.price,order_type,symbol_obj,DFUN); if(request.volume==0) return ERROR_CODE_PROCESSING_METHOD_EXIT; break; //--- Proximity to the order activation level is handled by five-second waiting - during this time, the price may go beyond the freeze level case MSG_LIB_TEXT_SL_LESS_FREEZE_LEVEL : case MSG_LIB_TEXT_TP_LESS_FREEZE_LEVEL : case MSG_LIB_TEXT_PR_LESS_FREEZE_LEVEL : return (ENUM_ERROR_CODE_PROCESSING_METHOD)5000; // ERROR_CODE_PROCESSING_METHOD_WAIT - wait 5 seconds default: break; } } return ERROR_CODE_PROCESSING_METHOD_OK; } //+------------------------------------------------------------------+
该方法的逻辑在代码注释中均有所描述。 简而言之:当检测到错误代码且尚无法处理时,我们返回 “中止交易尝试” 处理方法。 如果错误可以被纠正,则纠正参数值并返回 ОК。
改进检查交易限制和交易请求错误的方法:
//+------------------------------------------------------------------+ //| Check limitations and errors | //+------------------------------------------------------------------+ ENUM_ERROR_CODE_PROCESSING_METHOD CTrading::CheckErrors(const double volume, const double price, const ENUM_ACTION_TYPE action, const ENUM_ORDER_TYPE order_type, CSymbol *symbol_obj, const CTradeObj *trade_obj, const string source_method, const double limit=0, double sl=0, double tp=0) { //--- Check the previously set flag disabling trading for an EA if(this.IsTradingDisable()) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_FATAL_ERROR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(source_method,CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE)); return ERROR_CODE_PROCESSING_METHOD_DISABLE; } //--- result of all checks and error flags this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR; bool res=true; //--- Clear the error list this.m_list_errors.Clear(); this.m_list_errors.Sort(); //--- Check trading limitations res &=this.CheckTradeConstraints(volume,action,symbol_obj,source_method,sl,tp); //--- Check the funds sufficiency for opening positions/placing orders if(action<ACTION_TYPE_CLOSE_BY) res &=this.CheckMoneyFree(volume,price,order_type,symbol_obj,source_method); //--- Check parameter values by StopLevel and FreezeLevel res &=this.CheckLevels(action,order_type,price,limit,sl,tp,symbol_obj,source_method); //--- If there are limitations, display the header and the error list if(!res) { //--- Request was rejected before sending to the server due to: int total=this.m_list_errors.Total(); if(this.m_log_level>LOG_LEVEL_NO_MSG) { //--- For MQL5, first display the list header followed by the error list #ifdef __MQL5__ ::Print(source_method,CMessage::Text(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK ? MSG_LIB_TEXT_REQUEST_REJECTED_DUE : MSG_LIB_TEXT_INVALID_REQUEST)); for(int i=0;i<total;i++) ::Print((total>1 ? string(i+1)+". " : ""),CMessage::Text(m_list_errors.At(i))); //--- For MQL4, the journal messages are displayed in the reverse order: the error list in the reverse loop is followed by the list header #else for(int i=total-1;i>WRONG_VALUE;i--) ::Print((total>1 ? string(i+1)+". " : ""),CMessage::Text(m_list_errors.At(i))); ::Print(source_method,CMessage::Text(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK ? MSG_LIB_TEXT_REQUEST_REJECTED_DUE : MSG_LIB_TEXT_INVALID_REQUEST)); #endif } //--- If the action is performed at the "abort trading operation" error if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK) return ERROR_CODE_PROCESSING_METHOD_EXIT; //--- If the action is performed at the "create a pending request" error if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST) return ERROR_CODE_PROCESSING_METHOD_PENDING; //--- If the action is performed at the "correct parameters" error if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_CORRECT) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_CORRECTED_TRADE_REQUEST)); //--- Return the result of an attempt to correct the request parameters return this.RequestErrorsCorrecting(this.m_request,order_type,trade_obj.SpreadMultiplier(),symbol_obj); } } //--- No limitations return ERROR_CODE_PROCESSING_METHOD_OK; } //+------------------------------------------------------------------+
补充的代码用黄色标记。 现在,首先在该方法中检查禁用交易的标志。 若已设置,则返回 “EA 禁用交易” 错误处理类型。 接下来,取决于错误期间指定的 EA 行为,并根据错误代码,返回所需的错误处理方法。 如果没有错误,则返回 不需要错误处理的代码。
因为添加有关的必要标志,检查交易限制的方法经历了多次类似修改,这些标志指示各种错误类型的存在,以及处理错误的方式。
该方法中执行的所有动作,及其逻辑在代码注释中都有非常详实的说明。 所以,我们看一看最终的方法:
//+------------------------------------------------------------------+ //| Check trading limitations | //+------------------------------------------------------------------+ bool CTrading::CheckTradeConstraints(const double volume, const ENUM_ACTION_TYPE action_type, const CSymbol *symbol_obj, const string source_method, double sl=0, double tp=0) { //--- the result of conducting all checks bool res=true; //--- Check connection with the trade server (not in the test mode) if(!::TerminalInfoInteger(TERMINAL_CONNECTED)) { if(!::MQLInfoInteger(MQL_TESTER)) { //--- Write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(10031); return false; } } //--- Check if trading is enabled for an account (if there is a connection with the trade server) else if(!this.m_account.TradeAllowed()) { //--- Write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_ACCOUNT_NOT_TRADE_ENABLED); return false; } //--- Check if trading is allowed for any EAs/scripts for the current account if(!this.m_account.TradeExpert()) { //--- Write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_ACCOUNT_EA_NOT_TRADE_ENABLED); return false; } //--- Check if auto trading is allowed in the terminal. //--- AutoTrading button (Options --> Expert Advisors --> "Allowed automated trading") if(!::TerminalInfoInteger(TERMINAL_TRADE_ALLOWED)) { //--- Write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_TERMINAL_NOT_TRADE_ENABLED); return false; } //--- Check if auto trading is allowed for the current EA. //--- (F7 --> Common --> Allow Automated Trading) if(!::MQLInfoInteger(MQL_TRADE_ALLOWED)) { //--- Write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_EA_NOT_TRADE_ENABLED); return false; } //--- Check if trading is enabled on a symbol. //--- If trading is disabled, write the error code to the list and return 'false' - there is no point in further checks if(symbol_obj.TradeMode()==SYMBOL_TRADE_MODE_DISABLED) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_TRADE_MODE_DISABLED); return false; } //--- If not closing/removal/modification if(action_type<ACTION_TYPE_CLOSE_BY) { //--- In case of close-only, write the error code to the list and return 'false' - there is no point in further checks if(symbol_obj.TradeMode()==SYMBOL_TRADE_MODE_CLOSEONLY) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_TRADE_MODE_CLOSEONLY); return false; } //--- Check the minimum volume if(volume<symbol_obj.LotsMin()) { //--- The volume in a request is less than the minimum allowed one. //--- add the error code to the list this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_REQ_VOL_LESS_MIN_VOLUME); //--- If the EA behavior during the trading error is set to "abort trading operation", //--- return 'false' - there is no point in further checks if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK) return false; //--- If the EA behavior during a trading error is set to //--- "correct parameters" or "create a pending request", //--- write 'false' to the result else res &=false; } //--- Check the maximum volume else if(volume>symbol_obj.LotsMax()) { //--- The volume in the request exceeds the maximum acceptable one. //--- add the error code to the list this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_REQ_VOL_MORE_MAX_VOLUME); //--- If the EA behavior during the trading error is set to "abort trading operation", //--- return 'false' - there is no point in further checks if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK) return false; //--- If the EA behavior during a trading error is set to //--- "correct parameters" or "create a pending request", //--- write 'false' to the result else res &=false; } //--- Check the minimum volume gradation double step=symbol_obj.LotsStep(); if(fabs((int)round(volume/step)*step-volume)>0.0000001) { //--- The volume in the request is not a multiple of the minimum gradation of the lot change step //--- add the error code to the list this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_INVALID_VOLUME_STEP); //--- If the EA behavior during the trading error is set to "abort trading operation", //--- return 'false' - there is no point in further checks if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK) return false; //--- If the EA behavior during a trading error is set to //--- "correct parameters" or "create a pending request", //--- write 'false' to the result else res &=false; } } //--- When opening a position if(action_type<ACTION_TYPE_BUY_LIMIT) { //--- Check if sending market orders is allowed on a symbol. //--- If using market orders is disabled, write the error code to the list and return 'false' - there is no point in further checks if(!symbol_obj.IsMarketOrdersAllowed()) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_MARKET_ORDER_DISABLED); return false; } } //--- When placing a pending order else if(action_type>ACTION_TYPE_SELL && action_type<ACTION_TYPE_CLOSE_BY) { //--- If there is a limitation on the number of pending orders on an account and placing a new order exceeds it if(this.m_account.LimitOrders()>0 && this.OrdersTotalAll()+1 > this.m_account.LimitOrders()) { //--- The limit on the number of pending orders is reached - write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(10033); return false; } //--- Check if placing limit orders is allowed on a symbol. if(action_type==ACTION_TYPE_BUY_LIMIT || action_type==ACTION_TYPE_SELL_LIMIT) { //--- If setting limit orders is disabled, write the error code to the list and return 'false' - there is no point in further checks if(!symbol_obj.IsLimitOrdersAllowed()) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_LIMIT_ORDER_DISABLED); return false; } } //--- Check if placing stop orders is allowed on a symbol. else if(action_type==ACTION_TYPE_BUY_STOP || action_type==ACTION_TYPE_SELL_STOP) { //--- If setting stop orders is disabled, write the error code to the list and return 'false' - there is no point in further checks if(!symbol_obj.IsStopOrdersAllowed()) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_STOP_ORDER_DISABLED); return false; } } //--- For MQL5, check if placing stop limit orders is allowed on a symbol. #ifdef __MQL5__ else if(action_type==ACTION_TYPE_BUY_STOP_LIMIT || action_type==ACTION_TYPE_SELL_STOP_LIMIT) { //--- If setting stop limit orders is disabled, write the error code to the list and return 'false' - there is no point in further checks if(!symbol_obj.IsStopLimitOrdersAllowed()) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_STOP_LIMIT_ORDER_DISABLED); return false; } } #endif } //--- In case of opening/placing/modification if(action_type!=ACTION_TYPE_CLOSE_BY) { //--- If not modification if(action_type!=ACTION_TYPE_MODIFY) { //--- When buying, check if long trading is enabled on a symbol if(this.DirectionByActionType(action_type)==ORDER_TYPE_BUY) { //--- If only short positions are enabled, write the error code to the list and return 'false' - there is no point in further checks if(symbol_obj.TradeMode()==SYMBOL_TRADE_MODE_SHORTONLY) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_TRADE_MODE_SHORTONLY); return false; } //--- If a symbol has the limitation on the total volume of an open position and pending orders in the same direction if(symbol_obj.VolumeLimit()>0) { //--- (If the total volume of placed long orders and open long positions)+open volume exceed the maximum one if(this.OrdersTotalVolumeLong()+this.PositionsTotalVolumeLong()+volume > symbol_obj.VolumeLimit()) { //--- Exceeded maximum allowed aggregate volume of orders and positions in one direction //--- write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED); return false; } } } //--- When selling, check if short trading is enabled on a symbol else if(this.DirectionByActionType(action_type)==ORDER_TYPE_SELL) { //--- If only long positions are enabled, write the error code to the list and return 'false' - there is no point in further checks if(symbol_obj.TradeMode()==SYMBOL_TRADE_MODE_LONGONLY) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_TRADE_MODE_LONGONLY); return false; } //--- If a symbol has the limitation on the total volume of an open position and pending orders in the same direction if(symbol_obj.VolumeLimit()>0) { //--- (If the total volume of placed short orders and open short positions)+open volume exceed the maximum one if(this.OrdersTotalVolumeShort()+this.PositionsTotalVolumeShort()+volume > symbol_obj.VolumeLimit()) { //--- Exceeded maximum allowed aggregate volume of orders and positions in one direction //--- write the error code to the list and return 'false' - there is no point in further checks this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_MAX_VOLUME_LIMIT_EXCEEDED); return false; } } } } //--- If the request features StopLoss and its placing is not allowed if(sl>0 && !symbol_obj.IsStopLossOrdersAllowed()) { //--- add the error code to the list this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_SL_ORDER_DISABLED); //--- If the EA behavior during the trading error is set to "abort trading operation", //--- return 'false' - there is no point in further checks if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK) return false; //--- If the EA behavior during a trading error is set to //--- "correct parameters" or "create a pending request", //--- write 'false' to the result else res &=false; } //--- If the request features TakeProfit and its placing is not allowed if(tp>0 && !symbol_obj.IsTakeProfitOrdersAllowed()) { //--- add the error code to the list this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_SYM_TP_ORDER_DISABLED); //--- If the EA behavior during the trading error is set to "abort trading operation", //--- return 'false' - there is no point in further checks if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_BREAK) return false; //--- If the EA behavior during a trading error is set to //--- "correct parameters" or "create a pending request", //--- write 'false' to the result else res &=false; } } //--- When closing by an opposite position else if(action_type==ACTION_TYPE_CLOSE_BY) { //--- When closing by an opposite position is disabled if(!symbol_obj.IsCloseByOrdersAllowed()) { //--- write the error code to the list and return 'false' this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_CLOSE_BY_ORDERS_DISABLED); return false; } } return res; } //+------------------------------------------------------------------+
在按 StopLevel 和 FreezeLevel 返回参数值的方法中,添加标志,指示应查看错误列表中的每个检测到的错误:
//+------------------------------------------------------------------+ //| Check parameter values by StopLevel and FreezeLevel | //+------------------------------------------------------------------+ bool CTrading::CheckLevels(const ENUM_ACTION_TYPE action, const ENUM_ORDER_TYPE order_type, double price, double limit, double sl, double tp, const CSymbol *symbol_obj, const string source_method) { //--- the result of conducting all checks bool res=true; //--- StopLevel //--- If this is not a position closure/order removal if(action!=ACTION_TYPE_CLOSE && action!=ACTION_TYPE_CLOSE_BY) { //--- When placing a pending order if(action>ACTION_TYPE_SELL) { //--- If the placement distance in points is less than StopLevel if(!this.CheckPriceByStopLevel(order_type,price,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_PR_LESS_STOP_LEVEL); res &=false; } } //--- If StopLoss is present if(sl>0) { //--- If StopLoss distance in points from the open price is less than StopLevel double price_open=(action==ACTION_TYPE_BUY_STOP_LIMIT || action==ACTION_TYPE_SELL_STOP_LIMIT ? limit : price); if(!this.CheckStopLossByStopLevel(order_type,price_open,sl,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_SL_LESS_STOP_LEVEL); res &=false; } } //--- If TakeProfit is present if(tp>0) { double price_open=(action==ACTION_TYPE_BUY_STOP_LIMIT || action==ACTION_TYPE_SELL_STOP_LIMIT ? limit : price); //--- If TakeProfit distance in points from the open price is less than StopLevel if(!this.CheckTakeProfitByStopLevel(order_type,price_open,tp,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_TP_LESS_STOP_LEVEL); res &=false; } } } //--- FreezeLevel //--- If this is a position closure/order removal/modification if(action>ACTION_TYPE_SELL_STOP_LIMIT) { //--- If this is a position if(order_type<ORDER_TYPE_BUY_LIMIT) { //--- StopLoss modification if(sl>0) { //--- If the distance from the price to StopLoss is less than FreezeLevel if(!this.CheckStopLossByFreezeLevel(order_type,sl,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_SL_LESS_FREEZE_LEVEL); res &=false; } } //--- TakeProfit modification if(tp>0) { //--- If the distance from the price to StopLoss is less than FreezeLevel if(!this.CheckTakeProfitByFreezeLevel(order_type,tp,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_TP_LESS_FREEZE_LEVEL); res &=false; } } } //--- If this is a pending order else { //--- Placement price modification if(price>0) { //--- If the distance from the price to the order activation price is less than FreezeLevel if(!this.CheckPriceByFreezeLevel(order_type,price,symbol_obj)) { //--- add the error code to the list and write 'false' to the result this.m_error_reason_flags &=TRADE_REQUEST_ERR_FLAG_ERROR_IN_LIST; this.AddErrorCodeToList(MSG_LIB_TEXT_PR_LESS_FREEZE_LEVEL); res &=false; } } } } return res; } //+------------------------------------------------------------------+
在设置交易请求价格的方法中,添加更新价格,并在出现更新错误时携带相应的错误代码退出:
//+------------------------------------------------------------------+ //| Set trading request prices | //+------------------------------------------------------------------+ template <typename PR,typename SL,typename TP,typename PL> bool CTrading::SetPrices(const ENUM_ORDER_TYPE action,const PR price,const SL sl,const TP tp,const PL limit,const string source_method,CSymbol *symbol_obj) { //--- Reset prices ::ZeroMemory(this.m_request); //--- Update all data by symbol if(!symbol_obj.RefreshRates()) { this.AddErrorCodeToList(10021); return false; } //--- Open/close price
并且,设置交易请求价格的方法中还修改了价格计算:
//--- Calculate the order price switch((int)action) { //--- Pending order case ORDER_TYPE_BUY_LIMIT : this.m_request.price=::NormalizeDouble(symbol_obj.Ask()-price*symbol_obj.Point(),symbol_obj.Digits()); break; case ORDER_TYPE_BUY_STOP : case ORDER_TYPE_BUY_STOP_LIMIT : this.m_request.price=::NormalizeDouble(symbol_obj.Ask()+price*symbol_obj.Point(),symbol_obj.Digits()); break; case ORDER_TYPE_SELL_LIMIT : this.m_request.price=::NormalizeDouble(symbol_obj.BidLast()+price*symbol_obj.Point(),symbol_obj.Digits()); break; case ORDER_TYPE_SELL_STOP : case ORDER_TYPE_SELL_STOP_LIMIT : this.m_request.price=::NormalizeDouble(symbol_obj.BidLast()-price*symbol_obj.Point(),symbol_obj.Digits()); break; //--- Default - current position open prices default : this.m_request.price= ( this.DirectionByActionType((ENUM_ACTION_TYPE)action)==ORDER_TYPE_BUY ? ::NormalizeDouble(symbol_obj.Ask(),symbol_obj.Digits()) : ::NormalizeDouble(symbol_obj.BidLast(),symbol_obj.Digits()) ); break; }
品种对象类的方法 Bid() 已由 BidLast() 方法取代,它取决于图表构建模式返回 Bid 或 Last 价格。
为所有品种的交易对象设置点差倍数的方法:
//+------------------------------------------------------------------+ //| Set the spread multiplier | //| for trading objects of all symbols | //+------------------------------------------------------------------+ void CTrading::SetSpreadMultiplier(const uint value=1,const string symbol=NULL) { CSymbol *symbol_obj=NULL; if(symbol==NULL) { CArrayObj *list=this.m_symbols.GetList(); if(list==NULL || list.Total()==0) return; int total=list.Total(); for(int i=0;i<total;i++) { symbol_obj=list.At(i); if(symbol_obj==NULL) continue; CTradeObj *trade_obj=symbol_obj.GetTradeObj(); if(trade_obj==NULL) continue; trade_obj.SetSpreadMultiplier(value); } } else { CTradeObj *trade_obj=this.GetTradeObjBySymbol(symbol,DFUN); if(trade_obj==NULL) return; trade_obj.SetSpreadMultiplier(value); } } //+------------------------------------------------------------------+
该方法接收倍数值(默认为 1)和品种名称(默认为 NULL)。
如果传递 NULL 作为品种名称,则该倍数会被设置到已有交易品种集合中所有品种的交易对象之中。
否则,该值将赋值给该方法所传递品种的交易对象。
由于新增错误处理,所有交易方法均被重新提炼。
我们研究开立多头仓位的代码:
//+------------------------------------------------------------------+ //| Open Buy position | //+------------------------------------------------------------------+ template<typename SL,typename TP> bool CTrading::OpenBuy(const double volume, const string symbol, const ulong magic=ULONG_MAX, const SL sl=0, const TP tp=0, const string comment=NULL, const ulong deviation=ULONG_MAX) { //--- Set the trading request result as 'true' and the error flag as "no errors" bool res=true; this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_NO_ERROR; ENUM_ACTION_TYPE action=ACTION_TYPE_BUY; ENUM_ORDER_TYPE order_type=ORDER_TYPE_BUY; //--- Get a symbol object by a symbol name. If failed to get CSymbol *symbol_obj=this.m_symbols.GetSymbolObjByName(symbol); //--- If failed to get - write the "internal error" flag, display the message in the journal and return 'false' if(symbol_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_SYM_OBJ)); return false; } //--- get a trading object from a symbol object CTradeObj *trade_obj=symbol_obj.GetTradeObj(); //--- If failed to get - write the "internal error" flag, display the message in the journal and return 'false' if(trade_obj==NULL) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_ERROR_FAILED_GET_TRADE_OBJ)); return false; } //--- Set the prices //--- If failed to set - write the "internal error" flag, display the message in the journal and return 'false' if(!this.SetPrices(order_type,0,sl,tp,0,DFUN,symbol_obj)) { this.m_error_reason_flags=TRADE_REQUEST_ERR_FLAG_INTERNAL_ERR; if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(10021)); return false; } //--- Write the volume to the request structure this.m_request.volume=volume; //--- Get the method of handling errors from the CheckErrors() method while checking for errors ENUM_ERROR_CODE_PROCESSING_METHOD method=this.CheckErrors(this.m_request.volume,symbol_obj.Ask(),action,order_type,symbol_obj,trade_obj,DFUN,0,this.m_request.sl,this.m_request.tp); //--- In case of trading limitations, funds insufficiency, //--- if there are limitations by StopLevel or FreezeLevel ... if(method!=ERROR_CODE_PROCESSING_METHOD_OK) { //--- If trading is disabled completely, display a journal message, play the error sound and exit if(method==ERROR_CODE_PROCESSING_METHOD_DISABLE) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_DISABLE)); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); return false; } //--- If the check result is "abort trading operation" - display a journal message, play the error sound and exit if(method==ERROR_CODE_PROCESSING_METHOD_EXIT) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_TRADING_OPERATION_ABORTED)); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); return false; } //--- If the check result is "waiting", display the message in the journal if(method==ERROR_CODE_PROCESSING_METHOD_EXIT) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST)); //--- Instead of creating a pending request, we temporarily wait the required time period (the CheckErrors() method result is returned) ::Sleep(method); //--- after waiting, update all data symbol_obj.Refresh(); } //--- If the check result is "create a pending request", do nothing temporarily if(this.m_err_handling_behavior==ERROR_HANDLING_BEHAVIOR_PENDING_REQUEST) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_CREATE_PENDING_REQUEST)); } } //--- Send the request res=trade_obj.OpenPosition(POSITION_TYPE_BUY,this.m_request.volume,this.m_request.sl,this.m_request.tp,magic,comment,deviation); //--- If the request is successful, play the success sound set for a symbol trading object for this type of trading operation if(res) { if(this.IsUseSounds()) trade_obj.PlaySoundSuccess(action,order_type); } //--- If the request is not successful, play the error sound set for a symbol trading object for this type of trading operation else { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_SYS_ERROR),": ",CMessage::Text(trade_obj.GetResultRetcode())); if(this.IsUseSounds()) trade_obj.PlaySoundError(action,order_type); } //--- Return the result of sending a trading request in a symbol trading object return res; } //+------------------------------------------------------------------+
所有澄清都在代码注释中进行了详细说明。 其他交易方法也以类似的方式加以改进。 我希望此处一切都清楚。 如有情况,欢迎您使用评论板块。
返回计算有效停止和挂单价格的方法:
//+------------------------------------------------------------------+ //| Return correct StopLoss relative to StopLevel | //+------------------------------------------------------------------+ double CTrading::CorrectStopLoss(const ENUM_ORDER_TYPE order_type,const double price_set,const double stop_loss,const CSymbol *symbol_obj,const uint spread_multiplier=1) { if(stop_loss==0) return 0; uint lv=(symbol_obj.TradeStopLevel()==0 ? symbol_obj.Spread()*spread_multiplier : symbol_obj.TradeStopLevel()); double price=(order_type==ORDER_TYPE_BUY ? symbol_obj.BidLast() : order_type==ORDER_TYPE_SELL ? symbol_obj.Ask() : price_set); return (this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY ? ::NormalizeDouble(fmin(price-lv*symbol_obj.Point(),stop_loss),symbol_obj.Digits()) : ::NormalizeDouble(fmax(price+lv*symbol_obj.Point(),stop_loss),symbol_obj.Digits()) ); } //+------------------------------------------------------------------+ //| Return correct TakeProfit relative to StopLevel | //+------------------------------------------------------------------+ double CTrading::CorrectTakeProfit(const ENUM_ORDER_TYPE order_type,const double price_set,const double take_profit,const CSymbol *symbol_obj,const uint spread_multiplier=1) { if(take_profit==0) return 0; uint lv=(symbol_obj.TradeStopLevel()==0 ? symbol_obj.Spread()*spread_multiplier : symbol_obj.TradeStopLevel()); double price=(order_type==ORDER_TYPE_BUY ? symbol_obj.BidLast() : order_type==ORDER_TYPE_SELL ? symbol_obj.Ask() : price_set); return (this.DirectionByActionType((ENUM_ACTION_TYPE)order_type)==ORDER_TYPE_BUY ? ::NormalizeDouble(fmax(price+lv*symbol_obj.Point(),take_profit),symbol_obj.Digits()) : ::NormalizeDouble(fmin(price-lv*symbol_obj.Point(),take_profit),symbol_obj.Digits()) ); } //+------------------------------------------------------------------+ //| Return the correct order placement price | //| relative to StopLevel | //+------------------------------------------------------------------+ double CTrading::CorrectPricePending(const ENUM_ORDER_TYPE order_type,const double price_set,const double price,const CSymbol *symbol_obj,const uint spread_multiplier=1) { uint lv=(symbol_obj.TradeStopLevel()==0 ? symbol_obj.Spread()*spread_multiplier : symbol_obj.TradeStopLevel()); double pp=0; switch((int)order_type) { case ORDER_TYPE_BUY_LIMIT : pp=(price==0 ? symbol_obj.Ask() : price); return ::NormalizeDouble(fmin(pp-lv*symbol_obj.Point(),price_set),symbol_obj.Digits()); case ORDER_TYPE_BUY_STOP : case ORDER_TYPE_BUY_STOP_LIMIT : pp=(price==0 ? symbol_obj.Ask() : price); return ::NormalizeDouble(fmax(pp+lv*symbol_obj.Point(),price_set),symbol_obj.Digits()); case ORDER_TYPE_SELL_LIMIT : pp=(price==0 ? symbol_obj.BidLast() : price); return ::NormalizeDouble(fmax(pp+lv*symbol_obj.Point(),price_set),symbol_obj.Digits()); case ORDER_TYPE_SELL_STOP : case ORDER_TYPE_SELL_STOP_LIMIT : pp=(price==0 ? symbol_obj.BidLast() : price); return ::NormalizeDouble(fmin(pp-lv*symbol_obj.Point(),price_set),symbol_obj.Digits()); default : if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(DFUN,CMessage::Text(MSG_LIB_SYS_INVALID_ORDER_TYPE),::EnumToString(order_type)); return 0; } } //+------------------------------------------------------------------+
即使没有代码注释,此处所有内容也应该很清晰 — 将传递给方法的价格与开仓价加上 StopLevel 偏移获得的价位进行比较。 有效(高于/低于,取决于订单类型)价格返回给调用程序。
返回在该价位开仓时可能的交易量的方法:
//+------------------------------------------------------------------+ //| Return the volume, at which it is possible to open a position | //+------------------------------------------------------------------+ double CTrading::CorrectVolume(const double price,const ENUM_ORDER_TYPE order_type,CSymbol *symbol_obj,const string source_method) { //--- If funds are insufficient for the minimum lot, inform of that and return zero if(!this.CheckMoneyFree(symbol_obj.LotsMin(),price,order_type,symbol_obj,source_method)) { if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_NOT_POSSIBILITY_CORRECT_LOT)); return 0; } //--- Update account and symbol data this.m_account.Refresh(); symbol_obj.RefreshRates(); //--- Calculate the lot, which is closest to the acceptable one double vol=symbol_obj.NormalizedLot(this.m_account.Equity()*this.m_account.Leverage()/symbol_obj.TradeContractSize()/(symbol_obj.CurrencyBase()=="USD" ? 1.0 : symbol_obj.BidLast())); //--- Calculate a sufficient lot double margin=this.m_account.MarginForAction(order_type,symbol_obj.Name(),1.0,price); if(margin!=EMPTY_VALUE) vol=symbol_obj.NormalizedLot(this.m_account.MarginFree()/margin); //--- If the calculated lot is invalid or the margin calculation returns an error if(!this.CheckMoneyFree(vol,price,order_type,symbol_obj,source_method)) { //--- In the do-while loop, while the calculated valid volume exceeds the minimum lot do { //--- Subtract the minimum lot from the valid lot value vol-=symbol_obj.LotsStep(); //--- If the calculated lot allows opening a position/setting an order, return the lot value if(this.CheckMoneyFree(symbol_obj.NormalizedLot(vol),price,order_type,symbol_obj,source_method)) return vol; } while(vol>symbol_obj.LotsMin() && !::IsStopped()); } //--- If the lot is calculated correctly, return the calculated lot else return vol; //--- If the current stage is reached, the funds are insufficient. Inform of that and return zero if(this.m_log_level>LOG_LEVEL_NO_MSG) ::Print(CMessage::Text(MSG_LIB_TEXT_NOT_POSSIBILITY_CORRECT_LOT)); return 0; } //+------------------------------------------------------------------+
该代码也在此加以注释。
首先,我们检查按最小手数开仓的能力。若不可,那么进一步的计算就毫无意义 — 返回零。
接下来,我们计算大约允许的手数(以免在可能调整的情况下从最大值里“选择”所需的手数)。
接着,计算倾尽所有允许的开仓最大手数。 为何如此? 如果资金不足以开仓,则意味着所需的交易量很大,意即我们需要计算最大可能交易量。
在此计算中,我们用到 OrderCalcMargin() 函数,该函数在出现错误的情况下也许会返回 false,而 CAccount 类的 MarginForAction() 方法调用此函数返回与 DBL_MAX 常量值(最大值可由 double 类型表示)相应的 EMPTY_VALUE。 如果我们收到此值,则说明存在错误,并且尚未计算手数。
在此情况下(不仅是在出错的情况下,且在检查计算有效性时),我们在交易订单中用计算出的最大可能交易量简单地减去手数间距,得到所需最大手数的 “选择” 。 其中我们需要先前计算的 大约可用交易量。 如果我们无法计算确切的交易量,则手数减少循环将从其最近的手数开始(而不是为该品种设置的最大手数),从而大大减少了循环迭代次数。
顺便说一句,在检查期间,我在计算手数时没有收到 OrderCalcMargin() 函数错误。 但是,仍然有无效的计算发生(大约,一手的间距)。
交易类别的修改和改进至此完毕。
为了执行测试,我们利用上一篇文章中的 EA,将其保存到 \MQL5\Experts\TestDoEasy\Part24\ 之下,命名为 TestDoEasyPart24.mq5。
在全局变量列表中添加在策略测试器中操作的标记:
//--- global variables CEngine engine; SDataButt butt_data[TOTAL_BUTT]; string prefix; double lot; double withdrawal=(InpWithdrawal<0.1 ? 0.1 : InpWithdrawal); ulong magic_number; uint stoploss; uint takeprofit; uint distance_pending; uint distance_stoplimit; uint slippage; bool trailing_on; double trailing_stop; double trailing_step; uint trailing_start; uint stoploss_to_modify; uint takeprofit_to_modify; int used_symbols_mode; string used_symbols; string array_used_symbols[]; bool testing; //+------------------------------------------------------------------+
在 OnInit() 处理程序中,设置策略测试器中操作的标记值:
//+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Calling the function displays the list of enumeration constants in the journal //--- (the list is set in the strings 22 and 25 of the DELib.mqh file) for checking the constants validity //EnumNumbersTest(); //--- Set EA global variables prefix=MQLInfoString(MQL_PROGRAM_NAME)+"_"; testing=engine.IsTester(); for(int i=0;i<TOTAL_BUTT;i++) { butt_data[i].name=prefix+EnumToString((ENUM_BUTTONS)i); butt_data[i].text=EnumToButtText((ENUM_BUTTONS)i); }
为了在测试器中操作时将事件发送到 OnDoEasyEvent() 函数库事件处理程序,我们需要特殊的函数 EventsHandling()。
它已得到略微改进:
//+------------------------------------------------------------------+ //| Working with events in the tester | //+------------------------------------------------------------------+ void EventsHandling(void) { //--- If a trading event is present if(engine.IsTradeEvent()) { //--- Number of trading events occurred simultaneously int total=engine.GetTradeEventsTotal(); for(int i=0;i<total;i++) { //--- Get the next event from the list of simultaneously occurred events by index CEventBaseObj *event=engine.GetTradeEventByIndex(i); if(event==NULL) continue; long lparam=i; double dparam=event.DParam(); string sparam=event.SParam(); OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } //--- If there is an account event if(engine.IsAccountsEvent()) { //--- Get the list of all account events occurred simultaneously CArrayObj* list=engine.GetListAccountEvents(); if(list!=NULL) { //--- Get the next event in a loop int total=list.Total(); for(int i=0;i<total;i++) { //--- take an event from the list CEventBaseObj *event=list.At(i); if(event==NULL) continue; //--- Send an event to the event handler long lparam=event.LParam(); double dparam=event.DParam(); string sparam=event.SParam(); OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } } //--- If there is a symbol collection event if(engine.IsSymbolsEvent()) { //--- Get the list of all symbol events occurred simultaneously CArrayObj* list=engine.GetListSymbolsEvents(); if(list!=NULL) { //--- Get the next event in a loop int total=list.Total(); for(int i=0;i<total;i++) { //--- take an event from the list CEventBaseObj *event=list.At(i); if(event==NULL) continue; //--- Send an event to the event handler long lparam=event.LParam(); double dparam=event.DParam(); string sparam=event.SParam(); OnDoEasyEvent(CHARTEVENT_CUSTOM+event.ID(),lparam,dparam,sparam); } } } } //+------------------------------------------------------------------+
此处的代码注释很清楚。
由于我们现在已经创建了新交易事件的列表,我们在 OnDoEasyEvent() 函数库事件处理程序中按事件索引从所有新交易事件的列表中获取每个事件,并简单地在日志中显示所有事件的描述:
//+------------------------------------------------------------------+ //| Handling DoEasy library events | //+------------------------------------------------------------------+ void OnDoEasyEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { int idx=id-CHARTEVENT_CUSTOM; //--- Retrieve (1) event time milliseconds, (2) reason and (3) source from lparam, as well as (4) set the exact event time ushort msc=engine.EventMSC(lparam); ushort reason=engine.EventReason(lparam); ushort source=engine.EventSource(lparam); long time=TimeCurrent()*1000+msc; //--- Handling symbol events if(source==COLLECTION_SYMBOLS_ID) { CSymbol *symbol=engine.GetSymbolObjByName(sparam); if(symbol==NULL) return; //--- Number of decimal places in the event value - in case of a 'long' event, it is 0, otherwise - Digits() of a symbol int digits=(idx<SYMBOL_PROP_INTEGER_TOTAL ? 0 : symbol.Digits()); //--- Event text description string id_descr=(idx<SYMBOL_PROP_INTEGER_TOTAL ? symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_INTEGER)idx) : symbol.GetPropertyDescription((ENUM_SYMBOL_PROP_DOUBLE)idx)); //--- Property change text value string value=DoubleToString(dparam,digits); //--- Check event reasons and display its description in the journal if(reason==BASE_EVENT_REASON_INC) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_DEC) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_MORE_THEN) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_LESS_THEN) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_EQUALS) { Print(symbol.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } } //--- Handling account events else if(source==COLLECTION_ACCOUNT_ID) { CAccount *account=engine.GetAccountCurrent(); if(account==NULL) return; //--- Number of decimal places in the event value - in case of a 'long' event, it is 0, otherwise - Digits() of a symbol int digits=int(idx<ACCOUNT_PROP_INTEGER_TOTAL ? 0 : account.CurrencyDigits()); //--- Event text description string id_descr=(idx<ACCOUNT_PROP_INTEGER_TOTAL ? account.GetPropertyDescription((ENUM_ACCOUNT_PROP_INTEGER)idx) : account.GetPropertyDescription((ENUM_ACCOUNT_PROP_DOUBLE)idx)); //--- Property change text value string value=DoubleToString(dparam,digits); //--- Checking event reasons and handling the increase of funds by a specified value, //--- In case of a property value increase if(reason==BASE_EVENT_REASON_INC) { //--- Display an event in the journal Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); //--- if this is an equity increase if(idx==ACCOUNT_PROP_EQUITY) { //--- Get the list of all open positions CArrayObj* list_positions=engine.GetListMarketPosition(); //--- Select positions with the profit exceeding zero list_positions=CSelect::ByOrderProperty(list_positions,ORDER_PROP_PROFIT_FULL,0,MORE); if(list_positions!=NULL) { //--- Sort the list by profit considering commission and swap list_positions.Sort(SORT_BY_ORDER_PROFIT_FULL); //--- Get the position index with the highest profit int index=CSelect::FindOrderMax(list_positions,ORDER_PROP_PROFIT_FULL); if(index>WRONG_VALUE) { COrder* position=list_positions.At(index); if(position!=NULL) { //--- Get a ticket of a position with the highest profit and close the position by a ticket engine.ClosePosition(position.Ticket()); } } } } } //--- Other events are simply displayed in the journal if(reason==BASE_EVENT_REASON_DEC) { Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_MORE_THEN) { Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_LESS_THEN) { Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } if(reason==BASE_EVENT_REASON_EQUALS) { Print(account.EventDescription(idx,(ENUM_BASE_EVENT_REASON)reason,source,value,id_descr,digits)); } } //--- Handling market watch window events else if(idx>MARKET_WATCH_EVENT_NO_EVENT && idx<SYMBOL_EVENTS_NEXT_CODE) { //--- Market Watch window event string descr=engine.GetMWEventDescription((ENUM_MW_EVENT)idx); string name=(idx==MARKET_WATCH_EVENT_SYMBOL_SORT ? "" : ": "+sparam); Print(TimeMSCtoString(lparam)," ",descr,name); } //--- Handling trading events else if(idx>TRADE_EVENT_NO_EVENT && idx<TRADE_EVENTS_NEXT_CODE) { //--- Get the list of trading events CArrayObj *list=engine.GetListAllOrdersEvents(); if(list==NULL) return; //--- get the event index shift relative to the end of the list //--- in the tester, the shift is passed by the lparam parameter to the event handler //--- outside the tester, events are sent one by one and handled in OnChartEvent() int shift=(testing ? (int)lparam : 0); CEvent *event=list.At(list.Total()-1-shift); if(event==NULL) return; //--- Accrue the credit if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CREDIT) { Print(DFUN,event.TypeEventDescription()); } //--- Additional charges if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CHARGE) { Print(DFUN,event.TypeEventDescription()); } //--- Correction if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_CORRECTION) { Print(DFUN,event.TypeEventDescription()); } //--- Enumerate bonuses if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BONUS) { Print(DFUN,event.TypeEventDescription()); } //--- Additional commissions if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION) { Print(DFUN,event.TypeEventDescription()); } //--- Daily commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_DAILY) { Print(DFUN,event.TypeEventDescription()); } //--- Monthly commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_MONTHLY) { Print(DFUN,event.TypeEventDescription()); } //--- Daily agent commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_DAILY) { Print(DFUN,event.TypeEventDescription()); } //--- Monthly agent commission if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_COMISSION_AGENT_MONTHLY) { Print(DFUN,event.TypeEventDescription()); } //--- Interest rate if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_INTEREST) { Print(DFUN,event.TypeEventDescription()); } //--- Canceled buy deal if(event.TypeEvent()==TRADE_EVENT_BUY_CANCELLED) { Print(DFUN,event.TypeEventDescription()); } //--- Canceled sell deal if(event.TypeEvent()==TRADE_EVENT_SELL_CANCELLED) { Print(DFUN,event.TypeEventDescription()); } //--- Dividend operations if(event.TypeEvent()==TRADE_EVENT_DIVIDENT) { Print(DFUN,event.TypeEventDescription()); } //--- Accrual of franked dividend if(event.TypeEvent()==TRADE_EVENT_DIVIDENT_FRANKED) { Print(DFUN,event.TypeEventDescription()); } //--- Tax charges if(event.TypeEvent()==TRADE_EVENT_TAX) { Print(DFUN,event.TypeEventDescription()); } //--- Replenishing account balance if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_REFILL) { Print(DFUN,event.TypeEventDescription()); } //--- Withdrawing funds from balance if(event.TypeEvent()==TRADE_EVENT_ACCOUNT_BALANCE_WITHDRAWAL) { Print(DFUN,event.TypeEventDescription()); } //--- Pending order placed if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_PLASED) { Print(DFUN,event.TypeEventDescription()); } //--- Pending order removed if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_REMOVED) { Print(DFUN,event.TypeEventDescription()); } //--- Pending order activated by price if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED) { Print(DFUN,event.TypeEventDescription()); } //--- Pending order partially activated by price if(event.TypeEvent()==TRADE_EVENT_PENDING_ORDER_ACTIVATED_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Position opened if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED) { Print(DFUN,event.TypeEventDescription()); } //--- Position opened partially if(event.TypeEvent()==TRADE_EVENT_POSITION_OPENED_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed by an opposite one if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_POS) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed by StopLoss if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_SL) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed by TakeProfit if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_BY_TP) { Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by a new deal (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET) { Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by activating a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING) { Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by partial market order execution (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_MARKET_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Position reversal by activating a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_REVERSED_BY_PENDING_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by a new deal (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET) { Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by partial execution of a market order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_MARKET_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by activating a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING) { Print(DFUN,event.TypeEventDescription()); } //--- Added volume to a position by partial activation of a pending order (netting) if(event.TypeEvent()==TRADE_EVENT_POSITION_VOLUME_ADD_BY_PENDING_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed partially if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL) { Print(DFUN,event.TypeEventDescription()); } //--- Position partially closed by an opposite one if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_POS) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed partially by StopLoss if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_SL) { Print(DFUN,event.TypeEventDescription()); } //--- Position closed partially by TakeProfit if(event.TypeEvent()==TRADE_EVENT_POSITION_CLOSED_PARTIAL_BY_TP) { Print(DFUN,event.TypeEventDescription()); } //--- StopLimit order activation if(event.TypeEvent()==TRADE_EVENT_TRIGGERED_STOP_LIMIT_ORDER) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order and StopLoss price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order and TakeProfit price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_TAKE_PROFIT) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order, StopLoss and TakeProfit price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_PRICE_STOP_LOSS_TAKE_PROFIT) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order's StopLoss and TakeProfit price if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS_TAKE_PROFIT) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order's StopLoss if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_STOP_LOSS) { Print(DFUN,event.TypeEventDescription()); } //--- Changing order's TakeProfit if(event.TypeEvent()==TRADE_EVENT_MODIFY_ORDER_TAKE_PROFIT) { Print(DFUN,event.TypeEventDescription()); } //--- Changing position's StopLoss and TakeProfit if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS_TAKE_PROFIT) { Print(DFUN,event.TypeEventDescription()); } //--- Changing position StopLoss if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_STOP_LOSS) { Print(DFUN,event.TypeEventDescription()); } //--- Changing position TakeProfit if(event.TypeEvent()==TRADE_EVENT_MODIFY_POSITION_TAKE_PROFIT) { Print(DFUN,event.TypeEventDescription()); } } } //+------------------------------------------------------------------+
为了更加简化,我们只是从列表中按事件索引得到事件(对于测试器,该索引是传递给 EventsHandling() 函数的 lparam 参数,而在模拟账户和真实账户里,索引始终等于零,因为每个事件都作为独立事件,而不是从列表发送到 OnChartEvent(),并在日志中显示所获事件的描述。
您可以自行安排处理流程。 您可以在同一代码中直接实现处理,也可以在此处声明事件标志列表,从而为发生的事件设置标志,而实际处理则在单独的函数中执行。
所有这些更改和改进,都是为控制所有同时发生的交易事件所必须的。 该函数库已经具有自动纠正交易请求参数错误的所有必要内容。 而 EA 无需任何修改(目前)。 进而,在创建了处理错误的所有方式之后,我们将引入一个附加输入,指示错误期间 EA 的行为。
编译 EA 并在测试器中将之启动。 下若干笔挂订单,并在单一循环中将其全部删除:
EA 在日志中显示四个事件。 单击 “删除挂单” 后,在一个循环中删除四笔挂单时,会发生这些事件。
现在,我们在策略测试器的 EA 设置中输入较大的手数(例如 100.0),然后尝试下挂单或开仓:
尝试下挂单并开立 100.0 手的仓位后,我们在日志里得到消息,告知资金不足和交易量调整。 此后,挂单被放置,并开仓。
在下一篇文章中,我们将实现处理交易服务器返回的错误。
文后附有当前版本含糊库的所有文件,以及测试 EA 文件,供您测试和下载。
请在评论中留下您的问题、意见和建议。
返回目录
系列中的前几篇文章:
第一部分 概念,数据管理本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程