Topics

Forum Topics not found

Replies

amusleh
14 Apr 2022, 11:00 ( Updated at: 14 Apr 2022, 11:02 )

Hi,

The rounding error was causing it, you can fix it by using Symbol.NormalizeVolume method:

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

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.FullAccess)]
    public class BotDemo : Robot
    {
        [Parameter("Name", Group = "Global Settings", DefaultValue = "_")]
        public string _name { get; set; }

        [Parameter("High/Low Timeframe", Group = "Global Settings", DefaultValue = "Daily")]
        public TimeFrame _hltf { get; set; }

        [Parameter("Max Lots", Group = "Risk", DefaultValue = 100.0, MinValue = 0.01, MaxValue = 1000.0)]
        public double _maxlots { get; set; }

        [Parameter("Account Risk (%)", Group = "Risk", DefaultValue = 100.0, MinValue = 0.01, MaxValue = 100.0)]
        public double _riskpercent { get; set; }

        [Parameter("Take Profit", Group = "Risk", DefaultValue = 5.0, MinValue = 0.1, MaxValue = 1000000.0)]
        public double _takeprofit { get; set; }

        [Parameter("Stop Loss", Group = "Risk", DefaultValue = 100.0, MinValue = 0.1, MaxValue = 1000000.0)]
        public double _stoploss { get; set; }

        [Parameter("Safety (Pips)", Group = "Risk", DefaultValue = 3.0, MinValue = 0.1, MaxValue = 10000.0)]
        public double _safety { get; set; }

        [Parameter("Maximum Visible Lots", Group = "Iceberg Settings", DefaultValue = 1, MaxValue = 100, MinValue = 0.01, Step = 1)]
        public double _maxvisiblelots { get; set; }

        [Parameter("Use Balance Target?", Group = "Targets", DefaultValue = true)]
        public bool _usetarget { get; set; }

        [Parameter("Balance Target (Base Currency)", Group = "Targets", DefaultValue = 100.0, MinValue = 0.01, MaxValue = 1000000.0, Step = 1.0)]
        public double _target { get; set; }

        private Bars _hlbars;
        private double _previoushigh, _previouslow, _maxunits, _total_volume_needed, _total_lots_needed, _riskpercentadjusted, _maxpowernormalized, _adjustedunits, _marginrequired;
        private bool _todaybuyhappened, _todaysellhappened, _targetcheck;

        public IEnumerable<PendingOrder> MyPendingOrders
        {
            get { return PendingOrders.Where(order => order.SymbolName.Equals(SymbolName, StringComparison.Ordinal) && order.Label.Equals(_name, StringComparison.Ordinal)); }
        }

        protected override void OnStart()
        {
            _riskpercentadjusted = _riskpercent / 100;

            _hlbars = MarketData.GetBars(_hltf);

            _hlbars.BarOpened += _hlbars_BarOpened;
            _safety *= Symbol.PipSize;

            Positions.Closed += PositionsOnClosed;

            _todaybuyhappened = true;
            _todaysellhappened = true;
        }

        private void _hlbars_BarOpened(BarOpenedEventArgs obj)
        {
            _todaybuyhappened = false;
            _todaysellhappened = false;

            foreach (var _PendingOrders in MyPendingOrders)
            {
                CancelPendingOrder(_PendingOrders);
            }

            if (_hlbars.OpenPrices.Last(0) > _hlbars.HighPrices.Last(1))
            {
                _todaybuyhappened = true;
            }

            if (_hlbars.OpenPrices.Last(0) < _hlbars.LowPrices.Last(1))
            {
                _todaysellhappened = true;
            }

            _total_volume_needed = GetVolume();
            _maxunits = Symbol.QuantityToVolumeInUnits(_maxlots);
            if (_total_volume_needed > _maxunits)
            {
                _total_volume_needed = _maxunits;
            }

            _total_lots_needed = Symbol.VolumeInUnitsToQuantity(_total_volume_needed);

            double[] volumes = GetVolumeSplits(_total_lots_needed, _maxvisiblelots);

            if (Symbol.NormalizeVolumeInUnits(volumes.Sum()) != Symbol.NormalizeVolumeInUnits(Symbol.QuantityToVolumeInUnits(_total_lots_needed)))
            {
                throw new InvalidOperationException(string.Format("Volumes Match Error, Volume Sum: {0} | Total Volume Needed: {1} | Max Visible Lots: {2} | # of Fragments: {3}", Symbol.VolumeInUnitsToQuantity(volumes.Sum()), _total_lots_needed, _maxvisiblelots, volumes.Length));
            }

            _previoushigh = Math.Abs(_hlbars.HighPrices.Last(1) + _safety);
            _previouslow = Math.Abs(_hlbars.LowPrices.Last(1) - _safety);

            _targetcheck = GetTargetCheck();

            if (_usetarget)
            {
                if (_targetcheck && !_todaybuyhappened)
                {
                    for (var i = 0; i < volumes.Length; i++)
                    {
                        PlaceStopOrder(TradeType.Buy, Symbol.Name, volumes[i], _previoushigh, _name, _stoploss, _takeprofit);
                    }
                    _todaybuyhappened = true;
                }

                if (_targetcheck && !_todaysellhappened)
                {
                    for (var i = 0; i < volumes.Length; i++)
                    {
                        PlaceStopOrder(TradeType.Sell, Symbol.Name, volumes[i], _previouslow, _name, _stoploss, _takeprofit);
                    }
                    _todaysellhappened = true;
                }

                if (!_targetcheck)
                {
                    Print("Target Balance Reached");
                    foreach (var _PendingOrders in MyPendingOrders)
                    {
                        CancelPendingOrder(_PendingOrders);
                    }
                }
            }

            if (!_usetarget)
            {
                if (!_todaybuyhappened)
                {
                    for (var i = 0; i < volumes.Length; i++)
                    {
                        PlaceStopOrder(TradeType.Buy, Symbol.Name, volumes[i], _previoushigh, _name, _stoploss, _takeprofit);
                    }
                    _todaybuyhappened = true;
                }

                if (!_todaysellhappened)
                {
                    for (var i = 0; i < volumes.Length; i++)
                    {
                        PlaceStopOrder(TradeType.Sell, Symbol.Name, volumes[i], _previouslow, _name, _stoploss, _takeprofit);
                    }
                    _todaysellhappened = true;
                }

                if (!_targetcheck)
                {
                    Print("Target Balance Reached");
                    foreach (var _PendingOrders in MyPendingOrders)
                    {
                        CancelPendingOrder(_PendingOrders);
                    }
                }
            }
        }

        private void PositionsOnClosed(PositionClosedEventArgs args)
        {
            _total_volume_needed = GetVolume();
            if (_total_volume_needed > _maxunits)
            {
                _total_volume_needed = _maxunits;
            }

            _total_lots_needed = Symbol.VolumeInUnitsToQuantity(_total_volume_needed);

            double[] modvolumes = GetVolumeSplits(_total_lots_needed, _maxvisiblelots);

            if (Symbol.NormalizeVolumeInUnits(modvolumes.Sum()) != Symbol.NormalizeVolumeInUnits(Symbol.QuantityToVolumeInUnits(_total_lots_needed)))
            {
                throw new InvalidOperationException(string.Format("Volumes Match Error, *Modify Volume Sum: {0} | *Total Volume Needed: {1} | Max Visible Lots: {2} | # of Fragments: {3}", modvolumes.Sum(), Symbol.QuantityToVolumeInUnits(_total_lots_needed), _maxvisiblelots, modvolumes.Length));
            }

            var OrdersGroupedByComment = MyPendingOrders.GroupBy(Order => Order.Comment);

            foreach (var OrderGroup in OrdersGroupedByComment)
            {
                foreach (var Order in OrderGroup)
                {
                    if (OrderGroup.Count() == modvolumes.Length)
                    {
                        for (var i = 0; i < modvolumes.Length; i++)
                        {
                            Order.ModifyVolume(modvolumes[i]);
                        }
                    }

                    if (OrderGroup.Count() != modvolumes.Length)
                    {
                        Print("Order Group Count Not Equal To Modify Volume Length");
                    }
                }
            }
        }

        private bool GetTargetCheck()
        {
            if (Account.Balance > _target)
            {
                return false;
            }
            else
                return true;
        }

        private double GetVolume()
        {
            double result = 3.14;
            _maxpowernormalized = Symbol.NormalizeVolumeInUnits((Account.Balance * _riskpercentadjusted) * Account.PreciseLeverage, RoundingMode.Down);
            if (_maxpowernormalized > Symbol.VolumeInUnitsMax)
            {
                _maxpowernormalized = Symbol.VolumeInUnitsMax;
            }
            else if (_maxpowernormalized < Symbol.VolumeInUnitsMin)
            {
                _maxpowernormalized = Symbol.VolumeInUnitsMin;
            }
            else if (_maxpowernormalized % Symbol.VolumeInUnitsStep != 0)
            {
                _maxpowernormalized = _maxpowernormalized - (_maxpowernormalized % Symbol.VolumeInUnitsStep);
            }
            double _marginrequiredformaxpower = GetMarginRequiredForMaxPower();

            if (_marginrequiredformaxpower > Account.FreeMargin)
            {
                _adjustedunits = _maxpowernormalized;
                _marginrequired = GetMarginRequiredAdjusted();
                while (_marginrequired > Account.FreeMargin)
                {
                    _adjustedunits = _adjustedunits - 1000;
                    _marginrequired = GetMarginRequiredAdjusted();
                }
                if (_adjustedunits > Symbol.VolumeInUnitsMax)
                {
                    _adjustedunits = Symbol.VolumeInUnitsMax;
                }
                else if (_adjustedunits < Symbol.VolumeInUnitsMin)
                {
                    _adjustedunits = Symbol.VolumeInUnitsMin;
                }
                else if (_adjustedunits % Symbol.VolumeInUnitsStep != 0)
                {
                    _adjustedunits = _adjustedunits - (_adjustedunits % Symbol.VolumeInUnitsStep);
                }
                return _adjustedunits;
            }
            else if (_marginrequiredformaxpower < Account.FreeMargin)
            {
                result = _maxpowernormalized;
            }
            return result;
        }

        private double[] GetVolumeSplits(double lots, double maxLotsPerSplit)
        {
            if (maxLotsPerSplit > lots)
                throw new InvalidOperationException("maxLotsPerSplit can't be more than lots Needed");

            var modulus = lots % maxLotsPerSplit;

            var numberOfFragments = Convert.ToInt32((lots - modulus) / maxLotsPerSplit);

            if (modulus > 0)
                numberOfFragments++;

            var lotsPerFragement = lots / numberOfFragments;

            var unitsPerFragment = Symbol.QuantityToVolumeInUnits(lotsPerFragement);

            var unitsPerFragementNormalized = Symbol.NormalizeVolumeInUnits(unitsPerFragment, RoundingMode.Up);

            var volumes = new double[numberOfFragments];

            for (var i = 0; i < numberOfFragments; i++)
            {
                volumes[i] = i == volumes.Length - 1 && modulus > 0 ? unitsPerFragment - ((unitsPerFragementNormalized - unitsPerFragment) * (volumes.Length - 1)) : unitsPerFragementNormalized;
            }

            return volumes;
        }

        private double GetMarginRequiredForMaxPower()
        {
            string _home_currency = Account.Asset.Name;
            string _base_currency = Symbol.Name.Substring(0, 3);
            string _quote_currency = Symbol.Name.Substring(3, 3);
            double _home_rate;
            double _leverage;
            double _units;
            double _margin_required_for_max_power;

            if (_home_currency == _base_currency || _home_currency == _quote_currency)
            {
                _home_rate = Symbol.Bid;
            }
            else
            {
                _home_rate = GetHomeRate(_home_currency, _base_currency);
            }

            _leverage = Account.PreciseLeverage;
            _units = _maxpowernormalized;

            _margin_required_for_max_power = RoundUp(((_home_rate) * _units) / _leverage, 2);
            return _margin_required_for_max_power;
        }

        private double GetHomeRate(string fromCurrency, string toCurrency)
        {
            Symbol symbol = TryGetSymbol(fromCurrency + toCurrency);

            if (symbol != null)
            {
                return symbol.Bid;
            }

            symbol = TryGetSymbol(toCurrency + fromCurrency);
            return symbol.Bid;
        }

        public static double RoundUp(double input, int places)
        {
            double multiplier = Math.Pow(10, Convert.ToDouble(places));
            return Math.Ceiling(input * multiplier) / multiplier;
        }

        private Symbol TryGetSymbol(string symbolCode)
        {
            try
            {
                Symbol symbol = Symbols.GetSymbol(symbolCode);
                if (symbol.Bid == 0.0)
                    return null;
                return symbol;
            }
            catch
            {
                return null;
            }
        }

        private double GetMarginRequiredAdjusted()
        {
            string _home_currency = Account.Asset.Name;
            string _base_currency = Symbol.Name.Substring(0, 3);
            string _quote_currency = Symbol.Name.Substring(3, 3);
            double _home_rate;
            double _leverage;
            double _units;
            double _margin_required_adjusted;

            if (_home_currency == _base_currency || _home_currency == _quote_currency)
            {
                _home_rate = Symbol.Bid;
            }
            else
            {
                _home_rate = GetHomeRate(_home_currency, _base_currency);
            }

            _leverage = Account.PreciseLeverage;
            _units = _adjustedunits;

            _margin_required_adjusted = RoundUp(((_home_rate) * _units) / _leverage, 2);
            return _margin_required_adjusted;
        }
    }
}

