交易机器人在市场发布前必须经过的检验

本文详细介绍了如何使用交易平台的策略测试器来发现和修复交易机器人中的错误,包括检查资金不足、交易量错误、挂单限制、获利和止损水平以及订单修改限制等。通过在历史数据上进行调试,可以有效识别和解决这些问题,确保交易系统的稳定性和效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

怎样快速捕捉和修复交易机器人中的错误

平台中集成的策略测试器不仅允许回测交易系统,而且可以用于发现交易机器人开发过程中的逻辑和算法错误,在测试中,所有有关交易操作的消息以及发现的错误都输出在测试器的日志(Journal)中。使用特别的记录阅读器就可以很方便地分析这些消息, 它可以使用上下文菜单的命令调用出来。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

在EA交易的测试之后,打开阅读器并启用"只显示错误(Error only)"模式,如上图所示。如果您的交易机器人包含错误,您就能立即看到它们。如果第一次没有侦测到错误,可以在不同的交易品种/时段/输入参数以及不同数量的初始存款情况下进行一系列的测试。使用这些简单的技巧可以发现 99% 的错误,并且我们会在本文中讨论它们。

我们可以使用在 MetaEditor 中的在历史数据上做调试的功能来仔细研究发现的错误,通过这个方法,我们可以使用可视化测试模式,不仅监控价格图表和使用的指标,也能跟踪程序在每个时刻的变量数值。这样,您就能够调试您的交易策略了,而不必在实时模式下花费很长时间。

资金不足以进行交易操作

在发送每个交易订单之前,需要检查账户是否有足够的资金,缺乏资金以进行未来的建仓或者订单会被认为是疏忽大意的。

请一定要记住就算设置一个挂单也可能会要求担保 — 保证金

编辑切换为居中

添加图片注释,不超过 140 字(可选)

我们推荐特意使用很小的初始存款来测试交易机器人,例如,1美元或者1欧元。

如果检查显示,资金不足以进行交易操作,就有必要在记录中输出一个错误消息而不是调用 OrderSend() 函数。检验实例:

MQL5

 
 

bool CheckMoneyForTrade(string symb,double lots,ENUM_ORDER_TYPE type) { //--- 取得建仓价格 MqlTick mqltick; SymbolInfoTick(symb,mqltick); double price=mqltick.ask; if(type==ORDER_TYPE_SELL) price=mqltick.bid; //--- 所需以及可用保证金的数值 double margin,free_margin=AccountInfoDouble(ACCOUNT_MARGIN_FREE); //--- 调用检验函数 if(!OrderCalcMargin(type,symb,lots,price,margin)) { //--- 出错了,发送报告并返回 false Print("有错误出现在 ",__FUNCTION__," 编号=",GetLastError()); return(false); } //--- 如果资金不够进行操作 if(margin>free_margin) { //--- 报告错误并返回 false Print("资金不足以进行 ",EnumToString(type)," ",lots," ",symb," 错误编号=",GetLastError()); return(false); } //--- 检验成功 return(true); }

MQL4

 
 

bool CheckMoneyForTrade(string symb, double lots,int type) { double free_margin=AccountFreeMarginCheck(symb,type, lots); //-- 如果资金不够 if(free_margin<0) { string oper=(type==OP_BUY)?"买入":"卖出"; Print("资金不足以进行", oper," ",lots, " ", symb, " 错误编号",GetLastError()); return(false); } //--- 检验成功 return(true); }

交易操作中的无效交易量

在发送交易订单之前,也有必要检查在订单中指定的交易量的正确性,EA交易中订单设置的手数必须在调用 OrderSend() 函数之前进行检查,交易品种所允许的最小和最大交易量,以及交易量之间的步长是在规格说明中指定的。 这些数值可以通过ENUM_SYMBOL_INFO_DOUBLE 枚举,在SymbolInfoDouble()函数的帮助下获得。

SYMBOL_VOLUME_MIN

交易的最小交易量

SYMBOL_VOLUME_MAX

交易的最大交易量

SYMBOL_VOLUME_STEP

执行交易时最小的交易量变化步长

检查交易量正确性函数的实例

 
 

//+------------------------------------------------------------------+ //| 检查订单交易量的正确性 | //+------------------------------------------------------------------+ bool CheckVolumeValue(double volume,string &description) { //--- 交易操作允许的最小交易量 double min_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MIN); if(volume<min_volume) { description=StringFormat("交易量小于允许的最小交易量,SYMBOL_VOLUME_MIN=%.2f",min_volume); return(false); } //--- 交易操作允许的最大交易量 double max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX); if(volume>max_volume) { description=StringFormat("交易量大于允许的最大交易量,SYMBOL_VOLUME_MAX=%.2f",max_volume); return(false); } //--- 取得交易量变化的最小步长 double volume_step=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_STEP); int ratio=(int)MathRound(volume/volume_step); if(MathAbs(ratio*volume_step-volume)>0.0000001) { description=StringFormat("交易量不是最小交易步长的整数倍,SYMBOL_VOLUME_STEP=%.2f, 最接近的正确交易量是 %.2f", volume_step,ratio*volume_step); return(false); } description="正确的交易量数值"; return(true); }

挂单的限制数量

账户中允许同时设置的活动挂单的数量可能也会有所限制。IsNewOrderAllowed() 函数的实例, 它用来检查是否还允许继续设置挂单。

 
 

//+------------------------------------------------------------------+ //| 检查是否还允许设置订单 | //+------------------------------------------------------------------+ bool IsNewOrderAllowed() { //--- 取得账户中允许设置的挂单数量 int max_allowed_orders=(int)AccountInfoInteger(ACCOUNT_LIMIT_ORDERS); //--- 如果没有限制,返回 true; 您可以发送一个订单 if(max_allowed_orders==0) return(true); //--- 如果我们达到这一行,说明有限制; 找出已经设置了多少挂单 int orders=OrdersTotal(); //--- 返回比较结果 return(orders<max_allowed_orders); }

这个函数很简单: 取得允许的最大订单数并赋予max_allowed_orders变量; 如果它不等于0,把它与当前订单数量做比较。但是,这个函数没有考虑到另外的可能的限制 - 对某一特定交易品种开启仓位总交易量的限制和挂单数量的限制。

某特定交易品种的手数限制

为了取得某一特定交易品种的开启仓位的总交易量,首先您需要使用PositionSelect()函数来选择一个仓位,之后您就可以使用PositionGetDouble()来得到已建仓位的交易量, 它可以返回双精度类型的所选仓位的各种属性。让我们写一个 PostionVolume() 函数来取得所需交易品种的仓位交易量。

 
 

//+------------------------------------------------------------------+ //| 返回指定交易品种的仓位大小 | //+------------------------------------------------------------------+ double PositionVolume(string symbol) { //--- 尝试根据交易品种选择仓位 bool selected=PositionSelect(symbol); //--- 有仓位 if(selected) //--- 返回仓位交易量 return(PositionGetDouble(POSITION_VOLUME)); else { //--- 选择仓位出错报告 Print(__FUNCTION__," 执行 PositionSelect() 失败,交易品种为 ", symbol," 错误编号 ",GetLastError()); return(-1); } }

对于支持对冲的账户,还需要迭代当前交易品种所有的仓位。

在根据交易品种生成交易请求以设置挂之前, 您应该检查在一个交易品种上的已开启仓位和挂单总交易量的限制 - SYMBOL_VOLUME_LIMIT,如果没有限制,那么挂单的总交易量不能超过使用SymbolInfoDouble()得到的最大交易量。

 
 

double max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_LIMIT); if(max_volume==0) volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX);

