Skip to content
Level 5 · Real-Time AmiBroker UserLabPart 26 · page 3 of 345 min
45Minutes
11AFL functions
9Sources
StandardRequires
AFL functions taught here11

Replay Exercise: Intraday Setups and Alerts

The previous exercise put your reading of a chart under test. This one puts your code under test. You will run a breakout alert and a higher-timeframe filter against a replayed intraday session and answer four questions that nobody can answer from the documentation alone: does the alert reach the Alert Output window at all, how many times does it fire, what happens to the completed-bar guard when it meets the lookback argument, and how different a daily filter looks when the daily bar is only half built.

Every one of those has bitten somebody at 09:32 on a Monday. Finding out here costs forty-five minutes and nothing else.

This exercise is written for intraday bars. It works on daily bars and the fallback is named at every step, but the session-filter material only means something intraday.

What you have Chart interval Step interval Higher frame
One-minute bars 5-minute 1 minute Daily
Five-minute bars 30-minute 5 minutes Daily
End-of-day bars only Daily Daily Weekly

If you have no intraday data at all, Part 18 covers the free and end-of-day sources AmiQuote supports, and Part 19’s lab builds an intraday database from historical files. Neither requires a subscription. A single day of one-minute bars for one liquid symbol is enough for this exercise, and that is a small enough amount of data to obtain by ordinary means.

Alerts raised from a chart formula only reach the Alert Output window if AmiBroker has been told to accept them. In Tools, Preferences, Alerts there is an “Enable alerts from” group with separate checkboxes for Automatic Analysis, Commentary and custom indicators. The formula below is a custom indicator, so that box must be ticked.

Open Window, Alert Output and dock it where you can see it without covering the chart. The window carries a column identifying which subsystem raised each line, which is how you will tell an alert from your chart apart from one raised by a scan.

Four panes on one sheet: the price chart with the alert harness applied, the replay clock from the first lesson, the higher-timeframe context panel, and the Alert Output window.

Set the replay range to cover one full session plus at least a hundred bars before it, so that the lookback windows are warm at the open rather than filling up during the exercise.

Different from the previous exercise, because a different thing is under test.

A breakout alert you can rehearse. It raises a line in the Alert Output window when a bar closes above the previous N bars’ high on above-average volume inside your session hours, it stamps every line with the replay position so the log tells you when in the session each alert fired, and it exposes as parameters exactly the two settings whose interaction this exercise exists to establish.

It stops at the alert. There is no order, no broker connection, and no automation beyond putting a line of text in front of a human being.

Complete runnable AFL