I back tested and somewhere you are passing maxLotsPerSplit greater than lots to GetVolumeSplits, and it throws exception:

02/01/2019 09:49:00.000 | Crashed in Positions.Closed event with InvalidOperationException: maxLotsPerSplit can't be more than lots Needed

You have to fix that, and please open new threads for your other issues.


@amusleh

amusleh
14 Apr 2022, 10:43

Hi,

I tried to replicate the issue, but I was not able to.

The Positions and PendingOrders collections are derived from IEnumerable, and if your code is running a loop over them and they get modified it will cause an enumerable modified exception which is normal .NET behavior.

These collections aren't thread safe, actually Automate API in general is not thread safe, 

Please post a minimum cBot sample that can reproduce this issue.

And regarding creating a copy of Positions collections, use ToArray instead of ToList, as the later overhead is much larger than the former.


@amusleh

amusleh
14 Apr 2022, 10:00

Hi,

Right now the session data is not available on Automate API, you can code it by using some fixed hours of day for each session.

We might consider adding this data in future version of cTrader Automate.


@amusleh

amusleh
14 Apr 2022, 09:58 ( Updated at: 14 Apr 2022, 18:26 )

Hi,

Can you share a minimum cBot sample code that can reproduce the issue of not being able to access/load that data which is within the range of backtest start/end time.

