A big bug in tester - optimizer

Created at 05 Mar 2019, 12:56
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!
alexander.n.fedorov's avatar

alexander.n.fedorov

Joined 02.01.2018

A big bug in tester - optimizer
05 Mar 2019, 12:56


Dear Panagiotis!

It looks like there is a big bug in tester-optimizer/

The way I see the optimization is done is this

1. You do optimization with a set of parameters

2. You  select certain result and push the "Apply" green button

3. when you do that all the timing, onTick, comissions and parameters are copied to backtester

4. You run backtest and , theoretically, and as it always was happening, must get the same results

in this case it did not work like that

see the images:


@alexander.n.fedorov
Replies

alexander.n.fedorov
05 Mar 2019, 12:58 ( Updated at: 21 Dec 2023, 09:21 )

RE:

alexander.n.fedorov said:

Dear Panagiotis!

It looks like there is a big bug in tester-optimizer/

The way I see the optimization is done is this

1. You do optimization with a set of parameters

2. You  select certain result and push the "Apply" green button

3. when you do that all the timing, onTick, comissions and parameters are copied to backtester

4. You run backtest and , theoretically, and as it always was happening, must get the same results

in this case it did not work like that

see the images:

 


@alexander.n.fedorov

PanagiotisCharalampous
05 Mar 2019, 14:05

Hi Sasha,

It is probably related to this issue.

Best Regards,

Panagiotis


@PanagiotisCharalampous

alexander.n.fedorov
05 Mar 2019, 14:09

RE:

Panagiotis Charalampous said:

Hi Sasha,

It is probably related to this issue.

Best Regards,

Panagiotis

So, it is known bug?

 


@alexander.n.fedorov

PanagiotisCharalampous
05 Mar 2019, 14:18

Hi Sasha,

The issue in the thread I posted is a bug and will be fixed. However I don't know if this is your case as well since I do not have the source code. But probably it is caused by the same issue.

Best Regards,

Panagiotis


@PanagiotisCharalampous

alexander.n.fedorov
05 Mar 2019, 14:24

There is no prob to give you source code. It is just a simple Big Shadow strategy

I was trying to use optimizor, cause different pairs have differen behaviour

But, if you need code, just tell me, no prob

 

 


@alexander.n.fedorov

PanagiotisCharalampous
05 Mar 2019, 14:32

Hi Sasha,

This happens only when you are using data from another timeframe. If you do so then this is the reason.

Best Regards,

Panagiotis


@PanagiotisCharalampous

alexander.n.fedorov
05 Mar 2019, 21:26

RE:

Panagiotis Charalampous said:

Hi Sasha,

This happens only when you are using data from another timeframe. If you do so then this is the reason.

Best Regards,

Panagiotis

TimeFrame is the same


@alexander.n.fedorov

PanagiotisCharalampous
06 Mar 2019, 10:08

Hi Sasha,

Can you please share the code then?

Best Regards,

Panagiotis


@PanagiotisCharalampous

alexander.n.fedorov
06 Mar 2019, 11:53

RE:

Panagiotis Charalampous said:

Hi Sasha,

Can you please share the code then?

Best Regards,

Panagiotis

I sent it to  you

 


@alexander.n.fedorov

PanagiotisCharalampous
07 Mar 2019, 16:59

Hi Sasha,

I have checked your cBot and you are using data from another timeframe

       [Parameter("S & R TimeFrame", DefaultValue = "Daily")]
        public TimeFrame SRTimframe { get; set; }
.
.
.

     seriesSR = MarketData.GetSeries(SRTimframe);
.
.
.
.

Best Regards,

Panagiotis


@PanagiotisCharalampous

alexander.n.fedorov
07 Mar 2019, 17:36

RE:

Panagiotis Charalampous said:

Hi Sasha,

I have checked your cBot and you are using data from another timeframe

       [Parameter("S & R TimeFrame", DefaultValue = "Daily")]
        public TimeFrame SRTimframe { get; set; }
.
.
.

     seriesSR = MarketData.GetSeries(SRTimframe);
.
.
.
.

Best Regards,

Panagiotis

O , that is tru. I thought you were saying that I do backtesting on different timeframe, then optimization

But that is still a bug

Best regards,

Alexander

 

 

 


@alexander.n.fedorov

alexander.n.fedorov
07 Mar 2019, 17:37

RE: RE:

alexander.n.fedorov said:

Panagiotis Charalampous said:

Hi Sasha,

