Pine Script v6 drawing objects

Pine Script Labels: Reuse, Deletion and Object Limits

A Pine Script label is a drawing object with an ID, position and dynamic text. label.new() creates it; label.set_xy() and label.set_text() update it; label.delete() removes it. Reuse one persistent label for a current readout. For historical events, store IDs in an array and delete expired objects. Setting a coordinate to na hides a label but does not release its ID.

Review label logic with AI

New objects and new text are different operations

Calling label.new on every historical bar allocates a new object each time. Assigning its result to the same variable name does not delete the previous object. A setter changes the object referenced by an existing ID. var keeps the initial reference across bars unless you explicitly reassign it.

  • Labels accept series strings, so price text can use str.tostring(close, format.mintick). plotshape text requires a const string.
  • Labels can be created inside an if block. Put the condition around creation when an event is absent; do not allocate an invisible object on every bar.
RequirementAllocation ruleCleanup rule
Caption on every qualifying barOne new label per eventDelete old events or accept garbage collection
Current price beside the latest barOne label for the script runMove and rewrite the same object
Last three event pricesNew label when an event closesDelete the oldest before adding a fourth
Fixed CROSS caption across chart historyUse a conditional plotshape seriesNo individual label ID to delete

Complete example: one live label and three confirmed events

This indicator has two independent jobs. The blue label follows the latest high and displays the current close. Teal labels record closes crossing strictly above an input level, with the previous close at or below it. The event queue defaults to three entries. Set the level near your chart price; 100 is only an illustrative default.

  • The script reserves at most 100 event labels plus one live label. Deletion happens before creation, so even the replacement step stays within 101 objects.
  • With keep set to 0, events are counted but no event labels are allocated. The live label still exists.
  • The live object starts with missing coordinates and already consumes one ID. Its setters run on every update of the last bar, including open-bar updates.
Complete Pine v6 indicator. Event labels use bar timestamps by default; turn that input off to use bar indices.pinescript
//@version=6
indicator("Label reuse and bounded history", overlay=true, max_labels_count=101)

float level = input.float(100.0, "Crossing level")
int keep = input.int(3, "Event labels to keep", minval=0, maxval=100)
bool useTime = input.bool(true, "Anchor events by bar time")

var label live = label.new(na, na, "", xloc=xloc.bar_index, yloc=yloc.price, style=label.style_label_down, color=color.blue, textcolor=color.white)
var array<label> events = array.new<label>(0)
var int eventCount = 0

bool crossedUp = not na(close[1]) and close[1] <= level and close > level
bool confirmedEvent = crossedUp and barstate.isconfirmed

if confirmedEvent
    eventCount += 1
    if keep > 0
        if array.size(events) >= keep
            label.delete(array.shift(events))
        int eventX = useTime ? time : bar_index
        label eventLabel = label.new(eventX, low, "Event " + str.tostring(eventCount) + "\nClose " + str.tostring(close, format.mintick), xloc=useTime ? xloc.bar_time : xloc.bar_index, yloc=yloc.price, style=label.style_label_up, color=color.teal, textcolor=color.white)
        array.push(events, eventLabel)

if barstate.islast
    label.set_xy(live, bar_index, high)
    label.set_text(live, "Close " + str.tostring(close, format.mintick) + "\n" + (barstate.isconfirmed ? "Closed" : "Open"))

plot(level, "Crossing level", color=color.gray)
plot(confirmedEvent ? 1 : 0, "Confirmed event", display=display.data_window)
plot(array.size(events), "Retained event labels", display=display.data_window)
plot(eventCount, "Total confirmed events", display=display.data_window)

Recalculate the event and object counts

Assume a fresh run with level 100 and keep 3, and feed these hypothetical confirmed closes in order: 99, 101, 99, 102, 99, 103, 99, 104. The first bar has no previous close, so it cannot qualify. The four upward crossings occur at zero-based indices 1, 3, 5 and 7. These are arithmetic test cases, not observed market results.

  • After E confirmed events, the example holds 1 + min(keep, E) label IDs. The array size counts event labels only, so it is one less than the total.
  • On 300 historical bars, an unconditional new-label call makes 300 allocations. A single var label with setters makes one allocation. The event example makes one live allocation plus E event allocations, while retaining only the bounded subset.
  • Previous close 99 and current close 100 do not qualify. Previous close 100 and current close 101 qualify at bar close. Previous close 101 and current close 102 do not qualify because price was already above the level.
After bar indexClose / eventRetained event indicesAllocated labels
099 / noneEmpty1 live + 0 events = 1
1101 / event 111 + 1 = 2
3102 / event 21, 31 + 2 = 3
5103 / event 31, 3, 51 + 3 = 4
7104 / event 43, 5, 7; index 1 deleted1 + 3 = 4

Array removal must delete the drawing too

