Topics
09 Jan 2023, 17:06
 1
 613
 1
13 Oct 2022, 12:25
 554
 1
04 Oct 2022, 16:27
 757
 3
Replies

pick
02 Aug 2023, 14:00

The name of that function seems pretty straightforward - it would just be converting a TimeFrame to a TimeSpan. 

Just by clicking onto that velocity indicator link, you can find their github. This is the file you require:

https://github.com/abhacid/cAlgoBot/blob/master/Sources/Library/cAlgoLib/TimeFrameExtensions.cs

 


@pick

pick
20 Jul 2023, 12:51

With many things in programming, there are multiple ways this could be achieved. My route of choice would be something like this:

DayData getPrevDayValues(Bars bars, DateTime dt, int startHour = 8, int endHour = 20)
{
	DateTime prevTime = getStartPrevDay(dt);
	DateTime startTime = prevTime.AddHours(startHour);
	DateTime endTime = prevTime.AddHours(endHour);

	Bar[] usableBars = bars.Where(b => b.OpenTime >= startTime && b.OpenTime < endTime).ToArray();

	if (usableBars.Length <= 0)
		return null;

	double max = usableBars.Max(b => b.High);
	double min = usableBars.Min(b => b.Low);
	double close = usableBars[usableBars.Length - 1].Close;

	return new DayData(max, min, close);
}

DateTime getStartPrevDay(DateTime dt)
{
	DateTime prevStart = dt.Date.AddDays(-1);
	for (; prevStart.DayOfWeek == DayOfWeek.Sunday || prevStart.DayOfWeek == DayOfWeek.Saturday; prevStart = prevStart.AddDays(-1)) ;

	return prevStart;
}

internal class DayData
{
	public DayData(double h, double l, double c)
	{
        // Calculate in here
	}
}

I would also store the date as a key in a dictionary with the value being the DayData, so you'd only have to calculate it once per day.


@pick

pick
22 Jun 2023, 12:57

You can access this via Application.Version


@pick

pick
19 Jun 2023, 12:58

sue.bugg said:

You are an absolute champion. Thankyou so much. This appears to have worked. Incorrect trades have disappeared. I will test more thoroughly. But looking very good so far. Thanks again. Totally appreciate your help. 

No worries. Good luck with your bot!


@pick

pick
19 Jun 2023, 12:35

RE: RE:

sue.bugg said:

pick said:

The first "obvious" issue I see in that code is that in "CanTradeBuyDaily", "CanTradeBuyFourHour", "CanTradeBuyHourly" and "CanTradeBuyFiveMinute" you're using "_close" for comparisons - which, at a quick glance, looks like it is always equal to the timeframe on which the bot is loaded. I see you've loaded the bars for the relevant timeframe for each method, but you're not using those bars? 

Yes, I am thinking that is the issue also, but when you say I’m not using the bars, that is the problem. I don’t know how to use them. When I do the following, (put them in the right section and repeat for 4hour & 1hour), I believe I’m just changing the name from _close to _closeDaily. I’m missing something to connect _closeDaily to the actual Daily bars

private DataSeries _closeDaily;

_closeDaily = Bars.ClosePrices;

if (_closeDaily.Last(1) > _ichimokuKinkoHyoDaily.SenkouSpanA.Last(27)

&& _closeDaily.Last(1) > _ichimokuKinkoHyoDaily.SenkouSpanB.Last(27)

 

If I use Ichimoku as example… In the bracket, the standard to put in is 9,26,52… But it accepted _dailyBars as additional entry. Which I think is what’s connecting it to the bars. The following works for Ichimoku

_ichimokuKinkoHyoDaily = Indicators.IchimokuKinkoHyo(_dailyBars,9, 26, 52);

 

If I do the following for the Close, it errors out and says “non invocable member, cannot be used like a method”

_closeDaily = Bars.ClosePrices(_dailyBars);

Basically, I don’t know how to connect the _closeDaily to the bars. Do you know what I’m missing?

Thankyou

Have you tried using the bars you've already loaded like this?

private bool CanTradeBuyHourly(BuyMethod method)
{
	if (_hourlyBars.ClosePrices.Last(1) > _ichimokuKinkoHyoHourly.SenkouSpanA.Last(27)
		&& _hourlyBars.ClosePrices.Last(1) > _ichimokuKinkoHyoHourly.SenkouSpanB.Last(27))
		return true;
	else
		return false;
}

@pick

pick
19 Jun 2023, 11:47

The first "obvious" issue I see in that code is that in "CanTradeBuyDaily", "CanTradeBuyFourHour", "CanTradeBuyHourly" and "CanTradeBuyFiveMinute" you're using "_close" for comparisons - which, at a quick glance, looks like it is always equal to the timeframe on which the bot is loaded. I see you've loaded the bars for the relevant timeframe for each method, but you're not using those bars? 


@pick

pick
30 May 2023, 11:22

RE:

coffeedarkmatter said:

Thanks Pick! That code works. And I now also understand how cTrader operates a bit better. I much appreciate your reply and the time taken. :)

