Pine Script to Trigr: Convert Indicators to Formulas

A function-by-function map from Pine Script v6 to Trigr formulas: ta.crossover, request.security, ta.barssince, and what still has no direct equivalent.

Trigr Research6 min read
On this page
  1. Why does the Expression node change Pine porting?
  2. Which Pine functions map to which Trigr functions?
  3. How do you write indicators Trigr does not have built in?
  4. What happens to request.security and repainting?
  5. What does a full port look like?
  6. What still does not translate?
  7. How do you check the port?

TL;DR: With the Expression node, most of Pine Script's ta.* library has a one-line Trigr equivalent. ta.crossover(a, b) is crosses_above(a, b), close[3] is lag(close, 3), ta.barssince(cond) is bars_since(cond), and request.security(syminfo.tickerid, "240", close) is close@4h. Indicators Pine has built in but Trigr does not, such as the stochastic or a volume-weighted average, can usually be written from their definitions. What still does not translate is anything stateful or procedural: var, loops, arrays, custom functions and order-type tricks.

Why does the Expression node change Pine porting?

Before formulas, porting a Pine strategy to Trigr meant mapping each condition onto a catalog indicator node. Standard setups mapped well, but any custom math (a z-score, a ratio between two markets, a "bars since the last cross" rule) had no home. Our TradingView import guide listed those cases as the main reason a port could fail.

An Expression formula closes most of that gap. It is a single true or false condition with Pine-like functions, evaluated point in time on Trigr's own data. You still translate rather than paste, but the translation is now mostly mechanical.

Which Pine functions map to which Trigr functions?

The table below covers the Pine v6 functions that appear in most strategies. Trigr's indicator functions use the same formulas as its catalog nodes; we verified that close > ema(close, 50) and the EMA indicator node produce identical backtests.

Pine Script v6 Trigr formula Notes
close, open, high, low, volume close, open, high, low, volume Of the node's market and timeframe
close[n] lag(close, n) n of at least 1
ta.sma(x, n), ta.ema(x, n) sma(x, n), ema(x, n) EMA is seeded with an SMA
ta.rsi(x, n) rsi(x, n) Wilder smoothing
ta.macd(x, f, s, sig) macd(x, f, s), macd_signal(x, f, s, sig), macd_hist(x, f, s, sig) One function per output line
ta.bb(x, n, k) bbands_mid(x, n), bbands_upper(x, n, k), bbands_lower(x, n, k) Population standard deviation
ta.stdev(x, n) stdev(x, n) or rolling_std(x, n) Both population
ta.atr(n) atr(n) Wilder; atr(asset("ETH"), n) for another market
ta.dmi(n, smooth) ADX output adx(n) Matches only when smooth equals n; +DI and -DI are not exposed
ta.crossover(a, b) crosses_above(a, b) True on the bar a moves from at or below b to above b
ta.crossunder(a, b) crosses_below(a, b)
ta.change(x, n), ta.mom(x, n) diff(x, n)
ta.roc(x, n) 100 * pct_change(x, n) pct_change returns a fraction
ta.highest(x, n), ta.lowest(x, n) highest(x, n), lowest(x, n) Current bar included
math.sum(x, n) rolling_sum(x, n) A condition counts as 1 or 0
ta.barssince(cond) bars_since(cond) Missing, so comparisons are false, if cond was not true in the last 1,000 bars; bars_since(cond, 5000) raises the cap
ta.percentrank(x, n) rank(x, n) Trigr returns (0, 1] with the current bar included; close to Pine's 0 to 100 rank, not identical
math.abs, math.log, math.sign, math.min, math.max abs, log, sign, min, max
request.security(syminfo.tickerid, "240", close) close@4h Closed bars only, see below
request.security("BINANCE:ETHUSDT", "240", close) asset("ETH").close@4h Any market in Trigr's registry
and, or, not and, or, not Comparisons cannot be chained

Pine's input.* values become plain numbers. Window lengths must be whole-number literals from 1 to 5,000 (at least 2 for rsi, stdev, zscore, rank, atr, adx and the Bollinger bands), so ta.sma(close, len) with a variable len becomes sma(close, 20) with the number written in.

How do you write indicators Trigr does not have built in?

Many Pine built-ins are short formulas over primitives Trigr does have. Each of these was checked against the live engine before publishing:

Pine built-in Trigr formula
ta.stoch(close, high, low, 14) (0 to 100) 100 * (close - lowest(low, 14)) / (highest(high, 14) - lowest(low, 14))
ta.wpr(14) (Williams %R, minus 100 to 0) 100 * (close - highest(high, 14)) / (highest(high, 14) - lowest(low, 14))
ta.vwma(close, 20) rolling_sum(close * volume, 20) / rolling_sum(volume, 20)
Keltner upper band (ta.kc, EMA basis, ATR range) ema(close, 20) + 2 * atr(14)
Donchian breakout close > lag(highest(high, 20), 1)
Bollinger bandwidth (bbands_upper(close, 20, 2) - bbands_lower(close, 20, 2)) / bbands_mid(close, 20)

On SOLUSDT 1h data, the stochastic-below-20 condition and the Williams-%R-below-minus-80 condition were true on exactly the same 9,017 bars, as the algebra says they should be. That kind of cross-check is worth doing on any indicator you rebuild. The definitions on Wikipedia for the stochastic oscillator and Keltner channels are a good reference.

