System Timer and Stopwatch Support

Created at 17 Dec 2018, 20:53
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!
WE

westend.trading

Joined 12.08.2018

System Timer and Stopwatch Support
17 Dec 2018, 20:53


Dear Spotware-Team,

a great but missing feature in my opinion is the support of a proper system time reference which also works in backtest.
Using Stopwatch and Timer events will not work in backtest but they are quite fundamental features for many operations.

The important thing is to have precise reference source. It should not be too much of an effort in my humble opinion.

Thanks in advance.

Ben

 


@westend.trading
Replies

PanagiotisCharalampous
18 Dec 2018, 09:39

Hi Ben,

Thank you for posting in our forum and for sharing yor suggestion. Please note that cTrader Automate API features its own Timer class which works in backtesting as well.

Best Regards,

Panagiotis


@PanagiotisCharalampous

westend.trading
19 Dec 2018, 20:53

Hi Panagiotis,

thank you for the quick response!
Can I assume it to run precisely in millisecond range?
The signal processing I intent to implement requires a very reliable reference clock. In realtime mode, this is no problem but results in backtest seem to be unrealistic.

How can I get the server timestamp of incoming ticks in backtest and normal operation mode?
Is it possible at all?

Best regards,

Ben


@westend.trading

PanagiotisCharalampous
20 Dec 2018, 09:44

Hi Ben,

Yes it will be accurate, you can verify it with the following experiment

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.UTC, AccessRights = AccessRights.None)]
    public class NewcBot : Robot
    {
        [Parameter(DefaultValue = 0.0)]
        public double Parameter { get; set; }

        protected override void OnStart()
        {
            Timer.Start(new TimeSpan(10000));
            Timer.TimerTick += OnTimerTimerTick;
        }

        void OnTimerTimerTick()
        {
            Print(Server.Time.Hour + ":" + Server.Time.Minute + ":" + Server.Time.Second + ":" + Server.Time.Millisecond);
        }

        protected override void OnTick()
        {

        }

        protected override void OnStop()
        {
            // Put your deinitialization logic here
        }
    }
}

Regarding the timestamp, check here how you can get the timestamp in C#.

Best Regards,

Panagiotis


@PanagiotisCharalampous

westend.trading
04 Jan 2019, 22:03

Hi Panagiotis,

thanks for the hint.

Server.Time

actually solves the issue. And the Timer class is working precisely as well.

Is it possible to acquire the specific timestamps of past price ticks as well when creating an indicator or does this only work for a robot (including backtesting)?

Thank you again.

Best regards,

Benjamin


@westend.trading

PanagiotisCharalampous
07 Jan 2019, 09:36

Hi Ben,

An indicator's Calculate method is called only on each bar for past bars and on each tick for the current bar. So using an indicator you can obtain such information only for the current bar. So it is not possible to acquire this information without using backtesting.

Best Regards,

Panagiotis

 


@PanagiotisCharalampous

westend.trading
09 Jan 2019, 19:51

Hi Panagiotis,

thank you again. This is what I expected. Will find a workaround for this.

Nonetheless I can run a robot that e.g. calculates time between incoming ticks and processes this data somehow. This works for simple backtesting precisely as far as I can verify. But runnning the same robot in Optimization mode (using same parameter and a dummy value for optimization) fails. The robot does not open any position in optimization mode. In backtest it works fine.

Can you please comment on that?

Best regards,

Ben


@westend.trading

PanagiotisCharalampous
10 Jan 2019, 17:04

Hi Ben,

I will need to have the robot to comment on this. Can you post it?

Best Regards,

Panagiotis


@PanagiotisCharalampous

westend.trading
10 Jan 2019, 22:11 ( Updated at: 21 Dec 2023, 09:21 )

Hi Panagiotis,

I was just preparing the code to post (it is huge and I was removing all parts not related to the issue). Funny thing is: the stripped down version works in optimization mode.
Only non-relevant parts have been removed.

Backtest looks like this:
Screenshot Backtest

Optimization mode (same parameter, modifying one value only) -no output:

Core feature is a incoming tick data histogram.

Weird.

Best regards,

Ben


@westend.trading

PanagiotisCharalampous
11 Jan 2019, 10:24

Hi Ben,

So probably the problem in the non relevant parts :)

Best Regards,

Panagiotis


@PanagiotisCharalampous

westend.trading
14 Jan 2019, 21:55

