Pine Script custom candles

Pine Script plotcandle: Synthetic OHLC and Missing Bars

Pine Script plotcandle() draws a candle from four numeric series: open, high, low, and close. If any of the four values is na, that bar has no plotted candle. The function can display calculated OHLC, but its drawings do not replace the chart data or turn synthetic values into executable prices. Validate the four inputs before inspecting colors or placement.

Review a plotcandle script

Validate all four OHLC values

For a price candle, require four available values, high at least as large as open and close, and low no greater than open and close. plotcandle() itself uses the largest supplied OHLC value as the high and the smallest as the low. That normalization can conceal a swapped column or a broken calculation. A strict guard makes the error visible as an omitted candle instead.

CheckWhy it matters
No na in O, H, L, COne missing input suppresses the complete candle. Zero is a number, not a missing-value marker.
H >= max(O, C)The high must contain both ends of the body.
L <= min(O, C)The low must contain both ends of the body.
H >= LThe range must not be inverted.

Run a six-case reproduction in a separate pane

This complete v6 indicator repeats six hypothetical cases across chart bars. The values are test coordinates, not market observations. Add it to a chart and open the Data Window. Use Case number to locate each row in the next table; the cycle is based on bar_index, so it is not tied to specific timestamps. Reject invalid ranges is on by default.

Fixed OHLC inputs isolate drawing behavior from market data.pinescript
//@version=6
indicator("plotcandle input cases", overlay = false)

rejectInvalid = input.bool(true, "Reject invalid ranges")
caseNumber = bar_index % 6
float o = caseNumber == 1 ? 102.0 : caseNumber == 2 ? na : 100.0
float h = caseNumber == 1 ? 105.0 : caseNumber == 3 ? 99.0 : caseNumber == 4 ? 101.0 : 104.0
float l = caseNumber == 1 ? 99.0 : caseNumber == 3 ? 103.0 : caseNumber == 4 ? 99.0 : caseNumber == 5 ? na : 98.0
float c = caseNumber == 1 or caseNumber == 4 ? 100.0 : 102.0

hasOHLC = not na(o) and not na(h) and not na(l) and not na(c)
rangeIsValid = hasOHLC and h >= math.max(o, c) and l <= math.min(o, c) and h >= l
canDraw = hasOHLC and (not rejectInvalid or rangeIsValid)
bodyColor = c > o ? color.teal : c < o ? color.orange : color.gray

plotcandle(canDraw ? o : na, h, l, c, title = "Test candles", color = bodyColor, wickcolor = color.gray, bordercolor = bodyColor)
plot(caseNumber, "Case number", display = display.data_window)
plot(o, "Input open", display = display.data_window)
plot(h, "Input high", display = display.data_window)
plot(l, "Input low", display = display.data_window)
plot(c, "Input close", display = display.data_window)
plot(rangeIsValid ? 1 : 0, "Valid range: 1=yes", display = display.data_window)

Compare each bar with the expected output

These are expected results derived from the inputs and documented plotting rules, not a claim of a live TradingView test. With the guard on, cases 2, 3, and 5 must be empty. Turn the guard off to isolate the built-in normalization in case 3. Missing-value cases stay empty in either mode.

  • For case 3, the raw Input high readout remains 99. The displayed upper extreme is max(100, 99, 103, 102) = 103. Normalizing a drawing does not repair its source variables.
  • Replacing na with nz(value, 0) would invent a zero price and could stretch the scale. Preserve the gap until a documented fallback is appropriate.
CaseInput O / H / L / CGuard onGuard off
0100 / 104 / 98 / 102Teal body 100 to 102; wick 98 to 104Same
1102 / 105 / 99 / 100Orange body 100 to 102; wick 99 to 105Same
2na / 104 / 98 / 102No candle: open unavailableNo candle
3100 / 99 / 103 / 102No candle: inverted rangeTeal body; displayed high 103 and low 99
4100 / 101 / 99 / 100Gray doji; equal open and closeSame
5100 / 104 / na / 102No candle: low unavailableNo candle

Calculate synthetic OHLC without treating it as a trade

