Backtesting with Pending Orders - Amazing in Backtest, Devastating in Reality - is it my code?

Created at 13 Dec 2023, 21:22
How’s your experience with the cTrader Platform?
Your feedback is crucial to cTrader's development. Please take a few seconds to share your opinion and help us improve your trading experience. Thanks!
MI

mike.r.alderson

Joined 05.10.2023

Backtesting with Pending Orders - Amazing in Backtest, Devastating in Reality - is it my code?
13 Dec 2023, 21:22


Hi there, 

I've developed a few strategies recently that rely on Pending Orders and they blow me away in Backtest but the Reality is very different.  Any idea why this and is there a way to fix it?  Is there something wrong with my code or is this just a “thing” in cTrader?  Many thanks in Advance.
 

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using cAlgo.API;
using cAlgo.API.Collections;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;

namespace cAlgo.Robots
{
    [Robot(AccessRights = AccessRights.None)]
    public class OutsideBollingerBounces5mAllDay1m : Robot
    {
        private BollingerBands BB;

        [Parameter(DefaultValue = 6, Group = "Extra")]
        public int StartTime { get; set; }

        [Parameter(DefaultValue = "BollingerBounceBuy", Group = "General")]
        public string LabelBuy { get; set; }
        
        [Parameter(DefaultValue = "BollingerBounceSell", Group = "General")]
        public string LabelSell { get; set; }

        [Parameter("TakeProfit", DefaultValue = 3, MinValue = 3, MaxValue = 8, Step = 0.1, Group = "Risk Management")]
        public double TakeProfit { get; set; }

        [Parameter("StopLoss", DefaultValue = 8, MinValue = 3, MaxValue = 8, Step = 0.1, Group = "Risk Management")]
        public double StopLoss { get; set; }

        [Parameter("Gap", DefaultValue = 5, MinValue = 1.5, MaxValue = 10, Step = 0.5, Group = "Logic")]
        public double Gap { get; set; }

        [Parameter("Volume Percent", DefaultValue = 1, MinValue = 0, Group = "Extra")]
        public double VolumePercent { get; set; }

        [Parameter("Minimum Account Balance", DefaultValue = 300, Group = "Extra")]
        public int MinBalance { get; set; }
        
        [Parameter("Include Trailing Stop", DefaultValue = false, Group = "Risk Management")]
        public bool IncludeTrailingStop { get; set; }

        [Parameter("Trailing Stop Trigger (pips)", DefaultValue = 1.5, MinValue = 0.1, MaxValue = 6, Step = 0.1, Group = "Risk Management")]
        public double TrailingStopTrigger { get; set; }

        [Parameter("Trailing Stop Step (pips)", DefaultValue = 1.5, MinValue = 0.1, MaxValue = 6, Step = 0.1, Group = "Risk Management")]
        public double TrailingStopStep { get; set; }
        
        [Parameter("Drop Rate", DefaultValue = 8, MinValue = 5, MaxValue = 15, Step = 1, Group = "Extra")]
        public double DropRate { get; set; }
        
        [Parameter("Drop Rate 2", DefaultValue = 12, MinValue = 2, MaxValue = 20, Step = 1, Group = "Extra")]
        public double DropRate2 { get; set; }
        
        [Parameter("Drop Gap", DefaultValue = 3, MinValue = 2, MaxValue = 5, Step = 1, Group = "Extra")]
        public double DropGap { get; set; }

        [Parameter("BandPeriods", DefaultValue = 20)]
        public int BandPeriod { get; set; }
        
        [Parameter("Std", DefaultValue = 2)]
        public int Std { get; set; }
        
        [Parameter("MAType")]
        public MovingAverageType MAType { get; set; }
        
        [Parameter("Band Range Max", DefaultValue = 35)]
        public int BandRangeMax { get; set; }
        
        [Parameter("Band Range Min", DefaultValue = 25)]
        public int BandRangeMin { get; set; }
        
        public bool OverTop;
        public bool UnderBottom;

        public double spread;
        public double volumne;

        protected override void OnStart()
        {
            var TimeFrame5 = MarketData.GetBars(TimeFrame.Minute5);
            BB = Indicators.BollingerBands(TimeFrame5.ClosePrices,BandPeriod,Std,MAType);
            //Positions.Closed += PositionsOnClosed;
        }