然呢人,这种方法没有考虑到指定交易品种的当前挂单交易量。这里有一个计算此值的例子函数:

 
 

//+------------------------------------------------------------------+ //| 返回交易品种当前挂单的交易量 | //+------------------------------------------------------------------+ double PendingsVolume(string symbol) { double volume_on_symbol=0; ulong ticket; //--- 获得全部交易品种当前下单数量 int all_orders=OrdersTotal(); //--- 恢复范围内的全部订单 for(int i=0;i<all_orders;i++) { //--- 获得列表中持仓的订单号 if(ticket=OrderGetTicket(i)) { //--- 如果订单中指定我们的交易品种,增加该订单的交易量 if(symbol==OrderGetString(ORDER_SYMBOL)) volume_on_symbol+=OrderGetDouble(ORDER_VOLUME_INITIAL); } } //--- 返回指定交易品种当前已下挂单的总交易量 return(volume_on_symbol); }

在考虑到已建仓位和挂单交易量之后,最终的检验将看起来是这样的:

 
 

//+------------------------------------------------------------------+ //| 返回交易品种中一个订单允许的最大交易量 | //+------------------------------------------------------------------+ double NewOrderAllowedVolume(string symbol) { double allowed_volume=0; //--- 取得订单最大交易量限制 double symbol_max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_MAX); //--- 取得一个交易品种的交易量限制 double max_volume=SymbolInfoDouble(Symbol(),SYMBOL_VOLUME_LIMIT); //--- 取得一个交易品种已建仓位的交易量 double opened_volume=PositionVolume(symbol); if(opened_volume>=0) { //--- 如果我们已经用完了交易量 if(max_volume-opened_volume<=0) return(0); //--- 如果已建仓位的交易量没有超过 max_volume double orders_volume_on_symbol=PendingsVolume(symbol); allowed_volume=max_volume-opened_volume-orders_volume_on_symbol; if(allowed_volume>symbol_max_volume) allowed_volume=symbol_max_volume; } return(allowed_volume); }

在 SYMBOL_TRADE_STOPS_LEVEL 最小水平之内设置获利(TakeProfit)和止损(StopLoss)水平

许多EA交易在进行买入或者卖出时动态计算获利和止损水平,订单的止损是在价格走向期望的方向时用于关闭仓位的,而止损是当价格走向不希望的方向时用于限制损失的。