replay-alert-harness.afl
// replay-alert-harness.afl
// Part 26 - Replay Exercise: Intraday Setups and Alerts
//
// A breakout alert built so that it can be rehearsed under Bar Replay instead
// of being tested for the first time in a live session. It writes to the Alert
// Output window and stamps every line with the replay position, so the log you
// end up with says WHEN in the replayed session each alert was raised.
//
// It ends where this course ends: an alert, a human reading it, a decision.
// There is no order routing here and none is coming.
//
// How to run it:
// Tools -> Preferences -> Alerts: tick "Enable alerts from" custom indicators,
// or nothing you do below will reach the Alert Output window.
// Formula Editor -> paste -> name it "Replay alert harness" -> Apply Indicator.
// Window -> Alert Output to see the lines arrive.
// Then Tools -> Bar Replay, Step interval = your base interval, Speed 1.
//
// Assumptions declared up front:
// - Chart formula on an intraday database. It runs on daily bars too, in
// which case leave the session filter off.
// - The breakout level uses the previous BreakoutLook bars only. The current
// bar's own high is excluded, so the level cannot move to meet the price.
// - Session times are read with TimeNum(), which reports the timestamp your
// database holds. If your bars are stamped in a different time zone from
// the exchange, this filter is wrong in exactly that many hours - Part 20
// covers how to check which convention your data uses.
// - Alerts here are informational. No position size, no risk, no order.
_SECTION_BEGIN( "Replay alert harness" );
BreakoutLook = Param( "Breakout lookback (bars)", 20, 2, 200, 1 );
MinRelVolume = Param( "Minimum relative volume", 1.5, 0, 10, 0.1 );
VolumeLook = Param( "Relative-volume lookback", 20, 5, 200, 1 );
UseSession = ParamToggle( "Apply session filter", "No|Yes", 1 );
SessionStart = Param( "Session start (HHMMSS)", 100000, 0, 235959, 100 );
SessionEnd = Param( "Session end (HHMMSS)", 154500, 0, 235959, 100 );
CompletedOnly = ParamToggle( "Alert on completed bars only", "No|Yes", 1 );
LookbackBars = Param( "AlertIf lookback argument", 2, 1, 10, 1 );
AlertFlags = Param( "AlertIf flags", 1 + 2, 0, 15, 1 );
// --- The setup -------------------------------------------------------------
PriorHigh = Ref( HHV( High, BreakoutLook ), -1 );
Breakout = Cross( Close, PriorHigh );
AverageVolume = MA( Volume, VolumeLook );
RelVolume = IIf( AverageVolume > 0, Volume / AverageVolume, 0 );
VolumeOk = RelVolume >= MinRelVolume;
InSession = IIf( UseSession,
TimeNum() >= SessionStart AND TimeNum() <= SessionEnd,
True );
// The last bar on an intraday chart is still forming: a condition can go true
// and false again inside it. This is AmiBroker's own documented remedy - alert
// only from bars that have closed.
//
// The interaction between this guard and the lookback argument is NOT written
// down anywhere, and it is the first thing this lab asks you to establish.
// AlertIf examines only the `lookback` most recent bars, `lookback` defaults to
// 1, and BarComplete is false on that bar by construction. Two readings follow.
// Either the alert for a signal on bar T appears when you step onto T+1, or a
// lookback of 1 leaves nothing for it to fire from at all. LookbackBars is a
// parameter so that you can run both and record which one your build does.
// The default of 2 is the setting that produces output under either reading.
BarComplete = IIf( CompletedOnly, BarIndex() < LastValue( BarIndex() ), True );
Condition = Breakout AND VolumeOk AND InSession AND BarComplete;
// --- Where the clock is ----------------------------------------------------
PlaybackPos = GetPlaybackDateTime(); // zero when Bar Replay is not active
if ( PlaybackPos )
{
ClockText = "REPLAY " + DateTimeToStr( PlaybackPos );
}
else
{
ClockText = "live/EOD " + Now( 0 );
}
AlertText = "Breakout candidate: " + Name()
+ " closed above the prior " + NumToStr( BreakoutLook, 1.0 )
+ "-bar high on " + NumToStr( LastValue( RelVolume ), 1.2 )
+ "x volume [" + ClockText + "]"
+ " -- review before acting; this is not an order";
// Type 1 is AmiBroker's "buy" alert type. The type is the key the built-in
// de-duplication state machine remembers per symbol, so give different kinds of
// alert different types or they will suppress each other.
// Flags default to 1+2+4+8, which also suppresses repeats of the same type and
// the same timestamp. The parameter above starts at 1+2 so that you can see
// every raised alert while you are rehearsing, and can put the suppression
// bits back once you know what the formula does.
AlertIf( Condition, "", AlertText, 1, AlertFlags, LookbackBars );
// --- What you see on the chart ---------------------------------------------
Plot( Close, "Close", colorDefault, styleCandle );
Plot( PriorHigh, "Prior " + NumToStr( BreakoutLook, 1.0 ) + "-bar high",
colorBlue, styleLine | styleThick );
PlotShapes( IIf( Condition, shapeUpArrow, shapeNone ), colorBrightGreen, 0, Low );
// Read the state as text, not as a colour: the title says in words whether the
// three parts of the condition are true on the last visible bar.
Title = Name() + " " + ClockText + "\n"
+ "Breakout: " + WriteIf( LastValue( Breakout ), "yes", "no" )
+ " Volume filter: " + WriteIf( LastValue( VolumeOk ), "yes", "no" )
+ " (" + NumToStr( LastValue( RelVolume ), 1.2 ) + "x)"
+ " In session: " + WriteIf( LastValue( InSession ), "yes", "no" ) + "\n"
+ "Alert raised on this bar: "
+ WriteIf( LastValue( Condition ), "YES", "no" );
_SECTION_END();

