Server Time Zone

Created at 28 Dec 2018, 10:30
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!
lec0456's avatar

lec0456

Joined 14.11.2012

Server Time Zone
28 Dec 2018, 10:30


I have been trying to convert the Server.Time to the proper time zone in order to detect the end of day used when agregating Daily open and close prices. 

Part of the year, roughly between the first week in November and the first week in March, the Time is UTC +2 after that it is UTC+3.

 

I was using a simple convertion:

        DateTimeOffset EESTDate(DateTime d)
        {
            return TimeZoneInfo.ConvertTimeBySystemTimeZoneId(d, "E. Europe Standard Time");
        }

However, the server is not using this zone exactly.  This zone goes to DST the Last Sunday of March at 2:00 AM and ends DST the Last Sunday of October at 3:00 AM

What time zone is being used by cTrader to agregate the daily open and close prices?

 

 


@lec0456
Replies

PanagiotisCharalampous
28 Dec 2018, 11:17

Hi lec0456,

A day changes at 22:00 UTC +0. 

Best Regards,

Panagiotis


@PanagiotisCharalampous

lec0456
28 Dec 2018, 12:04

Now it changes at 22:00 UTC but after the first week in March it changes to 21:00 UTC all the way until the first week in November.

You can see the behavior using the daily series: Backtest a year usng the indicator below.  It will show exactly when the the agregated daily price opens and closes.

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

namespace cAlgo.Indicators
{
    [Indicator(IsOverlay = true, AutoRescale = false, TimeZone = TimeZones.UTC)]
    public class myDailyOCHL : Indicator
    {
        [Parameter(DefaultValue = false)]
        public bool HideWeeklyOCHL { get; set; }

        [Parameter(DefaultValue = false)]
        public bool HideDailyOCHL { get; set; }

        [Output("DailyHigh", Thickness = 0, PlotType = PlotType.Points, Color = Colors.Black)]
        public IndicatorDataSeries DHigh { get; set; }

        [Output("DailyLow", Thickness = 0, PlotType = PlotType.Points, Color = Colors.Black)]
        public IndicatorDataSeries DLow { get; set; }

        [Output("DailyOpen", Thickness = 0, PlotType = PlotType.Points, Color = Colors.Black)]
        public IndicatorDataSeries DOpen { get; set; }

        [Output("DailyClose", Thickness = 0, PlotType = PlotType.Points, Color = Colors.Black)]
        public IndicatorDataSeries DClose { get; set; }

        [Output("WeeklyHigh", Thickness = 0, PlotType = PlotType.Points, Color = Colors.Black)]
        public IndicatorDataSeries WHigh { get; set; }

        [Output("WeeklyLow", Thickness = 0, PlotType = PlotType.Points, Color = Colors.Black)]
        public IndicatorDataSeries WLow { get; set; }

        [Output("WeeklyOpen", Thickness = 0, PlotType = PlotType.Points, Color = Colors.Black)]
        public IndicatorDataSeries WOpen { get; set; }

        [Output("WeeklyClose", Thickness = 0, PlotType = PlotType.Points, Color = Colors.Black)]
        public IndicatorDataSeries WClose { get; set; }

        private MarketSeries Daily;
        private MarketSeries Weekly;

        protected override void Initialize()
        {
            Daily = MarketData.GetSeries(TimeFrame.Daily);
            Weekly = MarketData.GetSeries(TimeFrame.Weekly);
        }