If we were able to reproduce such an issue then it's a bug. 


@amusleh

amusleh
14 Apr 2022, 09:55

Hi,

The convergence and divergence are two very broad topics and subjective.

But I will give you an example that might help you:

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

namespace cAlgo.Indicators

{
    [Indicator(IsOverlay = false, AccessRights = AccessRights.None)]
    public class NewIndicator : Indicator
    {
        private MovingAverage _fastMa, _slowMa;

        [Parameter("Convergence", DefaultValue = 10, Group = "Thresholds (Pips)")]
        public double ConvergenceThresholds { get; set; }

        [Parameter("Divergence", DefaultValue = 20, Group = "Thresholds (Pips)")]
        public double DivergenceThresholds { get; set; }

        [Parameter("Periods", DefaultValue = 10, Group = "Fast MA")]
        public int FastMaPeriods { get; set; }

        [Parameter("Source", Group = "Fast MA")]
        public DataSeries FastMaSource { get; set; }

        [Parameter("Type", DefaultValue = MovingAverageType.Simple, Group = "Fast MA")]
        public MovingAverageType FastMaType { get; set; }

        [Parameter("Periods", DefaultValue = 20, Group = "Slow MA")]
        public int SlowMaPeriods { get; set; }

        [Parameter("Source", Group = "Slow MA")]
        public DataSeries SlowMaSource { get; set; }

