Replies

bishbashbosh
03 Oct 2019, 19:51

Hi Panagiotis,

How is the dev effort coming?

Best regards,

bbb


@bishbashbosh

bishbashbosh
20 Sep 2019, 14:51

This would be helpful for me as I have several indicators that encode setups which when matched set a value in the output series above or below the bar in order to display a dot for the user, so that they know the setup has hit on the bar.

Realise that this is a slight hack of the functionality - probably wasn't designed with this scenario in mind. But it would be great to be able to exclude such indicators from market snapshot tho as they are making it noisy. It could be implemented e.g. by an additional boolean property in the Indicator attribute.

If you have any other ideas of how to work around this, do please suggest.. (n.b. tried earlier adding text above/below bar, but when there were a lot of such markings the memory usage of cT appeared to climb alarmingly over time - using dots seems to be much more efficient)


@bishbashbosh

bishbashbosh
06 Sep 2019, 17:32

Excellent. Thank you very much.


@bishbashbosh

bishbashbosh
04 Jun 2019, 15:19

+1 for VS 2019 support - Community edition supports extensions, so no barrier..


@bishbashbosh

bishbashbosh
20 May 2019, 10:15

Hi Panagiotis,

Yes, when I increase the value of the parameter limitPips, the orders start filling.

Best regards.


@bishbashbosh

bishbashbosh
17 May 2019, 14:59

RE:

Panagiotis Charalampous said:

We managed to reproduce and we are investigating :)  we will reply!

 

Hi Panagiotis,

Good to know it's not just me then! 

Will await the outcome of your investigation with interest.

Best regards.


@bishbashbosh

bishbashbosh
17 May 2019, 09:47

BTW, when I was running the above code using genuine signal code - i.e. so the buys/sells would trigger at the same bar each time, rather than randomly as above - the orders would cancel at exactly the same time, every time.

This leads me to suspect that perhaps the orders were about to fill, but got cancelled instead for some reason. Could be something to do with my demo account or the order settings or..?

In the absence of anyone from cTrader replying, I would be very interested if anyone else runs the above code and does/does not see the same behaviour.


@bishbashbosh

bishbashbosh
20 Aug 2018, 14:14 ( Updated at: 21 Dec 2023, 09:20 )

Actually, spoke too soon - still seeing lots of "incomplete bar" in the logs.


@bishbashbosh

bishbashbosh
20 Aug 2018, 13:12

Ok, to answer my own question here - it looks like the above approach works, but only if you have at least one [Output] parameter that has a value set for every period.

Btw, I tested this by creating a modified copy of the indicator with a single [Output] parameter that sets a value for every value of index - and even when I did this, I still needed the Dictionary, as the values I was getting back from it using .Last(1) did not correspond to the correct bar!

:laughing emoji:


@bishbashbosh

bishbashbosh
20 Aug 2018, 10:10 ( Updated at: 21 Dec 2023, 09:20 )

RE: RE:

Further testing...

I appears that attempting to use BuyDots plain does not work, perhaps because it only intermittently contains a value that is not double.NaN - IndicatorDataSeries.LastValue etc. seem to do an !IsNaN on the series before calculating any look-back. Is this a correct diagnosis?

Moving on, I have developed the code further and am now using a custom method on the indicator to retrieve values from a Dictionary instead.

I notice that Calculate on my indicator is being called from the bot for a given bar/index sometimes correctly at the at the start of the next bar, sometimes at the start of the bar in question (so OHLC are all equal) and sometimes both, so it gets it right in the end.

What is the logic behind this? It works perfectly when I just place the indicator on a chart, but from the bot... not.

        protected override void OnBar()
        {
            AdjustStops();

            // needed or else GetResult returns 0 every time
            var buyLast = _indicator.BuyDots.LastValue;

            var openTimeLast = MarketSeries.OpenTime.Last(1);
            var resultLast = _indicator.GetResult(openTimeLast);

            if (resultLast > 0)
                Print("openTime: {0}; result: {1}", openTimeLast, resultLast);

            if (resultLast.IsBuy())
                PlaceEntryOrder(TradeType.Buy, MarketSeries.High.Last(1));

            if (resultLast.IsSell())
                PlaceEntryOrder(TradeType.Sell, MarketSeries.Low.Last(1));

            _bar++;
        }

Indicator:


        private readonly Dictionary<DateTime, Patterns> _result;

        public Patterns GetResult(DateTime time)
        {
            Patterns value;
            return _result.TryGetValue(time, out value) ? value : Patterns.None;
        }

 

        private bool CalledAtStartOfBar(int index)
        {
            return Math.Abs(MarketSeries.Open[index] - MarketSeries.High[index]) < double.Epsilon &&
                   Math.Abs(MarketSeries.Low[index] - MarketSeries.Close[index]) < double.Epsilon &&
                   Math.Abs(MarketSeries.Open[index] - MarketSeries.Close[index]) < double.Epsilon;
        }

        public override void Calculate(int index)
        {
            var time = MarketSeries.OpenTime[index];

            if (CalledAtStartOfBar(index))
            {
                Print("[{0}] {1}: incomplete bar ({2}, {3}, {4}, {5})", index, time, MarketSeries.Open[index], MarketSeries.High[index], MarketSeries.Low[index], MarketSeries.Close[index]);
                return;
            }

            var matches = Matches(index);
            if (matches > Patterns.None)
            {
                _result[time] = matches;
                Print("[{0}] {1}: {2} ({3}, {4}, {5}, {6})", index, time, matches, MarketSeries.Open[index], MarketSeries.High[index], MarketSeries.Low[index], MarketSeries.Close[index]);

            }
        }


What I see in the log - the log output is always the same, suggesting that it is a logical problem and not e.g. a timing issue:

Hard to diagnose this without understanding cTrader internal logic fully - is my pattern of trying to access the output of the indicator via a method that is not an [Output] property inherently doomed to failure?

I want to be able to dual-use the indicator - it works fine as is placed on a chart, but I don't want to add any more [Output] properties that will mess that up, hence adding a separate method that gets the output in a form more usable by the bot.


@bishbashbosh

bishbashbosh
10 Aug 2018, 12:53

RE:

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,

 

 


@bishbashbosh

bishbashbosh
09 Aug 2018, 22:58 ( Updated at: 21 Dec 2023, 09:20 )

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


@bishbashbosh

bishbashbosh
09 Aug 2018, 08:08

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


@bishbashbosh