因而,获利和止损水平应该与当前的价格相比较而进行反向的操作:

  • 买入是根据卖方报价(Ask)的 — 而止损和获利水平应该和买方报价(Bid)做比较。

  • 卖出是根据买方报价(Bid)的 - 获利和止损水平应该和卖方报价(Ask)做比较。

买入是根据卖方报价的

卖出是根据买方报价的

TakeProfit >= BidStopLoss <= Bid

TakeProfit <= AskStopLoss >= Ask

编辑切换为居中

添加图片注释,不超过 140 字(可选)

金融资产在交易品种设置中可能有SYMBOL_TRADE_STOPS_LEVEL参数,它决定了止损和获利水平距离当前已建仓位平仓价格的最小距离点数,如果这个属性的值是0,就没有设定止损/获利单距离买入和卖出价格的最小距离。

一般来说,检查获利和止损水平,看是否符合账户设置的最小距离SYMBOL_TRADE_STOPS_LEVEL的方法如下所示:

  • 买入是根据卖方报价的 — 获利和止损水平必须距离买方报价至少 SYMBOL_TRADE_STOPS_LEVEL 个点。

  • 卖出是根据买方报价的 — 获利和止损水平必须距离卖方报价至少 SYMBOL_TRADE_STOPS_LEVEL 个点。

买入是根据卖方报价的

卖出是根据买方报价的

TakeProfit - Bid >= SYMBOL_TRADE_STOPS_LEVELBid - StopLoss >= SYMBOL_TRADE_STOPS_LEVEL

Ask - TakeProfit >= SYMBOL_TRADE_STOPS_LEVELStopLoss - Ask >= SYMBOL_TRADE_STOPS_LEVEL

这样,我们就能创建一个CheckStopLoss_Takeprofit()检验函数,它要求获利和止损水平距离收盘价至少 SYMBOL_TRADE_STOPS_LEVEL 个点:

 
 

