Var Line in Pine Script: How to Use, Examples, and Best Practices?

Drawing objects on a TradingView chart dynamically is a powerful feature for visualizing trading ideas, marking key levels, or automating chart pattern recognition. Pine Script provides several ways to handle graphical objects like lines, labels, and boxes. For lines that need to persist across bars and be modified over time, understanding the var line declaration is fundamental.

As experienced Pine Script developers, we constantly leverage these drawing tools to build sophisticated indicators and strategies. This article dives deep into using var line, distinguishing it from other line concepts, providing practical examples, and sharing best practices for integration into your trading scripts.

Introduction to var line in Pine Script

Persistent graphical objects are essential for scripts that analyze historical data or need to maintain visual references across multiple bars. Lines are among the most common objects used for this purpose.

What is var line and why use it?

In Pine Script, variables declared with the var keyword are special. They are initialized only once on the first bar (bar_index == 0) and retain their value across subsequent bars. This persistence is crucial for graphical objects like lines. When you declare a variable to hold a reference to a line object using var line, you create a container that will hold the unique ID of the line created on the first bar. This ID allows you to modify the same line object on subsequent bars using functions like line.set_xy().

Without var, a variable assigned a line ID would be re-declared and potentially re-initialized on every bar, which isn’t how you manage a single, persistent line object. var ensures the line is created once and its reference is stored for modification throughout the script’s execution on different bars.

The primary use cases for var line include:

  • Drawing trend lines that update as new price data arrives.
  • Maintaining dynamic support and resistance levels.
  • Highlighting channel boundaries or other chart patterns that evolve.
  • Creating visual cues tied to specific events or conditions that need to persist.

Key differences between line, var line, and line.new()

It’s important to distinguish between the Pine Script type line, the var keyword used for variable declaration, and the function line.new().

  • line: This is the type in Pine Script that represents a line object. When you declare a variable to hold a line ID, you specify its type as line, e.g., line myLine = na. Note that in Pine Script v5+, mutable objects assigned to variables (like line IDs returned by line.new()) are generally persistent across bars even without var, if they are initialized within the scope of the script execution on a bar. However, using var explicitly ensures the variable declaration and initial assignment happens only once on the first bar, which is the correct pattern for creating a single graphical object.
  • var line: This is the most common and recommended way to declare a variable that will hold the ID of a persistent line object. The var keyword ensures that the variable is declared and initialized only on the first bar, and its value (the line ID) persists across bars. This is essential because line.new() should typically only be called once per line instance you want to exist.
  • line.new(): This is the function used to create a new line object on the chart. It returns a unique ID of type line that refers to the newly created object. line.new() is typically called conditionally, often wrapped within an if barstate.isfirst block or, more commonly and implicitly correctly, as the initial assignment to a var line variable, which ensures it’s called only on the first bar.

So, in practice, you almost always use var line in conjunction with line.new() like this:

//@version=5
indicator("My Persistent Line", overlay=true)

// Declare a variable to hold the line ID, initialized on the first bar
var line myLine = line.new(x1=na, y1=na, x2=na, y2=na)

// Rest of your logic to update the line using myLine ID

This structure ensures myLine holds a valid line ID created on the first bar, which you can then modify on every subsequent bar.

Basic syntax and usage of var line

The basic syntax involves declaring the variable with var and the type line, then initializing it by calling line.new():

var line myLine = line.new(x1, y1, x2, y2, extend, color, style, width)

Where:

  • x1, y1, x2, y2: Integers or floats representing the coordinates of the start (x1, y1) and end (x2, y2) points. x-coordinates are bar indices (integers), and y-coordinates are price values (floats). You can use na if you don’t have initial coordinates.
  • extend: An optional argument of type extend.none, extend.left, extend.right, or extend.both to make the line extend indefinitely past its endpoints.
  • color: An optional argument of type color to set the line’s color.
  • style: An optional argument of type line.style_solid, line.style_dotted, or line.style_dashed.
  • width: An optional integer argument for the line’s thickness.

As noted, the initialization line.new(...) should typically occur as the assignment to the var declared variable to ensure it only runs on the first bar.

Drawing and Modifying Lines with var line

Once a var line variable holds a line ID, you can manipulate its properties on subsequent bars using a suite of line functions.

Creating initial lines using var line.new()

The creation happens on the first bar. A common pattern is to create it with dummy or na coordinates if the true starting points depend on calculations from later bars or conditions. Then, update its coordinates when the relevant information is available.

