Pine Script conditionals and loops, past the basics
This page assumes you already know a plain if and a plain for, covered in Pine Script basics. It covers the control-flow Pine Script adds on top of those: else if chains, if as an expression, the switch structure, while, for...in, loop control with break and continue, and the ternary operator, ending with turning one of these into a live Autoview command.
else if chains and if as an expression
A plain if can chain as many else if clauses as you need, evaluated in order, with the first true one winning:
if close > ta.sma(close, 50) * 1.05
label.new(bar_index, high, "Strong")
else if close > ta.sma(close, 50)
label.new(bar_index, high, "Above avg")
else
label.new(bar_index, low, "Below avg")if can also be assigned directly to a variable, making it an expression rather than just a side effect. Every branch's block has to resolve to the same type:
zone = if close > ta.sma(close, 50) * 1.05
"strong"
else if close > ta.sma(close, 50)
"above"
else
"below"zone holds whichever string the matching branch produced. If nothing matches and there's no else, the whole structure returns na (or false, if the result type is bool) instead.
switch: one clause, no fallthrough
switch picks exactly one clause to run, based on matching an expression against a list of cases. Unlike a C-style switch, there's no fallthrough between cases; once one clause matches, the rest are skipped entirely.
rsi = ta.rsi(close, 14)
zone = switch
rsi > 70 => "overbought"
rsi < 30 => "oversold"
=> "neutral"Each expression => block line is a case; the final bare => block with no expression before it is the default, the clause that runs when nothing else matched. switch can also key off a single expression instead of a series of boolean cases, matching it against a list of exact values, which reads more clearly than a long else if chain once you have more than two or three branches.
while: loop until a condition stops holding
for needs a fixed range up front. while instead repeats for as long as its condition stays true, re-checking that condition before every iteration, including the first:
sum = 0.0
count = 0
while count < 14 and not na(close[count])
sum := sum + close[count]
count := count + 1
avg14 = count > 0 ? sum / count : naThis accumulates up to 14 bars of close, stopping early if it runs out of history (na(close[count)] catches that). for can't express "stop early for a reason unrelated to the counter" nearly this cleanly; that's the case while is for.
for...in: iterate a collection directly
When you already have an array, matrix, or map, for...in iterates its items directly, no manual index counting required:
levels = array.from(100.0, 105.0, 110.0)
touched = 0
for level in levels
if high >= level and low <= level
touched := touched + 1Add an index alongside the item with for [index, item in collection_id], the form maps require since a map's keys aren't sequential integers.
break and continue
Both work inside for, while, and for...in. continue skips whatever's left in the current iteration and jumps straight to re-checking the loop's condition; break exits the loop immediately, with no further iterations at all:
firstBigMove = na
for i = 0 to 19
change = math.abs(close[i] - close[i + 1])
if na(change)
continue
if change > ta.sma(close, 20) * 0.02
firstBigMove := i
breakcontinue skips a bar where change can't be computed yet (not enough history); break stops the search the moment the first qualifying bar is found, rather than checking all 20 regardless.
The ternary operator: if, inline
condition ? value_if_true : value_if_false is if compressed into a single expression, useful when you just need to pick between two values rather than run a block of statements:
barColor = close > open ? color.green : color.red
plot(close, color=barColor)Chaining ternaries can stand in for a small switch, but past two or three links it reads worse, not better; reach for switch once you're past a single condition.
Turning any of these into a live command
A switch or while result is computed at runtime, so it can change bar to bar, the same category of value the functions guide covers. alertcondition() can't carry it, its message has to be a constant string fixed at compile time. alert() plus str.tostring() can:
//@version=5
indicator("Zone Signal", overlay=true)
rsi = ta.rsi(close, 14)
zone = switch
rsi > 70 => "overbought"
rsi < 30 => "oversold"
=> "neutral"
justEnteredOversold = zone == "oversold" and zone[1] != "oversold"
if justEnteredOversold
alert("s=BTCUSDT b=buy q=1 t=market // zone=" + zone, alert.freq_once_per_bar)zone is computed fresh every bar by the switch; justEnteredOversold is an if-friendly boolean built by comparing zone against its own value one bar back. When it turns true, alert() fires with zone's current text folded into the message via str.tostring()-style concatenation (a string value like zone concatenates with + directly, no str.tostring() call needed since it's already text). See label & comment your alerts for what the trailing // comment does once Autoview receives it.
Test before you trust it
Add d=1 to the message before you rely on any of this. Autoview's dry-run gate is exchange-agnostic: every integration's trade path calls a shared withDebugReporter check, and when cmd.d is set, it logs what the command would have done and returns before any live order is sent. Let justEnteredOversold trigger a few times, check the log for the zone and side you expected, then remove d=1 once it matches.
A note on what Autoview is. Autoview is an execution tool, not a trading or investment advisor. It places the orders your script and alerts tell it to; it doesn't generate signals or make any claim about results. Trading carries risk of loss, and you're responsible for the strategy you automate. See our disclosures.