Logic for repeating Min/Max value when condition is true

Hi all,

Continuing the discussion from Bars since higher or equal value:
As @ Tomasz has given the following functions in the above thread, it appears this request falls outside the scope of those available:

Bars since highest counting from some event
or (bars since highest in given period):
or (bars since all time highest):

I'm looking for the logic behind how I could:

  1. Test a condition, in this case if the same day
  2. Find the Min/Max value when the condition was met i.e. like LowestSince
  3. Repeat ONLY the Min/Max value across all bars when the condition was true.

Essentially I guess what I'm looking or perhaps even make a request for is the equivalent of a "MaxSinceLast" function. The function would work as MaxSinceLast(Condition,Array); where it will test the condition for true false and if true, will return only the max value of the specified array until the condition returns false where it will then return the next max value

* HighestSinceLast() OR MaxSinceLast(Condition,Array) 
* HighestSumSince() OR MaxSumSince(Condition,Array)
* LowestSinceLast() OR LowestSince(Condition,Array)
* LowestSumSince() OR MinSumSince(Condition,Array) // Probably not needed because may always = 0
* ValueWhenLast(Condition,Array)
* HighestBarsSinceLast() OR MaxBarsSince(Condition,Array)
* LowestBarsSinceLast() OR LowestBarsSince(Condition,Array) // Probably not needed because may always = 0

The rationale in this example is to find the number of bars when a condition occurred on a given time interval, then divide this by the total bars that occurred on that time interval to get a percentage of how long the situation occurred over that timeframe, like a time in market gauge. By being able to conditionally return only the highest/lowest value, you could then also use this to compare other values from a different condition.

Below is scenario to make illustrate the requirement:

Sameday = DateNum() == Ref(DateNum(),-1); // find if the same day.
BarsCount = BarsSince(Sameday)
MaxBarsInDay = MaxSinceLast(Sameday,BarsCount) // Returns the max bars when same day is true until false.
ConditionTF = C>5 // Returns a true for when the condition is met
ConditionSum = MaxSumSince(Sameday, ConditionTF) // While a sumSince would sum when the condition is met and create a tally the MaxSumSince would return the highest of SumSince across all bars where the condition is met. So if Condition1TF occurred 100 times, the function would return 100 for all times true opposed to the running count.
TimeInMarket = Condition1Sum / MaxBarsInDay //You can now compare the output of highest and lowest across multiple conditions within a given timeframe which is really useful for exploration and distribution information.

Amibroker Function For last Value

The image above is the closest I've come to a valid result as shown in the Repeat Values and HHV Values columns.

My code is:

Y51 = NewDay != 1;
Y52 = BarsSince(Y51 == 0);
Y50 = IIf(NewDay + 1 == Ref(NewDay,1),Y52, Null);
Y54 = ValueWhen(Y52>Ref(Y52,1),Y52);
Y55 = HHV(Y50,Y50);
Y56 = BarsSinceCompare(Y51,"<", Y51);

AddColumn(BI, "BI",1);
AddColumn(Y50,"Last Bar",1);
AddColumn(Y51,"New Day",1);
AddColumn(Y52,"Max Bars",1);
AddColumn(Y54,"Repeat Values",1);
AddColumn(Y55,"HHV Values",1);

Functions I've explored include:
Highest - Only pulls the highest value of the entire array, not the highest since last true.
HighestSince - works well, but creates a moving highest value instead of returning ONLY the highest since condition was true.
BarsSince - Used this to work out the number of bars since the condition was true. I need the only the highest of this value to be repeated on the same day.
HHV - Thought this may have solved it but only end up retrieving the previous days bars.
BarsSinceCompare - Another I thought may work, but again only ended up achieving the running count again.


How about HighestSince, all you need is reverse condition

HighestSince( NOT Condition, High );

If you want single scalar number from all bars when condition was true use this:

LastValue( highest( high * condition ) )

Hi Thomas,

Thanks for your assistance. I'm not sure if I'm applying this wrong, but when I use HighestSince and the condition is true (i.e. same day), the function can return multiple highs as a new high is created throughout the day. What I was chasing was just the last highest value (max) for the same day until a new day. The restriction is to keep the same time interval being analysed.

