1% Risk-Per-Trade Bot Risking more

Created at 17 Nov 2017, 15:45
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!
AR

aronbara664

Joined 29.10.2017

1% Risk-Per-Trade Bot Risking more
17 Nov 2017, 15:45


Hi guys,

 

So I've written a very basic bot that's trading based on bollinger bands. The SL/TP levels are dynamic, meaning that they always change from trade-to-trade. (SL is difference between open/close of the candle...)

I decided that I want to write a function that will determine my lot sizing so that I never risk more than 1% per trade. However, if I start backtesting from early 2011, I see that I'm losing more on my trades than 1%. I'm losing like 1.20% per trade, not 1%. That's a huge discrepancy and I'm not sure where it's coming from. 

I'll paste the code below. This robot is specifically written for the EURUSD pair.

I'll also attach a screenshot of backtesting showing that I'm really down more than 1% per trade.

 

private long LotSizingFunction()
        {

            var askvolume = (((Account.Balance * Risk) * this.Symbol.Ask / CalculateStopLoss()) * 10000;
            var aVolume = askvolume - askvolume % 1000;
            var normalavalue = Symbol.NormalizeVolume(aVolume);

            return normalavalue;

        }
    }

 


@aronbara664
Replies

BeardPower
17 Nov 2017, 16:34

Post more details

Hi,

Please post more details:

  • price value of the SL order, so the SL in ticks can be calculated
  • SL in ticks, which was calculated by CalculateStopLoss

Check, if both were the same.


@BeardPower

aronbara664
17 Nov 2017, 17:07

Hi there,

 

Thank you for reaching out! I'm a newbie so my apologies if I miss something you requested. 

I'll post my CalculateStopLoss() function here. This is all I have that gets me the SL in pips. (Returns an integer...)

 

private int CalculateStopLoss()
        {
            double candleClose = MarketSeries.Close.Last(1);
            double candleOpen = MarketSeries.Open.Last(1);

            double candleClose2 = candleClose * 10000;
            double candleOpen2 = candleOpen * 10000;

            int candleClose3 = Convert.ToInt32(candleClose2);
            int candleOpen3 = Convert.ToInt32(candleOpen2);

            int SL;

            return SL = candleClose3 > candleOpen3 ? candleClose3 - candleOpen3 : candleOpen3 - candleClose3;

        }

 


@aronbara664

BeardPower
17 Nov 2017, 17:44

You can just do:

return Math.Abs(candleClose3 - candleOpen3);

to get the absolute value, which is always positive.

Print out the returned value of each trade, post them and also post the stop loss and entry price of your trades so that they can be compared.

 


@BeardPower

aronbara664
17 Nov 2017, 18:11

Hi, I will compile my data and will reply back. Thanks!


@aronbara664

aronbara664
18 Nov 2017, 15:36 ( Updated at: 21 Dec 2023, 09:20 )

Hi there!

Finally managed to get back.

I did a little test on the first 10 trades. I had my Stop Losses printed out. They match. I will also post the simplified version of my whole code.

I'm thinking maybe the issue is that pip value and all these things change while the trade is on and while price is moving up & down? Not sure.

Anyhow, here are the screenshots.

 

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