        protected override void OnBar()
        {            
            //Spread and Time Avoid
            spread = Symbol.Spread;
            
            //Volume Calculation
            Asset baseAssetForNewOrder = Assets.GetAsset("GBP");
            Asset quoteAssetForNewOrder = Assets.GetAsset("USD");
            double BalanceUSD = baseAssetForNewOrder.Convert(quoteAssetForNewOrder, Account.Balance);
            if (Math.Floor(BalanceUSD * 200 * VolumePercent / Bars.ClosePrices.Last(0)) > 1000)
            {
                volumne = 1000;
            }
            else
            {
                volumne = Math.Floor(BalanceUSD * 200 * VolumePercent / Bars.ClosePrices.Last(0));
            }            
            if (Account.Balance < MinBalance / 1.3)
            {
                Stop();
            }
            
            ///////////////////////////////// INITIAL PLACEMENTS /////////////////////////////////
            //EMA14 Initial Placement
            var Buy = PendingOrders.Count(x => x.TradeType == TradeType.Buy && x.SymbolName == Symbol.Name && x.Label == LabelBuy);
            var Sell = PendingOrders.Count(x => x.TradeType == TradeType.Sell && x.SymbolName == Symbol.Name && x.Label == LabelSell);

            if (Bars.HighPrices.Last(0) > BB.Top.Last(0) + Gap && Positions.Count == 0 && Buy == 0)
            {
                PlaceLimitOrder(TradeType.Buy, SymbolName, volumne / 10, BB.Top.Last(0) + spread, LabelBuy, StopLoss, TakeProfit);
            }
            else if (Bars.LowPrices.Last(0) < BB.Bottom.Last(0) - Gap && Positions.Count == 0 && Sell == 0)
            {
                PlaceLimitOrder(TradeType.Sell, SymbolName, volumne / 10, BB.Bottom.Last(0), LabelSell, StopLoss, TakeProfit);
            }
            
            ///////////////////////////////// RESET ORDERS /////////////////////////////////
            //Reset the EMA14 Pending Orders.
            Buy = PendingOrders.Count(x => x.TradeType == TradeType.Buy && x.SymbolName == Symbol.Name && x.Label == LabelBuy);
            Sell = PendingOrders.Count(x => x.TradeType == TradeType.Sell && x.SymbolName == Symbol.Name && x.Label == LabelSell);
            
            if (Buy > 0 && Positions.Count == 0)
            {
                foreach (var pending in PendingOrders)
                {
                    if (pending.Label == LabelBuy)
                    {
                        PlaceLimitOrder(TradeType.Buy, SymbolName, volumne / 10, BB.Top.Last(0) + spread, LabelBuy, StopLoss, TakeProfit);
                        CancelPendingOrder(pending);
                    }
                }
            }
            else if (Sell > 0 && Positions.Count == 0)
            {
                foreach (var pending in PendingOrders)
                {
                    if (pending.Label == LabelSell)
                    {
                        PlaceLimitOrder(TradeType.Sell, SymbolName, volumne / 10, BB.Bottom.Last(0), LabelSell, StopLoss, TakeProfit);
                        CancelPendingOrder(pending);
                    }
                }
            }
            
        }

