Topics

Forum Topics not found

Replies

amusleh
20 Apr 2022, 09:23

Hi,

Try this:

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

namespace cAlgo
{
    [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class USDCorelation : Indicator
    {
        [Parameter("Reset Time", DefaultValue = "2022-4-19 22:15:00", Group = "Ver. 1.01")]
        public string ResetPoint { get; set; }

        [Output("GBP", LineColor = "FFB600FF", Thickness = 2, PlotType = PlotType.DiscontinuousLine)]
        public IndicatorDataSeries GBP { get; set; }

        [Output("NZD", LineColor = "FF00BF00", Thickness = 2, PlotType = PlotType.DiscontinuousLine)]
        public IndicatorDataSeries NZD { get; set; }

        private DateTime dt_RstPnt; //Reset Time Point;

        //Bars to load for Calculate
        private Bars EURUSD, GBPUSD, NZDUSD;

        //Index at ResetTimePoint of EachSymbol;
        private int in_EURUSD, in_GBPUSD, in_NZDUSD;

        //BaseValue at ResetTimePoint of EachSymbol;
        private double db_EURUSD, db_GBPUSD, db_NZDUSD;

        protected override void Initialize()
        {
            //1.Get MarketData in Series Ready
            EURUSD = MarketData.GetBars(Bars.TimeFrame, "EURUSD");
            GBPUSD = MarketData.GetBars(Bars.TimeFrame, "GBPUSD");
            NZDUSD = MarketData.GetBars(Bars.TimeFrame, "NZDUSD");

            //2.Get ResetTimePoint Index
            dt_RstPnt = DateTime.Parse(ResetPoint).Add(-Application.UserTimeOffset);

            in_EURUSD = EURUSD.OpenTimes.GetIndexByTime(dt_RstPnt);
            in_GBPUSD = GBPUSD.OpenTimes.GetIndexByTime(dt_RstPnt);
            in_NZDUSD = NZDUSD.OpenTimes.GetIndexByTime(dt_RstPnt);

            //3.Get OpenPrice of each symbol at ResetTimePoint
            db_EURUSD = EURUSD[in_EURUSD].Open;
            db_GBPUSD = GBPUSD[in_GBPUSD].Open;
            db_NZDUSD = NZDUSD[in_NZDUSD].Open;
        }

        public override void Calculate(int index)
        {
            if (Bars.OpenTimes[index] < dt_RstPnt) return;
                
            var gbpIndex = GBPUSD.OpenTimes.GetIndexByTime(Bars.OpenTimes[index]);
            var gbpGap = gbpIndex - in_GBPUSD;
            
            GBP[index] = ((GBPUSD[in_GBPUSD + gbpGap].Close) + db_EURUSD - db_GBPUSD);
            
            var nzdIndex = NZDUSD.OpenTimes.GetIndexByTime(Bars.OpenTimes[index]);
            var nzdGap = nzdIndex - in_NZDUSD;
            
            NZD[index] = ((NZDUSD[in_NZDUSD + nzdGap].Close) + db_EURUSD - db_NZDUSD);
        }
    }
}

 


@amusleh

amusleh
20 Apr 2022, 09:10

Hi,

You can use the TriggerAlert method instead of ShowPopup:

using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Alert;

namespace cAlgo
{
    [Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.FullAccess)]
    public class ADXwithAlert : Indicator
    {
        private AverageDirectionalMovementIndexRating _adx;
        private int _lastAlertBarIndex;

        [Parameter(DefaultValue = 14, Group = "ADX")]
        public int Periods { get; set; }

        [Parameter("Level", DefaultValue = 25, Group = "Alert")]
        public double AlertLevel { get; set; }

        [Parameter("Setup", DefaultValue = 25, Group = "Alert")]
        public bool AlertSetup { get; set; }

        [Output("ADX", LineColor = "Blue")]
        public IndicatorDataSeries Adx { get; set; }

