Create a custom indicator
Bring your own signal from Pine or a CSV, save it, and reference it in any query with an @-tag.
Beyond the built-in library, you can author your own indicator, write
code, or upload a CSV of values: save it, and then use it inside a strategy exactly like any other
series. This guide covers making one; using @-tags in a query is covered under
entry & exit.
Four ways to bring a series#
- JavaScript: write an indicator in plain JavaScript in the editor. It runs in a sandbox against real bars and, unlike Pine, is not pinned to the symbol or timeframe you wrote it on. See JavaScript indicators for the full language.
- Python: the same capability in Python, with the same contract, the same
ta.*helpers and the same limits. See Python indicators. - Pine: write or paste a Pine v6 study in the editor and compile it against real market data.
- CSV: upload a sheet whose columns are your series (e.g. a signal exported from elsewhere), and tag the columns you want to use.
Neither is preferred and both produce the same artefact, a saved indicator that recomputes for whatever symbol and timeframe a run uses. Write in whichever you would rather debug.
Author and save#
Go to /editor. Custom indicators are a plan-gated feature.
Pick a workspace in the editor (Pine, JavaScript or Python. A multi-plot study) Pine plot()
or a PLOTS entry, exposes each plot as its own named column.
Validate and lint compile-check the script with no run and no charge. Preview compiles and runs it against real data so you can see the plotted output.
Give it a name, that name is how you'll reference it in a query.
// JavaScript
export const INPUTS = { length: 14 }
export const PLOTS = [{ name: 'signal', kind: 'line' }]
export function calc(candles, opts) {
const rsi = ta.rsi(candles.close, opts.length)
return { signal: rsi.map((v) => (Number.isFinite(v) ? v - 50 : null)) }
}//@version=6
indicator("My Osc", overlay=false)
src = close
osc = ta.rsi(src, 14) - 50
plot(osc, "signal")Reference it in a query#
Tag a saved indicator with @ and its name. For a multi-plot indicator (or a CSV with several
columns), name the specific plot/column.
- Single series →
@"My Osc" - A specific plot or CSV column → tag the column, e.g.
@"My Sheet:signal"
Align the custom series to your strategy's timeframe in the UI, then use it in conditions like any built-in indicator.
Long when @"My Osc" crosses above 0, SL 2%, TP 3RR, BTC 4h last 90 daysRun in terminalEverything the phrasing reference describes works on your series too#
A saved indicator is a series like any other, so the whole phrasings vocabulary applies to it: percentile rank, streaks, windows, rolling extremes, correlation, z-scores, the lot. Name the plot and say the same thing you would say about RSI.
| Say this about your series | What the engine checks |
|---|---|
@"My Sheet:signal" is in the top 10% of its last 100 readings | percentile rank of your column |
@"My Sheet:signal" has been above 0 for 5 consecutive bars | a streak on your column |
@"My Sheet:signal" is 2 standard deviations above its 20-bar mean | a statistical band on your column |
the daily @"My Sheet:signal" is in its top decile | ranked against daily history |
@"A:x" crosses above @"B:y" | two of your series compared |
On a sheet with several plots, name the one you mean, @"My Sheet:signal" or
My Sheet.signal. A named column is matched exactly: if the name is wrong the condition
reads as no-data rather than quietly measuring a different column. Leave the column off and
the engine still never guesses: the run uses the sheet's primary series (declared in the
indicator, or inferred from the data) and the result tells you which line it used, or, if
it cannot tell, the condition reads as no-data. It never trades on whichever column
happened to come first.
Marker columns: signals that only print sometimes#
Many Pine indicators plot a value only on signal bars, a buy/sell arrow, a divergence dot
and na everywhere else. For those the presence of a value is the signal, so say fires,
prints or appears rather than comparing it to a number.
@"WT:Sell"fires → the marker printed on this bar- no
@"WT:Sell"in the last 5 bars → the marker did not print @"WT:Sell"has fired at least 3 times in the last 20 bars → counted over a window- at least 10 bars since
@"WT:Sell"fired → time since the last one
Marker columns rarely hold 1, the value is usually a price or an oscillator level. Ask for
the signal to fire and the engine checks whether the bar has a value at all.
With strict mode on, a custom query requires the @ tag, a data source, and a parsed custom
condition, so a typo can't silently fall back to a built-in indicator. Leave it on when a run must
use your series and nothing else.
Related: entry & exit, @-tags, indicators, export to TradingView.