Skip to content
Level 5 · Real-Time AmiBroker UserLessonPart 24 · page 2 of 330 min Professional edition Live feed
30Minutes
15AFL functions
13Sources
Professional + live feedRequires

This page needs the Professional edition and a real-time data feed. Every gated part of this course ships a Level A path that uses Bar Replay and historical data instead — look for it below.

AFL functions taught here15

Defining Intraday Setup Conditions

“Buy the opening-range breakout in a stock with high relative volume when the daily trend is up” is a sentence with three unspecified terms in it. Each one sounds like a definition. None of them is. Opening range over what window, measured from which bar, frozen when? Relative to what volume, over how many days, adjusted for the time of day or not? Daily trend as of when — yesterday’s close, or a daily bar that is still being built out of the minutes you are trading through?

Getting those three right is most of the work in this part. Getting them wrong produces a scanner that finds beautiful candidates in testing and finds nothing useful in a session, because at least one of the three was reading data that did not exist yet.

By the end of this lesson you should be able to write all three in AFL from past bars only, explain exactly which line in each one prevents a forward reference, and say why the intraday versions of these ideas are structurally more brittle than the daily versions you met earlier in the course.

Every intraday condition begins with a time test, and there are three ways to get one wrong before you have written a single condition.

Bar timestamps are not trade times. A timestamp identifies a whole bar. AmiBroker’s documented default is to stamp a bar with the start of its interval, so a five-minute bar stamped 10:35 covers 10:35:00 to 10:39:59. The setting lives in Tools → Preferences → Intraday and can be changed. If your bars carry the end time instead, every comparison below is one bar out, silently, in the direction that flatters you.

TimeNum() is not a number you can do arithmetic on. It packs the clock into decimal digits: 10000 * hour + 100 * minute + second, so 12:37:15 becomes 123715. Comparisons against it are safe — TimeNum() >= 93000 means what you think — but subtraction is not. TimeNum() - 100 is “one minute earlier” only when the minute field happens to be non-zero. Hour() and Minute() return ordinary numbers, so the safe habit is to build a minutes-since-midnight array once and work in that:

Fragment — not a complete formula

BarMin = 60 * Hour() + Minute(); // minutes since midnight, safe for arithmetic

The clock in your database may not be the exchange’s clock. The intraday settings include a time shift, and Status("timeshift") reports it in seconds. Your formula cannot tell whether the session start you typed is exchange time or shifted time; it just compares numbers. Part 20 is the place that argument belongs, and it is a prerequisite for this one.

With minutes-since-midnight in hand, the session and its landmarks fall out cleanly:

Fragment — not a complete formula

InSession = BarMin >= SessionStartMin AND BarMin < SessionEndMin;
PrevInSess = Nz( Ref( InSession, -1 ) ); // Nz, so bar 0 does not compare with Null
SessionOpen = InSession AND NOT PrevInSess; // the first bar of each session

The Nz() matters more than it looks. On the very first bar of the array Ref(InSession, -1) is Null, and negating a Null is not a question with a good answer. Handling missing data explicitly rather than letting Null propagate is a course rule; this is one of the places it earns its keep.

An opening range is the high and low of the first N minutes of a session, fixed once those minutes are over. Two things must be true of any correct implementation: it must reset at each session boundary, and it must stop changing at the end of the window without ever referring to a bar after the current one.

The reset is SessionOpen. The running extremes are HighestSince and LowestSince, which return the highest and lowest value of an array since a condition was last true. The freeze is ValueWhen.

Fragment — not a complete formula

ORHighRunning = HighestSince( SessionOpen, High );
ORLowRunning = LowestSince( SessionOpen, Low );
InOR = InSession AND BarMin < SessionStartMin + ORMinutes;
ORHigh = ValueWhen( InOR, ORHighRunning );
ORLow = ValueWhen( InOR, ORLowRunning );

ValueWhen( InOR, x ) returns x as it stood on the most recent bar where InOR was true. During the opening range that is the current bar, so the value tracks. Once the range window closes, the most recent such bar is permanently the last bar of the range — so the level freezes, using only bars in the past.