Download replay-alert-harness.afl114 lines

The setup is three conditions combined. Cross( Close, PriorHigh ) fires on the bar where the close moves above the level, not on every bar it stays above it — the state versus event distinction from Part 8, and the single most common cause of an alert that repeats forty times. Ref( HHV( High, BreakoutLook ), -1 ) shifts the high window back one bar so that the current bar’s own high is excluded from the level it is supposed to be breaking; without the shift the level moves to meet the price and the cross can never happen. The volume filter and the session filter are ordinary gates.

The session filter uses TimeNum(), which returns the timestamp your database holds in HHMMSS form. It is worth being clear that this is your database’s idea of the time, not the exchange’s: if your bars are stamped in a different zone, or your bar time-stamping preference records the start of the interval where you assumed the end, the filter is wrong by a fixed offset and everything downstream is wrong with it. Part 20 covers how to establish which convention your data uses. Replay is a good place to notice the symptom — alerts clustering an hour before or after the session — but it will not diagnose it for you.

The completed-bar guard is AmiBroker’s own documented remedy for intraday alerts: BarComplete = BarIndex() < LastValue( BarIndex() ) is false on the last, still-forming bar and true on every bar before it. The tutorial gives it precisely because a condition can go true and then false again inside a forming bar.

The AlertIf call takes the condition, an empty command string (text only, no sound, no e-mail, no external program), the text, a type of 1, the flags, and the lookback. Type matters more than it looks: the documented internal state machine remembers the type of the last alert per symbol, and two different alerts sharing a type will suppress each other. Flags default to 1+2+4+8, where 4 suppresses repeats of the same type and 8 suppresses repeats at the same timestamp; the parameter starts at 1+2 so that you can see everything during a rehearsal, which is the tutorial’s own suggestion for experimentation.

The chart output draws the level, marks triggering bars with an arrow, and — because meaning must never live in a colour alone — writes the state of all three conditions into the title as words.

  • AlertIf( expression, command, text, type = 0, flags = 1+2+4+8, lookback = 1 ) — raises an alert when the expression is true on one of the lookback most recent bars. An empty command string writes to the Alert Output window; the documented alternatives are "SOUND path", "EMAIL" and "EXEC path-or-URL".
  • Cross( array1, array2 ) — true on the bar where the first rises above the second.
  • TimeNum() — the bar’s time as HHMMSS.
  • BarIndex() and LastValue( array ) — together, the completed-bar test.
  • GetPlaybackDateTime() — the replay position, or zero, used here only to stamp the text.

With replay off, applying the formula to an intraday chart should draw the prior-high line as a staircase above the price and put arrows on the bars where a breakout closed. The title should report the three conditions in words on the last bar.

Under replay, stepping through a session should produce lines in the Alert Output window whose text ends with [REPLAY <timestamp>], where the timestamp advances with the playback position rather than sitting at today’s date.

Set BreakoutLook to 3 and replay a volatile stretch. You should get many alerts — enough to be sure the mechanism works. Then set it to 100 on the same stretch; you should get very few or none. A formula that produces the same number of alerts at both settings is not computing the level you think it is.