I have checked your cBot and you are using data from another timeframe

       [Parameter("S & R TimeFrame", DefaultValue = "Daily")]
        public TimeFrame SRTimframe { get; set; }
.
.
.

     seriesSR = MarketData.GetSeries(SRTimframe);
.
.
.
.

Best Regards,

Panagiotis

O , that is tru. I thought you were saying that I do backtesting on different timeframe, then optimization

But that is still a bug

Best regards,

Alexander

P. S. And thank you very much for checking

 

 

 

 

 


@alexander.n.fedorov

PanagiotisCharalampous
07 Mar 2019, 17:55

Hi Sasha,

Yes it is a bug and we are aware about it. I just wanted to make sure it is the one we know. It happens on optimization when cBots get data form other timeframes than the one they execute on.

Best Regards,

Panagiotis


@PanagiotisCharalampous

tradermatrix
02 Apr 2019, 12:20

yes it's a bug ..
I hulled all your bot piece by piece but finally I did not find any error ..
so i figured that the optimization flaw came up when the cBot gets data in the form of deadlines other than the one on which it runs.
I modified your code a little and I realized to the optimization that when my setting "Time Frame" is identical to the one on which it is executed there is no more problem ...
that's why it worked previously because I optimized on 5m ....
thanks to you I just noticed that some of my robots do not have the right settings ... because I trusted optimization.
hope that this problem will be settled soon
cordially