namespace cAlgo
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class BB1PercentRiskTradeBOT : Robot
    {
        [Parameter("Instance Name", DefaultValue = "003")]
        public string InstanceName { get; set; }

        [Parameter("Start Trading (hour)", DefaultValue = 8)]
        public int StartHour { get; set; }

        [Parameter("Stop Trading (hour)", DefaultValue = 20)]
        public int StopHour { get; set; }

        [Parameter("Stop Opening (hour)", DefaultValue = 17)]
        public int BedTime { get; set; }

        [Parameter("Close all positions?", DefaultValue = true)]
        public bool IsCloseAllPositions { get; set; }

        [Parameter("Bollinger Source")]
        public DataSeries BollSource { get; set; }

        [Parameter("Bollinger Period", DefaultValue = 20)]
        public int BollPeriod { get; set; }

        [Parameter("Bollinger Deviation", DefaultValue = 2.0)]
        public double BollDev { get; set; }

        [Parameter(" Max Open Positions (at a time)", DefaultValue = 1)]
        public int positionLimit { get; set; }

        [Parameter("TP/SL Ratio", DefaultValue = 2)]
        public int TPRatio { get; set; }

        [Parameter("Risk [%]", DefaultValue = 0.01)]
        public double Risk { get; set; }

        [Parameter("Max Executed Trades", DefaultValue = 500, MinValue = 0, Step = 100)]
        public int maxTrades { get; set; }

        private BollingerBands boll;
        private const string label = "Sample Trend cBot";
        private DateTime _tradingTime { get; set; }
        private int trades = 0;
        private int buytrades = 0;
        private int selltrades = 0;
        public int currentpositions2 = 0;

        protected override void OnStart()
        {
            boll = Indicators.BollingerBands(BollSource, BollPeriod, BollDev, MovingAverageType.Simple);
        }

        protected override void OnBar()
        {
            _tradingTime = TradingDateTime();

            // _tradingTime.Hour >= StartHour : true) : false)
            if (_tradingTime.DayOfWeek != DayOfWeek.Friday || _tradingTime.DayOfWeek != DayOfWeek.Saturday || _tradingTime.DayOfWeek != DayOfWeek.Sunday)
            {
                if (_tradingTime.Hour >= StartHour && _tradingTime.Hour < StopHour)
                {
                    ManagePositions();
                    return;
                }
            }
            else if (_tradingTime.DayOfWeek == DayOfWeek.Friday)
            {
                if (_tradingTime.Hour >= StartHour && _tradingTime.Hour < BedTime)
                {
                    ManagePositions();
                    return;
                }
                else if (_tradingTime.Hour >= BedTime)
                {
                    try
                    {
                        CloseAllPositions();
                    } catch (Exception ex)
                    {
                        Print("Error: " + ex.Message);
                    }
                }
            }
        }

        public DateTime TradingDateTime()
        {
            if (this.IsBacktesting)
            {
                return Server.Time;
            }
            else
            {
                return TimeZoneInfo.ConvertTime(DateTime.Now, TimeZoneInfo.Local);
            }
        }

        private void CloseAllPositions()
        {
            // if user has chosen to close all positions
            if (IsCloseAllPositions)
            {
                foreach (var position in Positions)
                {
                    ClosePositionAsync(position);
                }
            }
        }

        private void ManagePositions()
        {
            var longPosition = Positions.Find(label, Symbol, TradeType.Buy);
            var shortPosition = Positions.Find(label, Symbol, TradeType.Sell);

            #region Variables

            var upperBoll = boll.Top.Last(1);
            var lowerBoll = boll.Bottom.Last(1);
            var middleBoll = boll.Main.Last(1);

            var lastPrice = MarketSeries.Close.Last(1);
            var SL = CalculateStopLoss();

            if (lastPrice > upperBoll && trades < maxTrades && SL != 0)
            {
                if (MaxPositions2() == true)
                {
                    OpenPosition(TradeType.Sell);
                    Print("STOP LOSS IS: {0}", CalculateStopLoss());
                    trades++;

                }

            }

            if (lastPrice < lowerBoll && trades < maxTrades && SL != 0)
            {
                if (MaxPositions2() == true)
                {
                    OpenPosition(TradeType.Buy);
                    Print("STOP LOSS IS: {0}", CalculateStopLoss());
                    trades++;

                }
            }
        }

        private void OpenPosition(TradeType type)
        {
            ExecuteMarketOrder(type, this.Symbol, LotSizingFunction(), InstanceName, CalculateStopLoss(), CalculateTP());

        }

        private bool MaxPositions2()
        {

            bool realcount = Positions.Count < positionLimit ? true : false;
            return realcount;


        }

        private int CalculateStopLoss()
        {
            double candleClose = MarketSeries.Close.Last(1);
            double candleOpen = MarketSeries.Open.Last(1);

            double candleClose2 = candleClose * 10000;
            double candleOpen2 = candleOpen * 10000;

            int candleClose3 = Convert.ToInt32(candleClose2);
            int candleOpen3 = Convert.ToInt32(candleOpen2);

            int SL;

            return SL = candleClose3 > candleOpen3 ? candleClose3 - candleOpen3 : candleOpen3 - candleClose3;

        }

        private int CalculateTP()
        {
            int takeProfit = CalculateStopLoss() * TPRatio;
            return takeProfit;
        }

        private long LotSizingFunction()
        {

            var askvolume = (((Account.Balance * Risk) * this.Symbol.Ask / CalculateStopLoss())) * 10000;
            var aVolume = askvolume - askvolume % 1000;
            var normalavalue = Symbol.NormalizeVolume(aVolume);

            return normalavalue;

        }
    }

    #endregion
}