Then check the arrow and the alert agree. Every arrow on a completed bar should correspond to exactly one line in the Alert Output window. An arrow with no line, or a line with no arrow, is the discrepancy the next section is about.

  • Arrows but no alert lines. The custom-indicators checkbox in Preferences is off, or the flags have had bit 1 cleared, which silences the window while leaving other actions active.
  • The same alert repeats every bar. The condition is a state rather than an event — Close > PriorHigh instead of Cross( Close, PriorHigh ).
  • Alerts stop after the first one and never come back. Flags include 4, which suppresses repeats of the same type. That is usually what you want in production and never what you want while rehearsing.
  • Alerts an hour either side of the session. A time-zone or bar-time-stamping mismatch, not a formula bug. Part 20.
  • No alerts at all with the completed-bar guard on. That is phase 3 below, and it is the point of the exercise rather than a fault.

Replace the empty command string with "SOUND" and a path to a short WAV file, and rehearse with your eyes off the screen. It changes the exercise considerably: an alert you have to notice is a different thing from an alert you were already watching for. The documented alternatives to AlertIf for unconditional actions are PlaySound, SendEmail and ShellExecute, which do the same jobs without the de-duplication state machine.

Phase 1. Does an alert reach the window under replay at all?

Section titled “Phase 1. Does an alert reach the window under replay at all?”

Set CompletedOnly to No and LookbackBars to 1, so nothing is masked. Step forward through a stretch containing at least one obvious breakout and watch the Alert Output window.

The documentation supports two halves of this and not the third. It says Bar Replay affects all formulas, in charts and in Analysis alike. It says indicators can raise alerts when the Preferences checkbox is ticked. It does not, anywhere, state what happens to alerts while replay is driving the chart. That combination is exactly why this is phase 1 rather than an assumption: you are establishing it on your installation, on your version, and writing the answer down.

Record: alerts appeared / alerts did not appear, and the AmiBroker version from Help, About.

Phase 2. How many times does one breakout fire?

Section titled “Phase 2. How many times does one breakout fire?”

Keep CompletedOnly at No. Run the same stretch three ways and count the lines each time.

Flags What is suppressed Count
1+2 Nothing beyond the default text and beep
1+2+8 Repeats at the same date and time
1+2+4+8 Repeats of the same type, and at the same timestamp

Fill in the counts before reading on. The interesting comparison is the first row against the third. On a forming intraday bar the condition can flicker, and each re-execution of the formula is another opportunity for the alert to be raised; the suppression bits are what stand between you and a log full of the same event.

Phase 3. The completed-bar guard and the lookback argument

Section titled “Phase 3. The completed-bar guard and the lookback argument”

Now set CompletedOnly to Yes, leaving LookbackBars at 1. Replay the same stretch.

Two documented facts collide here, and the documentation never says what happens when they do. AlertIf examines only the lookback most recent bars, and lookback defaults to 1. BarComplete is false on the most recent bar by construction. Two readings follow, and both are defensible from the text.

Reading one, which Part 25 states as a prediction to be checked: a signal on bar T produces an alert when you step onto bar T+1, because stepping makes bar T complete. The one-bar delay is the acknowledged cost of the guard.

Reading two: on the step onto T+1, the only bar being examined is T+1 itself, where BarComplete is false — so nothing fires, and the guard with a lookback of 1 silences the alert entirely.

Neither reading is documented. Run it and find out. Then set LookbackBars to 2 and run the same stretch again, which puts the newly completed bar inside the examined window under either reading.

Record all three counts: guard off with lookback 1, guard on with lookback 1, guard on with lookback 2. Whatever they turn out to be on your build, you will have settled a question the manual leaves open, by observation rather than by argument — and that habit is worth considerably more than this particular answer.

Most intraday rules carry a filter from a larger frame: only take longs when the daily close is above its average, and so on. Under replay you can see the thing that a daily-bar backtest hides completely — at 11:00 the daily bar does not exist yet.

What a daily filter actually sees at 11:00

  1. Your daily filterCompares the daily close with its own average
  2. Today's daily barOpen, high so far, low so far, last price so far - it will not settle until the close
  3. Intraday bars replayed so farEverything from the open up to the playback position
  4. Bars not yet replayedInvisible to every formula while replay is active
