# Create a custom indicator

> Bring your own signal from JavaScript, Python, Pine or a CSV, save it, and reference it in any query with an @-tag.

Source: https://docs.texttoquant.com/guides/custom-indicators

---

Beyond the [built-in library](/docs/reference/indicators), 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](/docs/reference/entry-exit#custom-indicators--tags).

## Four ways to bring a series

- **JavaScript**: write an indicator in plain JavaScript in the [editor](/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](/docs/reference/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](/docs/reference/python-indicators).
- **Pine**: write or paste a Pine v6 study in the [editor](/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.

<Callout title="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.
</Callout>

## Author and save

<Steps>
  <Step n={1} title="Open the editor">
    Go to [/editor](/editor). Custom indicators are a plan-gated feature.
  </Step>
  <Step n={2} title="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.
  </Step>
  <Step n={3} title="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.
  </Step>
  <Step n={4} title="Save it with a name">
    Give it a name, that name is how you'll reference it in a query.
  </Step>
</Steps>

```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.

<RunInTerminal query='Long when @"My Osc" crosses above 0, SL 2%, TP 3RR, BTC 4h last 90 days' />

## Everything the phrasing reference describes works on your series too

A saved indicator is a series like any other, so the whole [phrasings](/docs/reference/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 |

<Callout variant="info" title="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.
</Callout>

## 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

<Callout variant="warning" title="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.
</Callout>

<Callout variant="warning" title="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.
</Callout>

Related: [entry & exit, @-tags](/docs/reference/entry-exit#custom-indicators--tags),
[indicators](/docs/reference/indicators), [export to TradingView](/docs/guides/export-to-pine).
