Skip to content
Level 5 · Real-Time AmiBroker UserProjectPart Capstone · page 6 of 955 min
55Minutes
29AFL functions
7Sources
StandardRequires
AFL functions taught here29

Component 5: Real-Time or Replay Workspace

Not the chart. The decision log.

Every other component produces something a computer made. This one produces a record of twenty decisions you made, each one written down before you knew the outcome. That record is the only evidence in the whole capstone about the part of the process that is you.

Where the chain stops

  1. Condition becomes true on a completed barComputed by the formula, on data that has finished arriving.
  2. AlertAlertIf writes to the Alert Output window, and optionally makes a sound or sends an email.
  3. Human reviewYou look at the chart, the context, the liquidity, and whatever is happening that the formula cannot see.
  4. Human decision, written downTake it, skip it, or wait — and the reason. This is the deliverable.
  5. (Not part of this course) OrderPlaced by you, through your broker, on your own judgement.
Level A — Bar Replay Level B/C — live feed
Edition Standard or Professional Professional required
Data Your existing historical bars A real-time feed
Next bar comes from Bar Replay, at your pace The market, at its pace
Decision logic Identical Identical
Extra features You can pause and think GetRTData() quote panel, timed self-refresh
Cost None A subscription

Complete runnable AFL

replay-workspace.afl
// replay-workspace.afl
// Capstone Component 5 - the Level A path (no data subscription required)
//
// GOAL
// The same decision workspace as the real-time version, driven by Bar Replay
// instead of by a live feed. Everything about the DECISION is identical; only
// the source of the next bar changes.
//
// This is the path the course guarantees: it needs no subscription, no
// Professional edition and no live feed. Real-time data requires the
// Professional edition, and this course is completable without it.
//
// WHAT IT SHOWS
// The trigger level, the context state, the distance to the level, whether
// the newest bar is complete, and a running count of decision points. It
// raises an alert on a qualifying bar and stops there.
//
// ALERT -> HUMAN REVIEW -> DECISION. Nothing in this course places an order.
//
// HOW TO RUN
// 1. Apply this to a chart pane at your trading interval.
// 2. Tools -> Bar Replay. Set the replay to your trading interval and step
// forward one bar at a time, or run it at a slow speed.
// 3. At each step, before advancing, write your decision in the log described
// in the lesson. The point of the exercise is the record, not the chart.
//
// ASSUMPTIONS
// Interval any. On intraday intervals the session filter does real work.
// Bar stamps TimeNum() returns the START or the END of the interval
// depending on Tools -> Preferences -> Intraday. Confirm which
// convention your database uses before trusting the session gate.
// Alerts appear only if "custom indicators" is ticked under
// Tools -> Preferences -> Alerts, "Enable alerts from".
// Not modelled costs, fills, size, and everything after the decision.
_SECTION_BEGIN( "Capstone replay workspace" );
SetChartOptions( 2, chartWrapTitle );
TriggerPeriod = Param( "Trigger look-back (bars)", 20, 2, 400, 1 );
AtrPeriod = Param( "ATR period", 14, 1, 100, 1 );
BufferAtr = Param( "Trigger buffer (x ATR)", 0.10, 0, 2, 0.05 );
TrendPeriod = Param( "Trend average (bars)", 50, 2, 400, 1 );
SessionStart = Param( "Session start (HHMMSS)", 0, 0, 235959, 100 );
SessionEnd = Param( "Session end (HHMMSS)", 235959, 0, 235959, 100 );
UseSession = ParamToggle( "Session filter", "Off|On", 0 );
AlertsOn = ParamToggle( "Alerts", "Off|On", 1 );
AlertLookback = Param( "Alert lookback (bars)", 2, 1, 20, 1 );
// ----------------------------------------------------------- 1. the level
// Shifted back one bar, so the current bar is never part of the level it is
// being measured against.
PriorHigh = Ref( HHV( High, TriggerPeriod ), -1 );
Level = PriorHigh + BufferAtr * ATR( AtrPeriod );
HaveLevel = NOT IsNull( Level );
TrendMa = MA( Close, TrendPeriod );
ContextUp = Close > TrendMa;
Distance = SafeDivide( Level - Close, ATR( AtrPeriod ), Null );
// --------------------------------------------------------- 2. the event
// Cross() fires on the one bar where High first exceeded the level. The state
// "High > Level" stays true while price remains up there, which is the
// difference between one decision point and forty.
RawBreak = HaveLevel AND Cross( High, Level );
InSession = NOT UseSession
OR ( TimeNum() >= SessionStart AND TimeNum() <= SessionEnd );
// The newest bar is still being built by Bar Replay: its high can still grow.
// Only completed bars are decision points.
BarComplete = BarIndex() < LastValue( BarIndex() );
NewSession = Day() != Ref( Day(), -1 );
FirstBreak = ExRem( RawBreak AND InSession, NewSession );
Trigger = FirstBreak AND BarComplete;
// -------------------------------------------------------- 3. replay state
// GetPlaybackDateTime() returns the playback position, or zero when Bar Replay
// is not running. That is what lets a practice run be labelled as one and kept
// out of a real decision log.
Playback = GetPlaybackDateTime();
InReplay = Playback > 0;
ModeText = WriteIf( InReplay, "[REPLAY] ", "[LIVE OR STATIC] " );
// ------------------------------------------------------------- 4. alert
AlertText = ModeText + "DECISION POINT " + Name()
+ " " + Interval( 2 )
+ " bar " + DateTimeToStr( LastValue( DateTime() ) )
+ " high " + NumToStr( LastValue( High ), 1.4 )
+ " level " + NumToStr( LastValue( Level ), 1.4 );
// Lookback of 2 puts the most recent COMPLETED bar inside the window that
// AlertIf examines; the default of 1 is exactly the bar BarComplete excludes.
// Flag bit 8 - do not repeat alerts with the same date/time - is what keeps one
// completed-bar signal from being re-reported on every refresh.
if( AlertsOn )
{
AlertIf( Trigger, "", AlertText, 1, 1 + 2 + 4 + 8, AlertLookback );
}
// ---------------------------------------------------------- 5. the chart
Plot( Close, "Close", colorDefault, styleCandle );
Plot( TrendMa, "Trend average", colorBlue, styleLine );
Plot( Level, "Trigger level", colorOrange, styleLine | styleStaircase );
PlotShapes( Trigger * shapeUpArrow, colorGreen, 0, Low, -20 );
RibbonTint = IIf( IsNull( TrendMa ), colorLightGrey,
IIf( ContextUp, colorPaleGreen, colorRose ) );
Plot( 1, "Context", RibbonTint, styleArea | styleOwnScale | styleNoLabel, 0, 100 );
// ---------------------------------------------------------- 6. the title
ContextWord = WriteIf( IsNull( TrendMa ), "no trend yet",
WriteIf( ContextUp, "above trend average",
"below trend average" ) );
_N( Title = StrFormat(
"%s%s %s\n"
+ "Context: %s Trigger level %g distance to level %g ATR\n"
+ "Newest bar complete: %s raw breaks %g decision points %g\n"
+ "This raises an alert. It does not place an order. "
+ "Alert -> your review -> your decision.",
ModeText, Name(), Interval( 2 ),
ContextWord, SelectedValue( Level ), SelectedValue( Distance ),
WriteIf( BarComplete, "yes", "no - still forming" ),
LastValue( Cum( RawBreak ) ), LastValue( Cum( Trigger ) ) ) );
_SECTION_END();

