Topics

Forum Topics not found

Replies

amusleh
29 Mar 2021, 11:45

I changed the lists to IndicatorDataSeries and now the results are matching, the indicator might be changed so please modify it if something is changed related to indicator formula.

using cAlgo.API;
using cAlgo.API.Indicators;
using System;
using System.Linq;

namespace cAlgo
{
    [Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class ConnorsCRSI11 : Indicator
    {
        private IndicatorDataSeries listOfAvgGain;
        private IndicatorDataSeries listOfAvgLoss;
        private IndicatorDataSeries listOfGain;
        private IndicatorDataSeries listOfLoss;
        private IndicatorDataSeries listOfStreaks;

        [Output("Main", Color = Colors.Orange, IsHistogram = true, PlotType = PlotType.Histogram)]
        public IndicatorDataSeries Result { get; set; }

        [Parameter(DefaultValue = 3.0)]
        public int rsiPeriods { get; set; }

        [Parameter(DefaultValue = 2.0)]
        public int streakPeriods { get; set; }

        [Parameter(DefaultValue = 100.0)]
        public int rocPeriods { get; set; }

        public RelativeStrengthIndex rsi;
        public int streak;
        public double avgGain1;
        public double avgLoss1;
        public double avgGain;
        public double avgLoss;

        public int startIndex;
        public double percentRank = 0.0;

        protected override void Initialize()
        {
            listOfAvgGain = CreateDataSeries();
            listOfAvgLoss = CreateDataSeries();
            listOfGain = CreateDataSeries();
            listOfLoss = CreateDataSeries();
            listOfStreaks = CreateDataSeries();

            rsi = Indicators.RelativeStrengthIndex(Bars.ClosePrices, rsiPeriods);
            startIndex = 1;
        }

        public override void Calculate(int index)
        {
            if (index <= rocPeriods) return;

            UpDownStreak(streakPeriods, index);
            var rsiStreakValue = GetRsiStreak(streakPeriods, index);
            Roc(rocPeriods, index);

            Result[index] = (rsiStreakValue + rsi.Result[index] + percentRank) / 3;
        }

        // crsi calculations
        public void Roc(double periods, int index)
        {
            var countRocDn = 0;
            var prevDayRoc = 0.0;
            var todayRoc = (Bars.ClosePrices[index] - Bars.ClosePrices[index - 1]) / Bars.ClosePrices[index - 1];
            for (int i = 1; i <= periods; i++)
            {
                prevDayRoc = (Bars.ClosePrices[index - i] - Bars.ClosePrices[index - i - 1]) / Bars.ClosePrices[index - i - 1];
                if (todayRoc > prevDayRoc)
                    countRocDn++;
            }
            percentRank = (countRocDn / periods) * 100;
        }

        public void UpDownStreak(int periods, int index)
        {
            int countUp = 0;
            int countDn = 0;
            int countEqual = 0;
            for (int i = 1; i < 100; i++)
                if (Bars.ClosePrices[index - i] > Bars.ClosePrices[index - i - 1])
                {
                    countUp++;
                    continue;
                }
                else
                    break;
            for (int i = 1; i < 100; i++)
                if (Bars.ClosePrices[index - i] < Bars.ClosePrices[index - i - 1])
                {
                    countDn++;
                    continue;
                }
                else
                    break;
            for (int i = 1; i < 100; i++)
                if (Bars.ClosePrices[index - i] == Bars.ClosePrices[index - i - 1])
                {
                    countEqual++;
                    continue;
                }
                else
                    break;
            if (countUp > countDn && countUp > countEqual)
            {
                streak = countUp;
                listOfStreaks[index] = countUp;
            }
            else if (countDn > countUp && countDn > countEqual)
            {
                streak = -countDn;
                listOfStreaks[index] = -countDn;
            }
            else if (countEqual > countUp && countEqual > countDn)
            {
                streak = 0;
                listOfStreaks[index] = 0;
            }
        }

        public double GetRsiStreak(int periods, int index)
        {
            if (listOfStreaks.Count < periods)
                return 0;

            double changeValue;
            double rs;
            double result;

            for (int i = index; i > index - periods; i--)
            {
                changeValue = listOfStreaks[i - 1] - listOfStreaks[i];

                listOfLoss[index] = 0;
                listOfGain[index] = 0;

                if (double.IsNaN(changeValue)) continue;

                if (changeValue > 0)
                {
                    listOfGain[index] = changeValue;
                }
                else
                {
                    listOfLoss[index] = Math.Abs(changeValue);
                }
            }

            if (1 == startIndex)
            {
                avgGain1 = listOfGain.Average();
                avgLoss1 = listOfLoss.Average();

                listOfAvgGain[index] = double.IsNaN(avgGain1) ? 0 : avgGain1;
                listOfAvgLoss[index] = double.IsNaN(avgLoss1) ? 0 : avgLoss1;
            }
            else
            {
                avgGain = ((listOfAvgGain[index - 1] * (periods - 1)) + listOfGain[index]) / periods;
                avgLoss = ((listOfAvgLoss[index - 1] * (periods - 1)) + listOfLoss[index]) / periods;

                listOfAvgGain[index] = avgGain;
                listOfAvgLoss[index] = avgLoss;
            }

            if (1 == startIndex)
            {
                startIndex = 2;
                rs = avgGain1 / avgLoss1;
                if (avgLoss1 == 0)
                    result = 100;
                else
                    result = 100 - (100 / (1 + rs));
            }
            else
            {
                rs = avgGain / avgLoss;
                if (avgLoss == 0)
                    result = 100;
                else
                    result = 100 - (100 / (1 + rs));
            }

            return result;
        }
    }
}
using cAlgo.API;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class cBotNew : Robot
    {
        [Parameter("RSI periods", Group = "CRSI", DefaultValue = 3.0, MinValue = 1)]
        public int rsiPeriods { get; set; }

        [Parameter("Up/down streak periods", Group = "CRSI", DefaultValue = 2.0, MinValue = 1)]
        public int streakPeriods { get; set; }

        [Parameter("Rate of change periods", Group = "CRSI", DefaultValue = 100.0, MinValue = 2)]
        public int rocPeriods { get; set; }

        public ConnorsCRSI11 crsi;

        protected override void OnStart()
        {
            crsi = Indicators.GetIndicator<ConnorsCRSI11>(rsiPeriods, streakPeriods, rocPeriods);
        }

        protected override void OnBar()
        {
            var index = Bars.Count - 2;

            Print(crsi.Result[index], " | ", Bars.OpenTimes[index].ToString("R"));
        }
    }
}

 