bool CheckStopLoss_Takeprofit(ENUM_ORDER_TYPE type,double SL,double TP) { //--- 取得 SYMBOL_TRADE_STOPS_LEVEL 水平 int stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL); if(stops_level!=0) { PrintFormat("SYMBOL_TRADE_STOPS_LEVEL=%d: 止损和获利必须"+ " 距离当前价格大于 %d 个点",stops_level,stops_level); } //--- bool SL_check=false,TP_check=false; //--- 只检查两种订单类型 switch(type) { //--- 买入操作 case ORDER_TYPE_BUY: { //--- 检验止损 SL_check=(Bid-SL>stops_level*_Point); if(!SL_check) PrintFormat("对于订单 %s 止损=%.5f 必须小于 %.5f"+ " (Bid=%.5f - SYMBOL_TRADE_STOPS_LEVEL=%d points)", EnumToString(type),SL,Bid-stops_level*_Point,Bid,stops_level); //--- 检验获利 TP_check=(TP-Bid>stops_level*_Point); if(!TP_check) PrintFormat("对于 %s 获利=%.5f 必须大于 %.5f"+ " (Bid=%.5f + SYMBOL_TRADE_STOPS_LEVEL=%d points)", EnumToString(type),TP,Bid+stops_level*_Point,Bid,stops_level); //--- 返回检验结果 return(SL_check&&TP_check); } //--- 卖出操作 case ORDER_TYPE_SELL: { //--- 检验止损 SL_check=(SL-Ask>stops_level*_Point); if(!SL_check) PrintFormat("对于订单 %s 止损=%.5f 必须大于 %.5f "+ " (Ask=%.5f + SYMBOL_TRADE_STOPS_LEVEL=%d points)", EnumToString(type),SL,Ask+stops_level*_Point,Ask,stops_level); //--- 检验获利 TP_check=(Ask-TP>stops_level*_Point); if(!TP_check) PrintFormat("对于订单 %s 获利=%.5f 必须小于 %.5f "+ " (Ask=%.5f - SYMBOL_TRADE_STOPS_LEVEL=%d points)", EnumToString(type),TP,Ask-stops_level*_Point,Ask,stops_level); //--- 返回检验结果 return(TP_check&&SL_check); } break; } //--- 对于挂单有少许不同 return false; }

检验本身如下所示:

 
 

//+----------------------------------------+ //| 脚本程序起始函数 | //+----------------------------------------+ void OnStart() { //--- 随机取得操作类型 int oper=(int)(GetTickCount()%2); // 被2除永远余0或者1 switch(oper) { //--- buy case 0: { //--- 取得建仓价格并且故意设置无效的获利/止损/ double price=Ask; double SL=NormalizeDouble(Bid+2*_Point,_Digits); double TP=NormalizeDouble(Bid-2*_Point,_Digits); //--- 进行检查 PrintFormat("Buy at %.5f SL=%.5f TP=%.5f Bid=%.5f",price,SL,TP,Bid); if(!CheckStopLoss_Takeprofit(ORDER_TYPE_BUY,SL,TP)) Print("止损或者获利水平是不对的!"); //--- 还是尝试买入,是为了看执行结果 Buy(price,SL,TP); } break; //--- sell case 1: { //--- 取得建仓价格并且故意设置无效的获利/止损/ double price=Bid; double SL=NormalizeDouble(Ask-2*_Point,_Digits); double TP=NormalizeDouble(Ask+2*_Point,_Digits); //--- 进行检查 PrintFormat("Sell at %.5f SL=%.5f TP=%.5f Ask=%.5f",price,SL,TP,Ask); if(!CheckStopLoss_Takeprofit(ORDER_TYPE_SELL,SL,TP)) Print("止损或者获利水平是不对的!"); //--- try to sell anyway, in order to see the execution result Sell(price,SL,TP); } break; //--- } }

例子函数可以在附件中的脚本程序中找到。执行的实例:

 
 

MQL5 Check_TP_and_SL (EURUSD,H1) Buy at 1.11433 SL=1.11425 TP=1.11421 Bid=1.11423 Check_TP_and_SL (EURUSD,H1) SYMBOL_TRADE_STOPS_LEVEL=30: 止损和获利距离平仓价格不能少于30个点 Check_TP_and_SL (EURUSD,H1) For order ORDER_TYPE_BUY 止损=1.11425 必须小于 1.11393 (Bid=1.11423 - SYMBOL_TRADE_STOPS_LEVEL=30 个点) Check_TP_and_SL (EURUSD,H1) For order ORDER_TYPE_BUY 获利=1.11421 必须大于 1.11453 (Bid=1.11423 + SYMBOL_TRADE_STOPS_LEVEL=30 个点) Check_TP_and_SL (EURUSD,H1) 止损或者获利水平是不对的! Check_TP_and_SL (EURUSD,H1) OrderSend 错误 4756 Check_TP_and_SL (EURUSD,H1) retcode=10016 deal=0 order=0 MQL4 Check_TP_and_SL EURUSD,H1: 卖出于1.11430 SL=1.11445 TP=1.11449 卖方报价=1.11447 Check_TP_and_SL EURUSD,H1: SYMBOL_TRADE_STOPS_LEVEL=1: 止损和获利水平与收盘价的距离不能少于1个点 Check_TP_and_SL EURUSD,H1: 对于订单 ORDER_TYPE_SELL 止损=1.11445 必须大于 1.11448 (买方报价=1.11447 + SYMBOL_TRADE_STOPS_LEVEL=1 个点) Check_TP_and_SL EURUSD,H1: 对于订单 ORDER_TYPE_SELL 获利=1.11449 必须小于 1.11446 (卖方报价=1.11447 - SYMBOL_TRADE_STOPS_LEVEL=1 个点) Check_TP_and_SL EURUSD,H1: 止损或者获利水平是不对的! Check_TP_and_SL EURUSD,H1: OrderSend 错误 130

为了模拟无效获利和止损值的情形EA交易可以在本文的附件中找到。它们只能在模拟账户上运行。学习这些实例就可以看到可以成功买入时的条件。

例子EA交易的执行结果:

 
 

Test_Wrong_StopLoss_LEVEL.mq5 Point=0.00001 Digits=5 SYMBOL_TRADE_EXECUTION=SYMBOL_TRADE_EXECUTION_INSTANT SYMBOL_TRADE_FREEZE_LEVEL=20: 如果距离激活的价格只有20个点,不允许修改订单或者仓位 SYMBOL_TRADE_STOPS_LEVEL=30: 止损和获利距离当前价格不能少于30个点 1. 买入1.0 EURUSD 价格1.11442 SL=1.11404 Bid=1.11430 ( StopLoss-Bid=-26 个点 )) CTrade::OrderSend: 立即买入 1.00 EURUSD 价格 1.11442 sl: 1.11404 [无效止损] 2. 买入1.0 EURUSD 价格1.11442 SL=1.11404 Bid=1.11431 ( StopLoss-Bid=-27 个点 )) CTrade::OrderSend: 立即买入 1.00 EURUSD 价格 1.11442 sl: 1.11404 [无效止损] 3. 买入 1.0 EURUSD 价格 1.11442 SL=1.11402 Bid=1.11430 ( StopLoss-Bid=-28 个点 )) CTrade::OrderSend: 立即买入 1.00 EURUSD 价格 1.11442 sl: 1.11402 [无效止损] 4. 买入 1.0 EURUSD 价格 1.11440 SL=1.11399 Bid=1.11428 ( StopLoss-Bid=-29 个点 )) CTrade::OrderSend: 立即买入 1.00 EURUSD 价格 1.11440 sl: 1.11399 [无效止损] 5. Buy 1.0 EURUSD at 1.11439 SL=1.11398 Bid=1.11428 ( StopLoss-Bid=-30 points )) Buy 1.0 EURUSD done at 1.11439 with StopLoss=41 points (spread=12 + SYMBOL_TRADE_STOPS_LEVEL=30)