Some built-ins remain out of reach. Session-anchored ta.vwap, ta.wma and ta.hma (weighted averages), ta.cci (it needs a mean absolute deviation) and ta.supertrend (it carries state from bar to bar) do not reduce to the available functions. For SuperTrend and CCI, check the catalog: several such indicators exist as regular indicator nodes you can combine with formula filters.

What happens to request.security and repainting?

Higher-timeframe data is where Pine strategies most often look better than they should. Called without care, request.security can return the value of a higher-timeframe bar that is still forming, which leaks future price into history. The usual fix in Pine is to request the previous bar's value with lookahead on, so only completed bars are used. TradingView's repainting guide explains the details.

In Trigr there is no setting to get wrong. close@4h on a 1h strategy is the close of the last completed 4h bar, carried forward until the next one completes, and the same rule applies to asset("ETH").close@4h. A formula cannot read a timeframe finer than its own node, so the reverse leak is refused at validation. Multi-timeframe strategies without look-ahead bias covers why this matters.

What does a full port look like?

Here is a small Pine strategy: a pullback entry in a higher-timeframe uptrend, a volatility exit and a fixed stop.

//@version=6
strategy("Trend pullback", overlay = true)
htfUp = request.security(syminfo.tickerid, "240", close[1] > ta.ema(close, 50)[1], lookahead = barmerge.lookahead_on)
if ta.crossover(ta.rsi(close, 14), 40) and htfUp
    strategy.entry("L", strategy.long)
if ta.crossunder(close, ta.ema(close, 20) - 2 * ta.atr(14))
    strategy.close("L")
strategy.exit("SL", "L", stop = strategy.position_avg_price * 0.97)

The Trigr version uses one TRIGGER formula and two RISK exits:

Pine Trigr node and parameter Value
Entry condition TRIGGER long crosses_above(rsi(close, 14), 40) and close@4h > ema(close@4h, 50)
Long only RISK direction long
strategy.close condition RISK exitLong crosses_below(close, ema(close, 20) - 2 * atr(14))
3% stop from entry RISK sl 3
Hold until an exit fires RISK exitOnFlip false

That last row matters, and it is why the port is long only: with exitOnFlip off, onOpposite must stay unset or ignore. A two-sided port that should reverse on the opposite signal sets exitLong to the short entry condition and exitShort to the long one. The entry formula is an event: it is true on one bar. With the default exitOnFlip on, the trade would close as soon as the event ends, one bar later. Event vs state signals explains why, and why this port turns it off and relies on the exit formula and the stop instead.

What still does not translate?

Be explicit about these, because a port that quietly drops them is a different strategy:

  • State that persists across bars. var variables, counters that reset on custom events, and trailing values updated by if blocks have no equivalent. bars_since covers the most common case.
  • Loops, arrays, maps and custom functions. A formula is one expression, at most 500 characters.
  • Conditional values. There is no ternary operator. Split the logic into separate conditions, or into long and short formulas.
  • ta.valuewhen and pivot functions. ta.pivothigh confirms a pivot only after later bars arrive, so it cannot be point in time on the pivot bar itself.
  • Order types. Limit and stop entries, partial exits with qty_percent, break-even moves and multi-level targets have no direct equivalent. Trigr fills at the next bar's open and its RISK node offers one stop, one target, an ATR trail, a time stop and formula exits.
  • Intrabar logic. calc_on_every_tick and process_orders_on_close behaviors do not exist; formulas evaluate on closed bars.

How do you check the port?

Run the Trigr version gross first, then with slippage and funding, and compare trade lists rather than headline returns. Expect fewer, later entries (next-bar-open fills) and lower returns (fees on every trade). Why your Pine Script backtest looks better than it will trade lists each source of difference, and the strategy builder docs define every RISK parameter used above.

If you would rather not translate by hand, paste the Pine code into an assistant connected to Trigr's MCP server and ask for a mapping table first, with every approximation listed. The TradingView import walkthrough shows that session step by step, and Pine's own language reference is the authority on what each ta.* function does.

Backtests are not guarantees, and perps are leveraged instruments that can lose more than expected.

Frequently asked questions

Can I paste Pine Script code into Trigr directly?

No. Trigr does not run Pine Script. You translate the conditions into Trigr Expression formulas, which use a similar vocabulary: ta.crossover becomes crosses_above, ta.ema becomes ema, close[1] becomes lag(close, 1), and request.security for a higher timeframe becomes close@4h. An AI assistant connected to Trigr's MCP server can do most of the translation.

How does Trigr handle request.security and repainting?

A formula like close@4h only sees a 4h value after that 4h bar has closed, and carries it forward until the next one closes. That matches what the non-repainting Pine idiom tries to achieve, without any lookahead setting to get wrong.

Which Pine features cannot be converted?

Anything stateful or procedural: var variables, loops, arrays, custom functions, ta.valuewhen, ternary if-else logic and limit or stop entry orders. Pivot functions that confirm a high only after later bars arrive also have no point-in-time equivalent.

Will my Trigr backtest match my TradingView numbers?

Rarely exactly. Trigr fills at the next bar's open, charges fees on every trade, can add slippage and real funding, and resolves stop and target conflicts inside a bar conservatively. Compare the trade lists, not just the summary, and explain every large difference.

Put the idea to an honest test.

Describe a strategy in plain English or from your own AI assistant, backtest it on point-in-time data, and forward-test it on paper before any real money is involved.