A backtest on completed daily bars never meets the second layer. Live trading meets nothing else until the close.

Show the larger frame the way it exists during a session rather than the way it exists in a finished database: computed from the intraday bars that have arrived, updating as they arrive, and labelled clearly as incomplete.

Complete runnable AFL

replay-mtf-context.afl
// replay-mtf-context.afl
// Part 26 - Replay Exercise: Intraday Setups and Alerts
//
// The higher-timeframe check, done the way it has to be done under replay: the
// daily context is COMPUTED from the intraday bars that have arrived so far,
// not looked up from a finished daily bar. Under Bar Replay the current daily
// bar is a partial bar, exactly as it is at eleven o'clock on a real Tuesday.
// That is the honest version of a multi-timeframe filter, and it is the version
// that will disagree with your backtest.
//
// How to run it:
// Formula Editor -> paste -> name it "Replay MTF context" -> Apply Indicator
// on an intraday chart. Then drive it with Tools -> Bar Replay.
//
// Assumptions declared up front:
// - Chart formula on an INTRADAY database. On a daily chart the compressed
// frame equals the base frame and the panel has nothing to say.
// - Compression uses AmiBroker's own session definitions from
// File -> Database Settings -> Intraday Settings. If those are wrong, the
// daily bars built here are wrong in the same way.
// - expandLast is used deliberately. The documented caveat is that
// expandFirst, applied to anything other than the period's open, can make a
// value visible before the period that produced it has finished - which is
// the definition of reading the future.
// - Nothing here is a signal. It is a description of the larger frame that
// you record in the log sheet alongside your intraday decision.
_SECTION_BEGIN( "Replay MTF context" );
HigherFrame = ParamList( "Higher timeframe", "Daily|Hourly|15-minute", 0 );
TrendPeriod = Param( "Higher-frame average period", 20, 2, 200, 1 );
if ( HigherFrame == "Hourly" )
{
UpperInterval = inHourly;
}
else
{
if ( HigherFrame == "15-minute" )
{
UpperInterval = in15Minute;
}
else
{
UpperInterval = inDaily;
}
}
// Guard against the case that makes this panel meaningless: asking for a frame
// that is not actually larger than the chart you are looking at.
BaseSeconds = Interval();
FrameIsUp = UpperInterval > BaseSeconds;
if ( FrameIsUp )
{
// Inside this block Open/High/Low/Close/Volume ARE the compressed bars.
TimeFrameSet( UpperInterval );
UpperClose = Close;
UpperHigh = High;
UpperLow = Low;
UpperTrend = MA( Close, TrendPeriod );
UpperAbove = Close > UpperTrend;
TimeFrameRestore();
// Expand back to the chart's own interval for display and for comparison
// with intraday values. expandLast places each compressed value on the last
// bar of its period, and carries it forward from there.
UpperCloseX = TimeFrameExpand( UpperClose, UpperInterval, expandLast );
UpperHighX = TimeFrameExpand( UpperHigh, UpperInterval, expandLast );
UpperLowX = TimeFrameExpand( UpperLow, UpperInterval, expandLast );
UpperTrendX = TimeFrameExpand( UpperTrend, UpperInterval, expandLast );
UpperAboveX = TimeFrameExpand( UpperAbove, UpperInterval, expandLast );
Plot( Close, "Close", colorDefault, styleCandle );
Plot( UpperTrendX, HigherFrame + " average", colorBlue, styleLine | styleThick );
Plot( UpperHighX, HigherFrame + " high", colorLightGrey, styleLine );
Plot( UpperLowX, HigherFrame + " low", colorLightGrey, styleLine );
PlaybackPos = GetPlaybackDateTime();
if ( PlaybackPos )
{
ClockText = "REPLAY " + DateTimeToStr( PlaybackPos );
}
else
{
ClockText = "Replay OFF";
}
Title = Name() + " " + ClockText + "\n"
+ HigherFrame + " frame: close "
+ NumToStr( LastValue( UpperCloseX ), 1.4 )
+ " average " + NumToStr( LastValue( UpperTrendX ), 1.4 )
+ " above average: "
+ WriteIf( LastValue( UpperAboveX ), "yes", "no" ) + "\n"
+ "The " + HigherFrame + " bar covering this moment is INCOMPLETE. "
+ "It contains only the bars replayed so far.";
}
else
{
Plot( Close, "Close", colorDefault, styleCandle );
Title = Name() + "\nChosen higher timeframe (" + HigherFrame
+ ") is not larger than this chart's interval ("
+ Interval( 2 ) + "). Nothing to compress.";
}
_SECTION_END();

