Skip to content

Custom node

If a node is missing, build it yourself. A custom node has inputs on the left of the card, settings you change on the board and outputs on the right, each with a formula. You don’t write code: the formula is a calculation such as ema(price, 20) or rsi(price, 14) < 30.

Custom nodes compute exactly like the official ones: the same in tests and live, and never with prices from the future. They sit in the node panel under Custom nodes, connect like any other node and can be shared as a file.

Custom nodes come with Pro. On Free you can still build them and test them in the replay; a chain with a custom node can only be activated with Pro, see Plans.

  1. Open the node panel on the right and click New node under Custom nodes.

  2. Pick an example such as Distance to EMA, or Start empty. An example is the fastest way: it is complete and easy to adapt.

  3. Under Name and look, give the node a name, a short description for the panel and an icon.

  4. Add the inputs. Each has a name shown on the card and a type: Price series (such as the price of a candle), Number (such as an RSI or the volume) or Signal (true or false). The colored field next to it, such as price, is the name in formulas.

  5. Add settings if something should be adjustable on the board, such as the number of candles with default 20, from 2 to 500.

  6. Add the outputs and write a formula for each. Names and functions are inserted with one click. On the right you see how the card looks on the board and a trial run over 500 hours of BTC/USDT. Save and add to board places the node on the board, and from then on it is under Custom nodes.

A formula computes on every candle. price - ema(price, 20) gives the distance of the price to its EMA 20 on every candle. What you need:

How to write it Meaning
+ - * / % ^ calculate; ×, ÷ and − work too
> < >= <= == != compare; the result is a signal
and, or, not combine signals
condition ? a : b a or b per candle, such as close > open ? high : low
price[1] the value one candle earlier, price[4] four candles earlier
1.5, 0.02 decimals with a point

Good to know:

  • Signal output: the formula must give true or false, so it usually contains a comparison, such as rsi(price, 14) < 30.
  • Lengths are fixed values: a number, a setting or a calculation from them, such as ema(price, length * 2). 1 to 1,000 candles are allowed. A setting used as a length therefore needs a minimum and a maximum.
  • Later outputs read earlier ones: in RSI zones the first output computes value = rsi(price, length), the next two only compare value < low and value > high.
  • Empty values at the start: an EMA 200 only has a value after 200 candles. Before that the result is empty and every signal false. The replay loads these candles beforehand, so you won’t notice. nz(value, 0) puts in a number where needed.

Write functions in lowercase, EMA works too. The editor shows the same list under Functions at the formula field.

Averages

FunctionWhat it doesExample
sma(value, candles)Simple average: every candle counts the same.sma(price, 50)
ema(value, candles)Exponential average: recent candles count more.ema(price, 200)
wma(value, candles)Weighted average: the weight drops evenly.wma(price, 20)
rma(value, candles)Wilder’s smoothed average, as used in the RSI and the ATR.rma(price, 14)
hma(value, candles)Hull average: fast and still smooth.hma(price, 21)

Indicators

FunctionWhat it doesExample
rsi(value, candles)Relative strength from 0 to 100.rsi(price, 14) < 30
stdev(value, candles)Standard deviation: how much the value swings.stdev(price, 20)
highest(value, candles)Highest value of the last candles, including the current one.price > highest(price, 20)[1]
lowest(value, candles)Lowest value of the last candles, including the current one.lowest(price, 20)
sum(value, candles)Sum of the last candles.sum(volume, 24)
change(value, candles = 1)Difference from the value that many candles ago.change(price, 4)
roc(value, candles = 1)Change in percent compared with the value that many candles ago.roc(price, 24) < -5
prev(value, candles = 1)The value that many candles ago. Short form: value[1].price > prev(price)
atr(high, low, close, candles)Average range of a candle, Average True Range.atr(high, low, close, 14)
tr(high, low, close)Range of a candle including the gap to the previous close, True Range.tr(high, low, close)
stoch(value, high, low, candles)Where the value sits within the range of the last candles, from 0 (lowest low) to 100 (highest high); empty when high and low are equal.sma(stoch(close, high, low, 14), 3) < 20
cci(value, candles)Distance from the average relative to the mean deviation; above 100 unusually strong, below −100 unusually weak.cci((high + low + close) / 3, 20) > 100
dev(value, candles)Mean deviation of the last values from their average.dev(price, 20)
wpr(high, low, close, candles)Williams %R from −100 (close at the lowest low) to 0 (at the highest high) of the last candles; empty when high and low are equal.wpr(high, low, close, 14) < -80
mfi(value, volume, candles)Money Flow Index from 0 to 100: like the RSI, but weighted by volume; below 20 counts as oversold.mfi((high + low + close) / 3, volume, 14) < 20
adx(high, low, close, candles, smoothing = candles)Trend strength from 0 to 100, whatever the direction; above 25 counts as a strong trend.adx(high, low, close, 14) > 25
diplus(high, low, close, candles)Strength of the upward movement (+DI) from 0 to 100.diplus(high, low, close, 14) > diminus(high, low, close, 14)
diminus(high, low, close, candles)Strength of the downward movement (−DI) from 0 to 100.crossover(diminus(high, low, close, 14), diplus(high, low, close, 14))
linreg(value, candles, back = 0)Linear regression: the value of the best-fit line through the last candles, at the current one or that many candles before.linreg(price, 20) > linreg(price, 20)[1]
highestbars(value, candles)How many candles ago the highest value of the last candles was, 0 on the current one; with equal values the most recent counts.100 * (14 - highestbars(high, 15)) / 14
lowestbars(value, candles)How many candles ago the lowest value of the last candles was, 0 on the current one; with equal values the most recent counts.lowestbars(low, 20) == 0