        public override void Calculate(int index)
        {
            if (index - 1 < 0)
                return;

            Colors DOCcolor;
            Colors WOCcolor;
            var weeklyindex = Weekly.OpenTime.GetIndexByTime(MarketSeries.OpenTime[index]);
            var dailyindex = Daily.OpenTime.GetIndexByTime(MarketSeries.OpenTime[index]);

            if (dailyindex > -1)
            {
                DHigh[index] = Daily.High[dailyindex];
                DLow[index] = Daily.Low[dailyindex];
                DOpen[index] = Daily.Open[dailyindex];
                DClose[index] = Daily.Close[dailyindex];

                if (!HideDailyOCHL)
                {
                    if (Daily.Open[dailyindex] < Daily.Close[dailyindex])
                        DOCcolor = Colors.Green;
                    else
                        //If a gain for the day use green
                        DOCcolor = Colors.Red;
                    //if a loss for the day use red
                    ChartObjects.DrawLine("DOpen" + index, index, DOpen[index], index + 1, DOpen[index], Colors.White, 1, LineStyle.DotsRare);
                    ChartObjects.DrawLine("DClose" + index, index, DClose[index], index + 1, DClose[index], DOCcolor, 1, LineStyle.DotsRare);
                    ChartObjects.DrawLine("DHigh" + index, index, DHigh[index], index + 1, DHigh[index], Colors.Orange, 1, LineStyle.DotsRare);
                    ChartObjects.DrawLine("DLow" + index, index, DLow[index], index + 1, DLow[index], Colors.Orange, 1, LineStyle.DotsRare);
                }
            }

            if (weeklyindex > -1)
            {
                WHigh[index] = Weekly.High[weeklyindex];
                WLow[index] = Weekly.Low[weeklyindex];
                WOpen[index] = Weekly.Open[weeklyindex];
                WClose[index] = Weekly.Close[weeklyindex];

                if (!HideWeeklyOCHL)
                {
                    if (Weekly.Open[weeklyindex] < Weekly.Close[weeklyindex])
                        WOCcolor = Colors.DarkGreen;
                    else
                        //If a gain for the week use blue
                        WOCcolor = Colors.Maroon;
                    //if a loss for the week use marroon
                    // Weekly Close is Blue if positive Marron if negetive
                    ChartObjects.DrawLine("WOpen" + index, index, WOpen[index], index + 1, WOpen[index], Colors.Yellow, 1, LineStyle.DotsVeryRare);
                    ChartObjects.DrawLine("wClose" + index, index, WClose[index], index + 1, WClose[index], WOCcolor, 1, LineStyle.DotsVeryRare);
                    ChartObjects.DrawLine("WHigh" + index, index, WHigh[index], index + 1, WHigh[index], Colors.Purple, 1, LineStyle.DotsRare);
                    ChartObjects.DrawLine("WLow" + index, index, WLow[index], index + 1, WLow[index], Colors.Purple, 1, LineStyle.DotsRare);
                }
            }
        }
    }
}

 


@lec0456

lec0456
28 Dec 2018, 12:12

Look at this chart

http://roboforex.ctrader.com/c/PKmPn

do you see where the horizontal lines stop before the horizontal line.  That is showing that the daily agregation closed at 21:00 UTC not not 22:00 on October 29, 2018.  The horizontal line is drawn at UTC 22:00.  So, it is not the start of the day.  


@lec0456

lec0456
28 Dec 2018, 12:13

RE:

lec0456 said:

Look at this chart

http://roboforex.ctrader.com/c/PKmPn

do you see where the horizontal lines stop before the horizontal line.  That is showing that the daily agregation closed at 21:00 UTC not not 22:00 on October 29, 2018.  The horizontal line is drawn at UTC 22:00.  So, it is not the start of the day.  

Sorry made a mistake, I meant to say:

do you see where the horizontal lines stop before the VERTICAL line.  That is showing that the daily agregation closed at 21:00 UTC not not 22:00 on October 29, 2018.  The VERTICAL line is drawn at UTC 22:00.  So, it is not the start of the day.  


@lec0456

PanagiotisCharalampous
28 Dec 2018, 12:55

Hi lec0456,

If you want to have a fixed time for day change throughout the year, the use EasternStandardTime. The change of the day takes place at 17:00 EST.

Best Regards,

Panagiotis


@PanagiotisCharalampous

lec0456
28 Dec 2018, 13:07

That won't work. I can not set the date upon which the candles are generated.  The daily candle is agregated according to the how your server establishes an open time and close time for the day.  That is the start of the trading day no matter what I set my time to.  There are time zone rules set by microsoft .net timezone object. I can get the rules but nothing I use is matching the rules you are currently using to agregate the daily data.


@lec0456

lec0456
28 Dec 2018, 13:10

RE:

lec0456 said:

That won't work. I can not set the date upon which the candles are generated.  The daily candle is agregated according to the how your server establishes an open time and close time for the day.  That is the start of the trading day no matter what I set my time to.  There are time zone rules set by microsoft .net timezone object. I can get the rules but nothing I use is matching the rules you are currently using to agregate the daily data.

I mean:

I can not set the TIME upon which the candles are generated. 


@lec0456

PanagiotisCharalampous
28 Dec 2018, 13:10

Hi lec0456,

This is the rule we are using. We agreggate the data at 17:00 EST.

Best Regards,

Panagiotis


@PanagiotisCharalampous

lec0456
28 Dec 2018, 13:18

Ah!!! that might be true!! Looks like the DST occurs the first week in november and march for EST like I was experiencing.

https://www.timeanddate.com/worldclock/usa/new-york

Thank you.


@lec0456