@amusleh

amusleh
28 Mar 2021, 11:27

You can reference custom indicators on your cBots by following below steps:

1. Right click on your cBot name on cTrader automate, select "Manage References"

2. Find the indicator you want to reference in indicators tab and select it, then click "Apply" button

3. Re-build your indicator, then to use indicator:

using cAlgo.API;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class CorrelationBot : Robot
    {
        private Correlation _correlation;

        protected override void OnStart()
        {
            _correlation = Indicators.GetIndicator<Correlation>("USDCHF", 50);
        }

        protected override void OnTick()
        {
            if (_correlation.Result.Last(1) > 50)
            {
            }
            else
            {
            }
        }
    }
}

You have to pass all indicator parameters a value on "GetIndicator" method, otherwise it will not work, and you can only use the indicator outputs not properties.


@amusleh

amusleh
28 Mar 2021, 11:19

RE: RE: UserVoice

astralalex said:

ctdn_voter said:

It'd be nice to have cAlgo.API compatible with .NET Core.

Totally agree, as a C# developer I wasn't keen on learning MQL4/5 and I jumped for joy when I saw there was a platform which allowed me to do bots and indicators.

However working with such an old version of .net rather makes things a lot harder since I'm having to find libraries which are 10 years old and many are obsolete now. 

I spent about half a day trying to create a .netstandard library with a COM wrapper but I couldn't get that approach to work. 

I also tried making api calls from cTrader to a dotnetcore api service for a 'microservice' style approach but there didn't seem to be a nuget package which would support http requests for System.Net.Http on .net 4.

If anybody has a semi-clean work around for this it would be great to know!

Alternatively if it was somewhere on the roadmap it would be one to watch as I'm sure this is going to be the number one C# developer wish.

