Problems using indicator in bot

08 Aug 2018, 07:58Problems using indicator in bot#1
bishbashboshposts: 4since: 08 Aug 2018

I have developed an indicator that is working exactly as planned.

I now want to use this to power a bot, but I am having difficulty in using the output from the indicator correctly.

The indicator has two output series (sells omitted for brevity):


        [Output("Buys", PlotType = PlotType.Points, Color = Colors.Green, Thickness = 4)]
        public IndicatorDataSeries BuyDots { get; set; }
        public override void Calculate(int index)
        {
            // logic..

            if (buy)
            {
                BuyDots[index] = DotUpY(low); // place dot below low of bar
            }
        }

Buy and sell dots are placed correctly on the chart.

However, when I tried to use these output series in the bot:

        protected override void OnBar()
        {
            if (_indicator.BuyDots.Last(1) > 0)
                PlaceEntryOrder(TradeType.Buy, MarketSeries.High.Last(1));
        }

...the output is garbage - it seems as if BuyDots and MarketSeries do not refer to the same bar.

So I try a different tack; in the indicator:

        public override void Calculate(int index)
        {
            // logic..

            if (buy)
            {
                BuyDots[index] = DotUpY(low); // place dot below low of bar
                LastBuyTime = MarketSeries.OpenTime.LastValue;
            }
        }

And in the bot:

        protected override void OnBar()
        {
            if (_indicator.LastBuyTime == MarketSeries.OpenTime.Last(1)) // never matches
                PlaceEntryOrder(TradeType.Buy, MarketSeries.High.Last(1));
        }

After stepping through in VS 2017, the reason that it never matches now is that OnBar is called for the entire back-test before Calculate even hits once (so LastBuyTime is always DateTime.MinValue).

Please advise what I should be doing.

08 Aug 2018, 10:47#2
Panagiotis Charalampousposts: 1186since: 13 Jan 2017

Hi bishbashbosh,

Thank you for posting in our forum. Could you please send us the full source code of the cBot and indicator so that we can reproduce and advise accordingly?

Best Regards,

Panagiotis


Head of Community Management at cTrader
09 Aug 2018, 08:08#3
bishbashboshposts: 4since: 08 Aug 2018

Hi Panagiotis