Download replay-workspace.afl130 lines

The four guards that turn a condition into one decision point

Section titled “The four guards that turn a condition into one decision point”

The level is shifted. Ref( HHV( High, TriggerPeriod ), -1 ) — the current bar is never part of the level it is measured against.

Cross(), not >. The state “High > Level” stays true while price remains up there. The event fires once. This is the difference between one decision point and forty.

Completed bars only.

Fragment — not a complete formula

BarComplete = BarIndex() < LastValue( BarIndex() );
Trigger = FirstBreak AND BarComplete;

The newest bar is still being built — by the feed, or by Bar Replay. Its high can still grow. Alerting on a forming bar is how one breakout becomes six alerts, each one a slightly different price.

One per session. ExRem( RawBreak AND InSession, NewSession ) removes every break after the first until the calendar day changes.

Fragment — not a complete formula

Playback = GetPlaybackDateTime();
InReplay = Playback > 0;

GetPlaybackDateTime() returns the playback position, or zero when Bar Replay is not running. That single fact is what lets a practice session be labelled as one and kept out of a real decision log — and it is why the title and every alert carry a [REPLAY] prefix during practice.

Fragment — not a complete formula

AlertIf( Trigger, "", AlertText, 1, 1 + 2 + 4 + 8, AlertLookback );