        [Parameter("Type", DefaultValue = MovingAverageType.Simple, Group = "Slow MA")]
        public MovingAverageType SlowMaType { get; set; }

        [Output("Convergence", LineColor = "Green", PlotType = PlotType.Histogram)]
        public IndicatorDataSeries Convergence { get; set; }

        [Output("Divergence", LineColor = "Red", PlotType = PlotType.Histogram)]
        public IndicatorDataSeries Divergence { get; set; }

        [Output("Normal", LineColor = "Yellow", PlotType = PlotType.Histogram)]
        public IndicatorDataSeries Normal { get; set; }

        protected override void Initialize()
        {
            _fastMa = Indicators.MovingAverage(FastMaSource, FastMaPeriods, FastMaType);
            _slowMa = Indicators.MovingAverage(SlowMaSource, SlowMaPeriods, SlowMaType);

            ConvergenceThresholds *= Symbol.PipSize;
            DivergenceThresholds *= Symbol.PipSize;
        }

        public override void Calculate(int index)
        {
            var distance = _fastMa.Result[index] - _slowMa.Result[index];
            var distanceAbs = Math.Abs(distance);

            Convergence[index] = double.NaN;
            Divergence[index] = double.NaN;
            Normal[index] = double.NaN;

            if (distanceAbs <= ConvergenceThresholds)
            {
                Convergence[index] = distance;
            }
            else if (distanceAbs >= DivergenceThresholds)
            {
                Divergence[index] = distance;
            }
            else
            {
                Normal[index] = distance;
            }
        }
    }
}

 


@amusleh

amusleh
14 Apr 2022, 09:38 ( Updated at: 14 Apr 2022, 10:47 )

Hi,

cTrader 4.2 supports both new SDK style projects and legacy projects built by cTrader 4.1 and older versions.

If your Indicator/cBot was built by an older version of cTrader / legacy project then cTrader 4.2 will use Visual Studio 2019 for opening it not 2022.

If you only have Visual Studio 2022 then you can fix this by rebuilding your old legacy style indicators/cBots inside cTrader 4.2 and changing it to Target .NET 6, then you will be able to open it with Visual Studio 2019 but it will not work anymore on cTrader 4.1.

Regarding cTrader 4.2 not showing any error message when it can't find Visual Studio 2019, it does show an error dialog.


@amusleh

amusleh
14 Apr 2022, 09:34

Hi,

What do you mean by "4 Ichimoku components are equal"?

Can you put a comment on your code where you want to get alerted?

What kind of alert you need? sound? email?

And please use code post option next time:

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

namespace cAlgo.Indicators

{
    [Indicator(IsOverlay = true, AccessRights = AccessRights.None)]
    public class IchimokuKinkoHyo : Indicator

    {
        [Parameter(DefaultValue = 9)]
        public int periodFast { get; set; }