//@version=5
indicator("Dynamic Line Example", overlay=true)

// Declare and create the line on the first bar
var line myLine = line.new(na, na, na, na, color=color.blue, width=2)

// Now, on any bar, myLine refers to the same line object.
// We can update it later.

This var declaration ensures myLine is ready to hold a line ID and line.new is called just once.

Updating line positions dynamically

The primary way to update a line’s position is using line.set_xy(). This function takes the line ID and the new coordinates for both points.

// Assuming myLine was declared with var line.new(...) on the first bar

float start_y = nz(low[10], close) // Example start y-coordinate
float end_y = nz(high[1], close)   // Example end y-coordinate

// Update the line on the current bar
line.set_xy(id=myLine, x1=bar_index[10], y1=start_y, x2=bar_index[1], y2=end_y)

You can call line.set_xy() on every bar to make the line track specific price points or bars dynamically. bar_index provides the current bar’s x-coordinate.

Customizing line appearance (color, width, style)

You can modify a line’s visual properties using dedicated functions, typically called on bars after the line has been created.

  • line.set_color(id, color): Changes the line’s color.
  • line.set_width(id, width): Changes the line’s width.
  • line.set_style(id, style): Changes the line’s style.
  • line.set_extend(id, extend): Changes how the line extends.

Example:

// Assuming myLine is a valid line ID

if close > open
    line.set_color(myLine, color.green)
else
    line.set_color(myLine, color.red)

if bar_index % 20 == 0 // Every 20 bars
    line.set_style(myLine, line.style_dotted)
else
    line.set_style(myLine, line.style_solid)

These functions allow your lines to change appearance based on market conditions or bar progress.

Extending lines with line.set_x1(), line.set_y1(), line.set_x2(), and line.set_y2()

While line.set_xy() changes both points simultaneously, you can also modify individual coordinates using:

  • line.set_x1(id, x): Sets the x-coordinate of the first point.
  • line.set_y1(id, y): Sets the y-coordinate of the first point.
  • line.set_x2(id, x): Sets the x-coordinate of the second point.
  • line.set_y2(id, y): Sets the y-coordinate of the second point.

These are useful if only one endpoint needs to be updated or if you’re building a line incrementally. For instance, drawing a line starting at a fixed past point and extending it to the current bar:

// Assuming myLine is created on bar_index == 0 with na coordinates

int start_bar = 100 // Example fixed start bar index
float start_price = close[start_bar]

if bar_index == start_bar // Set the first point when we reach the start bar
    line.set_xy(myLine, start_bar, start_price, na, na)

if bar_index >= start_bar // On or after the start bar, update the end point
    line.set_x2(myLine, bar_index)
    line.set_y2(myLine, close)

This pattern allows for lines that start at a specific historical event and extend to the current price.

Practical Examples of Using var line in Trading Strategies

Here are a few common ways var line is applied in real-world scripts.

Drawing trend lines based on price action

Identifying and visualizing trend lines is a classic use case. You can detect swing highs/lows and draw lines connecting them.

//@version=5
indicator("Auto Trend Line", overlay=true)

// Simple swing high/low detection (requires more sophisticated logic in practice)
float swingHigh = ta.pivothigh(high, 5, 5)
float swingLow = ta.pivotlow(low, 5, 5)

// Keep track of the most recent swing points
var int lastHighBar = na
var float lastHighPrice = na
var int lastLowBar = na
var float lastLowPrice = na

if not na(swingHigh)
    lastHighBar := bar_index[5] // Pivot is 5 bars ago
    lastHighPrice := swingHigh

if not na(swingLow)
    lastLowBar := bar_index[5]
    lastLowPrice := swingLow

// Declare persistent trend lines
var line downtrend = line.new(na, na, na, na, color=color.red, width=2, extend=extend.right)
var line uptrend = line.new(na, na, na, na, color=color.green, width=2, extend=extend.right)

// Update lines if we have at least two points
// This is a very basic example; real trend lines need more complex logic
// to select relevant pivots and manage multiple lines.

// Example: connect last two swing highs (simplified)
// Requires tracking previous swing highs, not just the last one
// A real implementation would store pivots in arrays.

// For illustration: just draw a line from a fixed point to the last high
var line exampleLine = line.new(na, na, na, na, color=color.orange, width=1)