Two of the six arguments are the ones people leave alone and should not.

AlertLookback = 2. AlertIf examines only the most recent lookback bars, and the default of 1 is exactly the bar BarComplete excludes — so with the default, nothing would ever fire. Two puts the most recent completed bar inside the window.

Flag bit 8 — do not display repeated alerts having the same date/time. Without it, one completed-bar signal is re-reported on every timed refresh for the whole life of the next bar. The AFL decides which bar may alert; bit 8 decides that it is said once.

Complete runnable AFL

realtime-workspace.afl
// realtime-workspace.afl
// Capstone Component 5 - the Level B/C path (live feed)
//
// ###################################################################
// # REAL-TIME DATA REQUIRES THE AMIBROKER PROFESSIONAL EDITION. #
// # If you are running the Standard edition, or have no feed, use #
// # replay-workspace.afl instead. It is not a lesser version: the #
// # DECISION logic is identical and only the source of the next #
// # bar differs. #
// ###################################################################
//
// GOAL
// The same workspace as the replay version, plus the two things a live feed
// adds: a self-refresh timer so the pane re-executes without a new tick, and
// a real-time quote panel that shows what the feed currently believes.
//
// ALERT -> HUMAN REVIEW -> DECISION. Nothing here places an order, and no
// part of this course automates live trading.
//
// ASSUMPTIONS
// Edition Professional, for the real-time feed.
// Interval an intraday interval, with the session window set to match
// the instrument's actual trading hours.
// Bar stamps TimeNum() returns the START or the END of the interval
// depending on Tools -> Preferences -> Intraday. Confirm which
// convention your database uses.
// Feed fields GetRTData() reads the CURRENT real-time record for the
// current symbol. Which fields a given plug-in actually
// populates varies by vendor: an unsupported field returns
// zero or empty rather than raising an error, so a zero here
// means "not supplied", not "the market is at zero".
// Alerts require "custom indicators" ticked under Tools ->
// Preferences -> Alerts, "Enable alerts from".
// Not modelled costs, fills, size, and everything after the decision.
_SECTION_BEGIN( "Capstone real-time workspace" );
SetChartOptions( 2, chartWrapTitle );
TriggerPeriod = Param( "Trigger look-back (bars)", 20, 2, 400, 1 );
AtrPeriod = Param( "ATR period", 14, 1, 100, 1 );
BufferAtr = Param( "Trigger buffer (x ATR)", 0.10, 0, 2, 0.05 );
TrendPeriod = Param( "Trend average (bars)", 50, 2, 400, 1 );
SessionStart = Param( "Session start (HHMMSS)", 93000, 0, 235959, 100 );
SessionEnd = Param( "Session end (HHMMSS)", 155900, 0, 235959, 100 );
UseSession = ParamToggle( "Session filter", "Off|On", 1 );
AlertsOn = ParamToggle( "Alerts", "Off|On", 1 );
AlertLookback = Param( "Alert lookback (bars)", 2, 1, 20, 1 );
RefreshSecs = Param( "Self-refresh (seconds)", 5, 0, 300, 1 );
// RequestTimedRefresh() asks this pane to re-execute on a timer, and it works
// with or without a data plug-in. That is what makes the formula testable with
// no subscription: the same file drives a Bar Replay session unchanged.
if( RefreshSecs > 0 )
{
RequestTimedRefresh( RefreshSecs );
}
// ------------------------------- 1. identical decision logic to the replay
PriorHigh = Ref( HHV( High, TriggerPeriod ), -1 );
Level = PriorHigh + BufferAtr * ATR( AtrPeriod );
HaveLevel = NOT IsNull( Level );
TrendMa = MA( Close, TrendPeriod );
ContextUp = Close > TrendMa;
Distance = SafeDivide( Level - Close, ATR( AtrPeriod ), Null );
RawBreak = HaveLevel AND Cross( High, Level );
InSession = NOT UseSession
OR ( TimeNum() >= SessionStart AND TimeNum() <= SessionEnd );
BarComplete = BarIndex() < LastValue( BarIndex() );
NewSession = Day() != Ref( Day(), -1 );
FirstBreak = ExRem( RawBreak AND InSession, NewSession );
Trigger = FirstBreak AND BarComplete;
// ---------------------------------------------------- 2. the live quote
// GetRTData reads the current real-time record. Fields are vendor-dependent, so
// each one is reported with an explicit "not supplied" state rather than being
// silently displayed as zero.
RtLast = GetRTData( "Last" );
RtBid = GetRTData( "Bid" );
RtAsk = GetRTData( "Ask" );
HaveQuote = RtLast > 0;
HaveBook = RtBid > 0 AND RtAsk > 0;
SpreadPct = IIf( HaveBook, 100 * SafeDivide( RtAsk - RtBid, RtAsk, Null ), Null );
// -------------------------------------------------------------- 3. alert
Playback = GetPlaybackDateTime();
ModeText = WriteIf( Playback > 0, "[REPLAY] ", "[LIVE] " );
AlertText = ModeText + "DECISION POINT " + Name()
+ " " + Interval( 2 )
+ " bar " + DateTimeToStr( LastValue( DateTime() ) )
+ " high " + NumToStr( LastValue( High ), 1.4 )
+ " level " + NumToStr( LastValue( Level ), 1.4 );
if( AlertsOn )
{
AlertIf( Trigger, "", AlertText, 1, 1 + 2 + 4 + 8, AlertLookback );
}
// -------------------------------------------------------------- 4. chart
Plot( Close, "Close", colorDefault, styleCandle );
Plot( TrendMa, "Trend average", colorBlue, styleLine );
Plot( Level, "Trigger level", colorOrange, styleLine | styleStaircase );
PlotShapes( Trigger * shapeUpArrow, colorGreen, 0, Low, -20 );
RibbonTint = IIf( IsNull( TrendMa ), colorLightGrey,
IIf( ContextUp, colorPaleGreen, colorRose ) );
Plot( 1, "Context", RibbonTint, styleArea | styleOwnScale | styleNoLabel, 0, 100 );
// -------------------------------------------------------------- 5. title
QuoteText = WriteIf( HaveQuote,
"last " + NumToStr( RtLast, 1.4 ),
"no real-time last price supplied by this plug-in" );
BookText = WriteIf( HaveBook,
" bid " + NumToStr( RtBid, 1.4 )
+ " ask " + NumToStr( RtAsk, 1.4 )
+ " spread " + NumToStr( SpreadPct, 1.3 ) + "%",
" (no bid/ask supplied)" );
ContextWord = WriteIf( IsNull( TrendMa ), "no trend yet",
WriteIf( ContextUp, "above trend average",
"below trend average" ) );
_N( Title = StrFormat(
"%s%s %s refresh every %g s\n"
+ "Feed: %s%s\n"
+ "Context: %s Trigger level %g distance %g ATR "
+ "newest bar complete: %s\n"
+ "Raw breaks %g decision points %g\n"
+ "This raises an alert. It does not place an order.",
ModeText, Name(), Interval( 2 ), RefreshSecs,
QuoteText, BookText,
ContextWord, SelectedValue( Level ), SelectedValue( Distance ),
WriteIf( BarComplete, "yes", "no - still forming" ),
LastValue( Cum( RawBreak ) ), LastValue( Cum( Trigger ) ) ) );
_SECTION_END();