        [Parameter(DefaultValue = 26)]
        public int periodMedium { get; set; }

        [Parameter(DefaultValue = 52)]
        public int periodSlow { get; set; }

        [Parameter(DefaultValue = 26)]
        public int DisplacementChikou { get; set; }

        [Parameter(DefaultValue = 26)]
        public int DisplacementCloud { get; set; }

        [Output("TenkanSen", Color = Colors.Red)]
        public IndicatorDataSeries TenkanSen { get; set; }

        [Output("Kijunsen", Color = Colors.Blue)]
        public IndicatorDataSeries KijunSen { get; set; }

        [Output("ChikouSpan", Color = Colors.DarkViolet)]
        public IndicatorDataSeries ChikouSpan { get; set; }

        [Output("SenkouSpanB", Color = Colors.Red, LineStyle = LineStyle.Lines)]
        public IndicatorDataSeries SenkouSpanB { get; set; }

        [Output("SenkouSpanA", Color = Colors.Green, LineStyle = LineStyle.Lines)]
        public IndicatorDataSeries SenkouSpanA { get; set; }

        private double maxfast, minfast, maxmedium, minmedium, maxslow, minslow;

        public override void Calculate(int index)

        {
            if ((index < periodFast) || (index < periodSlow)) { return; }

            maxfast = MarketSeries.High[index];

            minfast = MarketSeries.Low[index];

            maxmedium = MarketSeries.High[index];

            minmedium = MarketSeries.Low[index];

            maxslow = MarketSeries.High[index];

            minslow = MarketSeries.Low[index];

            for (int i = 0; i < periodFast; i++)

            {
                if (maxfast < MarketSeries.High[index - i]) { maxfast = MarketSeries.High[index - i]; }

                if (minfast > MarketSeries.Low[index - i]) { minfast = MarketSeries.Low[index - i]; }
            }

            for (int i = 0; i < periodMedium; i++)

            {
                if (maxmedium < MarketSeries.High[index - i]) { maxmedium = MarketSeries.High[index - i]; }

                if (minmedium > MarketSeries.Low[index - i]) { minmedium = MarketSeries.Low[index - i]; }
            }

            for (int i = 0; i < periodSlow; i++)

            {
                if (maxslow < MarketSeries.High[index - i]) { maxslow = MarketSeries.High[index - i]; }

                if (minslow > MarketSeries.Low[index - i]) { minslow = MarketSeries.Low[index - i]; }
            }

            TenkanSen[index] = (maxfast + minfast) / 2;

            KijunSen[index] = (maxmedium + minmedium) / 2;

            ChikouSpan[index - DisplacementChikou] = MarketSeries.Close[index];

            SenkouSpanA[index + DisplacementCloud] = (TenkanSen[index] + KijunSen[index]) / 2;

            SenkouSpanB[index + DisplacementCloud] = (maxslow + minslow) / 2;
        }
    }
}

 


@amusleh

amusleh
13 Apr 2022, 17:28 ( Updated at: 14 Apr 2022, 18:26 )

Hi,

As I said on my previous post you can't load data earlier than your back test start time, the only way to load data is to change your back test start time and it doesn't matter if it's a week or 6 months.

Here is an example that might help you:

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

namespace NewcBot
{
    [Robot(AccessRights = AccessRights.None)]
    public class NewcBot : Robot
    {
        private const string Time_FORMAT = "dd/MM/yyyy HH:mm:ss";

        private DateTime _startTime;

        [Parameter("Start Time", DefaultValue = Time_FORMAT)]
        public string StartTime { get; set; }

        protected override void OnStart()
        {
            if (!DateTime.TryParseExact(StartTime, Time_FORMAT, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out _startTime))
            {
                Print("Invalid start time");

                Stop();
            }

            LoadBarsData(TimeFrame.Hour);
            LoadBarsData(TimeFrame.Minute30);
            LoadBarsData(TimeFrame.Minute15);
            LoadBarsData(TimeFrame.Minute5);
        }

        protected override void OnTick()
        {
            if (Server.TimeInUtc < _startTime)
            {
                // Do your calculation here and continue

                return;
            }

            // Here do your trading
        }

        protected override void OnBar()
        {
            if (Server.TimeInUtc < _startTime)
            {
                // Do your calculation here and continue

                return;
            }

            // Here do your trading
        }

        private void LoadBarsData(TimeFrame timeFrame)
        {
            var bars = MarketData.GetBars(timeFrame);

            while (bars.LoadMoreHistory() > 0)
            {
            }

            Print("First Bar Open Time: {0:dd/MM/yyyy HH:mm:ss} | TimeFrame: {1}", bars[0].OpenTime, timeFrame);
        }
    }
}

You can change your back test start time to an earlier date for loading more data and use cBot start time parameter to skip trading before a specific date.


@amusleh

amusleh
13 Apr 2022, 14:20