if bar_index > 100 // Start drawing after 100 bars for context
    line.set_xy(exampleLine, 100, high[100], bar_index, high) // Line from bar 100 high to current high

// A more realistic scenario involves storing pivot points and drawing lines between recent valid ones.
// This often requires arrays (see Advanced Techniques).

This simplified example shows the structure. A professional script would use arrays to store multiple historical pivot points and draw lines between valid combinations, deleting old lines as new, more relevant ones appear.

Creating dynamic support and resistance levels

Similar to trend lines, var line is perfect for drawing horizontal S/R levels that might adjust over time or represent significant historical prices.

//@version=5
indicator("Dynamic S/R", overlay=true)

// Example: A support line based on a past significant low
var float majorLowPrice = na
var int majorLowBar = na

// Identify a significant low (simplified)
if ta.barssince(low == ta.lowest(low, 50)) == 0
    majorLowPrice := low
    majorLowBar := bar_index

// Declare a persistent support line
var line supportLine = line.new(na, na, na, na, color=color.blue, style=line.style_dashed, width=2, extend=extend.right)

// Update the support line's position only if we have a valid low identified
if not na(majorLowPrice)
    // Set the first point at the significant low bar
    line.set_xy(supportLine, majorLowBar, majorLowPrice, bar_index, majorLowPrice)
    // The second point's x-coordinate is the current bar, keeping the line horizontal.

// Example: A resistance line that follows a moving average (less common, but shows dynamism)
float resistancePrice = ta.sma(high, 20)

// Declare a persistent resistance line
var line resistanceLine = line.new(na, na, na, na, color=color.red, style=line.style_dashed, width=2)

// Update resistance line - make it track the MA
// This requires moving both points or using extend
if bar_index > 20 // Wait for MA to be calculated
    line.set_xy(resistanceLine, bar_index[1], resistancePrice[1], bar_index, resistancePrice)

Horizontal lines are often simpler as only the y-coordinate needs tracking, but the principle of creating once (var) and updating dynamically remains.

Highlighting chart patterns automatically

Complex patterns like triangles, flags, or head and shoulders can be visualized by drawing the constituent lines using var line once the pattern is detected. The detection logic would identify key points, and then line.new would be called (via var initialization) to draw the lines connecting those points.

//@version=5
indicator("Flag Pattern Visualizer", overlay=true)

// This is highly simplified pattern detection logic!
// A real pattern detector is complex.

var bool patternDetected = false
var int patternStartBar = na
var float patternStartPrice = na
var int flagpoleTopBar = na
var float flagpoleTopPrice = na
var int flagBottomBar = na
var float flagBottomPrice = na

// Assume detection logic sets the above variables
// For demo, let's simulate detection at a specific bar
if bar_index == 150
    patternDetected := true
    patternStartBar := 100
    patternStartPrice := close[50]
    flagpoleTopBar := 120
    flagpoleTopPrice := high[30]
    flagBottomBar := 145
    flagBottomPrice := low[5]

// Declare persistent lines for the pattern
var line flagpole = line.new(na, na, na, na, color=color.blue, width=3)
var line flagTopLine = line.new(na, na, na, na, color=color.purple, style=line.style_dotted)
var line flagBottomLine = line.new(na, na, na, na, color=color.purple, style=line.style_dotted)

// Draw the pattern lines once detected
if patternDetected and not na(patternStartBar)
    // Draw flagpole
    line.set_xy(flagpole, patternStartBar, patternStartPrice, flagpoleTopBar, flagpoleTopPrice)
    // Draw flag channel lines (example points)
    line.set_xy(flagTopLine, flagpoleTopBar, flagpoleTopPrice, flagBottomBar + 5, flagBottomPrice + (flagpoleTopPrice - flagBottomPrice)/2)
    line.set_xy(flagBottomLine, flagpoleTopBar, flagpoleTopPrice, flagBottomBar + 5, flagBottomPrice - (flagpoleTopPrice - flagBottomPrice)/2)

// In a real script, you might update flag lines as pattern evolves or delete them on break out.

The challenge here is the pattern detection logic, not the line drawing itself, which is straightforward using var line once the key points are known.

Implementing dynamic alerts based on line interactions

Lines can also be used as references for generating alerts. You can check if the price crosses a dynamic line you’ve drawn.

//@version=5
indicator("Alert Line Example", overlay=true)

// Declare a line
var line alertLine = line.new(na, na, na, na, color=color.fuchsia, width=2, extend=extend.right)