Example of the Test_Wrong_TakeProfit_LEVEL.mq5 EA execution:

 
 

Test_Wrong_TakeProfit_LEVEL.mq5 Point=0.00001 Digits=5 SYMBOL_TRADE_EXECUTION=SYMBOL_TRADE_EXECUTION_INSTANT SYMBOL_TRADE_FREEZE_LEVEL=20: 如果距离激活的价格只有20个点,不允许修改订单或者仓位 SYMBOL_TRADE_STOPS_LEVEL=30: 止损和获利距离当前价格不能少于30个点 1. 买入1.0 EURUSD 价格 1.11461 TP=1.11478 Bid=1.11452 (TakeProfit-Bid=26 个点) CTrade::OrderSend: 立即买入 1.00 EURUSD 价格 1.11461 tp: 1.11478 [无效止损] 2. 买入 1.0 EURUSD 价格 1.11461 TP=1.11479 Bid=1.11452 (TakeProfit-Bid=27个点) CTrade::OrderSend: 立即买入 1.00 EURUSD 价格 1.11461 tp: 1.11479 [无效止损] 3. 买入1.0 EURUSD 价格 1.11461 TP=1.11480 Bid=1.11452 (TakeProfit-Bid=28个点) CTrade::OrderSend: 立即买入 1.00 EURUSD 价格 1.11461 tp: 1.11480 [无效止损] 4. 买入1.0 EURUSD 价格1.11461 TP=1.11481 Bid=1.11452 (TakeProfit-Bid=29 个点) CTrade::OrderSend: 立即买入 1.00 EURUSD 价格 1.11461 tp: 1.11481 [无效止损] 5. 买入1.0 EURUSD 价格1.11462 TP=1.11482 Bid=1.11452 (TakeProfit-Bid=30个点) 买入1.0 EURUSD 完成,价格1.11462,TakeProfit=20 个点 (SYMBOL_TRADE_STOPS_LEVEL=30 - 点差=10)

在挂单中检查止损和获利水平简单多了,因为这些水平必须基于订单的建仓价格,也就是说,考虑SYMBOL_TRADE_STOPS_LEVEL最小距离来检查水平的方法如下: 获利和止损水平必须距离订单的激活价格大于 SYMBOL_TRADE_STOPS_LEVEL 个点。

限价买入和止损买入

限价卖出和止损卖出

TakeProfit - Open >= SYMBOL_TRADE_STOPS_LEVELOpen - StopLoss >= SYMBOL_TRADE_STOPS_LEVEL

Open - TakeProfit >= SYMBOL_TRADE_STOPS_LEVELStopLoss - Open >= SYMBOL_TRADE_STOPS_LEVEL

EA 交易进行了一系列的止损买入和限价买入尝试,直到操作成功。在每次成功的尝试中,止损或者获利水平都在正确方向上偏移一个点。此EA交易执行的实例:

 
 

Test_StopLoss_Level_in_PendingOrders.mq5 SYMBOL_TRADE_EXECUTION=SYMBOL_TRADE_EXECUTION_INSTANT SYMBOL_TRADE_FREEZE_LEVEL=20: 如果距离激活的价格只有20个点,不允许修改订单或者仓位 SYMBOL_TRADE_STOPS_LEVEL=30: 止损和获利距离最新价格不能少于30个点 1. 止损买入 1.0 EURUSD 价格 1.11019 SL=1.10993 (Open-StopLoss=26 个点) CTrade::OrderSend: 止损买入 1.00 EURUSD 价格 1.11019 sl: 1.10993 [无效止损] 2. 止损买入1.0 EURUSD 价格 1.11019 SL=1.10992 (Open-StopLoss=27 个点) CTrade::OrderSend: 止损买入 1.00 EURUSD 价格 1.11019 sl: 1.10992 [无效止损] 3. 止损买入 1.0 EURUSD 价格 1.11020 SL=1.10992 (Open-StopLoss=28 个点) CTrade::OrderSend: 止损买入 1.00 EURUSD 价格 1.11020 sl: 1.10992 [无效止损] 4. 止损买入 1.0 EURUSD 价格 1.11021 SL=1.10992 (Open-StopLoss=29 个点) CTrade::OrderSend: 止损买入 1.00 EURUSD 价格 1.11021 sl: 1.10992 [无效止损] 5. 止损买入 1.0 EURUSD 价格 1.11021 SL=1.10991 (Open-StopLoss=30 个点) 止损买入 1.0 EURUSD 完成 价格 1.11021 止损=1.10991 (SYMBOL_TRADE_STOPS_LEVEL=30) --------- 1. 限价买入 1.0 EURUSD 价格 1.10621 TP=1.10647 (TakeProfit-Open=26 个点) CTrade::OrderSend: 限价买入1.00 EURUSD 价格 1.10621 tp: 1.10647 [无效止损] 2. 限价买入1.0 EURUSD 价格 1.10621 TP=1.10648 (TakeProfit-Open=27 个点) CTrade::OrderSend: 限价买入1.00 EURUSD 价格1.10621 tp: 1.10648 [无效止损] 3. 限价买入1.0 EURUSD 价格1.10621 TP=1.10649 (TakeProfit-Open=28 个点) CTrade::OrderSend: 限价买入1.00 EURUSD 价格1.10621 tp: 1.10649 [无效止损] 4. 限价买入1.0 EURUSD 价格1.10619 TP=1.10648 (TakeProfit-Open=29个点) CTrade::OrderSend: 限价买入1.00 EURUSD 价格 1.10619 tp: 1.10648 [无效止损] 5. 限价买入 1.0 EURUSD 价格1.10619 TP=1.10649 (TakeProfit-Open=30个点) 限价买入 1.0 EURUSD 完成 价格1.10619 TakeProfit=1.10649 (SYMBOL_TRADE_STOPS_LEVEL=30)

