Skip to content
Level 5 · Real-Time AmiBroker UserLessonPart 20 · page 2 of 428 min Professional edition Live feed
28Minutes
10AFL functions
8Sources
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 here10

Sessions: Regular Hours, Pre-Market and After-Hours

“The market is open” is a sentence with at least three meanings, and an intraday database will happily hold bars from all of them without distinguishing between them. A single day in a US equity database can contain trading from before dawn, the main auction-anchored session, and several hours after the closing bell — and unless something in your setup separates them, all of it goes into the same day, the same daily bar and the same average.

This lesson is about drawing those boundaries deliberately: what the sessions are, where AmiBroker lets you filter them, how to filter them in AFL, and what measurably changes in a daily bar when the extended sessions are included in it.

Exchanges differ, instruments differ, and the specific hours are published by the exchange and by nobody else. What generalises is the shape.

A typical equity trading day, conceptually

Sessions
Pre-marketRegular trading hoursAfter-hours
Liquidity
Thin, wide spreadsDeepest of the dayThin again
Proportions are schematic. The actual hours are whatever the exchange publishes for that instrument, and they differ by market and by product.

Regular trading hours, usually shortened to RTH, is the session the exchange’s own opening and closing procedures anchor. It is where most volume prints, where the closing auction sets the reference price that indices and funds use, and where a spread is narrowest. When a data vendor says “daily bar” without qualification, this is normally what it means — but normally is not always, which is why the measurement later in this lesson exists.

Pre-market and after-hours are extended sessions. They are real trading with real prints, and they are structurally different: far less volume, wider spreads, fewer participants, and a much higher chance that one order moves the printed price a long way. A five-minute bar from the middle of the regular session and a five-minute bar from two hours after the close are not comparable observations, even though your array holds them in the same units.

Overnight futures sessions break the model in a way worth understanding even if you never trade futures. Many futures contracts trade nearly around the clock, with a break rather than a close, and the exchange assigns the trading that begins on one calendar evening to the next day’s trade date. The consequences are immediate: the “day” you want to analyse starts before midnight and ends the following afternoon, so a filter written as “time is between the start and the end” is false for the whole first half of it. A session that wraps past midnight needs OR, not AND:

Fragment — not a complete formula

// A session contained within one calendar day: start before end.
InDaySession = TimeNum() >= 93000 AND TimeNum() <= 155959;
// A session that crosses midnight: start AFTER end, so the test inverts.
InOvernight = TimeNum() >= 180000 OR TimeNum() <= 160000;

Both forms are correct; which one you need depends entirely on the instrument, and using the first where the second belongs silently discards half the session. Note also that the overnight form does not, by itself, group the two halves into one trade date — the bars before midnight still carry the previous calendar date. Grouping them is a design decision you have to make explicitly, and Part 14’s timeframe material is where the machinery for it lives.

Two calendar facts damage intraday work more than their frequency suggests.

Short sessions — half days around public holidays — end early and sometimes have no extended session at all. Bar counts drop, the closing auction happens at an unusual time, and any calculation that assumes a fixed number of bars per day quietly changes meaning. A “last thirty minutes of the day” filter written as a clock time selects the wrong bars, or none.

Holidays produce no bars. This is the awkward one, because a holiday and a failed download look identical in the data. Your database does not hold an exchange calendar, AmiBroker does not ship one, and there is no AFL function that will tell you whether a missing Thursday was a public holiday or a broken update. What you can do is separate the two classes of explanation by their fingerprints:

What you see More likely Why
One date absent for every symbol on the exchange Holiday A download failure rarely lands on exactly the symbols of one venue
One date absent for one symbol, present for its peers Data problem The exchange did not close for one company
A date present but with a small fraction of the usual bars Short session, or a partial download Check whether the missing bars are at the end (short session) or scattered (download)
A run of consecutive absent dates Suspension, delisting, or a symbol change Corporate events, not the calendar

