New Features in cTrader Automate API 3.7

Spotware's avatar

Spotware since: 23 Sep 2013;

  04 Dec 2019, 15:42
New Features in cTrader Automate API 3.7

Historical Data API

cBots and indicators now have a new API to access historical price data.
Previously, historical data was accessible using MarketSeries interface which contains DataSeries for Open, High, Low, Close prices and more. Where each DataSeries is a collection of double numbers that can be accessible using bar index or you can be iterated using loops.

Old API DataSeries

MarketSeries.Open
MarketSeries.High
MarketSeries.Low
MarketSeries.Close
MarketSeries.TickVolume
MarketSeries.OpenTime

New API keeps the same DataSeries with a different names

Bars.OpenPrices
Bars.HighPrices
Bars.LowPrices
Bars.ClosePrices
Bars.TickVolumes
Bars.OpenTimes

Bar Type

Bars object adds more functionality. Now you can choose if you want to operate with a collection of prices or with a collection of bar objects. Bars itself is a collection of Bar objects.
You can get Bar by index or using LastBar property or Last() method to get bar from the end of the collection.
Example: print to log close prices for first and last bar on the chart.

var firstBar = Bars[0];
var lastBar = Bars.LastBar;
Print("First bar close {0}, last bar close {1}", firstBar.Close, lastBar.Close);

Output:
First bar close 1.23043, last bar close 1.10541

Or you can iterate through all of the bars.
Example: draw an arrow on each doji bar, where open equals to close.

foreach (var bar in Bars)
{
    if (bar.Open == bar.Close)
    {
        Chart.DrawIcon(bar.OpenTime.ToString(), ChartIconType.DownArrow, bar.OpenTime, bar.High, Color.Blue);
    }
}

Bar Events

You can subscribe to events for Bars:

Bars.Tick // Raised when a new tick arrives
Bars.BarOpened // Raised when last bar is closed and new bar is opened
Bars.HistoryLoaded // Raised when more history is loaded due to chart scroll on the left or due to API call
Bars.Reloaded // Raised when bars are refreshed due to reconnect

Ticks

Automate API adds Ticks to work with tick data.
Ticks is a collection of Tick objects with following properties

Tick.Bid
Tick.Ask
Tick.Time

Note, in the previous versions tick data was accessible only if cBot or indicator was running on Tick1 chart and only for bid prices using old MarketSeries. With the new API you can access tick data from any timeframe and use bid and ask prices

Load more history

For both Bars and Ticks you can load more history using LoadMoreHistory or LoadMoreHistoryAsync methods.
Below Is an example of loading 10 000 bars on the chart

Print("{0} bar on the chart. Loading 10 000 bars", Bars.Count);
while (Bars.Count < 10000)
{
    var loadedCount = Bars.LoadMoreHistory();
    Print("Loaded {0} bars", loadedCount);
    if (loadedCount == 0)
        break;
}
Print("Finished, total bars {0}", Bars.Count);

Output:
1143 bar on the chart. Loading 10 000 bars
Loaded 1141 bars
Loaded 1162 bars
Loaded 1129 bars
Loaded 1090 bars
Loaded 1126 bars
Loaded 1134 bars
Loaded 1134 bars
Loaded 1166 bars
Finished, total bars 10225

Bars and Ticks for other symbols and timeframes

cBots and indicators can request bars and ticks for other symbols and timeframes. MarketData contains synchronous and asynchronous methods to load this data

MarketData.GetBars
MarketData.GetBarsAsync
MarketData.GetTicks
MarketData.GetTicksAsync

Multi-Symbol Backtesting

Backtesting now allows to use and trade on other symbols than the symbol the cBot is running on. You can get desired Symbol using Symbol.GetSymbol or Symbols.GetSymbols methods.
To get events about price updates for received symbols use Symbol.Tick event.

Also, you can get historical data for other symbols during backtesting using MarketData.GetBars and MarketData.GetTicks methods.

