Trading Strategy Formulas: The Trigr Expression Node

Trigr's Expression node lets you write entries, filters, regimes and exits as formulas across timeframes and markets, with point-in-time backtests.

Trigr Research7 min read
On this page
  1. What is the Expression node?
  2. What can a formula read?
  3. What functions are available?
  4. Where does a formula go in a strategy?
  5. How does Trigr keep formulas point in time?
  6. What does this unlock?
  7. Where can a formula strategy run?
  8. How do you build one?
  9. Next steps

TL;DR: The Expression node turns any trading condition into a short formula. Instead of picking one indicator from a list, you write close > ema(close, 200) and close@1d > ema(close@1d, 50) or pct_change(close, 168) > pct_change(asset("BTC").close, 168) and the engine evaluates it point in time. Formulas can be a strategy's entry trigger, a filter, a regime rule or an exit. They read other timeframes, other markets, point-in-time data sources and, for research, your own uploaded signals. A strategy built this way backtests, gets a paper track and runs in Agents like any other.

What is the Expression node?

Until now, every condition in a Trigr strategy came from the catalog: pick an indicator, set its length, choose a comparator and a threshold. That covers a lot of ground, but it breaks down as soon as an idea combines things. "RSI under 30, but only when the daily trend is up and BTC is not dumping" needed three nodes and a combine rule, and "price stretched two standard deviations below its 50-bar average" needed an indicator that did not exist.

The Expression node removes that limit. It is a node whose only setting is a formula: a plain-ASCII condition, up to 500 characters, that evaluates to true or false on every bar. The backtesting service parses it, type-checks it, and refuses it with the exact character position if something is wrong, before any credits are spent on a run.

A formula is still a research hypothesis like any other condition. It makes strategies easier to express, not more likely to work, so the usual discipline about backtest overfitting applies with more force, not less.

What can a formula read?

A formula can read five kinds of input:

Input Syntax Example
The node's market and timeframe open, high, low, close, volume close > open
Another timeframe (same or coarser) close@4h, high@1d close@4h > ema(close@4h, 50)
Another market asset("ETH").close asset("ETH").close / asset("BTC").close
Another market on another timeframe asset("ETH").close@4h asset("ETH").close@4h > ema(asset("ETH").close@4h, 50)
A point-in-time data source api("source_id") api("funding_rate") < 0

A sixth input, upload("name"), reads a series you computed yourself in Python or a notebook. It has its own rules, covered in backtesting your own Python signal.

Operators are the ones you would expect: + - * /, the comparisons < <= > >= == !=, and and, or, not with parentheses. Numbers and true/false are valid operands, but every formula must read at least one market input; long: true on its own is refused. Comparisons cannot be chained, so write 30 < rsi(close, 14) and rsi(close, 14) < 70 rather than 30 < rsi(close, 14) < 70. The error message tells you exactly that.

What functions are available?

More than thirty functions, grouped by what they do. Window lengths are whole-number literals from 1 to 5,000 (at least 2 for rsi, stdev, rolling_std, zscore, rank, atr, adx and the Bollinger bands).

Group Functions
Indicators sma, ema, rsi, macd, macd_signal, macd_hist, stdev, bbands_mid, bbands_upper, bbands_lower, atr, adx
Windows lag, diff, pct_change, rolling_mean, rolling_std, rolling_min, rolling_max, rolling_sum, highest, lowest, zscore, rank, bars_since
Crosses crosses_above, crosses_below
Element-wise abs, log, sign, min, max, clip
Inputs api("source_id"), upload("name")

The indicators use the same math as the matching catalog nodes. We checked this directly: a strategy whose trigger is the formula close > ema(close, 50) and the same strategy built with the EMA indicator node produced identical results on SOLUSDT 1h, trade for trade. atr and adx also take a market or timeframe as their first argument, as in atr(asset("ETH"), 14) or adx(tf("4h"), 14).

Because functions compose, many indicators that are not built in can be written in one line. A stochastic oscillator is (close - lowest(low, 14)) / (highest(high, 14) - lowest(low, 14)), a Keltner band is ema(close, 20) + 2 * atr(14), and a rolling VWAP is rolling_sum(close * volume, 24) / rolling_sum(volume, 24). The recipes article has 25 of these.

Where does a formula go in a strategy?

A Trigr strategy is a node graph: one TRIGGER, optional FILTERs, a SIGNAL that combines them, a RISK node and an EXECUTE node. The no-code strategy builder guide explains each stage. Formulas plug into four of those places:

Place Parameters What it does
TRIGGER long, short, optional delivery Entry conditions per side. A bar where both are true is flat.
FILTER expr, or long and/or short Gates entries. expr applies to both sides exactly as written; it is never mirrored for shorts. With the default settings a filter that stops passing also closes an open trade; set RISK filtersCloseTrades to false to gate entries only.
RISK regime rule Same as FILTER Scales or blocks sizing by regime.
RISK exits exitLong, exitShort When true at a bar close, every open position on that side closes (exit reason formula_exit), and new entries on that side wait until it turns false.

Every expression node also takes tf, the timeframe it is evaluated on. A filter can run on 4h while the strategy trades 1h bars.

Two settings matter more with formulas than with indicator nodes. First, a TRIGGER's delivery is level by default, which means the entry signal repeats on every bar the formula is true. Set it to rising_edge and the trigger emits one pulse when the formula turns from false to true. Second, with the RISK default exitOnFlip on, a trigger that stops being true closes the trade. Combine an event formula such as crosses_above with that default and every trade lasts one bar. When you turn exitOnFlip off, leave onOpposite unset or ignore (reverse is refused, because nothing closes on an opposite signal any more); to reverse anyway, set exitLong to the short entry condition and exitShort to the long one. Event vs state signals walks through the numbers and the fix, and the backtest result now warns you when a strategy is set up this way.

