TradingView Drawings with Pine Script
Pine Script creates lines, boxes, labels, and polylines from code. Store each object's ID to update or delete it. These objects are separate from the drawings you place manually with TradingView's toolbar.
Choose an object for the output you need
| Object | Use | Update and remove |
|---|---|---|
| line.new() | Two price/time points, levels, or rays | line.set_*(), line.delete() |
| box.new() | Ranges with left, right, top, and bottom edges | box.set_*(), box.delete() |
| label.new() | Dynamic text, prices, and tooltips at a bar | label.set_*(), label.delete() |
| polyline.new() | A path through an array of chart.point values | Delete and recreate; no setter functions |
| table.new() | A dashboard fixed to a pane position | table.cell(), table.set_*(), table.delete() |
Use plot() for a series such as a moving average. Use objects when you need independent coordinates or lifecycle control. Tables do not anchor to candles. For labels, yloc.price uses the supplied price; yloc.abovebar and yloc.belowbar position relative to the bar and ignore the y value.
Reference: drawing APIs, label placement, and tables.
Editable example: confirmed pivot resistance
Paste this complete indicator into a new Pine Editor script, save it, and add it to a chart. Its inputs control pivot strength, retained zones, retained labels, and zone color. Each confirmed high creates a box from the candle body to its wick. The script connects the latest two highs and deletes a zone when a later candle closes above its top.
//@version=6
indicator("Confirmed pivot drawings", overlay=true, max_boxes_count=60, max_lines_count=2, max_labels_count=60)
int strength = input.int(5, "Pivot bars on each side", minval=2, maxval=50)
int keepZones = input.int(10, "Maximum resistance zones", minval=1, maxval=50)
int keepLabels = input.int(20, "Maximum breakout labels", minval=1, maxval=50)
color zoneColor = input.color(color.red, "Resistance color")
var array<box> zones = array.new<box>()
var array<label> badges = array.new<label>()
var line swingLine = na
var int previousTime = na
var float previousHigh = na
float pivotHigh = ta.pivothigh(high, strength, strength)
// A pivot is known only after 'strength' later bars have closed.
if barstate.isconfirmed and not na(pivotHigh)
int pivotTime = time[strength]
float bodyTop = math.max(open[strength], close[strength])
box zone = box.new(left=pivotTime, top=pivotHigh,
right=time, bottom=bodyTop, xloc=xloc.bar_time,
border_color=zoneColor, bgcolor=color.new(zoneColor, 85),
text="RES", text_color=zoneColor, text_size=size.tiny,
text_halign=text.align_right)
array.push(zones, zone)
if array.size(zones) > keepZones
box.delete(array.shift(zones))
// Keep one line connecting the latest two confirmed pivot highs.
if not na(previousTime)
if na(swingLine)
swingLine := line.new(previousTime, previousHigh, pivotTime,
pivotHigh, xloc=xloc.bar_time, color=color.blue,
extend=extend.right, style=line.style_dashed)
else
line.set_xy1(swingLine, previousTime, previousHigh)
line.set_xy2(swingLine, pivotTime, pivotHigh)
previousTime := pivotTime
previousHigh := pivotHigh
// Walk backward so removing an array entry does not skip its neighbor.
if barstate.isconfirmed and array.size(zones) > 0
for i = array.size(zones) - 1 to 0
box zone = array.get(zones, i)
if close > box.get_top(zone)
label badge = label.new(bar_index, high, "RES BREAK",
yloc=yloc.abovebar, style=label.style_label_down,
color=color.blue, textcolor=color.white, size=size.tiny,
tooltip="Close: " + str.tostring(close, format.mintick))
array.push(badges, badge)
if array.size(badges) > keepLabels
label.delete(array.shift(badges))
box.delete(zone)
array.remove(zones, i)
else
box.set_right(zone, time)
- A strength of 5 requires five subsequent bars to confirm a pivot. The box is anchored in the past only after confirmation; it was not available on the pivot candle.
- Boxes extend on confirmed closes. The line is reused with setters; expired boxes and excess labels are deleted explicitly.
- Time coordinates keep a long-lived zone independent of the bar-index distance limit. A candle with no upper wick can produce a zero-height box.
- This is a drawing example, not a backtested trading strategy or investment advice. Inspect its chart behavior before adapting the conditions.
Bar coordinates and time coordinates
xloc.bar_index interprets x values as bar indices. Endpoints can be up to 10,000 bars behind or 500 bars ahead of the current bar. xloc.bar_time interprets them as UNIX timestamps in milliseconds. Pass time values with that mode, not a small bar number.
A duration and a bar count answer different questions. Adding 86,400,000 milliseconds means 24 elapsed hours; holidays, closed sessions, and daylight-saving changes can prevent it from matching the next session. Use the intended session's timestamp when session alignment matters.
//@version=6
indicator("Bar and time coordinates", overlay=true, max_lines_count=2)
int barsForward = input.int(20, "Bars forward", minval=1, maxval=500)
var line byIndex = line.new(na, na, na, na, color=color.blue)
var line byTime = line.new(na, na, na, na, xloc=xloc.bar_time, color=color.orange)
if barstate.islast
// A bar offset, not a clock duration.
line.set_xy1(byIndex, bar_index, close)
line.set_xy2(byIndex, bar_index + barsForward, close)
// Exactly 24 hours after this bar's opening timestamp.
// This is not necessarily the next trading session or next daily bar.
line.set_xy1(byTime, time, high)
line.set_xy2(byTime, time + 86400000, high)
Reference: TradingView's maximum bars backward and forward. The blue and orange lines are live values and can move before the bar closes.
Limits and object lifetime
Without explicit limits, scripts show roughly the most recent 50 lines, boxes, polylines, and labels. The hard maxima are 500 line IDs, 500 box IDs, 500 label IDs, and 100 polyline IDs per script. Set the corresponding max_lines_count, max_boxes_count, max_labels_count, or max_polylines_count in the declaration.
Choose an allowance that matches the retained history; setting every limit to 500 is not required. Garbage collection removes older drawings when an allowance is exceeded. An ID with invisible coordinates still counts. To manage a queue, delete the object returned by array.shift(); clearing or removing an array reference alone leaves the object on the chart.
The main example retains at most 50 zones, 50 labels, and one line. Its declared limits leave room for creation before pruning. This makes its retention rule explicit instead of relying on automatic collection.
Reference: drawing limits. Tables use their own positioning rules, with up to nine visible tables, one per available position.
Realtime rollback is not an object leak
On the open bar, indicators run again as updates arrive. Rollback removes ordinary temporary drawings from the preceding tick and restores committed state. A drawing can disappear because its condition is no longer true, not because hundreds of hidden duplicates accumulated.
Use barstate.isconfirmed for events that should wait for close. For a live display, keep an object with var and reapply setters on each update, as the coordinate example does. Persistent IDs do not freeze changing price inputs or bypass rollback.
Reference: execution model and rollback.
Redraw a polyline without retaining old paths
A polyline connects multiple points but has no setter API. This standalone example retains ten confirmed pivot highs, deletes its previous path, and builds the replacement. It connects highs only; it is not an alternating high/low Zig Zag detector.
//@version=6
indicator("Recent confirmed highs polyline", overlay=true, max_polylines_count=1)
var array<chart.point> points = array.new<chart.point>()
var polyline path = na
float pivotHigh = ta.pivothigh(high, 5, 5)
if barstate.isconfirmed and not na(pivotHigh)
array.push(points, chart.point.from_time(time[5], pivotHigh))
if array.size(points) > 10
array.shift(points)
if array.size(points) >= 2
polyline.delete(path)
path := polyline.new(points, xloc=xloc.bar_time,
curved=false, closed=false, line_color=color.blue, line_width=2)
Check the output in TradingView
- Start each example in a separate script. Wait for enough bars to form a pivot; no pivot means no object.
- Change the retained-zone input to 1. After another valid pivot, confirm that only the most recent unbroken zone remains.
- Compare an open candle with its close. The main example waits for confirmation; the coordinate example updates live.
- Reload the script and inspect its history. Earlier pivot anchors should not be interpreted as signals known at those earlier times.
These are reproducible inspection steps, not a claim that this page records a chart test. If nothing appears, check pane placement, coordinates, na values, and whether the creation condition ran.
Build a drawing indicator from your rules
Describe the anchor, confirmation rule, update rule, and deletion rule to the Pine Script AI Coding Agent. Ask for editable inputs and a bounded number of objects, then inspect the generated source and its behavior on your chart. The Visual Pine Script Editor offers a separate configuration workflow for indicators and strategies.