An array stores references to labels. Removing an element with shift, pop or clear does not itself destroy the referenced drawing. The example passes the ID returned by array.shift to label.delete, then appends the replacement. To clear a whole collection, delete each referenced label before clearing the array.

  • The example tests keep > 0 and array.size(events) >= keep before shifting. It therefore never shifts an empty queue.
  • array.copy duplicates the collection of references, not the label objects. Updating a referenced label through either array affects that same drawing; use label.copy if you need a separate drawing.
  • label.all is maintained by Pine for the script's visible labels. Deleting its oldest entry can be useful for a single pool, but it can also remove a live readout. A dedicated event array keeps those lifetimes separate.

Object limits and invisible labels

TradingView normally displays approximately the most recent 50 labels. Set max_labels_count in indicator or strategy to raise the allowance, up to 500 label IDs. When the allowance is exceeded, garbage collection removes older labels. This cap is a rendering resource limit, not an event-history database.

  • For example, 12 calls that allocate labels still create 12 IDs even if eight have missing coordinates and only four can be seen.
  • Raising max_labels_count does not make an unbounded queue safe. Set the application retention below the drawing allowance and include every label family in that budget.
OperationID still consumes the budget?Result
Move a label to an na coordinateYesHidden drawing, allocated object
Set its text to an empty stringYesNo caption, allocated object
Skip label.new when the condition is falseNo new IDExisting labels are unaffected
Delete the label explicitlyNoThe drawing is removed
Clear an array of label IDsYes, unless separately deletedReferences removed; drawings can remain

Match x coordinates to xloc and y coordinates to yloc

With xloc.bar_index, x is an absolute bar index, not a number of bars ago. With xloc.bar_time, x is a UNIX timestamp in milliseconds; the example passes time, the signal bar opening timestamp. The live label uses the current bar index. Changing the event input reloads the script and rebuilds the history using the chosen coordinate mode.

  • Use stored event timestamps for older drawings that must be positioned outside the bar-index range. Timestamp mode does not create additional historical data or expand the history buffer.
  • Adding 86,400,000 milliseconds advances one elapsed day, not one trading session. Weekends, holidays and missing bars mean time arithmetic is not interchangeable with bar offsets.
  • label.set_xy changes coordinates without changing their mode. If switching an existing object between index and time coordinates, also set its xloc with label.set_xloc and provide the matching x value.
Coordinate caseInterpretationBoundary or failure
Current index 20,000; x = 20,003Three bars ahead in index modeA visual position, not a future signal
Current index 20,000; x = 20,501501 bars aheadExceeds the 500-bar forward limit
Current index 20,000; x = 9,99910,001 bars backExceeds the 10,000-bar past placement limit
time with xloc.bar_indexTimestamp misread as a huge bar indexUse xloc.bar_time with timestamp values
yloc.price and y = lowLabel anchor at the signal lowIts style controls where the text sits around that anchor
yloc.abovebar or yloc.belowbarRelative placement near the barThe supplied y price is ignored

Why a live label can change or disappear before close

Indicators execute on updates of the realtime bar. Before the next execution, rollback removes newly created ordinary labels from the previous update and restores changes to persistent objects to their committed state. The script then runs again with current data. var retains a label reference across bars; it does not make intrabar setter changes permanent.

  • An ungated label.new call can appear on one tick and disappear on the next. Repeated tick executions do not automatically leave one permanent label per tick.
  • barstate.isconfirmed is true on historical bars and on the closing update of a realtime bar. Here it gates event creation, while barstate.islast gates only the live readout setters.
  • Do not create a persistent event only under barstate.isnew and assume it survives subsequent updates. The opening-tick drawing can be removed by rollback before close.
  • This example uses chart-timeframe data. Confirmation does not repair future-data leaks or validate requested higher-timeframe data. A successful syntax check also does not test these chart behaviors.
Update with previous close 99Live blue labelNew event label
Current close 101; bar openShows 101 and OpenNone: bar is not confirmed
Current close falls to 99; bar openShows 99 and OpenNone
Bar closes at 99Shows 99 and ClosedNone: crossing condition is false
Alternative: bar closes at 101Shows 101 and ClosedOne confirmed event, then queue pruning

Give the coding agent a retention rule it can check

Supply the full script, Pine version, triggering condition, coordinate mode, text format and maximum retained events. For this example, request one live readout plus three confirmed crossing labels, with no event at equality and no additional label while the bar is open. Ask Pineify to revise the code and run a syntax check. Then inspect the Data Window counts, fourth-event deletion and realtime updates in TradingView.

Where Pineify fits

Pine Script AI Coding Agent

Generate or revise a Pine Script from your label rules. Include expected counts and bar states, request syntax validation, and verify object retention and placement on a chart.

Review label logic with AI

Frequently asked questions

Educational programming example, not investment advice or a tested trading strategy. Syntax validation does not establish runtime chart behavior or trading performance.

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.