Freezing a 15-minute opening range on 5-minute bars

The running high keeps climbing. ValueWhen stops reading it the moment InOR stops being true, and no expression looks forward.
Bar9:309:359:409:459:509:55
High101.0101.8101.4102.6102.2103.1
InOR111000
HighestSince(SessionOpen, High)101.0101.8101.8102.6102.6103.1
ValueWhen(InOR, running high)101.0101.8101.8101.8101.8101.8
The running high keeps climbing. ValueWhen stops reading it the moment InOR stops being true, and no expression looks forward. Prices in this diagram are invented for the illustration. They are not market data and nothing should be inferred from them.

Compare that with the tempting alternative: find the last bar of the range window by testing whether the next bar is outside it. That requires Ref( InOR, 1 ), a positive shift, which reads a future bar. On a chart used only for drawing it would be harmless. In a rule it is a look-ahead defect, and it is the single most common way this particular condition is written wrongly.

One further guard is needed. Before today’s range has closed, ValueWhen is still holding yesterday’s level, because that was genuinely the most recent bar where InOR was true. A scanner that reads ORHigh at 09:35 gets a real number that describes a different day. So nothing may consult the level unless the range has actually completed:

Fragment — not a complete formula

AfterOR = InSession AND BarMin >= SessionStartMin + ORMinutes;
ORHighPlot = IIf( AfterOR, ORHigh, Null ); // do not draw, or test, a level that does not exist yet

There is also an arithmetic requirement that has nothing to do with AFL: the scan interval must divide the opening-range length. A thirty-minute range on seven-minute bars has no bar boundary to end on, so BarMin >= SessionStartMin + 30 first becomes true part way through a bar that is partly inside the range. You will get a level. It will not be the level you defined.

“Relative volume” is used to mean at least three different things, and the differences are not cosmetic. The version that is useful intraday is a pace: how much has traded so far today, compared with how much would have traded by this point on an ordinary day.

The numerator is easy, and SumSince does it in one call — it accumulates an array since a condition was last true, which is exactly a session-cumulative volume:

Fragment — not a complete formula

SessionVolume = SumSince( SessionOpen, Volume, True ); // True: include the opening bar

The denominator is where the judgement lives. The baseline must come from completed days, or it contains today. Compute it in the daily timeframe, shift it back one daily bar, and bring it down:

Fragment — not a complete formula

TimeFrameSet( inDaily );
BaselineVolume = Ref( MA( Volume, VolDays ), -1 ); // completed daily bars only
TimeFrameRestore();
BaselineVolume = TimeFrameExpand( BaselineVolume, inDaily, expandFirst );

Then the expectation, and the ratio:

Fragment — not a complete formula

BarLengthMin = Interval() / 60; // Interval() returns seconds
ElapsedMin = BarMin - SessionStartMin + BarLengthMin;
ExpectedVolume = BaselineVolume * ElapsedMin / SessionMinutes;
VolumePace = IIf( ExpectedVolume > 0, SessionVolume / ExpectedVolume, Null );

ExpectedVolume assumes volume arrives at a constant rate through the session. It does not. Trading is concentrated near the open and near the close in most equity markets, so a flat-rate expectation is too low in the morning and too high in the middle of the day. A pace of 1.4 at 09:45 and a pace of 1.4 at 13:30 are not the same observation, and a threshold of “pace above 1.2” applied all day is really a threshold that is easy to clear in the first half hour and hard to clear at lunchtime.

That is a defect in the measure, and it has two honest responses.

The cheap one is to be explicit about it: use the pace as a ranking device within a single scan, where every symbol is being measured at the same moment and the distortion is common to all of them, rather than as an absolute gate that means the same thing at every hour.

The better one is to replace the flat rate with a baseline measured at the same time of day. Instead of comparing against a fraction of an average day, compare today’s cumulative volume at bar k of the session against the cumulative volume at bar k of previous sessions. That is a genuinely better measure, and it depends on every session having the same number of bars in the same order — which brings us to the reason the last section of this lesson exists.