Download replay-mtf-context.afl110 lines

TimeFrameSet( interval ) replaces the price arrays with time-compressed bars of the requested interval. Inside that block, Close is the daily close and MA( Close, 20 ) is a twenty-day average, computed from whatever intraday bars are currently visible. TimeFrameRestore() puts the base interval back, and TimeFrameExpand maps each compressed value onto the chart’s own bars so the two can be drawn and compared.

expandLast is chosen deliberately. The documentation’s own caveat is about expandFirst: used on anything other than the period’s open it makes a value visible before the period that produced it has ended, which lets a formula know on Monday what the week’s high turned out to be. expandLast places the value at the end of its period and carries it forward, which is the behaviour that matches how the information actually became available.

The guard at the top compares the requested interval with Interval() and refuses to compress to something that is not larger than the chart. It is two lines, and it prevents a panel that appears to work while telling you nothing.

The title says in words that the higher-frame bar covering this moment is incomplete. That sentence is the entire lesson of the panel, and it belongs on screen rather than in a comment.

  • TimeFrameSet( interval ) / TimeFrameRestore() — switch the price arrays to a compressed interval and back. The documentation is explicit that you must restore before calling TimeFrameSet again with a different interval.
  • TimeFrameExpand( array, interval, mode = expandLast ) — map a compressed array back onto the base interval. The interval must match the one used to compress.
  • Interval( format = 0 ) — the chart’s own bar size in seconds, used here as the guard.
  • ParamList( name, values, default ) — a dropdown parameter returning the chosen string.

On a five-minute chart with the higher frame set to Daily, you should see a slow line for the daily average and two flat lines for the daily high and low, all of them stepping once per day. Under replay, the daily high and low lines should extend during the session — the day’s high can only rise and its low can only fall as bars arrive — and the average should adjust slightly as today’s partial close changes.

The ratchet test above is the one that matters, and it is worth doing every time you build a multi-timeframe rule. A second check: press Stop and compare the panel’s daily close with the same day’s close on an actual daily chart of the same symbol. They should agree at the end of the day and disagree during it, and both of those are correct.

  • The panel says the frame is not larger than the chart. You are on a daily chart asking for a daily frame. Change one of them.
  • The daily bars do not line up with the exchange’s day. Compression follows File, Database Settings, Intraday Settings. A wrong session definition or time shift produces daily bars that begin and end in the wrong place, and every value here inherits the error.
  • The expanded lines are flat across the whole chart. The interval passed to TimeFrameExpand does not match the one passed to TimeFrameSet.
  • Values appear one period early. expandFirst has been used where expandLast was needed. This is look-ahead, and a backtest built on it will look considerably better than it should.

Add a second higher frame — hourly as well as daily — and record at each alert whether they agreed. Disagreement between two filter frames is common, and deciding in advance which one wins is a rule you have to write down before the session, not during it.

For every alert raised during the session, one row:

Field What goes in it
Alert time From the [REPLAY ...] stamp, not the system clock
Bar The bar the alert refers to, and whether it was the forming bar or the one before
Phase Which parameter combination was running
Level broken The prior-high value from the chart
Relative volume From the title
Daily filter Above or below, from the context panel, at that moment
Would you have acted? Yes, no, or “needed something the alert did not tell me” — decided before stepping on
Outcome Filled in during the debrief, from the exploration