cTrader is migrating to .NET core, its right now the main priority of developers, but we can't give you an ETA.


@amusleh

amusleh
28 Mar 2021, 11:18

RE: RE: RE: RE: RE: RE: RE: RE: RE:

kebbo said:

hi,

I followed the link and read it through, thank you.

amusleh said:

A DateTimeOffset Offset property returns the offset or difference of that DateTimeOffset from UTC, it doesn't change if you change the system time settings.

 

 

Here we are exactly at the point where I have difficulties in understanding.

You say an offset value from UTC time, but what is the reference time? If the reference point (from which the offset is calculated) is my system time, then the DateTimeOffset value should also adjust when the system time changes, shouldn't it?

 

best regards

Hi,

Offset is the time zone offset not time of day, if you want to get the time of day you can use DateTimeOffset.Now.TimeOfDay.


@amusleh

amusleh
28 Mar 2021, 11:14

We are adding a ton of new indicator/cBot examples very soon, in mean time you can access those examples at: 

 


@amusleh

amusleh
28 Mar 2021, 11:13

Hi,

To get the previous completed value on a data series you must use index -1 or Last(1).

If that didn't solved the issue please post the code and we will help you.


@amusleh

amusleh
27 Mar 2021, 11:25

RE: RE: RE: RE: RE: RE: RE:

kebbo said:

amusleh said:

kebbo said:

Hi again,

do you have more precise information on which time value "DateTimeOffset" refers to? Where are the times taken from which the offset value is calculated?

When I use DateTimeOffset.Now.Offset.Hours, I always have the same value output, regardless of the platform time set, as well as regardless of the computer system time set.
This could help me, but for my understanding I would like to know how this offset value is calculated.

Best regards
kebbo

Read this: 

Both DateTime.Now and DateTimeOffset.Now gives you the system time, you can get time from somewhere else like an API if you have one or if you don't have any you can use the web requests time, you can find other options if you google.

Hi and thank you again!

I did some experiments with the DateTimeOffset class and got the "Offset hours" output as described in my previous post.
Additionally the server time and the system time via the DateTime.
I then set the system time one hour earlier or later and found that the offset value always remained the same. I therefore asked myself where the time for DateTimeOffset is taken from.
With DateTime, the new time is output when the system time is changed.

Do you understand what I mean?

DateTimeOffset.Now.Offset.Hours gives me the difference in hours between my system time and the server time, right? But why do they stay the same when I change the system time...?

best regards

Hi,

Your question is not related to the cTrader automate API, its related to .NET.

A DateTimeOffset Offset property returns the offset or difference of that DateTimeOffset from UTC, it doesn't change if you change the system time settings.

Please read the link I posted on my previous post and you will find all detail related to DateTimeOffset.


@amusleh

amusleh
26 Mar 2021, 22:40

RE: where are 'List' Stored?

emeeder1 said:

This is old post, but relevant to my question. Maybe someone can explain this for me?

In the List function, is the resulting list saved somewhere on local computer? what happens to data saved to list when disconnected or if Ctrader restarted? is List functions available in both indicator and Bot?

Primarily i want to store data permanently so it does not get lost when ctrader turned off.

Also: if List is not saved locally, is there a function available that can access local data on computer (excel table, database...)?

 

Thanks :)

 

 

cTrader automate API is written in C# and is based on .NET framework, you can use all .NET libraries inside your cTrader indicator/cBot.

The .NET collections including list is stored in your system memory and when your .NET program in our case indicator/cBot is terminated the memory will be cleaned up and you will not have access to it next time your .NET program runs.

To save data permanently you have to write it on the disk, you can easily write anything you want to on files via .NET file stream object.

If you need code examples please Google reading/writing files C# and you will find lots of code samples.


@amusleh

amusleh
26 Mar 2021, 18:57

RE: RE: RE: RE: RE:

kebbo said:

Hi again,

do you have more precise information on which time value "DateTimeOffset" refers to? Where are the times taken from which the offset value is calculated?

When I use DateTimeOffset.Now.Offset.Hours, I always have the same value output, regardless of the platform time set, as well as regardless of the computer system time set.
This could help me, but for my understanding I would like to know how this offset value is calculated.

Best regards
kebbo

Read this: 

