PlotShapes() and Visual Signals
Lines describe states. Arrows describe events. PlotShapes() is how an event that happened on
one particular bar gets marked on that bar, and it is the function most likely to put your
arrows somewhere baffling the first few times you use it — because two of its arguments do not
mean what their names suggest.
The signature
Section titled “The signature”Fragment — not a complete formula
PlotShapes( shape, color, layer = 0, yposition = graph0, offset = -12, XShift = 0 );| Argument | Meaning |
|---|---|
shape |
A number or an array. Zero draws nothing; each non-zero value selects a shape. |
color |
A colour, or an array of colours, one per bar. |
layer |
Layer number. Default 0. |
yposition |
Where to anchor the shape, in price units. Default graph0 — the first plotted line. |
offset |
A nudge, in screen pixels. Default −12. |
XShift |
Shifts shapes sideways by N bars. Added in AmiBroker 5.66. |
PlotShapes() returns nothing, so you cannot use its result in an expression.
The two arguments that catch everyone
Section titled “The two arguments that catch everyone”yposition is a price. offset is pixels. They are two different coordinate systems in
one call. Learners routinely write offset = ATR( 10 ) hoping for “one ATR above the bar” and
get shapes flung off the top of the pane, because an ATR of 1.8 was interpreted as a
one-and-a-bit pixel nudge — or, on a high-priced instrument, as a nudge of several hundred
pixels. To offset in price terms, put the arithmetic into yposition:
Fragment — not a complete formula
// Wrong: ATR is a price, offset is pixels.PlotShapes( MyShape, colorGreen, 0, Low, ATR( 10 ) );
// Right: do the price arithmetic in the position argument.PlotShapes( MyShape, colorGreen, 0, Low - ATR( 10 ), 0 );The sign of offset is inverted. Negative moves the shape down, positive moves it
up. The default of −12 pushes a shape twelve pixels below its anchor, which is why arrows
appear under the price line if you never touch that argument.
The shape constants
Section titled “The shape constants”The complete documented list, in the order the guide gives it:
shapeNone, shapeUpArrow, shapeDownArrow, shapeHollowUpArrow, shapeHollowDownArrow,
shapeSmallUpTriangle, shapeSmallDownTriangle, shapeHollowSmallUpTriangle,
shapeHollowSmallDownTriangle, shapeUpTriangle, shapeDownTriangle,
shapeHollowUpTriangle, shapeHollowDownTriangle, shapeSmallSquare,
shapeHollowSmallSquare, shapeSquare, shapeHollowSquare, shapeSmallCircle,
shapeHollowSmallCircle, shapeCircle, shapeHollowCircle, shapeStar, shapeHollowStar,
shapeDigit0 … shapeDigit9, and the modifier shapePositionAbove.
Positioning is baked into the names
Section titled “Positioning is baked into the names”Two official rules govern where a shape lands relative to its anchor:
- Odd shape values are drawn below the indicator; even values are drawn above it.
- Consequently, every shape whose name contains “Up” is already positioned below its anchor, and every shape whose name contains “Down” is already positioned above it.
That second rule is the reason for the one genuine prohibition in this function.
shapePositionAbove is an additive modifier that lifts an otherwise unpositioned shape above
its anchor, and AmiBroker’s author has stated plainly that it must not be added to any
shape whose name contains “Up” or “Down”. It is valid only with the squares, circles, stars
and digits.
An up arrow marking a buy therefore sits below the bar by default, which is the convention every charting package uses, and is why it looks right without you doing anything.
Turning conditions into shapes
Section titled “Turning conditions into shapes”shape is an array, and zero means “draw nothing”. Two idioms follow from that.
The arithmetic idiom appears throughout AmiBroker’s own examples:
Fragment — not a complete formula
MyShape = Buy * shapeUpArrow + Sell * shapeDownArrow;PlotShapes( MyShape, IIf( Buy, colorGreen, colorRed ) );It works because a false condition contributes zero. It also breaks quietly: if Buy and
Sell are both true on one bar, the two constants add, and the sum is some third shape
you never asked for. On a bar where two conditions overlap you get a random-looking glyph.
The conditional idiom does not have that failure mode:
Fragment — not a complete formula
PlotShapes( IIf( Buy, shapeUpArrow, shapeNone ), colorGreen, 0, Low, -12 );PlotShapes( IIf( Sell, shapeDownArrow, shapeNone ), colorRed, 0, High, 12 );Two calls, two independent decisions, no arithmetic on undocumented values. Prefer it.
Colour follows the same pattern. The color argument accepts an array, so one call can mark
the same shape in different colours according to a second condition — strength of the move,
whether a filter agreed, whatever you are testing.
Marking events, not states
Section titled “Marking events, not states”The most common way to ruin a chart is to mark a state as though it were an event.
Close > MA( Close, 50 ) is true on hundreds of consecutive bars; plotting a shape wherever
it is true gives you hundreds of arrows and no information.
A state, the event that starts it, and what each one plots
| Bar | 1 | 2 | 3 | 4 | 5 | 6 |
|---|---|---|---|---|---|---|
Close > MA | 0 | 1 | 1 | 1 | 0 | 1 |
Cross( Close, MA ) | 0 | 1 | 0 | 0 | 0 | 1 |
Shapes from the state | — | ▲ | ▲ | ▲ | — | ▲ |
Shapes from the event | — | ▲ | — | — | — | ▲ |
Cross() already gives you a one-bar event. ExRem( array1, array2 ) removes the repeats
that survive anyway — the case where two averages sit on top of each other and cross back and
forth for several bars. Flip( array1, array2 ) is the inverse: it turns a pair of events
back into a state that stays on until the opposite event arrives, which is exactly what you
want for a ribbon.
Three further habits keep charts readable:
- One idea per marker shape. If entries and exits share a glyph, the chart is a puzzle.
- Anchor to the bar, not to the indicator. Passing
LoworHighasypositionputs the marker against the price bar it refers to. The default anchors to the first plotted line, which is rarely what you mean on a price chart. - Use a ribbon for the state and shapes for the events. A two-per-cent-tall coloured band at the foot of the pane tells you which side of the line you are on for every bar without adding a glyph to every bar.
What about text?
Section titled “What about text?”PlotText( "text", x, y, color, bkcolor = colorDefault, yoffset = 0 ) writes a string at an
arbitrary position. Its coordinate system is a third variation on the theme: x is a bar
index, y is a price, and yoffset is pixels. Mixing those up is the number one
PlotText() bug.
PlotText() also takes numbers rather than arrays, so the documented way to label many bars is
a for loop indexing the price arrays — which is expensive on a long chart and should always
be guarded by a condition. PlotTextSetFont() does the same job with a chosen typeface and
size, and carries a side effect worth knowing: it sets the font for every subsequent
PlotText() call in the same formula.
For marking signals, shapes are almost always the better tool. Reach for text when the label carries information a glyph cannot — a price level, a count, a name.
Putting it together
Section titled “Putting it together”What we are building
Section titled “What we are building”A price pane that marks the bars where a fast average crosses a slow one, with arrows held clear of the bar in either pixels or ATR units, plus a ribbon at the foot of the pane showing which side of the cross the market is currently on.
The moving-average cross is used here because everybody recognises it, not because the course is recommending it. Its behaviour is examined properly in the backtesting parts.
The formula
Section titled “The formula”Complete runnable AFL
/* * Marking events without burying the chart - worked example for Part 10. * * Assumptions * - Chart pane formula, applied to the price pane. * - The moving-average cross used here is a demonstration of event marking, * not a recommendation. It is one of the most heavily reused rules in * circulation and is chosen only because every reader recognises it. * - Markers are drawn on the bar where the event occurred. Acting on that * bar's close is a separate decision with its own execution assumptions. */
_SECTION_BEGIN( "Signal markers" );
// Arrows drawn with a pixel offset are clipped against the pane edge unless// there is head and foot room to draw into.GraphXSpace = 8;
FastPeriod = Param( "Fast periods", 20, 2, 200, 1 );SlowPeriod = Param( "Slow periods", 50, 2, 400, 1 );MarkerOffset = Param( "Marker offset (screen pixels)", 12, 0, 60, 1 );PriceAnchor = ParamToggle( "Anchor in ATR units instead", "No|Yes", 0 );AtrMultiple = Param( "ATR clearance", 0.75, 0.1, 4, 0.05 );ShowRibbon = ParamToggle( "State ribbon", "No|Yes", 1 );
FastLine = MA( Close, FastPeriod );SlowLine = MA( Close, SlowPeriod );
Plot( Close, "Price", colorDefault, styleBar );Plot( FastLine, "Fast " + NumToStr( FastPeriod, 1.0 ), colorBlue, styleLine );Plot( SlowLine, "Slow " + NumToStr( SlowPeriod, 1.0 ), colorOrange, styleLine );
// Events, not states. Cross() is already one bar wide; ExRem removes the// repeated crossings that happen when two averages sit on top of each other in// a flat market, so each change of side is marked exactly once.CrossUp = ExRem( Cross( FastLine, SlowLine ), Cross( SlowLine, FastLine ) );CrossDown = ExRem( Cross( SlowLine, FastLine ), Cross( FastLine, SlowLine ) );
// Two ways to hold a marker clear of the bar it belongs to.// Pixel clearance keeps the gap constant on screen at every zoom level.// ATR clearance keeps the gap constant in the instrument's own units, so it// stays honest when the pane is rescaled or the symbol changes.Clearance = AtrMultiple * ATR( 14 );
if( PriceAnchor ){ LowAnchor = Low - Clearance; HighAnchor = High + Clearance; PixelNudge = 0;}else{ LowAnchor = Low; HighAnchor = High; PixelNudge = MarkerOffset;}
// PlotShapes( shape, color, layer, yposition, offset, XShift )// yposition is in PRICE units.// offset is in SCREEN PIXELS, and its sign is inverted: negative moves DOWN.// shapeUpArrow is already positioned below its anchor and shapeDownArrow// above it, so the offsets only push them further clear of the bar.PlotShapes( IIf( CrossUp, shapeUpArrow, shapeNone ), colorBrightGreen, 0, LowAnchor, -PixelNudge );
PlotShapes( IIf( CrossDown, shapeDownArrow, shapeNone ), colorRed, 0, HighAnchor, PixelNudge );
// A ribbon answers "which side are we on" for every bar without adding a marker// to every bar. The plotted constant is the ribbon height as a percentage of// pane height, because the own-scale range is set to -0.5 .. 100.if( ShowRibbon ){ OnFastSide = Flip( CrossUp, CrossDown ); Plot( 2, "Side", IIf( OnFastSide, colorPaleGreen, colorRose ), styleOwnScale | styleArea | styleNoLabel | styleNoTitle, -0.5, 100 );}
_N( Title = StrFormat( "%s fast %g / slow %g crossings in the loaded range: %g", Name(), FastPeriod, SlowPeriod, LastValue( Cum( CrossUp OR CrossDown ) ) ) );
_SECTION_END();How it works
Section titled “How it works”After the parameters, the two averages are plotted so that the reader can see what the arrows refer to.
CrossUp and CrossDown reduce the relationship between the two lines to single bars.
Cross() supplies the event; ExRem() removes any repeat before the opposite event has
occurred, so the chart shows an alternating sequence of up and down markers rather than
clusters.
The PriceAnchor toggle switches between the two ways of holding a marker clear of the bar.
With it off, the anchor is the bar’s own low or high and the clearance is a pixel offset —
constant on screen at any zoom level. With it on, the clearance is a multiple of ATR and the
pixel nudge is zero — constant in the instrument’s own units, so it survives a change of
symbol or a rescale. Building both into one formula makes the distinction concrete, and there
is no universally right answer: pixels are better for reading, price units are better for
screenshots and comparisons.
The two PlotShapes() calls use the conditional idiom. shapeUpArrow is anchored to the low
and pushed further down; shapeDownArrow is anchored to the high and pushed up. Neither
carries shapePositionAbove, because both already carry a position.
Finally the ribbon. Flip( CrossUp, CrossDown ) converts the two event arrays back into a
state that is true from an up-cross until the next down-cross. Plotting the constant 2 against
an own scale of −0.5 to 100 produces a band two per cent of the pane’s height at its foot,
which is the official ribbon idiom.
Key functions
Section titled “Key functions”PlotShapes( shape, color, layer, yposition, offset, XShift )— draws glyphs.ypositionis a price,offsetis pixels, negative offsets move down.ExRem( array1, array2 )— removes excess signals: keeps the first true inarray1until a true appears inarray2.Flip( array1, array2 )— a latch: on at the first true inarray1, off at the next true inarray2.GraphXSpace— a reserved variable; percentage of extra room above and below the graph.
What you should see
Section titled “What you should see”Green up arrows below the bar where the fast average crossed above the slow one, red down arrows above the bar where it crossed back, exactly one marker per change of side, and a green-or-pink band along the bottom of the pane. The title reports how many crossings exist in the loaded data.
Test it
Section titled “Test it”- Count the arrows in one screen and compare with the crossing count in the title over the same range. Every crossing should have exactly one marker, alternating in direction.
- Set Marker offset to 0. The arrows should sit exactly on the bar’s low and high.
- Switch Anchor in ATR units on and zoom in and out. The pixel-anchored arrows keep a constant screen gap; the ATR-anchored ones keep a constant price gap and therefore change their screen gap as you zoom.
- Remove
ExRemfromCrossUpon a choppy sideways symbol. Clusters of arrows should appear where the averages are entangled. That is the behaviourExRemexists to remove.
Common errors
Section titled “Common errors”- Arrows are off the top or bottom of the pane. A price expression ended up in
offset. Move it toyposition. - Arrows are on the wrong side of the bar. Remember negative
offsetmoves down; and an up arrow is already positioned below its anchor. - A strange, unrequested glyph appears occasionally. Two shape constants were added
together on a bar where both conditions were true. Use the
IIf( cond, shape, shapeNone )form instead. - Arrows are clipped at the pane edge. Raise
GraphXSpace. shapePositionAboveseems to do nothing. It was combined with a shape whose name contains “Up” or “Down”. Those already carry a position.
Extension
Section titled “Extension”Add a third marker: a hollow square on any bar where a cross occurred and the bar’s range was larger than its recent average. Then look at how often that condition coincides with the cross. The interesting result is usually how little the extra filter changes, which is a useful thing to discover on a chart before discovering it in a backtest.
What changed
Section titled “What changed”You can now put a mark on the exact bar where something happened, control its position in either pixels or price, colour it conditionally, and keep the chart legible by separating the event from the state it starts. You also know the two documented rules that decide whether a shape lands above or below its anchor, and why arithmetic on shape constants is a bad idea.
Next: putting the settings of all this on the Parameters dialog, so you stop editing code to change a number.
Check your understanding
Sources for this lesson
6 verified · checked 2026-08-31
- 01AFL Function Reference — PlotShapesamibroker.com/guide/afl/plotshapes.html2026-08-31
- 02AFL Function Reference — PlotTextamibroker.com/guide/afl/plottext.html2026-08-31
- 03AFL Function Reference — PlotTextSetFontamibroker.com/guide/afl/plottextsetfont.html2026-08-31
- 04AmiBroker User's Guide — Creating your own indicators, part 2amibroker.com/guide/h_indbuilder2.html2026-08-31
- 05AFL Function Reference — ExRemamibroker.com/guide/afl/exrem.html2026-08-31
- 06AFL Function Reference — Flipamibroker.com/guide/afl/flip.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.