Skip to content
Level 3 · AFL DeveloperLessonPart 10 · page 2 of 826 min
26Minutes
11AFL functions
9Sources
StandardRequires
AFL functions taught here11

Chart Titles and Dynamic Text

The strip of text at the top of a chart pane is the highest-value real estate in AmiBroker. It is always visible, it updates as you move the selection line, and it is the only place a formula can put a number that is too precise to read off an axis. Most people leave it as AmiBroker generated it. That is a waste.

By the end of this lesson you will be able to replace it with a title that reports exactly the numbers your analysis depends on, colour-coded, at the bar you are pointing at, without it turning into an unreadable wall of text on a laptop screen.

There is no Title() function in AFL. Title is a reserved variable: you assign a string to it and AmiBroker draws that string instead of the automatically generated one.

Fragment — not a complete formula

Title = "Hello from the title bar";

The word “instead” matters. As soon as you assign Title, the automatic “plot name: value, plot name: value” listing disappears. Any plot value you still want has to be put back by hand — which is exactly why styleNoTitle exists, so you can suppress the automatic entries on plots you have already covered yourself.

Because Title is a reserved variable rather than a function, it behaves like any other assignment. You can build it in stages, branch on a parameter, or assemble it from several named pieces, and most readable title code does exactly that rather than writing one enormous concatenation.

AmiBroker’s own example formulas write the assignment like this:

Fragment — not a complete formula

_N( Title = "Symbol: " + Name() );

_N() means “no text output”. In a Commentary or Interpretation context, a bare string expression at global level gets printed to the output window; _N() stops the title text being dumped there as a side effect. It costs four characters and prevents a confusing symptom, so make it a habit.

Three functions do the conversion, and they are not interchangeable.

Fragment — not a complete formula

NumToStr( NUMBER, format = 1.3, separator = True, roundAndPad = False )

The format argument is a single number that encodes two things. Its integer part is the minimum field width, space-padded; its fractional part is the number of decimal places. So 1.2 gives two decimals with no padding, 1.0 gives no decimals at all, and 8.4 gives four decimals right-aligned in an eight-character field — which is how you get columns of numbers to line up.

separator controls the thousands separator, on by default and configurable under Tools → Preferences → Misc. There is also the special format constant formatDateTime, which converts the value returned by DateTime() using the user’s Windows regional settings, and formatDateTimeISO for the international YYYY-MM-DD HH:MM:SS form.

WriteVal() — the old name for the same thing

Section titled “WriteVal() — the old name for the same thing”

WriteVal() has the identical signature and the identical behaviour. The official page says plainly that it is obsolete and that NumToStr() is preferred in new code; the name survives only because people migrating from MetaStock expected it. You will meet it constantly in formulas found online, so recognise it — and do not write it.

Both functions share a behaviour that surprises people: given an array, they return one value, not a series of them. In an indicator that value is the selected one, the bar under the vertical selection line. Everywhere else it is effectively the last value.

Fragment — not a complete formula

WriteIf( EXPRESSION, "TRUE TEXT", "FALSE TEXT" )

WriteIf() does not write anything either. It returns one of two strings depending on the selected value of the condition. Think of it as IIf() for text. It nests, so a three-way classification reads reasonably well:

Fragment — not a complete formula

Word = WriteIf( Regime == 1, "UP",
WriteIf( Regime == -1, "DOWN", "RANGE" ) );

StrFormat() — when you have several numbers

Section titled “StrFormat() — when you have several numbers”

StrFormat( formatstr, ... ) is printf that returns a string instead of printing it, and it is by far the tidiest way to assemble a line with several values in it. The rules that matter:

  • Use %f, %e or %g for numbers. %d and %x do not work, because AFL has no integer type. %g is the usual choice: it drops trailing zeros.
  • %s for strings, supported from AmiBroker 6.20 onwards.
  • A literal percent sign is %%. Since version 6.10 AmiBroker checks that the number of specifiers matches the number of arguments and reports “Error 61. The number of % formatting specifier(s) does not match the number of arguments passed” when it does not — which is almost always a single % that should have been %%.
  • Given an array, StrFormat() uses its selected value, exactly like NumToStr().