Hi Panagiotis,

please find below a code stub with the described behaviour.

To reproduce problem:

  1. Run Backtest with default values, Tickdata from server (accurate)
  2. Run Optimization with above parameter, and optimize e.g. only Stoploss 5 to 9 with stepsize 1
  3. Backtest produces output with any parameter for stoploss from 5 to 9
  4. Optimization passes have zero trades
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
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 UITest : Robot
    {
        [Parameter(DefaultValue = 60)]
        public double Samples { get; set; }

        [Parameter(DefaultValue = 15)]
        public int HistogramBins { get; set; }

        [Parameter(DefaultValue = 20)]
        public int PeakMinHeight { get; set; }

        [Parameter("Lifetime", DefaultValue = 3)]
        public double Lifetime { get; set; }
        [Parameter("Stoploss", DefaultValue = 5)]
        public double Stoploss { get; set; }
        [Parameter("Hedge Size", DefaultValue = 7)]
        public double Hedgesize { get; set; }
        [Parameter("Trigger When Gaining", DefaultValue = 5)]
        public double TriggerWhenGaining { get; set; }
        [Parameter("Trailing Stop Loss Distance", DefaultValue = 2)]
        public double TrailingStopLossDistance { get; set; }

        long ticks_prev = 0;
        long ticks_act = 0;
        int ticks_neg = 0;
        int ticks_pos = 0;
        double bid = 0;
        double bid_last = 0;
        bool first = true;

        //Stopwatch s;
        double tick_per_min = 0;
        double last_val = 0;
        double delta_t_max = 0;
        double delta_t_min = 0;
        double delta_t_median = 0;
        List<double> delta_t = new List<double>();
        int tick_temp = -1;
        bool update = false;
        int h = 0;
        int negative_ticks = 0;
        int positive_ticks = 0;

        protected override void OnStart()
        {
            Positions.Opened += PositionsOnOpened;
        }


        protected override void OnBar()
        {
            tick_per_min = (double)tick_temp;
            negative_ticks = ticks_neg;
            positive_ticks = ticks_pos;
            ticks_pos = 0;
            ticks_neg = 0;
            tick_temp = 0;
        }

        protected override void OnTick()
        {
            int peaks = 0;

            checkOrder();
            bid = Symbol.Bid;
            ticks_act = Server.Time.Ticks;
            if (first)
            {
                ticks_prev = ticks_act;
                bid_last = bid;
                first = false;
                return;
            }
            double change = (bid - bid_last);
            //double sign = 1;
            if (change > 0)
            {
                ticks_pos++;
            }
            else
            {
                ticks_neg++;
                // sign = -1;
            }

            bid_last = bid;
            tick_temp++;
            double delta_t_tick = ((new TimeSpan(ticks_act - ticks_prev)).Milliseconds);
            ticks_prev = ticks_act;
            delta_t.Add(delta_t_tick);
            delta_t_max = delta_t.Max();
            delta_t_min = delta_t.Min();
            if (delta_t.Count > Samples)
            {
                delta_t.RemoveAt(0);
                double[] ta = delta_t.ToArray();

                string hist = "";

                double binsize = ta.Max() / ((double)HistogramBins);
                int[] histogram = new int[HistogramBins + 1];
                for (int i = 0; i < ta.Length; i++)
                {
                    int index = (int)Math.Floor(ta[i] / binsize);
                    if (index >= histogram.Length)
                    {
                        Print("{0} {1} {2}", index, ta[i], binsize);
                    }
                    else
                    {
                        histogram[index]++;
                    }
                }

                for (int i = 0; i < histogram.Length; i++)
                {
                    string xt = "";
                    for (int j = 0; j < histogram[i]; j++)
                    {
                        if (histogram[i] >= PeakMinHeight)
                        {
                            peaks++;
                        }
                        xt += "X";
                    }
                    hist += xt + "\r\n";
                }
                Chart.DrawStaticText("hist", hist, VerticalAlignment.Top, HorizontalAlignment.Right, Color.Green);
            }

            last_val = tick_per_min;
            Chart.DrawStaticText("tpm", String.Format("Tick/min: {0:0.0}\r\nHist Items {1}", tick_per_min, HistogramBins), VerticalAlignment.Top, HorizontalAlignment.Left, Color.Red);
            if (peaks > 0)
            {
                int d = positive_ticks - negative_ticks;
                Color c = Color.AliceBlue;
                if (PendingOrders.Count == 0 && Positions.Count == 0)
                {
                    ExecuteOrder();
                }
            }
        }

        private bool IsBusyTime()
        {
            if (Server.Time.TimeOfDay > new TimeSpan(5, 45, 0) && Server.Time.TimeOfDay < new TimeSpan(19, 45, 0))
            {
                return true;
            }
            else
            {
                return false;
            }
        }

        private void PositionsOnOpened(PositionOpenedEventArgs args)
        {
            if (PendingOrders.Count == 1)
            {
                PendingOrders[0].Cancel();
            }
            _highestGain = Positions[0].Pips;
            _isTrailing = false;
            Print("Position Opened. Gain: {0}", _highestGain);
        }

        double profit = 0;
        double last_profit = 0;
        private void ExecuteOrder()
        {
            profit = 0;
            last_profit = 0;
            var volumeInUnits = Symbol.QuantityToVolumeInUnits(0.1);
            var result = PlaceStopOrder(TradeType.Buy, Symbol, volumeInUnits, Symbol.Ask + Hedgesize * Symbol.PipSize, "UP", Stoploss, null, Server.Time.AddMinutes(Lifetime), "", false);
            result = PlaceStopOrder(TradeType.Sell, Symbol, volumeInUnits, Symbol.Bid - Hedgesize * Symbol.PipSize, "DOWN", Stoploss, null, Server.Time.AddMinutes(Lifetime), "", false);
            if (result.Error == ErrorCode.NoMoney)
                Stop();
        }

        private double _highestGain;
        private bool _isTrailing;

        void checkOrder()
        {
            if (Positions.Count == 1)
            {
                //If the trigger is reached, the robot starts trailing
                if (!_isTrailing && Positions[0].Pips >= TriggerWhenGaining)
                {
                    _isTrailing = true;
                }
                //If the cBot is trailing and the profit in pips is at the highest level, we need to readjust the stop loss
                if (_isTrailing && _highestGain < Positions[0].Pips)
                {
                    //Based on the position's direction, we calculate the new stop loss price and we modify the position
                    if (Positions[0].TradeType == TradeType.Buy)
                    {
                        var newSLprice = Math.Round(Symbol.Bid - TrailingStopLossDistance * Symbol.PipSize, Symbol.Digits);
                        //var newSLprice = Symbol.Ask - (Symbol.PipSize * TrailingStopLossDistance);
                        if (newSLprice > Positions[0].StopLoss)
                        {
                            ModifyPosition(Positions[0], newSLprice, null);
                        }
                    }
                    else
                    {
                        var newSLprice = Math.Round(Symbol.Ask + TrailingStopLossDistance * Symbol.PipSize, Symbol.Digits);
                        //var newSLprice = Symbol.Bid + (Symbol.PipSize * TrailingStopLossDistance);
                        if (newSLprice < Positions[0].StopLoss)
                        {
                            ModifyPosition(Positions[0], newSLprice, null);
                        }
                    }
                    //We reset the highest gain
                    _highestGain = Positions[0].Pips;
                }
            }

        }
    }
}

Best regards,

Benjamin

 


@westend.trading

PanagiotisCharalampous
15 Jan 2019, 09:41 ( Updated at: 21 Dec 2023, 09:21 )

Hi Benjamin,

If you check the log of your optimization, you will see the reason why this happens

This happens because you use Chart.DrawStaticText() function. Do you need this function in backtesting?

Best Regards,

Panagiotis


@PanagiotisCharalampous

westend.trading
15 Jan 2019, 21:43

Hi Panagiotis,

this is the place I did not look. Wasn't really expecting an exception.

But, yes, I need(ed) the text in backtesting. There is no other option to output this kind of data "live". I admit it is not the smoothest way.

Is there a property which allows to know whether a robot is called by the optimizer?
Like: IsBacktest, IsOptimizer or so?

This would be my last question in this thread.

Thanks a lot!

Best regards,

Benjamin


@westend.trading

PanagiotisCharalampous
16 Jan 2019, 09:42

Hi Benjamin,

You can use IsBacktesting to check if the cBot is in backtesting. This parameter will be true both in backtesting and optimization. If you need this function in backtesting as well, then you can just optimize a copy of the cBot that will not have these functions.

Best Regards,

Panagiotis

 


@PanagiotisCharalampous