Here is some code that demonstrates the problem:

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

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class TestIndicator : Indicator
    {
        private CrossoverEvent _lastEvent;
        private ExponentialMovingAverage _longEma;
        private ExponentialMovingAverage _shortEma;

        [Parameter("EMA periods (fast)", DefaultValue = 8)]
        public int FastEmaPeriods { get; set; }

        [Parameter("EMA periods (slow)", DefaultValue = 13)]
        public int SlowEmaPeriods { get; set; }

        [Output("Buys", PlotType = PlotType.Points, Color = Colors.Green, Thickness = 4)]
        public IndicatorDataSeries BuyDots { get; set; }

        public DateTime LastBuyTime { get; set; }

        protected override void Initialize()
        {
            _lastEvent = CrossoverEvent.None;
            _shortEma = Indicators.ExponentialMovingAverage(MarketSeries.Close, FastEmaPeriods);
            _longEma = Indicators.ExponentialMovingAverage(MarketSeries.Close, SlowEmaPeriods);
        }

        public override void Calculate(int index)
        {
            var shortEma = _shortEma.Result[index];
            var longEma = _longEma.Result[index];
            var low = MarketSeries.Low[index];
            var high = MarketSeries.High[index];

            if (shortEma > longEma && _lastEvent != CrossoverEvent.CrossUp)
            {
                _lastEvent = CrossoverEvent.CrossUp;
            }

            if (shortEma < longEma && _lastEvent != CrossoverEvent.CrossDown)
            {
                _lastEvent = CrossoverEvent.CrossDown;
            }

            if (low > shortEma && _lastEvent == CrossoverEvent.CrossUp)
            {
                _lastEvent = CrossoverEvent.Above;
                BuyDots[index] = DotUpY(low);
                LastBuyTime = MarketSeries.OpenTime.LastValue;
            }
        }

        private double DotUpY(double low, double offset = 1)
        {
            return low - offset * Symbol.PipSize;
        }

        private enum CrossoverEvent
        {
            None = 0,
            CrossUp,
            CrossDown,
            Above,
            Below
        }
    }
}
using System;
using System.Collections.Generic;
using System.Linq;
using cAlgo;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using cAlgo.Indicators;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class TestBot : Robot
    {
        private readonly Dictionary<string, CampaignInfo> _trades;
        private int _bar;
        private TestIndicator _indicator;

        public TestBot()
        {
            _trades = new Dictionary<string, CampaignInfo>();
        }

        [Parameter("EMA periods (fast)", DefaultValue = 8)]
        public int FastEmaPeriods { get; set; }

        [Parameter("EMA periods (slow)", DefaultValue = 13)]
        public int SlowEmaPeriods { get; set; }

        [Parameter("Trade risk %", DefaultValue = 1.0)]
        public double TradeRiskPercent { get; set; }

        [Parameter("Entry order high/low offset (pips)", DefaultValue = 1.0)]
        public double EntryOrderPipOffset { get; set; }

        [Parameter("Entry order stop limit range (pips)", DefaultValue = 1.0)]
        public double StopLimitRangePips { get; set; }

        protected override void OnStart()
        {
            _indicator = Indicators.GetIndicator<TestIndicator>(FastEmaPeriods, SlowEmaPeriods);
            Positions.Opened += OnPositionsOpened;
        }

        private void OnPositionsOpened(PositionOpenedEventArgs args)
        {
            var info = _trades[args.Position.Label];
            info.PositionOpenedBar = _bar;
        }

        protected override void OnBar()
        {
            foreach (var position in Positions)
            {
                FixedLifetime(position, _trades[position.Label]);
            }

            if (_indicator.BuyDots.Last(1) > 0 && Positions.Count == 0)
                PlaceEntryOrder(TradeType.Buy, MarketSeries.High.Last(1));

            _bar++;
        }

        protected override void OnStop()
        {
            foreach (var position in Positions)
            {
                ClosePosition(position);
            }
        }

        private void FixedLifetime(Position position, CampaignInfo info, int lifetime = 3)
        {
            if (!(info.PositionOpenedBar - _bar >= lifetime))
                return;
            Print("Closing position {0} ({1}) after {3} bar{4}", position.Id, position.Label, lifetime, lifetime > 1 ? "s" : string.Empty);
            ClosePosition(position);
        }

        private TradeResult PlaceEntryOrder(TradeType side, double targetPrice, int expiryInBars = 1)
        {
            var label = side + "-" + MarketSeries.OpenTime.LastValue.ToString("yyyyMMddhhmm");

            CampaignInfo info;
            if (_trades.TryGetValue(label, out info) && info.TradeResult.IsSuccessful)
                return info.TradeResult;

            var rangePips = (MarketSeries.High.Last(1) - MarketSeries.Low.Last(1)) / Symbol.PipSize;
            var stopLossPips = rangePips + 2 * (StopLimitRangePips + EntryOrderPipOffset);
            var valueAtRisk = Account.Balance * TradeRiskPercent / 100;
            const double scalingFactor = 10000;
            var volume = RoundDown(valueAtRisk * scalingFactor / stopLossPips);
            double? takeProfitPips = null;
            var interval = MarketSeries.OpenTime[1] - MarketSeries.OpenTime[0];
            var expiration = MarketSeries.OpenTime.LastValue.Add(TimeSpan.FromTicks(interval.Ticks * expiryInBars));
            const bool hasTrailingStop = true;
            var entryPrice = targetPrice + EntryOrderPipOffset * Symbol.PipSize;
            var result = PlaceStopLimitOrder(side, Symbol, volume, entryPrice, StopLimitRangePips, label, stopLossPips, takeProfitPips, expiration, "automated order",
            hasTrailingStop);

            _trades[label] = new CampaignInfo 
            {
                IslPips = stopLossPips,
                TradeResult = result
            };

            Print("Placed {0} at bar {1} ({2} {3}, entry={4}, stopLossPips={5}, expiration={6})", label, _bar, volume, Symbol, entryPrice, stopLossPips, expiration);

            return result;
        }

        private static double RoundDown(double n)
        {
            const double unit = 1000.0;
            return Math.Floor(n / unit) * unit;
        }

        internal struct CampaignInfo
        {
            public double IslPips { get; set; }
            public int PositionOpenedBar { get; set; }
            public TradeResult TradeResult { get; set; }
        }
    }
}

Cheers

09 Aug 2018, 09:56#4
Panagiotis Charalampousposts: 1186since: 13 Jan 2017

Hi bishbashbosh,

I had a look at the cBot and indicator but I cannot see any issue. Can you please explain to me why you think the output is garbage? What does the cBot do and what would you expect it to do instead?

Best Regards,

Panagiotis


Head of Community Management at cTrader
09 Aug 2018, 22:58#5
bishbashboshposts: 4since: 08 Aug 2018

Hi Pangiotis

Here's an example of what I am talking about - the bot is trading when/where there is clearly no green dot:

The intention of the code (and please correct me if you do not agree that this is what it is doing) is to place a buy order that is valid for one bar just above the high of an immediately preceding green dot bar, with an initial stop loss just below its low.

Cheers

10 Aug 2018, 09:39#6
Panagiotis Charalampousposts: 1186since: 13 Jan 2017

Hi bishbashbosh,

Your cBot places stop limit orders after the green dot. The stop limit orders will be executed when the target price is reached. This might happen some bars after the green dot. If there is a position that you cannot understand why it is there, you can send me backtesting parameters and a trade that you think is wrong and I will explain you why it is placed. 

Best Regards,

Panagiotis


Head of Community Management at cTrader
10 Aug 2018, 12:53RE:#7
bishbashboshposts: 4since: 08 Aug 2018

Hi Panagiotis

The backtest above was EURUSD hourly, as I recall. You can see the dates on the chart pic above - it’s the last day or two.

Like I say, the orders should be cancelling if they are not hit in one bar, as is hopefully clear from the code. So there should be no time when a trade enters on a bar that doesn’t follow a green dot. If the problem is in the order code, great - please point out where. But having stepped through it quite a few times trying to figure this out myself, it wasn’t my impression.

Thanks for your assistance.

Panagiotis Charalampous said:

Hi bishbashbosh,

Your cBot places stop limit orders after the green dot. The stop limit orders will be executed when the target price is reached. This might happen some bars after the green dot. If there is a position that you cannot understand why it is there, you can send me backtesting parameters and a trade that you think is wrong and I will explain you why it is placed. 

Best Regards,