Topics
Forum Topics not found
Replies
amusleh
30 Mar 2021, 09:57
RE: RE:
JerryTrader said:
amusleh said:
You have to pass all indicator parameters a value on "GetIndicator" method, otherwise it will not work, and you can only use the indicator outputs not properties.
Why are you saying not to use properties ? In some of my bots and indicators, I use properties and events, and it seems to work fine.
I double checked the documentation, but no mentions saying if we should or shouldn't use properties.I read in a topic (but can't find it back) that an indicator needs to be calculated (by accessing an output for example) before being able to access a property.
Can you please detail your answer a bit more ?
Thanks !
Hi,
You can use properties and events, but some times they don't behave the way they should, so that's why I said not use properties when referencing custom indicators on a cBot.
@amusleh
amusleh
29 Mar 2021, 12:31
Hi,
Why you want to embed JS on your indicators/cBots? technically its possible, but it will be an heavy cBot or indicator.
You can do it with a rich browser control of WPF or WinForms, something like CefSharp, in future when cTrader migrated to .NET 5 you can use the Microsoft Edge based webview control.
If you are looking to use JS for building something for cTrader I recommend you to use Open API.
@amusleh
amusleh
29 Mar 2021, 12:27
This sample might help you:
using cAlgo.API;
using System;
using System.Globalization;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class CloseAllOnMarketCloseSample : Robot
{
private TimeSpan _closeBeforeTime;
[Parameter("Close Before", DefaultValue = "00:05:00")]
public string CloseBeforeTime { get; set; }
protected override void OnStart()
{
if (!TimeSpan.TryParse(CloseBeforeTime, CultureInfo.InvariantCulture, out _closeBeforeTime))
{
Print("You have provided invalid value for Close Before parameter");
Stop();
}
Timer.Start(1);
}
protected override void OnTimer()
{
var timeTillClose = Symbol.MarketHours.TimeTillClose();
if (!Symbol.MarketHours.IsOpened() || timeTillClose > _closeBeforeTime) return;
foreach (var position in Positions)
{
ClosePosition(position);
}
}
}
}
@amusleh
amusleh
29 Mar 2021, 11:45
I changed the lists to IndicatorDataSeries and now the results are matching, the indicator might be changed so please modify it if something is changed related to indicator formula.
using cAlgo.API;
using cAlgo.API.Indicators;
using System;
using System.Linq;
namespace cAlgo
{
[Indicator(IsOverlay = false, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class ConnorsCRSI11 : Indicator
{
private IndicatorDataSeries listOfAvgGain;
private IndicatorDataSeries listOfAvgLoss;
private IndicatorDataSeries listOfGain;
private IndicatorDataSeries listOfLoss;
private IndicatorDataSeries listOfStreaks;
[Output("Main", Color = Colors.Orange, IsHistogram = true, PlotType = PlotType.Histogram)]
public IndicatorDataSeries Result { get; set; }
[Parameter(DefaultValue = 3.0)]
public int rsiPeriods { get; set; }
[Parameter(DefaultValue = 2.0)]
public int streakPeriods { get; set; }
[Parameter(DefaultValue = 100.0)]
public int rocPeriods { get; set; }
public RelativeStrengthIndex rsi;
public int streak;
public double avgGain1;
public double avgLoss1;
public double avgGain;
public double avgLoss;
public int startIndex;
public double percentRank = 0.0;
protected override void Initialize()
{
listOfAvgGain = CreateDataSeries();
listOfAvgLoss = CreateDataSeries();
listOfGain = CreateDataSeries();
listOfLoss = CreateDataSeries();
listOfStreaks = CreateDataSeries();
rsi = Indicators.RelativeStrengthIndex(Bars.ClosePrices, rsiPeriods);
startIndex = 1;
}
public override void Calculate(int index)
{
if (index <= rocPeriods) return;
UpDownStreak(streakPeriods, index);
var rsiStreakValue = GetRsiStreak(streakPeriods, index);
Roc(rocPeriods, index);
Result[index] = (rsiStreakValue + rsi.Result[index] + percentRank) / 3;
}
// crsi calculations
public void Roc(double periods, int index)
{
var countRocDn = 0;
var prevDayRoc = 0.0;
var todayRoc = (Bars.ClosePrices[index] - Bars.ClosePrices[index - 1]) / Bars.ClosePrices[index - 1];
for (int i = 1; i <= periods; i++)
{
prevDayRoc = (Bars.ClosePrices[index - i] - Bars.ClosePrices[index - i - 1]) / Bars.ClosePrices[index - i - 1];
if (todayRoc > prevDayRoc)
countRocDn++;
}
percentRank = (countRocDn / periods) * 100;
}
public void UpDownStreak(int periods, int index)
{
int countUp = 0;
int countDn = 0;
int countEqual = 0;
for (int i = 1; i < 100; i++)
if (Bars.ClosePrices[index - i] > Bars.ClosePrices[index - i - 1])
{
countUp++;
continue;
}
else
break;
for (int i = 1; i < 100; i++)
if (Bars.ClosePrices[index - i] < Bars.ClosePrices[index - i - 1])
{
countDn++;
continue;
}
else
break;
for (int i = 1; i < 100; i++)
if (Bars.ClosePrices[index - i] == Bars.ClosePrices[index - i - 1])
{
countEqual++;
continue;
}
else
break;
if (countUp > countDn && countUp > countEqual)
{
streak = countUp;
listOfStreaks[index] = countUp;
}
else if (countDn > countUp && countDn > countEqual)
{
streak = -countDn;
listOfStreaks[index] = -countDn;
}
else if (countEqual > countUp && countEqual > countDn)
{
streak = 0;
listOfStreaks[index] = 0;
}
}
public double GetRsiStreak(int periods, int index)
{
if (listOfStreaks.Count < periods)
return 0;
double changeValue;
double rs;
double result;
for (int i = index; i > index - periods; i--)
{
changeValue = listOfStreaks[i - 1] - listOfStreaks[i];
listOfLoss[index] = 0;
listOfGain[index] = 0;
if (double.IsNaN(changeValue)) continue;
if (changeValue > 0)
{
listOfGain[index] = changeValue;
}
else
{
listOfLoss[index] = Math.Abs(changeValue);
}
}
if (1 == startIndex)
{
avgGain1 = listOfGain.Average();
avgLoss1 = listOfLoss.Average();
listOfAvgGain[index] = double.IsNaN(avgGain1) ? 0 : avgGain1;
listOfAvgLoss[index] = double.IsNaN(avgLoss1) ? 0 : avgLoss1;
}
else
{
avgGain = ((listOfAvgGain[index - 1] * (periods - 1)) + listOfGain[index]) / periods;
avgLoss = ((listOfAvgLoss[index - 1] * (periods - 1)) + listOfLoss[index]) / periods;
listOfAvgGain[index] = avgGain;
listOfAvgLoss[index] = avgLoss;
}
if (1 == startIndex)
{
startIndex = 2;
rs = avgGain1 / avgLoss1;
if (avgLoss1 == 0)
result = 100;
else
result = 100 - (100 / (1 + rs));
}
else
{
rs = avgGain / avgLoss;
if (avgLoss == 0)
result = 100;
else
result = 100 - (100 / (1 + rs));
}
return result;
}
}
}
using cAlgo.API;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class cBotNew : Robot
{
[Parameter("RSI periods", Group = "CRSI", DefaultValue = 3.0, MinValue = 1)]
public int rsiPeriods { get; set; }
[Parameter("Up/down streak periods", Group = "CRSI", DefaultValue = 2.0, MinValue = 1)]
public int streakPeriods { get; set; }
[Parameter("Rate of change periods", Group = "CRSI", DefaultValue = 100.0, MinValue = 2)]
public int rocPeriods { get; set; }
public ConnorsCRSI11 crsi;
protected override void OnStart()
{
crsi = Indicators.GetIndicator<ConnorsCRSI11>(rsiPeriods, streakPeriods, rocPeriods);
}
protected override void OnBar()
{
var index = Bars.Count - 2;
Print(crsi.Result[index], " | ", Bars.OpenTimes[index].ToString("R"));
}
}
}
@amusleh
amusleh
28 Mar 2021, 11:27
You can reference custom indicators on your cBots by following below steps:
1. Right click on your cBot name on cTrader automate, select "Manage References"
2. Find the indicator you want to reference in indicators tab and select it, then click "Apply" button
3. Re-build your indicator, then to use indicator:
using cAlgo.API;
namespace cAlgo.Robots
{
[Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class CorrelationBot : Robot
{
private Correlation _correlation;
protected override void OnStart()
{
_correlation = Indicators.GetIndicator<Correlation>("USDCHF", 50);
}
protected override void OnTick()
{
if (_correlation.Result.Last(1) > 50)
{
}
else
{
}
}
}
}
You have to pass all indicator parameters a value on "GetIndicator" method, otherwise it will not work, and you can only use the indicator outputs not properties.
@amusleh
amusleh
28 Mar 2021, 11:19
RE: RE: UserVoice
astralalex said:
ctdn_voter said:
It'd be nice to have cAlgo.API compatible with .NET Core.
Totally agree, as a C# developer I wasn't keen on learning MQL4/5 and I jumped for joy when I saw there was a platform which allowed me to do bots and indicators.
However working with such an old version of .net rather makes things a lot harder since I'm having to find libraries which are 10 years old and many are obsolete now.
I spent about half a day trying to create a .netstandard library with a COM wrapper but I couldn't get that approach to work.
I also tried making api calls from cTrader to a dotnetcore api service for a 'microservice' style approach but there didn't seem to be a nuget package which would support http requests for System.Net.Http on .net 4.
If anybody has a semi-clean work around for this it would be great to know!Alternatively if it was somewhere on the roadmap it would be one to watch as I'm sure this is going to be the number one C# developer wish.
cTrader is migrating to .NET core, its right now the main priority of developers, but we can't give you an ETA.
@amusleh
amusleh
28 Mar 2021, 11:18
RE: RE: RE: RE: RE: RE: RE: RE: RE:
kebbo said:
hi,
I followed the link and read it through, thank you.
amusleh said:
A DateTimeOffset Offset property returns the offset or difference of that DateTimeOffset from UTC, it doesn't change if you change the system time settings.
Here we are exactly at the point where I have difficulties in understanding.
You say an offset value from UTC time, but what is the reference time? If the reference point (from which the offset is calculated) is my system time, then the DateTimeOffset value should also adjust when the system time changes, shouldn't it?
best regards
Hi,
Offset is the time zone offset not time of day, if you want to get the time of day you can use DateTimeOffset.Now.TimeOfDay.
@amusleh
amusleh
27 Mar 2021, 11:25
RE: RE: RE: RE: RE: RE: RE:
kebbo said:
amusleh said:
kebbo said:
Hi again,
do you have more precise information on which time value "DateTimeOffset" refers to? Where are the times taken from which the offset value is calculated?
When I use DateTimeOffset.Now.Offset.Hours, I always have the same value output, regardless of the platform time set, as well as regardless of the computer system time set.
This could help me, but for my understanding I would like to know how this offset value is calculated.Best regards
kebboRead this:
Both DateTime.Now and DateTimeOffset.Now gives you the system time, you can get time from somewhere else like an API if you have one or if you don't have any you can use the web requests time, you can find other options if you google.
Hi and thank you again!
I did some experiments with the DateTimeOffset class and got the "Offset hours" output as described in my previous post.
Additionally the server time and the system time via the DateTime.
I then set the system time one hour earlier or later and found that the offset value always remained the same. I therefore asked myself where the time for DateTimeOffset is taken from.
With DateTime, the new time is output when the system time is changed.Do you understand what I mean?
DateTimeOffset.Now.Offset.Hours gives me the difference in hours between my system time and the server time, right? But why do they stay the same when I change the system time...?
best regards
Hi,
Your question is not related to the cTrader automate API, its related to .NET.
A DateTimeOffset Offset property returns the offset or difference of that DateTimeOffset from UTC, it doesn't change if you change the system time settings.
Please read the link I posted on my previous post and you will find all detail related to DateTimeOffset.
@amusleh
amusleh
26 Mar 2021, 22:40
RE: where are 'List' Stored?
emeeder1 said:
This is old post, but relevant to my question. Maybe someone can explain this for me?
In the List function, is the resulting list saved somewhere on local computer? what happens to data saved to list when disconnected or if Ctrader restarted? is List functions available in both indicator and Bot?
Primarily i want to store data permanently so it does not get lost when ctrader turned off.
Also: if List is not saved locally, is there a function available that can access local data on computer (excel table, database...)?
Thanks :)
cTrader automate API is written in C# and is based on .NET framework, you can use all .NET libraries inside your cTrader indicator/cBot.
The .NET collections including list is stored in your system memory and when your .NET program in our case indicator/cBot is terminated the memory will be cleaned up and you will not have access to it next time your .NET program runs.
To save data permanently you have to write it on the disk, you can easily write anything you want to on files via .NET file stream object.
If you need code examples please Google reading/writing files C# and you will find lots of code samples.
@amusleh
amusleh
26 Mar 2021, 18:57
RE: RE: RE: RE: RE:
kebbo said:
Hi again,
do you have more precise information on which time value "DateTimeOffset" refers to? Where are the times taken from which the offset value is calculated?
When I use DateTimeOffset.Now.Offset.Hours, I always have the same value output, regardless of the platform time set, as well as regardless of the computer system time set.
This could help me, but for my understanding I would like to know how this offset value is calculated.Best regards
kebbo
Read this:
Both DateTime.Now and DateTimeOffset.Now gives you the system time, you can get time from somewhere else like an API if you have one or if you don't have any you can use the web requests time, you can find other options if you google.
@amusleh
amusleh
26 Mar 2021, 17:55
Hi,
You can use the cTrader Period Separator, it will draw a line at the beginning of each day and it works for time frames lower than hourly.
If that's not enough for you then you can use this sample indicator:
using cAlgo.API;
using System;
using System.Globalization;
using System.Linq;
namespace cAlgo
{
[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class DailySeparatorLineSample : Indicator
{
private const string ChartObjectNamesSuffix = "DailySeparatorLineSample";
private TimeSpan _dayTime, _dayTimeUserOffset;
private Color _linesColor;
[Parameter("Day Time", DefaultValue = "00:00:00")]
public string DayTime { get; set; }
[Parameter("Color", DefaultValue = "Blue")]
public string LinesColor { get; set; }
[Parameter("Color Alpha", DefaultValue = 200, MaxValue = 255, MinValue = 0)]
public int LinesColorAlpha { get; set; }
[Parameter("Thickness", DefaultValue = 1)]
public int LinesThickness { get; set; }
[Parameter("Style", DefaultValue = LineStyle.Solid)]
public LineStyle LinesStyle { get; set; }
protected override void Initialize()
{
if (TimeSpan.TryParse(DayTime, CultureInfo.InvariantCulture, out _dayTime))
{
_dayTimeUserOffset = _dayTime.Add(-Application.UserTimeOffset);
}
_linesColor = ParseColor(LinesColor, LinesColorAlpha);
Application.UserTimeOffsetChanged += Application_UserTimeOffsetChanged;
DrawLines();
}
public override void Calculate(int index)
{
}
private void Application_UserTimeOffsetChanged(UserTimeOffsetChangedEventArgs obj)
{
_dayTimeUserOffset = _dayTime.Add(-Application.UserTimeOffset);
DrawLines();
}
private void DrawLines()
{
RemoveLines();
var endDate = Bars.OpenTimes.LastValue.Date.AddDays(100);
for (var date = Bars.OpenTimes[0].Date; date <= endDate; date = date.AddDays(1))
{
var lineTime = date.Add(_dayTimeUserOffset);
var lineName = string.Format("{0}_{1}", ChartObjectNamesSuffix, date);
Chart.DrawVerticalLine(lineName, lineTime, _linesColor, LinesThickness, LinesStyle);
}
}
private void RemoveLines()
{
var chartObjects = Chart.Objects.ToArray();
foreach (var chartObject in chartObjects)
{
if (!chartObject.Name.StartsWith(ChartObjectNamesSuffix, StringComparison.OrdinalIgnoreCase)) continue;
Chart.RemoveObject(chartObject.Name);
}
}
private Color ParseColor(string colorString, int alpha = 255)
{
var color = colorString[0] == '#' ? Color.FromHex(colorString) : Color.FromName(colorString);
return Color.FromArgb(alpha, color);
}
}
}
@amusleh
amusleh
26 Mar 2021, 08:52
RE: RE: RE: VolumeROC only works OnTick()
3eunguyen said:
amusleh said:
3eunguyen said:
Sorry for the noob question.
I'm running this cBot, where I just print the last value of the VolumeROC indicator to the log. When I backtest it and compare it to the value of the exact same indicator that I display on my chart, I get completely different values. It keeps printing negative numbers around -80 and -99 in the log.
Can you post your cBot code here? the Volume ROC can go in negative side.
It's good, turns out VolumeROC won't work if you run it inside OnBar(), it has to be OnTick(). That kinda sucks, I hope it doesn't affect all volume indicators.
//Doesn't Work
protected override void OnBar()
{
Print(vRoc.Result.LastValue);
}//Works
protected override void OnTick()
{
Print(vRoc.Result.LastValue);
}
The OnBar is called when a new bar is opened or a previous bar closed, so the last value of indicator series is not completed yet or even maybe not set yet.
Inside OnBar you should not use LastValue, instead use Last(1) which will give you the last completed bar values.
@amusleh
amusleh
26 Mar 2021, 08:49
RE:
yuval.ein said:
How can I get current EST time by code when my time is set to a different time zone?
Thanks
Try this:
using cAlgo.API;
using System;
namespace cAlgo
{
[Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
public class TimezoneSample : Indicator
{
protected override void Initialize()
{
var estTime = GetEasternStandardTime();
Print(estTime.ToString("o"));
}
public override void Calculate(int index)
{
}
private DateTime GetEasternStandardTime()
{
var easternTimeZone = TimeZoneInfo.FindSystemTimeZoneById("Eastern Standard Time");
return TimeZoneInfo.ConvertTimeFromUtc(Server.TimeInUtc, easternTimeZone);
}
}
}
And for more info about time zones check the Microsoft Docs for TimeZoneInfo.
@amusleh
amusleh
26 Mar 2021, 08:40
RE:
visha said:
I have created my own cBot code and applied multiple instruments but it does not all work all the instruments parallelly. Only it is placing the order in active chart.
It does not work non interactive charts. is that expected ?
Hi,
Please post your cBot code, otherwise we can't help you.
@amusleh
amusleh
26 Mar 2021, 08:39
Hi,
BarOpened/Tick events were added in recent versions of Automate API, they were not available in older versions.
Both do the same thing but if you are developing a single time frame/symbol based indicator/cBot then you should use the overridden methods because that's much easier and you don't have to subscribe for the events, and if you are developing a multi symbol/ time frame indicator/cBot then you should use the BarOpened/Tick events.
@amusleh
amusleh
25 Mar 2021, 10:11
RE:
3eunguyen said:
Sorry for the noob question.
I'm running this cBot, where I just print the last value of the VolumeROC indicator to the log. When I backtest it and compare it to the value of the exact same indicator that I display on my chart, I get completely different values. It keeps printing negative numbers around -80 and -99 in the log.
Can you post your cBot code here? the Volume ROC can go in negative side.
@amusleh
amusleh
30 Mar 2021, 14:34
RE: RE: RE: RE:
JerryTrader said:
Hi,
It was not working properly with generic collections, not sure if it's working now or not, it was a long time ago.
@amusleh