Module B · The AFL Language - Chapter 10

Dates, Times & the Trading Session

Read the calendar and the clock - Day, Month, time-of-day - and answer the question every intraday system asks: has a new day started?

AFLRealtime
What you'll learn
  • ·Day / Month / Year / DayOfWeek
  • ·TimeNum & the HHMMSS clock
  • ·Detecting a new day or session
  • ·Bars since the open; first & last bar of the day
  • ·The day's open, high & low so far
  • ·Restricting entries to a time window

Every intraday system, sooner or later, has to ask two very human questions: what time is it? and did a new day just start? A breakout strategy needs to know the market only opened a few minutes ago. A square-off rule needs to fire at 15:15, not before. A VWAP resets at the bell every morning. None of that is possible until your formula can read the calendar and the clock - and that is exactly the toolkit we build in this chapter.

In the last chapter you learned to look back with Ref, count with BarsSince, and avoid peeking at the future. Here we put those skills to work on time itself. Everything that follows is still pure array thinking from Chapter 3 - each function below hands you a whole column, one value per bar - we are just filling those columns with dates and times instead of prices.

Good to know

AmiBroker stores every intraday bar's timestamp down to the second. That is why your formula can read the exact clock - TimeNum() knows the difference between 09:15:00 and 09:15:01. On end-of-day data there is no meaningful intraday time, so these clock functions only come alive on intraday charts.

The calendar functions

AmiBroker gives you a small family of functions that read the date of each bar. Each one returns an array - a column with one whole number per bar:

d  = Day();        // day of the month, 1 to 31
m  = Month();      // month, 1 to 12
y  = Year();       // four-digit year, e.g. 2026
dw = DayOfWeek();  // 0 = Sunday, 1 = Monday ... 6 = Saturday
dy = DayOfYear();  // day of the year, 1 to 366

DayOfWeek() is the handy one for Indian markets: the NSE trades Monday to Friday, so its values run 1 to 5. Want to skip Mondays for some reason? DayOfWeek() != 1. Want only the expiry-day weekday? Test against the right number. Because these are arrays, you combine them with the comparisons and AND/OR from Chapter 8 exactly like any price array.

The clock: TimeNum and DateNum

For the time of day, TimeNum() is the workhorse. It returns the bar's time packed into a single whole number as HHMMSS:

Clock time TimeNum()
09:15:00 91500
09:30:00 93000
14:30:00 143000
15:30:00 153000

Because it is just a number, you compare it the obvious way: TimeNum() >= 91500 is true from the opening bar onward, and TimeNum() < 143000 is true before 2:30 pm. (Note 09:15 packs to 91500, not 091500 - the leading zero is just a digit, the value is ninety-one thousand five hundred.)

Its sibling DateNum() packs the date into one number using the formula (Year - 1900) * 10000 + Month * 100 + Day. So 26 June 2026 becomes (126 * 10000) + (6 * 100) + 26 = 1260626. You rarely read that number by eye; its job is to give each calendar day a unique value, perfect for equality and new-day tests.

Two more to know in passing: Now() returns the current real date-time (useful for "is this the latest bar?" checks), and Interval() returns the bar size in seconds - 60 for a 1-minute chart, 300 for 5-minute, 86400 for daily. Handy when one formula must adapt to different timeframes.

Heads up

These are intraday tools. On end-of-day data, TimeNum() has no real meaning (every bar is just "the day"), so any logic built on it will misbehave. Always apply the formulas in this chapter to intraday charts - a 1, 3, 5 or 15-minute NIFTY, BANKNIFTY or RELIANCE chart.

Has a new day started? The backbone idea

Here is the single most important line in all of intraday AFL. We want a true/false array that is True on the first bar of each session and False everywhere else. The trick: compare each bar's day number with the previous bar's, using Ref from the last chapter.

newDay = Day() != Ref(Day(), -1);   // True on the first bar of each session

