Replies

bishbashbosh
13 Jul 2023, 21:04

RE:

jim.tollan said:

Hi BBB - it would appear that v1.0.8 is now available (I just updated via my solution). This event is a timely addition and will definitely make some of my logic a lot less convoluted.

thanks cTrader team!!

hurrah!


@bishbashbosh

bishbashbosh
21 Feb 2023, 17:03

solved

as you were - just found another forum post with the same problem from last year 

Never mind, figured out there's a property that can be set which bypasses the build.

Add the below snippet to your library .csproj file:

<PropertyGroup>
  <AlgoBuild>False</AlgoBuild>
</PropertyGroup>

Hope that helps someone else.


@bishbashbosh

bishbashbosh
21 Feb 2023, 15:04

macOS version

that's interesting - what's the stack? and it seems like quite a task to take on - are you planning to market it?

for cTrader themselves, now that .net MAUI is out, they could presumably rework their (WPF?) codebase into that and achieve native macOS support.


@bishbashbosh

bishbashbosh
16 Feb 2023, 16:25

co-sign

co-sign on this as well - have submitted info as requested via the dialog

I am running the latest 4.6.3 on arm64 macOS via Parallels. I deduce it's not a problem with the VM, as I have a couple of VS 2022 instances open and they and other windows are all fine.

System Info from Windows:
Processor    Apple Silicon   3.20 GHz  (4 processors)
Installed RAM    12.0 GB
System type    64-bit operating system, ARM-based processor
 


@bishbashbosh

bishbashbosh
16 Dec 2022, 15:30

Ok, seems extra memory wasn't it - just got the error dialog again. Strange thing is, it says please close cTrader, but you can just close the dialog and carry on.


@bishbashbosh

bishbashbosh
16 Dec 2022, 10:21

I have some helper bots for order-entry, but these are only short-lived, so I don't think they are the issue here.

Have upped the VM memory setting from 8 GB (auto) to 12 GB to see if this helps things.


@bishbashbosh

bishbashbosh
08 Aug 2022, 12:28

Hello!

OK, thanks I was not aware of this setting - it was set to "Embedded".

All sorted now.

Many thanks.


@bishbashbosh

bishbashbosh
20 May 2020, 12:14 ( Updated at: 21 Dec 2023, 09:22 )

Yep, co-sign on this. Just copying the TradingView fork functionality would be a great improvement; so, adding Schiff and Modified Schiff forks (this is simply changing the location of the root pivot, which should be super simple to do) and then adding the ability to add in more parallels at user-defined percentage offset values.

example of Schiff forks on GBPUSD monthly powered by TradingView on investing.com


@bishbashbosh

bishbashbosh
05 Feb 2020, 11:40

RE: Thanks a lot

driftingprogrammer said:

Thanks so much for this, this is such an integral need for a robot that it should be built into the API.

 

Jiri said:

Hi, there isn't built-in function called on each bar except from the market series of the chart that you can override. However, you can write your own function for each series.

using cAlgo.API;
using cAlgo.API.Internals;

namespace cAlgo
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class OnBar : Robot
    {
        private MarketSeries seriesMinute1, seriesMinute15, seriesMinute30;
        private int lastIndexMinute1, lastIndexMinute15, lastIndexMinute30;

        protected override void OnStart()
        {
            seriesMinute1 = MarketData.GetSeries(TimeFrame.Minute);
            seriesMinute15 = MarketData.GetSeries(TimeFrame.Minute15);
            seriesMinute30 = MarketData.GetSeries(TimeFrame.Minute15);
        }

        protected override void OnTick()
        {
            int currentIndexMinute1 = seriesMinute1.Open.Count - 1;
            int currentIndexMinute15 = seriesMinute15.Open.Count - 1;
            int currentIndexMinute30 = seriesMinute30.Open.Count - 1;

            if (currentIndexMinute1 > lastIndexMinute1)
            {
                OnBarMinute1(currentIndexMinute1);
                lastIndexMinute1 = currentIndexMinute1;
            }

            if (currentIndexMinute15 > lastIndexMinute15)
            {
                OnBarMinute15(currentIndexMinute15);
                lastIndexMinute15 = currentIndexMinute15;
            }

            if (currentIndexMinute30 > lastIndexMinute30)
            {
                OnBarMinute30(currentIndexMinute30);
                lastIndexMinute30 = currentIndexMinute30;
            }
        }

        private void OnBarMinute1(int index)
        {
            // Called on each new bar of Minute1 dataseries
        }

        private void OnBarMinute15(int index)
        {
            // Called on each new bar of Minute15 dataseries
        }

        private void OnBarMinute30(int index)
        {
            // Called on each new bar of Minute30 dataseries
        }
    }
}

 

 

This would be a good use case for Reactive Extensions..

EDIT: btw, good news for you - it IS now part of the API - checkout the new Bars interface, specifically the BarOpened event.


@bishbashbosh

bishbashbosh
29 Jan 2020, 20:17

You can create a cBot and then override the OnBar method and then within that check if MarketSeries.OpenTime.LastValue is the time you want; if it is, submit your order. 