        protected override void OnTick()
        {
            if (IncludeTrailingStop)
            {
                SetTrailingStop();
            }
            
            if (Positions.Count > 0)
            {
                foreach(var pending in PendingOrders)
                {
                    CancelPendingOrder(pending);
                }
            }
            
            if (Positions.Count > 0 && (Bars.ClosePrices.Last(0) < BB.Top.Last(0) || Bars.OpenPrices.Last(0) < BB.Top.Last(0)))
            {
                foreach(var position in Positions.FindAll(LabelBuy))
                {
                    ClosePosition(position);
                }
            }
            else if (Positions.Count > 0 && (Bars.ClosePrices.Last(0) > BB.Bottom.Last(0) || Bars.OpenPrices.Last(0) > BB.Bottom.Last(0)))
            {
                foreach(var position in Positions.FindAll(LabelSell))
                {
                    ClosePosition(position);
                }
            }
        }    
            
        
        private void SetTrailingStop()
        {
            var sellPositions = Positions.FindAll(LabelSell, SymbolName, TradeType.Sell);
            foreach (var position in sellPositions)
            {
                double distance = position.EntryPrice - Symbol.Ask;
                if (distance < TrailingStopTrigger * Symbol.PipSize)
                    continue;

                double newStopLossPrice = Symbol.Ask + TrailingStopStep * Symbol.PipSize;
                if (position.StopLoss == null || newStopLossPrice < position.StopLoss)
                {
                    ModifyPosition(position, newStopLossPrice, (position.TakeProfit.HasValue ? position.TakeProfit.GetValueOrDefault() : (double?)null));
                    Print("Position TSL Modified");
                }
            }

            var buyPositions = Positions.FindAll(LabelBuy, SymbolName, TradeType.Buy);
            foreach (var position in buyPositions)
            {
                double distance = Symbol.Bid - position.EntryPrice;
                if (distance < TrailingStopTrigger * Symbol.PipSize)
                    continue;

                double newStopLossPrice = Symbol.Bid - TrailingStopStep * Symbol.PipSize;
                if (position.StopLoss == null || newStopLossPrice > position.StopLoss)
                {
                    ModifyPosition(position, newStopLossPrice, (position.TakeProfit.HasValue ? position.TakeProfit.GetValueOrDefault() : (double?)null));
                    Print("Position TSL Modified");
                }
            
            }
            
            
        }
        

        protected override void OnStop()
        {
            
        }
    }
}


@mike.r.alderson
Replies

PanagiotisCharalampous
14 Dec 2023, 07:13

Hi there,

You should explain your issue with more details. You should provide at least some examples of what the problem is.

Best regards,

Panagiotis


@PanagiotisCharalampous

mike.r.alderson
14 Dec 2023, 09:07 ( Updated at: 14 Dec 2023, 09:09 )

RE: Backtesting with Pending Orders - Amazing in Backtest, Devastating in Reality - is it my code?

PanagiotisCharalampous said: 

Hi there,

You should explain your issue with more details. You should provide at least some examples of what the problem is.

Best regards,

Panagiotis

Thank you for such a Quick Response.  Ok, so first we have the backtest results, testing this on the Nas100 (Pepperstone) 12 June 2023 - 12 Dec 2023, it shows an increase of 1,782% - (Yeay! Early Retirement) - but then I notice that a LOT of the trades are doubled up, which shouldn't be possible given the code setup (in my limited experience) and many of the trades close past their StopLoss and TakeProfit by substantial amounts.  Here's a summary:

  1. Results are Unrealistic
  2. Trades double up - (Trades 5 and 6, for example.)
  3. Trades close beyond the TakeProfit by a substantial amount: (Trades 7, 9, 23, etc.)
  4. Trades close beyond the StopLoss by a substantial amount: (Trades 107, 183, etc.)

I'm currently trialling this cBot in a Demo account for a real-life comparison.

(Edit: FYI, Huge Fan of the Software and Support (so much easier than learning MQL5) - this has been a game-changer for me, personally, and I recommend cTrader/Pepperstone to everyone interested)

Many thanks

Mike


@mike.r.alderson

PanagiotisCharalampous
14 Dec 2023, 09:24

Hi Mike,

It is still hard to help you without exact steps on how to reproduce the issues on our machines. If you want the community to help you with coding issues, you need to help them reproduce the problem in two minutes :) Nobody will spend hours guessing what is wrong. We have our own problems to solve :)

Best regards,

Panagiotis


@PanagiotisCharalampous

mike.r.alderson
14 Dec 2023, 15:21

RE: Backtesting with Pending Orders - Amazing in Backtest, Devastating in Reality - is it my code?

PanagiotisCharalampous said: 

Hi Mike,

It is still hard to help you without exact steps on how to reproduce the issues on our machines. If you want the community to help you with coding issues, you need to help them reproduce the problem in two minutes :) Nobody will spend hours guessing what is wrong. We have our own problems to solve :)

Best regards,

Panagiotis

Ok, I'll try to be more exact:

I don't ‘think’ there's anything wrong with my code (although, happy to be corrected)- it seems to operate (in real-time), as expected.