Similar in style to using TimeframeSet(inDaily), to repeat the day high for all minutes of that day when the periodicity is in 1 Minute.

I got it using a loop, but not sure this is the best approach. I have illustrated what I wanted in case it was not clear before:

BI = BarIndex();
NewDay = DateNum() != Ref(DateNum(),-1);
TrailingBars = BarsSince(NewDay);
TrailingHighestBars = HighestSince(NOT NewDay, TrailingBars);
CountingBarsTJ = HighestSince(NOT NewDay, TrailingBars );
MaxBarsOverall = LastValue( highest( TrailingBars * NewDay ));


TrailingBarsSinceConditionStart1 = BarsSince(NewDay);
ConditionLastBar = IIf(NewDay + 1 == Ref(NewDay,1),TrailingBarsSinceConditionStart1, Null);
TotalBarsSinceConditionEnd1 = Null;

for(j = BarCount; j >=1; j--)
	if(j == BarCount)
	else if (IsNull(ConditionLastBar[j-1]))
	else if(NOT(IsNull(ConditionLastBar[j-1])) )

AddColumn(TotalBarsSinceConditionEnd1,"Total Bars In this Day",1);

I am not sure why you complicate things like that. If you want day's HIGH for all the intraday bars of that day (effectively looking into the future as you don't know what the High is until day ends) you can use AFL Function Reference - TIMEFRAMEGETPRICE

// shift = 0 makes it looking into the future
dH = TimeFrameGetPrice( "H", inDaily, 0 );

For price data yes I agree and initially I took the TimeframeGetPrice approach for efficiency, but ended up moving to timeframeset because other calculations were needed while in the higher timeframe so I'd need to call the timeframeset/restore anyway; not sure which would be the better of the two there.

However this only works for price arrays and is also why I suggested the new functions to carry out the task of the loop above. I was trying to obtain other variables like the total minute bars in a given day as above or more importantly how to put a value calculated on the minute timeframe (and differs from next day) across all minute bars of the same day so it is ultimately unaffected by periodicity and holds for BOTH minute and higher timeframes. Is this where the application of StaticVarSet / Get functions would be of use? Would StaticVarSet (persistant) also be the appropriate function to store calculated values avoid repeating a processing intense function that only needs execution once in the day?

Most of this use is either exploratory or for statistical manipulation (and to learn the software). It seems a general no go to put data from the lower timeframe into the higher.

You don't need any new functions. TimeFrameGetPrice is just a shortcut to TimeFrameCompress/TimeFrameExpand pair.
READ THE MANUAL. I pointed it out to you. Click on the link I gave. It is right before your eyes. It provides information how to apply that to ANY ARRAY.

Read it and notice the sentence "Note these functions work like these 3 nested functions:" ... then you have the code for ANY array.

Really ChatGPT is going to rule the world soon if people fail to read single page of instructions.

PS: Each link that I post on this forum is tracked by Discourse. If anybody clicks on it there is a click counter next to the link. Since there is no click count on that link to manual I provided in my previous reply it means that you DID NOT BOTHER to click on that link. Really, please show at least SOME respect to the person who helps you and if somebody points you to to documentation, CLICK on that link.


Thomas your tone here is unnecessary and again I do appreciate your time. There is a clear misunderstanding on my end which is what I was hoping to resolve by posting back and I am genuinely happy to be corrected as you have done.

It is however, unfair to say lack of respect and attack by assuming I never visited the TimeframeGetPrice, when in fact I use a pdf version of the manual to mark notes on each function and therefore had no need to click the link directly. I also wouldn't have known it's more efficient than the TimeframeSet approach or had the desire to remove the loop entirely without some reading or following the "Performance tuning tips" at the very end of the manual.

This is why I posted "(and to learn the software)" because I understand I don't know something that I should and it's a "you don't know till you know" scenario. I initially dismissed TimeframeGetPrice because:

If you want day's HIGH

was not what I was after and the function using pricefield data. What I needed was how it worked as you later clarified and glad you wrote back.