The example below shows how to display changing prices on the chart for 4 symbols during visual backtesting.

public Symbol[] MySymbols;

protected override void OnStart()
{
    MySymbols = Symbols.GetSymbols("EURUSD", "GBPUSD", "USDJPY", "USDCHF");
    foreach (var symbol in MySymbols)
    {
        symbol.Tick += Symbol_Tick;
    }
}

private void Symbol_Tick(SymbolTickEventArgs obj)
{
    var sb = new StringBuilder();
    foreach (var symbol in MySymbols)
    {
        var textLine = string.Format("{0} {1} {2}", symbol.Name, symbol.Bid, symbol.Ask);
        sb.AppendLine(textLine);
    }
    Chart.DrawStaticText("symbols", sb.ToString(), VerticalAlignment.Top, HorizontalAlignment.Left, Chart.ColorSettings.ForegroundColor);
}

Clouds between indicator lines

Indicators now can add a semi-transparent cloud between two lines. Cloud can be easily added by adding Cloud attribute to your indicator.
It takes two line names as required parameters.
Cloud changes its color based on the color of the top line. If one line crosses another, cloud color changes. Line color is multiplied by the default opacity which is 0.2. You can change default behavior by setting following parameters in the Cloud attribute:

  • Opacity - opacity that will be applied to selected color
  • FirstColor - the color to use when first line is above the second line
  • SecondColor - the color to use when second line is above the first line

Below is an example of an indicator with fast Moving Average and slow Moving Average and a cloud between them