Both DateTime.Now and DateTimeOffset.Now gives you the system time, you can get time from somewhere else like an API if you have one or if you don't have any you can use the web requests time, you can find other options if you google.


@amusleh

amusleh
26 Mar 2021, 17:55

Hi,

You can use the cTrader Period Separator, it will draw a line at the beginning of each day and it works for time frames lower than hourly.

If that's not enough for you then you can use this sample indicator:

using cAlgo.API;
using System;
using System.Globalization;
using System.Linq;

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class DailySeparatorLineSample : Indicator
    {
        private const string ChartObjectNamesSuffix = "DailySeparatorLineSample";

        private TimeSpan _dayTime, _dayTimeUserOffset;

        private Color _linesColor;

        [Parameter("Day Time", DefaultValue = "00:00:00")]
        public string DayTime { get; set; }

        [Parameter("Color", DefaultValue = "Blue")]
        public string LinesColor { get; set; }

        [Parameter("Color Alpha", DefaultValue = 200, MaxValue = 255, MinValue = 0)]
        public int LinesColorAlpha { get; set; }

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

        [Parameter("Style", DefaultValue = LineStyle.Solid)]
        public LineStyle LinesStyle { get; set; }

        protected override void Initialize()
        {
            if (TimeSpan.TryParse(DayTime, CultureInfo.InvariantCulture, out _dayTime))
            {
                _dayTimeUserOffset = _dayTime.Add(-Application.UserTimeOffset);
            }

            _linesColor = ParseColor(LinesColor, LinesColorAlpha);

            Application.UserTimeOffsetChanged += Application_UserTimeOffsetChanged;

            DrawLines();
        }

        public override void Calculate(int index)
        {
        }

        private void Application_UserTimeOffsetChanged(UserTimeOffsetChangedEventArgs obj)
        {
            _dayTimeUserOffset = _dayTime.Add(-Application.UserTimeOffset);

            DrawLines();
        }

        private void DrawLines()
        {
            RemoveLines();

            var endDate = Bars.OpenTimes.LastValue.Date.AddDays(100);

            for (var date = Bars.OpenTimes[0].Date; date <= endDate; date = date.AddDays(1))
            {
                var lineTime = date.Add(_dayTimeUserOffset);
                var lineName = string.Format("{0}_{1}", ChartObjectNamesSuffix, date);

                Chart.DrawVerticalLine(lineName, lineTime, _linesColor, LinesThickness, LinesStyle);
            }
        }

        private void RemoveLines()
        {
            var chartObjects = Chart.Objects.ToArray();

            foreach (var chartObject in chartObjects)
            {
                if (!chartObject.Name.StartsWith(ChartObjectNamesSuffix, StringComparison.OrdinalIgnoreCase)) continue;

                Chart.RemoveObject(chartObject.Name);
            }
        }

        private Color ParseColor(string colorString, int alpha = 255)
        {
            var color = colorString[0] == '#' ? Color.FromHex(colorString) : Color.FromName(colorString);

            return Color.FromArgb(alpha, color);
        }
    }
}

 


@amusleh

amusleh
26 Mar 2021, 08:52

RE: RE: RE: VolumeROC only works OnTick()

3eunguyen said:

amusleh said:

3eunguyen said:

Sorry for the noob question.

I'm running this cBot, where I just print the last value of the VolumeROC indicator to the log. When I backtest it and compare it to the value of the exact same indicator that I display on my chart, I get completely different values. It keeps printing negative numbers around -80 and -99 in the log.

Can you post your cBot code here? the Volume ROC can go in negative side.

 

It's good, turns out VolumeROC won't work if you run it inside OnBar(), it has to be OnTick(). That kinda sucks, I hope it doesn't affect all volume indicators.

//Doesn't Work  

protected override void OnBar()
        {
            Print(vRoc.Result.LastValue);
        }

//Works

protected override void OnTick()
        {
            Print(vRoc.Result.LastValue);
        }

The OnBar is called when a new bar is opened or a previous bar closed, so the last value of indicator series is not completed yet or even maybe not set yet.

Inside OnBar you should not use LastValue, instead use Last(1) which will give you the last completed bar values.


@amusleh

amusleh
26 Mar 2021, 08:49