How does Trigr keep formulas point in time?

A formula engine is only useful if it cannot quietly cheat. These rules are enforced by the grammar, not by convention:

  • No negative lag. lag(close, n) requires n of at least 1. There is no way to reference a future bar.
  • Every function looks backward. Rolling windows, z-scores, ranks and bars_since include the current bar and earlier ones only.
  • Coarser timeframes count only once closed. At 13:00 on a 1h strategy, close@4h is the close of the 4h bar that ended at 12:00, carried forward until the next one completes. Weekly values change only at the week boundary. We verified that a close@1w strategy flips only on Mondays at 00:00 UTC.
  • No finer timeframes. A formula on a 1h node cannot read close@15m; that is refused, in exit formulas too.
  • Missing values block. During warm-up, or when a data source has a gap, comparisons are unknown and an unknown condition is false. sma(close, 5000) cannot trade until 5,000 bars exist.
  • Cross-asset coverage clips the window. If a formula reads a market with shorter history, the backtest starts where that market's data starts, and the result says so.

This is the same discipline described in point-in-time backtesting, applied to arbitrary formulas instead of fixed indicators.

What does this unlock?

The practical difference is how many ideas you can test without waiting for a new node:

  • Indicators that are not in the catalog: stochastic, Williams %R, Keltner channels, rolling VWAP, rate of change, volume-weighted averages.
  • Relative strength and ratios: "SOL outperformed BTC over the last week", or "the ETH/BTC ratio is above its 50-bar average".
  • Regimes in one line: volatility percentile with rank(atr(14) / close, 500), trend strength with adx(14) > 25.
  • Cooldowns and patterns: "no entries for 12 bars after a 5% drop" is rolling_sum(pct_change(close, 1) < -0.05, 13) == 0.
  • Formula exits: leave a long when the 4h trend breaks, or when price closes two ATRs under the 20 EMA.
  • Ports from other platforms: most of Pine Script's ta.* library now has a direct equivalent. See converting Pine Script to Trigr formulas.
  • Your own research signals, through upload("name"), for exploratory backtests.

Where can a formula strategy run?

Formulas run in backtests, on the marketplace paper track, and in Paper, Hyperliquid and Propr Agents. Operators can switch formulas off per venue; if that happens, Agent creation refuses and names the venue.

There are three limits to know about:

  • ML optimization and ML training do not accept formula nodes yet.
  • Build-on-top composition from another creator's strategy does not accept formula nodes yet.
  • Uploaded series are research only. A strategy that reads upload("name") can be backtested, but it can never be published, get a marketplace paper track or be deployed to an Agent, Paper included, because Trigr cannot verify when your values were knowable.

volume is refused on CFD-sourced markets such as SPX, where the data provider's volume is tick activity rather than traded volume. Formulas that need more history than live evaluation loads, such as very long windows, are refused when an Agent is created, even if they backtest fine.

How do you build one?

You can write formulas in Trigr Studio or have an AI assistant write them for you over MCP. The Claude Code and Codex setup guide covers connecting an assistant. Either way, a minimal trend strategy looks like this as a graph:

{
  "market": "SOLUSDT",
  "timeframe": "1H",
  "nodes": [
    { "id": "entry", "kind": "TRIGGER", "source": "expr", "title": "EMA cross in a 4h uptrend",
      "params": [
        { "k": "tf", "v": "1H" },
        { "k": "long", "v": "crosses_above(ema(close, 12), ema(close, 26)) and close@4h > ema(close@4h, 50)" }
      ] },
    { "id": "signal", "kind": "SIGNAL", "title": "Entry", "params": [], "combine": { "mode": "all" } },
    { "id": "risk", "kind": "RISK", "title": "Risk",
      "params": [
        { "k": "size", "v": "10% eq" }, { "k": "lev", "v": "1x" }, { "k": "sl", "v": "3" },
        { "k": "direction", "v": "long" },
        { "k": "exitLong", "v": "ema(close, 12) < ema(close, 26) or rsi(close, 14) > 75" },
        { "k": "exitOnFlip", "v": "false" }
      ] },
    { "id": "execute", "kind": "EXECUTE", "title": "Paper", "params": [], "venue": "paper" }
  ]
}

The cross opens the trade, and the exit formula and the stop close it. Backtest it gross first, then with slippage and funding as described in slippage and funding in perp backtests, and read the trade log before trusting the summary.

Next steps

Pick one idea you could never express with a single indicator node and write it as a formula. Start from the formula recipes, check the strategy builder docs for every parameter, and keep each variation as a labelled experiment on one strategy so your trial count stays honest. External references for the underlying math: the Bollinger Bands and average true range entries on Wikipedia.

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

Frequently asked questions

What is the Expression node in Trigr?

It is a node type that takes a formula instead of a single catalog indicator. You write a true or false condition such as zscore(close - sma(close, 50), 100) < -2, and the backtesting engine parses it, type-checks it and evaluates it bar by bar using only data that existed at each bar.

Where can I use a formula in a Trigr strategy?

In four places: as the TRIGGER (separate long and short conditions), as a FILTER (one condition for both sides, or one per side), as a regime rule on the RISK node, and as exitLong or exitShort exit conditions on the RISK node.

Can a formula strategy trade live?

Yes. A strategy built with formulas can publish with a marketplace paper track and run in Paper, Hyperliquid and Propr Agents. The exceptions are formulas that read your own uploaded series, which are research only, and ML optimization and build-on-top composition, which do not support formulas yet.

Can a formula look into the future by mistake?

The grammar is built to prevent it. Every function reads the current and earlier bars only, lag requires at least one bar, and higher-timeframe or other-market values become visible only after their own bar has closed.

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.