Download realtime-workspace.afl142 lines

Identical decision logic, plus two things a live feed makes possible.

Fragment — not a complete formula

if( RefreshSecs > 0 )
{
RequestTimedRefresh( RefreshSecs );
}

Asks the pane to re-execute on a timer rather than only when a new tick arrives. It works with or without a data plug-in, which is what makes the same file drive a Bar Replay session unchanged.

Fragment — not a complete formula

RtLast = GetRTData( "Last" );
RtBid = GetRTData( "Bid" );
RtAsk = GetRTData( "Ask" );

GetRTData retrieves the most recent value of a documented set of fields — Last, Bid, Ask, BidSize, AskSize, High, Low, Open, Prev, TotalVolume, Change, and others.

The spread percentage is worth having on screen for a reason that is not obvious: it is the cost you are about to pay, live, and it is much larger at some moments than the constant you put in your backtest. Watching it move is the fastest way to develop an intuition for why Part 30’s cost lesson insists that a constant slippage assumption is a simplification in a known direction.

The workspace is more than one pane. A workable arrangement:

Pane Contents
Top, large This formula: price, trend average, trigger level, context ribbon
Below The multi-timeframe chart on the same symbol
Side The Alert Output window (Window → Alert output)
Side Your decision log — a text file or spreadsheet, open and in front of you