My Question maybe should be: Does anyone else find that the Backtest doesn't seem to reflect anywhere near the Real-Time performance when testing a cBot that uses Pending Orders (i.e. Backtest performs amazingly, but in real-time shows poor results) and has anyone overcome this problem?  

(Perhaps, a setting I need to change or an add-on I need to download, a certain library I should be using?)


@mike.r.alderson

firemyst
16 Dec 2023, 14:51 ( Updated at: 17 Dec 2023, 17:00 )

RE: RE: Backtesting with Pending Orders - Amazing in Backtest, Devastating in Reality - is it my code?

mike.r.alderson said: 

PanagiotisCharalampous said: 

Hi Mike,

It is still hard to help you without exact steps on how to reproduce the issues on our machines. If you want the community to help you with coding issues, you need to help them reproduce the problem in two minutes :) Nobody will spend hours guessing what is wrong. We have our own problems to solve :)

Best regards,

Panagiotis

Ok, I'll try to be more exact:

I don't ‘think’ there's anything wrong with my code (although, happy to be corrected)- it seems to operate (in real-time), as expected.

My Question maybe should be: Does anyone else find that the Backtest doesn't seem to reflect anywhere near the Real-Time performance when testing a cBot that uses Pending Orders (i.e. Backtest performs amazingly, but in real-time shows poor results) and has anyone overcome this problem?  

(Perhaps, a setting I need to change or an add-on I need to download, a certain library I should be using?)

So as @PanagiotisChar hinted in a previous post, how are you doing your back tests? What settings in the back testing mechanism do you use? Do you have any screen captures to show your back test results with your settings?

Like, you're not providing us with any details on how you are backtesting, what the parameter settings are, etc etc.


@firemyst

mike.r.alderson
19 Dec 2023, 10:23 ( Updated at: 21 Dec 2023, 09:23 )

RE: RE: RE: Backtesting with Pending Orders - Amazing in Backtest, Devastating in Reality - is it my code?

firemyst said: 

mike.r.alderson said: 

PanagiotisCharalampous said: 

Hi Mike,

It is still hard to help you without exact steps on how to reproduce the issues on our machines. If you want the community to help you with coding issues, you need to help them reproduce the problem in two minutes :) Nobody will spend hours guessing what is wrong. We have our own problems to solve :)

Best regards,

Panagiotis

Ok, I'll try to be more exact:

I don't ‘think’ there's anything wrong with my code (although, happy to be corrected)- it seems to operate (in real-time), as expected.

My Question maybe should be: Does anyone else find that the Backtest doesn't seem to reflect anywhere near the Real-Time performance when testing a cBot that uses Pending Orders (i.e. Backtest performs amazingly, but in real-time shows poor results) and has anyone overcome this problem?  

(Perhaps, a setting I need to change or an add-on I need to download, a certain library I should be using?)

So as @PanagiotisChar hinted in a previous post, how are you doing your back tests? What settings in the back testing mechanism do you use? Do you have any screen captures to show your back test results with your settings?

Like, you're not providing us with any details on how you are backtesting, what the parameter settings are, etc etc.

 

Ok, thanks for the feedback, this is my first community post and I'm not really aware of other ways to perform a backtest other than: I copied the code from above, pasted it into a New Algo on cTrader, Compiled using .NET6 - added the Nas100 (1m) as an instance, as above, the parameters are default - then I ran the backtest from 12/6/2023 - 12/12/2023, as above and it looked a lot like this:

 

It seems awesome, right?  But in reality, the algorithm does not make money.  My question is: Is there a way to ensure that Algorithms in Backtest perform even close to how they do in the live markets?  Or does the cTrader backtest simply not work properly when using Pending Orders?

 

Here's an Example of one of the errors:

Trade 7:  Closed at a Profit of 13.1 pips (History), even though the TakeProfit was set to 3 pips and when looking in Events, it looks like it actually Stopped Out:

I would imagine, using the same code as above, with the default parameters, backtesting in the NAS100 (1m) on the same dates would provide the same result on a different computer.

 

Happy to provide more information, if you could let me know what might be helpful?

 

Kind regards

 

Mike

 


@mike.r.alderson