Hi,

It looks like the documentation is missing for PercentageOfValue commission type, we will add this to documentation of PreciseTradingCommissionRate.

When symbol commission type is PERCENTAGE_OF_VALUE divide the PreciseTradingCommissionRate to 10^5.


@amusleh

amusleh
13 Apr 2022, 13:19 ( Updated at: 13 Apr 2022, 13:21 )

Hi,

When you made the connection and sent the LogonRequest to keep alive your connection you have to keep sending Test Request based on your session heartbeat interval and responding to server Test request by sending an heartbeat and using server Test Request TestReqID.

Whenever you send a Test Request server will send you back an heartbeat and you should use an incremental value for Test Request TestReqID, and increment it by one on each subsequent Test Request you send to the server.

This way you will be able to keep your connection alive.

I tested it on our Python package console sample and it works, the issue with our console sample is that it blocks the main running thread while waiting for getting user input, we use a time out based on user config heartbeat amount and when it times out the thread will be released again to continue receiving and sending message from/to server, and after thread release we send a Test Request to keep the connection alive. 

I just released a new version of our Python package and I made some changes on console sample, update to latest version and also update your console sample, then test it see if it can keep the connection alive or not.

If you send a Test request before sending Logon message your connection will be dropped.

 


@amusleh

amusleh
13 Apr 2022, 11:53

Hi,

This might help you:

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

namespace NewcBot
{
    [Robot(AccessRights = AccessRights.None)]
    public class NewcBot : Robot
    {
        private const string DATE_FORMAT = "dd/MM/yyyy HH:mm:ss";

        [Parameter("Initial Balance", DefaultValue = 10000)]
        public double InitialBalance { get; set; }

        [Parameter("Start Time", DefaultValue = DATE_FORMAT)]
        public string StartTime { get; set; }

        [Parameter("End Time", DefaultValue = DATE_FORMAT)]
        public string EndTime { get; set; }

        protected override void OnStart()
        {
            DateTime startTime, endTime;

            if (!DateTime.TryParseExact(StartTime, DATE_FORMAT, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out startTime))
            {
                Print("Invalid start time");

                Stop();
            }

            if (!DateTime.TryParseExact(EndTime, DATE_FORMAT, CultureInfo.InvariantCulture, DateTimeStyles.AssumeUniversal, out endTime))
            {
                Print("Invalid end time");

                Stop();
            }

            var history = from trade in History
                          where trade.EntryTime >= startTime && trade.EntryTime <= endTime
                          where trade.ClosingTime >= startTime && trade.ClosingTime <= endTime
                          orderby trade.ClosingTime
                          select trade;

            var troughValue = InitialBalance;
            var peakValue = InitialBalance;
            var currentBalance = InitialBalance;
            var tradeCount = 0;
            var maxDrawdown = 0.0;
            DateTime? peakTime = null;
            DateTime? troughTime = null;

            foreach (var trade in history)
            {
                // As we don't have account registration date we use the first trade
                // closing time as initial value for peak and trough times
                if (peakTime.HasValue == false) peakTime = trade.EntryTime;
                if (troughTime.HasValue == false) troughTime = peakTime;

                tradeCount++;

                currentBalance += trade.NetProfit;

                var isPeakOrTroughChanged = false;

                if (currentBalance > peakValue)
                {
                    peakValue = currentBalance;

                    isPeakOrTroughChanged = true;

                    peakTime = trade.ClosingTime;
                }

                if (currentBalance < troughValue)
                {
                    troughValue = currentBalance;

                    isPeakOrTroughChanged = true;

                    troughTime = trade.ClosingTime;
                }

                if (isPeakOrTroughChanged && peakTime < troughTime)
                {
                    var currentDrawdown = (troughValue - peakValue) / peakValue * 100;

                    if (currentDrawdown < maxDrawdown)
                    {
                        maxDrawdown = currentDrawdown;
                    }
                }
            }

            Print("Peak Value: {0} | Trough Value: {1} | Max DD %: {2} | # Of Trades: {3}", peakValue, troughValue, maxDrawdown, tradeCount);
        }
    }
}

You have to pass the initial account balance and start/end time for max drawdown period.


@amusleh

amusleh
13 Apr 2022, 10:55

Hi,

I tested on our cTraderFixPy Python package and the connection remained open after sending heartbeat, can you try cTraderFixPy console sample and see if it happens or not.

spotware/cTraderFixPy: A Python package for interacting with cTrader FIX API. (github.com)


@amusleh

amusleh
13 Apr 2022, 10:50

Hi,

Different types of symbols can have different types of commissions, you can fine commission types here: Models - cTrader Open API (spotware.github.io)

To correctly interpret a position/trade commission amount you have to know first what kind of commission the symbol uses.

Then based on symbol commission type you know what a position commission value represents, for symbols that uses PercentageOfValue you can get the actual percentage by dividing the position commission value to 100000.