Please understand that concepts read vs applied are not going to be as deep as experienced users. If I everyone got something first go, then there would be no need for this forum and I have tried to justify and show work in each case but now I am being unnecessarily criticised. People are going to hit stumbling blocks in different ways and seasoned users can take things for granted because their because of their longer exposure to the program.

I will explore the nested functions as you have pointed out very clearly. Now I understand where you were going with TimeFrameGetPrice it is the perfect way to move from loop to array processing. Thank you! If I have further questions, hopefully you will continue to assist :slight_smile:

My tone is unnecessary? It is easy to say when you don't do my work. How about following instructions when you received the answer? Is it necessary or not? Wouldn't you be frustrated if people constantly ignore instructions and don't read what is pointed out? Really my eyes are bleeding from spending too much time behind monitor, and repeating the same thing that is already answered in the documentation is complete and utter waste of my time and health. That is why I am frustrated. I don't understand what is difficult in clicking the link and reading. It takes LESS time than writing post in which you explain that you have PDF.
The hyper text was invented for two reason - that you can refer to RELEVANT information quickly and then you can COPY-PASTE. PDF should only be used for printing (if you really want to waste paper).

Now if you clicked on link provided earlier AFL Function Reference - TIMEFRAMEGETPRICE

You would read that:

Note these functions (TimeFrameGetPrice) work like these 3 nested functions:

TimeFrameExpand( Ref( TimeFrameCompress( array, interval, compress(depending on field used) ), shift ), interval, expandFirst )

So the answer to your "problem" is right before your eyes.

In my previous reply I gave you the code for High field. If you want to apply to any other array you:

  1. copy-paste the code from manual
  2. replace array with any array of your choice
  3. since you want "high" you use compressHigh for TimeFrameCompress
  4. use shift = 0 as in code provided (or better yet remove Ref() as it does nothing with zero shift)

Is it really difficult to copy-paste that code that is in the docs?

ANY_ARRAY = ... this is ANY ARRAY that you want to process
// copy-pasted from documentation
hh_any_array = TimeFrameExpand( TimeFrameCompress( ANY_ARRAY, inDaily, compressHigh ), inDaily, expandFirst );

Note: since shift is zero as I earlier wrote, the Ref() can be removed completely, so I removed it

Yes you could say "you should provide that ready code in first place". And I say: if I always gave ready-to-use codes people would never learn to read the docs and help themselves.

"If you give a man a fish, you feed him for a day. If you teach a man to fish, you feed him for a lifetime"


I think I understand your style now, so it should be easier moving forward without getting on your nerves :pray:.
The nested functions from AFL Function Reference - TIMEFRAMEGETPRICE do indeed fulfill the requirement. The penny dropped when you specifically mentioned the nested function section. I now know to search function for it's internal code if I understand how it operates so it can be applied to non-norm applications which is great :+1:

I have another question on the topic, which I have spent hours trying to work out, so if you would be so generous, your assistance will be of great help. When applying the nested function to mimic the TimeframeGetPrice function for proof of understanding it works great and indeed for other arrays as required. I ran into the next issue when moving from lower to higher timeframes. I was trying to take the array values calculated on the daily to weekly. A simple case scenario is taking the highest/lowest daily spreads of the week as illustrated below. I suspect the problem may lie because of the H / L arrays changing when periodicity changes, but to the best of my ability I failed to resolve it. My application was to employ TimeframeSet(), but still couldn't get it.

HighestDayOfWeekPrice = TimeFrameExpand(TimeFrameCompress(H, inWeekly,compressHigh), inWeekly, expandFirst); 
LowestDayOfWeekPrice = TimeFrameExpand(TimeFrameCompress(L, inWeekly,compressLow), inWeekly, expandFirst);

Spread = (H-L)/L*100;
Spread2 = TimeFrameExpand(TimeFrameCompress(Spread,inWeekly,compressLow),inWeekly,expandFirst);
Spread3 = TimeFrameExpand(TimeFrameCompress(Spread,inWeekly,compressHigh),inWeekly,expandFirst);

AddColumn(HighestDayOfWeekPrice,"Highest Price in Week",1.3); 
AddColumn(LowestDayOfWeekPrice,"Lowest Price in Week",1.3); 