This is where multi-timeframe look-ahead usually enters an intraday scanner, and the mechanism is worth understanding precisely rather than avoiding by superstition.

TimeFrameExpand( array, interval, mode ) decides at which base-interval bar a higher-timeframe value becomes visible. The documented meanings are exact: expandLast writes the value starting from the last bar of the period, expandFirst writes it starting from the first bar of the period, and expandPoint writes it only at the period’s last bar and leaves the rest Null.

The documentation is explicit that expandFirst “used on price different than open may look into the future” — the whole period’s aggregate is written onto the period’s first bar, so on Monday you would be handed a value that is only knowable after Friday. Applied to an intraday scanner reading daily bars, the same leak means the 09:30 bar is handed a daily close that will not exist until the market shuts.

And yet the pattern below uses expandFirst, deliberately and safely:

Fragment — not a complete formula

TimeFrameSet( inDaily );
DailyTrendUp = Ref( Close > MA( Close, TrendLen ), -1 );
TimeFrameRestore();
DailyTrendUp = TimeFrameExpand( DailyTrendUp, inDaily, expandFirst );

The Ref( ..., -1 ) is doing the work. Inside the daily frame it steps back one daily bar, so the array holds yesterday’s value. Expanding that with expandFirst writes yesterday’s completed result onto today’s first intraday bar — which is precisely what you want, because it is known before the session opens and it stays constant all day. Remove the Ref and the identical call becomes the leak.

The documented alternative says the same thing more briefly:

Fragment — not a complete formula

PrevDayClose = TimeFrameGetPrice( "C", inDaily, -1 ); // -1: previous COMPLETED daily bar

One more caution belongs to this section. Compressing daily bars out of an intraday database is one of the cases the QuickAFL documentation names by name: partial evaluation “may not give identical results” for timeframe functions used with intervals much higher than the base interval. The remedy is documented — SetBarsRequired( sbrAll, sbrAll ) at the top of the formula, which turns QuickAFL off. Without it, the same scanner can produce different daily-trend values for a narrow range than for all quotations, and you will spend an evening looking for a bug in your logic.

Note also that the daily bars you get this way are the daily compression of your intraday database. They inherit its intraday settings — the session filtering, the daily-compression basis, the time shift. They are not guaranteed to equal an end-of-day vendor’s daily bars for the same symbol, and if you have both, comparing them is a worthwhile ten minutes.

One intraday setup, assembled from causal parts

  1. Yesterday closedDaily trend and volume baseline become known and fixed
  2. Session opensSessionOpen resets the running extremes and the volume accumulator
  3. Opening range formsHighestSince / LowestSince track; nothing may be tested yet
  4. Range window closesValueWhen freezes the levels using past bars only
  5. Rest of sessionLevels fixed, pace updating, trend gate constant
Every arrow points forwards. No value on the left is computed from anything on its right.

Draw all three quantities on a chart, in the same formula and with the same definitions the scanner will use, so that they can be inspected before they are trusted.

Complete runnable AFL