        [Output("ADXR", LineColor = "Yellow")]
        public IndicatorDataSeries AdxR { get; set; }

        [Output("DI+", LineColor = "Green")]
        public IndicatorDataSeries DiPlus { get; set; }

        [Output("DI-", LineColor = "Red")]
        public IndicatorDataSeries DiMinus { get; set; }

        protected override void Initialize()
        {
            _adx = Indicators.AverageDirectionalMovementIndexRating(Periods);

            if (AlertSetup)
            {
                Notifications.ShowPopup();
            }
        }

        public override void Calculate(int index)
        {
            Adx[index] = _adx.ADX[index];
            AdxR[index] = _adx.ADXR[index];
            DiPlus[index] = _adx.DIPlus[index];
            DiMinus[index] = _adx.DIMinus[index];

            if (IsLastBar && _lastAlertBarIndex != index && Adx[index] > AlertLevel)
            {
                _lastAlertBarIndex = index;

                var type = string.Format("ADX above {0}", AlertLevel);

           AlertModel alert = new AlertModel
            {
                TimeFrame = TimeFrame,
                Symbol = Symbol,
                Price = Adx[index],
                TriggeredBy = "ADX with Alert",
                Type = type,
                Time = Server.Time
            };

                Notifications.TriggerAlert(alert);
            }
        }
    }
}

It only works if you already setup the telegram alert on popup settings.


@amusleh

amusleh
19 Apr 2022, 09:48

RE: RE:

ncel01 said:

amusleh said:

Hi,

Try this:

using cAlgo.API;
using System.Linq;
using System.Threading;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class NewcBot : Robot
    {
        private bool allPositionsClosed = false;

        protected override void OnStart()
        {
            var tradeResults = SetTrade();

            Print("Closing Positions");

            foreach (var tradeResult in tradeResults)
            {
                ClosePosition(tradeResult.Position);
            }

            allPositionsClosed = true;

            Print("All positions closed: ", allPositionsClosed);

            Stop();
        }

        private TradeResult[] SetTrade()
        {
            var tradeResults = new TradeResult[3];

            tradeResults[0] = ExecuteMarketOrder(TradeType.Buy, SymbolName, Symbol.QuantityToVolumeInUnits(0.01), "order_1");
            tradeResults[1] = ExecuteMarketOrder(TradeType.Buy, SymbolName, Symbol.QuantityToVolumeInUnits(0.01), "order_2");
            tradeResults[2] = ExecuteMarketOrder(TradeType.Buy, SymbolName, Symbol.QuantityToVolumeInUnits(0.01), "order_3");

            while (tradeResults.Any(result => result.Position is null))
            {
                Thread.Sleep(100);

                RefreshData();
            }

            return tradeResults;
        }

        protected override void OnStop()
        {
        }
    }
}

 

Hi amusleh,

I am afraid that creating time delays will not solve the issue as there are dozens/hundreds of orders involved and this wouldn't be an efficient/effective solution at all, I believe.

Because either the Positions collection is not updated yet (in my case), it gets updated after the thread is released from the execution of an event (OnStart. OnBar, OnTick, etc...) or even if it's updated (based on your output) the close position request can't suspend the current execution and jump to executing the event handlers (here Positions_Closed).

What can be a reason for such limitation? Wouldn't be possible that these follow the code sequence, as it happens for user-defined methods?

I am sure this would be much more intuitive and, as result, the outcome a lot more effective. Looks to me that managing a high amount of orders/positions seems to be quite hard (if not impossible) considering that internal (API) methods/functions execute asynchronously with respect to the code sequence.

Is there available any diagram (or similar) illustrating how execution is processed through cTrader API functions/methods? I believe this particularity is not really a detail when it comes to creating any "reliable" (behaving as expected) cBot/strategy. Maybe I can get to a solution if I am fully aware of how this really works. So far, it looks like I've been on a loop trying to get this solved.

