Variables and Types

Variables are the foundation of every program. Before you can calculate a moving average, check a condition, or plot a line, you need somewhere to store values. This lesson teaches you how Pine Script handles data - and introduces a concept unique to Pine Script that trips up almost every beginner: the difference between regular variables and var variables.

What is a Variable?

Think of a variable as a labeled box. You give it a name (the label) and put a value inside it. Later, you can look at the box by its name to retrieve the value, or you can replace the value with something new.

Pine Script
// Create a box labeled "length" and put the number 14 inside
length = 14

// Create a box labeled "fastMA" and put the result of a calculation inside
fastMA = ta.sma(close, length)

The name length is just a label. You could call it myPeriod or lookbackWindow - the computer doesn't care. But you will care in a week when you're trying to remember what each variable means, so choose names that describe the contents.

Data Types

Every value in Pine Script has a type. The type determines what kind of data the box can hold. Here are the fundamental types, each explained with a real trading use case.

int - Whole Numbers

Integers are numbers without decimal points. Use them for things that are naturally counted in whole units: the number of bars in a lookback period, the number of trades, or a bar index.

Pine Script
//@version=5
indicator("Integer Example")

lookbackPeriod = 14          // Number of bars for RSI calculation
barsSinceEntry = 0           // Counter for bars since last signal
maxTradesPerDay = 3          // A configurable limit

You'd use an integer for lookbackPeriod because "14.7 bars" doesn't make sense - you can't look back a fractional number of bars.

float - Decimal Numbers

Floats are numbers with decimal points. Prices, percentages, and indicator values are almost always floats.

Pine Script
//@version=5
indicator("Float Example")

currentPrice = close         // 145.67, 3289.50, etc.
stopLossPercent = 2.5        // 2.5% below entry
rsiValue = ta.rsi(close, 14) // Returns values like 65.432

Most of the time, Pine Script automatically figures out whether a number is an int or a float. If you write 100, it's an int. If you write 100.0, it's a float. When in doubt, add a decimal point.

bool - True or False

Booleans hold one of two values: true or false. They're the backbone of every trading condition. "Is price above the moving average?" is a boolean question.

Pine Script
//@version=5
indicator("Boolean Example", overlay=false)

isBullish = close > open            // true if the candle is green
isAboveSMA = close > ta.sma(close, 20)  // true if price is above the 20 SMA
isHighVolume = volume > ta.sma(volume, 50) // true if today's volume exceeds average

// Plot 1 when bullish and above SMA, 0 otherwise
plot(isBullish and isAboveSMA ? 1 : 0)

The variables isBullish, isAboveSMA, and isHighVolume each evaluate to either true or false on every bar. You can combine them with and, or, and not to express complex trading rules.

string - Text

Strings hold text. You'll use them mainly for labels, tooltips, and alert messages.

Pine Script
//@version=5
indicator("String Example", overlay=true)

// Build a dynamic label message
direction = close > open ? "Bullish" : "Bearish"
message = "Today is " + direction

// Display it on the last bar
if barstate.islast
    label.new(bar_index, high, message)

Strings are enclosed in double quotes. You can join them together with the + operator, as shown above. The result on the chart is a label that says either "Today is Bullish" or "Today is Bearish" on the most recent bar.

color - Colors

Colors control the appearance of everything you draw. Pine Script has built-in color constants and the ability to create custom colors with transparency.

Pine Script
//@version=5
indicator("Color Example", overlay=true)

// Built-in colors
plot(ta.sma(close, 10), color=color.blue)
plot(ta.sma(close, 20), color=color.red)

// Custom color with transparency (0 = opaque, 100 = invisible)
bgColor = close > open ? color.new(color.green, 85) : color.new(color.red, 85)
bgcolor(bgColor)

This script plots two moving averages in blue and red, and tints the chart background faintly green on bullish bars and faintly red on bearish bars. The 85 in color.new() means 85% transparent - a subtle tint rather than a solid block of color.

Variable Declaration: Regular vs var

This is the most important section of this lesson. The difference between regular variables and var variables is a concept unique to Pine Script, and misunderstanding it is the number one source of bugs for intermediate Pine Script developers.

Regular variables: recalculated on every bar

When you write a regular variable assignment, Pine Script recalculates it from scratch on every single bar. The variable does not "remember" its value from the previous bar.

Pine Script
//@version=5
indicator("Regular Variable")

// This is recalculated on every bar
myValue = 0
myValue := myValue + 1

plot(myValue)

What do you think myValue will be? You might expect it to count up: 1, 2, 3, 4, 5... But it doesn't. On every bar, myValue is set to 0, then incremented to 1. So myValue is always 1. The counter resets every bar because regular variables don't persist.

var variables: initialized once, persist across bars

The var keyword tells Pine Script: "Set this value on the first bar only, and keep it from bar to bar after that."

Pine Script
//@version=5
indicator("Var Variable")

