Built-in Namespaces
Every built-in capability lives under a namespace. This is the reference for all of them. For language syntax (variables, control flow) see the Language Reference.
Bar series
Top-level price/volume series — a value per bar, indexable with [N].
| Identifier | Meaning |
|---|---|
close | Closing price |
open | Opening price |
high | High price |
low | Low price |
volume | Volume |
gap_up = open > close[1] # today's open above yesterday's close
range_today = high - low
ta — technical analysis
| Function | Returns | Notes |
|---|---|---|
ta.sma(series, length) | number | Simple moving average |
ta.ema(series, length) | number | Exponential moving average |
ta.wma(series, length) | number | Weighted moving average |
ta.rsi(series, length) | number | Relative strength index |
ta.atr(length) | number | Average true range |
ta.adr(length) | number | Average daily range % |
ta.macd(series, fast, slow, signal) | tuple (macd, signal, hist) | MACD |
ta.bbands(series, length, mult) | tuple (upper, middle, lower) | Bollinger Bands |
ta.highest(series, length) | number | Highest value over last N bars |
ta.lowest(series, length) | number | Lowest value over last N bars |
ta.crossover(a, b) | boolean | true only on the bar a crosses above b |
ta.crossunder(a, b) | boolean | true only on the bar a crosses below b |
ta.change(series, length=1) | number | series[0] - series[length] |
fast = ta.sma(close, 50)
slow = ta.sma(close, 200)
golden_cross = ta.crossover(ta.sma(close, 50), ta.sma(close, 200))
ta.stoch, ta.vwap, ta.twap, and ta.intra are not yet available. For a
crossover on derived values (not bare series), use the manual previous-difference
pattern shown in Examples.
strategy — trade actions
| Function | Effect |
|---|---|
strategy.enter_long(...) | Open a long position |
strategy.enter_short(...) | Open a short position |
strategy.close_long() | Close any open long |
strategy.close_short() | Close any open short |
strategy.close_all() | Close any open position |
A position is opened or closed only by these calls. v1 allows one
position per ticker — a second enter_long while already long does nothing.
Closes always close the entire position.
Position sizing
Entries take exactly one of these sizing keywords:
| Keyword | Meaning |
|---|---|
size_pct=N | Allocate N% of current equity. This compounds — as profits grow, each position grows. |
cash=N | Deploy a fixed dollar amount (shares = floor(N / price)). Does not compound. |
qty=N | Buy a fixed number of shares. Does not compound. |
strategy.enter_long(size_pct=100) # all-in, compounding
strategy.enter_long(cash=10000) # always $10,000
strategy.enter_long(qty=100) # always 100 shares
cash= or qty=size_pct is a percentage of your growing equity, so it snowballs. If you
want every trade to be the same size, use cash= or qty= — never try to fake
it with size_pct.
Fill timing
By default an order fills at the next bar's open (look-ahead safe). Override with one keyword:
| Keyword | Fills at |
|---|---|
| (none) | Next bar's open (default) |
at_open=true | This bar's open |
at_close=true | This bar's close |
at_next_close=true | Next bar's close |
strategy.enter_long(cash=10000, at_close=true) # fill at today's close
position — current position state
| Field | Value when flat |
|---|---|
position.size | 0 (positive = long, negative = short) |
position.entry_price | na |
position.unrealized_pnl | na |
position.unrealized_pnl_pct | na |
position.bars_held | na |
if position.size > 0 and position.unrealized_pnl_pct > 10:
strategy.close_all()
input — tunable parameters
Inputs become editable fields in the UI, so users can tune your strategy without editing code.
| Function | Returns |
|---|---|
input.int(default, title) | integer |
input.float(default, title) | float |
input.bool(default, title) | boolean |
input.string(default, title, options=[...]) | string |
length = input.int(14, "RSI Length")
threshold = input.float(30.0, "Oversold Level")
time — date & time
Calendar and clock fields for the current bar.
| Field | Range |
|---|---|
time.hour | 0–23 (Eastern Time) |
time.minute | 0–59 |
time.dayofweek | 0=Sunday, 1=Monday, 2=Tuesday … 5=Friday, 6=Saturday |
time.dayofmonth | 1–31 |
time.dayofyear | 1–366 |
time.month | 1–12 |
time.year | full year |
time.unix | Unix timestamp (milliseconds) |
# Trade only on Tuesdays
if position.size == 0 and time.dayofweek == 2:
strategy.enter_long(cash=10000, at_close=true)
time.dayofweek is 0=Sunday, 1=Monday, 2=Tuesday, 3=Wednesday, 4=Thursday,
5=Friday, 6=Saturday. A common mistake is to treat 2 as Monday — it's
Tuesday. On a daily timeframe, the value reflects the bar's trading date.
On intraday timeframes, bars outside the regular session — pre-market
(≈4:00–9:30 ET, time.hour < 9) and after-hours (≈16:00–20:00 ET,
time.hour >= 16) — appear only when extended hours is enabled. Enable it from
the header so the strategy can't silently no-op:
strategy("After-hours strategy", timeframe="5Min", extended_hours=true)
(Or toggle Extended Hours in the backtest settings.) With it off, only the
regular session exists, so a pre/post-market strategy won't fire. Gate on
time.hour to target a session. Note: extended-hours data must also exist for
the ticker.
math — math helpers
| Function | Returns |
|---|---|
math.max(a, b, ...) | max |
math.min(a, b, ...) | min |
math.abs(x) | absolute value |
math.sqrt(x) | square root |
math.log(x) | natural log |
math.exp(x) | e^x |
math.floor(x) | floor |
math.ceil(x) | ceiling |
math.round(x) | nearest integer |
Any argument that is na makes the result na.
external — other tickers
Read another symbol's price series from inside your strategy (read-only). Useful for market/regime filters.
| Function | Returns |
|---|---|
external.close("TICKER") | that ticker's close series |
external.open / high / low / volume("TICKER") | other OHLCV series |
The ticker must be a string literal.
# Only go long when SPY is above its 200-day average
spy_uptrend = external.close("SPY") > ta.sma(external.close("SPY"), 200)
if spy_uptrend and position.size == 0 and rsi < 30:
strategy.enter_long(size_pct=10)
htf — higher-timeframe data
Read a higher timeframe for the current ticker or a named symbol — e.g. the weekly trend while trading daily bars.
| Function | Returns |
|---|---|
htf.close("1W") | this ticker's close on the given timeframe |
htf.open / high / low / volume("1W") | other OHLCV fields |
htf.close("SPY", "1W") | a named symbol's series at a given timeframe |
The timeframe (and optional symbol) must be string literals, and the timeframe must be equal to or higher than the backtest's base timeframe.
# Daily strategy gated by the weekly trend
weekly_sma = ta.sma(htf.close("1W"), 30)
if close > weekly_sma and position.size == 0:
strategy.enter_long(size_pct=10)
A higher-timeframe bar only becomes visible once it has fully closed. While
the current week is still forming, htf.close("1W") returns the last
completed week's close — never a value from the future.
syminfo — current symbol
| Field | Value |
|---|---|
syminfo.ticker | the symbol the script is currently running on (a string) |
A backtest runs the same script once per ticker. syminfo.ticker lets one
strategy apply different rules to different symbols:
if syminfo.ticker == "QQQ":
if position.size == 0 and time.dayofweek == 2: # QQQ: buy Tuesday
strategy.enter_long(cash=10000, at_close=true)
if position.size > 0 and time.dayofweek == 3: # sell Wednesday
strategy.close_all(at_close=true)
elif syminfo.ticker == "AAPL":
if position.size == 0 and time.dayofweek == 3: # AAPL: buy Wednesday
strategy.enter_long(cash=10000, at_close=true)
if position.size > 0 and time.dayofweek == 5: # sell Friday
strategy.close_all(at_close=true)
Run this over a ["QQQ", "AAPL"] universe and each symbol follows its own
branch.
syminfo.ticker only chooses which rules apply — it does not move money on
its own. Each branch that should trade must call strategy.enter_long /
close_all / etc. Tracking a "position" with your own true/false variable
places no trades and produces no results.