Does this characteristic/limitation only apply to the events or, to any method? Only to methods?

Another question:  PendingOrders_Created() and PlaceStopOrderAsync(). Do these methods always get triggered simultaneously (as a pair) or, can be that some code is still executed in between?

On my last attempt, I've added PendingOrders_Created() event. The goal was only to modify the value of a variable inside the event. However, soon I noticed that, unlike for a single order, the amount of orders keeps increasing whenever the "real" order is placed. A "clone" order is created, which does not happen if only a single order is initially defined. I also don't know why this is happening nor I do know how to solve this.

My strategy has 2000+ code lines and it is almost finished (I would say). So far, I've been able to solve every issue I noticed but not this one. The only issue, still remaining, is in fact, related to the loop ( pending order placed --> pending order filled --> position closed --> pending order placed ... ), which is still not working as expected, after many tries.

Thank you once again!

Hi,

There is no way to prevent waiting, Automate API is an abstraction over sending and receiving messages to cTrader server, if you have done any kind of network programming you know that when you send something to server/client you have to wait to get back a response, when you send a request from Automate API you have to wait to get back the response from server, there we use the Thread.Sleep method to wait until we receive response for all of our order requests from server.

Regarding your other points, all operations on a cBot is executed by a single thread, and a single thread can execute a single line of code at the time.

If you understand how a single thread environment works you will be able to understand how cTrader automate executes your code.

cTrader automate executes your cBot overloaded methods and event handlers one by one, if the thread is inside OnTick method and a position is closed it can't suspend OnTick method execution and jump to your Position closed event handler, first it finish the execution of OnTick method then it executes your position closed event handler, the same is true for all other methods, event handlers, and collections (Positions and PendingOrders).

When you send something to server you have to wait to get back the response, you can't expect the response to be available instantly, if you sent a market order request you can't expect Positions collections to have your market order position immediately.

A better approach for you to control the sequence of execution will be used the Async calls with callbacks, the async method callbacks will be called when a response is received from server, and while waiting for server response you can execute some other code instead of suspending the thread and waiting for a server response.


@amusleh

amusleh
18 Apr 2022, 18:48

Hi,

We are working on a cross platform console app that will allow you to run cBots on both Linux and Mac.

Regarding other Automate features like custom indicators you can only use them on a cTrader desktop which will not be available for Mac or Linux because it uses WPF.

 


@amusleh

amusleh
18 Apr 2022, 10:39

Hi,

Try this:

using cAlgo.API;
using System.Linq;
using System.Threading;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class NewcBot : Robot
    {
        private bool allPositionsClosed = false;

        protected override void OnStart()
        {
            var tradeResults = SetTrade();

            Print("Closing Positions");

            foreach (var tradeResult in tradeResults)
            {
                ClosePosition(tradeResult.Position);
            }

            allPositionsClosed = true;

            Print("All positions closed: ", allPositionsClosed);

            Stop();
        }

        private TradeResult[] SetTrade()
        {
            var tradeResults = new TradeResult[3];

            tradeResults[0] = ExecuteMarketOrder(TradeType.Buy, SymbolName, Symbol.QuantityToVolumeInUnits(0.01), "order_1");
            tradeResults[1] = ExecuteMarketOrder(TradeType.Buy, SymbolName, Symbol.QuantityToVolumeInUnits(0.01), "order_2");
            tradeResults[2] = ExecuteMarketOrder(TradeType.Buy, SymbolName, Symbol.QuantityToVolumeInUnits(0.01), "order_3");

            while (tradeResults.Any(result => result.Position is null))
            {
                Thread.Sleep(100);

                RefreshData();
            }

            return tradeResults;
        }

        protected override void OnStop()
        {
        }
    }
}

 


@amusleh

amusleh
18 Apr 2022, 10:23

Hi,

Please create a job request or contact one of the consultant companies.


@amusleh

amusleh
18 Apr 2022, 10:21

Hi,