opening-range-panel.afl
// opening-range-panel.afl
// Part 24 - Defining Intraday Setup Conditions
//
// Draws, on an intraday chart, the three quantities the scanner in this part
// depends on, so that you can look at them before you trust them:
//
// 1. the session opening range, frozen at the moment the range window closes;
// 2. the session volume pace ("relative volume"), measured against a baseline
// built only from COMPLETED previous days;
// 3. a daily trend gate, read from yesterday's completed daily bar.
//
// Nothing here is a trading rule. It is a set of definitions made visible.
//
// Assumptions declared up front:
// - An intraday database, and a chart interval that divides the opening-range
// length exactly. A 30-minute opening range on 7-minute bars has no bar
// boundary to end on, and the level you get will not be the level you meant.
// - Bars are stamped with the START time of the interval, which is AmiBroker's
// documented default (Tools -> Preferences -> Intraday). If your bars carry
// the END time instead, every time comparison below is one bar out.
// - The session times below are entered in the SAME clock the database uses.
// If a time shift is configured, these are shifted times, not exchange
// times. This formula cannot detect that; Part 20 shows how to check it.
// - The daily values come from daily compression OF THIS DATABASE, so they
// inherit its Intraday Settings - session filtering and the daily-compression
// basis in particular. They are not guaranteed to equal an end-of-day
// vendor's daily bars for the same symbol.
_SECTION_BEGIN( "Opening range panel" );
StartHour = Param( "Session start - hour", 9, 0, 23, 1 );
StartMinute = Param( "Session start - minute", 30, 0, 59, 1 );
EndHour = Param( "Session end - hour", 16, 0, 23, 1 );
EndMinute = Param( "Session end - minute", 0, 0, 59, 1 );
ORMinutes = Param( "Opening range length (minutes)", 30, 5, 240, 5 );
VolDays = Param( "Volume baseline (completed days)", 20, 5, 100, 1 );
TrendLen = Param( "Daily trend average (days)", 50, 10, 200, 5 );
SetBarsRequired( sbrAll, sbrAll ); // daily compression from intraday bars is one
// of the documented QuickAFL edge cases
// ---------------------------------------------------------------------------
// Session geometry. Everything is expressed in minutes since midnight, because
// TimeNum() is a decimal-packed number: 100000 minus 100 is not "one minute
// before 10:00". Hour() and Minute() give values you can safely do arithmetic on.
// ---------------------------------------------------------------------------
BarLengthMin = Interval() / 60;
SessionStartMin = 60 * StartHour + StartMinute;
SessionEndMin = 60 * EndHour + EndMinute;
SessionMinutes = SessionEndMin - SessionStartMin;
BarMin = 60 * Hour() + Minute();
InSession = BarMin >= SessionStartMin AND BarMin < SessionEndMin;
PrevInSess = Nz( Ref( InSession, -1 ) ); // Nz, so the first bar of the array
// does not compare against Null
SessionOpen = InSession AND NOT PrevInSess;
InOR = InSession AND BarMin < SessionStartMin + ORMinutes;
AfterOR = InSession AND BarMin >= SessionStartMin + ORMinutes;
// ---------------------------------------------------------------------------
// The opening range. HighestSince/LowestSince give the running extremes of the
// session so far; ValueWhen freezes them.
//
// ValueWhen( InOR, x ) returns x as it stood on the most recent bar where InOR
// was true. Once the range window has closed, that bar is the LAST bar of the
// range - so the level is frozen using past bars only, with no forward
// reference anywhere in the expression.
// ---------------------------------------------------------------------------
ORHighRunning = HighestSince( SessionOpen, High );
ORLowRunning = LowestSince( SessionOpen, Low );
ORHigh = ValueWhen( InOR, ORHighRunning );
ORLow = ValueWhen( InOR, ORLowRunning );
// Before today's range has closed, ValueWhen is still holding YESTERDAY's
// levels. Blank them rather than drawing a line that does not exist yet.
ORHighPlot = IIf( AfterOR, ORHigh, Null );
ORLowPlot = IIf( AfterOR, ORLow, Null );
ORRange = ORHighPlot - ORLowPlot;
// ---------------------------------------------------------------------------
// Volume pace and the daily trend gate, both built from completed daily bars.
// ---------------------------------------------------------------------------
SessionVolume = SumSince( SessionOpen, Volume, True ); // includes the open bar
ElapsedMin = BarMin - SessionStartMin + BarLengthMin; // minutes of session done
TimeFrameSet( inDaily );
// Ref( ..., -1 ) steps back one DAILY bar, so today's still-forming daily
// bar is excluded from both of these.
BaselineVolume = Ref( MA( Volume, VolDays ), -1 );
DailyTrendUp = Ref( Close > MA( Close, TrendLen ), -1 );
TimeFrameRestore();
// expandFirst is safe here, and it is safe only because of the Ref( ..., -1 )
// above. What lands on today's first intraday bar is yesterday's completed
// value. Without the shift, this same call would hand today's unfinished daily
// bar to the first bar of the morning - the documented look-ahead leak.
BaselineVolume = TimeFrameExpand( BaselineVolume, inDaily, expandFirst );
DailyTrendUp = TimeFrameExpand( DailyTrendUp, inDaily, expandFirst );
// The flat-rate assumption: if today traded like an average recent day, and
// volume arrived evenly through the session, this is what would have traded by
// now. Volume does NOT arrive evenly - see the lesson - so a pace above 1.0
// early in the session is normal rather than notable.
ExpectedVolume = BaselineVolume * ElapsedMin / SessionMinutes;
VolumePace = IIf( InSession AND ExpectedVolume > 0,
SessionVolume / ExpectedVolume, Null );
// ---------------------------------------------------------------------------
// Output.
// ---------------------------------------------------------------------------
Plot( Close, "Close", colorDefault, styleCandle );
Plot( ORHighPlot, "OR high", colorGreen, styleStaircase | styleThick );
Plot( ORLowPlot, "OR low", colorRed, styleStaircase | styleThick );
// Own scale: the pace ratio sits around 1 and would otherwise be an invisible
// flat line at the bottom of a price axis.
Plot( VolumePace, "Volume pace", colorBlue, styleLine | styleOwnScale );
PlotShapes( IIf( SessionOpen, shapeUpArrow, shapeNone ), colorLightGrey, 0, Low, -18 );
ReplayActive = GetPlaybackDateTime() > 0; // zero when Bar Replay is not running
Title = Name() + " " + Interval( 2 ) + " " +
WriteIf( InSession, "in session", "outside session" ) + " " +
WriteIf( AfterOR, "OR complete",
WriteIf( InOR, "OR forming", "no opening range yet" ) ) + " " +
"OR " + WriteVal( ORLowPlot, 1.2 ) + " - " + WriteVal( ORHighPlot, 1.2 ) +
" (range " + WriteVal( ORRange, 1.2 ) + ") " +
"pace " + WriteVal( VolumePace, 1.2 ) + "x " +
WriteIf( DailyTrendUp, "daily trend up", "daily trend not up" ) + " " +
WriteIf( ReplayActive, "[Bar Replay active]", "" );
// A modest, deliberately unambitious refresh. Part 23 covers what this costs and
// why a shorter interval on a heavier formula is how people peg a CPU.
RequestTimedRefresh( 5 );
_SECTION_END();

