Strategy Tester Optimization Question

 

Hello guys,

I'm a bit stumped here. I have developed an EA to trade Bollinger Bands. Any of you who have seen my previous topics know I'm new to coding, but I feel like I'm making progress. The issue I'm having is that when running an optimization on this EA, I get a "Zero Divide" error. This error does not show up when backtesting with the EA, only with optimization. My code is posted below. Please excuse the ugliness/messiness of it, I have broken some steps down to try and identify the problem. Although the tester highlights the error at (58,43), I have traced it back to the ZZZ variable. I have run prints on the ZZZ variable and it works fine giving me a return in the format of 0.00XX. When I replace ZZZ with a static value in the same format the code works fine and the optimization process runs smoothly, but as the SL is based on the ATR this isn't very practical. So to put an end to a long-winded explanation, ZZZ seems to cause the problem but when I put in a value of say .0025 it works fine. 


I know some things such as dividing by constants etc. aren't best practice, but they are in there to help me achieve the inputs/outputs. The entire code is below. It is still very much a work in progress. Any help or insight you can provide is greatly appreciated. Thanks!


#define MAGICNUMBER 785223

input int      SLMultiplier=2;
input int      BBPeriod=50;
input int      ATRPeriod=50;
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
//---

//---
   return(INIT_SUCCEEDED);
  }
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
  {
//---

  }
//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   int IndexPosition;
   int TotalOrders;
   TotalOrders=OrdersTotal();
   double LowerBand        = iBands(Symbol(),0,BBPeriod,2,0,PRICE_CLOSE,2,1); //--- Value of lower Bollinger Band
   double UpperBand        = iBands(Symbol(),0,BBPeriod,2,0,PRICE_CLOSE,1,1); //--- Value of upper Bollinger Band
   double MidBand          = iBands(Symbol(),0,BBPeriod,2,0,PRICE_CLOSE,0,1); //--- Value of middle Bollinger Band
   double HourlyATR        = iATR(Symbol(),0,ATRPeriod,0); //--- Hourly ATR rounded to 4 decimal places"
   double PrevRSI          = iRSI(Symbol(),0,9,PRICE_CLOSE,2);
   double SignalRSI        = iRSI(Symbol(),0,9,PRICE_CLOSE,1);
   double RiskAmount       = AccountBalance()*.01;                      //--- 1% risk per trade  
   double TickValue        = MarketInfo(Symbol(),MODE_TICKVALUE) /.1;    //---Value of 1 tick in account currency @ standatd lot size
   double TickSize         = MarketInfo(Symbol(), MODE_TICKSIZE) /.1;
   double LotStep=MarketInfo(Symbol(),MODE_LOTSTEP);        //--- Minimim change in Lot Size
   double ZZZ              = NormalizeDouble(HourlyATR,4);   
   double A                = ZZZ / TickSize;
   double B                = A/.1;   
   double StopLoss         = B*SLMultiplier;          
   double Stopp            = NormalizeDouble(StopLoss, 0);
   double RiskCalc         = NormalizeDouble(StopLoss*.1,0);
   double Risk             = RiskAmount / RiskCalc;               //-- <<< PROBLEM LINE
   double MaxValuePerPip   = Risk / TickValue;                            //--- Max amount of risk per pip
   double LotCalc          = MathFloor(MaxValuePerPip / LotStep);         //--- Contract size * 1000 in order to round down 
   double LotSize=LotCalc*LotStep;                            //--- Contract Size / 1000 to return to normal value after rounding                  
   double PrevHigh         = iHigh(Symbol(),0,1);                       //--- Signal candle high
   double PrevLow          = iLow(Symbol(),0,1);                        //-- Signal candle low   
   double PrevClose        = iClose(Symbol(),0,1);                      //--- Signal candle close
   int BarsCount           = 0;
   Print("HourlyATR = ",ZZZ);

   for(IndexPosition=TotalOrders -1; IndexPosition>=0; IndexPosition--) //--- Cycle through open orders
     {
      if(!OrderSelect(IndexPosition,SELECT_BY_POS,MODE_TRADES)) continue;
      if(OrderMagicNumber()==MAGICNUMBER && OrderSymbol()==Symbol())
         if(OrderType()==OP_BUY)
           {
            CloseLong();
           }

      if(OrderMagicNumber()==MAGICNUMBER && OrderSymbol()==Symbol())
         if(OrderType()==OP_SELL)
           {
            CloseShort();
           }

      int total,ord=0,a;                                                //--- If there is a current trade open for this symbol & EA, Ord will be >0
                                                                        //--- which will return to OnTick, thus not running the entry criteria.
      int mn=MAGICNUMBER;

      string symbol;

      total=OrdersTotal();

      for(a=0;a<total;a++)

        {

         if(OrderSelect(a,SELECT_BY_POS)==false)
           {
            Print("OrderSelect() returned error - ",GetLastError());
           }

         if(OrderSymbol()==Symbol() && OrderMagicNumber()==mn)ord++;    //--- Ensure only one order per symbol

        }

      if(ord>0)
         return;
     }

   if(Bars>BarsCount)
     {
      if(PrevLow<=LowerBand)
         if(PrevClose>LowerBand && PrevClose<MidBand)
            if(PrevRSI<=25 && SignalRSI<25)
               if(OrderSend(Symbol(),OP_BUY,LotSize,Ask,50,Ask-Stopp*_Point,0,NULL,785223,0,clrAliceBlue)<0)
                  Print(__FUNCTION__+"OrderSendError: ",GetLastError());

      if(PrevHigh>=UpperBand)
         if(PrevClose<UpperBand && PrevClose>MidBand)
            if(PrevRSI>=75 && SignalRSI<75)
               if(OrderSend(Symbol(),OP_SELL,LotSize,Bid,50,Bid+Stopp*_Point,0,NULL,785223,0,clrAliceBlue)<0)
                  Print(__FUNCTION__+" OrderSendError: ",GetLastError());

      BarsCount=Bars;
     }
  }