In your case: 17500 / 100000 = 0.175


@amusleh

amusleh
13 Apr 2022, 10:36

Hi,

Can you post the full code of cBot with parameter values? and on which Symbol it happens? 


@amusleh

amusleh
13 Apr 2022, 10:33

Hi, 

Can you post a cBot sample code that can reproduce this issue?


@amusleh

amusleh
13 Apr 2022, 10:32 ( Updated at: 14 Apr 2022, 18:26 )

Hi,

I followed your discussion with Panagiotis on Telegram, the issue is with your misunderstanding of how cTrader backtester works.

When you load another time frame, symbol, or current time frame bars data cTrader uses the back tester start date as the farthest point in time that it will load the data.

So for example, if you start a back test and set the start/end time to 01/01/2020 and 01/01/2022, and try to load more bars data of current symbol time frame or any other bars, the amount of data that you will be able to load is limited to your backtest session start/end time range, even if the data is available cTrader will not load the data before your back test start time or after it's end time, because cTrader thinks you need only the amount of data relative to your backtest session start/end time and this is true for most cases.

If you want to load more historical data then change your back test session start time.

For example, lets back test this cBot:

using cAlgo.API;

namespace NewcBot
{
    [Robot(AccessRights = AccessRights.None)]
    public class NewcBot : Robot
    {
        protected override void OnStart()
        {
            LoadBarsData(TimeFrame.Hour);
            LoadBarsData(TimeFrame.Minute30);
            LoadBarsData(TimeFrame.Minute15);
            LoadBarsData(TimeFrame.Minute5);
        }
        
        private void LoadBarsData(TimeFrame timeFrame)
        {
            var bars = MarketData.GetBars(timeFrame);
            
            while (bars.LoadMoreHistory() > 0)
            {
            }

            Print("First Bar Open Time: {0:dd/MM/yyyy HH:mm:ss} | TimeFrame: {1}", bars[0].OpenTime, timeFrame);
        }
    }
}

Set the start and time to: 19/11/2021 and 12/04/2022

Check the logs tab what it prints for each time frame.

Then move the start time few months back, and backtest it again, and check the cBot logs tab.

The only way you can load more data is to change the start time of back test based on amount of data you want to load.

Now if you are saying I just want to use the data for some calculation and not for trading, then you can skip the time you don't want to trade by checking the Server.Time.


@amusleh

amusleh
12 Apr 2022, 12:35 ( Updated at: 14 Apr 2022, 18:26 )

Hi,

If Bars LoadMoreHisotry method is returning missing data then please tell me which broker you are using? and which symbol and time frame has this issue.

 


@amusleh

amusleh
12 Apr 2022, 10:34

Hi,