Download opening-range-panel.afl140 lines

The formula converts the session parameters into minutes since midnight and derives InSession, SessionOpen, InOR and AfterOR from the bar clock. It computes the running session extremes, freezes them with ValueWhen, and blanks the frozen levels outside the part of the session where they legitimately exist. It accumulates session volume with SumSince, builds a completed-days baseline and a completed-days trend flag inside a daily TimeFrameSet block, expands both down to the intraday bars, and forms the pace ratio. Finally it plots the price, the two frozen levels and the pace on its own scale, and writes a title that states the session state in words.

HighestSince( EXPRESSION, ARRAY ) and LowestSince( EXPRESSION, ARRAY ) give running extremes since a condition was last true. SumSince( condition, array, incFirst ) accumulates since a condition, and incFirst = True includes the bar on which it was true. Interval( format = 0 ) returns the bar interval in seconds, which is how the formula learns its own bar length instead of being told. GetPlaybackDateTime() returns the Bar Replay position, or zero when replay is not active — the official example guards on exactly that, and so does the title line here.

Two horizontal step lines appear from the end of the opening-range window and hold their value for the rest of the session, restarting the next day. A grey up-arrow marks the first bar of each session. The pace line, on its own scale, tends to start high and settle — that visible shape is the U-shaped volume distribution discussed above, and seeing it is the point.

Set the opening-range length to a multiple of your chart interval and count bars: the level must stop changing on the bar immediately after the last bar of the window. Then change the chart interval to something that does not divide the range length and watch the level move to a value you did not define. Finally, scroll back to a half day — an early-close session — and check what the levels and the pace do. That last check is the subject of the next section.