//+------------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CloseLong()
  {
   double SignalBar=iHigh(Symbol(),0,1);
   double MidBand =  iBands(Symbol(),0,BBPeriod,2,0,PRICE_CLOSE,0,1);
   double TopBand =  iBands(Symbol(),0,BBPeriod,2,0,PRICE_CLOSE,1,1);

   if(SignalBar>=MidBand)
      if(OrderClose(OrderTicket(),OrderLots(),OrderClosePrice(),50,clrRed)==false)
        {
         Print("OrderClose() Long returned error - ",GetLastError());
        }
  }
//+----------------------------------------------------------------+
//|                                                                  |
//+------------------------------------------------------------------+
void CloseShort()

  {

   double SignalBar=iLow(Symbol(),0,1);
   double MidBand=iBands(Symbol(),0,BBPeriod,2,0,PRICE_CLOSE,0,1);
   double LowerBand  = iBands(Symbol(),0,BBPeriod,2,0,PRICE_CLOSE,2,1);

   if(SignalBar<=MidBand)
      if(OrderClose(OrderTicket(),OrderLots(),OrderClosePrice(),50,clrRed)==false)
        {
         Print("OrderClose()Short returned error - ",GetLastError());
        }
  }

//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
 
  1.    double TickValue        = MarketInfo(Symbol(),MODE_TICKVALUE) /.1;
       double TickSize         = MarketInfo(Symbol(), MODE_TICKSIZE) /.1;
    Why are you dividing? Risk depends on your initial stop loss, lot size, and the value of the pair.
    1. You place the stop where it needs to be - where the reason for the trade is no longer valid. E.g. trading a support bounce the stop goes below the support.
    2. Account Balance * percent/100 = RISK = OrderLots * (|OrderOpenPrice - OrderStopLoss| * DeltaPerLot + CommissionPerLot) (Note OOP-OSL includes the SPREAD, and DeltaPerLot is usually around $10/pip but it takes account of the exchange rates of the pair vs. your account currency.)
    3. Do NOT use TickValue by itself - DeltaPerLot and verify that MODE_TICKVALUE is returning a value in your deposit currency, as promised by the documentation, or whether it is returning a value in the instrument's base currency (EUR, in this case).
                MODE_TICKVALUE is not reliable on non-fx instruments with many brokers.
    4. You must normalize lots properly and check against min and max.
    5. You must also check FreeMargin to avoid stop out
    Most pairs are worth about $10 per PIP. A $5 risk with a (very small) 5 PIP SL is $5/$10/5=0.1 Lots maximum.

  2. double ZZZ              = NormalizeDouble(HourlyATR,4);   
    Do NOT use NormalizeDouble, EVER. For ANY Reason. It's a kludge, don't use it. It's use is always wrong

  3. for(IndexPosition=TotalOrders -1; IndexPosition>=0; IndexPosition--) //--- Cycle through open orders
      :
                CloseLong();
    In the presence of multiple orders (one EA multiple charts, multiple EAs, manual trading,) while you are waiting for the current operation (closing, deleting, modifying) to complete, any number of other operations on other orders could have concurrently happened and changed the position indexing:
    1. For non-FIFO (US brokers,) (or the EA only opens one order per symbol,) you can simply count down in a position loop, and you won't miss orders. Get in the habit of always counting down.
                Loops and Closing or Deleting Orders - MQL4 and MetaTrader 4 - MQL4 programming forum
      For FIFO (US brokers,) and you (potentially) process multiple orders per symbol, you must count up and on a successful operation, reprocess all positions (set index to -1 before continuing.)
    2. and check OrderSelect in case earlier positions were deleted.
                What are Function return values ? How do I use them ? - MQL4 and MetaTrader 4 - MQL4 programming forum
                Common Errors in MQL4 Programs and How to Avoid Them - MQL4 Articles
    3. and if you (potentially) process multiple orders, must call RefreshRates() after server calls if you want to use the Predefined Variables (Bid/Ask) or OrderClosePrice() instead, on the next order/server call.

  4.       for(a=0;a<total;a++){
             if(OrderSelect(a,SELECT_BY_POS)==false){
                Print("OrderSelect() returned error - ",GetLastError());
               }
    
             if(OrderSymbol()==Symbol() && OrderMagicNumber()==mn)ord++;    //--- Ensure only one order per symbol
    
            }
    
          if(ord>0)
             return;
    If the select fails, you print a message and then go right on as if it succeeded. You are already in a loop, closing all orders. Why check in the middle? When the outer loop finishes, you know all orders are closed.
  5. You should be able to read your code out loud and have it make sense. You would never write if( (2+2 == 4) == true) would you? if(2+2 == 4) is sufficient. So don't write if(bool == true), just use if(bool) or if(!bool). Code becomes self documenting when you use meaningful variable names, like bool isLongEnabled where as Long_Entry sounds like a trigger price or a ticket number and "if long entry" is an incomplete sentence.

  6.  if(Bars>BarsCount)
    For a new bar test, Bars is unreliable (a refresh/reconnect can change number of bars on chart,) volume is unreliable (miss ticks,) Price is unreliable (duplicate prices and The == operand. - MQL4 and MetaTrader 4 - MQL4 programming forum.) Always use time.
    I disagree with making a new bar function, because it can only be called once per tick. A variable can be tested multiple times.
              New candle - MQL4 and MetaTrader 4 - MQL4 programming forum

  7. if(OrderSend(Symbol(),OP_BUY,LotSize,Ask,50,Ask-Stopp*_Point
    You buy at the Ask and sell at the Bid.
    • Your buy order's TP/SL are triggered when the Bid reaches it. Not the Ask.
    • Your sell order's TP/SL will be triggered when the Ask reaches it. To trigger at a specific Bid price, add the average spread.
                MODE_SPREAD (Paul) - MQL4 and MetaTrader 4 - MQL4 programming forum - Page 3
    • The charts show Bid prices only. Turn on the Ask line to see how big the spread is (Tools -> Options {control-O} -> charts -> Show ask line.)

  8. As far as your problem, print out your variables and find out why.
 
whroeder1:
  1. Why are you dividing? Risk depends on your initial stop loss, lot size, and the value of the pair.
    1. You place the stop where it needs to be - where the reason for the trade is no longer valid. E.g. trading a support bounce the stop goes below the support.
    2. Account Balance * percent/100 = RISK = OrderLots * (|OrderOpenPrice - OrderStopLoss| * DeltaPerLot + CommissionPerLot) (Note OOP-OSL includes the SPREAD, and DeltaPerLot is usually around $10/pip but it takes account of the exchange rates of the pair vs. your account currency.)
    3. Do NOT use TickValue by itself - DeltaPerLot and verify that MODE_TICKVALUE is returning a value in your deposit currency, as promised by the documentation, or whether it is returning a value in the instrument's base currency (EUR, in this case).
                MODE_TICKVALUE is not reliable on non-fx instruments with many brokers.
    4. You must normalize lots properly and check against min and max.
    5. You must also check FreeMargin to avoid stop out
    Most pairs are worth about $10 per PIP. A $5 risk with a (very small) 5 PIP SL is $5/$10/5=0.1 Lots maximum.

  2. Do NOT use NormalizeDouble, EVER. For ANY Reason. It's a kludge, don't use it. It's use is always wrong

  3. In the presence of multiple orders (one EA multiple charts, multiple EAs, manual trading,) while you are waiting for the current operation (closing, deleting, modifying) to complete, any number of other operations on other orders could have concurrently happened and changed the position indexing:
    1. For non-FIFO (US brokers,) (or the EA only opens one order per symbol,) you can simply count down in a position loop, and you won't miss orders. Get in the habit of always counting down.
                Loops and Closing or Deleting Orders - MQL4 and MetaTrader 4 - MQL4 programming forum
      For FIFO (US brokers,) and you (potentially) process multiple orders per symbol, you must count up and on a successful operation, reprocess all positions (set index to -1 before continuing.)
    2. and check OrderSelect in case earlier positions were deleted.
                What are Function return values ? How do I use them ? - MQL4 and MetaTrader 4 - MQL4 programming forum
                Common Errors in MQL4 Programs and How to Avoid Them - MQL4 Articles
    3. and if you (potentially) process multiple orders, must call RefreshRates() after server calls if you want to use the Predefined Variables (Bid/Ask) or OrderClosePrice() instead, on the next order/server call.

  4. If the select fails, you print a message and then go right on as if it succeeded. You are already in a loop, closing all orders. Why check in the middle? When the outer loop finishes, you know all orders are closed.
  5. You should be able to read your code out loud and have it make sense. You would never write if( (2+2 == 4) == true) would you? if(2+2 == 4) is sufficient. So don't write if(bool == true), just use if(bool) or if(!bool). Code becomes self documenting when you use meaningful variable names, like bool isLongEnabled where as Long_Entry sounds like a trigger price or a ticket number and "if long entry" is an incomplete sentence.

  6. For a new bar test, Bars is unreliable (a refresh/reconnect can change number of bars on chart,) volume is unreliable (miss ticks,) Price is unreliable (duplicate prices and The == operand. - MQL4 and MetaTrader 4 - MQL4 programming forum.) Always use time.
    I disagree with making a new bar function, because it can only be called once per tick. A variable can be tested multiple times.
              New candle - MQL4 and MetaTrader 4 - MQL4 programming forum

  7. You buy at the Ask and sell at the Bid.
    • Your buy order's TP/SL are triggered when the Bid reaches it. Not the Ask.
    • Your sell order's TP/SL will be triggered when the Ask reaches it. To trigger at a specific Bid price, add the average spread.
                MODE_SPREAD (Paul) - MQL4 and MetaTrader 4 - MQL4 programming forum - Page 3
    • The charts show Bid prices only. Turn on the Ask line to see how big the spread is (Tools -> Options {control-O} -> charts -> Show ask line.)

  8. As far as your problem, print out your variables and find out why.

Hi Whroder, 


Thanks for taking the time to write such a detailed response. It helps me greatly. Apologies for taking so long to reply, I have been trying to implement the changes you suggested. I'll answer the questions above in sequence so as not to confuse things.

  1. The division in the original code was my way of getting the value per pip. I have fixed this now with your DeltaPerLot suggestion which also takes care of the Tickvalue issue. I had no idea Tickvalue was not reliable on certain instruments. I believe I had normalized lots correctly, however, I had never run into an issue with Min/Max lot sizing. I have implemented a check for both now. Great suggestion on free margin, usually with an EA of this sort I would only trade one pair per day @ 1% so margin was never really an issue, however, I agree it should be added in as a check in case I decide to trade a few extra charts. I have implemented a FreeMarginCheck, but I am struggling badly with figuring out the Max equity loss across all charts (in case of adverse price movements after opening a trade). I will continue on with trying to figure it out.
  2. Normalize double. I have replaced all my Normalize doubles with other functions such as MathRound where appropriate. Thanks for the heads up.
  3. This point should not be an issue as all this EA will work on is one chart per symbol. 
  4. It never crossed my mind that I was creating a loop inside a loop. Amateur mistake. I have removed the second loop.
  5. I have not converted my entry conditions into Boolean variables, but after reading your point it is something that I will work on. It makes sense. 
  6. Again, the limitations of using Bars was lost on me. As per your suggestions I have changed to Time.
  7. This is an interesting one, although I think looking from the outside the Ask - Stopp looks like an obvious oversight, there is a reason behind it. When placing a stop, rather than calculating the average spread, I include it in the overall stop loss. So even though on a long entry the SL is triggered at the bid price, that price is identified by subtracting the SL (including spread) from the original open (Ask) price and vice versa for a short entry.
  8. Never actually got to the bottom of the problem, however, it seems to have resolved itself while I was changing some variables. A tad frustrating actually seeing as I don't know what was causing the issue, but my best guess is that your suggested changes to TickValue etc, had an effect.

Still very much a work in progress, but thanks for speeding up the process so much for me. 


Edit: Nevermind, the error is back. Looks like it back to the drawing board and printing out variables!

 
UYPTrade: 3. This point should not be an issue as all this EA will work on is one chart per symbol.

Read #1.3 again. It is precisely because you have multiple EAs running (multiple symbols/charts,) that you need to be concerned if you ever have multiple trades on a single chart.

 
whroeder1:

Read #1.3 again. It is precisely because you have multiple EAs running (multiple symbols/charts,) that you need to be concerned if you ever have multiple trades on a single chart.

Thanks for the response Whroeder. I think I may have understood the counting down process. Apologies if this is a bit of a dense reply, but I'd like to know if I have my understanding right.

So what you are saying is that after the loop has been initiated it gets all the open orders and counts down to find a match for the MagicNumber and Symbol as per the check. While it finds one and runs the rest of the code, a different EA could have closed a trade in the meantime disturbing the order of the original loop? I'm sorry if this is a bit rudimentary, but I'd like to have a firm grasp of it before I continue. This idea for this EA is for it to work on the 1HR timeframe on multiple symbols and only open one order per symbol. 

 
You got the idea, If you're fifth in line and someone steps out in front of you, you're not fifth anymore. TF is irrelevant.
 
That's great, thanks for taking the time to explain.