Math

FunctionWhat it doesExample
abs(value)Absolute value: makes negative numbers positive.abs(change(price))
min(a, b, …)The smallest of the values.min(open, close)
max(a, b, …)The largest of the values.max(open, close)
avg(a, b, …)Average of the values on the same candle.avg(high, low, close)
sqrt(value)Square root; empty for negative values.sqrt(abs(change(price)))
pow(value, exponent)Power, like value ^ exponent.pow(change(price), 2)
log(value)Natural logarithm; empty for 0 and negative values.log(price / prev(price))
exp(value)e to the power of the value.exp(1)
round(value, digits = 0)Rounds to that many decimal places.round(rsi(price, 14), 1)
floor(value)Rounds down to a whole number.floor(price / 1000) * 1000
ceil(value)Rounds up to a whole number.ceil(price / 1000) * 1000
sign(value)1 for positive, -1 for negative values, otherwise 0.sign(change(price))
clamp(value, low, high)Limits the value to a range.clamp(rsi(price, 14), 20, 80)
nz(value, fallback = 0)Uses the fallback while the value is still empty, e.g. during warm-up.nz(volume)

Signals

FunctionWhat it doesExample
crossover(a, b)True on the candle where a rises from below to above b.crossover(ema(price, 50), ema(price, 200))
crossunder(a, b)True on the candle where a falls from above to below b.crossunder(price, sma(price, 200))
cross(a, b)True on the candle where a crosses line b in either direction.cross(price, 100000)
rising(value, candles = 1)True when the value is higher than on each of the candles before.rising(volume, 3)
falling(value, candles = 1)True when the value is lower than on each of the candles before.falling(price, 3)
holds(signal, candles)True when the signal has been true for that many candles in a row.holds(price < ema(price, 200), 3)
count(signal, candles)How often the signal was true in the last candles.count(close > open, 5) >= 4
barssince(signal)Candles since the signal was last true; empty while it has never been true.barssince(crossover(price, ema(price, 50))) <= 3
if(condition, then, else)Picks one of two values per candle, like condition ? then : else.if(close > open, high, low)

These examples are available in the editor under New node. You can also copy the JSON and paste it under Import.

How far the price is above or below its average, in percent.

  • Inputs:Price (price series, price)
  • Settings:Candles length = 50
  • Outputs:
    • Distance (number): (price - ema(price, length)) / ema(price, length) * 100
As JSON to import
{
"format": 1,
"id": "custom.abstand-zum-ema",
"version": "1.0.0",
"label": {
"de": "Abstand zum EMA",
"en": "Distance to EMA"
},
"description": {
"de": "Abstand des Kurses zum EMA in %",
"en": "Distance of the price to the EMA in %"
},
"help": {
"de": "Rechnet aus, wie viel Prozent der Kurs über (positiv) oder unter (negativ) seinem exponentiellen Durchschnitt liegt. Beispiel: Mit 50 Kerzen und „unter -8“ meldet sich ein Alarm, wenn der Kurs ungewöhnlich weit unter den Trend gefallen ist.",
"en": "Calculates how many percent the price is above (positive) or below (negative) its exponential average. Example: with 50 candles and “below -8” an alert fires when the price has fallen unusually far below the trend."
},
"icon": "ruler",
"sentence": {
"de": "EMA {length}",
"en": "EMA {length}"
},
"inputs": [
{
"id": "price",
"type": "price",
"label": {
"de": "Kurs",
"en": "Price"
}
}
],
"settings": [
{
"id": "length",
"label": {
"de": "Kerzen",
"en": "Candles"
},
"help": {
"de": "Über wie viele Kerzen der EMA rechnet, etwa 50 oder 200.",
"en": "How many candles the EMA covers, such as 50 or 200."
},
"default": 50,
"min": 2,
"max": 500,
"integer": true
}
],
"outputs": [
{
"id": "out",
"type": "number",
"unit": "percent",
"label": {
"de": "Abstand",
"en": "Distance"
},
"formula": "(price - ema(price, length)) / ema(price, length) * 100"
}
]
}

