Component 5: Real-Time or Replay Workspace
What this component is actually for
Section titled “What this component is actually for”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
- Condition becomes true on a completed barComputed by the formula, on data that has finished arriving.
- AlertAlertIf writes to the Alert Output window, and optionally makes a sound or sends an email.
- Human reviewYou look at the chart, the context, the liquidity, and whatever is happening that the formula cannot see.
- Human decision, written downTake it, skip it, or wait — and the reason. This is the deliverable.
- (Not part of this course) OrderPlaced by you, through your broker, on your own judgement.
Two paths, one decision logic
Section titled “Two paths, one decision logic”| 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 |
The Level A formula
Section titled “The Level A formula”Complete runnable 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. alertAlertText = 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 chartPlot( 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 titleContextWord = 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();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.
The replay label
Section titled “The replay label”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.
The alert arguments that matter
Section titled “The alert arguments that matter”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.
The Level B/C formula
Section titled “The Level B/C formula”Complete runnable 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 replayPriorHigh = 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. alertPlayback = 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. chartPlot( 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. titleQuoteText = 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();Identical decision logic, plus two things a live feed makes possible.
Timed self-refresh
Section titled “Timed self-refresh”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.
The quote panel
Section titled “The quote panel”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.
Layout
Section titled “Layout”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.
Expected result
Section titled “Expected result”Validation
Section titled “Validation”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.
Common errors
Section titled “Common errors”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.
The decision log
Section titled “The decision log”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.
Extensions
Section titled “Extensions”-
Log automatically. Add the
fopenjournal 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. -
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.
-
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.
-
Replay the same period twice, a week apart, and compare your two logs. Where they disagree, your rules were not doing the deciding.
What to record for the report
Section titled “What to record for the report”- 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
Sources for this lesson
7 verified · checked 2026-08-31
- 01AFL Function Reference — GetRTData§ available ONLY in PROFESSIONAL editionamibroker.com/guide/afl/getrtdata.html2026-08-31
- 02AFL Function Reference — GetPlaybackDateTimeamibroker.com/guide/afl/getplaybackdatetime.html2026-08-31
- 03AFL Function Reference — RequestTimedRefreshamibroker.com/guide/afl/requesttimedrefresh.html2026-08-31
- 04AFL Function Reference — AlertIfamibroker.com/guide/afl/alertif.html2026-08-31
- 05AmiBroker User's Guide — Bar Replay windowamibroker.com/guide/w_barreplay.html2026-08-31
- 06AmiBroker User's Guide — How to get quotes from various markets§ Real-time data tableamibroker.com/guide/h_quotes.html2026-08-31
- 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.