Topics
Replies
firemyst
24 May 2021, 16:58
RE:
PanagiotisCharalampous said:
Hi firemyst,
Check Chart.Drag and Chart.ObjectsUpdated events.
Best Regards,
Panagiotis
Thanks for that.
Another question. I have the following sample code -- see my question in the commented code:
using System;
using System.Collections.Generic;
using cAlgo.API;
using cAlgo.API.Indicators;
namespace cAlgo.Indicators
{
[Indicator(IsOverlay = true, AccessRights = AccessRights.None)]
public class Test : Indicator
{
private ChartHorizontalLine _chl;
private double _value;
protected override void Initialize()
{
_value = MarketData.GetBars(Chart.TimeFrame, Symbol.Name).ClosePrices.LastValue;
_chl = Chart.DrawHorizontalLine("test line", _value, Color.Goldenrod, 2, LineStyle.Solid);
_chl.IsInteractive = true;
Chart.ObjectsUpdated += LineUpdated;
}
public override void Calculate(int index)
{
}
//When I drag the horizontal line created on the chart, why does this method get called numerous times?
//Every time I drag the line, if I set a breakpoint on the opening curly brace, this method gets hit for each
//object on the chart.
//Is there a way to to reduce this so it only gets called for horizontal line objects?
private void LineUpdated(ChartObjectsUpdatedEventArgs args)
{
ChartHorizontalLine obj = (ChartHorizontalLine)args.Chart.FindObject("test line");
if (obj != null && obj.Y != _value)
{
_value = obj.Y;
Chart.DrawStaticText("value", _value.ToString(), VerticalAlignment.Top, HorizontalAlignment.Right, Color.Goldenrod);
}
}
}
}
@firemyst
firemyst
29 Apr 2021, 06:24
cTrader is unfortunately very limited in its alerting abilities, and I think the only alerts possible are those based on "price". Although I'm sure someone from Spotware can correct me if I'm wrong.
If you need this, I see a few options:
1) sign up with another trading platform like ThinkTrader, which has awesome alerting functionality built in, where you can customize any kind of alert from one indicator crossing a price/indicator, price notifications, or a lot of other options.
2) program a 3 ema indicator yourself to alert you of when prices ema values are crossing.
3) use or find someone else's prebuilt indicator that has such alerting functionality provided.
@firemyst
firemyst
29 Apr 2021, 06:15
RE: RE:
bienve.pf said:
PanagiotisCharalampous said:
Hi bienve.pf,
The reason it doesn't move is because the retrieved bars are consist of completed bars. So you get the completed bar of the specific date. The price will change as soon as the date changes. There is a similar discussion about this here.
Best Regards,
Panagiotis
I understand,
But if I test multi-timeframe indicator in backtesting I need receive data like in realtime to parse all tics at the moment. Now, with all the finalized bars and finalized values the ClosePrices no moves and It is not a real stage.
Two questions:
1) have you chosen "tick data" from the backtesting menu option?
2) A lot of chart operations are not supported (as far as I understand) in backtesting mode. Try surrounding your indicator with
if (!IsBacktesting)
{
// ... indicator calls here
}
and see what happens.
@firemyst
firemyst
23 Apr 2021, 03:43
Glad that helped!
Something else to try is using IsLastBar such as follows:
if (!notified && IsLastBar())
{
notified = true;
PlaySound();
}
so it only plays on the last bar. If you're watching the charts live, this will be the most current bar. Thus you don't have to worry about the sound being played on any previous bars.
@firemyst
firemyst
22 Apr 2021, 10:16
( Updated at: 24 May 2022, 03:29 )
RE:
amusleh said:
Hi,
You can call Bars.OpenTimes.GetindexByTime two times with two indices, for example you call it first with index 5 and then 6, and it returns 10 for the first call and 20 for the second call, then you know from 10 to 20 belongs to first index.
Thank you!
That makes perfect sense!!
I appreciate your help!!
@firemyst
firemyst
22 Apr 2021, 09:42
( Updated at: 22 Apr 2021, 09:44 )
RE:
amusleh said:
Hi,
Indices are not fixed, they can change, instead use bar open times.
If you want to get index of a bar on a different time frame you can use GetIndexByTime method of Bars.OpenTimes, check this sample indicator code:
using cAlgo.API; using cAlgo.API.Indicators; using System; namespace cAlgo { [Cloud("Top", "Bottom", Opacity = 0.2)] [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)] public class BollingerBandsMTFCloudSample : Indicator { private BollingerBands _bollingerBands; private Bars _baseBars; [Parameter("Base TimeFrame", DefaultValue = "Daily")] public TimeFrame BaseTimeFrame { get; set; } [Parameter("Source", DefaultValue = DataSeriesType.Close)] public DataSeriesType DataSeriesType { get; set; } [Parameter("Periods", DefaultValue = 14, MinValue = 0)] public int Periods { get; set; } [Parameter("Standard Deviation", DefaultValue = 2, MinValue = 0)] public double StandardDeviation { get; set; } [Parameter("MA Type", DefaultValue = MovingAverageType.Simple)] public MovingAverageType MaType { get; set; } [Output("Main", LineColor = "Yellow", PlotType = PlotType.Line, Thickness = 1)] public IndicatorDataSeries Main { get; set; } [Output("Top", LineColor = "Red", PlotType = PlotType.Line, Thickness = 1)] public IndicatorDataSeries Top { get; set; } [Output("Bottom", LineColor = "Red", PlotType = PlotType.Line, Thickness = 1)] public IndicatorDataSeries Bottom { get; set; } protected override void Initialize() { _baseBars = MarketData.GetBars(BaseTimeFrame); var baseSeries = GetBaseSeries(); _bollingerBands = Indicators.BollingerBands(baseSeries, Periods, StandardDeviation, MaType); } public override void Calculate(int index) { var baseIndex = _baseBars.OpenTimes.GetIndexByTime(Bars.OpenTimes[index]); Main[index] = _bollingerBands.Main[baseIndex]; Top[index] = _bollingerBands.Top[baseIndex]; Bottom[index] = _bollingerBands.Bottom[baseIndex]; } private DataSeries GetBaseSeries() { switch (DataSeriesType) { case DataSeriesType.Open: return _baseBars.OpenPrices; case DataSeriesType.High: return _baseBars.HighPrices; case DataSeriesType.Low: return _baseBars.LowPrices; case DataSeriesType.Close: return _baseBars.ClosePrices; default: throw new ArgumentOutOfRangeException("DataSeriesType"); } } } public enum DataSeriesType { Open, High, Low, Close } }
Thank you, but this doesn't help and doesn't work for what I need.
What your code is doing is getting the indexes of the bars in the faster time frame and mapping that to a single index to the bars in the slower time frame.
With your method, it's going from "many" to "one".
I need the opposite. I have 1 index in a higher time frame and need to map it to all the related indexes in the faster time frame.
I have "one" and need to map it to "many".
If you try doing the opposite:
Bars.OpenTimes.GetIndexByTime(_baseBars.OpenTimes[index]);
simply won't work because it will not return all the indexes related. That is, if _baseBars is M15, and the chart is M1, Bars.OpenTimes.GetindexByTime will only return 1 index, not the corresponding 15 indexes I need.
@firemyst
firemyst
22 Apr 2021, 09:21
RE:
sascha.dawe said:
Hi,
I am having trouble playing sound notifications in an indicator in the latest version of Ctrader 4.0.
Yes, I have sound enabled and notifications on Ctrader and Filesystem access is enabled. I have
also converted this code to a cBot and the sound works fine. What could be causing this issue?
See demonstration code below.
using System; using System.IO; using cAlgo.API; using cAlgo.API.Internals; using cAlgo.API.Indicators; using cAlgo.Indicators; namespace cAlgo { [Indicator(IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.FileSystem)] public class TestSound : Indicator { [Parameter("Enable Sound Alerts", Group = "Sound", DefaultValue = false)] public bool soundAlerts { get; set; } [Parameter("Sound Filename", Group = "Sound", DefaultValue = "foghorn.mp3")] public string filename { get; set; } private string path; private bool notified = false; protected override void Initialize() { try { var desktopFolder = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); path = Path.Combine(desktopFolder, filename); Print(path); } catch (Exception error) { Print("Error: " + error); } PlaySound(); } public override void Calculate(int index) { if (!notified) { notified = true; PlaySound(); } } private void PlaySound() { if (soundAlerts) { try { Notifications.PlaySound(path); } catch (Exception error) { Print("Error: " + error); } } } } }
Have you tried stepping through your code in Visual Studio debug mode to see what happens?
Also, because ticks could be coming in rather quickly, the system might be trying to play, but then gets stopped immediately when the next tick comes in because the "notified" variable is never set to true. I've had that happen before when trying to play sounds too quickly in succession.
Finally, what's the Print/logging output?
@firemyst
firemyst
22 Apr 2021, 09:12
Rough pseudo logic:
DateTime startTime = new DateTime(2021,4,22,6,0,0);
int startTimeIndex = Bars.OpenTimes.GetIndexByTime(startTime);
DateTime endTime = new DateTime(2021,4,22,10,0,0);
int endTimeIndex = Bars.OpenTimes.GetIndexByTime(endTime);
double highestPrice = 0;
for (int x= startTimeIndex; x <= endTimeIndex; x++)
{
if (Bars.HighPrices[x] > highestPrice)
highestPrice = Bars.HighPrices[x];
}
@firemyst
firemyst
13 Apr 2021, 15:28
RE:
amusleh said:
Hi,
Right now there is no way to change a chart time frame while a cBot instance is running on that chart.
We have plans to allow changing the time frame but we can't give you any ETA for that, I recommend you to open a thread for this feature request on forum suggestions section.
For clarification, yomm0401 didn't specifically ask if the "chart" time frame itself could be changed, only if the timeframe could be changed.
The answer to the latter question is yes in cBot code if that's what the intended question was.
The cbot code can always call:
MarketData.GetBars(TimeFrame.xxx, Symbol.Name);
and get data from a diffferent symbol and/or timeframe than what's on the chart.
@firemyst
firemyst
13 Apr 2021, 05:40
RE:
PanagiotisCharalampous said:
Hi caglar_G,
There is no option for the shift in the API at the moment, but it is not hard to implement it yourself.
Best Regards,
Panagiotis
Just wanted to follow up on this as I was looking for this parameter today as well, and it's not there (yet?) with the latest cTrader release.
Is it on a road map to implement natively? Seems like an easy job that shouldn't take a @Spotware programmer more than half a day since it's basically 1 important line of code similar to:
Result[index + Shift] = _movingAverage.Result[index];
THank you. :-)
@firemyst
firemyst
05 Apr 2021, 10:38
RE:
travkinsm1 said:
Hello! When testing the indicator, I noticed that one of the variables (_isLong) is displayed differently in the indicator and in the robot. If you do not pay attention to the different processing of ticks by the robot and the indicator, you can verify this by analyzing 2 log files. Tell me, please, what could be the matter? (For testing, you need to change the paths to the files)
using System; using System.Linq; using System.Windows; using System.IO; using System.Text; using System.Text.RegularExpressions; using cAlgo.API; using cAlgo.API.Indicators; using cAlgo.API.Internals; using cAlgo.Indicators; namespace cAlgo { [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.FullAccess)] public class MycBot : Robot { [Parameter("MA Method", DefaultValue = MovingAverageType.Simple)] public MovingAverageType MaType { get; set; } [Parameter("True:High_Low False:Close", DefaultValue = false)] public bool UseHighAndLow { get; set; } [Parameter("Timeframe1", DefaultValue = "Ticks10")] public TimeFrame TF { get; set; } [Parameter("Загрузить Баров", DefaultValue = 30000)] public int BarCount_set { get; set; } [Parameter("Загрузка ист. в индикаторе", DefaultValue = false)] public bool LoadIndHist { get; set; } [Parameter("Запись в файл", DefaultValue = true)] public bool FileWrite { get; set; } [Parameter("Запись торговли в файл", DefaultValue = true)] public bool FileWrite2 { get; set; } private Bars Bars2; public StreamWriter file; public int LoadHistory_for_ind = 3000; protected override void OnStart() { while (Bars.Count < BarCount_set) { var loadedCount = Bars.LoadMoreHistory(); Print("Loaded {0} bars", loadedCount); Print("Total bars {0}", Bars.Count); if (loadedCount == 0) break; } Print("Finished, total bars {0}", Bars.Count); Bars2 = MarketData.GetBars(TF, Symbol.Name); while (Bars2.Count < (BarCount_set / 10)) { var loadedCount = Bars2.LoadMoreHistory(); if (loadedCount == 0) break; } Print("Finished, total bars {0}", Bars2.Count); file = new StreamWriter("C:\\Users\\travk_000\\Documents\\bot1.3_is_long2.txt", false); file.WriteLine("Begin"); file.Flush(); file.Close(); } protected override void OnTick() { bool dirInd = Indicators.GetIndicator<ATR_Stops_time_MarketData_History>(TF, MaType, 10, 4, UseHighAndLow, LoadHistory_for_ind, LoadIndHist)._isLong; file = new StreamWriter("C:\\Users\\travk_000\\Documents\\bot1.3_is_long2.txt", true); file.WriteLine("Bot " + Bars.OpenPrices.LastValue + " " + Bars.OpenTimes.LastValue + " " + dirInd); file.Flush(); file.Close(); } } }
using System; using System.IO; using System.Text; using System.Text.RegularExpressions; using cAlgo.API; using cAlgo.API.Indicators; using cAlgo.API.Internals; namespace cAlgo.Indicators { [Indicator("ATR Trailing Stop", AutoRescale = false, IsOverlay = true, TimeZone = TimeZones.UTC, AccessRights = AccessRights.FileSystem)] public class ATR_Stops_time_MarketData_History : Indicator { [Parameter("Timeframe", DefaultValue = 0.0)] public TimeFrame TF { get; set; } [Parameter("MA Method", DefaultValue = MovingAverageType.Simple)] public MovingAverageType MaType { get; set; } [Parameter("Period", DefaultValue = 10, MinValue = 2, MaxValue = 50000)] public int Period { get; set; } [Parameter("Weight", DefaultValue = 4.0, MinValue = 0.1, MaxValue = 400.0)] public double Weight { get; set; } [Parameter("True:High_Low False:Close", DefaultValue = false)] public bool UseHighAndLow { get; set; } [Parameter(DefaultValue = 16000)] public int LoadHistory_for_ind { get; set; } [Parameter("Загрузка ист. в индикаторе", DefaultValue = false)] public bool LoadIndHist { get; set; } [Output("Main")] public IndicatorDataSeries Result { get; set; } private ATRMTF_time_History _atr; public bool _isLong; private MarketSeries _marketSeries; private Bars Bars2; public StreamWriter file3; protected override void Initialize() { Print("Start"); Bars2 = MarketData.GetBars(TF, Symbol.Name); Print("{0} bar on the chart. Loading 2.000.000 bars", Bars2.Count); if (LoadIndHist == true) { while (Bars2.Count < LoadHistory_for_ind) { var loadedCount = Bars2.LoadMoreHistory(); if (loadedCount == 0) break; } Print("Finished, total bars {0}", Bars2.Count); } Print("1"); this._marketSeries = MarketData.GetSeries(MarketData.GetSymbol(Symbol.Name), TF); } public override void Calculate(int index) { var currentAtr = Weight * _atr.Result[index]; if (double.IsNaN(currentAtr)) return; if (double.IsNaN(Result[index - 1]) && !double.IsNaN(_atr.Result[index - 1])) { var previousATR = Weight * _atr.Result[index - 1]; _isLong = MarketSeries.Close.IsRising(); var previous = UseHighAndLow ? (_isLong ? this._marketSeries.High[_marketSeries.OpenTime.GetIndexByTime(MarketSeries.OpenTime[index - 1])] : this._marketSeries.Low[_marketSeries.OpenTime.GetIndexByTime(MarketSeries.OpenTime[index - 1])]) : this._marketSeries.Close[_marketSeries.OpenTime.GetIndexByTime(MarketSeries.OpenTime[index - 1])]; Result[index] = _isLong ? previous - previousATR : previous + previousATR; } else { var current = this._marketSeries.Close[_marketSeries.OpenTime.GetIndexByTime(MarketSeries.OpenTime[index])]; if (_isLong) { if (current >= Result[index - 1]) { if (UseHighAndLow) current = this._marketSeries.High[_marketSeries.OpenTime.GetIndexByTime(MarketSeries.OpenTime[index])]; Result[index] = Math.Max(Result[index - 1], current - currentAtr); } else { _isLong = false; if (UseHighAndLow) current = this._marketSeries.Low[_marketSeries.OpenTime.GetIndexByTime(MarketSeries.OpenTime[index])]; Result[index] = current + currentAtr; } } else { if (current <= Result[index - 1]) { if (UseHighAndLow) current = this._marketSeries.Low[_marketSeries.OpenTime.GetIndexByTime(MarketSeries.OpenTime[index])]; Result[index] = Math.Min(Result[index - 1], current + currentAtr); } else { _isLong = true; if (UseHighAndLow) current = this._marketSeries.High[_marketSeries.OpenTime.GetIndexByTime(MarketSeries.OpenTime[index])]; Result[index] = current - currentAtr; } } } file3 = new StreamWriter("C:\\Users\\travk_000\\Documents\\bot1.3_is_long.txt", true); file3.WriteLine("Indicator " + Bars.OpenPrices.LastValue + " " + Bars.OpenTimes.LastValue + " " + Period + " " + Weight + " " + _isLong); file3.Flush(); file3.Close(); } } }
When calling an indicator from a bot, you ALWAYS have to get the latest value from the indicator, which you aren't doing. You're initializing the indicator on every tick in the bot, but you're not getting its latest value.
You need to do something like the following in your bot:
//because cTrader does "lazy loading" of indicator values, so force it
double a = YourIndicator.Result.Last(0);
//now that the indicator's values have been refresh above, get the latest value.
_isLong = YourIndicator.Result.Last(0);
@firemyst
firemyst
04 Apr 2021, 13:38
RE:
PanagiotisCharalampous said:
Hi firemyst,
It seems to be a bug. We will fix it in an upcoming update.
Best Regards,
Panagiotis
Awesome.
Thank you @Panagiotis! If you could update here when fixed that would be great.
ALSO! Would it be possible to include within the API a "data source" parameter within the constructor? It seems incredibly limiting that we cannot pass in another datasource (eg, different time frame for example) like we can with PSARs and other indicators.
Thank you. And Happy Easter if you celebrate it. :-)
@firemyst
firemyst
28 Mar 2021, 17:05
RE:
PanagiotisCharalampous said:
Hi firemyst,
I would put forth the suggestion that since Spotware has everyone's email address that has joined the forum, to put out a survey to all registered forum users through something like Survey Monkey.
We cannot get into this burden every time someone dislikes something. We would be sending five polls a day :) Also I do not think that we need to get everybody's vote to make such decisions. This is not how polls and statistics work. I believe that the forums have enough participants and voters to run a representative poll with a low margin of error.
Best Regards,
Panagiotis
We're not asking you to send 5 polls a day. Spotware put up 1 poll on Telegram -- how difficult is it to put that same poll in a location where you have a greater audience and feedback from every cTrader user?
What Spotware has done is like only asking the people in California who the President should be rather than the whole of the USA.
@firemyst
firemyst
27 Mar 2021, 03:56
RE:
PanagiotisCharalampous said:
Hi firemyst,
I don't like the new layout because the "New Order" button at the top has been removed! That was so convenient!
Why don't you just press F9?
Best Regards,
Panagiotis
Because I (like others as expressed in this thread), don't know or have keyboard shortcuts memorized.
Why can't Spotware just put it back? There's still plenty of room for it, even in the new layout.
@firemyst
firemyst
29 Aug 2021, 12:13
Use Positions.Find()
@firemyst