Two signals from one node: oversold and overbought, with your own limits.

  • Inputs:Price (price series, price)
  • Settings:Candles length = 14, Low low = 30, High high = 70
  • Outputs:
    • RSI (number): rsi(price, length)
    • oversold (signal): value < low
    • overbought (signal): value > high
As JSON to import
{
"format": 1,
"id": "custom.rsi-zonen",
"version": "1.0.0",
"label": {
"de": "RSI-Zonen",
"en": "RSI zones"
},
"description": {
"de": "Überverkauft und überkauft als Signal",
"en": "Oversold and overbought as signals"
},
"icon": "gauge",
"sentence": {
"de": "unter {low}, über {high}",
"en": "below {low}, above {high}"
},
"inputs": [
{
"id": "price",
"type": "price",
"label": {
"de": "Kurs",
"en": "Price"
}
}
],
"settings": [
{
"id": "length",
"label": {
"de": "Kerzen",
"en": "Candles"
},
"default": 14,
"min": 2,
"max": 100,
"integer": true
},
{
"id": "low",
"label": {
"de": "Unten",
"en": "Low"
},
"default": 30,
"min": 0,
"max": 100
},
{
"id": "high",
"label": {
"de": "Oben",
"en": "High"
},
"default": 70,
"min": 0,
"max": 100
}
],
"outputs": [
{
"id": "value",
"type": "number",
"label": {
"de": "RSI",
"en": "RSI"
},
"formula": "rsi(price, length)"
},
{
"id": "oversold",
"type": "signal",
"label": {
"de": "überverkauft",
"en": "oversold"
},
"formula": "value < low"
},
{
"id": "overbought",
"type": "signal",
"label": {
"de": "überkauft",
"en": "overbought"
},
"formula": "value > high"
}
]
}

The fast average crosses above the slow one, classically 50 over 200.

  • Inputs:Price (price series, price)
  • Settings:Fast fast = 50, Slow slow = 200
  • Outputs:
    • Cross (signal): crossover(ema(price, fast), ema(price, slow))
As JSON to import
{
"format": 1,
"id": "custom.golden-cross",
"version": "1.0.0",
"label": {
"de": "Golden Cross",
"en": "Golden cross"
},
"description": {
"de": "Schneller EMA kreuzt langsamen nach oben",
"en": "Fast EMA crosses above the slow one"
},
"icon": "trending-up",
"sentence": {
"de": "EMA {fast} über {slow}",
"en": "EMA {fast} over {slow}"
},
"inputs": [
{
"id": "price",
"type": "price",
"label": {
"de": "Kurs",
"en": "Price"
}
}
],
"settings": [
{
"id": "fast",
"label": {
"de": "Schnell",
"en": "Fast"
},
"default": 50,
"min": 2,
"max": 500,
"integer": true
},
{
"id": "slow",
"label": {
"de": "Langsam",
"en": "Slow"
},
"default": 200,
"min": 2,
"max": 500,
"integer": true
}
],
"outputs": [
{
"id": "out",
"type": "signal",
"label": {
"de": "Kreuz",
"en": "Cross"
},
"formula": "crossover(ema(price, fast), ema(price, slow))"
}
]
}

A candle with a multiple of the usual volume, as with news.

  • Inputs:Volume (number, volume)
  • Settings:Candles length = 20, Factor factor = 2.5
  • Outputs:
    • Spike (signal): volume > sma(volume, length) * factor
As JSON to import
{
"format": 1,
"id": "custom.volumen-spitze",
"version": "1.0.0",
"label": {
"de": "Volumen-Spitze",
"en": "Volume spike"
},
"description": {
"de": "Volumen über dem Vielfachen des Schnitts",
"en": "Volume above a multiple of its average"
},
"icon": "chart-column",
"sentence": {
"de": "{factor} × Ø {length}",
"en": "{factor} × avg {length}"
},
"inputs": [
{
"id": "volume",
"type": "number",
"label": {
"de": "Volumen",
"en": "Volume"
}
}
],
"settings": [
{
"id": "length",
"label": {
"de": "Kerzen",
"en": "Candles"
},
"default": 20,
"min": 2,
"max": 500,
"integer": true
},
{
"id": "factor",
"label": {
"de": "Faktor",
"en": "Factor"
},
"default": 2.5,
"min": 1,
"max": 20,
"step": 0.5
}
],
"outputs": [
{
"id": "out",
"type": "signal",
"label": {
"de": "Spitze",
"en": "Spike"
},
"formula": "volume > sma(volume, length) * factor"
}
]
}