The last row is not optional. A log you have to go and open is a log you will not write.

Confirm the latch works. Find a session in your replay data where price broke the level several times. The raw-break count should rise several times; the decision-point count should rise once.

Confirm the completed-bar guard works. Step Bar Replay to the middle of a bar that is going to trigger. No alert. Step past it. One alert.

Confirm the replay label. Run the formula with Bar Replay stopped. The title must say [LIVE OR STATIC] — or [LIVE] in the real-time version. Start replay; it must change. This is what keeps practice alerts out of a real log.

Confirm the alert lookback. Set AlertLookback to 1 and step through a trigger. Nothing should fire, because the only bar in the window is the forming one that BarComplete excludes. Set it back to 2. Seeing this fail deliberately is what makes the argument memorable.

Confirm your preferences. If nothing ever appears in the Alert Output window, check Tools → Preferences → Alerts before touching the formula.

Six alerts for one breakout. BarComplete is missing, or flag bit 8 is not set, or both. The first causes alerts on a growing bar; the second re-reports one alert on every refresh.

No alerts ever. Three candidates, in order of likelihood: alerts are not enabled in Preferences; AlertLookback is 1; the trigger condition never becomes true because the level is unshifted.

The session filter blocks everything. TimeNum() returns the start or the end time of the interval depending on Tools → Preferences → Intraday. Confirm which convention your database uses before assuming the window is wrong. On daily bars the filter is harmless either way.

The chart re-executes constantly and traces flood the log. RequestTimedRefresh plus trace lines. Remove the traces once the formula behaves.

GetRTData returns zero for everything. Standard edition, or no real-time plug-in, or the field is not supplied by that vendor. All three are documented; none is a formula bug.

The decision log has three entries. This is the most common failure of this component and it is not technical. See below.

Twenty entries. That is the deliverable, and it is the part that requires discipline rather than skill.

Each entry, written before you advance the replay or before the next bar closes:

Field Example
Date/time of the bar 2024-03-14
Symbol
What the alert said Break of 47.20, close 47.35
Context state Above trend average; benchmark regime open
Your decision Skip
Your reason, in one sentence Spread was 0.8%, which is most of the first ATR of the move
What you would have needed to change your mind A tighter spread, or a bigger gap above the level