You can see the simplified version of my code above. It seems when opening trades, we're risking more than 1%, in fact it's around 1.20% - 1.30%. Both the SL and TP are overextended.


@aronbara664

BeardPower
18 Nov 2017, 17:30

Hi,

It seems it's a cTrader issue.
cTrader is not using the closing price of EUR/USD, as it should.

E.g., trade number 1:
It made a profit of 80 pips ((1.41428 - 1.40628) * 10000), which matches two times the SL (2 * 40), as specified in your code.
The profit in EUR would be the volume * TP (in ticks) * 1 / EUR/USD: EUR 197.98 (35000 * 0.00800 * 1 / 1.41428), but it shows a wrong value of EUR 240.22

Try to calculate it by the actual EUR/USD price on your platform at the time of your backtest. If I use the actual price on my chart (1.17943), your profit would be:
35000 * 0.008 / 1.17943 = EUR 237.40, which is more closer to your result. The EUR/USD used in the calculation was 240.22 / 200 = 1.2011. None of these numbers are matching up.

There must be some issue with calculating the profit.


@BeardPower

BeardPower
18 Nov 2017, 17:46

Is your Account in EUR?

cTrader is using the account currency for volume, so you don't multiply it with the quote currency:

var askvolume = Account.Balance * Risk * Symbol.Ask / CalculateStopLoss * 10000;

It would only be:

var askvolume = Account.Balance * Risk  / CalculateStopLoss() * 10000;

But even then, the numbers don't match up:
35000 * 1.40628 * 0.008 / 1.41428 = EUR 278.42 profit

 

 


@BeardPower

aronbara664
18 Nov 2017, 18:20

Hey there,

 

Thank you for getting back to me. Yes, my account is in EUR. I created the LotSizing function based on this article: https://www.babypips.com/learn/forex/calculating-position-sizes but it seems like I might really have to take out the * this.Symbol.Ask ... ANYHOW:

So I tried to run a couple more backtests and I found out some really interesting things... If I backtest the algorithm on a very short timeframe, I really do get something close to 197 profit in EUR. However, if I change the backtest time period to a longer period, all my numbers change -- this doesn't seem to be right and it doesn't make any sense to me, since it should be irrelevant. At least when looking at the first X trades. 

Any ideas? I'm pretty lost right now lol. Still trying to figure it all out though!


@aronbara664

aronbara664
18 Nov 2017, 21:06 ( Updated at: 21 Dec 2023, 09:20 )

Alright so we recreated our volume calculation to this, as laid out in the API reference:

 

            var juro = ((Account.Balance * Risk) / CalculateStopLoss()) / Symbol.PipValue;

 

We decided to do a backtest with only 1 trade. The only change we made is the timeframe which we trade on. As expected, the TP/SL changes if we change the timeframe, which makes little sense. Please see screenshots below:

 


@aronbara664

BeardPower
18 Nov 2017, 23:14

RE:

aronbara664 said:

Alright so we recreated our volume calculation to this, as laid out in the API reference:

 

            var juro = ((Account.Balance * Risk) / CalculateStopLoss()) / Symbol.PipValue;

This calculation would only be correct if your account currency is USD, which is assumed by the API reference.

As expected, the TP/SL changes if we change the timeframe, which makes little sense.

Are you testing based on ticks? When you are talking about the timeframe, are you talking about the testing period?
The TP/SL does not change, only the volume, which is very weird. In both screenshots, there is the same entry price and the same close price, so why is the volume 0.19 Lots in the first screenshot, but 0,23 Lots in the second? The difference in volume would only happen if open and close are different in CalculateStopLoss(). Can you please also print these values?
Both values should be the same, regardless of the testing period.


@BeardPower

aronbara664
19 Nov 2017, 19:43 ( Updated at: 21 Dec 2023, 09:20 )

Hey there!

Alright, first things first -- I rewrote my volume calculation formula to this:

 

            var askvolume = Account.Balance * Risk / CalculateStopLoss() * 10000;

 