A title is a single line of text, but every array in your formula has one value per bar. Some function has to choose, and the choice is SelectedValue().

Fragment — not a complete formula

Reading = SelectedValue( RSI( 14 ) );

SelectedValue() returns a number, which is why it is safe inside if() where an array condition would not be. Its meaning, however, depends on where the formula is running:

  • In an Indicator, Commentary or Interpretation, it is the bar marked by the chart’s vertical selection line — so the title follows your mouse.
  • In an Analysis window there is no selection line, so it means the last bar of the selected analysis range. With the range set to “all quotes” that is the final bar of the data.

Its siblings are LastValue() (always the final bar), BeginValue() and EndValue() (the ends of a From–To range). For a chart title you want SelectedValue(); for a fixed summary line you want LastValue().

EncodeColor( colorIndex ) returns a string — the escape sequence that switches text colour from that point in the title onwards. It is concatenated with +; it is never passed to Plot() as a colour.

Fragment — not a complete formula

Line = EncodeColor( colorGreen ) + "up" + EncodeColor( colorDefault ) + " today";

The colour applies to the rest of the string until the next EncodeColor(). The underlying escape is \cXX with a two-digit colour index, and \c-1 resets to the default axis colour, but the official guide describes writing those by hand as hard to read and recommends EncodeColor() instead. Take the advice.

\n embeds a line break. A long title is clipped rather than wrapped unless you turn wrapping on:

Fragment — not a complete formula

SetChartOptions( 2, chartWrapTitle );

Mode 2 means “set this flag on the existing chart”. Mode 0 only applies defaults the first time a chart is inserted into a pane, which is why editing a mode-0 call in a chart you already have open appears to do nothing at all.

A title is read at a glance while you are looking at something else. Three habits help.

Ration the fields. Three or four numbers per line is the limit before the eye stops parsing and starts scanning. If you find yourself wanting eight, you want a table, and Part 12 will give you Explorations for that.

Give the reader a switch. A ParamToggle that collapses the title to one short line is two lines of code, and it is the difference between a formula that works on a 13-inch laptop and one that does not. Titles do not reflow: on a narrow pane a three-line title eats the chart.

Put the units in the text, not in the reader’s head. “ATR(14) 1.83% of price” is unambiguous; “ATR 1.83” is not, because the reader cannot tell whether that is points, percent, or something you normalised earlier.

A replacement title bar that reports, for the bar under the selection line: the symbol, the bar interval, the date and time, the four price fields, the percentage change from the previous close in green or red, average true range as a percentage of price, and whether the close sits above or below a long moving average. Plus a compact mode for small screens.

Complete runnable AFL