RE:

yuval.ein said:

How can I get current EST time by code when my time is set to a different time zone?

 

Thanks

Try this: 

using cAlgo.API;
using System;

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class TimezoneSample : Indicator
    {
        protected override void Initialize()
        {
            var estTime = GetEasternStandardTime();

            Print(estTime.ToString("o"));
        }

        public override void Calculate(int index)
        {
        }

        private DateTime GetEasternStandardTime()
        {
            var easternTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");

            return TimeZoneInfo.ConvertTimeFromUtc(Server.TimeInUtc, easternTimeZone);
        }
    }
}

And for more info about time zones check the Microsoft Docs for TimeZoneInfo.


@amusleh

amusleh
26 Mar 2021, 08:40

RE:

visha said:

I have created my own cBot code and applied multiple instruments but it does not all work all the instruments parallelly. Only it is placing the order in active chart.

It does not work non interactive charts. is that expected ?

Hi,

Please post your cBot code, otherwise we can't help you.


@amusleh

amusleh
26 Mar 2021, 08:39

Hi,


BarOpened/Tick events were added in recent versions of Automate API, they were not available in older versions.

Both do the same thing but if you are developing a single time frame/symbol based indicator/cBot then you should use the overridden methods because that's much easier and you don't have to subscribe for the events, and if you are developing a multi symbol/ time frame indicator/cBot then you should use the BarOpened/Tick events.


@amusleh

amusleh
25 Mar 2021, 10:11

RE:

3eunguyen said:

Sorry for the noob question.

I'm running this cBot, where I just print the last value of the VolumeROC indicator to the log. When I backtest it and compare it to the value of the exact same indicator that I display on my chart, I get completely different values. It keeps printing negative numbers around -80 and -99 in the log.

Can you post your cBot code here? the Volume ROC can go in negative side.


@amusleh

amusleh
25 Mar 2021, 10:08

You can use this free indicator: 

 


@amusleh

amusleh
24 Mar 2021, 09:23

There are different ways to show a popup message box, the simplest option is to use WinForms dialogs:

MessageBox.Show("Some text", "Some title", 
    MessageBoxButtons.OK, MessageBoxIcon.Error);

The MessageBox is inside System.Windows.Forms, so you have to reference that assembly for the above code to work.

If you need something more advanced you can use the open source ctrader-alert_popup library.


@amusleh

amusleh
24 Mar 2021, 09:19

RE: RE: RE:

kebbo said:

amusleh said:

kebbo said:

Hi,

thank you very much for your detailed answer!

I will test this once, however, I do not have so much experience with web requests, possibly there are difficulties with the implementation.

Is it possible to access the time information that is displayed at the bottom right of the platform at "current time:"?

Thank you!

I updated the code, please try again.

Thanks again for your effort!

I would like to get along without WebRequests.
Do you have any other ideas, or are you convinced that there is no other way?

Is it possible to access the time displayed at the bottom right of cTrader? This time is retrieved directly from the server, isn't it?

When backtesting, I need the current time and not the times of the respective candle, and also not the system time of the executing PC, but the current server time...

It would be great if you could help me.

Many greetings and many thanks

 

Not sure but I don't think its possible to get the current server time during back test, getting time from web request is one option, if you Google you might find other options.


@amusleh

amusleh
24 Mar 2021, 09:17