在挂单中检验获利和止损水平的实例可以在附件的源文件中找到:赫兹股票期货量化软件

尝试在 SYMBOL_TRADE_FREEZE_LEVEL 水平范围之内修改订单或者仓位

SYMBOL_TRADE_FREEZE_LEVEL参数可以在交易品种的规格中设定,它显示了冻结挂单操作水平与建仓价格之间的距离。例如,如果某金融资产的交易被重定向到一个外部交易系统,而限价买入挂单可能现在距离当前的卖家报价太近了,另外,如果此时有请求来修改订单,并且距离卖家报价很接近,就可能出现订单被执行而无法修改。

所以,交易品种的规格对挂单和已建仓位指定了冻结距离,在距离之内它们就不能修改。一般来说,在尝试发送修改请求之前,需要考虑到SYMBOL_TRADE_FREEZE_LEVEL而进行检查:

订单/仓位的类型

激活价格

检查

限价买入订单

卖家报价(Ask)

Ask-OpenPrice >= SYMBOL_TRADE_FREEZE_LEVEL

止损买入订单

卖家报价(Ask)

OpenPrice-Ask >= SYMBOL_TRADE_FREEZE_LEVEL

限价卖出订单

买家报价(Bid)

OpenPrice-Bid >= SYMBOL_TRADE_FREEZE_LEVEL

止损卖出订单

买家报价(Bid)

Bid-OpenPrice >= SYMBOL_TRADE_FREEZE_LEVEL

买入仓位

买家报价(Bid)

TakeProfit-Bid >= SYMBOL_TRADE_FREEZE_LEVELBid-StopLoss >= SYMBOL_TRADE_FREEZE_LEVEL

卖出仓位

卖家报价(Ask)

Ask-TakeProfit >= SYMBOL_TRADE_FREEZE_LEVELStopLoss-Ask >= SYMBOL_TRADE_FREEZE_LEVEL

用于检查订单和仓位 SYMBOL_TRADE_FREEZE_LEVEL 水平的完整函数实例可以在附件中的 Check_FreezeLevel.mq5 和 Check_FreezeLevel.mq4 脚本程序中找到。

 
 

//--- 检查订单类型 switch(type) { //--- 限价买入挂单 case ORDER_TYPE_BUY_LIMIT: { //--- 检查当前价格与激活价格的距离 check=((Ask-price)>freeze_level*_Point); if(!check) PrintFormat("订单 %s #%d 不能被修改: Ask-Open=%d 个点 < SYMBOL_TRADE_FREEZE_LEVEL=%d 个点", EnumToString(type),ticket,(int)((Ask-price)/_Point),freeze_level); return(check); } //--- 限价买入挂单 case ORDER_TYPE_SELL_LIMIT: { //--- 检查当前价格与激活价格的距离 check=((price-Bid)>freeze_level*_Point); if(!check) PrintFormat("订单 %s #%d 不能被修改: Open-Bid=%d 个点 < SYMBOL_TRADE_FREEZE_LEVEL=%d 个点", EnumToString(type),ticket,(int)((price-Bid)/_Point),freeze_level); return(check); } break; //--- BuyStop 挂单 case ORDER_TYPE_BUY_STOP: { //--- 检查当前价格与激活价格的距离 check=((price-Ask)>freeze_level*_Point); if(!check) PrintFormat("订单 %s #%d 不能被修改: Ask-Open=%d 个点 < SYMBOL_TRADE_FREEZE_LEVEL=%d 个点", EnumToString(type),ticket,(int)((price-Ask)/_Point),freeze_level); return(check); } //--- 止损卖出挂单 case ORDER_TYPE_SELL_STOP: { //--- 检查当前价格与激活价格的距离 check=((Bid-price)>freeze_level*_Point); if(!check) PrintFormat("订单 %s #%d 不能被修改: Bid-Open=%d points < SYMBOL_TRADE_FREEZE_LEVEL=%d 个点", EnumToString(type),ticket,(int)((Bid-price)/_Point),freeze_level); return(check); } break; }