Can you provide a sample cBot that can reproduce this issue? not the one that you use just a sample.


@amusleh

amusleh
18 Apr 2022, 10:18

Hi,

Do you mean the current bar index?

To access current bar index of another time frame Bars you can use Bars.OpenTimes.GetIndexByTime method: cAlgo API Reference - TimeSeries Interface (ctrader.com)


@amusleh

amusleh
18 Apr 2022, 10:16

Hi,

If you changed the target of your indicator and then recompiled/rebuild an indicator inside cTrader 4.2 then it's project structure changed, if you compile it again on cTrader 4.1 you will get that Guid error.

To solve this issue you can recreate those indicators inside cTrader 4.1 and just copy the code from old ones to new ones.

cTrader 4.2 is backward compatible, but cTrader 4.1 can't use cTrader 4.2 indicators/cBots. 


@amusleh

amusleh
18 Apr 2022, 10:12

Hi,

I just tested and it worked fine, with both .NET 6 and .NET framework targets and embedded/SDK compilation.


@amusleh

amusleh
18 Apr 2022, 10:04

Hi,

When did you faced this issue? can you try again now and see if it works or not and did you received any error from server?


@amusleh

amusleh
18 Apr 2022, 10:03

Hi,

You sent use the source code file of Sample EMA indicator, I tested both version 4.1 and 4.2, it works fine.

I referenced it by another indicator and it worked, then I used it on version 4.2 and it worked without any issue.

I was not able to replicate the issue you are facing.

I asked you to send us the indicator ".algo" file with it's solution directory, zip them and then send them.

Regarding the exceptions you are facing, please submit a troubleshoot report with the forum thread link.


@amusleh

amusleh
15 Apr 2022, 13:09

Hi,

You can use this:

using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Alert;

namespace cAlgo
{
    [Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.FullAccess)]
    public class ADXwithAlert : Indicator
    {
        private AverageDirectionalMovementIndexRating _adx;
        private int _lastAlertBarIndex;

        [Parameter(DefaultValue = 14, Group = "ADX")]
        public int Periods { get; set; }

        [Parameter("Level", DefaultValue = 25, Group = "Alert")]
        public double AlertLevel { get; set; }

        [Parameter("Setup", DefaultValue = 25, Group = "Alert")]
        public bool AlertSetup { get; set; }

        [Output("ADX", LineColor = "Blue")]
        public IndicatorDataSeries Adx { get; set; }

        [Output("ADXR", LineColor = "Yellow")]
        public IndicatorDataSeries AdxR { get; set; }

        [Output("DI+", LineColor = "Green")]
        public IndicatorDataSeries DiPlus { get; set; }

        [Output("DI-", LineColor = "Red")]
        public IndicatorDataSeries DiMinus { get; set; }

        protected override void Initialize()
        {
            _adx = Indicators.AverageDirectionalMovementIndexRating(Periods);

            if (AlertSetup)
            {
                Notifications.ShowPopup();
            }
        }

        public override void Calculate(int index)
        {
            Adx[index] = _adx.ADX[index];
            AdxR[index] = _adx.ADXR[index];
            DiPlus[index] = _adx.DIPlus[index];
            DiMinus[index] = _adx.DIMinus[index];

            if (IsLastBar && _lastAlertBarIndex != index && Adx[index] > AlertLevel)
            {
                _lastAlertBarIndex = index;

                var type = string.Format("ADX above {0}", AlertLevel);

                Notifications.ShowPopup(TimeFrame, Symbol, type, "ADX with Alert");
            }
        }
    }
}

It uses the cAlgo.API.Alert Nuget package.

To use it:

  1. Copy the code on a new indicator and save
  2. Open indicator in Visual studio
  3. Change the Project .NET framework to target 4.5
  4. Install cAlgo.API.Alert Nuget package
  5. Rebuild the indicator from Visual Studio

For a more detailed guide check: Installation · afhacker/ctrader-alert_popup Wiki (github.com)