This might solve your issue:

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

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class IcebergDemo : Robot
    {
        [Parameter("Label", DefaultValue = "_")]
        public string _name { get; set; }

        [Parameter("Maximum Visible Lots", Group = "Iceberg Settings", DefaultValue = 1, MaxValue = 100, MinValue = 1, Step = 1)]
        public double _maxvisiblelots { get; set; }

        [Parameter("Total Lots Needed", Group = "Risk", DefaultValue = 1.0, MaxValue = 100.0, MinValue = 0.01, Step = 0.01)]
        public double _total_lots_needed { get; set; }

        [Parameter("Protection", Group = "Risk", DefaultValue = 7.5, MaxValue = 10000, MinValue = 0.1, Step = 0.1)]
        public double _protection { get; set; }

        [Parameter("Stop Loss", Group = "Risk", DefaultValue = 10, MaxValue = 10000, MinValue = 0.1, Step = 0.1)]
        public double _stoploss { get; set; }

        [Parameter("Take Profit", Group = "Risk", DefaultValue = 15, MaxValue = 10000, MinValue = 0.1, Step = 0.1)]
        public double _takeprofit { get; set; }

        private bool _buy, _sell;

        private MovingAverage _fast;
        private MovingAverage _slow;

        protected override void OnStart()
        {
            _fast = Indicators.MovingAverage(Bars.ClosePrices, 12, MovingAverageType.Exponential);
            _slow = Indicators.MovingAverage(Bars.ClosePrices, 26, MovingAverageType.Exponential);
        }

        protected override void OnBar()
        {
            var ActiveBuys = Positions.FindAll(_name, Symbol.Name, TradeType.Buy);
            var ActiveSells = Positions.FindAll(_name, Symbol.Name, TradeType.Sell);

            _buy = _fast.Result.HasCrossedAbove(_slow.Result.Last(1), 1);
            _sell = _fast.Result.HasCrossedBelow(_slow.Result.Last(1), 1);

            if (ActiveBuys.Length > 0)
            {
                for (var i = 0; i < ActiveBuys.Length; i++)
                {
                    var buydistance = Math.Round((Symbol.Bid - ActiveBuys[0].EntryPrice) / Symbol.PipSize, 1);

                    if (buydistance >= _protection)
                    {
                        Print("Buy #{0} To Breakeven", History.Count.ToString());
                        //modify
                        ActiveBuys[0].ModifyStopLossPrice(ActiveBuys[0].EntryPrice);
                    }
                }
            }

            if (ActiveSells.Length > 0)
            {
                for (var i = 0; i < ActiveBuys.Length; i++)
                {
                    var selldistance = Math.Round((ActiveSells[0].EntryPrice - Symbol.Ask) / Symbol.PipSize, 1);

                    if (selldistance >= _protection)
                    {
                        Print("Sell #{0} To Breakeven", History.Count.ToString());
                        //modify
                        ActiveSells[0].ModifyStopLossPrice(ActiveSells[0].EntryPrice);
                    }
                }
            }

            var volumes = GetVolumeSplits(_total_lots_needed, _maxvisiblelots);

            if (volumes.Sum() != Symbol.QuantityToVolumeInUnits(_total_lots_needed))
            {
                throw new InvalidOperationException(string.Format("Volumes doesn't match, lots sum: {0} | lots to Split: {1} | max split size: {2} | # of splits: {3}", Symbol.VolumeInUnitsToQuantity(volumes.Sum()), _total_lots_needed, _maxvisiblelots, volumes.Length));
            }

            if (_buy && ActiveBuys.Length == 0)
            {
                for (var i = 0; i < volumes.Length; i++)
                {
                    ExecuteMarketOrder(TradeType.Buy, SymbolName, volumes[i], _name, _stoploss, _takeprofit, History.Count.ToString());
                    Print("History Count: {0}", History.Count.ToString());
                }
            }

            if (_sell && ActiveSells.Length == 0)
            {
                for (var i = 0; i < volumes.Length; i++)
                {
                    ExecuteMarketOrder(TradeType.Sell, SymbolName, volumes[i], _name, _stoploss, _takeprofit, History.Count.ToString());
                    Print("History Count: {0}", History.Count.ToString());
                }
            }
        }

        private double[] GetVolumeSplits(double lots, double maxLotsPerSplit)
        {
            if (maxLotsPerSplit > lots)
                throw new InvalidOperationException("maxLotsPerSplit can't be greater than lots");

            var modulus = lots % maxLotsPerSplit;

            var numberOfFragments = Convert.ToInt32((lots - modulus) / maxLotsPerSplit);

            if (modulus > 0)
                numberOfFragments++;

            var lotsPerFragement = lots / numberOfFragments;

            var unitsPerFragment = Symbol.QuantityToVolumeInUnits(lotsPerFragement);

            var unitsPerFragementNormalized = Symbol.NormalizeVolumeInUnits(unitsPerFragment, RoundingMode.Up);

            var volumes = new double[numberOfFragments];

            for (var i = 0; i < numberOfFragments; i++)
            {
                volumes[i] = i == volumes.Length - 1 && modulus > 0 ? unitsPerFragment - ((unitsPerFragementNormalized - unitsPerFragment) * (volumes.Length - 1)) : unitsPerFragementNormalized;
            }

            return volumes;
        }
    }
}

But it doesn't group the open positions based on their label/comment, I just followed your cBot design and added the loops for modification.

If you want to group open positions based on their label or comment you can use Linq group by method:

            var positionsGroupedByLabel = Positions.GroupBy(position => position.Label);

            foreach (var positionsGroup in positionsGroupedByLabel)
            {
                // Iterate over a group of positions with the same label
                foreach (var position in positionsGroup)
                {
                    // Do anything you want with the position here
                }
            }

            // you can group the positions based on comment like this/
            // You can use any other property of the position instead of comment/label
            // for grouping them
            var positionsGroupedByComment = Positions.GroupBy(position => position.Comment);

 


@amusleh

amusleh
12 Apr 2022, 10:20

RE: RE:

Prospect said:

xabbu said:

Thank you very much, Panagiotis - I think I have found a way to prevent multiple execution due to the speed of the ...async functionality. 

Do you mind sharing your solution to this?

Hi,

This might help you:

using cAlgo.API;

namespace NewcBot
{
    [Robot(AccessRights = AccessRights.None)]
    public class NewcBot : Robot
    {
        private TradeOperation _operation;

        protected override void OnStart()
        {
            // Execute a new market order only if there is no ongoing operation
            if (_operation == null)
            {
                _operation = ExecuteMarketOrderAsync(TradeType.Buy, SymbolName, Symbol.VolumeInUnitsMin, ExecuteMarketOrderCallback);
            }
        }

        private void ExecuteMarketOrderCallback(TradeResult result)
        {
            _operation = null;

            // use result for getting the opened position
            // or error if there was any
        }
    }
}

 


@amusleh