using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using cAlgo.Indicators;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.RussianStandardTime, AccessRights = AccessRights.FullAccess)]
    public class VolatilityBreakout4 : Robot
    {
        [Parameter("Source")]
        public DataSeries Source { get; set; }

        [Parameter("Time Frame", DefaultValue = "Minute5")]
        public TimeFrame timeframe { get; set; }

        [Parameter("Risk", MaxValue = 10, MinValue = 0.5, DefaultValue = 1)]
        public double Risk { get; set; }

        [Parameter("Squeeze bars number", MaxValue = 180, MinValue = 5, Step = 1, DefaultValue = 20)]
        public int SqueezeBarsNumber { get; set; }

        [Parameter("Squeeze in pips", MaxValue = 300, MinValue = 1, Step = 1, DefaultValue = 20)]
        public int Squeeze { get; set; }

        [Parameter("TP multiple", MaxValue = 8, MinValue = 1, Step = 0.1, DefaultValue = 2)]
        public double TpMultiple { get; set; }

        [Parameter("Close on Profit %", Step = 0.1, DefaultValue = 4.0)]
        public double CloseOnProfit { get; set; }

        [Parameter("Close Trade Hour", DefaultValue = 23)]
        public int CloseTradesHour { get; set; }

        [Parameter("Close Trade Minute", DefaultValue = 30)]
        public int CloseTradesMinute { get; set; }

        [Parameter("Open Trade Hour", DefaultValue = 2)]
        public int OpenTradesHour { get; set; }

        private string instance;
        private MarketSeries series;
        private double range, high, low, volume, barsRange, SL, TP, minVolume, risk, longPrice;
        private double shortPrice, closeOnProfit;
        private int pipDigits, n;
        private DateTime squeezeFirstBarTime, squeezeLastBarTime;

        protected override void OnStart()
        {
            instance = ToString() + ", " + Symbol.Code + ", " + TimeFrame + ", " + Account.BrokerName + ", " + Account.Number;
            range = Squeeze * Symbol.PipSize;

            series = MarketData.GetSeries(timeframe);
            minVolume = Symbol.VolumeInUnitsMin;
            risk = Risk * 0.01;
            closeOnProfit = CloseOnProfit * 0.01;
            pipDigits = Symbol.Digits - (int)Math.Log10(1 / Symbol.PipSize);
            Positions.Opened += Positions_Opened;
            Positions.Closed += PositionsOnClosed;
            squeezeFirstBarTime = CandleOpenTime(2 + SqueezeBarsNumber);
            squeezeLastBarTime = CandleOpenTime(2);
        }

        protected override void OnBar()
        {
            var position = Positions.Find(instance);
            var spread = Symbol.Spread / Symbol.PipSize;
            if (position == null)
            {
                if (spread > 2)
                {
                    return;
                }
                if ((CandleOpenTime(0).Hour >= CloseTradesHour && CandleOpenTime(0).Minute >= CloseTradesMinute) || CandleOpenTime(0).Hour < OpenTradesHour)
                {
                    return;
                }
                squeezeFirstBarTime = CandleOpenTime(1 + SqueezeBarsNumber);
                squeezeLastBarTime = CandleOpenTime(1);
                high = CandleHigh(1);
                low = CandleLow(1);
                barsRange = CandleClose(1) > (1) ? MarketSeries.Close.Last(1) - MarketSeries.Open.Last(1) : MarketSeries.Open.Last(1) - MarketSeries.Close.Last(1);
                for (int i = 0; i < SqueezeBarsNumber; i++)
                {
                    high = CandleHigh(i + 1) > high ? CandleHigh(i + 1) : high;
                    low = CandleLow(i + 1) < low ? CandleLow(i + 1) : low;
                    barsRange = high - low;
                }
                //Chart.RemoveAllObjects();
                //Chart.DrawTrendLine("sHigh", squeezeFirstBarTime, high, squeezeLastBarTime, high, Color.Aqua, 1, LineStyle.Solid);
                //Chart.DrawTrendLine("sLow", squeezeFirstBarTime, low, squeezeLastBarTime, low, Color.Red, 1, LineStyle.Solid);
                //double barRangePips = Math.Round(barsRange / Symbol.PipSize, 1);
                //string text = "Squeeze = " + Squeeze + " , barRange = " + barRangePips;
                //Chart.DrawStaticText("Current range", text, VerticalAlignment.Bottom, HorizontalAlignment.Right, Color.Aquamarine);


                n = 0;
                foreach (var order in PendingOrders)
                {
                    if (order.Label == instance)
                    {
                        n = n + 1;
                    }
                }
                if (n == 0)
                {
                    if (high - low < range)
                    {
                        {
                            longPrice = high;
                            shortPrice = low;
                            SL = (high - low) / Symbol.PipSize + 1;
                            volume = Math.Floor(((Account.Equity * risk) / SL) / Symbol.PipValue / minVolume) * minVolume;
                            SL = Math.Round(SL, pipDigits, MidpointRounding.AwayFromZero);
                            TP = SL * TpMultiple;
                            PlaceStopOrder(TradeType.Buy, Symbol, volume, longPrice, instance, null, TP);
                            PlaceStopOrder(TradeType.Sell, Symbol, volume, shortPrice, instance, null, TP);
                            //Chart.DrawHorizontalLine(longPrice.ToString(), longPrice - SL * Symbol.PipSize, Color.Crimson, 1, LineStyle.DotsRare);
                            //Chart.DrawHorizontalLine(shortPrice.ToString(), shortPrice + SL * Symbol.PipSize, Color.Crimson, 1, LineStyle.DotsRare);
                        }
                    }
                }
            }
            if (position != null)
            {
                if (spread > 2)
                {
                    return;
                }
                if (position.TradeType == TradeType.Buy)
                {
                    if (CandleClose(1) < position.EntryPrice - SL * Symbol.PipSize)
                    {
                        ClosePosition(position);
                        //Chart.RemoveAllObjects();
                    }
                }
                if (position.TradeType == TradeType.Sell)
                {
                    if (CandleClose(1) > position.EntryPrice + SL * Symbol.PipSize)
                    {

                        ClosePosition(position);
                        //Chart.RemoveAllObjects();
                    }
                }
            }
        }
        protected override void OnTick()
        {
            if (Account.UnrealizedNetProfit > Account.Balance * closeOnProfit)
                foreach (var position in Positions)
                {
                    ClosePositionAsync(position);
                }
        }
        private void Positions_Opened(PositionOpenedEventArgs args)
        {
            var position = args.Position;
            if (position.Label == instance)
            {
                foreach (var order in PendingOrders)
                {
                    if (order.Label == instance)
                        CancelPendingOrder(order);
                }
                if (position.TradeType == TradeType.Buy)
                {
                    //Chart.RemoveObject(shortPrice.ToString());
                }
                if (position.TradeType == TradeType.Sell)
                {
                    //Chart.RemoveObject(longPrice.ToString());
                }
            }

        }
        private void PositionsOnClosed(PositionClosedEventArgs args)
        {
            var position = args.Position;

            if (args.Reason == PositionCloseReason.TakeProfit)
            {
                //
            }
        }
        #region Candles Processing
        #region Candle Type
        private bool CandleTypeBullish(int k)
        {
            if (series.Open.Last(k) > series.Close.Last(k))
            {
                return false;
            }
            return true;
        }
        #endregion

        #region Candle Range(int k)
        private double CandleRange(int k)
        {
            return series.High.Last(k) - series.Low.Last(k);
        }
        #endregion

        #region Candle body bottom
        private double CandleBodyBottom(int k)
        {
            return (series.Open.Last(k) < series.Close.Last(k)) ? series.Open.Last(k) : series.Close.Last(k);
        }
        #endregion

        #region Candle body Top
        private double CandleBodyTop(int k)
        {
            return (series.Open.Last(k) > series.Close.Last(k)) ? series.Open.Last(k) : series.Close.Last(k);
        }
        #endregion

        #region Candle high
        private double CandleHigh(int k)
        {
            return series.High.Last(k);
        }
        #endregion

        #region Candle low
        private double CandleLow(int k)
        {
            return series.Low.Last(k);
        }
        #endregion

        #region Candle body
        private double CandleBody(int k)
        {
            return Math.Abs((series.Open.Last(k) - series.Close.Last(k)));
        }
        #endregion

        #region Candle Close
        private double CandleClose(int k)
        {
            if (CandleTypeBullish(k))
            {
                return CandleBodyTop(k);
            }
            return CandleBodyBottom(k);
        }
        #endregion

        #region Candle Open
        private double CandleOpen(int k)
        {
            if (CandleTypeBullish(k))
            {
                return CandleBodyBottom(k);
            }
            return CandleBodyTop(k);
        }

        #endregion

        #region Candle Sinus
        private double CandleSinus(int k)
        {
            return Math.Abs((series.Open.Last(k) - series.Close.Last(k))) / CandleRange(k) * (series.Close.Last(k) > series.Open.Last(k) ? 1 : -1);
        }
        #endregion

        #region Trend Angle
        private double TrendAngle(int CandlesInTrend)
        {
            double absBody = 0;
            double bodiesRange = 0;
            for (int i = 0; i <= CandlesInTrend; i++)
            {
                if (CandleTypeBullish(i))
                {
                    bodiesRange = bodiesRange + CandleBody(i);
                }
                if (!CandleTypeBullish(i))
                {
                    bodiesRange = bodiesRange - CandleBody(i);
                }
                absBody = absBody + CandleBody(i);
            }
            var trendSinus = bodiesRange / absBody;
            return Math.Asin(trendSinus) * 180 / Math.PI;
        }
        #endregion

        #region Candle OpenTime
        private DateTime CandleOpenTime(int k)
        {
            return series.OpenTime.Last(k);
        }
        #endregion

        #region No k Candles on the left High
        private bool NoLeftCandlesHigh(int k)
        {
            for (int i = 1; i < k; i++)
            {
                if (CandleHigh(1) > CandleLow(1 + i) && CandleHigh(1) < CandleHigh(1 + i))
                {
                    return false;
                }
                if (CandleHigh(1) < CandleLow(1 + i) || CandleHigh(1) > CandleHigh(1 + i))
                {
                    continue;
                }
            }
            return true;
        }
        #endregion
        #region No k Candles on the left Low
        private bool NoLeftCandlesLow(int k)
        {
            for (int i = 1; i < k; i++)
            {
                if (CandleLow(1) < CandleHigh(1 + i) && CandleLow(1) > CandleLow(1 + i))
                {
                    return false;
                }
                if (CandleLow(1) < CandleHigh(1 + i) || CandleLow(1) > CandleLow(1 + i))
                {
                    continue;
                }
            }
            return true;
        }
        #endregion
        #endregion
    }
}

 


@tradermatrix

alexander.n.fedorov
03 Apr 2019, 13:49 ( Updated at: 21 Dec 2023, 09:21 )

Hi, TraDerMatTix

Be carefull with using different timeframe then the default is. You may face the same problem
My bot  is supposingly doing good results, and the optimization is nor serving a curve fitting, but rather makes aware of every pair and timeframe characteristik kind of "wavelength"

You have to run a portfolio of a 15-20 instruments.

Optimization should be not done before the date I put for a couple of reasons:

1. Brexit affected all the results in 2016 (checked it)

2. 2 years and a quater give  you rather representative stats on the wavelenth

What  i do first, I do multitimeframe optimization on 1 minute bars, then get the best timeframe based on pre-analysis, the live it overnight on tick in the best timeframe

With the risk structure I put, I normally accept a pair/timeframe if gives me a return of >=60% for 2.25.years. 

 

Then I take the optimized portfolio and consolidate the trades in Excel

This is the kind of curve that I receive

As you cand see with the original deposit of 5K, you should tripple your account in a matter of 4 month.

That should be statistically representative. So, now I am running a live test on Demo

Should find it usefull, you can try it . If you find some improvements, please, let me know

 

Alexander

 


@alexander.n.fedorov