[Cloud("Fast MA", "Slow MA")]
[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class MaCrossCloud : Indicator
{
    [Parameter("Fast MA Period", DefaultValue = 21)]
    public int FastMaPeriod { get; set; }

    [Parameter("Slow MA Period", DefaultValue = 50)]
    public int SlowMaPeriod { get; set; }

    [Output("Fast MA", LineColor = "#FF6666")]
    public IndicatorDataSeries FastMaResult { get; set; }

    [Output("Slow MA", LineColor = "#0071C1")]
    public IndicatorDataSeries SlowMaResult { get; set; }

    SimpleMovingAverage FastMa;
    SimpleMovingAverage SlowMa;


    protected override void Initialize()
    {
        FastMa = Indicators.SimpleMovingAverage(Bars.ClosePrices, FastMaPeriod);
        SlowMa = Indicators.SimpleMovingAverage(Bars.ClosePrices, SlowMaPeriod);
    }

    public override void Calculate(int index)
    {
        FastMaResult[index] = FastMa.Result[index];
        SlowMaResult[index] = SlowMa.Result[index];
    }
}

Set custom bar color

cBots or indicators can set a specific color for an individual bar on the chart. You can set fill color and outline color separately using Chart.SetBarFillColor and Chart.SetBarOutlineColor methods or change both colors together using Chart.SetBarColor.
Also, color for tick volumes can be changed using Chart.SetTickVolumeColor.

Bars will be changed till cBot or indicator is running on the chart. On stop, bar colors will be changed back to the user settings. If you want to reset bar color to users settings during the work of cBot/indicator, you can use Chart.ResetBarColor to reset individual bar or Chart.ResetBarColors to reset all bars. Same for tick volumes with Chart.ResetTickVolumeColor and Chart.ResetTickVolumeColors.

The example below shows how to set bar color to green if its close price is above Simple Moving Average and to red if it is below.

var sma = Indicators.SimpleMovingAverage(Bars.ClosePrices, 14);
for (var i = 0; i < Bars.Count; i++)
{
    var closePrice = Bars.ClosePrices[i];
    var smaValue = sma.Result[i];
    var barColor = closePrice > smaValue ? Color.Green : Color.Red;
    Chart.SetBarColor(i, barColor);
}

Changes in charts zoom API

As cTrader 3.7 introduces new smooth chart zooming, zoom values in API was changed.
Old Chart.Zoom property is deprecated. It used to have range of possible values from 0 to 5, which reflected old zoom levels on the user interface.
New Chart.ZoomLevel was added with values from 5 to 500 with a step of 5. These steps repeat new values on the user interface.

Obsoleted API

1. MarketSeries is obsolete. All methods that take a parameter of this type now have a new overload with Bars parameter instead of MarketSeries.
Affected methods to get indicator with a data source from another timeframe or symbol:

  • Indicators.GetIndicator() for custom indicators
  • And methods to get built-in indicators, like Indicators.Aroon() or Indicators.ParabolicSAR()

2. MarketData.GetSeries() is obsolete. It was replaced with MarketData.GetBars().

TRADERS FIRST™ Vote for your favorite features: https://ctrader.com/forum/suggestions
Waxy's avatar

Waxy since: 12 May 2015;

  05 Dec 2019, 05:05
RE:

Hello Spotware,

These are indeed great additions,

I see now that when I load an indicator that uses multiple MarketSeries (or now Bars) it takes much less than before, and this is great news, but at the start, it takes the same as before, so, I'd like to know if it's possible to improve or what's the best way now to load multiple series loading for indicators/bots during initialization, let's say I want to load 10 symbol bars, can I load these in parallel now?

¨You are not what you have done, you are what you have overcome.¨ - Anonymous

ctid1574514 since: 18 Oct 2019;

  05 Dec 2019, 07:24
Multi Symbol Sample Trend cBot

The Sample Trend cBot trading 3 pairs.

I would have liked to have not used the switch and not declared all the indicators separately so it can scale to more pairs,  but it works trading multiple pairs on a back test

using System;
using System.Linq;
using System.Text;
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 MultSymbol : Robot
    {
        public Symbol[] MySymbols;

        [Parameter("Quantity (Lots)", Group = "Volume", DefaultValue = 1, MinValue = 0.01, Step = 0.01)]
        public double Quantity { get; set; }

        [Parameter("MA Type", Group = "Moving Average")]
        public MovingAverageType MAType { get; set; }

        [Parameter("Source", Group = "Moving Average")]
        public DataSeries SourceSeries { get; set; }

        [Parameter("Slow Periods", Group = "Moving Average", DefaultValue = 10)]
        public int SlowPeriods { get; set; }

        [Parameter("Fast Periods", Group = "Moving Average", DefaultValue = 5)]
        public int FastPeriods { get; set; }

        private MovingAverage slowMaEURUSD;
        private MovingAverage fastMaEURUSD;
        private MovingAverage slowMaUSDJPY;
        private MovingAverage fastMaUSDJPY;
        private MovingAverage slowMaGBPUSD;
        private MovingAverage fastMaGBPUSD;

        private const string label = "MultSymbolcBot";

        protected override void OnStart()
        {
            MySymbols = Symbols.GetSymbols("EURUSD", "USDJPY", "GBPUSD");
            foreach (var symbol in MySymbols)
            {
                symbol.Tick += Symbol_Tick;
            }

            Bars seriesEURUSD = MarketData.GetBars(TimeFrame, "EURUSD");
            Bars seriesUSDJPY = MarketData.GetBars(TimeFrame, "USDJPY");
            Bars seriesGBPUSD = MarketData.GetBars(TimeFrame, "GBPUSD");

            fastMaEURUSD = Indicators.MovingAverage(seriesEURUSD.ClosePrices, FastPeriods, MAType);
            slowMaEURUSD = Indicators.MovingAverage(seriesEURUSD.ClosePrices, SlowPeriods, MAType);

            fastMaUSDJPY = Indicators.MovingAverage(seriesUSDJPY.ClosePrices, FastPeriods, MAType);
            slowMaUSDJPY = Indicators.MovingAverage(seriesUSDJPY.ClosePrices, SlowPeriods, MAType);

            fastMaGBPUSD = Indicators.MovingAverage(seriesGBPUSD.ClosePrices, FastPeriods, MAType);
            slowMaGBPUSD = Indicators.MovingAverage(seriesGBPUSD.ClosePrices, SlowPeriods, MAType);
        }

        protected override void OnTick()
        {

        }

        protected override void OnStop()
        {

        }

        private void Symbol_Tick(SymbolTickEventArgs obj)
        {
            foreach (var symbol in MySymbols)
            {
                switch (symbol.Name)
                {
                    case "EURUSD":
                        SymbolTrade(symbol, fastMaEURUSD, slowMaEURUSD);
                        break;
                    case "USDJPY":
                        SymbolTrade(symbol, fastMaUSDJPY, slowMaUSDJPY);
                        break;
                    case "GBPUSD":
                        SymbolTrade(symbol, fastMaGBPUSD, slowMaGBPUSD);
                        break;
                }
            }
        }

        private double VolumeInUnits
        {
            get { return Symbol.QuantityToVolumeInUnits(Quantity); }
        }

        private void SymbolTrade(Symbol SymbolName, MovingAverage MaNameFast, MovingAverage MaNameSlow)
        {
            var longPosition = Positions.Find(label, SymbolName.Name, TradeType.Buy);
            var shortPosition = Positions.Find(label, SymbolName.Name, TradeType.Sell);

            var currentSlowMa = MaNameSlow.Result.Last(0);
            var currentFastMa = MaNameFast.Result.Last(0);
            var previousSlowMa = MaNameSlow.Result.Last(1);
            var previousFastMa = MaNameFast.Result.Last(1);

            if (previousSlowMa > previousFastMa && currentSlowMa <= currentFastMa && longPosition == null)
            {
                if (shortPosition != null)
                    ClosePosition(shortPosition);
                ExecuteMarketOrder(TradeType.Buy, SymbolName.Name, VolumeInUnits, label);
            }
            else if (previousSlowMa < previousFastMa && currentSlowMa >= currentFastMa && shortPosition == null)
            {
                if (longPosition != null)
                    ClosePosition(longPosition);
                ExecuteMarketOrder(TradeType.Sell, SymbolName.Name, VolumeInUnits, label);
            }
        }
    }
}
PanagiotisCharalampous's avatar

