Pine Script: How to Create and Manage Boxes with Arrays?

Working with graphical objects is fundamental for building informative indicators and strategies in Pine Script. While basic static objects are straightforward, dynamic scenarios—where you need to draw, modify, or remove objects based on changing conditions—require more sophisticated management. This is where arrays become invaluable, particularly when dealing with multiple box.new objects.

Introduction to Boxes and Arrays in Pine Script

Pine Script provides several drawing objects like line, label, box, polyline, and polygon. Each serves a specific purpose for visualizing data or regions on the chart. Managing a single instance of these objects is simple, but handling many simultaneously or creating/deleting them dynamically introduces complexity.

What are Boxes in Pine Script?

The box.new function creates a rectangular area on the chart defined by two time/price points or two bar index/price points. It’s ideal for highlighting specific price zones, trading sessions, or periods of interest. A box object is created once, and its properties (like coordinates, color, border, text) can be modified later using functions like box.set_top, box.set_color, etc., by referencing its unique ID.

Understanding Arrays for Dynamic Box Management

Arrays in Pine Script allow you to store collections of values, including the unique IDs returned by drawing object constructors like box.new. Instead of declaring numerous individual variables to hold box IDs (which is impractical for a variable number of boxes), an array provides a single structure to manage them. This enables iteration, dynamic addition, and removal of objects.

Why Use Arrays to Manage Boxes?

Using arrays for boxes offers several key advantages:

  • Dynamic Creation/Deletion: Easily create new boxes and add their IDs to the array as conditions are met, and remove/delete them when no longer needed.
  • Batch Operations: Iterate through the array to modify properties (color, visibility) or delete multiple boxes efficiently.
  • Scalability: Handle an arbitrary number of boxes without needing to pre-declare countless variables.
  • Code Readability: Centralize the management logic for graphical objects, making the script cleaner and easier to understand.

Creating and Initializing Box Arrays

To manage boxes with arrays, you need an array to store the unique integer IDs returned by box.new. Since box IDs are integers, you’ll typically use an int[] array.

Declaring an Array to Store Box IDs

Declare an array of integers to hold the IDs. It’s good practice to initialize it as an empty array or with a predefined size if you know the maximum number of boxes upfront (though dynamic resizing is often preferred).

// Declare an array to hold box IDs
var boxIds = array.new<int>()

Using var ensures the array persists across bars. The <int> type explicitly defines it as an array of integers.

Dynamically Resizing Box Arrays

Pine Script arrays are dynamic by default. You don’t typically need to pre-allocate a size unless you have specific performance considerations or a known maximum limit. You can add elements to the array using array.push().

// Add a new box ID to the array
array.push(boxIds, newBoxId)

Similarly, you can remove elements using functions like array.pop() (removes the last element) or array.remove() (removes at a specific index).

Initializing Boxes and Storing Their IDs in the Array

When you create a box using box.new, it returns an integer ID. You should immediately store this ID in your array.

// Example: Create a box based on a condition and store its ID
var boxIds = array.new<int>()

if someCondition
    // Define box coordinates (using bar index and price for simplicity)
    int leftBarIndex = bar_index - 10
    int rightBarIndex = bar_index
    float topPrice = high
    float bottomPrice = low

    // Create the box and get its ID
    int newBoxId = box.new(
         leftBarIndex,
         topPrice,
         rightBarIndex,
         bottomPrice,
         bgcolor=color.new(color.blue, 80)
         )

    // Add the ID to the array
    array.push(boxIds, newBoxId)

This pattern allows you to track every box created dynamically by your script.

Managing Boxes with Arrays: Key Operations

Once box IDs are stored in an array, you can perform various operations by iterating through the array or accessing elements by index.

Creating New Boxes and Adding to the Array

This is covered in the initialization step. The core logic is: check a condition, if true, call box.new with the desired coordinates and properties, and then array.push the returned ID into your management array.

// Continuation of previous example structure

// Logic to create a new box if needed
if anotherCondition
    int newBoxId = box.new(...
         // box creation parameters
         )
    array.push(boxIds, newBoxId)

This allows your script to create multiple boxes over time as different conditions are met.