View → Pad non-trading days exists and pads Saturdays, Sundays and holidays with the previous close. It is a display convenience for making calendars line up across symbols. It is not a data repair, and it is not something you want switched on while you are counting bars.

There are three separate levers, and confusion between them accounts for a lot of “my bars disappeared” reports.

File → Database Settings → Intraday Settings is the per-database control. Its documented options are a filtering choice — show 24-hour trading with no filtering, day session only, night session only, or day and night sessions — a Filter weekends option, Trading hours start and end defined separately for the day and night sessions in your local time zone, a Time shift in hours between local and exchange time, and a Daily time-compression uses choice of exchange time, local time, or the day and night session times as defined above. That last setting is the one that decides how intraday bars become daily bars, and it is the subject of the next section.

View → Intraday carries the same session choices at the level of the individual chart window, alongside the interval list.

Per-group overrides exist and are easy to miss. Under Symbol → Categories → Groups, ticking “Group uses own intraday settings” gives that group its own Intraday Settings button and therefore its own trading hours. This is how one database holds instruments with genuinely different sessions. It is also why a symbol can behave differently from its neighbours for no reason visible in the database-level dialog.

The interface settings change what AmiBroker shows you. They do not change what a formula computes when it runs somewhere else — a different chart, a different machine, a scan someone else configured. Anything whose correctness matters should carry its own session definition.

Fragment — not a complete formula

SessionStart = Param( "Session start (HHMMSS)", 93000, 0, 235959, 100 );
SessionEnd = Param( "Session end (HHMMSS)", 155959, 0, 235959, 100 );
InSession = TimeNum() >= SessionStart AND TimeNum() <= SessionEnd;

Three details in three lines are worth spelling out.

The end is 155959, not 160000. Under start-of-interval stamping the last bar of a session ending at 16:00 is stamped one interval before it, and <= 155959 includes every bar whose stamp falls anywhere in the final minute regardless of the interval you are running at. Writing < 160000 works too; writing <= 160000 includes a bar stamped exactly at the close, which under end-of-interval stamping is the last bar and under start-of-interval stamping is the first bar of the after-hours session.

The boundaries are Param() values rather than literals. Not for tidiness — because a formula that carries its session in its parameters can be pointed at a second instrument without being rewritten, and because the Parameters dialog makes the assumption visible to whoever runs it next.

And InSession is an array: one true-or-false per bar, exactly like every other AFL expression. It is not a mode the formula enters. Part 9 makes that point in general; here it is what lets you count, aggregate and compare session bars using ordinary array functions.

Once you have InSession, the pattern for “position within the session” is the one used throughout this part:

Fragment — not a complete formula

PreviousDate = Nz( Ref( DateNum(), -1 ), 0 );
NewDay = DateNum() != PreviousDate;
// Cum() counts from the start of the array, so the count within one day is the
// running total minus whatever it was at the end of the previous day.
CumSession = Cum( InSession );
SessionBarNumber = CumSession - Nz( ValueWhen( NewDay, Ref( CumSession, -1 ) ), 0 );

SessionBarNumber is 1 on the first in-session bar of each day, 2 on the second, and so on. It is worth more than it looks: a rule expressed in bars from the session start survives a whole-hour displacement, a short session and a change of provider convention, none of which a rule expressed as an absolute clock time survives. The third lesson and the challenge both turn on that difference.

Here is the part that is easy to state and easy to underestimate. If your database contains extended-hours bars and your daily bars are built by compressing them, then your daily high, low and open are not the exchange’s daily high, low and open.

The mechanism is arithmetic, not subtle. The daily high is the maximum of the bars included. Include four more hours of trading and the maximum can only stay the same or rise. The same logic runs downward for the low. The open changes to whatever the first included bar’s open was, which under a 24-hour filter can be a single trade in a nearly empty book.