Testing ORHigh before AfterOR is true, and getting yesterday’s level. Omitting the Nz() around Ref( InSession, -1 ) and getting a Null in a Boolean chain. Dropping SetBarsRequired and finding that the daily trend flag changes when you zoom. Setting the session parameters in exchange time on a database that is time-shifted, which produces an opening range starting at the wrong hour with no complaint from anything.

Add a second pane that plots the pace against its own value at the same time of day on the previous session, rather than against a flat-rate expectation. If you build it, keep every reference negative — the point of the exercise is to construct the better measure without reintroducing a forward look.

Why intraday definitions are more fragile than daily ones

Section titled “Why intraday definitions are more fragile than daily ones”

A daily bar is handed to you. A session is something you infer. That one sentence explains most of the difference, but it is worth breaking down, because each part fails independently.

Session boundaries are assumptions, not data. Nothing in an intraday bar says “this is the first bar of the regular session”. You assert it by comparing a clock. A daily bar, by contrast, arrives already labelled as a day.

The session length is not constant. Half days before holidays shorten it. Some markets open late after an auction problem. If your SessionMinutes is a constant, every percentage-of-session quantity — the pace above, most obviously — is wrong on those days, and wrong in a direction that manufactures apparent activity.

Daylight saving moves everything by an hour, and not on the same date everywhere. A session filter hard-coded to 09:30 is correct until the exchange and your machine change clocks on different weekends, and then it is an hour out for a fortnight, twice a year. Part 20 covers the detection; the relevant point here is that the failure is silent and your opening range simply forms over the wrong sixty minutes.

Missing bars are normal. A thinly traded symbol produces no bar for an interval in which it did not trade. Any construction that counts bars — “bar 6 of the session”, “the same bar-of-session as yesterday” — is comparing different clock times on different days the moment one bar is absent. Constructions that compare clock times survive this; constructions that compare bar counts do not. That is the main argument for preferring BarMin tests over Ref(x, -k) offsets in intraday code.

Extended-hours data changes the meaning of everything. Whether pre-market bars are in your database, and whether your intraday settings filter them out, changes the session open, the daily compression, the volume baseline and therefore the pace. Two people running the identical formula on identical raw data can get different answers purely from those settings.

The auctions are not ordinary bars. Opening and closing auctions print large volumes at single prices. Depending on your feed and your settings they may or may not be inside your first and last bars. A volume measure that includes the opening auction and a baseline that excludes it are not comparable.

And the sample is much smaller than it looks. Ten years of daily bars is about 2,500 observations of a daily setup. Ten years of intraday data contains far more bars but still only about 2,500 sessions, and an opening-range setup gets at most one observation per session per symbol. The bar count flatters you; the number of independent events does not change.

Nothing in this lesson needs a streaming connection. Every definition here is computed from stored bars, so a historical intraday database is a complete substitute — and a better learning environment, because you can scroll back to the awkward days on purpose.

Load the panel formula on a historical intraday chart and step through: a normal session, a half day, the session either side of a daylight-saving change, and a symbol that trades thinly enough to have gaps in its bars. Each of those shows one of the fragilities above directly, and takes about two minutes. Then open Tools → Bar Replay, set the step interval to your database’s base interval, and play a session forward while watching the opening range form and freeze. The title’s [Bar Replay active] marker tells you which mode you are in, so a screenshot or a note taken during practice is never ambiguous later.

Readers on the Standard edition can do all of this, with one restriction worth stating: the Standard edition’s intraday support starts at the one-minute interval, so a database with a tick or sub-minute base interval is not available. Every definition in this lesson works unchanged on one-minute or five-minute bars.

Three phrases have become three expressions, and each one has a specific line that keeps it causal: ValueWhen(InOR, ...) freezes the opening range without a forward reference, SumSince(SessionOpen, ...) resets the volume accumulator at the session boundary rather than at midnight, and Ref( ..., -1 ) inside a daily TimeFrameSet block is what makes an expandFirst expansion safe instead of a leak. The fragility is not a reason to avoid intraday work; it is a reason to write the session assumptions down, test the awkward days deliberately, and treat a formula that has only been run on well-behaved sessions as untested.