// This is initialized on bar 0 and persists across all bars
var myCounter = 0
myCounter := myCounter + 1

plot(myCounter)

Now myCounter actually counts up. On bar 0, it's initialized to 0, then incremented to 1. On bar 1, it's not reset - it retains the value 1 from the previous bar, then increments to 2. On bar 2, it becomes 3. And so on. The plot shows a steadily rising line.

Side-by-side comparison

Let's make this crystal clear with a concrete trading example. Suppose you want to track the highest closing price the instrument has ever reached on the chart.

Pine Script
//@version=5
indicator("All-Time High Comparison", overlay=true)

// WRONG: Regular variable - resets every bar
highestClose_wrong = close  // Resets to current close on every bar

// RIGHT: var variable - persists and accumulates
var float highestClose_right = 0.0
if close > highestClose_right
    highestClose_right := close

plot(highestClose_wrong, color=color.red, title="Wrong (resets)")
plot(highestClose_right, color=color.green, title="Right (persists)")

The red line just mirrors the closing price - it "forgets" previous values on every bar. The green line ratchets upward, only updating when a new all-time high close is reached. That's the power of var.

⚠️When to use `var`

Use var when you need a variable to accumulate or remember a value across bars - counters, running totals, tracked highs/lows, or state flags. Use regular variables (no var) for calculations that should be fresh on every bar - moving averages, price comparisons, indicator values.

Series Variables: Every Bar Has Its Own Value

Here's a mental model that will clarify most Pine Script confusion: every variable in Pine Script is actually a series - a long column of values, one per bar, stretching back through the entire chart history.

Imagine your chart has 500 bars. The built-in variable close isn't a single number - it's a column of 500 numbers, one for each bar. When your script runs on bar 247, close returns bar 247's closing price. When it runs on bar 248, close returns bar 248's closing price.

The same is true for any variable you create:

Pine Script
//@version=5
indicator("Series Example")

// On each bar, this calculates a new RSI value
rsiValue = ta.rsi(close, 14)

plot(rsiValue)

rsiValue isn't a single number either. It's a series of 500 RSI values (one per bar). Pine Script automatically manages this - you just write the formula and it applies it to every bar.

This series nature is what makes the historical reference operator possible.

The Historical Reference Operator []

The [] operator lets you look backward in time. Think of it as a time machine for your data. close[1] means "the closing price one bar ago." close[5] means "the closing price five bars ago."

Here's a practical example. We want to detect when today's volume is at least double the previous bar's volume - a potential sign that something significant is happening.

Pine Script
//@version=5
indicator("Volume Spike Detector", overlay=false)

// Current bar's volume
currentVolume = volume

// Previous bar's volume - the [1] looks back one bar
previousVolume = volume[1]

// Is current volume at least 2x the previous bar?
isVolumeSpike = currentVolume >= previousVolume * 2

// Plot volume as columns, colored red on spike bars
plot(volume, style=plot.style_columns,
     color=isVolumeSpike ? color.red : color.gray)

When you add this to a chart, most bars will be gray. Occasionally, a bright red column appears - that's a bar where volume at least doubled compared to the previous bar. Professional traders watch for these spikes because they often precede significant moves.

How far back can you look?

You can use any non-negative integer inside []:

Pine Script
close[0]   // Current bar (same as just writing 'close')
close[1]   // 1 bar ago
close[2]   // 2 bars ago
close[10]  // 10 bars ago
close[100] // 100 bars ago