Try this:

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

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class Callindicator : Indicator
    {
        private Symbol[] _symbols;
        private Bars[] _bars;

        [Parameter("Symbols", DefaultValue = "EURUSD,GBPUSD,USDJPY,USDCHF,AUDCAD,AUDCHF,AUDJPY,AUDNZD,AUDUSD,CADCHF,CADJPY")]
        public string SymbolNames { get; set; }

        [Parameter("Bigger Timeframe", DefaultValue = "Daily")]
        public TimeFrame TimeframeBigger { get; set; }

        public int IndexMinDaily = 0;
        public int MinIndexB = 0;
        public double MinLineB = 0;

        public int IndexMaxDaily = 0;
        public int MaxIndexB = 0;
        public double MaxLineB = 0;

        public bool is_minB = false;
        public bool is_maxB = false;

        public List<Extremum> extremums = new List<Extremum>();

        public int lastHighindexB;
        public int LastLowindexB;

        public bool isBuy_allowedB = true;
        public bool isSell_allowedB = true;

        public bool signal_BuyB = false;
        public bool signal_SellB = false;

        public double high_insideB;
        public double low_insideB;

        public int lastbuy_exIndexB;
        public int lastsell_exIndexB;

        private MarketSeries Biggermarketseries;

        private int lastIndex = 0;

        protected override void Initialize()
        {
            _symbols = Symbols.GetSymbols(SymbolNames.Replace(" ", string.Empty).Split(',').ToArray());
            _bars = _symbols.Select(iSymbol => MarketData.GetBars(TimeframeBigger, iSymbol.Name)).ToArray();

            foreach (var bars in _bars)
            {
                while (bars.Count < 1000)
                {
                    var loadedCount = bars.LoadMoreHistory();
                    Print("Loaded {0} bars of {1}", loadedCount, bars.SymbolName);
                    if (loadedCount == 0)
                        break;
                }

                bars.Tick += Bars_Tick;
                bars.BarOpened += Bars_BarOpened;
            }

            Biggermarketseries = MarketData.GetSeries(TimeframeBigger);

            int index = Bars.Count - 2;

            int idx1 = Biggermarketseries.OpenTime.GetIndexByTime(MarketSeries.OpenTime[index]);
            int indaxstart = idx1 - 10;
            MinIndexB = indaxstart;
            MinLineB = double.MaxValue;
            MaxIndexB = indaxstart;
            MaxLineB = double.MinValue;
        }

        private void Bars_BarOpened(BarOpenedEventArgs obj)
        {
            // each symbol will call this method on new bar open
        }

        private void Bars_Tick(BarsTickEventArgs obj)
        {
            // each symbol will call this method on tick change
        }

        public override void Calculate(int index)
        {
            foreach (var symbol in _symbols)
            {
                // run indicator for each symbol
            }

            int idx1 = Biggermarketseries.OpenTime.GetIndexByTime(MarketSeries.OpenTime[index]);
            // index daily
            int idx2 = MarketSeries.OpenTime.GetIndexByTime(Biggermarketseries.OpenTime[idx1]);

            Bars barsD1 = MarketData.GetBars(TimeFrame.Daily);
            Bars barsH1 = MarketData.GetBars(TimeFrame.Hour);
            if (is_maxB && Biggermarketseries.High[idx1 - 1] > MaxLineB)
            {
                MaxLineB = Biggermarketseries.High[idx1 - 1];
                MaxIndexB = findindexmax(idx2, MaxLineB);
            }

            Chart.DrawStaticText("text", index + SymbolName, VerticalAlignment.Bottom, HorizontalAlignment.Left, Color.Red);
            returnfun(MaxIndexB);
        }

        public string returnfun(int index)
        {
            return index + SymbolName;
        }

        public int findindexmax(int index, double high)
        {
            for (int i = index - 24; i <= index + 24; i++)
            {
                if (high == Bars.HighPrices[i])
                    return i;
            }
            return index;
        }
    }
}

You can put any code you want to inside "Bars_BarOpened" and "Bars_Tick" methods, you can use the Calculate method to calculate the indicator for historical data, its called only for current chart symbol ticks not your other symbols, so instead use "Bars_Tick" method.

Now you have all you need, the symbols and their bars, and you can pass the symbols via a parameter instead of hard coding the symbol names inside your indicator.


@amusleh

amusleh
23 Mar 2021, 15:02

Hi,

Try StringBuilder:

using cAlgo.API;
using System.Text;

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class Blank : Indicator
    {
        protected override void Initialize()
        {
            var stringBuilder = new StringBuilder();

            stringBuilder.AppendLine("First Text");
            stringBuilder.AppendLine("Second Text");

            var text = stringBuilder.ToString();

            Chart.DrawText("text", text, Chart.LastVisibleBarIndex, Bars.HighPrices[Chart.LastVisibleBarIndex], Color.Red);
        }

        public override void Calculate(int index)
        {
        }
    }
}

With string builder you can easily add as many line as you want to, and then you just have to call the DrawText once.

You can't use font size, that's for chart controls not chart objects.


@amusleh