TL;DR: Below are 27 Expression formulas, numbered as 25 recipes, grouped by idea: trend, mean reversion, breakouts, volatility regimes, volume, cross-asset, funding data, timing and exits. Every formula was run on the live engine before publishing. Next to each is its hit rate, the share of SOLUSDT 1h bars (September 2020 to October 2026) on which it was true. That number tells you where the formula belongs: rare conditions are entry events, frequent ones are filters or regimes.
How should you read these recipes?
Each recipe is a condition, not a complete strategy. To use one, decide where it sits:
- TRIGGER
longorshort: the entry condition. Rare, event-like formulas (hit rate under about 5%) usually belong here. - FILTER
expr, orlong/short: a gate that must also hold. Regime-like formulas (20% to 80%) usually belong here. - RISK
exitLong/exitShort: closes the position when true.
If you use an event formula as the trigger, read event vs state signals first. With the default exit settings, a one-bar event closes its own trade one bar later.
The hit rates below describe how often a condition fires, not whether it makes money. Every recipe is a hypothesis to test with costs, run as labelled experiments, and judge against the overfitting warning signs.
Trend-following recipes
| # | Idea | Formula | Hit rate |
|---|---|---|---|
| 1 | Golden cross | crosses_above(sma(close, 50), sma(close, 200)) |
0.3% |
| 2 | Trend stack: price above 200 EMA and daily trend up | close > ema(close, 200) and close@1d > ema(close@1d, 50) |
34.5% |
| 3 | MACD cross below zero (early trend) | crosses_above(macd(close, 12, 26), macd_signal(close, 12, 26, 9)) and macd(close, 12, 26) < 0 |
2.4% |
| 4 | Strong and strengthening trend | adx(14) > 25 and adx(14) > lag(adx(14), 3) |
27.6% |
Recipe 1 is a classic event trigger; pair it with recipe 2 or 4 as a filter. Recipe 2 shows the multi-timeframe syntax: close@1d is the last completed daily close, so on a 1h strategy it only updates once a day. Building a BTC trend-following strategy covers the exit side of trend systems.
Mean-reversion recipes
| # | Idea | Formula | Hit rate |
|---|---|---|---|
| 5 | RSI(2) dip inside an uptrend | rsi(close, 2) < 10 and close > sma(close, 200) |
4.2% |
| 6 | Reclaim of the lower Bollinger band | crosses_above(close, bbands_lower(close, 20, 2)) |
3.1% |
| 7 | Stretched two deviations below trend | zscore(close - sma(close, 50), 100) < -2 |
4.6% |
| 8 | Stochastic oversold (built from primitives) | (close - lowest(low, 14)) / (highest(high, 14) - lowest(low, 14)) < 0.2 |
17.0% |
| 9 | Williams %R oversold (built from primitives) | (highest(high, 14) - close) / (highest(high, 14) - lowest(low, 14)) > 0.8 |
17.0% |
Recipes 8 and 9 are the same condition written two ways, and they fired on exactly the same 9,017 bars. That is a useful habit when you rebuild a standard indicator from its definition: write it twice and confirm the counts match. Mean reversion strategies for crypto discusses why the uptrend gate in recipe 5 matters.
Breakout and volatility recipes
| # | Idea | Formula | Hit rate |
|---|---|---|---|
| 10 | Donchian breakout of the prior 20-bar high | close > lag(highest(high, 20), 1) |
4.9% |
| 11 | Squeeze, then breakout | rolling_min((bbands_upper(close, 20, 2) - bbands_lower(close, 20, 2)) / sma(close, 20), 10) < 0.04 and close > lag(highest(high, 20), 1) |
2.4% |
| 12 | Keltner upper-band breakout | close > ema(close, 20) + 2 * atr(14) |
6.3% |
| 13 | Calm regime (ATR under 2% of price) | atr(14) / close < 0.02 |
76.3% |
| 14 | Volatility in its lowest fifth of the last 500 bars | rank(atr(14) / close, 500) < 0.2 |
24.9% |
Recipe 10 uses lag(..., 1) on purpose: the current bar's high is part of highest(high, 20), and a close can never exceed its own bar's high, so without the lag the condition would never be true. Recipe 11 shows that window functions accept whole expressions: rolling_min runs over the Bollinger bandwidth itself. Recipe 14 is a self-calibrating version of recipe 13 that adapts to each market's normal volatility.
Volume recipes
| # | Idea | Formula | Hit rate |
|---|---|---|---|
| 15 | Volume spike on a green bar | volume > 2 * rolling_mean(volume, 20) and close > open |
3.6% |
| 16 | Price above its 24-bar rolling VWAP | close > rolling_sum(close * volume, 24) / rolling_sum(volume, 24) |
49.7% |
| 17 | Rolling on-balance volume positive | rolling_sum(sign(diff(close)) * volume, 20) > 0 |
49.0% |
These read volume, so they are refused on CFD-sourced markets such as SPX. Recipe 16 is a rolling VWAP, not the session-anchored VWAP on many charting platforms. Recipe 17 is a 20-bar version of on-balance volume rather than the cumulative original.
Cross-asset recipes
| # | Idea | Formula | Hit rate |
|---|---|---|---|
| 18 | ETH/BTC ratio above its 50-bar average | asset("ETH").close / asset("BTC").close > sma(asset("ETH").close / asset("BTC").close, 50) |
45.9% |
| 19 | BTC moved, this market has not yet | pct_change(asset("BTC").close, 4) > 0.01 and pct_change(close, 4) < 0.005 |
2.1% |
| 20 | Outperforming BTC over the last week (1h bars) | pct_change(close, 168) > pct_change(asset("BTC").close, 168) |
45.7% |
asset("...") reads any market in Trigr's registry, and functions run on the derived series, so recipe 18 computes a moving average of a ratio of two markets. If a referenced market has shorter history, the backtest window starts where its data starts and the result says so. Cross-asset crypto signals goes deeper on lead-lag ideas like recipe 19.
Funding-data recipes
| # | Idea | Formula | Hit rate |
|---|---|---|---|
| 21 | Shorts paying longs inside an uptrend | api("funding_rate") < 0 and close > sma(close, 50) |
10.4% |
| 22 | Funding unusually high versus its own history | zscore(api("funding_rate"), 90) > 2 |
2.9% |
api("source_id") reads a point-in-time data source for the strategy's market. Check the source catalog for each source's units before choosing a threshold; funding here is a fraction per funding period (0.0001 means 0.01%), not a percentage. Recipe 22 avoids the units question by using a z-score. Funding rate and open interest strategies explains the crowd-positioning idea behind both.
Timing and pattern recipes
| # | Idea | Formula | Hit rate |
|---|---|---|---|
| 23 | Cooldown: no entries for 12 bars after a 5% drop | rolling_sum(pct_change(close, 1) < -0.05, 13) == 0 |
96.9% |
| 24 | Three green bars in a row | rolling_sum(close > open, 3) == 3 |
11.2% |
Recipe 23 is a filter that blocks entries rather than one that creates them: it is true unless a 5% drop happened in the current bar or the 12 before it. It counts drops with rolling_sum instead of using bars_since(...) > 12 on purpose. bars_since is missing when the condition never happened in its lookback (1,000 bars by default), and a missing value makes the comparison false, so that version blocks every entry in a calm market. On our test data it was true on only 62.1% of bars, against 96.9% for the version above.
Exit recipes
Exit formulas go on the RISK node as exitLong or exitShort. When true at a bar close, every open position on that side closes, whatever the flip settings say.
| # | Idea | Formula | Hit rate |
|---|---|---|---|
| 25a | Leave a long when the 4h trend breaks | close@4h < ema(close@4h, 50) |
49.7% |
| 25b | Leave a long on a close two ATRs under the 20 EMA | close < ema(close, 20) - 2 * atr(14) |
5.0% |
| 25c | Take profit on overbought | rsi(close, 14) > 75 |
2.1% |
A high hit rate is normal for a trend-break exit like 25a: it is true for as long as the trend is down, and that is when you want to be flat. Exit formulas run on the strategy's own timeframe, so 25a needs a strategy timeframe of 4h or finer. When several exits could fire on the same bar, the stop, target and time stop are checked first, then exit formulas, then signal flips. The strategy builder docs describe the stop, target, trail and time stop.
How do you turn a recipe into a strategy?
Take one trigger, at most two filters and one exit, and build a single strategy. Backtest it gross, then with slippage and funding, and read the trade log. Every variation after that (a different window, an extra filter, another exit) is a labelled experiment on the same strategy, which keeps the number of trials visible, as iterating with AI experiments explains.
A complete example, built from recipes 3, 20 and 25b on a 1h strategy:
TRIGGER long : crosses_above(macd(close, 12, 26), macd_signal(close, 12, 26, 9)) and macd(close, 12, 26) < 0
FILTER long : pct_change(close, 168) > pct_change(asset("BTC").close, 168)
RISK : direction long, sl 3, exitOnFlip false, filtersCloseTrades false
RISK exitLong: close < ema(close, 20) - 2 * atr(14)
The Expression node overview lists every function used here. For the math behind the indicators, Wikipedia's articles on the MACD and the average true range are good references.
Backtests are not guarantees, and perps are leveraged instruments that can lose more than expected.