Be aware that on the first few bars of a chart, looking back too far will return na (Pine Script's equivalent of "no data available"). For example, on bar 3, close[5] is na because there is no bar 5 bars before bar 3.

💡The `[]` operator works on any series

You can apply [] to any series variable, not just built-in ones. If you have rsi = ta.rsi(close, 14), then rsi[1] gives you the previous bar's RSI, and rsi[3] gives you the RSI from three bars ago.

Type Conversion

Sometimes you need to convert values from one type to another. The most common conversions are between numbers and strings, or between floats and integers.

Number to string

When you want to display a number inside a label or alert message, you need to convert it to text first.

Pine Script
//@version=5
indicator("Type Conversion", overlay=true)

rsiValue = ta.rsi(close, 14)

// Convert the float RSI value to a string with 1 decimal place
rsiText = str.tostring(rsiValue, "#.#")
message = "RSI: " + rsiText

if barstate.islast
    label.new(bar_index, high, message)

Without str.tostring(), trying to combine a number with a string using + would cause a type error. The "#.#" format tells Pine Script to show one decimal place - so 65.432 becomes "65.4".

Float to integer

Sometimes you need a whole number where you have a decimal. The int() function truncates (it doesn't round - it just drops the decimal part).

Pine Script
floatValue = 14.7
intValue = int(floatValue)  // Result: 14 (not 15)

Integer to float

This conversion happens automatically in most cases. If you assign an integer to a context that expects a float, Pine Script handles it silently.

Pine Script
intLength = 20
floatLength = float(intLength)  // Explicit conversion: 20.0

Scope

Scope determines where a variable is visible. A variable created inside a function or if block is local to that block - it doesn't exist outside of it.

Global scope

Variables declared at the top level of your script (not inside any block) are visible everywhere in the script.

Pine Script
//@version=5
indicator("Scope Example", overlay=true)

// Global variable - accessible everywhere
smaLength = 20
smaValue = ta.sma(close, smaLength)

plot(smaValue)

Local scope

Variables declared inside a function, if block, or for loop exist only within that block. Once the block ends, the variable is gone.

Pine Script
//@version=5
indicator("Local Scope Example")

// This function creates a local variable
getSignal() =>
    threshold = 70        // Local - only exists inside this function
    rsi = ta.rsi(close, 14)
    rsi > threshold        // Returns true or false

// 'threshold' and 'rsi' do NOT exist out here
// Uncommenting the next line would cause an error:
// plot(threshold)

signal = getSignal()
plot(signal ? 1 : 0)

The variable threshold exists only while getSignal() is executing. Trying to access it outside the function would produce a "Could not find variable" error.

⚠️Scope and `if` blocks

Variables declared inside an if block are also local. If you need a value from inside an if to be available afterward, declare the variable before the if and assign to it inside the block using :=.

Here's the correct pattern for using a variable across scope boundaries:

Pine Script
//@version=5
indicator("Scope Pattern", overlay=true)

// Declare outside the if block
var string trendLabel = "Neutral"

// Assign inside the if block using :=
if close > ta.sma(close, 50)
    trendLabel := "Bullish"
else if close < ta.sma(close, 50)
    trendLabel := "Bearish"

if barstate.islast
    label.new(bar_index, high, trendLabel)

Notice that we declared trendLabel before the if statement and used := (reassignment) inside it. This way, the variable is accessible both inside and after the conditional block.

The na Value

na is a special value that means "not available." It's Pine Script's way of saying "there is no data here." You'll encounter it in two main situations:

  1. At the beginning of the chart when looking back with []. On bar 0, close[1] is na because there's no previous bar.
  2. When a calculation can't produce a result. For example, an SMA with a 20-bar period can't produce a value until bar 19 (because it needs 20 bars of data).

You can check for na using the na() function:

Pine Script
//@version=5
indicator("Handling na", overlay=false)

sma50 = ta.sma(close, 50)

// Only plot when we have valid data
plot(na(sma50) ? 0 : sma50, title="SMA 50")

Ignoring na values is a common source of silent bugs - your script might appear to work but produce wrong results in the first bars of the chart.

Practice Exercises

Try these exercises in the Pine Editor. For each one, the expected output is described so you can verify your solution.

Exercise 1: Bar counter

Create an indicator that plots a running count of all the bars on the chart. On the first bar, it should show 1. On the second bar, 2. On bar 100, it should show 100.

Hint: You need a variable that persists across bars and increments by 1 each time.

Expected output: A line that rises steadily from 1 on the left to the total number of bars on the right.

Exercise 2: Days since highest close

Create an indicator that shows how many bars have passed since the instrument's highest closing price on the visible chart. This is a useful metric - if price hit its high 200 bars ago and has been declining since, that tells you something different than if the high was 3 bars ago.

Hint: You need two var variables - one to track the highest close seen so far, and one to track the bar index when that high occurred.

Expected output: A value that resets to 0 whenever a new highest close is made, and counts up from there.

Exercise 3: Candle body size as percentage

Create an indicator that plots the size of each candle's body as a percentage of the full candle range. A "doji" candle (where open and close are nearly equal) would show a value near 0%. A strong candle with no wicks would show near 100%.

Hint: Body size is math.abs(close - open). Full range is high - low. Divide the first by the second and multiply by 100. Watch out for bars where high == low (the range is zero) - you'll need to handle that to avoid dividing by zero.

Expected output: An oscillator-style line bouncing between 0 and 100.

Key Takeaways

Let's summarize the critical concepts from this lesson:

  • Variables are named containers for values. Use descriptive camelCase names.
  • Data types include int, float, bool, string, and color. Each has specific trading use cases.
  • Regular variables are recalculated from scratch on every bar. They do not remember previous values.
  • var variables are initialized once and persist across bars. Use them for counters, accumulators, and state tracking.
  • Every variable is a series - it has a separate value for each bar on the chart.
  • The [] operator lets you access past values. close[1] is the previous bar's close.
  • Scope determines where a variable is visible. Variables inside functions and if blocks are local to those blocks.
  • na means "not available" and appears when historical data doesn't exist yet.

Next Steps

You now understand how Pine Script stores and manages data. In the next lesson, we'll learn about operators and control flow - how to do math, make comparisons, and write conditional logic that drives trading decisions.