Accessing and Modifying Existing Boxes Using Array Indices

To modify a box, you need its ID. You can retrieve IDs from the array using array.get(). Then, pass this ID to the relevant box.set_... functions.

// Example: Change the color of the oldest box (first element in array)
if array.size(boxIds) > 0
    int oldestBoxId = array.get(boxIds, 0)
    // Check if the ID is valid (not null or a default sentinel value if you use one)
    if oldestBoxId
        box.set_bgcolor(oldestBoxId, color.new(color.green, 60))
        box.set_border_color(oldestBoxId, color.green)

You can iterate through the array to modify multiple boxes based on their indices or other criteria.

Deleting Boxes and Removing IDs from the Array

When a box is no longer needed, you should delete it from the chart using box.delete() and then remove its ID from the array to keep the array clean and avoid referencing deleted objects.

// Example: Delete the oldest box and remove its ID from the array
if array.size(boxIds) > 0
    int oldestBoxId = array.get(boxIds, 0)
    // Check for valid ID before attempting deletion
    if oldestBoxId
        box.delete(oldestBoxId)
        // Remove the ID from the array. This shifts subsequent elements.
        array.remove(boxIds, 0)

Removing from the array is crucial. If you only delete the box but keep the ID in the array, you’ll be iterating over invalid IDs, potentially leading to runtime errors if you try to modify them.

Iterating Through the Array to Manage Multiple Boxes

Looping through the array is the standard way to apply operations to all managed boxes or find specific ones.

// Example: Check all boxes and change color if they overlap with current price
if array.size(boxIds) > 0
    for i = 0 to array.size(boxIds) - 1
        int currentBoxId = array.get(boxIds, i)

        // Ensure the ID is valid
        if currentBoxId
            // Get box coordinates to check overlap
            float boxTop = box.get_top(currentBoxId)
            float boxBottom = box.get_bottom(currentBoxId)

            if close <= boxTop and close >= boxBottom
                box.set_bgcolor(currentBoxId, color.new(color.yellow, 50))
            else
                // Optionally revert color if no longer overlapping
                box.set_bgcolor(currentBoxId, color.new(color.blue, 80))

Use a for loop with the array size to iterate. Remember to get the ID inside the loop before using it.

Practical Examples: Box Array Implementation

Let’s look at more complete code snippets demonstrating common use cases.

Example 1: Creating Boxes Based on Price Action

This example creates a box whenever the price breaks above the high of the previous n bars, highlighting the breakout zone.

//@version=5
indicator("Breakout Boxes", overlay=true)

int lookback = input.int(20, "Breakout Lookback", minval=1)
int maxBoxes = input.int(50, "Maximum Boxes", minval=1)

// Array to store box IDs
var boxIds = array.new<int>()

// Calculate highest high of the last 'lookback' bars
float highestHigh = ta.highest(lookback)

// Check for a new breakout (current high above previous highest high)
bool isBreakout = high > highestHigh[1]

// Create a new box on breakout
if isBreakout
    // Define box coordinates: from 'lookback' bars ago up to current bar
    // Top is the new high, bottom is the previous highest high
    int leftBar = bar_index - lookback
    int rightBar = bar_index
    float topCoord = high
    float bottomCoord = highestHigh[1]

    // Create the box
    int newBoxId = box.new(
         leftBar,
         topCoord,
         rightBar,
         bottomCoord,
         bgcolor=color.new(color.green, 70),
         border_color=color.green,
         border_style=line.style_solid,
         border_width=2
         )

    // Add the new box ID to the array
    array.push(boxIds, newBoxId)

// --- Manage maximum number of boxes --- 
// If the array size exceeds maxBoxes, delete the oldest box
if array.size(boxIds) > maxBoxes
    int oldestBoxId = array.shift(boxIds) // shift removes and returns the first element
    if oldestBoxId // Check if ID is valid before deleting
        box.delete(oldestBoxId)