lec0456
29 Dec 2018, 06:23

ok, this I was able to match the behavior so that a 5pm or 17:00 EST marks a new trading day. If I convert the server time in UTC to Eastern Standard Time and add 7 hours, it is consistently following the open and close of the daily candles.

I used this function:

        DateTimeOffset TradeDate(DateTime d)
        {
            DateTimeOffset CurrentDateTime = DateTime.SpecifyKind(d, DateTimeKind.Utc);
            CurrentDateTime = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(CurrentDateTime, "Eastern Standard Time");
            CurrentDateTime = CurrentDateTime.AddHours(7);
            return CurrentDateTime;
        }

Basically, instead of subtracting 5 from utc during the winter and subtracting 4 during the summer, it returns the time as +2 during the winter and +3 during the summer. it now works consistently throughout the year.


@lec0456

bishbashbosh
22 Nov 2019, 20:31

NodaTime

Building on the above (thanks!) with NodaTIme, here are some extensions I made in order to take a UTC cTrader chart time and be able to filter based on the rollover all year round (i.e. especially in the period where USA and European DST are out-of-sync in spring and autumn (usually March and October)

using System;
using System.Collections.Generic;
using System.Linq;
using NodaTime;
using NodaTime.Text;

namespace CAlgoUtil
{
    public static class NodaTimeExtensions
    {
        private const string EasternStandardTime = "Eastern Standard Time";

        private static readonly TimeZoneInfo EasternStandardTimeZoneInfo =
            TimeZoneInfo.FindSystemTimeZoneById(EasternStandardTime);

        public static IEnumerable<LocalTime> ParseTimeString(this string value)
        {
            const string patternText = "H:mm";
            var localTimePattern = LocalTimePattern.CreateWithInvariantCulture(patternText);
            return value.Split(',', ' ', ';')
                .Where(s => !string.IsNullOrEmpty(s))
                .Select(s => s.Trim())
                .Select(s => localTimePattern.Parse(s))
                .Where(r => r.Success)
                .Select(r => r.Value);
        }

        public static bool Contains(this ICollection<LocalTime> localTimes, DateTime time,
            bool correctDaylightSavingMismatch = true)
        {
            var timeOfDay = time.GetServerTimeOfDayInLocalTz(correctDaylightSavingMismatch).LocalDateTime.TimeOfDay;
            return localTimes.Contains(timeOfDay);
        }

        public static ZonedDateTime GetServerTimeOfDayInLocalTz(this DateTime time,
            bool correctDaylightSavingMismatch = false)
        {
            var serverTime = time.ToServerOffsetDateTime();
            var tz = DateTimeZoneProviders.Bcl.GetSystemDefault();
            var serverTimeOfDayInLocalTz = serverTime.InZone(tz);
            var isEasternDaylightSavingTime = EasternStandardTimeZoneInfo.IsDaylightSavingTime(time);
            var hourCorrection = !correctDaylightSavingMismatch || serverTimeOfDayInLocalTz.IsDaylightSavingTime() == isEasternDaylightSavingTime ? 0 :
                isEasternDaylightSavingTime ? 1 : -1;
            return serverTimeOfDayInLocalTz.PlusHours(hourCorrection);
        }

        public static OffsetDateTime ToServerOffsetDateTime(this DateTime d)
        {
            DateTimeOffset dto = DateTime.SpecifyKind(d, DateTimeKind.Utc);
            var est = TimeZoneInfo.ConvertTimeBySystemTimeZoneId(dto, EasternStandardTime);
            var server = est.AddHours(7);
            var localDateTime = new LocalDateTime(
                server.Year, server.Month, server.Day, server.Hour, server.Minute, server.Second);
            return new OffsetDateTime(localDateTime, d.GetServerOffset());
        }

        private static Offset GetServerOffset(this DateTime d)
        {
            return EasternStandardTimeZoneInfo.IsDaylightSavingTime(d) ? Offset.FromHours(3) : Offset.FromHours(2);
        }
    }
}

If you're on, say, a 3h chart then the start of the European open bar moves from 8am to 7am for a few weeks in October and March. The above will handle this.

Here's an example indicator that can be used to mark said bar on a chart all year round, if you set its "Rollover" parameter to "8:00":

using System;
using System.Collections.Generic;
using cAlgo.API;
using CAlgoUtil;
using NodaTime;

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.FileSystem)]
    public class RolloverMarker : Indicator
    {
        private int _lastIndex = 0;
        private TimeSpan _offset;
        private Color _colour;
        private HashSet<LocalTime> _rolloverFilter;

        [Parameter(DefaultValue = 1, MinValue = 1)]
        public int Thickness { get; set; }

        [Parameter("Rollover", DefaultValue = "")]
        public string RolloverFilter { get; set; }

        [Parameter(DefaultValue = true)]
        public bool Sunday { get; set; }

        [Parameter(DefaultValue = true)]
        public bool Monday { get; set; }

        [Parameter(DefaultValue = true)]
        public bool Tuesday { get; set; }

        [Parameter(DefaultValue = true)]
        public bool Wednesday { get; set; }

        [Parameter(DefaultValue = true)]
        public bool Thursday { get; set; }

        [Parameter(DefaultValue = true)]
        public bool Friday { get; set; }

        protected override void Initialize()
        {
            _offset = TimeSpan.FromTicks(MarketSeries.TimeFrame.ToTimeSpan().Ticks / 2);
            _colour = Color.Pink;
            _rolloverFilter = new HashSet<LocalTime>(RolloverFilter.ParseTimeString());
        }

        public override void Calculate(int index)
        {
            if (index > _lastIndex && ShouldMarkBar(index))
            {
                MarkBar(index);
            }

            _lastIndex = index;
        }

        private bool ShouldMarkBar(int index)
        {
            var openTime = MarketSeries.OpenTime[index];
            return _rolloverFilter.Contains(openTime) && IsDayEnabled(openTime.DayOfWeek);
        }

        private bool IsDayEnabled(DayOfWeek day)
        {
            switch (day)
            {
                case DayOfWeek.Sunday:
                    return Sunday;
                case DayOfWeek.Monday:
                    return Monday;
                case DayOfWeek.Tuesday:
                    return Tuesday;
                case DayOfWeek.Wednesday:
                    return Wednesday;
                case DayOfWeek.Thursday:
                    return Thursday;
                case DayOfWeek.Friday:
                    return Friday;
                default:
                    return false;
            }
        }

        private void MarkBar(int index)
        {
            var ot = MarketSeries.OpenTime[index];
            var time = ot - _offset;
            var obj = Chart.DrawVerticalLine("vl_" + index, time, _colour, Thickness, LineStyle.Solid);
            obj.ZIndex = 1000;
        }
    }
}