For Telegram notification setup check: Telegram · afhacker/ctrader-alert_popup Wiki (github.com)


@amusleh

amusleh
15 Apr 2022, 08:57

Hi,

cTrader cBots/Indicators run on a single thread and their code indeed are executed sequentially.

Regarding your code, this is the result I got:

15/04/2022 13:10:42.947 | CBot instance [New cBot, EURUSD, h1] started.
15/04/2022 13:10:43.650 | Stage #1
15/04/2022 13:10:44.134 | Stage #2
15/04/2022 13:10:44.556 | Stage #5
15/04/2022 13:10:44.556 | Stage #4
15/04/2022 13:10:44.588 | CBot instance [New cBot, EURUSD, h1] stopped.

The Stage #1 gets printed because it's the first line to be executed inside SetTrade method, all good for now.

The Stage #2 is printed as it's the next line after Stack execution pointer returns from SetMethod.

Next is the loop for closing positions, it iterates over Positions collection and then sends a Close request for each of the positions, then why "Stage #3" is not printed?

Because either the Positions collection is not updated yet (in my case), it gets updated after the thread is released from the execution of an event (OnStart. OnBar, OnTick, etc...) or even if it's updated (based on your output) the close position request can't suspend the current execution and jump to executing the event handlers (here Positions_Closed).

It waits for thread to become free and then it executes the event handler of Positions.Closed event, that's what happened on your case.

Same thing is happening for Stop call, when you call the Stop method it can't suspend the execution of OnStart method and jump to execution of OnStop, instead it first finish the OnStart method and then it executes the OnStop and Positions closed event handler.

As I said each cBot/Indicator runs on a single thread, the thread can only execute one event per time, if you are inside OnTick it can't execute OnBar code or if you are inside OnStart it can't execute the code inside OnStop even if you call it from OnStart, it first finish the current event call and then the next one.

cTrader Automate uses event loop, and it calls each event sequentially when they arrive, only a single event is executed at a time.


@amusleh

amusleh
15 Apr 2022, 08:37

RE: TEST ROBOT

matthewgrayiveta said:

I didnt  have before any trouble with test my robot when i update spotwere i start have this problem is not my robot is spotwere update problem 

Hi,

We can't reproduce this issue, please provide a sample that can reproduce this issue and then we will be able to help you.


@amusleh

amusleh
15 Apr 2022, 08:36

RE: RE:

waym77 said:

amusleh said:

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.

Thanks, I'll add it manually.

What are the fixed hours according to cTrader?

Hi,

cTrader uses standard market exchange opening/closing times for each session.


@amusleh

amusleh
14 Apr 2022, 11:12

Hi,

It would be very rare for all of them to be exactly equal, you have to use some kind of threshold.

Here is the code with alert:

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

namespace cAlgo.Indicators

{
    [Indicator(IsOverlay = true, AccessRights = AccessRights.FullAccess)]
    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; }

        [Parameter("Threshold (Pips)", DefaultValue = 1, MinValue = 0, Group = "Alert")]
        public double AlertThreshold { get; set; }

        [Parameter("Sound File Path", Group = "Alert")]
        public string AlertSoundFile { 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;

        protected override void Initialize()
        {
            AlertThreshold *= Symbol.PipSize;
        }

        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;

            if (IsLastBar && Math.Abs(TenkanSen[index] - KijunSen[index]) <= AlertThreshold && Math.Abs(KijunSen[index] - SenkouSpanA[index]) <= AlertThreshold && Math.Abs(SenkouSpanA[index] - SenkouSpanB[index]) <= AlertThreshold)
            {
                Notifications.PlaySound(AlertSoundFile);
            }
        }
    }
}

Changed the threshold to 0 if you want to get alerted only if they were exactly equal, otherwise set some few pips as your tolerance level.

The values of them are in double, 99.99% of the time they will not be exactly equal as some decimal place might have different value.


@amusleh

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