Check your understanding

Question 1. Why does `ValueWhen( InOR, HighestSince( SessionOpen, High ) )` freeze the opening-range high without look-ahead?
ORHigh = ValueWhen( InOR, HighestSince( SessionOpen, High ) );
Show the answer and why

Answer: ValueWhen returns the value at the most recent bar where InOR was true, which after the window closes is permanently the last range bar

Every reference is backwards. Once the window has closed the most recent InOR bar never changes again, so the level is fixed using past bars only.

Question 2. Which of these makes `TimeFrameExpand( x, inDaily, expandFirst )` safe to use in an intraday rule?
Show the answer and why

Answer: Computing x inside TimeFrameSet( inDaily ) with a Ref( ..., -1 ) so it holds the previous completed daily bar

expandFirst writes a period’s value onto that period’s first bar. Shifting back one daily bar first means what lands on today’s open is yesterday’s completed value. Without the shift, the same call is the documented look-ahead leak.

Question 3. Your pace measure is `SessionVolume / (BaselineVolume * ElapsedMin / SessionMinutes)`. Why is a fixed threshold of 1.2 harder to clear at 13:30 than at 09:45?
Show the answer and why

Answer: Because volume in most equity markets is concentrated near the open and close, so a flat-rate expectation is too low in the morning and too high mid-session

The measure assumes a constant arrival rate. Real intraday volume is U-shaped, so the same numeric threshold represents a different degree of unusualness at different hours.

Question 4. Which of these intraday constructions survive a symbol having no bar for an interval in which it did not trade? Select all that apply.
Show the answer and why

Answer: BarMin >= SessionStartMin + 30, SumSince( SessionOpen, Volume, True )

Clock-based tests and since-condition accumulations are unaffected by a missing bar. Anything that counts bars silently shifts to a different time of day the moment a bar is absent.

Question 5. What does `GetPlaybackDateTime()` return when Bar Replay is not running?
Show the answer and why

Answer: Zero

It returns zero, which is a legal-looking number. That is why the official example, and the panel formula, guard on it before using or printing the value.

Sources for this lesson

13 verified · checked 2026-08-31

  1. 01AmiBroker AFL Function Reference — HighestSinceamibroker.com/guide/afl/highestsince.html2026-08-31
  2. 02AmiBroker AFL Function Reference — ValueWhenamibroker.com/guide/afl/valuewhen.html2026-08-31
  3. 03AmiBroker AFL Function Reference — SumSinceamibroker.com/guide/afl/sumsince.html2026-08-31
  4. 04AmiBroker AFL Function Reference — TimeNumamibroker.com/guide/afl/timenum.html2026-08-31
  5. 05AmiBroker AFL Function Reference — Intervalamibroker.com/guide/afl/interval.html2026-08-31
  6. 06AmiBroker AFL Function Reference — TimeFrameSetamibroker.com/guide/afl/timeframeset.html2026-08-31
  7. 07AmiBroker AFL Function Reference — TimeFrameExpandamibroker.com/guide/afl/timeframeexpand.html2026-08-31
  8. 08AmiBroker AFL Function Reference — TimeFrameGetPriceamibroker.com/guide/afl/timeframegetprice.html2026-08-31
  9. 09AmiBroker User's Guide — Multiple time frame supportamibroker.com/guide/h_timeframe.html2026-08-31
  10. 10AmiBroker User's Guide — Preferences, Intraday tabamibroker.com/guide/w_preferences.html2026-08-31
  11. 11AmiBroker User's Guide — Database settingsamibroker.com/guide/w_dbsettings.html2026-08-31
  12. 12AmiBroker Knowledge Base — QuickAFL factsamibroker.com/kb/2008/07/03/quickafl2026-08-31
  13. 13AmiBroker AFL Function Reference — GetPlaybackDateTimeamibroker.com/guide/afl/getplaybackdatetime.html2026-08-31

Every technical claim on this page was checked against the official AmiBroker documentation on the date shown. Where the course disagrees with folklore, the source is how you can tell which one to trust.