The consequences reach further than intraday work, because higher-timeframe filters are built on daily bars. A weekly moving average computed through TimeFrameSet( inWeekly ) on an intraday database is compressed from those same bars. A gap measured as today’s open against yesterday’s close is measuring a different gap. A daily ATR is measuring a different range. None of this produces an error and none of it looks wrong.

For each trading day, compute the open, high, low and close twice — once from regular-hours bars only, once from every bar the database holds for that date — and report the difference. Also report both bar counts, because a large difference with a small extended-hours bar count is a different story from a large difference with a busy one.

Complete runnable AFL

session-profile.afl
// ===========================================================================
// Session profile
// Measures, day by day, the difference between the daily bar you would get
// from regular-hours bars only and the daily bar you would get from every bar
// the database holds for that date. When a database carries pre-market and
// after-hours bars, those two daily bars are not the same instrument-day, and
// a daily-timeframe rule computed on the wrong one is answering a different
// question from the one you asked.
//
// HOW TO RUN
// Analysis window -> Apply to: Current symbol, Range: a few weeks,
// Periodicity: the database's base interval, then Explore.
// Set the two session parameters to the instrument's regular hours AS YOUR
// DATABASE STAMPS THEM. Run bar-timestamp-audit.afl first if you are not
// certain what those stamps are - guessing here invalidates the whole table.
//
// READING THE RESULT
// "High diff" and "Low diff" are the extra range contributed by bars
// outside the regular session. Zero in both columns on every day means the
// database holds regular hours only. A large value on a day with a
// scheduled announcement is the extended session doing exactly what
// extended sessions do. Blank session columns mean no bar fell inside the
// session window that day, which is itself a finding.
//
// ASSUMPTIONS, STATED SO THEY CAN BE CHECKED
// - Prices are positive. The sentinel values that exclude out-of-session
// bars from the running high and low depend on that.
// - The session is contained within one calendar day as the database stamps
// it. An overnight session that crosses midnight needs the wrap-around
// comparison described in the lesson, not the form used here.
// - Bar counts count bars present. They do not detect a bar that the
// provider never delivered.
// ===========================================================================
SessionStart = Param( "Session start (HHMMSS)", 93000, 0, 235959, 100 );
SessionEnd = Param( "Session end (HHMMSS)", 155959, 0, 235959, 100 );
InSession = TimeNum() >= SessionStart AND TimeNum() <= SessionEnd;
PreviousDate = Nz( Ref( DateNum(), -1 ), 0 );
NewDay = DateNum() != PreviousDate;
LastBarOfDay = Nz( Ref( NewDay, 1 ), 1 );
// Running bar counts. Cum() counts from the start of the array, so the count
// within one day is the cumulative total minus its value at the end of the
// previous day.
CumAll = Cum( 1 );
CumSession = Cum( InSession );
BarsInDay = CumAll - Nz( ValueWhen( NewDay, Ref( CumAll, -1 ) ), 0 );
BarsInSession = CumSession - Nz( ValueWhen( NewDay, Ref( CumSession, -1 ) ), 0 );
// Whole-day figures: every bar the database holds for that date.
DayOpenAll = ValueWhen( NewDay, Open );
DayHighAll = HighestSince( NewDay, High );
DayLowAll = LowestSince( NewDay, Low );
// Session-only figures. Bars outside the session are replaced by a value that
// cannot win the comparison: -1 can never be the highest price, and a large
// positive number can never be the lowest.
RawSessionHigh = HighestSince( NewDay, IIf( InSession, High, -1 ) );
RawSessionLow = LowestSince( NewDay, IIf( InSession, Low, 1000000 ) );
FirstSessionBar = InSession AND NOT Nz( Ref( InSession, -1 ), 0 );
LastSessionBar = InSession AND NOT Nz( Ref( InSession, 1 ), 0 );
// On a day with no in-session bar at all, the sentinels would survive into the
// output and read as prices. Blank them instead, so the absence is obvious.
SessionHigh = IIf( BarsInSession > 0, RawSessionHigh, Null );
SessionLow = IIf( BarsInSession > 0, RawSessionLow, Null );
SessionOpen = IIf( BarsInSession > 0, ValueWhen( FirstSessionBar, Open ), Null );
SessionClose = IIf( BarsInSession > 0, ValueWhen( LastSessionBar, Close ), Null );
// How much range the out-of-session bars added, in price terms.
HighDiff = DayHighAll - SessionHigh;
LowDiff = SessionLow - DayLowAll;
Filter = LastBarOfDay;
SetOption( "NoDefaultColumns", True );
AddTextColumn( Name(), "Symbol", 1.0, colorDefault, colorDefault, 70 );
AddColumn( DateTime(), "Date", formatDateTime );
AddColumn( BarsInDay, "Bars all", 1.0 );
AddColumn( BarsInSession, "Bars session", 1.0 );
AddColumn( SessionOpen, "Session O", 1.4 );
AddColumn( SessionHigh, "Session H", 1.4 );
AddColumn( SessionLow, "Session L", 1.4 );
AddColumn( SessionClose, "Session C", 1.4 );
AddColumn( DayOpenAll, "All-hours O", 1.4 );
AddColumn( DayHighAll, "All-hours H", 1.4 );
AddColumn( DayLowAll, "All-hours L", 1.4 );
AddColumn( Close, "All-hours C", 1.4 );
// The two differences are the point of the table. The tint is a convenience;
// the number says everything and is readable without seeing any colour.
AddColumn( HighDiff, "High diff", 1.4, colorDefault,
IIf( HighDiff > 0, colorRose, colorDefault ) );
AddColumn( LowDiff, "Low diff", 1.4, colorDefault,
IIf( LowDiff > 0, colorRose, colorDefault ) );
SetSortColumns( 2 );