AddColumn(Spread,"TimeFrame Indaily Spread");
AddColumn(Spread2,"Spread Day Low");
AddColumn(Spread3,"Spread Day High");

LowestDailySpreadinWeek = TimeFrameExpand(TimeFrameCompress(Spread, inWeekly,compressLow), inWeekly, expandFirst);
AddColumn(LowestDailySpreadinWeek,"Lowest Daily Spread in Week");
HighestDailySpreadinWeek = TimeFrameExpand(TimeFrameCompress(Spread, inWeekly,compressHigh), inWeekly, expandFirst);
AddColumn(HighestDailySpreadinWeek,"Highest Daily Spread in Week");

This was the last code I was working off. It works great from 1 Minute periodicity to 1 Day in keeping the true spread, but after you move above weekly it fails. I think I need to somehow work out how to use TimeFrameSet(inWeekly), but as of yet this is the best I achieved.

AddColumn(H,"Periodicity High");
AddColumn(L,"Periodicity Low");

HighestDayOfWeekPrice = TimeFrameExpand(TimeFrameCompress(H, inWeekly,compressHigh), inWeekly, expandFirst); 
LowestDayOfWeekPrice = TimeFrameExpand(TimeFrameCompress(L, inWeekly,compressLow), inWeekly, expandFirst);
AddColumn(HighestDayOfWeekPrice,"Highest Price in Week",1.3); 
AddColumn(LowestDayOfWeekPrice,"Lowest Price in Week",1.3); 

Spread = (H-L)/L*100;
SpreadHigh = TimeFrameExpand(Spread,indaily,expandFirst);
SpreadWeeksLowestDay = TimeFrameExpand(TimeFrameCompress(SpreadHigh,inWeekly,compressLow),inWeekly,expandFirst);
SpreadWeeksHighestDay = TimeFrameExpand(TimeFrameCompress(SpreadHigh,inWeekly,compressHigh),inWeekly,expandFirst);

AddColumn(Spread,"TimeFrame Indaily Spread");
AddColumn(SpreadHigh,"Spread Expanded");
AddColumn(SpreadWeeksLowestDay,"Spread Day Low");
AddColumn(SpreadWeeksHighestDay,"Spread Day High");

LowestDailySpreadinWeek = TimeFrameExpand(TimeFrameCompress(Spread, inWeekly,compressLow), inWeekly, expandFirst);
AddColumn(LowestDailySpreadinWeek,"Lowest Daily Spread in Week");
HighestDailySpreadinWeek = TimeFrameExpand(TimeFrameCompress(Spread, inWeekly,compressHigh), inWeekly, expandFirst);
AddColumn(HighestDailySpreadinWeek,"Highest Daily Spread in Week");

You do not need to TimeFrameSet, if you are TimeFrameCompress-ing -> TimeFrameExpand-ing already.

If solely TimeFrameSet is chosen, then you've have already compressed it (array), now you need to just TimeFrameExpand for the compressed bars to re-align once again to the base interval.

Multiple Time Frame support is a great guide with all necessary examples and explain concepts succinctly. Before applying further logic, please go through all the examples mentioned there.

Moreover, is Allow mixed EOD/Intraday data in Database Settings > Intraday Settings checked or unchecked?

Quoting from Database Settings / Intraday Settings:

Allow mixed EOD/Intraday data - it allows to work with database that has a mixture of intraday and EOD data in one data file. If this is turned on then in intraday modes EOD bars are removed on-the-fly and in daily mode EOD bars are displayed instead of time compressed intraday or if there is no EOD bar for corresponding day then intraday bars are compressed as usual.

So, if you are operating from an intraday timeframe and compressing/expanding those to capture higher timeframe such as Daily, Weekly, Monthly or Yearly then keep it unchecked.

Thanks @Cougar,

I can confirm the Allow mixed EOD/Intraday data is off. I can also confirm I have read the article a number of times, but from what I see in those examples I believe it is a different scenario - I will try to explain below.