// Example: Draw a line at a significant high and extend it
var float significantHigh = ta.highest(high, 20)
var int highBar = ta.barssince(high == significantHigh)

// Only draw/update if we have a valid high within last 20 bars
if highBar >= 0 and highBar < 20
    line.set_xy(alertLine, bar_index[highBar], significantHigh, bar_index, significantHigh)
    // Keep the line horizontal from the significant high

// Check for price crossing the line
bool crossUp = ta.crossover(close, line.get_y2(alertLine))
bool crossDown = ta.crossunder(close, line.get_y2(alertLine))

// Trigger alerts
alertcondition(crossUp, "Price crossed above alert line!")
alertcondition(crossDown, "Price crossed below alert line!")

// You could also color the line on cross
if crossUp
    line.set_color(alertLine, color.lime)
else if crossDown
    line.set_color(alertLine, color.red)
else
    line.set_color(alertLine, color.fuchsia)

line.get_y2(alertLine) retrieves the current y-coordinate of the second point of the line, allowing you to compare price directly to the line’s level. This is particularly useful for horizontal lines (extend=extend.right).

Advanced Techniques and Best Practices

Moving beyond basic usage, consider these techniques for more robust scripts.

Using var line with loops and conditional statements

While var line itself handles the one-time initialization, its modification is often driven by loops or complex conditions that evaluate on every bar.

Example: Drawing multiple short lines based on a condition.

//@version=5
indicator("Conditional Lines", overlay=true)

int lineLength = 10

// This approach is generally discouraged for drawing many lines
// that persist and require tracking. See array example next.
// var line conditionalLine = line.new(na,na,na,na) // Don't do this for many lines!

// Better: Create and manage lines based on specific events
if close > open and close[1] <= open[1] // Example condition: bullish engulfing
    // Draw a short horizontal line at the low of the engulfing bar
    // We need a way to *only* draw if it hasn't been drawn for this event yet.
    // This is where managing lines becomes complex without arrays/dictionaries.
    // A simpler approach might be to draw and immediately delete if needed, but this is inefficient.
    // The correct approach for multiple conditional objects involves tracking them.

    // Let's show simple update based on loop index (less practical, more illustrative of loops + lines)
    int numLines = 5
    for i = 0 to numLines - 1
        // This loop runs on *every* bar. Creating lines inside loop *without* `var`
        // or careful management leads to errors or too many objects.
        // The `var` approach requires an array.
        na

// See the array example below for the standard way to manage multiple lines.

The key takeaway is that while loops run per bar, var ensures the declaration and initial creation of the line variable happen once. To manage multiple lines created by conditions or loops that trigger over time, you need to store their IDs, often in an array.

Efficiently managing multiple lines using arrays

Drawing numerous lines (e.g., for Fibonacci levels, past S/R zones, or channel history) requires managing multiple line IDs. Arrays are the standard Pine Script tool for this.

//@version=5
indicator("Managed Lines with Array", overlay=true)

int maxLines = 10 // Max number of lines to display

// Declare a persistent array to hold line IDs
var line[] myLines = array.new_line()

// Example: On a new swing high, draw a horizontal line and add its ID to the array
float currentSwingHigh = ta.pivothigh(high, 10, 10)

if not na(currentSwingHigh)
    // Create a new line at the detected high
    line newLine = line.new(bar_index[10], currentSwingHigh, bar_index, currentSwingHigh, extend=extend.right, color=color.red, width=1, style=line.style_dotted)

    // Add the new line ID to the array
    array.push(myLines, newLine)

// Manage array size: remove the oldest line if exceeding maxLines
if array.size(myLines) > maxLines
    line oldestLine = array.shift(myLines) // Get and remove the first element
    line.delete(oldestLine) // Delete the graphical object from the chart

// Optionally, update existing lines (e.g., extend them)
// Loop through the array to update
for i = 0 to array.size(myLines) - 1
    line currentLine = array.get(myLines, i)
    // Update the end point to the current bar index if needed
    line.set_x2(currentLine, bar_index)

This pattern is robust: create lines conditionally, store their IDs in a var array, and manage the array size by deleting old lines from the chart when they fall off the array. The var keyword on the array ensures the array itself persists across bars.

