Monthly TimeFrame Constant

In looking at the Time Frame constants I see monthly is set to 25 * the inDaily value, but the average month has only 21 days not 25. Wondering why 25 is used instead of 21. Typical year has 252 trading days. 252 / 12 = 21.

When you attempt to convert bars using the formula DailyBars = MonthlyBars * inMonthly / Interval() your going to get incorrect results.

Why was a multiple of 25 times the inDaily constant used instead of 21?

Numerical value of constant doesn't really matter, it could be anything as long as it gets special treating inside AmiBroker. The inMonthly constant value is NOT 25 * inDaily. It is different (unique) value (2160001) and it does not mean 25 days.
The value is meant to be unique enough NOT to be integer multiply of days. Such value has special code path that uses ACTUAL CALENDAR MONTH, not any fixed number of days.

4 Likes

Okay, I was hoping to keep close to the same approximate length for indicators when switching intervals by using a formula like CurrentBars = BaseBars * BaseInterval / Interval() and then plotting the indicator. For example MA(C, CurrentBars) would then show roughly the same length MA on any time interval.

I know using TimeFrameSet, TimeFrameRestore, and TimeFrameExpand can be used when going from shorter to longer time frames, but the result will not be quite the same, and you can't go from longer time frames as the base to shorter time frames - for example can't go from MA(C, 10) on a monthly time frame to MA(C, 210) on a daily time frame. Using a simple time frame ratio factor would allow going either longer or shorter from the starting time frame.

Since you know the ratios you want to use, you could write a simple utility function to do the conversion from the shortest (base) bar interval you're interested in, e.g. daily, to the target interval. Something similar to this:

function GetIntervalBars(dailyBars)
{
	switch (Interval())
	{
		case inDaily: currBars = dailyBars; break;
		case inWeekly: currBars = round(dailyBars/5); break;
		case inMonthly: currBars = round(dailyBars/21); break;
		case inYearly: currBars = round(dailyBars/252); break;
		default: currBars = 0;
	}

	return currBars;
}
1 Like

Yes, I realize I could create a function like the one you proposed (though yours is too limited), but I was hoping for something more comprehensive and simple like the ratio I suggested. The ratio allows going both to longer and shorter time frames quite easily. The function you proposed covers only going from shorter to longer time frames. I was hoping to go to both longer and shorter time frames like from monthly to daily, daily to quarterly, weekly to daily, daily to weekly, etc. easily and simply.

Thinking in terms of some fixed ratios is incorrect, since those fixed "days per month" don't exist. The 21 days per month is a fiction. Some months are shorter some are longer. Some instruments (like cryptocurrencies) do not follow Monday-Friday scheme and trade 365 days per year. Sometimes there are holidays, sometimes trading stops due to some serious events (like 9/11). There is just no fixed ratio.

@mradtke gave you the solution you were seeking, why not just use it?

1 Like

As for my function being "too limited", it was meant as a simple example to illustrate the concept. If you want to convert from daily bars to intraday bars, then simply multiply by some factor instead of dividing.

If you need to convert between two random intervals (i.e. the base interval changes), then write a second function that can convert from any interval to daily. That function can then be used in conjunction with the first function that converts from daily to any other interval.

Okay, I will elaborate on what I was hoping to do.

To Tomasz's point I know each day doesn't hold the same number of hours, just as each week or month doesn't hold exactly the same number of days. But when working with moving averages, RSI, MACD, and a host of other indicators the differences when switching time frames with anything other than the smallest numbers for bars becomes insignificant using accurate approximations of the average bars in one time frame vs another.

Take for example starting with a 10 month moving average and converting to daily (210 bars) will result in the error of only 1 bar or so which would not be noticeable on a charted moving average.

Here is a breakdown of the differences between several time frames for 2024

Month Trading Days Trading Hours Quarter Days Quarter Hours
1 21 136.5
2 20 130
3 20 130 61 396.5
4 22 143
5 22 143
6 19 123.5 63 409.5
7 22 140
8 22 143
9 20 130 64 413
10 23 149.5
11 20 127
12 21 133.5 64 410
Total 252 1629 252 1629
Average 21 135.8 63 407.3

