Challenge: The Opening Range That Moved
A five-minute opening-range scanner has been running every trading day since the spring. It produces a handful of candidates each morning, the chart levels line up with the session, and its owner has stopped checking it closely because it has stopped surprising her.
On a Monday it returns nothing. She assumes a quiet market. On Tuesday it returns nothing again.
Your job is to find out why, from the evidence given, before you read the answer. The formula is not clever, the fault is not exotic, and everything you need is in the previous three lessons.
The situation
Section titled “The situation”Ines runs an intraday database of US-listed shares with a five-minute base interval, fed by her broker’s plugin, from a desk in continental Europe. Her country and the exchange’s country both move their clocks twice a year, on different weekends. She has a database time shift configured so that the bars on her charts line up with the exchange session rather than with her own wall clock, and she set it once, months ago, and has not thought about it since.
Her scanner is opening-range-fragile.afl, run as a Scan every five minutes during the
morning. Nothing about her setup changed over the weekend: no update, no reinstall, no
change of provider, no edit to the formula.
The symptoms
Section titled “The symptoms”Take these in the order she noticed them, because the order is part of the evidence.
- The scan returns no rows. It completes without error and reports zero symbols.
- The chart still draws normally. Candles, volume, the moving averages in the pane below — everything is present and current, and new bars arrive during the session.
- The opening range lines have disappeared. The two dashed levels the formula plots are simply not there. No error message, no red text, no broken-formula indicator.
- The problem affects every symbol. It is not one ticker with bad data; the whole watch list behaves the same way.
- Scrolling back a week, the lines are there. On the previous Thursday and Friday the levels plot exactly as they always did.
- The alert that used to fire on a breakout has gone quiet too, and its output window contains nothing since Friday.
- A backtest of the same rules over the last month still produces trades — fewer than she expected, and all of them dated before the weekend.
The evidence available
Section titled “The evidence available”Exhibit A: the formula
Section titled “Exhibit A: the formula”Complete runnable AFL
// ===========================================================================// Opening range breakout - fragile version// This is the formula as it is usually first written, and it is the broken// exhibit for the Part 20 challenge. It is NOT a recommendation and NOT a// trading system. It exists to be broken, diagnosed and repaired.//// WHAT IT DOES// Treats the bars from the one stamped 09:30:00 to the one stamped 09:55:00// as the opening range, then marks the first close above that range's high// as a long trigger, with a flat-by-the-close rule.//// WHY IT IS FRAGILE - which is the whole point// Every rule below is written against an absolute clock time that is// ASSUMED to exist in the data, and nothing in the formula checks that// assumption. When the stamps move - a time-zone change, a daylight saving// transition, a different provider, a different database time shift, a// different start/end-of-interval preference - the formula does not fail.// It quietly computes something else and keeps drawing.//// THE ONE HONEST LINE IN IT// The title prints how many bars in the loaded range carry the exact stamp// the formula depends on. When that number is zero, everything below it is// meaningless. Most formulas of this kind do not print that number, which// is exactly why the fault survives so long.// ===========================================================================
_SECTION_BEGIN( "Opening range - fragile" );
RangeStartStamp = 93000; // assumed first bar of the regular sessionRangeEndStamp = 95500; // assumed last bar of the opening rangeLastEntryStamp = 155000; // assumed cut-off for new entries
RangeStartBar = TimeNum() == RangeStartStamp;RangeEndBar = TimeNum() == RangeEndStamp;
OpeningHigh = ValueWhen( RangeEndBar, HighestSince( RangeStartBar, High ) );OpeningLow = ValueWhen( RangeEndBar, LowestSince( RangeStartBar, Low ) );
AfterRange = TimeNum() > RangeEndStamp AND TimeNum() <= LastEntryStamp;
Buy = AfterRange AND Cross( Close, OpeningHigh );Sell = TimeNum() > LastEntryStamp;
Buy = ExRem( Buy, Sell );Sell = ExRem( Sell, Buy );
Plot( Close, "Close", colorDefault, styleCandle );Plot( OpeningHigh, "Opening range high", colorGreen, styleDashed | styleNoRescale );Plot( OpeningLow, "Opening range low", colorRed, styleDashed | styleNoRescale );PlotShapes( Buy * shapeUpArrow, colorGreen, 0, Low, -20 );
StampMatches = LastValue( Cum( RangeStartBar ) );
Title = "FRAGILE opening range " + Name() + " " + Interval( 2 ) + "\n" + "Bars in the loaded range stamped " + NumToStr( RangeStartStamp, 1.0, False ) + ": " + NumToStr( StampMatches, 1.0, False ) + " - if this is zero, nothing plotted below it means anything";
_SECTION_END();Read the top third carefully. Three literal numbers, three comparisons against them, and no check anywhere that the bars those comparisons need actually exist.
Exhibit B: the timestamp evidence
Section titled “Exhibit B: the timestamp evidence”She runs timezone-break-evidence.afl over a range spanning the working days and the
broken ones, with stamp A set to 93000 and stamp B to 95500 — the two numbers the
formula depends on.
Complete runnable AFL
// ===========================================================================// Timezone break evidence// The diagnostic for a formula that has stopped producing signals. It asks// one blunt question of every trading day: does a bar carrying the exact// timestamp the formula depends on actually exist that day?//// A formula written against TimeNum() == 93000 is not asking "the first bar// of the session". It is asking "the bar whose stamp is exactly 09:30:00".// Those are the same question only while nothing about the stamps changes.//// HOW TO RUN// Analysis window -> Apply to: Current symbol, Range: a span that covers// both a working period and a broken one, Periodicity: the base interval,// then Explore. Enter the two stamps the suspect formula depends on.//// READING THE RESULT// "Bars at stamp A" of 1 on the days the formula worked and 0 on the days// it went quiet is the whole diagnosis. If the count is 1 throughout, the// stamps are not the fault and the problem is somewhere else - start with// session-profile.afl.// Compare "First bar" across the boundary: if it moved by exactly sixty// minutes, the cause is a clock change rather than a broken download.//// ASSUMPTIONS, STATED SO THEY CAN BE CHECKED// - Exact-match counting is deliberate. It reproduces what the suspect// formula does, faults included; it is not a good way to find a session.// - A count above 1 means several bars share the stamp, which happens when// the loaded range spans a repeated hour or when duplicate bars exist.// - This table shows what the database contains. It cannot tell you whether// the provider, your computer or the exchange is responsible.// ===========================================================================
StampA = Param( "Stamp the formula depends on (HHMMSS)", 93000, 0, 235959, 100 );StampB = Param( "Second stamp it depends on (HHMMSS)", 95500, 0, 235959, 100 );
PreviousDate = Nz( Ref( DateNum(), -1 ), 0 );NewDay = DateNum() != PreviousDate;LastBarOfDay = Nz( Ref( NewDay, 1 ), 1 );
CumAll = Cum( 1 );CumA = Cum( TimeNum() == StampA );CumB = Cum( TimeNum() == StampB );
BarsInDay = CumAll - Nz( ValueWhen( NewDay, Ref( CumAll, -1 ) ), 0 );MatchesA = CumA - Nz( ValueWhen( NewDay, Ref( CumA, -1 ) ), 0 );MatchesB = CumB - Nz( ValueWhen( NewDay, Ref( CumB, -1 ) ), 0 );
FirstStampOfDay = DateTimeConvert( 2, DateNum(), ValueWhen( NewDay, TimeNum() ) );
Filter = LastBarOfDay;SetOption( "NoDefaultColumns", True );
AddTextColumn( Name(), "Symbol", 1.0, colorDefault, colorDefault, 70 );AddColumn( FirstStampOfDay, "First bar", formatDateTime );AddColumn( DateTime(), "Last bar", formatDateTime );AddColumn( BarsInDay, "Bars", 1.0 );
// Zero is the finding. The tint helps a sighted reader scan the column; the// number alone carries the message.AddColumn( MatchesA, "Bars at stamp A", 1.0, colorDefault, IIf( MatchesA == 0, colorRose, colorDefault ) );AddColumn( MatchesB, "Bars at stamp B", 1.0, colorDefault, IIf( MatchesB == 0, colorRose, colorDefault ) );
SetSortColumns( 3 );The output looks like this.
| Day | First bar | Last bar | Bars | Bars at stamp A (09:30:00) | Bars at stamp B (09:55:00) |
|---|---|---|---|---|---|
| Day −4 | 09:30:00 | 15:55:00 | 78 | 1 | 1 |
| Day −3 | 09:30:00 | 15:55:00 | 78 | 1 | 1 |
| Day −2 | 09:30:00 | 15:55:00 | 78 | 1 | 1 |
| Day −1 | 09:30:00 | 15:55:00 | 78 | 1 | 1 |
| Day 0 | 08:30:00 | 14:55:00 | 78 | 0 | 0 |
| Day +1 | 08:30:00 | 14:55:00 | 78 | 0 | 0 |
| Day +2 | 08:30:00 | 14:55:00 | 78 | 0 | 0 |
Exhibit C: what has not changed
Section titled “Exhibit C: what has not changed”- The bar count per day is unchanged at 78. No bars are missing.
- The session length is unchanged: first to last bar spans the same number of bars.
Status("timeshift")returns the same value it returned in the spring.- The plugin’s status indicator is green and the chart is updating.
- No formula was edited. The file’s modification date is months old.
The task
Section titled “The task”Answer these four questions, in writing, before reading any hint.
- What, precisely, is the formula computing on Day 0? Not “nothing” — trace
HighestSince,ValueWhenandCrossand say what value each one holds. - Why does the chart still draw while the levels do not?
- What is the root cause? Name the mechanism, not the symptom, and say why it appeared on that particular day rather than gradually.
- What would have made this fault visible on the day it happened, rather than after two days of empty scans?
Then write a repaired formula that produces the same opening range on both sides of the boundary in Exhibit B, without your having to know what the boundary was.
Hints, in increasing order
Section titled “Hints, in increasing order”Read one, go back to the evidence, and only take the next if you are still stuck.
Hint 1
Section titled “Hint 1”Nothing is wrong with the data. Everything you need to explain all seven symptoms is already in Exhibit B, in two columns. Compare Day −1 with Day 0 across every column and write down the one thing that changed.
Hint 2
Section titled “Hint 2”The formula does not ask for “the first bar of the session”. Read the two lines that
define RangeStartBar and RangeEndBar out loud, in English, exactly as they are
written. What are they actually asking for?
Hint 3
Section titled “Hint 3”ValueWhen( EXPRESSION, ARRAY ) returns the value the array held on the most recent bar
where the expression was true. What does it return on a day where the expression is never
true anywhere? Now put that answer into Cross( Close, OpeningHigh ) and into
Plot( OpeningHigh, ... ) and predict what each one does.
Hint 4
Section titled “Hint 4”Exhibit C says the time shift did not change. The times in Exhibit B did change. Both are true at once. What sits between the exchange and the database that could have moved without anything on Ines’s machine being touched — and why would it move on one specific weekend and then stay moved?
Hint 5
Section titled “Hint 5”Two clocks changed on different weekends. Ines’s database shift is a single constant that was correct for the offset between them. For the weeks between the two changes, that constant is not the offset any more. Now decide: does that make the stored stamps wrong, or does it make the numbers in her formula wrong? Both answers can be defended, and which one you pick determines whether you fix the database or the code. Say which you would fix and why.
The solution
Section titled “The solution”What the formula computes on Day 0
Section titled “What the formula computes on Day 0”RangeStartBar is TimeNum() == 93000. Exhibit B says no bar on Day 0 carries that
stamp, so this array is false on every bar of the day. RangeEndBar is false everywhere
too, for the same reason.
HighestSince( RangeStartBar, High ) has no occurrence to start from within the day. It
carries forward from the last time the condition was true, which was Day −1 — so it holds
a value, and that value is a running high measured from a bar in the previous week’s
session. It is not empty; it is stale and meaningless.
ValueWhen( RangeEndBar, ... ) needs an occurrence of RangeEndBar to read from. Within
the loaded range on and after Day 0 there is none, so it too carries the last value it had
from before the boundary. On a range that begins on Day 0 it has nothing at all and
returns Null.
Cross( Close, OpeningHigh ) compares today’s close against that stale or Null level.
Against Null it is false on every bar, which is why the scan returns nothing. Against a
stale level from the previous week it will fire, eventually, on a completely unrelated
price — which is the version of this fault that produces signals instead of silence, and
is worse.
Plot( OpeningHigh, ... ) draws nothing where the value is Null, which is exactly what
Ines sees: no lines, no error.
Why the chart still draws
Section titled “Why the chart still draws”Because Plot( Close, ... ) does not depend on any of it. The price series is the price
series; the formula’s timestamp assumptions live entirely in the three derived arrays. A
formula can be half-broken and look entirely healthy, and this is the general lesson worth
taking away: the visible parts of a chart are rarely the parts that carry your
assumptions.
The root cause
Section titled “The root cause”Ines’s country and the exchange’s country changed their clocks on different weekends. Her database time shift is a single constant that was correct for the offset between the two zones for most of the year. During the weeks between the two changes, the true offset is one hour different from that constant, so every stamp AmiBroker stores for the session is displaced by an hour relative to what it was before.
Nothing on her machine changed. Nothing in the data is missing. The count of bars per day is identical. The exchange session is exactly as long as it always was. The only thing that moved is the name of each bar, and her formula was written against names.
From a legislative calendar to an empty scan
- Two countries change clocks on different weekendsNeither one did anything unusual; the dates simply do not match
- The true offset between the zones changes by an hourFor the weeks between the two changes only
- The database Time shift is still the old constantCorrect for most of the year, wrong for these weeks
- Every session bar is stamped an hour away from beforeSame bars, same count, different names
- TimeNum() == 93000 is false on every bar of the dayThe formula asked for a name, and the name is gone
- ValueWhen returns Null; Cross is never true; nothing plotsNo error is raised at any step
The repair
Section titled “The repair”Fixing the database time shift would restore the old stamps — and would be wrong again in a few weeks when the second country catches up and the offset returns to what it was. A constant cannot track a value that takes two settings a year, and chasing it by hand twice a year is a maintenance task that will eventually be forgotten at exactly the wrong moment.
Fix the formula instead, so that it does not care.
Complete runnable AFL
// ===========================================================================// Opening range breakout - robust version// The repaired form of opening-range-fragile.afl. Same idea; none of the// assumptions left implicit.//// WHAT CHANGED, AND WHY// 1. The session is still described by two clock times, but no bar is// required to carry an exact stamp. The formula works from the FIRST bar// that falls inside the session window, whatever time that bar is.// 2. The opening range is a number of BARS counted from that first session// bar, not a second absolute clock time. A whole-hour shift, a short// session and a missing first bar all leave the count meaningful.// 3. The assumption is displayed. The title prints the session start the// formula actually found today, the one it found on the previous trading// day, and a warning when they differ - so a shift appears on the chart// instead of silently changing the answer.// 4. Nothing is drawn before the opening range is complete, so the levels// on screen are never levels the formula could not have known yet.//// NOT A TRADING SYSTEM// Opening range breakout is a well-known idea and this formula makes no// claim about whether it is worth trading. It is here as a correct// implementation of a session-anchored calculation, which is what Part 20// is about. Testing whether the idea has any merit is Part 28's job.//// ASSUMPTIONS, STATED SO THEY CAN BE CHECKED// - Prices are positive; the sentinels used to exclude out-of-range bars// from the running high and low depend on that.// - The session lies inside one calendar day as the database stamps it.// - Counting bars assumes the bars are there. A provider that drops the// first two minutes of the session will produce a range that starts two// minutes late, and no formula can see a bar that was never delivered.// session-profile.afl and bar-timestamp-audit.afl are the checks for that.// ===========================================================================
_SECTION_BEGIN( "Opening range - robust" );
SessionStart = Param( "Session start (HHMMSS)", 93000, 0, 235959, 100 );SessionEnd = Param( "Session end (HHMMSS)", 155959, 0, 235959, 100 );RangeBars = Param( "Opening range length (bars)", 6, 1, 120, 1 );
// Converts minutes-since-midnight into a readable HHMM number for the title.function MinutesToHHMM( Minutes ){ WholeHours = Int( Minutes / 60 ); return WholeHours * 100 + ( Minutes - WholeHours * 60 );}
InSession = TimeNum() >= SessionStart AND TimeNum() <= SessionEnd;
PreviousDate = Nz( Ref( DateNum(), -1 ), 0 );NewDay = DateNum() != PreviousDate;
// Position within the session, counted in bars rather than read off the clock.CumSession = Cum( InSession );SessionBarNumber = CumSession - Nz( ValueWhen( NewDay, Ref( CumSession, -1 ) ), 0 );
FirstSessionBar = InSession AND SessionBarNumber == 1;InOpeningRange = InSession AND SessionBarNumber <= RangeBars;RangeComplete = InSession AND SessionBarNumber > RangeBars;
// Sentinels keep bars outside the opening range from winning the running high// and low. Prices are assumed positive, as the header states.OpeningHigh = HighestSince( FirstSessionBar, IIf( InOpeningRange, High, -1 ) );OpeningLow = LowestSince( FirstSessionBar, IIf( InOpeningRange, Low, 1000000 ) );
// The assumption made visible: where did today's session actually start, and// did that differ from the previous trading day?MinutesOfDay = Hour() * 60 + Minute();StartMinuteToday = ValueWhen( FirstSessionBar, MinutesOfDay, 1 );StartMinutePrev = ValueWhen( FirstSessionBar, MinutesOfDay, 2 );SessionMoved = StartMinuteToday != StartMinutePrev;
// Levels are shown only once the range is complete, so nothing on the chart// depends on bars that had not printed at the time.OpeningHighPlot = IIf( RangeComplete, OpeningHigh, Null );OpeningLowPlot = IIf( RangeComplete, OpeningLow, Null );
Buy = RangeComplete AND Cross( Close, OpeningHigh );Sell = NOT InSession; // flat outside the session, no look-ahead
Buy = ExRem( Buy, Sell );Sell = ExRem( Sell, Buy );
Plot( Close, "Close", colorDefault, styleCandle );Plot( OpeningHighPlot, "Opening range high", colorGreen, styleDashed | styleNoRescale );Plot( OpeningLowPlot, "Opening range low", colorRed, styleDashed | styleNoRescale );PlotShapes( Buy * shapeUpArrow, colorGreen, 0, Low, -20 );
Warning = "";if( LastValue( SessionMoved ) ){ Warning = "\n*** SESSION START MOVED since the previous trading day." + " Check the data before trusting these levels. ***";}
Title = "ROBUST opening range " + Name() + " " + Interval( 2 ) + "\n" + "Session start found today: " + NumToStr( LastValue( MinutesToHHMM( StartMinuteToday ) ), 1.0, False ) + " previous trading day: " + NumToStr( LastValue( MinutesToHHMM( StartMinutePrev ) ), 1.0, False ) + " range length: " + NumToStr( RangeBars, 1.0, False ) + " bars" + Warning;
_SECTION_END();Four changes, each addressing one of the four questions.
The session is a window, not a stamp. InSession is a range test rather than an
equality test, so it stays true across a displacement as long as the session still falls
inside the window. Widen the window and it tolerates an hour in either direction; the cost
is that a very wide window may also admit extended-hours bars, which is why the window is a
parameter and not a constant.
The opening range is counted in bars. SessionBarNumber is 1 on the first in-session
bar of the day, whatever time that bar carries, so “the first six bars” means the same
thing before and after the boundary, on a short day, and after a change of provider
convention. This is the change that actually fixes the failure.
The assumption is printed. The title reports the session start the formula found today and the one it found on the previous trading day, and prints a warning line when they differ. Ines would have seen that warning on Day 0, on the chart, before the scan ran.
Nothing draws before the range is complete. RangeComplete gates both plotted levels,
so the chart never shows a level derived from bars that had not printed yet — a separate
correctness point, and one Part 30 returns to at length.
What would have made it visible on day one
Section titled “What would have made it visible on day one”Three things, in order of cost.
The formula’s own title, printing the count of bars carrying the stamp it depends on — the one honest line in the fragile version. Zero on Day 0, visible on the chart the moment she looked at it.
A scheduled run of session-shift-detector.afl across the last month, which would have
reported Day 0 with a start shift and an end shift both equal to −60 and the whole-session
signature reading 1.
And a scan that reports its own emptiness. A scan that returns zero rows looks identical to a scan that has nothing to report, and Ines spent a day assuming the second. An exploration that always returns one row per symbol, with the setup condition as a column, distinguishes “no candidates today” from “this is not evaluating”.
The same fault, four other places
Section titled “The same fault, four other places”An hour of displacement does not break one formula. It breaks everything anchored to the session, at the same moment, and the other breakages are quieter because nothing stops plotting.
VWAP and any session-anchored average
Section titled “VWAP and any session-anchored average”A volume-weighted average price is a running sum of price times volume divided by a running sum of volume, both reset at the start of the session. The reset is the whole definition.
Fragment — not a complete formula
// The anchor is the only interesting line. Everything else is arithmetic.SessionAnchor = InSession AND SessionBarNumber == 1;
PriceVolume = Sum( IIf( InSession, Avg * Volume, 0 ), 1 );// ... accumulate from the anchor, not from a clock time.Anchor it to TimeNum() == 93000 and, on a displaced day, the reset either never happens —
so the average accumulates across two days and drifts steadily away from anything useful —
or it happens an hour into the session, so the first hour’s volume is excluded from a
statistic whose entire purpose is to weight by volume.
Neither produces an error. Both produce a smooth, credible line on the chart, sitting near the price, looking exactly like a VWAP. This is the most dangerous member of the family because there is no visual signature of the failure at all.
Alerts
Section titled “Alerts”AlertIf() fires on a Boolean expression evaluated over the most recent lookback bars.
If the expression is a breakout of a level that is Null, it never fires, and an alert
that never fires is indistinguishable from a market that never triggered it.
Two further details make the alert case worse than the scan case. The documented flags include one that suppresses repeated alerts having the same date and time, which means the alert system’s own deduplication is keyed on bar stamps — and displaced stamps are, from its point of view, new. And alerts are dispatched on your machine’s clock, so a repeated scan configured to run between two wall-clock times is now pointed at a session that starts an hour earlier, leaving the first hour of trading unscanned entirely. Part 25 covers alerts properly; the point here is that a timestamp fault reaches them through two independent routes.
Intraday backtests
Section titled “Intraday backtests”Symptom 7 said the backtest still produced trades, all of them dated before the weekend. That is the whole story in one line: the backtest is running the same broken rules over the same displaced data, and it stops generating entries at exactly the point the live scanner stopped generating signals.
The damage in a backtest is subtler than in a scan, because a backtest reports a number at the end and the number does not look wrong. A test spanning a transition contains weeks computed on one session definition and weeks computed on another, mixed together in one equity curve and one set of statistics. The result is not a test of the strategy. It is a test of the strategy for part of the period and of something else for the rest, with no marker separating them.
Daily bar construction
Section titled “Daily bar construction”The quietest one. File → Database Settings → Intraday Settings chooses what the daily
compression uses as its boundary: exchange time, local time, or the day and night session
times you defined. Displace the session by an hour and some of the day’s bars fall on the
wrong side of that boundary, so they are attributed to the adjacent day.
The consequences flow outward from there. The daily open, high, low and close change. A
gap measured as today’s open against yesterday’s close changes. A daily average true range
changes. A higher-timeframe filter built with TimeFrameSet( inDaily ) on this database
is compressed from the same misassigned bars, so a rule that only trades intraday when the
daily trend agrees is now consulting a daily bar that contains an hour of the wrong day.
None of this is visible on an intraday chart. You find it by running the session profile from the second lesson and comparing its all-hours daily figures against the daily bars the database actually produces.
Verification checklist
Section titled “Verification checklist”Before you consider any intraday setup trustworthy:
- Run
bar-timestamp-audit.afland record what the first and last stamps of a normal day are, and what the database time shift is. Write it down; it is the baseline everything else is compared against. - Confirm whether your bars are stamped at the start or the end of the interval, by re-running the audit at two different chart intervals.
- Run
session-shift-detector.aflover at least a year and record every date with the whole-session signature. - Check that no formula you rely on contains an equality test against a clock time.
TimeNum() ==anywhere in your code is worth a second look every time. - Check that every session-anchored calculation — opening range, VWAP, first-hour volume, closing-window filters — is anchored to a position in the session rather than to a stamp.
- Make each of those formulas print the session start it found and warn when it moves.
- Re-run any intraday backtest whose period spans a date from the third checklist item, after the repairs, and treat the earlier results as void rather than as a comparison.
The fault was not in the data, the plugin, the database or the software. It was an assumption — that a bar carrying a particular name would be there — encoded as an equality test and never checked. A legislative calendar in two countries moved the names, and every piece of logic that depended on them stopped meaning what it meant before.
The general shape is worth keeping. A clock-anchored intraday calculation has a hidden dependency on a chain of decisions made by an exchange, a provider, a plugin and a dialog box, none of which will tell you when they change. A position-anchored calculation depends on the bars being there, which is a much smaller claim and one you can check.
And the fix that matters most is not in the arithmetic. It is the line that prints what the formula found, so that the next time something moves, the formula says so instead of going quiet.
Check your understanding
Sources for this lesson
7 verified · checked 2026-08-31
- 01AmiBroker AFL Function Reference — TimeNumamibroker.com/guide/afl/timenum.html2026-08-31
- 02AmiBroker AFL Function Reference — ValueWhenamibroker.com/guide/afl/valuewhen.html2026-08-31
- 03AmiBroker AFL Function Reference — HighestSinceamibroker.com/guide/afl/highestsince.html2026-08-31
- 04AmiBroker AFL Function Reference — AlertIfamibroker.com/guide/afl/alertif.html2026-08-31
- 05AmiBroker AFL Function Reference — Status§ timeshiftamibroker.com/guide/afl/status.html2026-08-31
- 06AmiBroker User's Guide — Database Settings window§ Intraday Settingsamibroker.com/guide/w_dbsettings.html2026-08-31
- 07AmiBroker User's Guide — Using formula-based alertsamibroker.com/guide/h_alerts.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.