Download session-profile.afl102 lines

The whole-day figures use HighestSince( NewDay, High ) and LowestSince( NewDay, Low ), which run a maximum and a minimum from each day boundary forward. On the last bar of the day those two carry the day’s extremes over every bar present.

The session-only figures use the same two functions with a substitution: IIf( InSession, High, -1 ) replaces every out-of-session bar’s high with a value that cannot possibly be the highest, and the low uses a large positive sentinel for the same reason in the other direction. This is why the header states that prices are assumed positive — the technique is exact for any instrument that trades above zero and wrong for one that does not, and a formula that depends on a premise should say so.

The session open and close come from the first and last in-session bars of the day, found by looking one bar either side of the InSession flag. The bar counts use the same Cum() minus start-of-day trick as the session bar number above.

The final guard matters more than it looks. On a day where no bar falls inside the session window at all, the sentinels would survive into the output and be printed as though they were prices: a high of −1 and a low of 1,000,000. IIf( BarsInSession > 0, ..., Null ) blanks them instead, so an absent session reads as absent rather than as an absurd price that someone will eventually paste into a spreadsheet.

Set the session parameters to a window that certainly contains everything — 000000 to 235959 — and re-run. Both difference columns must be zero on every row and the two bar counts must be identical. If they are not, the fault is in the formula’s assumptions about your data rather than in the data, and the most likely cause is a session that crosses midnight.

Then narrow the window by one bar length at the front and check that “Bars session” falls by exactly one on every full trading day. That confirms your session boundaries are landing on bar edges rather than between them, which is the thing most easily got wrong by one interval.

  • Setting the session in exchange time when the database is stamped in another zone. Every column will be populated and every one will be about the wrong hours. Run the timestamp audit from the previous lesson first.
  • Using the day-contained form on an overnight instrument. The AND test is false for every bar before midnight, so the session figures describe the afternoon only.
  • Reading a zero difference as proof that no extended-hours data exists. It also happens when your session window is wide enough to swallow the extended sessions. Compare the two bar counts, not just the price columns.
  • Comparing these figures with a data vendor’s daily bar and assuming any difference is a defect. Vendors differ in which prints they include and how they handle late corrections. A difference is a question, not a verdict.