Then, later, the outcome — added afterwards and kept in a separate column, so that reading the log does not let you retro-fit the reasons.

  1. Log automatically. Add the fopen journal technique from Part 12 so every alert appends a row with the bar, the level and the context state. You still write the decision and the reason by hand — that is the part that cannot be automated.

  2. Add the spread to the alert text on the Level B/C path, so the log records the cost that was live at the moment of the decision.

  3. Add a second symbol pane and find out how differently you behave when two candidates compete. That is the discretionary version of the portfolio constraint from Part 28.

  4. Replay the same period twice, a week apart, and compare your two logs. Where they disagree, your rules were not doing the deciding.

  • Which path you used, and why.
  • The decision log, twenty entries minimum.
  • The paragraph comparing what you did with what your rules say.
  • Any decision reason that appeared repeatedly and is not in your Component 6 specification — because either it belongs there, or it is noise you should stop acting on.

Check your understanding

Question 1. Why is AlertLookback set to 2 rather than left at its default of 1?
BarComplete = BarIndex() < LastValue( BarIndex() );
Trigger     = FirstBreak AND BarComplete;
AlertIf( Trigger, "", AlertText, 1, 1 + 2 + 4 + 8, 2 );
Show the answer and why

Answer: Because AlertIf examines only the most recent lookback bars, and the default of 1 is exactly the forming bar that BarComplete excludes — so nothing would ever fire

The two guards interact. BarComplete deliberately refuses the newest bar; a lookback of 1 examines only the newest bar. Together they guarantee silence, which is a bug that looks exactly like a formula that is not triggering.

Question 2. GetRTData( "Bid" ) returns zero. Which explanations are documented? Select all that apply.
Show the answer and why

Answer: You are running the Standard edition, where every field is Null, The data source is not a real-time plug-in, The vendor does not supply that field, so it comes back as zero or empty rather than raising an error

All three are in the reference, which also says to check the real-time quote window to see which fields a source supplies. This is why the formula reports an explicit "not supplied" state instead of printing the number — a zero here is an absence, not a price.

Question 3. What does GetPlaybackDateTime() return when Bar Replay is not running, and why does that matter here?
Show the answer and why

Answer: Zero — which is what lets a practice session be labelled [REPLAY] and kept out of a real decision log

A zero return is an unambiguous "replay is not active", so a simple > 0 test distinguishes practice from live. Mixing replayed alerts into a real log would corrupt exactly the record this component exists to produce.

Question 4. Which differences between the Level A and Level B/C formulas are real? Select all that apply.
Show the answer and why

Answer: The live version adds a GetRTData quote panel, The live version uses RequestTimedRefresh so the pane re-executes on a timer, The live version requires the Professional edition

The decision logic is identical by design — that is what makes the replay path a genuine alternative rather than an approximation. Note that RequestTimedRefresh itself works with or without a plug-in; it is listed here because it is only useful when new data can arrive.

Question 5. What is the actual deliverable of this component?
Show the answer and why

Answer: A decision log of at least twenty entries, each written before the outcome was known, plus a paragraph comparing what you did with what your rules say

Every other component produces something a computer made. This one produces evidence about the discretionary layer sitting on top of your rules — and that layer appears in no backtest, which is precisely why it has to be measured some other way.

Sources for this lesson

7 verified · checked 2026-08-31

  1. 01AFL Function Reference — GetRTData§ available ONLY in PROFESSIONAL editionamibroker.com/guide/afl/getrtdata.html2026-08-31
  2. 02AFL Function Reference — GetPlaybackDateTimeamibroker.com/guide/afl/getplaybackdatetime.html2026-08-31
  3. 03AFL Function Reference — RequestTimedRefreshamibroker.com/guide/afl/requesttimedrefresh.html2026-08-31
  4. 04AFL Function Reference — AlertIfamibroker.com/guide/afl/alertif.html2026-08-31
  5. 05AmiBroker User's Guide — Bar Replay windowamibroker.com/guide/w_barreplay.html2026-08-31
  6. 06AmiBroker User's Guide — How to get quotes from various markets§ Real-time data tableamibroker.com/guide/h_quotes.html2026-08-31
  7. 07AmiBroker User's Guide — 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.