Using trade.BuyStop and unexpected Invalid Price errors inconsistently

 

Hey there. Getting working orders sent thru but on occasion invalid price is showing up, it appears to retry and then will go thru. Ive seen the posts about ask/bid and assuming this is something simple that isnt registering for me mentally. That said ive made this simple EA to reproduce the issue in backtesting to potentially help you help me haha. I hope everyone is having a good week and thanks in advance for pointing me in the right direction. 


//+------------------------------------------------------------------+
//|                            EA_Shark_BuyStopLimit_3BarFractal.mq5 |
//|                                  Copyright 2022, MetaQuotes Ltd. |
//|                                             https://www.mql5.com |
//+------------------------------------------------------------------+

#property copyright "Copyright 2023, MetaQuotes Ltd."
#property link      "https://www.mql5.com"
#property version   "1.00"

#include <Trade\Trade.mqh>
CTrade trade; 

//--- Global Service Variables 
MqlRates PriceDataTable[]; // Array to get price data to be used with fractals. Includes Volume.
int numberOfPriceDataPoints; // The amount of bars to use with CopyBuffer/CopyRates to populate new data from handles to arrays. 

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
//---
   ArraySetAsSeries(PriceDataTable,true); // Setting up table/array for time series data
   
//---
   return(INIT_SUCCEEDED);
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
//---
   
}
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
//---
      
   numberOfPriceDataPoints = CopyRates(_Symbol,0,0,10,PriceDataTable); // Collects data from shift 0 to shift 9 
   
   double currentBid = SymbolInfoDouble(_Symbol,SYMBOL_BID); // Get latest Bid Price
   double currentAsk = SymbolInfoDouble(_Symbol,SYMBOL_ASK); // Get latest Ask Price
   double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   
   bool priceFractalBull3Bar = (PriceDataTable[2].low < PriceDataTable[3].low && PriceDataTable[2].low <= PriceDataTable[1].low); // 3 Bar
   
   double confirmationLevel, entryLevel, stopLevel, takeProfitLevel, riskWindow;   
   
   if(PositionSelect(_Symbol) == false && PositionsTotal() < 1 && OrdersTotal() < 1) // We have no open position
   {
      if(priceFractalBull3Bar)
      {
         // Set Levels and Distances for 2:1RR
         entryLevel = PriceDataTable[1].high + tickSize;
         stopLevel = PriceDataTable[2].low - tickSize;
         riskWindow = (entryLevel-stopLevel) * 2;
         takeProfitLevel = riskWindow + entryLevel;
         
         // Clean up decimal points
         entryLevel = NormalizeDouble(entryLevel, 2);
         stopLevel = NormalizeDouble(stopLevel, 2);
         riskWindow = NormalizeDouble(riskWindow, 2);
         takeProfitLevel = NormalizeDouble(takeProfitLevel, 2);
         
         // Fire Trade
         trade.BuyStop(1, entryLevel, _Symbol, stopLevel, takeProfitLevel, ORDER_TIME_GTC, ORDER_TYPE_BUY_STOP_LIMIT);
         
         // Print Levels for review
         Print("Price Bull Fractal - 3 Bar: ");      
         Print("currentAsk: " + currentAsk); // adding these since forum posts previously mentioned this as the "explanation"
         Print("currentBid: " + currentBid);
         Print("entryLevel: " + entryLevel);
         Print("volume: " + PriceDataTable[1].tick_volume); // adding this to visually verify bars 
      }      
   }  
}

// END //
Discover new MetaTrader 5 opportunities with MQL5 community and services
Discover new MetaTrader 5 opportunities with MQL5 community and services
  • 2023.04.13
  • www.mql5.com
MQL5: language of trade strategies built-in the MetaTrader 5 Trading Platform, allows writing your own trading robots, technical indicators, scripts and libraries of functions
 

Please EDIT your post and use the CODE button when you post code.

Code button in editor

Thank you.
 

You don't seem to be checking the broker's contract specifications for the Stops Level condition.

You have a variable called "stoplevel" but it does not have anything to do with the broker's Stops Level condition.

You are also not normalising the prices correctly. Don't use NormalizeDouble for quote price normalisation. Normalise it properly by aligning it with the tick size.

 