On the first bar of a new day, Day() differs from Ref(Day(), -1) (yesterday's value), so the comparison is True. On every other bar they match, so it is False. You could write the identical idea with DateNum() != Ref(DateNum(), -1) - and that version is safer across month boundaries, since the day-of-month number alone repeats every month.

Let us see it happen. Below are six consecutive 5-minute bars straddling the close of one session and the open of the next. Watch newDay and barNum reset the instant the date rolls over:

Bar Time Day() newDay barNum
0 15:20 25 F 73
1 15:25 25 F 74
2 15:30 25 F 75
3 09:15 26 T 1
4 09:20 26 F 2
5 09:25 26 F 3

The chart's bar index keeps marching on, but the moment Day() jumps from 25 to 26, newDay fires T and our session counter snaps back to 1. That reset is the heartbeat of every intraday formula.

Key idea

newDay = Day() != Ref(Day(), -1); is the one line to memorise. It marks the first bar of each session, and almost everything else intraday - VWAP, opening range, the day's high - is built on top of it.

A 5-minute chart with a vertical line where newDay fires at 09:15 - the session counter resets each morning
ChartA 5-minute chart with a vertical line where newDay fires at 09:15 - the session counter resets each morning

Bars into the session, first and last bar

Once you can spot the new day, counting bars into the session is one step away with BarsSince:

barNum = 1 + BarsSince(newDay);   // 1 on the opening bar, 2, 3, ... through the day

The first bar of the day is simply newDay itself (or barNum == 1). The last actionable bar is best identified by its clock time - TimeNum() == 152000 for the 15:20 bar on a 5-minute chart, or whatever your chosen close is.

Heads up

Do not try to find the last bar with Ref(Day(), 1) - that reaches one bar into the future to ask "is tomorrow a different day?". That is look-ahead bias (Chapter 9): in a backtest it cheats, and in real trading bar +1 does not exist yet. Identify the session's final bar by its clock time, never by peeking ahead.

The day's open, high and low so far

Three more functions, all session-aware once you feed them newDay. ValueWhen grabs a value at the last time a condition was true; HighestSince and LowestSince track an extreme since a condition last fired:

dayOpen = ValueWhen(newDay, Open);     // today's opening price, held all day
dayHigh = HighestSince(newDay, High);  // highest High since the session began
dayLow  = LowestSince(newDay, Low);    // lowest Low since the session began

Trace the morning of a RELIANCE 5-minute chart and watch dayHigh ratchet up while dayLow holds the session low:

Bar Time High Low newDay dayHigh dayLow
0 09:15 1422 1416 T 1422 1416
1 09:20 1425 1420 F 1425 1416
2 09:25 1424 1419 F 1425 1416
3 09:30 1430 1426 F 1430 1416

dayOpen stays pinned to the 09:15 open all day; dayHigh only ever rises; dayLow only ever falls - until the next newDay resets all three. As an alternative for the open, TimeFrameGetPrice("O", inDaily) reads the daily bar's open directly - the same number, fetched from the higher timeframe.

Restricting entries to a time window

Most intraday traders avoid the first chaotic minutes, stop opening fresh positions after a cut-off, and square off well before the bell. Two true/false arrays express all of that:

tradeWindow = TimeNum() >= 93000 AND TimeNum() < 143000;  // entries only 09:30-14:30
squareOff   = TimeNum() >= 151500;                        // force flat from 15:15

Now gate a system with them. Here is a short, complete intraday skeleton - it detects the new day, computes the day's open, only allows a long inside the window, and forces an exit at square-off:

// --- A session-aware intraday skeleton (intraday data only) ---

newDay  = Day() != Ref(Day(), -1);      // first bar of each session
dayOpen = ValueWhen(newDay, Open);      // today's open, held all day

tradeWindow = TimeNum() >= 93000 AND TimeNum() < 143000;
squareOff   = TimeNum() >= 151500;

// example rule: long while price is above the day's open, inside the window
Buy  = Close > dayOpen AND tradeWindow;
Sell = squareOff;                       // exit purely on time

// keep just the first entry, and one clean exit at square-off
Buy  = ExRem(Buy, Sell);                // ExRem = "remove excess"; more in Module E
Sell = ExRem(Sell, Buy);

That is a genuinely tradable shape: no entries before 09:30, none after 14:30, and a guaranteed flat position by 15:15. Swap the Buy rule for a VWAP cross or an opening-range breakout and the time scaffolding stays exactly the same.

The intraday skeleton in the Formula Editor - new-day detection, the trade window, and a 15:15 square-off
AFLThe intraday skeleton in the Formula Editor - new-day detection, the trade window, and a 15:15 square-off
Tip

Build the clock numbers as named variables at the top of every intraday formula - startTime = 93000;, exitTime = 151500;. It reads better than scattering raw 93000s through your code, and changing your session window becomes a one-line edit. Test it all first in sandbox trading (analyzer mode in OpenAlgo) before anything touches the exchange.

Try it yourself

  • Apply Plot(newDay, "newDay", colorRed, styleHistogram); to a 5-minute chart and confirm the histogram spikes to 1 only at 09:15 each morning.
  • Plot dayHigh and dayLow as lines and watch them form a widening box around each session that resets every day.
  • Add Title = StrFormat("Bar %g of the day, time %g", barNum, TimeNum()); and hover across a day boundary to see the counter reset.
  • Change tradeWindow to your own hours and observe how the allowed entry zone moves.

Recap

  • Day(), Month(), Year(), DayOfWeek() (NSE = 1-5) and DayOfYear() each return a whole array of calendar values, one per bar.
  • TimeNum() packs the clock as HHMMSS (09:15 -> 91500), so you compare it directly; DateNum() gives each date a unique number.
  • newDay = Day() != Ref(Day(), -1); marks the first bar of each session - the backbone of all intraday logic.
  • barNum = 1 + BarsSince(newDay) counts bars into the day; find the last bar by clock time, never by Ref(Day(), 1) (look-ahead).
  • ValueWhen, HighestSince and LowestSince fed with newDay give the day's open, high and low so far.
  • A tradeWindow plus a squareOff time turn any signal into a disciplined intraday system - and remember, these tools need intraday data.

Next, we meet the rare cases where array thinking is not enough - and learn to write a clean, correct for-loop in Arrays vs Loops: the for-loop.