using cAlgo.API;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.EasternStandardTime, AccessRights = AccessRights.None)]
    public class OrderAtFixedTime : Robot
    {
        [Parameter(DefaultValue = 9)]
        public int Hour { get; set; }

        [Parameter(DefaultValue = 30)]
        public int Minute { get; set; }

        [Parameter(DefaultValue = TradeType.Buy)]
        public TradeType Side { get; set; }

        [Parameter]
        public double Volume { get; set; }

        [Parameter(DefaultValue = 10, MinValue = 0)]
        public double TriggerPipOffset { get; set; }

        [Parameter(DefaultValue = 5, MinValue = 0)]
        public double StopLimitRangePips { get; set; }

        protected override void OnBar()
        {
            var openTime = MarketSeries.OpenTime.LastValue;
            if (openTime.Hour != Hour || openTime.Minute != Minute) 
                return;

            var targetPrice = MarketSeries.Close.LastValue + (Side == TradeType.Buy ? 1 : -1) * TriggerPipOffset * Symbol.PipSize;
            var label = "AUTO_" + SymbolName + "_" + openTime.ToString("s");
            PlaceStopLimitOrder(Side, SymbolName, Volume, targetPrice, StopLimitRangePips, label);
            Stop();
        }
    }
}

 


@bishbashbosh

bishbashbosh
21 Jan 2020, 12:38 ( Updated at: 21 Dec 2023, 09:21 )

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

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class TimeTillCloseDemo : Robot
    {
        private readonly Dictionary<DateTime, TimeSpan> _timesTillClose;
        private static readonly TimeSpan Hours12 = TimeSpan.FromHours(12);

        public TimeTillCloseDemo()
        {
            _timesTillClose = new Dictionary<DateTime, TimeSpan>();
        }

        protected override void OnTick()
        {
            var date = MarketSeries.OpenTime.LastValue.Date;
            var timeTillClose = Symbol.MarketHours.TimeTillClose();
            if (timeTillClose < Hours12)
            {
                _timesTillClose[date] = timeTillClose;
            }
        }

        protected override void OnStop()
        {
            const int top = 10;
            Print("Top {0} TimeTillClose values upon last tick of the day follow...", top);
            var ordered = _timesTillClose.OrderByDescending(kvp => kvp.Value);
            foreach (var keyValuePair in ordered.Take(top))
            {
                Print("{0:t} on {1:dddd, d MMMM}", keyValuePair.Value, keyValuePair.Key);
            }
        }
    }
}

Running against the entirety of 2019 on EURUSD 3h gives for me:


@bishbashbosh

bishbashbosh
03 Jan 2020, 13:08

Absolutely fabulous

...news that multi-symbol back-testing has been released - look forward to testing that. Second the comment above that it would be incredibly useful to be able to see a chart per symbol traded - perhaps something to stick on the dev list, or should we create another suggestion?

Re. the economic calendar - is there an API for that, so that we can access this data from within bots? Just had a quick search and couldn't find anything.

Cheers and happy new year!


@bishbashbosh

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

bishbashbosh
13 Nov 2019, 11:00

Hi Panagiotis,

My code was creating an infinite loop; as per update above, this is not a fault of cTrader, but a feature of the framework that StackOverflowException cannot be caught and terminates the process by default.

Cheers


@bishbashbosh

bishbashbosh
24 Oct 2019, 15:53

I'm noticing slowly rising memory usage with cT 3.6 - been open for about 7h and now up to 1.6GB (over 2GB after I created a dump file - happy to upload somewhere if that would be a help).


@bishbashbosh

bishbashbosh
24 Oct 2019, 12:49

Co-sign - the ability to change timeframe on all - or a subset of - charts in a workspace would be great.

The tag says "started" - is this coming in 3.7 also?


@bishbashbosh

bishbashbosh
24 Oct 2019, 12:19 ( Updated at: 15 Jan 2024, 14:51 )

Updated URL for this ticket: [https://ctrader.com/forum/suggestions/20580]

I am wondering about this as well - has anyone employed any testing strategies that work well? It would be great to be able to e.g. capture a set of market data from within cTrader that demonstrates a bug in an indicator/robot and generate a test project from it.


@bishbashbosh

bishbashbosh
23 Oct 2019, 15:21

RE:

afhacker said:

Put your code on a separate class library project and compile it with Visual Studio, then reference the compiled library to your indicator/cBot.

That's how you can use any of the new C# features but if you put your code inside indicator/cBot project then for compiling cTrader compiler will be used which will fail as it doesn't support the new features.

I see. So the choice is either all-in-one solution or new C# - gotcha.


@bishbashbosh

bishbashbosh
15 Oct 2019, 21:55 ( Updated at: 21 Dec 2023, 09:21 )

Ping on this one - the following build output is on cT 3.6b, compiling in VS 2019 Community:


@bishbashbosh

bishbashbosh
15 Oct 2019, 15:22

Have all you guys not heard of Parallels? I run cTrader exclusively under Parallels for MacOS and it works fine, performance is great.

Personally, I would rather see more features added to the .net version of cTrader than have Spotware have to support a whole other codebase.

Who knows, in the future we may be able to run .net desktop apps natively on macOS anyway, now that Microsoft has started embracing Open Source Software more.

 

EDIT: I'm sure the guys at SW are already aware of it, but one route to do this would be using AvaloniaUI - this would probably mean a more or less complete rewrite tho, so don't hold your breath..


@bishbashbosh