Using the ratio approach I was hoping for involves the following function

function CurrentBars (BaseInterval, BaseBars) {
	return BaseBars * BaseInterval / Interval();
}

Using the method suggested here results in the following for only 3 time frames.

function CurrentBars(BaseInterval, BaseBars) {
	switch (Interval()) {
		case inDaily: 
			switch(BaseInterval) {
				case inWeekly: result = BaseBars * 4.85; break;
				case inMonthly: result = BaseBars * 21; break;
				default: result = BaseBars;
			} break;
		case inWeekly: 
			switch(BaseInterval) {
				case inDaily: result = BaseBars / 4.85; break;
				case inMonthly: result = BaseBars * 4.33; break;
				default: result = BaseBars;
			} break;
		case inMonthly: 
			switch(BaseInterval) {
				case inDaily: result = BaseBars / 21; break;
				case inWeekly: result = BaseBars / 4.33; break;
				default: result = BaseBars;
			} break;
	}
	return result;
}

As this handles only 3 time frames. Adding only one more time frame would add another switch and another case to each of the existing switches.

This is the reason I was looking for a simpler and easier approach.

I also realize the above approach is limited to stocks, Mutual Funds, CEFs, REITs, ETFs, and anything else that trades the same hours as stocks, but that is what I wanted to use it for.

I understand your goal and intended use cases. To avoid the N^2 complexity problem you identified, you can just use two functions as I described, which reduces your code complexity to 2N.

I don't see any N^2 "complexity" problem. You just call it TWICE (not 2 * N). You call @mradtke function just 2 times in the formula. Not 2*N, not N^2.

You call it TWO TIMES. Once to convert "base interval" and once to convert interval. Then you do one division, and you get the ratio you wanted.

2 Likes

Could not see how calling the function @mradtke presented twice would give me what I wanted. It doesn't reference the base interval anywhere. But it did give me the idea of how to create a function to call twice and then do a ratio.

//--- Create values globally so they only have to calculated once	
Daily = inHourly * 6.464; Weekly = Daily * 4.846; Monthly = Daily * 21; Quarterly = Daily * 63; Yearly = Daily * 252;

function TimeAdj(Time) { // Adjust time values to give desired ratios
	global Hourly, Weekly, Monthly, Quarterly, Yearly;
	switch (Time) {
		case inHourly: result = Time; break;
		case inDaily: result = Daily; break;
		case inWeekly: result = Weekly; break;
		case inMonthly: result = Monthly; break;
		case inQuarterly: result = Quarterly; break;
		case inYearly: result = Yearly; break;
		default: result = Time / inHourly; break;;
	} return result;
}

function BarsAdj (Bars, Time) { 
	factor = TimeAdj(Time) / TimeAdj(Interval()); if (Time > Interval()) factor = round(factor); 
	return Bars * factor; 
}

function TAMA (Array, Bars, Time) { // Time adjusted moving average
	if (Time > 0) Array = TimeFrameCompress(Array, Time); // Change time frame
	else if (Time < 0) Bars = BarsAdj(Bars, abs(Time)); // Convert bars to current time frame
	result = MA(Array, Bars); // Calculate moving average
	if (Time > 0) result = TimeFrameExpand(result, Time); // Convert results back to current time
	return result;
}

The last function is an example of how the time adjustment would be used for the MA() function.

If Time is 0, the function returns the same as just calling MA() with the specified number of bars. TAMA(C, 20, 0) is the same as MA(C, 20) based on the current time interval.

If the value of Time > 0, calculate the MA with bar length from the specified Time. TAMA(C, 20, inWeekly) keeps the moving average calculation on the weekly time interval no matter what the current time interval is.

If the value of Time < 0, convert the bar count from the base time to the current time. TAMA(C, 20, -inWeekly) so the MA will be pretty close to the original weekly moving average no matter what the current time frame is. If current time frame is daily, the MA would be converted a 100 bar daily MA.

The new moving average function now handles all three ways of calculating the moving average in relation to differing time frames. A similar approach could be used a lot of other indicators.

I was hoping for a simpler way of doing this, but this is as good as I came up with following Tomasz's suggestion.

1 Like