// --- Optional: Adjust box end time as new bars arrive --- 
// Iterate through existing boxes and extend their right border to the current bar
// This makes boxes grow from their start point
if array.size(boxIds) > 0
    for i = 0 to array.size(boxIds) - 1
        int currentBoxId = array.get(boxIds, i)
        if currentBoxId
            box.set_right(currentBoxId, bar_index)

// Plotting or further logic here...
// plot(highestHigh, color=color.gray, title="Previous Highest High")

This example demonstrates creating boxes dynamically and managing their count by deleting the oldest when a limit is reached.

Example 2: Highlighting Specific Time Periods with Boxes

This snippet shows how to use arrays to draw boxes around specific time sessions (e.g., market hours).

//@version=5
indicator("Session Boxes", overlay=true)

string sessionInput = input.session("0930-1600", "Session Times")
int maxSessions = input.int(10, "Max Visible Sessions", minval=1)

// Array to store box IDs for sessions
var sessionBoxIds = array.new<int>()

// Check if current bar is the start of the session
bool isSessionStart = not session.ismarket and session.ismarket[1]

// Check if current bar is inside the session
bool inSession = session.ismarket

// Bar index at session start
var int sessionStartBar = na

// Price level for the top/bottom of the box (can be adjusted)
float boxLevel = na // Use a relevant price level, e.g., session open, vwap, etc.

// Find the first bar of the session and record its index/price
if isSessionStart
    sessionStartBar := bar_index
    // Decide how to set the box vertical position - here using current close
    boxLevel := close

// When session ends or new session starts while inside one
// or simply on the last bar of the chart, finalize the box
if (not inSession and inSession[1]) or barstate.islast
    if not na(sessionStartBar)
        // Define box coordinates
        int leftBar = sessionStartBar
        int rightBar = bar_index // End at current bar index
        // Use actual high/low of the session duration for vertical bounds
        float sessionHigh = ta.highest(math.max(1, bar_index - sessionStartBar + 1))
        float sessionLow = ta.lowest(math.max(1, bar_index - sessionStartBar + 1))

        // Create the box
        int newBoxId = box.new(
             leftBar,
             sessionHigh,
             rightBar,
             sessionLow,
             bgcolor=color.new(color.orange, 80),
             border_color=color.orange
             )

        // Add ID to array
        array.push(sessionBoxIds, newBoxId)

        // Reset start bar for the next session
        sessionStartBar := na
        boxLevel := na

// --- Manage maximum number of session boxes --- 
// If the array size exceeds maxSessions, delete the oldest session box
if array.size(sessionBoxIds) > maxSessions
    int oldestSessionBoxId = array.shift(sessionBoxIds)
    if oldestSessionBoxId
        box.delete(oldestSessionBoxId)

This example uses session data to trigger box creation and manages a fixed number of historical session boxes.

Example 3: Dynamically Adjusting Box Positions Based on Array Data

This scenario involves creating boxes and then potentially moving them or resizing them based on subsequent events or calculations. We’ll track a simple moving level and adjust boxes based on its movement.

//@version=5
indicator("Adjustable Level Boxes", overlay=true)

int boxDuration = input.int(10, "Box Duration Bars", minval=1)
int maxActiveBoxes = input.int(5, "Max Active Boxes", minval=1)

// Array to store box IDs
var levelBoxIds = array.new<int>()

// Calculate a simple moving level (e.g., SMA of close)
float dynamicLevel = ta.sma(close, 5)

// --- Create a new box periodically or on condition ---
// Example: Create a box every 10 bars
bool createNewBox = bar_index % boxDuration == 0

if createNewBox
    // Define initial box coordinates
    int leftBar = bar_index
    int rightBar = bar_index + boxDuration // Define future end point
    float currentLevel = close // Start box at current close

    int newBoxId = box.new(
         leftBar,
         currentLevel + atr(14),
         rightBar,
         currentLevel - atr(14),
         bgcolor=color.new(color.red, 70),
         border_color=color.red
         )

    // Add ID to array
    array.push(levelBoxIds, newBoxId)

// --- Manage maximum active boxes --- 
// If array size exceeds limit, delete the oldest
if array.size(levelBoxIds) > maxActiveBoxes
    int oldestId = array.shift(levelBoxIds)
    if oldestId
        box.delete(oldestId)