First off the goal of timeframeset() in the code above was to ensure when changing periodicity e.g. daily through to 1 minute, that the daily spread value for that day/hour/minute stays consistent. If you enter the last code I put on any ticker and switch between daily through to minute periodicities you will see how the "Spread Day Low" and "Spread Day High" value stays the same from Daily periodicities through to 1minute. This is not the same when you remove the timeframeset from that section which is what I was after.

The problem is that when you switch from daily to weekly, the spread calculation uses the week's high and low from the periodicity values. To explain another way, say the week has 5 trading days. On a weekly periodicity, how could I pull the high and low values of a specific trading day for that week? I understand you can perform generic calculations with the TimeframeSet, but they won't reference a specific day of my choosing within that timeframe; they are period based only. Similarly with timeframecompress / compress low/high you could pull the low/high/open/close value for that week but not a specific low from the week over the last 5 trading days (or I don't understand how)...

For most applications I guess this isn't necessary because you will always operate on the lowest interval so it's not needed to bring the value up to the higher timeframe. It was to see if you are on a weekly chart, if you could pull values of a specific day e.g. the day that had the minimum spread and the week and the day that had the highest spread within that week. In the last image I posted, if you refer to the yellow section you can see how the spread calculations are forced off the local periodicity in the first columns. I was trying to get the bottom yellow values to match the top, but because I'm unsure how to pull a specific day's high/low while in weekly periodicity I don't know how to complete that step.

Thanks everyone for you time.

Since you mentioned that you went through Multiple Time Frame support, I believe you! However, few aspects are worth observing:

After digesting the entire article, towards the end of it you would notice, "How does it work internally ?":

You might be asking this for the first time, but its actually a decades old question asked innumerable times. :slightly_smiling_face:

To be honest, I do not consider this as limitation of TimeFrame (TF) functions rather its just how it behaves.

Below is one efficient way to tackle such a scenario. Basically, the idea is to first run an exploration that would store arrays onto persistent StaticVars. Then, capture those in your chart or wherever required.

Here for demonstration purposes, the exploration is carried on Daily (lower) timeframe and the chart depicts Weekly (higher) timeframe:

[Right click on the image to open in a new tab for better view]

|This code is for AmiBroker Formula Language (AFL) learning (non-commercial) purposes only. Please do not copy this code|
|(or any other version of it) and paste it over on other forums or anywhere else on the Internet or in any other form	|
|without the AmiBroker Forum owner's consent (https://forum.amibroker.com/).                                            |

EnableTextOutput( 0 ); // To turn-off Interpreter default display
frstWkDayNum = 1; // Monday
lastWkDayNum = 5; // Friday
weekDays	 = "Mon,Tues,Wed,Thurs,Fri";

procedure setWeekDayHL() {
	wkDay = DayOfWeek();
	wkDayNumRvrsdList = "5,4,3,2,1";
	for( d = frstWkDayNum; d <= lastWkDayNum; ++d ) {		
		dayCondition = wkDay == d;
		dH = ValueWhen( dayCondition, H );
		dL = ValueWhen( dayCondition, L );
		dBars			= BarsSince( dH != Ref( dH, -1 ) );
		wkDayNumRvrsd	= StrToNum( StrExtract( wkDayNumRvrsdList, d - 1 ) );
		dBarsCond		= dBars >= wkDayNumRvrsd;
		dH = IIf( dBarsCond, Null, dH );	VarSet( "wkDayH" + d, dH );
		dL = IIf( dBarsCond, Null, dL );	VarSet( "wkDayL" + d, dL );

_SECTION_BEGIN( "Capture lower-tf HL in higher" );
	if( Status( "ActionEx" ) == actionIndicator ) {
		SetChartOptions( 1, chartShowDates );
		Plot( C, "Price", colorDefault, styleCandle );		
		Title = StrFormat( "{{INTERVAL}} began on {{DATE}}\nO %1.2f H %1.2f L %1.2f C %1.2f", O, H, L, C );
		StaticVarSet( "chartBaseInterval" + Name(), Interval(), True ); // Chart's selected interval is passed onto the exploration using this staticvar
		dispStr	 = "";
		for( d = frstWkDayNum; d <= lastWkDayNum; ++d ) {
			wkDayH = StaticVarGet( "wkDayH" + d + Name() );		wkDayL = StaticVarGet( "wkDayL" + d + Name() );			
			weekDay = StrExtract( weekDays, d - 1 );
			dispStr += StrFormat( "%s\tH %s, L %s, Spread = %s\n",
				NumToStr( SelectedValue( wkDayH ), 1.2, False ),
				NumToStr( SelectedValue( wkDayL ), 1.2, False ),
				NumToStr( SelectedValue( wkDayH - wkDayL ), 1.2, False )
		printf( dispStr );

_SECTION_BEGIN( "Persist HL values from lower-tf" );
	if( Status( "ActionEx" ) == actionExplore ) {
		if( Interval() == inDaily ) {
			chBaseInterval = Nz( StaticVarGet( "chartBaseInterval" + Name() ), inWeekly ); // By default weekly timeframe is considered if base interval from chart somehow returns Null
			Filter = 1;
			AddColumn( O, "O", 1.2 );
			AddColumn( H, "H", 1.2 );
			AddColumn( L, "L", 1.2 );
			AddColumn( C, "C", 1.2 );			
			for( d = frstWkDayNum; d <= lastWkDayNum; ++d ) {
				dH = VarGet( "wkDayH" + d );
				dL = VarGet( "wkDayL" + d );
				StaticVarSet( "wkDayH" + d + Name(), TimeFrameExpand( TimeFrameCompress( dH, chBaseInterval, compressLast ), chBaseInterval, expandFirst ), True );
				StaticVarSet( "wkDayL" + d + Name(), TimeFrameExpand( TimeFrameCompress( dL, chBaseInterval, compressLast ), chBaseInterval, expandFirst ), True );
				weekDay = StrExtract( weekDays, d - 1 );
				AddColumn( dH, weekDay + "H", 1.2 );
				AddColumn( dL, weekDay + "L", 1.2 );
		else {
			Error( "Switch periodicity to Daily. For this exploration demonstration we are using inDaily as base interval." );

Thanks again @Cougar!

It's a fascinating world learning systematic trading and while not easy I am loving it. Amibroker has so much speed/automation power it's not funny and coupling that with a sound statistical baseline it really is on another level. I just need to learn how to apply it fully, which I get... will take time!

The second yellow block you referred to was the thing that stuck out when I went through the article again. I kinda touched on the StaticVar in my third comment, so I'm super happy you mentioned it and glad to finally have the right application to utilise it. Agree that it's not a limitation of timeframe but more a procedural difference to understand when what function applies. It's hard not to feel hopeless sometimes, something you know should be simple can take so long to grasp...

The posted code will be of great help and the video shows exactly what I needed. Thank you so much for your time and guidance.


1 Like

Actually, the "procedural" bit is fairly simple. The real fun lies in establishing logic without using loops, like the one shown in setWeekDayHL() method of aforementioned code.

When ValueWhen() is applied, it returns value of an array when a particular condition is met and carries that value until True again, then, its value updates to new array element. Like so,

dayCondition = wkDay == d;
dH = ValueWhen( dayCondition, H );
dL = ValueWhen( dayCondition, L );

We could very well wrap those into TimeframeCompress/Expand, but the issue would occur on a holiday.

Let's say, a Wednesday is a holiday in a week. If stuck to this logic, that non-existent Wednesday would still hold previous week's Wednesday values until next time wkDay == 3 is True.

That's why dBars >= wkDayNumRvrsd condition was implemented to restrict values onto its corresponding week and not flow until True. Now, TimeframeCompress-ing/Expand-ing array makes it potent.

Mostly, using combination of ValueWhen() and since functions, 9 out of 10 scenarios can be efficiently solved without using loops.

(categorized list of functions)

In my experience, daily 3-4 hours for 3 months with the manual - every example, every nuts-n-bolts! You will be astonished to discover aspects that one wouldn't even imagine to think otherwise. Yes, the process is overwhelming but highly rewarding. It's all well-thought, be it data visualization or analysis.

Thanks goes to Tomasz & team, we can only be appreciative of their passion (sometimes misconstrued)!

1 Like

This topic was automatically closed 100 days after the last reply. New replies are no longer allowed.