When I referreed to timeframe, I meant the backtesting period -- for example from 2011/4/5 to 2013/5/6. The robot itself trades on the HOURLY Timeframe. 

I did some new backtests and had my SLs & TPs printed out. I think I've gotten a lot closer to figuring this out, thanks to you.

Here are the results:

I had my robot do 3 trades in total. I tested 2 timeframes. The volumes match, the SLs match, the TPs match. Everything matches, except two things.

When I look at my chart, it says I should've made 171 EUR in profit. It doesn't change, regardless of the timeframe I test on. However, my NET PROFIT in the trade history section always shows a different number. My theory is that it actually shows the profit in EUR according to the current exchange rate or something similar to that? Since the chart numbers match but the trade history NET PROFT numbers don't.

So now that we have some clarity, one more thing that doesn't make sense to me is the actual SL/TP in EUR. According to the formula I sent above, I should be risking 1% constantly and gaining 2% (As my TP is 2X my SL...)... However, I fall short of that. If you look at my trades, all my TPs are around 173 EUR and my SLs are around 85 EUR. (According to the chart numbers...)

Is there something wrong with the volume calculation formula?

Anyhow, please see screenshots below:

 

 

 

 


@aronbara664

BeardPower
20 Nov 2017, 14:09

RE:

aronbara664 said:

Hey there!

Alright, first things first -- I rewrote my volume calculation formula to this:

 

            var askvolume = Account.Balance * Risk / CalculateStopLoss() * 10000;

As your account is in EUR, you would have to multiply the term by the entry price.

Your calculations are all correct, the issue with the profit calculation is a cTrader issue. It seems that it's using the price at the end of the testing period. Calculate it with these prices and you will see, that the profit is nearly correct:

01.11.2011: 18000 * 0.0112 / 1.3696 = 147.20
15.01.2013: 18000 * 0.0112 / 1.3304 = 151.53

I took the prices from here:
https://www.investing.com/currencies/eur-usd-historical-data

You should report this issue to Spotware!

 

 


@BeardPower

aronbara664
20 Nov 2017, 15:06

Hi!

So I guess the final formula should look something like this:

 

            var askvolume = (Account.Balance * Risk) / CalculateStopLoss() * 10000 * ENTRYPRICE;

 

Right? 

 

Now, I figured that not only the NET profit is calculated inaccurately but also the chart TP/SL numbers. (How much I win/lose per trade...)

I backtested my robot going only a few months back (Still 2017) and with the formula mentioned above I really do get -100 per LOSING trade and +200 per WINNING. (Almost, very close to it...)


However if I go back X years in time, these numbers will get overextended too. If I go back 3-4 years, I'll have WINNING trades netting me 240EUR or more. Then if I move forward in time a bit there are trades that are netting me LESS than 200 per winning trade. However, the closer I get to the present, the more accurate those numbers are.

So the only explanation I can find for this is that something really is wrong with the backtesting. However, that makes me wonder -- I can't possibly be the only one who noticed this? Thousands of people are developing algos on the platform DAILY, so I'm not sure whether my methods are incorrect or whether there really is something wrong with the platform.

Anyhow, I'll contact SPOTWARE.

Thank you for your help!


@aronbara664

PanagiotisCharalampous
20 Nov 2017, 15:34

Hi to all,

This might be related to this issue. Please read my last reply why this is happening. We are looking into how we can improve this behavior.

Best Regards,

Panagiotis


@PanagiotisCharalampous

BeardPower
20 Nov 2017, 15:47

RE:

aronbara664 said:

Hi!

So I guess the final formula should look something like this:

 

            var askvolume = (Account.Balance * Risk) / CalculateStopLoss() * 10000 * ENTRYPRICE;

 

Right?

Correct.

 


@BeardPower

BeardPower
20 Nov 2017, 15:48

RE: RE:
var askvolume = (Account.Balance * ENTRYPRICE * Risk ) / CalculateStopLoss() * 10000 ;

 


@BeardPower

BeardPower
20 Nov 2017, 15:49

RE: RE: RE:

Ah, the forum definitely needs an edit functionality!

var askvolume = (Account.Balance * ENTRYPRICE * Risk ) / CalculateStopLoss() * 10000;

 


@BeardPower