The last-but-one column is the one that repays the effort. An alert you would not have acted on is a false positive from your own point of view even if the price then went your way, and a run of them means the alert is firing on a condition you do not actually trade.

Press Stop, then run the debrief exploration from the previous exercise on the same symbol and date range, with BreakoutLook set to the same value the harness used and Report every bar switched off, so that the exploration reports precisely the breakout bars. Set Horizon to something that matches how long you would have held: twelve bars on a five-minute chart is an hour.

Match the rows to your sheet by timestamp and fill in the Outcome column. Then answer these in writing.

  1. How many alerts did the session produce in each of the five parameter combinations you ran? Which single setting changed the count most?
  2. Did the alert stamp ever disagree with the bar you thought it referred to? A stamp on the forming bar and an arrow on the completed one are not the same event.
  3. On how many alerts did the daily filter agree with the direction of the breakout? On those where it disagreed, what did the exploration say happened?
  4. How many alerts would you have acted on? Of those you would not, what was missing — and is it something the formula could compute, or something you were doing by eye?
  5. Did any alert fire outside your intended session hours? If so, is that a filter bug or a time-stamping mismatch, and how would you tell the two apart?
  6. Compare the number of alerts with the number of distinct situations. If eight lines describe three events, the suppression flags are the fix, not a shorter lookback.
  7. What would this formula have done on a day when the instrument gapped up at the open and drifted? Set the replay to such a day and find out, rather than reasoning about it.
  8. Which of today’s findings are about your code, and which are about the market? Sort every finding into one of those two piles before you finish.

Six criteria, 0 to 4 each, out of 24. As before, none of the points are for the price going your way.

Criterion 0 2 4
Phases run One combination tried Two or three All five combinations, each recorded, including the ones that produced nothing
Isolation Several parameters changed together Mostly one at a time Exactly one change per run, with the count written down before the next change
Stamp discipline Times taken from the system clock Mixed Every row timed from the replay stamp, forming and completed bars distinguished
Filter honesty Daily filter read from a finished daily chart Read from the panel but not questioned Read from the panel during the session, with the ratchet test performed first
Decision recorded in advance Outcome column filled before the “would you have acted” column Some rows Every row committed before the exploration was run
Sorting findings Findings about code and market mixed together Partially separated Every finding assigned to one pile, with the market pile explicitly marked as one session of one symbol

Below 12 usually means phases were skipped. Between 12 and 18 is a normal first pass. Above 18 means you have a rehearsal procedure you can run on any formula you write from here on, which is the actual deliverable.

It proved things about your software and your code, and those proofs are solid because they are deterministic. If the alert fired twice on one breakout with 1+2 and once with 1+2+4+8, that is a property of AlertIf and your flags, and it will behave the same way tomorrow. If no alert appeared until the lookback was raised to 2, that is a property of the interaction between two documented behaviours, and you established it by observation. If the daily high line ratcheted upward through the session, your multi-timeframe filter is not reading the future. All three are reproducible facts about a machine.

It proved nothing whatsoever about the setup.

You now have a rehearsal procedure for real-time code that does not need a real-time feed. Enable alerts from custom indicators, apply the harness and the context panel, set the replay range around a session, and change one parameter per pass while counting what comes out.

You have established, on your own installation, whether alerts reach the output window under replay, how the suppression flags change the count, what the completed-bar guard does when it meets a lookback of 1, and what a higher-timeframe filter looks like when its bar is only half built. You have an outcome sheet in which the decision was recorded before the result was known, and a debrief that sorts every finding into “about my code” or “about the market”.

That is the end of the real-time track. Everything in Parts 17 to 26 that needed a live feed now has a route that does not, and the remaining question — whether any of these rules have an edge — is a different kind of question, which Part 27 starts to ask properly.

Check your understanding

Question 1. Your alert harness draws arrows on the chart and the title says the condition is true, but the Alert Output window stays empty. What do you check first?
Show the answer and why