您可以模拟一种情形,尝试在冻结水平之内修改挂单。为此,开一个模拟账户,其中金融资产的 SYMBOL_TRADE_FREEZE_LEVEL 水平不为0, 然后把Test_SYMBOL_TRADE_FREEZE_LEVEL.mq5 (Test_SYMBOL_TRADE_FREEZE_LEVEL.mq4) EA 附加到图表上并人工设置任意的挂单。该 EA 交易将自动把挂单移动到距离当前尽可能近的位置而将开始进行非法的修改尝试。它将会使用PlaySound()函数发出声音提醒。

当操作缺少历史报价的交易品种时发生错误

如果一个EA交易或者指标在图表上运行,而历史数据不够,则会出现两种可能:

  1. 程序检查所需的历史是否足够长,如果柱数少于所需,程序会请求缺少的数据,并在下一个订单时刻来临之前结束操作。这种方法是最正确的,它可以帮助避免很多错误,例如超出数组返回或者除零错误;

  2. 程序不做任何检查,立即开始工作,就如同所有所需的交易品种和时段信息在请求时已经可用了,这种方法是有问题的,可能会带来许多无法预料的错误。

您可以尝试自己模拟这种情形。为此,在图表上运行测试的指标或者EA交易,然后关闭终端并删除历史,再重新打开终端。如果这样重启之后记录中没有错误,就尝试在运行程序的图表上改变交易品种和时段,许多指标会在每周或者每月的时段中出错,因为这些时段常常柱数有限。另外,立即改变图表的交易品种(例如,从 EURUSD 改为 CADJPY), 图表上的指标或者EA交易可能会因为缺少所需计算的历史而出错。赫兹股票期货量化软件

超出数组范围

当操作数组时,对它们元素的访问是通过索引编号进行的,它不能是负的,并且必须小于数组的大小。数组的大小可以通过使用ArraySize() 函数得到。赫兹股票期货量化软件

这个错误可能会在操作动态数组时遇到,它的大小可以明确使用ArrayResize()函数来设置, 或者当作为传入参数而在函数中间接设置了它的大小时也可能遇到。例如, CopyTicks() 函数尝试把所需数量的订单保存到数组中,但是如果复制的订单少于请求的数量,那么结果数组的大小将比索期待的要小。

另一个遇到这个错误的方法是尝试读取还没有初始化过的指标缓冲区。提醒一下,指标缓冲区也是动态数组,并且它们的大小是在图表初始化后由终端的执行系统定义的。所以,在 OnInit() 函数中尝试访问这种缓冲区的数据时会引起"数组超出范围"错误的。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

除以零

另一个严重错误是尝试除以零。在这种情况下,程序的执行会立即终止,策略测试器会在日志中显示出错函数的名称和代码中的行号。

编辑切换为居中

添加图片注释,不超过 140 字(可选)

从原则上看,除以零的错误都是因为遇到了程序开发人员没有预料到的情况,例如,读取属性或者计算表达式时使用了"损坏的"数据。

除以零可以使用 TestZeroDivide. EA交易简单重现, 它的源代码显示在屏幕截图中。另一个严重错误是使用不正确的对象指针在历史数据上调试对于找到引发错误的原因是很有用的。

发送请求来修改水平而并没有真正改变它们

如果交易系统的规则需要修改挂单或者已经建立的仓位,那么在发送交易请求进行这样的操作之前,还需要确认请求的操作会真正改变订单或者仓位的参数。ually change parameters of the order or position. 发送了交易请求而不能做任何改变将会被视为错误,而交易服务器会回应TRADE_RETCODE_NO_CHANGES=10025 的返回值 (MQL5) 或者 ERR_NO_RESULT=1 的代号 (MQL4)。

 
 