Avoiding common mistakes when using var line

  • Calling line.new() on every bar: This is a common error. line.new() should generally be called only once per line instance. Using var line myLine = line.new(...) correctly handles this by ensuring the call happens only on the first bar.
  • Not storing the line ID: The ID returned by line.new() is your handle to the line object. If you don’t store it in a persistent variable (like var line myLine), you lose the ability to modify or delete the line later.
  • Modifying a deleted line: If you use line.delete(), the line ID becomes invalid. Attempting to call line.set_xy() or other modification functions on an invalid ID will result in a runtime error. Always manage your line IDs carefully, especially when using arrays or conditional deletion.
  • Incorrect coordinate types: Remember that x-coordinates are bar indices (integers), and y-coordinates are price levels (floats).
  • Performance with too many lines: Drawing hundreds or thousands of lines can slow down your script and TradingView. Be mindful of how many objects you create and manage them efficiently, deleting those that are no longer needed or visible.

Optimizing performance for complex strategies

  • Limit the number of lines: Don’t draw unnecessary lines. If a line is no longer relevant (e.g., an old S/R level), delete it using line.delete().
  • Efficient updates: Only update line properties when necessary. If a line’s position or appearance doesn’t change on a given bar, don’t call the line.set_* functions.
  • Use arrays wisely: While arrays are great for managing multiple lines, operations on very large arrays (thousands of elements) can impact performance. Consider if you need to store every single line history or just the most recent/relevant ones.
  • Conditional drawing: If lines are only relevant under certain conditions (e.g., only in a detected pattern), ensure they are only created and managed when those conditions are met.

Managing line objects in Pine Script requires careful state management, similar to managing any other persistent data structure. The var keyword simplifies the persistence of the variable holding the line ID, but the logic for creating, updating, and deleting the lines themselves is up to the developer.

Troubleshooting and Limitations

Even with careful coding, you might encounter issues or limitations when working with graphical objects.

Common issues with line drawing and solutions

  • Lines disappear unexpectedly: This can happen if line.delete() is called unintentionally or if the line ID variable is overwritten or goes out of scope (though var helps prevent the latter across bars). Check your deletion logic. Also, lines might disappear if their coordinates go far off the visible chart, though they still exist in the script’s memory unless deleted.
  • Too many lines drawn: This is usually a result of calling line.new() on multiple bars instead of just once per intended line instance. Ensure line.new() is inside a var declaration or a condition that is true only when a new line is actually required.
  • Runtime errors related to line IDs: Often caused by attempting to use a line ID that is na (because line.new failed or wasn’t called) or an ID that refers to a line that has been deleted. Always check if the line ID is valid (line.is_valid(id)) before attempting to modify or delete it, especially when managing IDs in arrays.
  • Lines not updating: Ensure your line.set_xy() (or other setter) calls are within the execution path on the bars where you expect updates. Check your conditional logic.

Understanding the limitations of var line in Pine Script

  • Limited interactivity: Lines drawn by Pine Script are primarily for visualization. Users cannot directly click and drag these lines on the chart to change their position or properties from the UI in a way that feeds back into the script’s variables. Interactivity is limited to script inputs or possibly specific object methods if available (less common for lines).
  • Performance at scale: While improving, Pine Script can become slow if you manage a very large number of graphical objects simultaneously (e.g., thousands). There are internal limits to the number of objects a script can create.
  • Complexity of management: For complex scenarios involving many lines that need to be added, updated, and removed based on intricate conditions, the management logic using arrays can become quite complex.
  • Debugging: Debugging issues related to persistent objects and arrays can be trickier than debugging simple plot values, requiring careful use of log.info() or plotting state variables.

Alternative approaches for complex charting requirements

For highly complex charting needs or scenarios requiring rich user interaction with chart objects (like drawing and having the script react), consider:

  • TradingView Drawing Tools: Allow users to manually draw objects (lines, shapes, text). These are not controlled by Pine Script, but scripts can potentially read some drawing object data if shared publicly (a feature with limitations and privacy considerations).
  • External Libraries/Platforms: For extremely advanced visualization or interaction beyond Pine Script’s capabilities, you might need to look into using TradingView’s charting library or other charting solutions outside of a Pine Script indicator/strategy context.

For most dynamic charting visualizations within a Pine Script strategy or indicator, var line with array management is the workhorse. Mastering it is key to building powerful visual tools on TradingView.

By understanding the persistence offered by var, combined with the creation (line.new) and modification (line.set_*) functions, you can effectively draw and manage dynamic lines on your chart, greatly enhancing the utility and clarity of your Pine Script creations.


Leave a Reply