PanagiotisCharalampous since: 13 Jan 2017;

  05 Dec 2019, 10:26
RE: RE:

Waxy said:

Hello Spotware,

These are indeed great additions,

I see now that when I load an indicator that uses multiple MarketSeries (or now Bars) it takes much less than before, and this is great news, but at the start, it takes the same as before, so, I'd like to know if it's possible to improve or what's the best way now to load multiple series loading for indicators/bots during initialization, let's say I want to load 10 symbol bars, can I load these in parallel now?

Hi Xavier,

You can use MarketData.GetBarsAsync() and MarketData.GetTicksAsync() to get data asynchronously.

Best Regards,

Panagiotis 

Join us on Telegram

Head of Community Management at cTrader
Waxy's avatar

Waxy since: 12 May 2015;

  05 Dec 2019, 16:39
RE: RE: RE:

Hello Panagiotis,

I'm trying out the following code,

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

namespace cAlgo
{
    [Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class NewIndicator : Indicator
    {
        private readonly List<Bars> _bars = new List<Bars>();
        private readonly string[] _mySymbols = {"EURUSD","GBPUSD","EURJPY","USDJPY","AUDUSD","USDCHF","GBPJPY","USDCAD","EURGBP","EURCHF"};

        protected override void Initialize()
        {
            Print("Initializing");

            foreach (var symbol in _mySymbols)
            {
                Print(string.Format("Loading Symbol {0}", symbol));
                MarketData.GetBarsAsync(TimeFrame.Hour, symbol, bar =>
                {
                    _bars.Add(bar);
                    Print(string.Format("{0} Loaded, Bars: {1}", bar.SymbolName, bar.Count));
                });
            }

            Print("End Initializing");
        }

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

I get the responses in the log in this order, doesn't seem to work properly to me, is this correct, or I'm I doing something wrong, is this the best way to do it? It takes about 16 seconds to load them all (don't get me wrong it's a big improvement), could it load faster?

05/12/2019 14:20:04.782 | Initializing
05/12/2019 14:20:04.782 | Loading Symbol EURUSD
05/12/2019 14:20:04.797 | Loading Symbol GBPUSD
05/12/2019 14:20:04.797 | Loading Symbol EURJPY
05/12/2019 14:20:05.219 | Loading Symbol USDJPY
05/12/2019 14:20:05.235 | Loading Symbol AUDUSD
05/12/2019 14:20:05.657 | Loading Symbol USDCHF
05/12/2019 14:20:06.641 | Loading Symbol GBPJPY
05/12/2019 14:20:07.078 | Loading Symbol USDCAD
05/12/2019 14:20:07.110 | Loading Symbol EURGBP
05/12/2019 14:20:07.485 | Loading Symbol EURCHF
05/12/2019 14:20:07.953 | Loading Symbol AUDJPY
05/12/2019 14:20:08.344 | Loading Symbol NZDUSD
05/12/2019 14:20:08.391 | Loading Symbol CHFJPY
05/12/2019 14:20:08.813 | Loading Symbol EURAUD
05/12/2019 14:20:09.344 | Loading Symbol CADJPY
05/12/2019 14:20:09.782 | Loading Symbol GBPAUD
05/12/2019 14:20:10.250 | Loading Symbol EURCAD
05/12/2019 14:20:10.657 | Loading Symbol AUDCAD
05/12/2019 14:20:11.203 | Loading Symbol GBPCAD
05/12/2019 14:20:11.813 | Loading Symbol USDNOK
05/12/2019 14:20:13.063 | Loading Symbol AUDCHF
05/12/2019 14:20:13.672 | Loading Symbol USDMXN
05/12/2019 14:20:15.094 | Loading Symbol GBPNZD
05/12/2019 14:20:15.110 | Loading Symbol CADCHF
05/12/2019 14:20:15.719 | Loading Symbol USDSEK
05/12/2019 14:20:16.969 | Loading Symbol GBPCHF
05/12/2019 14:20:17.578 | Loading Symbol EURRUB
05/12/2019 14:20:19.453 | Loading Symbol NZDCHF
05/12/2019 14:20:20.078 | Loading Symbol NZDCAD
05/12/2019 14:20:20.703 | Loading Symbol AUDNZD
05/12/2019 14:20:20.735 | End Initializing
05/12/2019 14:20:20.735 | EURUSD Loaded, Bars: 2323
05/12/2019 14:20:20.735 | GBPUSD Loaded, Bars: 2013
05/12/2019 14:20:20.735 | EURJPY Loaded, Bars: 2013
05/12/2019 14:20:20.735 | USDJPY Loaded, Bars: 1650
05/12/2019 14:20:20.750 | AUDUSD Loaded, Bars: 2013
05/12/2019 14:20:20.750 | USDCHF Loaded, Bars: 2013
05/12/2019 14:20:20.750 | GBPJPY Loaded, Bars: 2013
05/12/2019 14:20:20.750 | USDCAD Loaded, Bars: 2013
05/12/2019 14:20:20.750 | EURGBP Loaded, Bars: 2013
05/12/2019 14:20:20.750 | EURCHF Loaded, Bars: 2013
05/12/2019 14:20:20.750 | AUDJPY Loaded, Bars: 2013
05/12/2019 14:20:20.750 | NZDUSD Loaded, Bars: 2013
05/12/2019 14:20:20.750 | CHFJPY Loaded, Bars: 2013
05/12/2019 14:20:20.750 | EURAUD Loaded, Bars: 2013
05/12/2019 14:20:20.750 | CADJPY Loaded, Bars: 2013
05/12/2019 14:20:20.750 | GBPAUD Loaded, Bars: 2013
05/12/2019 14:20:20.750 | EURCAD Loaded, Bars: 2013
05/12/2019 14:20:20.750 | AUDCAD Loaded, Bars: 2013
05/12/2019 14:20:20.750 | GBPCAD Loaded, Bars: 2013
05/12/2019 14:20:20.750 | USDNOK Loaded, Bars: 1170
05/12/2019 14:20:20.750 | AUDCHF Loaded, Bars: 2013
05/12/2019 14:20:20.750 | USDMXN Loaded, Bars: 1170
05/12/2019 14:20:20.750 | GBPNZD Loaded, Bars: 2013
05/12/2019 14:20:20.750 | CADCHF Loaded, Bars: 2013
05/12/2019 14:20:20.750 | USDSEK Loaded, Bars: 1170
05/12/2019 14:20:20.750 | GBPCHF Loaded, Bars: 2013
05/12/2019 14:20:20.750 | EURRUB Loaded, Bars: 1167
05/12/2019 14:20:20.750 | NZDCHF Loaded, Bars: 2013
05/12/2019 14:20:20.985 | NZDCAD Loaded, Bars: 2012
05/12/2019 14:20:21.203 | AUDNZD Loaded, Bars: 2013
 

PanagiotisCharalampous said:

Waxy said:

Hello Spotware,

These are indeed great additions,

I see now that when I load an indicator that uses multiple MarketSeries (or now Bars) it takes much less than before, and this is great news, but at the start, it takes the same as before, so, I'd like to know if it's possible to improve or what's the best way now to load multiple series loading for indicators/bots during initialization, let's say I want to load 10 symbol bars, can I load these in parallel now?

Hi Xavier,

You can use MarketData.GetBarsAsync() and MarketData.GetTicksAsync() to get data asynchronously.

Best Regards,

Panagiotis 

Join us on Telegram

¨You are not what you have done, you are what you have overcome.¨ - Anonymous
PanagiotisCharalampous's avatar

PanagiotisCharalampous since: 13 Jan 2017;

  05 Dec 2019, 16:55

Hi Xavier R,

1) As a good practice please try to create new threads for such discussions. It will be easier for future searches by other users.

2) I am not sure what is the issue. The 16 seconds? Note that this data needs be downloaded from the server so you should also take that into consideration. In my case, bars are loaded almost instantly. See below

Best Regards,

Panagiotis 

Join us on Telegram

Head of Community Management at cTrader
Waxy's avatar

Waxy since: 12 May 2015;