How did you know the code did work, but needed threading to give a visual result earlier? I looked at the chart and indicator and didn't notice a spinning wheel or loading sign. And in the Task Manager the cTrader's CPU usage was very low. So I mistakenly assumed the code didn't do anything.

Sorry for the delayed response. I'm glad that you could reproduce a working sample! I initially just reduced the number of iterations in the loop to 5 and it populated. Then, I just removed the graphical side of it and began logging with the timings to find the culprit. 


@pick

pick
25 May 2023, 16:24

RE:

couscousmckoy said:

Thanks Pick - I'll give that a try. Part of the problem was length of time it took to scroll through all historical bars to colour code certain bars.  I've managed to irradicate this half of the issue by only updating bar colour on visible bars. Still leaves the 10seconds it takes to calculate which bars should be colour coded and unfortunately to work that out it has to work through a lot of bars from the start.  I'll give threads a go though. Thank you for the help.

couscous

No worries - hopefully that can provide some benefit. Ten seconds just for the visible bars still sounds like a rather long time to compute. If you need any further help reducing this time, please could you elaborate more on what conditions you're colouring the bars with.


@pick

pick
25 May 2023, 13:50

What sort of tasks are you performing during this time consuming method? Can they be ran on another thread? Ideally I wouldn't want to block the main thread for so long. Below is an example of running the task on another thread:

protected override void Initialize()
{
	Print("Starting long job");
	Chart.DrawStaticText("LongJobWarning", "Warning: Long Job. Wait until this message disappears!!", VerticalAlignment.Center, HorizontalAlignment.Center, Color.Red);
	Task.Run(doLongJob);
}

async Task doLongJob()
{
	await Task.Delay(10000); //10 second delay

	BeginInvokeOnMainThread(() =>
	{
		Print("Finished Long Job");
		Chart.RemoveObject("LongJobWarning");
	});
}

 


@pick

pick
25 May 2023, 13:24

It works, but it takes a long time - mainly due to the Symbols.GetSymbol call (which must be done on the main thread) combined with how many symbols there are to load.

You can reduce the number of loops and it will populate faster, but if you want all symbols, below is a solution that I just quickly wrote up. It will populate the symbol names, then move on to filling in the descriptions.

Keep in mind that this is only a quick implementation and that it could probably be made more efficient:

protected override void Initialize()
{
	ScrollViewer scrollViewer = new ScrollViewer
	{
		HorizontalAlignment = HorizontalAlignment.Center,
		VerticalAlignment = VerticalAlignment.Center,
		BackgroundColor = Color.Gold,
		Opacity = 0.7,
		HorizontalScrollBarVisibility = ScrollBarVisibility.Auto,
		VerticalScrollBarVisibility = ScrollBarVisibility.Visible,
		Height = 300
	};

	Grid grid = new Grid(Symbols.Count + 1, 2)
	{
		BackgroundColor = Color.Gold,
		HorizontalAlignment = HorizontalAlignment.Center,
		VerticalAlignment = VerticalAlignment.Center,
	};

	grid.AddChild(new TextBlock
	{
		Text = "Name",
		Margin = 5,
		ForegroundColor = Color.Black,
		FontWeight = FontWeight.ExtraBold
	}, 0, 0);

	grid.AddChild(new TextBlock
	{
		Text = "Description",
		Margin = 5,
		ForegroundColor = Color.Black,
		FontWeight = FontWeight.ExtraBold
	}, 0, 1);

	Chart.AddControl(scrollViewer);
	scrollViewer.Content = grid;

	List<string> strings = Symbols.ToList();
	for (int i = 0; i < strings.Count; i++)
	{
		grid.AddChild(new TextBlock
		{
			Text = strings[i],
			Margin = 5,
			ForegroundColor = Color.Black,
			FontWeight = FontWeight.ExtraBold
		}, i + 1, 0);
	}

	foreach (string s in strings)
	{
		this.BeginInvokeOnMainThread(() =>
		{
			Symbol sym = Symbols.GetSymbol(s);
			grid.AddChild(new Button
			{
				Text = sym.Description,
				Margin = 5,
				ForegroundColor = Color.Black,
				FontWeight = FontWeight.ExtraBold
			}, strings.IndexOf(s) + 1, 1);
		});
	}
}

 


