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()andarray.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.newreturns an integer ID, but array functions can returnnaor unexpected values if the array is empty or accessed incorrectly. Crucially, when retrieving an ID from the array before usingbox.set_...orbox.delete(), always check if the ID is valid (notna, not0if you use0as a sentinel, or simply check if it’s non-zero as object IDs are positive integers).if myBoxIdis 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.