chart-title.afl
/*
* An informative chart title - worked example for Part 10.
*
* Assumptions
* - Chart pane only. Title is an Indicator-context reserved variable; it has
* no effect in a Scan, an Exploration or a Backtest.
* - Any interval. The date line switches itself between date-only and
* date-and-time because DateTimeToStr mode 3 omits the time part on
* end-of-day records.
* - Every number reported is taken at the SELECTED bar - the bar under the
* vertical selection line - which is not necessarily the last bar.
*/
_SECTION_BEGIN( "Informative title" );
AtrPeriod = Param( "ATR periods", 14, 2, 200, 1 );
TrendSpan = Param( "Trend reference periods", 200, 20, 500, 5 );
Compact = ParamToggle( "Compact (small screens)", "No|Yes", 0 );
// styleNoTitle keeps the price plot's own value out of the title, because this
// formula writes the whole title itself.
Plot( Close, "Price", colorDefault, styleCandle | styleNoTitle );
// Mode 2 sets a flag on an existing pane. Mode 0 would only apply the first
// time the chart was inserted, which is why editing a mode-0 call looks broken.
SetChartOptions( 2, chartWrapTitle );
ChangePercent = 100 * ( Close - Ref( Close, -1 ) ) / Ref( Close, -1 );
AtrPercent = 100 * ATR( AtrPeriod ) / Close;
// SelectedValue returns a single NUMBER, so it is safe inside if().
// An array condition would need IIf() instead.
SelectedChange = SelectedValue( ChangePercent );
if( SelectedChange >= 0 ) ChangeTint = colorGreen; else ChangeTint = colorRed;
// EncodeColor returns a STRING. It is concatenated into the title with "+",
// never passed to Plot() as a colour.
HeadLine = EncodeColor( colorDefault )
+ Name() + " " + Interval( 2 ) + " "
+ DateTimeToStr( SelectedValue( DateTime() ), 3 );
// StrFormat takes %f, %e and %g for numbers and %s for strings. %d does not
// work, because AFL has no integers. A literal percent sign must be written %%.
PriceLine = StrFormat( "O %g H %g L %g C %g",
SelectedValue( Open ), SelectedValue( High ),
SelectedValue( Low ), SelectedValue( Close ) );
ChangeLine = EncodeColor( ChangeTint )
+ NumToStr( SelectedChange, 1.2 ) + "%"
+ EncodeColor( colorDefault );
VolatilityLine = "ATR(" + NumToStr( AtrPeriod, 1.0 ) + ") "
+ NumToStr( SelectedValue( AtrPercent ), 1.2 ) + "% of price";
// WriteIf returns a string chosen by the SELECTED value of the condition,
// so it reads as one word rather than as an array.
TrendLine = "Close is "
+ WriteIf( Close > MA( Close, TrendSpan ), "above", "below" )
+ " the " + NumToStr( TrendSpan, 1.0 ) + "-bar average";
// _N() stops the assembled string from also being echoed into the Commentary
// or Interpretation window.
if( Compact )
_N( Title = HeadLine + "\n"
+ "C " + NumToStr( SelectedValue( Close ), 1.2 ) + " " + ChangeLine );
else
_N( Title = HeadLine + "\n"
+ PriceLine + " " + ChangeLine + "\n"
+ VolatilityLine + " | " + TrendLine );
_SECTION_END();

Download chart-title.afl71 lines

The price plot carries styleNoTitle, because the formula writes the whole title itself and does not want AmiBroker adding “Price: 143.20” to the end of it.

SetChartOptions( 2, chartWrapTitle ) turns on wrapping for the existing pane, so a long line degrades into two lines rather than being cut off.

The formula then samples the values it needs once, at the selected bar. SelectedChange is a number, which is what allows the ordinary if statement on the next line to choose a colour; had it been left as an array, that if would have been a type error and IIf() would have been needed instead.

The title is assembled from four named pieces rather than one long expression. HeadLine carries the symbol, the interval name from Interval( 2 ), and the timestamp. DateTimeToStr() with mode 3 gives an ISO date and appends the time only on non-end-of-day records, so the same line is correct on a daily chart and on a five-minute chart without any branching.

PriceLine uses StrFormat() with four %g specifiers, which is where that function pays for itself. ChangeLine switches to a colour, writes the number with two decimals via NumToStr(), and switches back. TrendLine uses WriteIf() to turn a comparison into a word.

The final if chooses between a two-line compact title and the full three-line version, and both are wrapped in _N().

  • Interval( 2 ) — returns the bar interval as a string such as "Daily" or "15-minute". Fine for display; the official page warns you never to compare against these strings, because they are translated in localised builds. Compare Interval() against inDaily instead.
  • DateTimeToStr( number, mode ) — mode 3 is ISO date and time, with the time part omitted on end-of-day records; mode 4 is ISO date only.
  • EncodeColor( colorIndex ) — returns a colour-escape string for concatenation.
  • _N( string ) — suppresses text output into the Commentary window.