// --- Dynamically Adjust Boxes --- 
// Iterate through existing boxes and adjust their vertical position
// based on the current dynamic level
if array.size(levelBoxIds) > 0
    for i = 0 to array.size(levelBoxIds) - 1
        int currentBoxId = array.get(levelBoxIds, i)

        if currentBoxId
            // Get the original vertical center of the box (approximation)
            // float originalTop = box.get_top(currentBoxId) // Note: box.get_top/bottom return current coords, not original creation coords
            // A better approach might be to store the original level alongside the ID, or recalculate based on the box's start bar.

            // For simplicity in this example, let's just center the box vertically
            // around the current dynamicLevel, maintaining its height.
            float boxHeight = box.get_top(currentBoxId) - box.get_bottom(currentBoxId)
            float newTop = dynamicLevel + boxHeight / 2
            float newBottom = dynamicLevel - boxHeight / 2

            box.set_top(currentBoxId, newTop)
            box.set_bottom(currentBoxId, newBottom)
            // Optionally update the right boundary as well
            // box.set_right(currentBoxId, bar_index)

            // Example: Change color if level touches box
            float boxTop = box.get_top(currentBoxId)
            float boxBottom = box.get_bottom(currentBoxId)
            if dynamicLevel <= boxTop and dynamicLevel >= boxBottom
                 box.set_bgcolor(currentBoxId, color.new(color.yellow, 60))
            else
                 box.set_bgcolor(currentBoxId, color.new(color.red, 70))

This demonstrates modifying existing boxes based on a calculation (dynamicLevel) performed on the current bar. Note the comment about retrieving original coordinates – this highlights a limitation: box.get_top/bottom give the current state. If you need the original creation values, you must store them in separate arrays, indexed in parallel to your boxIds array.

Best Practices and Advanced Techniques

Efficient and robust box management with arrays requires attention to detail and awareness of Pine Script’s execution model.

Optimizing Array Usage for Performance

  • Limit Array Size: Unbounded arrays can consume significant memory. Implement limits on the number of active boxes/IDs stored, deleting the oldest when the limit is exceeded (as shown in examples using array.shift()).
  • Minimize Array Operations: Operations like array.push() and array.remove() have overhead. If possible, structure your logic to minimize these, especially within tight loops or on every bar if not strictly necessary.
  • Avoid Excessive Object Creation/Deletion: Creating and deleting graphical objects on every bar or frequently can impact performance, especially on lower timeframes or for long history. Create objects only when required and manage their visibility (box.set_visible()) if you need to hide them temporarily instead of deleting.

Error Handling and Validation in Box Array Management

  • Check for Valid IDs: box.new returns an integer ID, but array functions can return na or unexpected values if the array is empty or accessed incorrectly. Crucially, when retrieving an ID from the array before using box.set_... or box.delete(), always check if the ID is valid (not na, not 0 if you use 0 as a sentinel, or simply check if it’s non-zero as object IDs are positive integers). if myBoxId is a common way to check if the ID is valid/non-zero.
  • Bounds Checking: When accessing array elements by index using array.get(), ensure the index is within the valid range [0, array.size() - 1] to prevent runtime errors.
  • Handle Empty Arrays: Always check array.size() before attempting to access elements or loop through an array.

Combining Box Arrays with Other Pine Script Features

Arrays of box IDs are powerful when combined with other features:

  • Parallel Arrays: Store related data (e.g., original price level, creation bar index, specific state) in separate arrays indexed in parallel with your box ID array. This allows you to retrieve context-specific information when iterating through box IDs.
  • User Inputs: Allow users to configure the number of boxes, their colors, durations, or the conditions that trigger their creation using input.* functions.
  • Alerts: Trigger alerts based on interactions between price and the managed boxes (e.g., price entering a box, price breaking out of a box).
  • Strategies: Use the presence, position, or properties of boxes managed by arrays as criteria for generating trading signals (entry/exit orders).

By mastering the use of arrays to manage box.new objects, you gain the ability to create dynamic, sophisticated, and visually informative indicators and strategies that react intelligently to market conditions.


Leave a Reply