Multi-source and aggregation
One indicator can read several data types and markets at once: the other feeds of the chart's market, other markets' candles, and a sum across venues.
Other markets come in through symbol + exchange pins, side-split flow
through side, sparse feeds through missing, and aggregation is
ordinary arithmetic across inputs. Everything rides input declarations,
and the finer bars inside each chart bar come from the intrabar celled
source.
Introduction
An indicator is not limited to its chart's candles. On the chart it can declare the other data types the chart serves for the chart's own market (open interest, funding, side-split trades, liquidations, a book snapshot, a volume profile) and the candles of other markets and venues, and combine them with ordinary math. "Aggregated" indicators (one quantity summed across venues) are not a special feature; they fall out of this plus a few lines of arithmetic.
input("close", ohlcv.close); // the first input: the chart's own candles
input("btc", ohlcv.close, { symbol: "BTCUSDT", exchange: "BINANCE_FUTURES" }); // a pinned reference market
// in onBar(): out_ratio(bar.close() / in_btc());How multiple sources coexist
Three rules make cross-source math safe by default:
- One grid: the chart's candles. The rows are the chart's own candles; the first input follows the chart's market and interval, and every other input is aligned onto those rows. Every input is readable on every bar.
- Values align by policy. A source at the chart's interval joins by
bar open. A coarser pin contributes only as of each bar's close
(Multi-timeframe). A scalar source with no
observation on a bar delivers the latest value carried forward
(
missing: "carry", the default),NaN("nan"), or0("zero"), and you choose per input. Funding against 1h candles just works. - Events never fabricate. Every comparison with
NaNis false, andCross.update()answers0when either side isNaN, so a venue that has not reported cannot invent a signal.
Multi-symbol
A secondary ohlcv input can name a market other than the chart's. The two
halves pin together (the editor refuses a lone symbol or a lone
exchange), written in the chart's own ids, and everything downstream is
source-agnostic: classes, folds, gates, shapes. The ETH/BTC ratio and the
relative-strength reading between them:
param("period", 14, { min: 2, max: 200, description: "Lookback for the ratio average and the leader's RSI" });
// The first input follows the chart (ETHUSDT on an ETH chart); the reference is pinned.
input("close", ohlcv.close);
input("btc", ohlcv.close, { symbol: "BTCUSDT", exchange: "BINANCE_FUTURES", description: "BTC close, the reference market" });
output("ratio", line, lower, { color: "#2563eb", width: 2, description: "The chart's close divided by the BTC close" });
output("ratio_sma", line, lower, { color: "#94a3b8", description: "Average of the ratio" });
output("lead_rsi", line, lower, { color: "#f59e0b", description: "RSI of the reference market: the leader's momentum" });
let sma = new Sma(14);
let rsi = new Rsi(14);
function onStart(): void {
sma = new Sma(i32(p_period()));
rsi = new Rsi(i32(p_period()));
}
function onBar(): void {
const close = bar.close();
const btc = in_btc();
const ratio = btc > 0.0 ? close / btc : NaN;
const ratioSma = isNaN(ratio) ? NaN : sma.update(ratio);
const leadRsi = rsi.update(btc);
if (isNaN(ratioSma) || isNaN(leadRsi)) return;
out_ratio(ratio);
out_ratio_sma(ratioSma);
out_lead_rsi(leadRsi);
}On the chart the pinned market's candles are joined onto the chart's rows
by timestamp, at the chart's interval, under the input's missing
policy, and kept live from that market's own candle feed, so the forming
bar reads the reference market's forming candle.
For another market at a coarser interval, the declaration adds interval
to the pin:
input("eth_4h", ohlcv.close, { symbol: "ETHUSDT", exchange: "BINANCE_FUTURES", interval: "4h" }), read as of each bar's close
(Multi-timeframe). Cross-symbol arithmetic is only
dimensionally sane under a shared quote currency.
Only a secondary ohlcv input may name another market. The chart refuses,
by name and before it fetches anything, a market pin on the first input,
on any other feed (trades, open interest, funding, liquidations, implied
volatility, skew, ETF flows, time) and on every celled source: those
always read the chart's own market. A Polymarket odds input is the one
exception, since it always names its own market by condition id
(Data sources). Stocks, ETFs, forex, gold and silver
pin the same way, on any chart (next section).
Stocks, forex and gold
An input can read a stock, an ETF, a forex pair, gold or silver beside the chart's own market. Pin it with the market's exchange id and symbol, as you pin any other market.
// The chart's market measured against a stock ETF, a forex pair and gold, each read from its own market's daily candles.
// The first input is the indicator's own market: leave it unpinned so it follows the chart.
input("close", ohlcv.close);
input("spy", ohlcv.close, { symbol: "SPY/USD", exchange: "POLYGON", interval: "1d" });
input("eurusd", ohlcv.close, { symbol: "EUR/USD", exchange: "FX_OTC", interval: "1d" });
input("gold", ohlcv.close, { symbol: "XAU/USD", exchange: "FX_OTC", interval: "1d" });
output("vs_spy", line, lower, { color: "#2563eb", description: "The close divided by SPY's last daily close" });
output("in_euro", line, lower, { color: "#16a34a", description: "The close in euros" });
output("in_ounces", line, lower, { color: "#eab308", description: "The close in ounces of gold" });
function onBar(): void {
const close = bar.close();
if (isNaN(close)) return;
const spy = in_spy();
const eurusd = in_eurusd();
const gold = in_gold();
// A pinned input reads NaN until its first daily candle has closed.
if (isFinite(spy) && spy > 0) out_vs_spy(close / spy);
if (isFinite(eurusd) && eurusd > 0) out_in_euro(close / eurusd);
if (isFinite(gold) && gold > 0) out_in_ounces(close / gold);
}- Keep the chart's own input first. The first input is the indicator's own market, and every pin lines up with its bars.
- Each pin reads its market's last closed daily candle, so run this on a chart finer than one day.
- A pinned input is
NaNuntil its first candle closes. Check before you divide.
Markets you can pin
| Market | exchange | symbol | What you get |
|---|---|---|---|
| US stocks and ETFs | POLYGON | AAPL/USD, SPY/USD, QQQ/USD, GLD/USD | candles with volume, in regular trading hours unless the chart is a US stock chart with extended hours on |
| Forex | FX_OTC | EUR/USD, GBP/USD, USD/JPY | quote candles: open, high, low and close; volume reads 0 |
| Gold and silver | FX_OTC | XAU/USD, XAG/USD | quote candles, as forex |
| Crypto | the venue's id | the venue's spelling | Exchange and symbol format |
Stocks and forex trade in sessions, so their bars do not always open where a crypto chart's bars open. Pin an interval coarser than the chart's, as the example does: a pin at the chart's own interval lines up bar by bar on exact times, and a stock's hourly bars (13:30 UTC, 14:30 UTC, ...) never meet a crypto chart's hourly bars.
What you cannot pin
| Pin | What happens |
|---|---|
a CME Group market (CME, CBOT, NYMEX, COMEX, GLOBEX) | the build refuses it: input 'es' pins exchange CME: wrun_. A param.symbol default on one is refused the same way. |
| more than one market in a pin | the build refuses it: input 'spy' pins symbol "SPY/USD + CME|ES1!": wrun_ |
| an index (S&P 500, Nasdaq 100, VIX, the dollar index) | the build accepts POLYGON_INDICES, but index series are not served yet. On the chart the input reads NaN on every bar and the legend says why; an alert on the indicator is refused. Pin an ETF on the same market instead (SPY/USD, QQQ/USD). |
a 3d pin on a stock, forex or gold market | the chart refuses it before it fetches: a three-day candle there groups trading days and would count as closed too early. Pin 1d or 1w. |
The chart's own market may still be a CME market on a chart entitled to it: the rule is about reading CME data from another market's script.
The chart's words for the last two rows:
Input 'spx' pins SPX on POLYGON_INDICES; index series (SPX, NDX, VIX, DJI) are not served yet: the gateway holds POLYGON_INDICES out of the market directory until the backend opens it, so the input reads its missing fill (NaN) on every bar.Input 'spy' pins interval '3d' on POLYGON/SPY/USD: a multi-day candle on a session venue groups trading days (a Friday bar runs into Monday) and would be confirmed at its open plus the span, early and still forming (wrun_pin_session_interval_unserved); pin 1d or 1w instead
In alerts
An alert evaluates pinned stock, forex and gold candles like any other pinned market. It refuses an indicator that pins an index ("This Indicator reads a market alerts cannot evaluate yet (an index).") or a CME Group market ("Indicator alerts are not allowed on this venue."). Alerts read stock candles with extended hours, while the chart reads regular hours unless its own market is a US stock with extended hours on, so a stock leg can differ slightly between the chart and its alert.
Multi-venue aggregation
The flagship pattern: one quantity, summed across exchanges. On the chart
the venues come in through their candles, so the natural aggregates are
candle ones: volume across venues, a cross-venue price spread, a
volume-weighted price. Each venue is one pinned ohlcv input declared with
missing: "nan", so a venue with no candle on a bar reads NaN, counts as
not reporting, and contributes nothing:
param("smooth", 21, { min: 2, max: 200, description: "EMA length over the aggregated volume" });
// The chart's own candles are the rows; each venue's candles join onto them by timestamp.
input("close", ohlcv.close);
input("vol_binance", ohlcv.volume, { symbol: "BTCUSDT", exchange: "BINANCE_FUTURES", missing: "nan", description: "Binance BTC perpetual volume" });
input("vol_bybit", ohlcv.volume, { symbol: "BTCUSDT", exchange: "BYBIT", missing: "nan", description: "Bybit BTC perpetual volume" });
output("agg_volume", histogram, lower, { color: "#2563eb", description: "Volume summed over the venues that reported" });
output("volume_ema", line, lower, { color: "#94a3b8", width: 2, description: "Smoothed aggregated volume" });
output("binance_share", line, lower, { color: "#f59e0b", unit: "%", description: "Binance share of the two venues' volume" });
let ema = new Ema(21);
// A venue with no candle on this bar reads NaN (missing: "nan") and contributes nothing.
function reported(value: f64): f64 {
return isNaN(value) ? 0.0 : value;
}
function onStart(): void {
ema = new Ema(i32(p_smooth()));
}
function onBar(): void {
const binance = in_vol_binance();
const bybit = in_vol_bybit();
if (isNaN(binance) && isNaN(bybit)) return;
const agg = reported(binance) + reported(bybit);
const smoothed = ema.update(agg);
// Venue dominance: NaN rather than a fake 100% when only one venue reported.
const share = isNaN(binance) || isNaN(bybit) || agg <= 0.0 ? NaN : (100.0 * binance) / agg;
out_agg_volume(agg);
out_volume_ema(smoothed);
out_binance_share(share);
}Venue dominance is the last output: one venue's share of the
aggregate, NaN rather than a fake 100% when only one venue reported.
The same shape covers a spread (subtract instead of add) or a volume
weighted price (sum close * volume and divide by the summed volume): one
pinned input per venue, one term per input, and a missing policy that
says what a silent venue means. Check that the venues count volume in the
same unit before you add them. Package the helpers as a class and the
whole family is one paste.
Flow and derivative feeds (side-split trades, open interest, funding, liquidations) always read the chart's own market, so a cross-venue sum of them is not available on the chart; accumulate the chart's own market instead.
Higher-timeframe views of aggregates
A fold composes with any input: bucket the aggregated series by
time.bar_open_sec and fold it on the next bucket, and the 4h view of a
cross-venue sum is confirmed and repaint-free exactly like a single-source
one (Multi-timeframe).
Lower-timeframe data and raw bars
The intrabar celled source delivers the finer bars inside each chart bar
as cells, for the chart's own market
(Multi-timeframe). There is no raw-bar request that
returns the last N native bars of another market as an array for drawing.
The celled sources slice the same bar by price (volume_profile), by level
(book), or by time (intrabar), and a drawing's coordinates come from
values the module computes on the chart's rows
(Drawing objects). To mark the last
twenty daily highs, keep them in a ring buffer from a daily fold or a 1d
pin and draw them as segments.
Sparse flow sources align to the grid
Some market-flow feeds are naturally sparse: a liquidation, a funding
print, or a side-split trade bucket may simply have no event for a candle.
The missing policy is how you say what that means, per input:
| Policy | A bar with no observation reads | Use it for |
|---|---|---|
"carry" (default) | the latest value, carried forward | levels and states: open interest, a coarse close, a funding rate between prints |
"nan" | NaN | "did this venue report?" logic: a NaN counts as not live and never fires a comparison |
"zero" | 0 | flows to be summed: liquidation volume, side-split volume, where nothing happened means zero |
The rows stay the chart's candles whatever the policy, so a sparse series
such as liquidations reads its fill on the bars where nothing happened.
The practical rule: keep an ohlcv spine as the
first input in a multi-source flow package, then read the flow sources
beside it. The candle grid stays stable, sparse "nothing happened" bars
become 0 or NaN as you choose, and truly unavailable history stays
distinguishable from zero activity.
Budgets and good citizenship
On the chart a
wrun indicator counts against your plan's per-chart indicator limit by the
number of distinct sources it reads (a source name plus its pinned
market), and the practical cost of each one is fetch time over the loaded
window (Data sources). Declare the inputs the
computation needs, pin symbol and exchange together, keep the chart's
own candles first, and put the sparse feeds on the policy that matches
their meaning.
Availability
Whether the chart can serve a feed on the market in front of you is answered by name when you press Run, never with silent empty data (Data sources).
Next
Multi-timeframe
A pin at a coarser interval, read as of each bar's close (Multi-timeframe)
Data sources
Every feed, its fields and its options (Data sources)
Exchange and symbol format
The venue ids and symbol spellings a pin takes (Exchange and symbol format)
Alerts
Which pinned markets an alert can evaluate (Alerts)