How large a candle’s body is, in percent; large bodies show strong moves.

  • Inputs:Open (price series, open), Close (price series, close)
  • Settings:none
  • Outputs:
    • Body (number): (close - open) / open * 100
    • green (signal): close > open
As JSON to import
{
"format": 1,
"id": "custom.kerzenkoerper",
"version": "1.0.0",
"label": {
"de": "Kerzenkörper",
"en": "Candle body"
},
"description": {
"de": "Größe des Kerzenkörpers in %",
"en": "Size of the candle body in %"
},
"icon": "chart-candlestick",
"inputs": [
{
"id": "open",
"type": "price",
"label": {
"de": "Eröffnung",
"en": "Open"
}
},
{
"id": "close",
"type": "price",
"label": {
"de": "Schluss",
"en": "Close"
}
}
],
"outputs": [
{
"id": "body",
"type": "number",
"unit": "percent",
"label": {
"de": "Körper",
"en": "Body"
},
"formula": "(close - open) / open * 100"
},
{
"id": "green",
"type": "signal",
"label": {
"de": "grün",
"en": "green"
},
"formula": "close > open"
}
]
}

Several green candles in a row, a sign of steady buying pressure.

  • Inputs:Open (price series, open), Close (price series, close)
  • Settings:Candles length = 3
  • Outputs:
    • Streak (signal): holds(close > open, length)
As JSON to import
{
"format": 1,
"id": "custom.gruene-serie",
"version": "1.0.0",
"label": {
"de": "Grüne Serie",
"en": "Green streak"
},
"description": {
"de": "Mehrere grüne Kerzen in Folge",
"en": "Several green candles in a row"
},
"icon": "repeat",
"sentence": {
"de": "{length} Kerzen in Folge",
"en": "{length} candles in a row"
},
"inputs": [
{
"id": "open",
"type": "price",
"label": {
"de": "Eröffnung",
"en": "Open"
}
},
{
"id": "close",
"type": "price",
"label": {
"de": "Schluss",
"en": "Close"
}
}
],
"settings": [
{
"id": "length",
"label": {
"de": "Kerzen",
"en": "Candles"
},
"default": 3,
"min": 2,
"max": 20,
"integer": true
}
],
"outputs": [
{
"id": "out",
"type": "signal",
"label": {
"de": "Serie",
"en": "Streak"
},
"formula": "holds(close > open, length)"
}
]
}

If you already built a chain, you can turn part of it into a node without writing a formula:

  1. Select the nodes on the board: click them one after another while holding ⌘ (Mac) or Ctrl, or hold Shift and drag a frame around them.

  2. On the right under Properties, click Save as node.

  3. The dialog shows the new node with formulas already written. What leads into the selection becomes an input, what leads out an output. Give it a name and click Save and replace.

The selection is replaced by the new node, connected as before. It computes exactly like the nodes did; the BTC Dip Buyer, for example, becomes rsi(price, 14) < 35 and price > ema(price, 200). Sources and actions stay on the board. Nodes that remember something across candles (Cooldown), depend on their whole history (Supertrend, OBV) or work with the time of day and calendar days (Time window, Pivot points, VWAP, High/Low with previous day or week) cannot be translated; the editor tells you which one it is.

  • Edit: the pencil next to the node in the node panel, or click the node on the board and choose Edit node under Properties.
  • Save raises the version, for example from 1.0.0 to 1.1.0. The open chain computes with the new version right away. Other chains keep their version until you place the node there again.
  • Delete in the dialog removes the node from Custom nodes. Chains that use it keep their copy and keep computing.
  • Export in the dialog saves the node as a file, such as abstand-zum-ema.bellchain-node.json. You can pass the file on, for example in a chat.
  • Import in the editor’s top bar accepts chains and custom nodes. A node lands under Custom nodes.
  • A chain carries its custom nodes. Whoever imports your chain gets the nodes too, without the files.
  • Under JSON in the dialog you see the node as text, as it is in the file. What you change there shows up under Design right away, and the other way round.

With an account, your custom nodes and favorites are stored in your account and available on every device. Without an account this browser keeps them; after you sign in they move to your account.

What Limit
Inputs, settings, outputs 8 each
Formula 1,000 characters
Length in candles 1 to 1,000
Custom nodes per chain 20
Custom nodes in an account 100
Favorites 50

Version 2026.10.21.23 · Changelog