//--- 用于进行交易操作的类 #include <Trade\Trade.mqh> CTrade trade; #include <Trade\Trade.mqh> //--- 用于操作订单的类 #include <Trade\OrderInfo.mqh> COrderInfo orderinfo; //--- 用于操作仓位的类 #include <Trade\PositionInfo.mqh> CPositionInfo positioninfo; //+------------------------------------------------------------------+ //| 在修改订单之前检查水平新的数值 | //+------------------------------------------------------------------+ bool OrderModifyCheck(ulong ticket,double price,double sl,double tp) { //--- 根据编号选择订单 if(orderinfo.Select(ticket)) { //--- 用于设置挂单的交易品种的点位大小和名称 string symbol=orderinfo.Symbol(); double point=SymbolInfoDouble(symbol,SYMBOL_POINT); int digits=(int)SymbolInfoInteger(symbol,SYMBOL_DIGITS); //--- 检查建仓价格有没有改变 bool PriceOpenChanged=(MathAbs(orderinfo.PriceOpen()-price)>point); //--- 检查止损水平有没有改变 bool StopLossChanged=(MathAbs(orderinfo.StopLoss()-sl)>point); //--- 检查获利水平有没有改变 bool TakeProfitChanged=(MathAbs(orderinfo.TakeProfit()-tp)>point); //--- 如果水平有任何变化 if(PriceOpenChanged || StopLossChanged || TakeProfitChanged) return(true); // 订单可以修改 //--- 开盘价,止损和获利水平没有变化 else //--- 通知错误 PrintFormat("订单 #%d 水平已经是 Open=%.5f SL=%.5f TP=%.5f", ticket,orderinfo.PriceOpen(),orderinfo.StopLoss(),orderinfo.TakeProfit()); } //--- 结束,订单没有改变 return(false); // 不需要做修改 } //+------------------------------------------------------------------+ //| 在修改订单之前检查水平新的数值 | //+------------------------------------------------------------------+ bool PositionModifyCheck(ulong ticket,double sl,double tp) { //--- 根据编号选择订单 if(positioninfo.SelectByTicket(ticket)) { //--- 用于设置挂单的交易品种的点位大小和名称 string symbol=positioninfo.Symbol(); double point=SymbolInfoDouble(symbol,SYMBOL_POINT); //--- 检查止损水平有没有改变 bool StopLossChanged=(MathAbs(positioninfo.StopLoss()-sl)>point); //--- 检查获利水平有没有改变 bool TakeProfitChanged=(MathAbs(OrderTakeProfit()-tp)>point); //--- 如果水平有任何变化 if(StopLossChanged || TakeProfitChanged) return(true); // 仓位可以修改 //--- 止损和获利水平没有变化 else //--- 通知错误 PrintFormat("订单 #%d 水平已经是 Open=%.5f SL=%.5f TP=%.5f", ticket,orderinfo.PriceOpen(),orderinfo.StopLoss(),orderinfo.TakeProfit()); } //--- 结束,订单没有改变 return(false); // 不需要做修改 } //+------------------------------------------------------------------+ //| 脚本程序起始函数 | //+------------------------------------------------------------------+ void OnStart() { //--- 订单和仓位的价格水平 double priceopen,stoploss,takeprofit; //--- 当前订单和仓位的编号 ulong orderticket,positionticket; /* ... 取得订单编号和新的止损/获利/建仓价格水平 */ //--- 在修改挂单之前检查水平 if(OrderModifyCheck(orderticket,priceopen,stoploss,takeprofit)) { //--- 检验成功 trade.OrderModify(orderticket,priceopen,stoploss,takeprofit, orderinfo.TypeTime(),orderinfo.TimeExpiration()); } /* ... 取得仓位的编号和新的止损/获利水平 */ //--- 在修改仓位之前做检查 if(PositionModifyCheck(positionticket,stoploss,takeprofit)) { //--- 检验成功 trade.PositionModify(positionticket,stoploss,takeprofit); } //--- }

 
 

#property strict //+------------------------------------------------------------------+ //| 在修改订单之前检查水平新的数值 | //+------------------------------------------------------------------+ bool OrderModifyCheck(int ticket,double price,double sl,double tp) { //--- 根据编号选择订单 if(OrderSelect(ticket,SELECT_BY_TICKET)) { //--- 用于设置挂单的交易品种的点位大小和名称 string symbol=OrderSymbol(); double point=SymbolInfoDouble(symbol,SYMBOL_POINT); //--- 检查建仓价格有没有改变 bool PriceOpenChanged=true; int type=OrderType(); if(!(type==OP_BUY || type==OP_SELL)) { PriceOpenChanged=(MathAbs(OrderOpenPrice()-price)>point); } //--- 检查止损水平有没有改变 bool StopLossChanged=(MathAbs(OrderStopLoss()-sl)>point); //--- 检查获利水平有没有改变 bool TakeProfitChanged=(MathAbs(OrderTakeProfit()-tp)>point); //--- 如果水平有任何变化 if(PriceOpenChanged || StopLossChanged || TakeProfitChanged) return(true); // 订单可以修改 //--- 开盘价,止损和获利水平没有变化 else //--- 通知错误 PrintFormat("订单 #%d 水平已经是 Open=%.5f SL=%.5f TP=%.5f", ticket,OrderOpenPrice(),OrderStopLoss(),OrderTakeProfit()); } //--- 结束,订单没有改变 return(false); // 不需要做修改 } //+----------------------------------------+ //| 脚本程序起始函数 | //+----------------------------------------+ void OnStart() { //--- 订单和仓位的价格水平 double priceopen,stoploss,takeprofit; //--- 当前订单的编号 int orderticket; /* ... 取得订单编号和新的止损/获利/建仓价格水平 */ //--- 在修改订单之前做检查 if(OrderModifyCheck(orderticket,priceopen,stoploss,takeprofit)) { //--- 检验成功 OrderModify(orderticket,priceopen,stoploss,takeprofit,OrderExpiration()); } }

使用 iCustom() 调用自定义指标

如果您程序的运行需要调用来自自定义指标的数据,所有所需的指标应该放到资源(Resources)中。市场中的产品需要在任何没有准备的环境下工作,所以它们应该在它们的 EX4/EX5 文件中包含所有它们所需的一切。推荐阅读的相关文章:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值