How-to guide

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.
JavaScript or Python?

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#

Open the editor

Go to /editor. Custom indicators are a plan-gated feature.

Write code, or upload a CSV

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, then preview

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.

Save it with a name

Give it a name, that name is how you'll reference it in a query.

javascript
// 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)) }
}
pine
//@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 terminal

Everything 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 seriesWhat the engine checks
@"My Sheet:signal" is in the top 10% of its last 100 readingspercentile rank of your column
@"My Sheet:signal" has been above 0 for 5 consecutive barsa streak on your column
@"My Sheet:signal" is 2 standard deviations above its 20-bar meana statistical band on your column
the daily @"My Sheet:signal" is in its top decileranked against daily history
@"A:x" crosses above @"B:y"two of your series compared
Multi-plot sheets: always name the column

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
Don't write a marker as “equals 1”

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.

Strict mode

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.