Add two columns holding the extended-hours volume before and after the session, as a fraction of the day’s total. A day where the overnight fraction is unusually large is a day where something happened outside the hours your daily bar describes, and that is a more informative flag than the price difference alone.

A trading day has more than one session in it, and an intraday database may hold any combination of them without saying so. Regular hours, the extended sessions around them, and the overnight sessions that begin on the previous calendar date all need different treatment, and a session that wraps past midnight needs an OR where a contained one needs an AND.

Short days and holidays are real and cannot be told apart from missing data by the data alone — only by the pattern across symbols and across the day. AmiBroker’s session filtering hides bars rather than removing them, exists at database, chart-window and group level, and does not travel with your formula, which is why anything that matters carries its own session definition.

And a daily bar built from all-hours intraday data is a different daily bar from the exchange’s. You now have a formula that measures how different, on your own data.

The next lesson takes the one assumption this one leaned on — that a session’s clock time is stable — and breaks it, twice a year, on dates that do not match between countries.

Check your understanding

Question 1. An instrument trades from 18:00 through to 16:00 the following afternoon. Which test selects its session correctly?
Show the answer and why

Answer: TimeNum() >= 180000 OR TimeNum() <= 160000

When the session start is later in the day than the session end, the session wraps past midnight and no single bar can satisfy both conditions at once. The AND form is false everywhere; OR is the correct shape. It still leaves the two halves carrying different calendar dates, which has to be handled separately.

Question 2. A Thursday is missing from one symbol's intraday history. Its exchange peers all have that Thursday. What is the most likely explanation?
Show the answer and why

Answer: A data problem specific to that symbol

Exchanges do not close for one company. A holiday removes the day for every symbol on the venue; a short session shortens the day rather than removing it. A gap in one symbol while its peers are complete points at that symbol's data — a suspension, a symbol change, or a failed update.

Question 3. Your database includes pre-market and after-hours bars, and daily bars are produced by compressing them. Which statements are true? Select all that apply.
Show the answer and why

Answer: The daily high can only be equal to or higher than the regular-hours high, The daily open may come from a bar with very little volume behind it, A weekly moving average computed from this database uses the same extended-hours bars

Including more bars can only widen the range, the first included bar sets the open regardless of how thin it was, and higher-timeframe compression is built from the same underlying bars. No warning exists — which is exactly why the difference has to be measured rather than assumed.

Question 4. Bars are missing from a chart but the Quote Editor shows them. What has happened?
Show the answer and why

Answer: A session filter is hiding them from the chart

Session filtering hides data rather than deleting it, and the Quote Editor is documented as showing every bar regardless of the filtering settings. The disagreement between the two windows is the normal, expected symptom of an active filter.

Sources for this lesson

8 verified · checked 2026-08-31

  1. 01AmiBroker User's Guide — Database Settings window§ Intraday Settingsamibroker.com/guide/w_dbsettings.html2026-08-31
  2. 02AmiBroker User's Guide — Charting guide§ Intervals and the View menuamibroker.com/guide/h_charting.html2026-08-31
  3. 03AmiBroker User's Guide — Categories, groups and marketsamibroker.com/guide/h_categories.html2026-08-31
  4. 04AmiBroker AFL Function Reference — TimeNumamibroker.com/guide/afl/timenum.html2026-08-31
  5. 05AmiBroker AFL Function Reference — HighestSinceamibroker.com/guide/afl/highestsince.html2026-08-31
  6. 06AmiBroker AFL Function Reference — ValueWhenamibroker.com/guide/afl/valuewhen.html2026-08-31
  7. 07AmiBroker AFL Function Reference — Cumamibroker.com/guide/afl/cum.html2026-08-31
  8. 08AmiBroker User's Guide — Multiple Time Frame Supportamibroker.com/guide/h_timeframe.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.