Topics
Forum Topics not found
Replies
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, 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, 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, 11:01
Hay varios indicadores VWAP personalizados desarrollados por la comunidad, puede usarlos.
Como ejemplo:
VWAP for every bar Indicator | Algorithmic Forex Trading | cTrader Community
VWAP Indicator | Algorithmic Forex Trading | cTrader Community
VWAP (Volume Weighted Average Price) Indicator | Algorithmic Forex Trading | cTrader Community
@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: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, 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
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:
I back tested and somewhere you are passing maxLotsPerSplit greater than lots to GetVolumeSplits, and it throws exception:
You have to fix that, and please open new threads for your other issues.
@amusleh