indicator() versus strategy()
Lesson 2 · about 10 min
The declaration statement decides what kind of thing your script is. An indicator calculates and draws. A strategy calculates, draws and also places simulated orders that TradingView's broker emulator fills, producing the Strategy Tester report at the bottom of the chart. A library exports functions for other scripts to import and draws nothing on its own. You will write mostly indicators, convert a few into strategies to test them, and maybe never write a library. Understanding the difference early stops a lot of confusion about why a script "does not trade".
The same idea, both ways
An RSI in its own pane, as an indicator:
//@version=6
indicator("RSI pane", overlay=false)
length = 14
rsi = ta.rsi(close, length)
plot(rsi, "RSI", color=color.purple, linewidth=2)
hline(70, "Upper", color=color.gray)
hline(30, "Lower", color=color.gray)
A moving-average cross that actually takes trades, as a strategy:
//@version=6
strategy("SMA cross demo", overlay=true, initial_capital=10000)
fast = ta.sma(close, 10)
slow = ta.sma(close, 30)
if ta.crossover(fast, slow)
strategy.entry("Long", strategy.long)
if ta.crossunder(fast, slow)
strategy.close("Long")
plot(fast, "Fast", color=color.orange)
plot(slow, "Slow", color=color.blue)
Add the second one to a chart and a "Strategy Tester" tab appears with a list of trades, a net profit figure and an equity curve. Add the first one and nothing of the sort happens, however many if statements you write, because an indicator has no strategy.* functions available.
What each declaration controls
| Option | indicator() | strategy() |
|---|---|---|
overlay |
price pane or own pane | same |
shorttitle |
short name on the chart | same |
initial_capital |
not available | starting equity for the backtest |
default_qty_type |
not available | fixed, cash, or percent of equity |
commission_type |
not available | percent, cash per order, cash per contract |
slippage |
not available | ticks added against you on every fill |
pyramiding |
not available | max entries in the same direction |
calc_on_every_tick |
n/a (indicators always update) | recalculate on every live tick, not bar close |
process_orders_on_close |
not available | fill market orders on the bar close |
The strategy options that matter for honest testing (commission, slippage, sizing) get their own lesson in module 6. For now, notice that a strategy defaults to zero commission and zero slippage. A backtest run with the defaults is a backtest of a world where trading is free.
Where the orders go
strategy.entry("Long", strategy.long) does not buy on the bar where the condition is true. It submits a market order, and by default the broker emulator fills it at the open of the next bar. That is the honest choice: on the signal bar you only know the close after the bar has ended, and you could not have traded at it. strategy.close("Long") submits an order to flatten the position named "Long", again filled at the next open. The "Long" string is an ID, not a direction; strategy.long is the direction.
Key idea: Indicators draw, strategies draw and place simulated orders. The declaration line, not the body, decides which one you have, and strategy defaults assume free trading until you tell them otherwise.
Converting between the two
Most working traders keep one script that can be both. Develop as an indicator because it compiles faster and does not clutter the chart with trade markers. When the logic is stable, change the declaration to strategy(...), replace the signal plots with strategy.entry and strategy.close, and read the tester. The signal logic itself does not change, which is exactly the point: the tester is checking the same rules you were eyeballing.
Two practical differences to remember when you convert:
- A strategy's
overlay=trueputs trade arrows on the price pane. If you also want an oscillator, you need a second script orforce_overlaytricks; a strategy cannot be in two panes at once. - Indicators can use
alertcondition(). Strategies cannot, but they get order-fill alerts instead, which module 5 covers.
Libraries in one paragraph
library("Name") declares a script whose export functions other scripts can import. It cannot plot and is published separately. If you find yourself pasting the same twenty-line helper into every script, that helper belongs in a library. Module 8 shows a small one.
Try it: Paste the strategy above onto a daily chart of any liquid index ETF or futures contract and open the Strategy Tester. Note the net profit. Now add
commission_type=strategy.commission.percent, commission_value=0.1to the declaration and see how much the number moves. That gap is the cost of the defaults.
Recap
indicator()calculates and draws;strategy()also submits simulated orders and produces the Strategy Tester report.- Strategy-only options include initial capital, quantity type, commission, slippage and pyramiding; the defaults model free trading.
strategy.entryandstrategy.closefill at the next bar's open by default.- Develop as an indicator, convert to a strategy to test, and keep the signal logic identical.
library()packages reusable functions and draws nothing.
See it drawn
Original diagrams for the ideas on this page. Illustrative, not real market data.