A title of two or three lines. Moving the vertical selection line along the chart changes every number in it. The change figure is green on up bars and red on down bars. Switching the Compact parameter to “Yes” collapses it to symbol, date and one price.

  1. Click on a bar in the middle of the chart. Every number in the title should correspond to that bar, not the last one. Compare the O/H/L/C against the Data window.
  2. Switch the chart from daily to a 15-minute interval, if you have intraday data. The interval name should change and the timestamp should gain a time part.
  3. Set the ATR period to 1. The reported ATR percentage should become the current bar’s own true range as a percentage of its close — verify one bar by hand.
  4. Deliberately break it: change %g to %d in PriceLine and refresh. You should get an error rather than a silently wrong number. Put it back.
  • Error 61 about formatting specifiers. A stray single % in a StrFormat() string. Double it.
  • The title shows the last bar no matter where you click. You used LastValue() where you meant SelectedValue().
  • The plot’s own value has vanished from the title. That is correct: assigning Title replaces the automatic listing. Add the value back yourself, or remove styleNoTitle from a plot whose automatic entry you want.
  • The title text appears in the Interpretation window too. The assignment is not wrapped in _N().
  • Colours leak. EncodeColor() applies until the next one; reset with EncodeColor( colorDefault ) after a coloured field.

Add a field that reports how many bars have passed since the last time the close crossed the long moving average, using BarsSince(). Then ask yourself what that number is actually evidence of — it is a description of the recent past, and a small one, and the title should not imply otherwise.

You can now take over the chart’s title bar completely: assign the reserved Title variable, convert numbers with NumToStr() and several at once with StrFormat(), turn conditions into words with WriteIf(), colour any part of the string with EncodeColor(), and sample every value at the bar the reader has selected rather than at the end of the data.

Next: putting marks on the chart itself, where the events happened.

Check your understanding

Question 1. Why does this line fail?
Title = StrFormat( "RSI is %d, up %g%", RSI( 14 ), ROC( Close, 1 ) );
Show the answer and why

Answer: %d is not supported because AFL has no integers, and the trailing single % starts an unfinished specifier

StrFormat supports %f, %e, %g and (from 6.20) %s. A literal percent sign must be written %%. Arrays are accepted - their selected value is used.

Question 2. In a chart pane, what does SelectedValue( Close ) return?
Show the answer and why

Answer: The close of the bar under the chart’s vertical selection line

In an Indicator, Commentary or Interpretation context the selected element is the bar marked by the selection line. In an Analysis window, where there is no such line, it means the last bar of the selected range instead.

Question 3. EncodeColor() can be passed as the colour argument of Plot().
Show the answer and why

Answer: False

EncodeColor returns a STRING - the escape sequence used inside text. Plot needs a colour number or an array of them.

Question 4. Which of these are true of NumToStr and WriteVal? Select all that apply.
Show the answer and why

Answer: They have the same signature and behaviour, WriteVal is documented as obsolete, Given an array they return one value, not a series

They are synonyms; the official page marks WriteVal obsolete and prefers NumToStr in new code; both return a single value from an array. Neither writes anything - printf does that.

Sources for this lesson

9 verified · checked 2026-08-31

  1. 01AmiBroker User's Guide — Creating your own indicators, part 2 (styles, colours, titles)amibroker.com/guide/h_indbuilder2.html2026-08-31
  2. 02AmiBroker User's Guide — AFL language reference (reserved variables)amibroker.com/guide/a_language.html2026-08-31
  3. 03AFL Function Reference — StrFormatamibroker.com/guide/afl/strformat.html2026-08-31
  4. 04AFL Function Reference — NumToStramibroker.com/guide/afl/numtostr.html2026-08-31
  5. 05AFL Function Reference — WriteValamibroker.com/guide/afl/writeval.html2026-08-31
  6. 06AFL Function Reference — WriteIfamibroker.com/guide/afl/writeif.html2026-08-31
  7. 07AFL Function Reference — SelectedValueamibroker.com/guide/afl/selectedvalue.html2026-08-31
  8. 08AFL Function Reference — EncodeColoramibroker.com/guide/afl/encodecolor.html2026-08-31
  9. 09AFL Function Reference — DateTimeToStramibroker.com/guide/afl/datetimetostr.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.