Required extension method:

using System;
using System.Collections.Generic;
using cAlgo.API;

namespace CAlgoUtil
{
    public static class TimeFrameExtensions
    {
        private static readonly Dictionary<string, TimeSpan> TimeFrameTicks = new Dictionary<string, TimeSpan>
        {
            {"Minute", TimeSpan.FromMinutes(1)},
            {"Minute2", TimeSpan.FromMinutes(2)},
            {"Minute3", TimeSpan.FromMinutes(3)},
            {"Minute4", TimeSpan.FromMinutes(4)},
            {"Minute5", TimeSpan.FromMinutes(5)},
            {"Minute6", TimeSpan.FromMinutes(6)},
            {"Minute7", TimeSpan.FromMinutes(7)},
            {"Minute8", TimeSpan.FromMinutes(8)},
            {"Minute9", TimeSpan.FromMinutes(8)},
            {"Minute10", TimeSpan.FromMinutes(10)},
            {"Minute15", TimeSpan.FromMinutes(15)},
            {"Minute20", TimeSpan.FromMinutes(20)},
            {"Minute30", TimeSpan.FromMinutes(30)},
            {"Minute45", TimeSpan.FromMinutes(45)},
            {"Hour", TimeSpan.FromHours(1)},
            {"Hour2", TimeSpan.FromHours(2)},
            {"Hour3", TimeSpan.FromHours(3)},
            {"Hour4", TimeSpan.FromHours(4)},
            {"Hour6", TimeSpan.FromHours(6)},
            {"Hour8", TimeSpan.FromHours(8)},
            {"Hour12", TimeSpan.FromHours(12)},
            {"Daily", TimeSpan.FromDays(1)},
            {"Day2", TimeSpan.FromDays(1)},
            {"Day3", TimeSpan.FromDays(3)},
            {"Weekly", TimeSpan.FromDays(7)},
            {"Monthly", TimeSpan.FromDays(30.5)}
        };

        public static TimeSpan ToTimeSpan(this TimeFrame timeFrame)
        {
            TimeSpan value;
            var tf = timeFrame.ToString();
            if (TimeFrameTicks.TryGetValue(tf, out value))
                return value;
            throw new ArgumentException(string.Format("Cannot convert TimeFrame.{0}", tf), "timeFrame");
        }
    }
}

 


@bishbashbosh