  05 Dec 2019, 18:19
RE:

What I mean is that it takes me 10-15 seconds to load these symbols, not almost instantly, I'm using 30mbps.

Only after the first load, any refresh of the bot/indicator and the bars load instantly afterward (which again, it's great news).

Also, I think a good option would be to have an overload where we can specify how many bars to load, because many times a thousand daily bars is not really needed, is it possible?

And I'm since this is part of the update I thought it would be correct to ask about it in this thread, I'll leave it here and if needed I'll open a new thread, sorry about that.

Thanks for your support

¨You are not what you have done, you are what you have overcome.¨ - Anonymous

kerrifox19 since: 11 Dec 2019;

  09 Jan 2020, 13:55
RE: RE:

Thanks for sharing this info with us.

w.b.z since: 14 Sep 2017;

  06 Feb 2020, 19:48
3.7 changed something in code, now half of my Algo`s dont work anymore..can anyone tell me what to do?

mparama's avatar

mparama since: 11 Oct 2016;

  10 Feb 2020, 03:15
RE: 3.7 changed something in code, now half of my Algo`s dont work anymore..can anyone tell me what to do?

w.b.z said:

I got the same problem with optimization using multi-timeframe.

Backtest is ok

Trading is ok

when I try to optimize I get  "Crashed in OnTick with NullReferenceException: Object reference not set to an instance of an object" and I found that the problem is related to the New API with Multi-TimeFrames,:

I replaced the old MarketSeries (everything was working fine) with the New API line guide (Optimization not working)

https://gumroad.com/activebot || active.ctrader.bot@gmail.com