Answer: Whether "Enable alerts from" custom indicators is ticked in Tools, Preferences, Alerts

Alert generation is enabled per subsystem in Preferences, with separate checkboxes for Automatic Analysis, Commentary and custom indicators. A chart formula is the third of those. With the box unticked, AlertIf produces nothing from that context and no error is shown anywhere.

Question 2. Why does the harness use Ref( HHV( High, N ), -1 ) rather than HHV( High, N ) as the breakout level?
PriorHigh = Ref( HHV( High, BreakoutLook ), -1 );
Breakout  = Cross( Close, PriorHigh );
Show the answer and why

Answer: To exclude the current bar from its own level, since otherwise the level rises with the bar and the cross can never happen

Without the shift, a bar that makes a new high raises HHV to that same high, so Close is being compared with a level that already includes it. The negative shift is not about look-ahead — a negative Ref reads backwards, which is always safe — it is about excluding the bar from the definition it is being tested against.

Question 3. Under replay, at 11:00 on a five-minute chart, what does a filter built with TimeFrameSet( inDaily ) see for today?
Show the answer and why

Answer: A partial daily bar built from the intraday bars replayed so far, whose high and low can still extend

Compression uses whatever base-interval data is visible, and replay makes only the bars up to the playback position visible. So the current daily bar is genuinely partial — exactly as it is during a live session. That is why the daily high line should ratchet upward through the day, and why a filter that looks fine in a daily backtest can behave quite differently intraday.

Question 4. Which of these are properties of your code that one replayed session can legitimately establish? Select all that apply.
Show the answer and why

Answer: How many alert lines one breakout produces with a given set of flags, Whether the session filter admits bars outside your intended hours, Whether the higher-timeframe panel reads bars it should not be able to see

The first, second and fourth are deterministic behaviours of software: run it, observe it, and it will do the same thing again. Whether a setup is worth trading is a claim about a distribution of outcomes net of costs across many instruments and periods, and no number of observations from one session you selected can address it.

Question 5. You add the completed-bar guard and set lookback to 1, and no alerts appear at all. What is the most reasonable interpretation of that observation?
Show the answer and why

Answer: The guard excludes the most recent bar, and a lookback of 1 examines only that bar, so there is nothing left for the alert to fire from

Two documented facts meet here: AlertIf considers only the lookback most recent bars, defaulting to 1, and BarComplete is false on the last bar by construction. Silence is what that combination predicts. The competing reading — that stepping onto bar T+1 makes bar T complete and the alert fires with one bar of delay — predicts lines instead, and that is the reading Part 25 offers as something to check. The documentation settles neither, which is why the phase is run rather than reasoned about; a lookback of 2 produces output under either reading.

Sources for this lesson

9 verified · checked 2026-08-31

  1. 01AmiBroker User's Guide — Bar Replay windowamibroker.com/guide/w_barreplay.html2026-08-31
  2. 02AmiBroker User's Guide — Using formula-based alertsamibroker.com/guide/h_alerts.html2026-08-31
  3. 03AmiBroker AFL Function Reference — AlertIfamibroker.com/guide/afl/alertif.html2026-08-31
  4. 04AmiBroker AFL Function Reference — GetPlaybackDateTimeamibroker.com/guide/afl/getplaybackdatetime.html2026-08-31
  5. 05AmiBroker AFL Function Reference — TimeFrameSetamibroker.com/guide/afl/timeframeset.html2026-08-31
  6. 06AmiBroker AFL Function Reference — TimeFrameExpandamibroker.com/guide/afl/timeframeexpand.html2026-08-31
  7. 07AmiBroker AFL Function Reference — TimeNumamibroker.com/guide/afl/timenum.html2026-08-31
  8. 08AmiBroker User's Guide — About AmiBroker Editionsamibroker.com/guide/versions.html2026-08-31
  9. 09AmiBroker User's Guide — Database Settings§ Intraday Settingsamibroker.com/guide/w_dbsettings.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.