Also read the following forum post ...

Forum on trading, automated trading systems and testing trading strategies

Tick size vs Point(), can be a little tricky in Multicurrency EA

Fernando Carreiro, 2022.03.09 12:11

Tick Size and Point Size can be very different especially on stocks and other symbols besides forex.

Always use Tick Size to adjust and align your prices, not the point size. In essence, make sure that your price quotes, are properly aligned to the Tick size (see following examples).

...
double tickSize = SymbolInfoDouble( _Symbol, SYMBOL_TRADE_TICK_SIZE );
...
double normalised_price = round( price / tick_size ) * tick_size;
...
// Or use a function
double Round2Ticksize( double price )
{
   double tick_size = SymbolInfoDouble( _Symbol, SYMBOL_TRADE_TICK_SIZE );
   return( round( price / tick_size ) * tick_size );
};

And also the following article ...

Articles

The checks a trading robot must pass before publication in the Market

MetaQuotes, 2016.08.01 09:30

Before any product is published in the Market, it must undergo compulsory preliminary checks in order to ensure a uniform quality standard. This article considers the most frequent errors made by developers in their technical indicators and trading robots. An also shows how to self-test a product before sending it to the Market.

 
Fernando Carreiro #:

Please EDIT your post and use the CODE button when you post code.


Thank you.

ah shoot. forgot about that. thanks sir for this reminder plus all the other info. Will digest and get those items addressed. Very much appreciated Fernando 

 
Fernando Carreiro #:

You don't seem to be checking the broker's contract specifications for the Stops Level condition.

You have a variable called "stoplevel" but it does not have anything to do with the broker's Stops Level condition.

You are also not normalising the prices correctly. Don't use NormalizeDouble for quote price normalisation. Normalise it properly by aligning it with the tick size.

Hi Fernado,

I am working with Joshua on an EA. I feel as if I am missing something pivotal in making things work. I am not understanding what you mean by "checking the contract specifications for the Stops Level condition." I have looked at your links and maybe I am just missing it but if the stop loss level in the BuyStop call is different than the "stops level" than where do I set the stops level? I found one example where buystop has a stop price as well as a stop loss. I understand that the stop price is where price needs to move to before it places the limit buy order at the entry level we want but this contradicts the BuyStop in ctrade where there is only stop loss not a stopPrice for the limit order. 

trade.BuyStop(stopPrice, trade.Volume(), limitPrice, stopLoss, takeProfit);
As far as checking the brokers stops level condition are you referring to something like this? If so it seemingly returns 0 every time when I add that in and print it.
SymbolInfoInteger(Symbol(),SYMBOL_TRADE_STOPS_LEVEL)

EDIT: I think I am on to what you are trying to say. if I print out 
SYMBOL_TRADE_STOPS_LEVEL

without using it in SymbolnfoInteger it returns 31. So on a MES contract I would need to have the stop loss be 31 ticks below entry? 31 ticks on MES is quite large. Am I really unable to make the stop closer than this?

 
@cgleckman #: I am working with Joshua on an EA. I feel as if I am missing something pivotal in making things work. I am not understanding what you mean by "checking the contract specifications for the Stops Level condition." I have looked at your links and maybe I am just missing it but if the stop loss level in the BuyStop call is different than the "stops level" than where do I set the stops level? I found one example where buystop has a stop price as well as a stop loss. I understand that the stop price is where price needs to move to before it places the limit buy order at the entry level we want but this contradicts the BuyStop in ctrade where there is only stop loss not a stopPrice for the limit order. As far as checking the brokers stops level condition are you referring to something like this? If so it seemingly returns 0 every time when I add that in and print it. EDIT: I think I am on to what you are trying to say. if I print out  without using it in SymbolnfoInteger it returns 31. So on a MES contract I would need to have the stop loss be 31 ticks below entry? 31 ticks on MES is quite large. Am I really unable to make the stop closer than this?

Please read the following section from the article mentioned — Setting the TakeProfit and StopLoss levels within the SYMBOL_TRADE_STOPS_LEVEL minimum level