@pick

pick
24 May 2023, 11:54

I don't believe you can set the default for the indicator, but you can access the users current offset:

protected override void Initialize()
{
	TimeSpan t = this.Application.UserTimeOffset;
	Print("User time offset = " + t.TotalHours);
	this.Application.UserTimeOffsetChanged += Application_UserTimeOffsetChanged;

}

private void Application_UserTimeOffsetChanged(UserTimeOffsetChangedEventArgs obj)
{
	TimeSpan t = this.Application.UserTimeOffset;
	Print("User time offset changed = " + t.TotalHours);
}

From there, it's easy to just combine the utc time with the offset for user time.


@pick

pick
18 May 2023, 19:26 ( Updated at: 21 Dec 2023, 09:23 )

RE:

o.o.d.exp said:

Great, I was able to successfully modify the indicator thanks to your help! Once it is perfected I will post the code so anyone who wants can download it.
I have one more question: the indicator draws horizontal opening session lines on the traditional candlestick chart, but when I change to heiken ashi candlesticks, the opening price lines disappear.
I assume this is because heiken ashi candles have a different calculation method to regular candles, so there is probably nothing to do about it.

Do you think this is the problem, or is it possible to fix this?


You posted just after I went off yesterday - so I've just seen this. I've never worked with Heiken Ashi charts, but I'd assume it's due to the timeframe check:

if (openPrices)
	if (TimeFrame <= TimeFrame.Hour) //Here
		if (MarketSeries.OpenTime[index] >= londonOpen)
			Chart.DrawTrendLine("_open london" + londonOpen, londonOpen, MarketSeries.Open[MarketSeries.OpenTime.GetIndexByTime(londonOpen)], londonClose, MarketSeries.Open[MarketSeries.OpenTime.GetIndexByTime(londonOpen)], Color.Blue);

If you change that to include Heikin timeframes, it may provide you a result:

if (TimeFrame <= TimeFrame.Hour || TimeFrame <= TimeFrame.HeikinHour)

Having not used Heikin Ashi charts before, but just having a light read about them, I'd assume this will produce a different value than if it were ran off of a standard timeframe. Give it a go and let me know how it goes!


@pick

pick
17 May 2023, 19:59 ( Updated at: 21 Dec 2023, 09:23 )

RE:

o.o.d.exp said:

Even though you didn't realise what I wanted, the indicator has improved a lot thanks to your advice!
I have now tried to add the code you told me, trying to be as precise as possible, but I get errors...

Sorry, I didn't realise what version of C# you were compiling. Change:

newestTime = Bars[^1].OpenTime;

to:

newestTime = Bars[Bars.Count - 1].OpenTime;

Also, it looks like you have an extra opening curly brace on line 52 in your screenshot - just delete that and the rest looks good.


@pick

pick
17 May 2023, 19:11 ( Updated at: 21 Dec 2023, 09:23 )

RE:

o.o.d.exp said:

Thanks, it's a real blessing to find helpful people like you!
I also made the last change you made, and it seems to work fine now. It has improved considerably, the first version was constantly causing cTrader to freeze every time I changed timeframe, and I always had to force close it and restart it.
I think the problem is due to the fact that the sessions drawn on the chart by the indicator are infinite! The person who created the indicator did not provide a parameter for setting a customised number of sessions. In fact, although it is much better now, it always seems a bit cumbersome for the platform to have to calculate infinite sessions backwards in time, when it could calculate three or four at most.
So going back to the title of my first post, what would it take to reduce that infinite value and be able to enter a custom value of sessions, so that everything runs smoother?
For example, I have a pivot point indicator that has a parameter to select the number of days back in time on which to calculate pivots (see image). Could I copy and paste that part of the code from the pivot indicator to the forex session indicator?

 

Apologies - I didn't realise that's what you wanted! 

If you add a parameter:

[Parameter("MaxDays", DefaultValue = 5)]
public int maxDays { get; set; }

Add a global variable (it can be right below the line above, it just needs to be outside of a method - but inside the class):

DateTime newestTime; //Track newest time

Then, in the Initialize function, set a starting value:

protected override void Initialize()
{
	openSeries = MarketData.GetSeries(TimeFrame.Hour);
	newestTime = Bars[^1].OpenTime; //Set starting value
}

Then, at the top of the Calculate function, check if the bar calculating is too old:

public override void Calculate(int index)
{
	DateTime time = Bars[index].OpenTime; //Get time of bar calculating

	if (time > newestTime) //If it's newer than the newest time
		newestTime = time; //Update newest time

	if (time < newestTime.AddDays(-maxDays)) //Check if it's too old
		return; //Return out of method

	//Below is code that was already there...
	//London Session
	string today = Bars.OpenTimes[index].Day.ToString() + "/" + Bars.OpenTimes[index].Month.ToString() + "/" + Bars.OpenTimes[index].Year.ToString() + " ";
	if (london)
	{
     .........

 

That should hopefully provide you with some filtering. Keep in mind that this count will include weekends, so it won't show 5 trading days. 


@pick

pick
17 May 2023, 17:13 ( Updated at: 21 Dec 2023, 09:23 )

RE:

o.o.d.exp said:

Thank you, I succeeded. Now cTrader no longer gives me the error.
However, now it does not always draw the most recent sessions, as you can see in the picture.

And besides that it has a couple of other things to fix.
I don't think I'll be able to fix it completely even with your help, also because it doesn't seem fair to me to monopolize your time.

Do you happen to do programming work in exchange for payment?

 

At least there's some progress on the error side. I just ran the indicator for the first time and that also appeared to be the case for me. Removing the normal check for the "londonPrice", "nyPrice" etc brought it back to drawing at current time (on my end, at least) without the error prompting. 

Changing from: 

if (double.IsNormal(londonMin) && double.IsNormal(londonMax) && double.IsNormal(londonPrice))
{

to: 

if (double.IsNormal(londonMin) && double.IsNormal(londonMax))

 

 

At the moment, I don't offer any paid services, but if you require more help: there is a Telegram group for cTrader programming help in which I occasionally look at. Or of course, feel free to post here and if I see it, I'm more than happy to chime in if necessary!


@pick

pick
17 May 2023, 14:49 ( Updated at: 21 Dec 2023, 09:23 )

RE:

o.o.d.exp said:

Hi, thanks for your help.

This is my first attempt to modify a code without knowing anything about programming at all.
Comparing the original code with the one you sent me, it seems to me that the only difference is this additional string:

if (double.IsNormal(londonMin) && double.IsNormal(londonMax) && double.IsNormal(londonPrice)) //Check all values are normal
{ //

So following your instructions I took the string in question and copied and pasted it to all the other sessions, replacing only the name of each session.
At the end I clicked on build but some errors came up.


This is the complete modified code:

--

 

It looks like you have missed the final closing curly brace. Look in my previous response for the line that is simply just "} //" which comes after the "Chart.DrawStaticText" line. Other than that, it looks good.


@pick

pick
17 May 2023, 12:56

It looks as if the error is coming from the drawing methods. A quick solution could be to just check if the values are normal before calling the drawing methods. Here is the london session part edited to a state I'd expect it to be working better, you will need to do the same for the other sessions.

 

if (london)
{
	DateTime londonOpen, londonClose;
	DateTime.TryParse(today + "07:00:00", out londonOpen);
	DateTime.TryParse(today + "15:00:00", out londonClose);
	double londonMax = 0, londonMin = double.PositiveInfinity;
	londonOpen = londonOpen.AddHours(MarketSeries.OpenTime[index].IsDaylightSavingTime() ? 1 : 0);
	londonClose = londonClose.AddHours(MarketSeries.OpenTime[index].IsDaylightSavingTime() ? 1 : 0);
	
	for (int i = MarketSeries.OpenTime.GetIndexByTime(londonOpen); i < MarketSeries.OpenTime.GetIndexByTime(londonClose); i++)
	{
		londonMax = Math.Max(londonMax, MarketSeries.High[i]);
		londonMin = Math.Min(londonMin, MarketSeries.Low[i]);
	}
	double londonPrice = Math.Round((MarketSeries.Close[index] - openSeries.Open[MarketSeries.OpenTime.GetIndexByTime(londonOpen)]) / Symbol.PipSize, 2);
	
	if (double.IsNormal(londonMin) && double.IsNormal(londonMax) && double.IsNormal(londonPrice)) //Check all values are normal
	{ //
	
		Chart.DrawRectangle("london session " + londonOpen, londonOpen, londonMax, londonClose, londonMin, Color.FromArgb(50, 0, 50, 255)).IsFilled = true;
		if (openPrices)
			if (TimeFrame <= TimeFrame.Hour)
				if (MarketSeries.OpenTime[index] >= londonOpen)
					Chart.DrawTrendLine("_open london" + londonOpen, londonOpen, MarketSeries.Open[MarketSeries.OpenTime.GetIndexByTime(londonOpen)], londonClose, MarketSeries.Open[MarketSeries.OpenTime.GetIndexByTime(londonOpen)], Color.Blue);
		if (info)
			if (TimeFrame <= TimeFrame.Hour)
				if (MarketSeries.OpenTime[index] >= londonOpen)
					Chart.DrawStaticText("london info", "Pips From London Open: " + londonPrice, VerticalAlignment.Top, HorizontalAlignment.Right, Color.RoyalBlue);
				else
					Chart.DrawStaticText("london info", "Pips From London Open: Waiting for Open", VerticalAlignment.Top, HorizontalAlignment.Right, Color.RoyalBlue);
	} //
}

 


@pick

pick
12 May 2023, 16:39

All you'd need to do is check whether the bid is higher than the previous candle high (but only once per bar I'm assuming):

bool prevHighViolated = true;

protected override void OnTick()
{
	if (!prevHighViolated && Bid > Bars.HighPrices.Last(1))
	{
		Print($"Last high: {Bars.HighPrices.Last(1)} violated");
		prevHighViolated = true;
		
		//Order logic here
	}
}

protected override void OnBar()
{
	prevHighViolated = false;
}

 


@pick

pick
13 Apr 2023, 13:24

This issue also appears on my end when calling to obj.ObjectType on a FibonacciTimezone. Below I will include a crude workaround, but don't rely on this to work going forward - hopefully an official change can be made in the near future.

if (obj.GetType().ToString().Equals("cTrader.Automate.Adapters.ChartApi.ChartObjects.ChartFibonacciTimezonesAdapter"))
{
	Print("Found fibTimzones: " + obj.Name);
	ChartFibonacciTimezones fibTimeZones = (ChartFibonacciTimezones)obj;
	myFibTimeZonesList.Add(fibTimeZones);
}

 


@pick

pick
21 Mar 2023, 12:32

Yes, it is quite straightforward for a competent programmer to decompile an algo file. However, I would say that the vast majority of users and "developers" are incapable of doing so. The question is: who are you targeting? If your friend managed to get the source code of your bot, would they even understand what they're looking at? If a developer was capable of retrieving your code, don't you think they'd also be capable of writing similar code in the first place? 

As the user above mentioned, you can obfuscate your code, which will add an extra layer of inconvenience to someone who wants to read your logic - but it also doesn't make it impossible.

You could run all of your important logic on a server and return the results to clients if you really want to keep your code as far away from the user as possible. 

Realistically, nothing that you send to other people is as safe as something you keep to yourself under lock and key - if someone really wants to get into a system, they will.


@pick