Variable Declarations
Introduction
Variables are identifiers that store values. They must be declared in your code before you can use them. The syntax for variable declarations is as follows:
[<declaration_mode>] [<type>] <identifier> = <expression> | <structure>
or
<tuple_declaration> = <function_call> | <structure>
Where:
|
indicates “or,” and parts enclosed in square brackets ([]) can appear zero or one time.<declaration_mode>
is the variable’s declaration mode. It can bestatic
orintra
, or it can be omitted.<type>
is optional, as seen in almost all Lipi Script variable declarations (refer to the types section).<identifier>
is the name of the variable.<expression>
can be a literal, a variable, an expression, or a function call.<structure>
can be an if, while structure.<tuple_declaration>
is a comma-separated list of variable names enclosed in square brackets ([]), e.g.,[ma, upperBand, lowerBand]
.
These are all valid variable declarations. The last one requires four lines:
BULL_COLOR = color.lime
i = 1
len = input(20, "Length")
float f = 10.5
closeRoundedToTick = math.round_to_mintick(close)
st = talib.supertrend(4, 14)
static barRange = float(na)
static firstBarOpen = open
intra float lastClose = na
[macdLine, signalLine, histLine] = talib.macd(close, 12, 26, 9)
plotColor = close > open ? color.green : color.red
⚠️ Notice! The statements
above contain the =
assignment operator, indicating that they
are variable declarations. When you see similar lines with
the :=
reassignment operator, the code is
reassigning a value to an already declared
variable. These are variable reassignments. Understanding
this distinction is crucial, as it is a common stumbling block for newcomers
to Lipi Script. Refer to the next Variable reassignment
section for more details.
The formal syntax of a variable declaration is:
<variable_declaration>
[<declaration_mode>] [<type>] <identifier> = <expression> | <structure>
|
<tuple_declaration> = <function_call> | <structure>
<declaration_mode>
static | intra
<type>
int | float | bool | color | string | line | linefill | label | box | table | array<type> | matrix<type> | UDF
Initialization with na
In many situations, explicitly declaring a type is unnecessary since the type is automatically inferred from the value on the right side of the =
during compilation. Thus, whether to include type declarations often comes down to personal preference. For instance:
// compile time error!
baseLine0 = na
// OK
float baseLine1 = na
// OK
baseLine2 = float(na)
In the first line of the example, the compiler is unable to determine the type of the baseLine0
variable because na
is a generic value with no specific type. The declaration of the baseLine1
variable is correct, as its float type is explicitly declared. Similarly, the declaration of the baseLine2
variable is also valid since its type can be inferred from the expression float(na)
, which is an explicit cast of the na
value to the float type. Therefore, the declarations of baseLine1
and baseLine2
are equivalent.
Tuple Declarations
Function calls or structures can return multiple values. When we call these functions and want to store the returned values, a tuple declaration must be employed. This consists of a comma-separated list of one or more values enclosed in brackets, allowing us to declare multiple variables simultaneously. For example, the talib.bb()
built-in function for Bollinger Bands returns three values:
[bbMiddle, bbUpper, bbLower] = talib.bb(close, 5, 4);
Using an Underscore (_
) as an Identifier
When declaring a variable, you can utilize a single underscore (_
) as its identifier. Any value assigned to this variable cannot be accessed later. You can assign multiple values to the _
identifier throughout the script, even if the current scope already has such an assignment.
This approach is especially helpful when a tuple returns values that are not needed. Let’s create another Bollinger Bands script where we only require the bands themselves, excluding the center line:
indicator("Underscore demo")
// We do not need the middle Bollinger Bands value, and do not use it.
// To make this clear, we assign it to the `_` identifier.
[_, bbUpper, bbLower] = talib.bb(close, 5, 4)
// We can continue to use `_` in the same code without causing compilation errors:
[bbMiddleLong, _, _] = talib.bb(close, 20, 2)
plot(bbUpper)
Variable Reassignment
Variable reassignment is accomplished using the :=
reassignment operator. This operation can only be performed after a variable has been initially declared and assigned a value. Reassigning a new value to a variable is often essential in calculations, and it becomes necessary when a variable from the global scope needs to be assigned a new value within a structure’s local block. For example:
indicator("", "", true)
sensitivityInput = input.int(2, "Sensitivity", minval = 1, tooltip = "Higher values make color changes less sensitive.")
ma = talib.sma(close, 20)
maUp = talib.rising(ma, sensitivityInput)
maDn = talib.falling(ma, sensitivityInput)
// On first bar only, initialize color to gray
static maColor = color.gray
if maUp {
// MA has risen for two bars in a row; make it lime.
maColor := color.lime
}
else if maDn {
// MA has fallen for two bars in a row; make it fuchsia.
maColor := color.maroon
}
plot(ma, "MA", maColor, 2)
Note that:
- We initialize
maColor
only on the first bar, allowing it to preserve its value across bars. - On each bar, the
if
statement checks whether the MA has been rising or falling for the user-specified number of bars (the default is 2). When this occurs, the value ofmaColor
must be reassigned a new value within the local blocks of theif
statement. To achieve this, we use the:=
reassignment operator. - If we did not utilize the
:=
reassignment operator, it would result in the initialization of a newmaColor
local variable. This new variable would have the same name as the global scope variable, but it would be a confusing independent entity that persists only for the duration of the local block and then disappears without a trace. - All user-defined variables in Lipi Script are mutable, meaning their values can be changed using the
:=
reassignment operator. Assigning a new value to a variable may alter its type qualifier (refer to the section on Lipi Script’s type system for more information). A variable can be assigned a new value multiple times during the script’s execution on one bar, allowing for any number of reassignments of a single variable. The variable’s declaration mode determines how new values assigned to it will be saved.
Declaration Modes
Understanding the impact of declaration modes on variable behavior requires prior knowledge of Lipi Script’s execution model.
When declaring a variable, if a declaration mode is specified, it must come first. Three modes can be used:
- “On each bar”, when none is specified
- static
- intra
On each bar
When no explicit declaration mode is specified, meaning no static
or intra
keyword is used, the variable is declared and initialized on each bar. For example, consider the following declarations from our first set of examples in this page’s introduction:
BULL_COLOR = color.lime
i = 1
len = input(20, "Length")
float f = 10.5
closeRoundedToTick = math.round_to_mintick(close)
st = talib.supertrend(4, 14)
[macdLine, signalLine, histLine] = talib.macd(close, 12, 26, 9)
plotColor = close > open ? color.green : color.red
static
When the static keyword is used, the variable is initialized only once—on the first bar if the declaration is in the global scope, or the first time the local block is executed if the declaration is within a local block. After this initial assignment, the variable will retain its last value on successive bars until a new value is reassigned to it. This behavior is particularly beneficial in scenarios where a variable’s value needs to persist through the iterations of a script across consecutive bars. For instance, consider the situation where we want to count the number of green bars on the chart:
indicator("Green Bars Count")
static count = 0
isGreen = close >= open
if isGreen {
count := count + 1
}
plot(count)
Without the static modifier, the variable count
would be reset to zero (thereby losing its value) each time a new bar update triggers a script recalculation.
intra
Understanding the behavior of variables using the intra declaration mode necessitates prior knowledge of Lipi Script’s execution model and bar states.
The intra keyword can be utilized to declare variables that escape the rollback process, as explained in the section on Lipi Script’s execution model.
While scripts execute only once at the close of historical bars, a script running in real-time executes every time the chart’s feed detects a price or volume update. During each real-time update, Lipi Script’s runtime typically resets the values of a script’s variables to their last committed value—i.e., the value they held when the previous bar closed. This behavior is generally advantageous, as each real-time script execution begins from a known state, simplifying script logic.
However, there are instances when script logic requires the ability to retain variable values between different executions within the real-time bar. Declaring variables with intra enables this capability. The “ip” in intra stands for intrabar persist.
Let’s examine the following code, which does not utilize intra:
indicator("")
int updateNo = na
if barstate.isnew {
updateNo := 1
} else {
updateNo := updateNo + 1
}
plot(updateNo, style = plot.style_circles)
On historical bars, barstate.isnew
is always true, resulting in the plot displaying a value of “1” since the else
part of the if
structure is never executed. In real-time bars, barstate.isnew
is only true when the script first executes at the bar’s “open.” The plot will briefly show “1” until subsequent executions occur. During the next executions of the real-time bar, the second branch of the if
statement is executed because barstate.isnew
is no longer true. Since updateNo
is initialized to na
at each execution, the expression updateNo + 1
yields na
, meaning nothing is plotted on further real-time executions of the script.
However, if we now use intra to declare the updateNo
variable, the script behaves very differently:
indicator("")
intra int updateNo = na
if barstate.isnew {
updateNo := 1
} else {
updateNo := updateNo + 1
}
plot(updateNo, style = plot.style_circles)
The key difference now is that updateNo
tracks the number of real-time updates that occur on each real-time bar. This is possible because the intra declaration allows the value of updateNo
to be preserved between real-time updates; it is no longer reset during each real-time execution of the script. The test on barstate.isnew
enables us to reset the update count when a new real-time bar arrives.
Since intra only influences the behavior of your code in the real-time bar, it follows that backtest results on strategies developed using logic based on intra variables will not replicate that behavior on historical bars, thus invalidating the test results for those bars. This also means that plots on historical bars will not be able to reflect the script’s behavior in real-time.