Samples: candle patterns
Sample programs that find candle patterns from their definitions. Engulfing bars, doji, hammers, runs of bars, gaps, outside bars and the close position.
A candle pattern is a rule about the open, high, low and close of one bar and the bars before it. Each program here writes the rule out as a condition and marks the bars where it is true. A pattern describes what the price did; it does not forecast what it will do. Read Results that look too good before you trade on one.
Engulfing bars#
A bar whose body covers the whole body of the bar before it, closing the other way.
// Engulfing bars: a bar whose body covers the whole body of the bar before it, in the other direction.
// A bullish engulfing bar is up and its body runs from below the previous open to above the previous close.
indicator "Engulfing bars"
edition = 1
display = price
let body_top = math.max(open, close)
let body_bottom = math.min(open, close)
let bullish = close > open
and close[1] < open[1]
and body_top >= body_top[1]
and body_bottom <= body_bottom[1]
let bearish = close < open
and close[1] > open[1]
and body_top >= body_top[1]
and body_bottom <= body_bottom[1]
mark.shape(
bullish,
name = "Bullish engulfing",
shape = triangle_up,
place = below,
color = color.green,
)
mark.shape(
bearish,
name = "Bearish engulfing",
shape = triangle_down,
place = above,
color = color.red,
)What it teaches: the body of a bar with math.max(open, close) and math.min(open, close), comparisons with the bar before through [1], and two marks from two conditions.
Doji#
A bar whose body is very small next to its whole range.
// Doji: a bar whose body is very small next to its whole range, so the open and the close are almost the same price.
indicator "Doji"
edition = 1
display = price
input ratio: float = 0.1,
name = "Largest body (share of the range)",
min = 0.01,
max = 0.5,
step = 0.01
let range = high - low
let body = math.abs(close - open)
let doji = range > 0 and body <= range * ratio
mark.char(doji, name = "Doji", char = "+", place = above, color = color.gray)What it teaches: a float input used as a share, math.abs, and a guard (range > 0) so that a bar with no range is never counted.
Hammer and shooting star#
A small body at one end of the bar and a long shadow at the other.
// Hammer and shooting star: a small body at one end of the bar and a long shadow at the other end.
// The shadow must be at least `factor` times the body, and the shadow on the body's side must be short.
indicator "Hammer and shooting star"
edition = 1
display = price
input factor: float = 2.0, name = "Shadow over body", min = 1.0, step = 0.5
let body = math.abs(close - open)
let lower_shadow = math.min(open, close) - low
let upper_shadow = high - math.max(open, close)
let hammer = body > 0 and lower_shadow >= body * factor and upper_shadow <= body
let star = body > 0 and upper_shadow >= body * factor and lower_shadow <= body
mark.shape(hammer, name = "Hammer", shape = triangle_up, place = below, color = color.teal)
mark.shape(star, name = "Shooting star", shape = triangle_down, place = above, color = color.orange)What it teaches: the upper and lower shadow of a bar, and how one input (factor) tunes two patterns at once.
Bars in a row#
A mark after a number of up closes in a row, or down closes in a row.
// Runs of bars: a mark after `count` up closes in a row, or `count` down closes in a row.
// tech.bars_since counts the bars since the last time the condition was true.
indicator "Bars in a row"
edition = 1
display = price
input count: int = 3, name = "Bars in a row", min = 2, max = 10
let up = close > close[1]
let down = close < close[1]
let up_run = tech.bars_since(not up)
let down_run = tech.bars_since(not down)
mark.shape(
up_run == count,
name = "Up run",
shape = triangle_up,
place = below,
color = color.green,
)
mark.shape(
down_run == count,
name = "Down run",
shape = triangle_down,
place = above,
color = color.red,
)What it teaches: tech.bars_since, which counts the bars since a condition was last true. Here it counts since the last bar that did not close up, which is the length of the current up run.
Gaps#
A bar that opens above the previous high or below the previous low leaves a gap. A box marks the empty space.
// Gaps: the bar opens above the previous high or below the previous low, so no trade happened in between.
// A box marks the empty space; the boxes of the last gaps are kept.
indicator "Gaps"
edition = 1
display = price
max_boxes = 50
let gap_up = low > high[1]
let gap_down = high < low[1]
if gap_up
box.new(
bar.index - 1,
low,
bar.index,
high[1],
border_color = color.green,
background = color.fade(color.green, 80),
)
if gap_down
box.new(
bar.index - 1,
low[1],
bar.index,
high,
border_color = color.red,
background = color.fade(color.red, 80),
)
plot(close, name = "Close", color = color.gray)What it teaches: box.new with two corners (bar number and price), max_boxes in the header, and a drawing made inside an if. On a market that trades around the clock, gaps are rare inside a day and appear mostly after a weekend or a pause.
Outside bars#
A bar whose high is above and whose low is below the bar before it, coloured by the way it closed.
// Outside bar: a bar whose high is above and whose low is below the bar before it. Its colour says which way it closed.
indicator "Outside bars"
edition = 1
display = price
let outside = high > high[1] and low < low[1]
let tint = if close > open then color.green else color.red
chart.candles(if outside then tint else na, name = "Outside bars")
mark.char(outside, name = "Outside bar", char = "o", place = above, color = color.gray)What it teaches: chart.candles to colour only some candles (na leaves the others as they are) and mark.char with a letter.
Close position in the bar#
Where the close sits inside the bar's range, from 0 at the low to 100 at the high, with a smoothed line.
// Close position in the bar: 100 when the bar closed at its high, 0 at its low.
// A long run of high readings means buyers kept the price near the top of each bar.
indicator "Close position in the bar"
edition = 1
display = pane
input length: int = 10, name = "Smoothing", min = 1
let range = high - low
let position = if range > 0 then 100 * (close - low) / range else 50
let smoothed = tech.sma(position, length)
plot(position, name = "Position", style = columns, color = color.fade(color.blue, 60))
plot(smoothed, name = "Smoothed", color = color.orange, width = 2)
level(50, name = "Middle", color = color.gray, style = dashed)What it teaches: an if ... then ... else that protects a division (a bar with no range gets 50), the columns style and a faded colour.