The next indicator averages each OHLC component with the preceding chart bar. It deliberately uses arithmetic rather than filling missing inputs: the first loaded bar has no prior values and stays empty. Use a standard Candles chart when comparing this output with market OHLC. On a Heikin-Ashi chart, the built-in inputs are already synthetic. This two-bar average is not the Heikin-Ashi formula.

  • The synthetic close is (102 + 100) / 2 = 101, while the current chart close is 100. The synthetic candle is a doji even though the current standard candle closes below its open.
  • The synthetic high 104.5 is an average of highs, not the highest price of the combined two-bar interval; that interval reached 105.
  • Drawing a close at 101 neither submits an order nor proves an execution at 101. If this calculation becomes a signal in a strategy, evaluate fills separately under the strategy emulator rules and real trading costs.
  • With Wait for chart bar close on, the live bar stays empty until confirmation. Turning it off lets the current synthetic candle change as the chart OHLC updates.
Hypothetical input or resultOHLC
Previous standard bar10010498102
Current standard bar10210599100
Component means101104.598.5101
Two-bar arithmetic means, with the original chart close in the Data Window.pinescript
//@version=6
indicator("Two-bar synthetic OHLC", overlay = true, behind_chart = false)

waitForClose = input.bool(true, "Wait for chart bar close")
o = (open + open[1]) / 2.0
h = (high + high[1]) / 2.0
l = (low + low[1]) / 2.0
c = (close + close[1]) / 2.0
hasOHLC = not na(o) and not na(h) and not na(l) and not na(c)
rangeIsValid = hasOHLC and h >= math.max(o, c) and l <= math.min(o, c) and h >= l
canDraw = rangeIsValid and (not waitForClose or barstate.isconfirmed)
directionColor = c > o ? color.teal : c < o ? color.orange : color.gray

plotcandle(canDraw ? o : na, h, l, c, title = "Synthetic means", color = color.new(directionColor, 65), wickcolor = directionColor, bordercolor = directionColor)
plot(close, "Original chart close", display = display.data_window)
plot(c, "Synthetic close", display = display.data_window)

Separate candle colors, pane placement, and visibility

color controls the body, wickcolor controls the wick, and bordercolor controls the outline. The examples explicitly set all three. The first script uses a separate pane because its fixed values could be far from the symbol price. The second uses overlay=true and behind_chart=false to place its candles over the main chart. The native candles still exist underneath.

  • A plotcandle call draws on chart-bar positions. It does not accept custom time coordinates or create a new chart timeframe.
  • Candles use plot counts, not label IDs. The script limit is 64 plot counts; a candle call can consume up to seven when its color arguments are series.
SymptomCheck before changing the formula
Blank barInspect all four inputs, the range guard, and the confirmation toggle.
Only the wick or outline is visibleCheck each color argument, transparency, and the Style tab. A transparent body can leave other components visible.
Candles are in the wrong paneCheck indicator overlay. A plot can use force_overlay=true to target the main chart from a separate pane. Re-add the script after changing declaration placement settings.
Custom candles are obscuredCheck behind_chart or the script Visual Order menu, plus whether the price scale includes the values.
Only recent candles appearCheck show_last and output visibility settings. display.none hides the output.
A local-scope compilation errorKeep plotcandle outside if blocks and loops; pass na to an OHLC argument when drawing is disabled.

Give the coding agent inputs and expected results

Send Pineify the complete failing script, the selected chart type, and one mismatching row from the six-case table. Specify whether malformed OHLC should be rejected, which bars may be omitted, and whether the live bar should wait for confirmation. Ask the agent to preserve the input readouts and run a syntax check. Then compare the resulting candle with the same row in TradingView; compilation alone cannot verify chart placement or price semantics.

Where Pineify fits

Pine Script AI Coding Agent

Use the coding agent to draft or revise custom OHLC calculations. Supply the reproduction and expected output, request a syntax check, then verify the drawing in TradingView.

Review a plotcandle script

Frequently asked questions

Educational plotting examples, not investment advice or tested trading strategies. Synthetic OHLC values do not establish executable prices. Syntax checks do not prove chart behavior or trading profitability.

Sources and verification

Published by Pineify. Pineify is an independent product and is not affiliated with TradingView. Sources support the technical explanations; examples do not establish trading performance.