Multi-Bar Candlestick Patterns
One bar gives you a handful of named shapes. Two bars give you hundreds. Three give you thousands. This lesson builds the two- and three-bar vocabulary properly — you will need it — and then does the arithmetic that most treatments of it leave out: how many patterns you could invent, and what that number does to the credibility of whichever one you eventually like best.
Yesterday’s bar becomes an input
Section titled “Yesterday’s bar becomes an input”Everything multi-bar starts with the ability to reach backwards. In AFL that is
Ref(), which shifts an array in time. A negative period looks back; a positive
one looks forward, into bars that had not printed, which is the mechanism behind most
look-ahead bugs. Part 9 treats it properly. For now you need one form:
Fragment — not a complete formula
PrevOpen = Ref( Open, -1 ); // yesterday's open, aligned onto today's barPrevHigh = Ref( High, -1 );PrevLow = Ref( Low, -1 );PrevClose = Ref( Close, -1 );After those four lines, every bar carries eight prices instead of four, and a two-bar pattern is just a condition on the eight.
Engulfing: one name, two different patterns
Section titled “Engulfing: one name, two different patterns”The bullish engulfing pattern is described as a down bar followed by an up bar that “engulfs” it. The ambiguity is in the word engulfs, and it is not a small one.
The body definition asks whether today’s body covers yesterday’s body:
Fragment — not a complete formula
BodyEngulfUp = Close > Open // today closed up AND PrevClose < PrevOpen // yesterday closed down AND BodyBottom <= PrevBodyBottom // covers the bottom of it AND BodyTop >= PrevBodyTop; // and the top of itThe range definition asks whether today’s whole bar covers yesterday’s whole bar, wicks included:
Fragment — not a complete formula
RangeEngulfUp = Close > Open AND PrevClose < PrevOpen AND High >= PrevHigh AND Low <= PrevLow;These are not two ways of writing the same test. The range version is much stricter, because a bar must exceed both extremes rather than both body edges, so it fires far less often. On ten years of daily data on a liquid instrument you can easily find several times as many body-engulfing bars as range-engulfing ones.
Both definitions are in common use, both are called “bullish engulfing”, and almost nobody says which one they mean. If you ever cannot reproduce someone’s pattern count, this is the first thing to check.
There is a third decision buried in both versions: what happens when today’s body
edge exactly equals yesterday’s. Written with <= and >=, as above, ties count.
Written with < and >, they do not. On instruments with coarse tick sizes, or on
low-priced instruments, ties are common enough to move the count noticeably.
Inside and outside bars, and why AmiBroker’s answer may differ from yours
Section titled “Inside and outside bars, and why AmiBroker’s answer may differ from yours”AmiBroker ships two of these relationships as built-in functions, and their documented definitions are worth reading precisely.
Inside() returns true when today’s high is lower than yesterday’s high and
today’s low is higher than yesterday’s low. Both comparisons are strict. A bar
whose high exactly equals yesterday’s high is therefore not an inside bar as far as
Inside() is concerned, even though most people drawing it on paper would call it
one.
Outside() returns true on an outside bar. GapUp() returns true when yesterday’s
high is lower than today’s low, and GapDown() when yesterday’s low is higher than
today’s high — note that these are full gaps between the bar extremes, not merely
an open away from the previous close, which is what many traders mean by the word.
This is a small example of a large habit. When a function exists, read its definition before assuming it matches the one in your head, and when your own version disagrees with it, find out which bars differ before deciding who is right.
Three bars, and the growth of unstated parameters
Section titled “Three bars, and the growth of unstated parameters”Three-bar patterns are built by composing the vocabulary you already have. A morning star, for example, is usually described as: a long down bar, then a small-bodied bar that gaps below it, then a long up bar that closes well into the body of the first.
Count the decisions in that one sentence:
- how long is “long”, as a fraction of range or of ATR
- how small is “small”
- must the gap be a full gap, as
GapDown()defines it, or just a lower open - how far into the first body is “well into” — a third, a half, more
- does the middle bar’s colour matter
- does the third bar have to close above the first bar’s open, or merely inside it
- and, as ever, what prior trend is required, measured how
Seven decisions, most of them unstated in the description, each with several plausible settings. The pattern in the book is one point in a space of thousands of patterns, and the point that made it into the book was chosen by somebody looking at charts.
How many patterns can you invent?
Section titled “How many patterns can you invent?”Do the arithmetic honestly and it becomes uncomfortable quickly.
Classify each bar coarsely — three body sizes (small, medium, large), three close positions (lower third, middle, upper third), and two directions. That is 18 states per bar. It is a deliberately crude classification; nobody would call it detailed.
The pattern space, one step at a time
- 18 single-bar states3 body sizes × 3 close positions × 2 directions
- 324 two-bar sequences18 × 18
- 5,832 three-bar sequences18 × 18 × 18
- 52,488 with gapsadd gap up / gap down / no gap at each of the two transitions
- 209,952 with a context filterfour plausible definitions of "after a decline"
You will not test two hundred thousand patterns. But you do not need to. The number that matters is how many you tried before you settled on the one you are now excited about — and that number is usually much larger than people remember, because it includes every threshold you nudged, every context filter you added, and every variant you abandoned after a quick look.
The arithmetic that ruins the fun
Section titled “The arithmetic that ruins the fun”Suppose none of the patterns in your search has any real association with what
follows. Test each one and accept it as “significant” whenever it clears a 5%
threshold. The probability that at least one of them clears it by chance alone is
1 - 0.95^k, where k is the number of patterns tested:
| Patterns tested | Chance at least one looks significant |
|---|---|
| 1 | 5.0% |
| 5 | 22.6% |
| 10 | 40.1% |
| 20 | 64.2% |
| 50 | 92.3% |
| 100 | 99.4% |
| 324 | above 99.9% |
At 324 two-bar patterns — the crude classification above, tested exhaustively — you would expect roughly 16 of them to clear a 5% threshold even if the market were pure noise. Sixteen patterns, each with a plausible-sounding story available after the fact, each with a chart you could put in a presentation.
There are three practical responses, in increasing order of usefulness:
- Adjust the threshold. The crude Bonferroni correction divides your significance level by the number of tests: 20 tests at an overall 5% level means demanding 0.25% from each. It is conservative and easy to apply, and it makes most pattern results disappear, which is informative in itself.
- Hold data back. Define the pattern on one period, then measure it on a period you have never looked at. Parts 30 and 32 build this into a discipline.
- Have a reason first. A pattern proposed because you can say why it might work, before you measure it, is a single test. A pattern found by ranking hundreds of candidates is not, and no amount of subsequent storytelling converts it into one.
Keeping a written research log — every pattern tried, every threshold moved, dated — is what makes the third response possible. It is unexciting and it is the difference between research and rummaging.
Counting them on your own chart
Section titled “Counting them on your own chart”Put both engulfing definitions, both inside-bar definitions, Outside() and the two
gap functions on one chart, and report all the counts at once. The purpose is not to
find signals. It is to make definitional disagreements visible as numbers, so that a
conversation about what “engulfing” means can be settled in seconds.
Complete formula
Section titled “Complete formula”Complete runnable AFL
// engulfing-and-bars.afl// Part 7 - Multi-Bar Candlestick Patterns//// Counts four two-bar relationships on the chart in front of you, so that a// disagreement about a definition can be settled with numbers instead of// adjectives://// - bullish and bearish engulfing, under BOTH definitions in common use// - inside bars, comparing AmiBroker's built-in Inside() with a hand-written// version that allows ties// - outside bars, via Outside()// - gaps, via GapUp() and GapDown()//// Assumptions:// - "Engulfing" is ambiguous in the literature. The body definition asks// whether today's body covers yesterday's body; the range definition asks// whether today's high-low covers yesterday's high-low. They are different// patterns wearing the same name, so the formula reports both counts.// - Nothing here is a trading signal. It is a labelling exercise whose whole// purpose is to show how sensitive a count is to a definition.
_SECTION_BEGIN("Two-bar relationships");
// Cum() no longer forces AmiBroker to process every bar (that changed in// version 5.30), so a running count would otherwise depend on how much history// the chart happened to compute. -2 means "all bars", which makes the counts// below reproducible.SetBarsRequired( -2, -2 );
UseRangeDefinition = ParamToggle("Engulfing definition", "Body|Range", 0 );MinBodyPct = Param("Minimum body as % of range", 20, 0, 60, 5 );
// -------------------------------------------------- yesterday's four pricesPrevOpen = Ref( Open, -1 );PrevHigh = Ref( High, -1 );PrevLow = Ref( Low, -1 );PrevClose = Ref( Close, -1 );
PrevBodyTop = Max( PrevOpen, PrevClose );PrevBodyBottom = Min( PrevOpen, PrevClose );BodyTop = Max( Open, Close );BodyBottom = Min( Open, Close );
BodyPct = 100 * SafeDivide( BodyTop - BodyBottom, High - Low, 0 );BigEnough = BodyPct >= MinBodyPct;
// ------------------------------------------------------- engulfing, twice// Body definition: today's body covers yesterday's body.BodyEngulfUp = Close > Open AND PrevClose < PrevOpen AND BodyBottom <= PrevBodyBottom AND BodyTop >= PrevBodyTop;BodyEngulfDown = Close < Open AND PrevClose > PrevOpen AND BodyBottom <= PrevBodyBottom AND BodyTop >= PrevBodyTop;
// Range definition: today's whole bar covers yesterday's whole bar. This is// Outside() plus a direction requirement, and it is a much rarer event.RangeEngulfUp = Close > Open AND PrevClose < PrevOpen AND High >= PrevHigh AND Low <= PrevLow;RangeEngulfDown = Close < Open AND PrevClose > PrevOpen AND High >= PrevHigh AND Low <= PrevLow;
BullEngulf = IIf( UseRangeDefinition, RangeEngulfUp, BodyEngulfUp ) AND BigEnough;BearEngulf = IIf( UseRangeDefinition, RangeEngulfDown, BodyEngulfDown ) AND BigEnough;
// ------------------------------------------------- inside, outside and gaps// Inside() is documented as: today's high LOWER than yesterday's high AND// today's low HIGHER than yesterday's low. Both inequalities are strict, so a// bar that exactly equals yesterday's high is not an inside bar. The// hand-written version below allows those ties, and the two counts differ.BuiltInInside = Inside();TiesAllowed = High <= PrevHigh AND Low >= PrevLow;Disagreement = BuiltInInside != TiesAllowed;
BuiltInOutside = Outside();Gapped = GapUp() OR GapDown();
// ------------------------------------------------------------------ display// SetBarFillColor must be called BEFORE the Plot() it applies to.SetBarFillColor( IIf( Close > Open, colorPaleGreen, colorRose ) );Plot( Close, "Price", colorDefault, styleCandle );
PlotShapes( IIf( BullEngulf, shapeUpArrow, shapeNone ), colorGreen, 0, Low, -16 );PlotShapes( IIf( BearEngulf, shapeDownArrow, shapeNone ), colorRed, 0, High, 16 );PlotShapes( IIf( BuiltInInside, shapeSmallCircle, shapeNone ), colorBlue, 0, High, 30 );PlotShapes( IIf( BuiltInOutside, shapeHollowCircle, shapeNone ), colorOrange, 0, High, 30 );PlotShapes( IIf( Disagreement, shapeStar, shapeNone ), colorViolet, 0, Low, -30 );
Title = Name() + " " + Date() + "\nEngulfing definition in use: " + WriteIf( UseRangeDefinition, "range (high-low covers high-low)", "body (body covers body)" ) + "\nBullish engulfing: " + NumToStr( Cum( BullEngulf ), 1.0 ) + " Bearish engulfing: " + NumToStr( Cum( BearEngulf ), 1.0 ) + "\nBody definition would give: " + NumToStr( Cum( BodyEngulfUp AND BigEnough ), 1.0 ) + " up / " + NumToStr( Cum( BodyEngulfDown AND BigEnough ), 1.0 ) + " down" + "\nRange definition would give: " + NumToStr( Cum( RangeEngulfUp AND BigEnough ), 1.0 ) + " up / " + NumToStr( Cum( RangeEngulfDown AND BigEnough ), 1.0 ) + " down" + "\nInside() " + NumToStr( Cum( BuiltInInside ), 1.0 ) + " ties-allowed version " + NumToStr( Cum( TiesAllowed ), 1.0 ) + " they disagree on " + NumToStr( Cum( Disagreement ), 1.0 ) + " bars" + "\nOutside() " + NumToStr( Cum( BuiltInOutside ), 1.0 ) + " gaps " + NumToStr( Cum( Gapped ), 1.0 ) + " total bars " + NumToStr( Cum( 1 ), 1.0 );
_SECTION_END();How it works
Section titled “How it works”The formula lifts yesterday’s four prices onto today’s bar with Ref(), then states
each relationship as a separate named condition. Both engulfing definitions are
computed every time; the ParamToggle() only decides which one drives the arrows,
while the title reports both counts regardless. The inside-bar section computes
AmiBroker’s Inside() and a hand-written version that allows ties, and marks the bars
where they disagree with a star. The title carries eight counts, which together make
up a small, reproducible fact sheet about the chart in front of you.
Key functions
Section titled “Key functions”Ref( array, -1 )shifts an array one bar forward in position, so that each bar can see the previous bar’s value.Inside()andOutside()are AmiBroker’s built-in inside-day and outside-day tests, both documented under “Basic price pattern detection”.GapUp()andGapDown()test for full gaps between bar extremes.ParamToggle( name, "A|B", default )produces a two-state parameter; the first string is the false value and the second is the true value.WriteIf( condition, "true text", "false text" )picks between two strings, used here so the title states which definition is currently driving the arrows.
Expected result
Section titled “Expected result”Test it
Section titled “Test it”Switch the engulfing definition parameter from Body to Range and watch the arrows thin out. The title’s two count lines do not change when you switch, because both are always computed — so you can read the ratio directly. Write it down. That ratio is the size of the definitional disagreement, and you now have it as a number rather than an opinion.
Then find a star. Zoom in on it and check by eye that the bar’s high or low exactly equals the previous bar’s. If none of your instruments produces a star, try one with a coarser tick size or a lower price.
Common errors
Section titled “Common errors”- No arrows at all.
MinBodyPctis too high, or the instrument gaps so much that bodies rarely overlap. Lower it to zero first to confirm the rest works. - Arrows on almost every bar.
MinBodyPctis at zero and the body definition is catching every bar that merely closed the other way. This is a genuine property of the loose definition, not a bug. - Counts change when you scroll. Remove the
SetBarsRequired( -2, -2 )line and they will; leave it in and they will not. - The star markers never appear. On instruments where exact price ties are rare, they legitimately do not occur. Check a lower-priced instrument before assuming the test is wrong.
Extension
Section titled “Extension”Add a third definition of engulfing — for instance, one that requires today’s close to exceed yesterday’s high rather than merely yesterday’s open — and add its count to the title. You now have three numbers for one pattern name. Decide which you would report, and write down why.
What changed
Section titled “What changed”You can now express a two-bar relationship as arithmetic on eight prices, you know
that two widely used definitions share the name “engulfing”, and you know that
AmiBroker’s Inside() uses strict inequalities that your hand-written version
probably does not.
More importantly, you have the count. The number of patterns available to be invented is large enough that a broad search will almost always throw up an impressive-looking one, whether or not the market had anything to do with it. Everything in the rest of this part follows from taking that seriously.
Check your understanding
Sources for this lesson
8 verified · checked 2026-08-31
- 01AFL Function Reference — Insideamibroker.com/guide/afl/inside.html2026-08-31
- 02AFL Function Reference — Outsideamibroker.com/guide/afl/outside.html2026-08-31
- 03AFL Function Reference — GapUpamibroker.com/guide/afl/gapup.html2026-08-31
- 04AFL Function Reference — GapDownamibroker.com/guide/afl/gapdown.html2026-08-31
- 05AFL Function Reference — Refamibroker.com/guide/afl/ref.html2026-08-31
- 06AFL Function Reference — ParamToggleamibroker.com/guide/afl/paramtoggle.html2026-08-31
- 07AFL Function Reference — WriteIfamibroker.com/guide/afl/writeif.html2026-08-31
- 08AFL Function Reference — Cumamibroker.com/guide/afl/cum.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.