As for "SYMBOL_TRADE_STOPS_LEVEL", it is part of an enumeration that serves as an index into the symbol properties. It is not a "value". Instead of 31 it could have easily been 3001 or even 0. It is an enumeration, not an actual value. Don't use it as a value. Please refer to the documentation to learn how enumerations work.

 
Fernando Carreiro #:

Please read the following section from the article mentioned — Setting the TakeProfit and StopLoss levels within the SYMBOL_TRADE_STOPS_LEVEL minimum level

As for "SYMBOL_TRADE_STOPS_LEVEL", it is part of an enumeration that serves as an index into the symbol properties. It is not a "value". Instead of 31 it could have easily been 3001 or even 0. It is an enumeration, not an actual value. Don't use it as a value. Please refer to the documentation to learn how enumerations work.

Thank you again for your reply and assistance. I am getting closer to understanding I beieve. I guess I am a bit more rusty with things than I realized.


In the code you mentioned it shows this as a check and I am assuming this is what you are referring to. This code generates a 0 when run in my code which per the example should mean that things are running correctly if I am understanding this error check properly. If that is the case then why am I still getting the invalid price error? 

   int stops_level=(int)SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL);
   if(stops_level!=0)
     {
      PrintFormat("SYMBOL_TRADE_STOPS_LEVEL=%d: StopLoss and TakeProfit must"+
                  " not be nearer than %d points from the closing price",stops_level,stops_level);
     }
For further understanding, if the value of SYMBOL_TRADE_STOPS_LEVEL is showing as 31 that means its saying the stops level value for the symbol is stored in the 30th position in the enum and SymbolInfoInteger is using SYMBOL_TRADE_STOPS_LEVEL as an index to get whatever tinfo is stored at that location. Is this correct?
 
@cgleckman #n the code you mentioned it shows this as a check and I am assuming this is what you are referring to. This code generates a 0 when run in my code which per the example should mean that things are running correctly if I am understanding this error check properly. 

If you are getting, zero for "SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)", then there is no "Stops Level" in play.

cgleckman #:If that is the case then why am I still getting the invalid price error?

In the code above, you don't seem to be checking if the Stop Orders are actually away from current prices or not. Don't assume they are. Check, that the stop order is above current Ask price for buys, and below current Bid for sell orders.

Show as actual log entries for the errors, where all the data is properly reported.

Add "debugging" to your code. Print out details to the log, such as current bid & ask, entry, stop-loss and take profit for the order, the trade result error code, etc.

cgleckman #: For further understanding, if the value of SYMBOL_TRADE_STOPS_LEVEL is showing as 31 that means its saying the stops level value for the symbol is stored in the 30th position in the enum and SymbolInfoInteger is using SYMBOL_TRADE_STOPS_LEVEL as an index to get whatever tinfo is stored at that location. Is this correct?

Not quite 100%, but you are getting a much better understanding of enumerations. Suffice to say that you should always use the enumerations for the "indexing" and not their value. Don't ever hard-code the "indexing" values (i.e.use "SYMBOL_TRADE_STOPS_LEVEL" and not "31").

EDIT: Please show your updated code with all the updates you have made in accordance with the requirements outlined in the article.

 
Fernando Carreiro #:

If you are getting, zero for "SymbolInfoInteger(_Symbol,SYMBOL_TRADE_STOPS_LEVEL)", then there is no "Stops Level" in play.

In the code above, you don't seem to be checking if the Stop Orders are actually away from current prices or not. Don't assume they are. Check, that the stop order is above current Ask price for buys, and below current Bid for sell orders.

Show as actual log entries for the errors, where all the data is properly reported.

Add "debugging" to your code. Print out details to the log, such as current bid & ask, entry, stop-loss and take profit for the order, the trade result error code, etc.

Not quite 100%, but you are getting a much better understanding of enumerations. Suffice to say that you should always use the enumerations for the "indexing" and not their value. Don't ever hard-code the "indexing" values (i.e.use "SYMBOL_TRADE_STOPS_LEVEL" and not "31").

EDIT: Please show your updated code with all the updates you have made in accordance with the requirements outlined in the article.

Fernando Carreiro #:

Hi Fernando,

I didn't want to leave you hanging. I am traveling right now so my responses can be somewhat delayed. I will post my updated code hopefully in the next couple hours when I can sit down and do so.

Reason: