Multi-Symbol Strategies
Public guide to building basket strategies with `symbols()` and `symbol.*`.
Multi-Symbol Strategies
Use a multi-symbol strategy when one script needs to read a fixed basket of markets and decide which leg to trade.
This is the public authoring guide. If you need engine details, read Multi-Symbol Backtesting after this page.
When To Use A Basket Strategy
- scan several markets with the same signal
- rotate into the first valid setup
- compare relative strength across a fixed list
- keep one shared portfolio instead of isolated per-symbol backtests
Canonical Script Shape
script({
type: 'strategy',
priceStudy: true,
initialCapital: 100000,
orderSize: 1,
orderType: 'con',
commission: 0,
commissionType: 'non',
slippage: 0,
marketType: 'crypto',
resolution: '15',
tradeBelowCash: false,
});
symbols([
'BTCUSDT:BINANCE:FUTURE:DEFAULT',
'ETHUSDT:BINANCE:FUTURE:DEFAULT',
'SOLUSDT:BINANCE:FUTURE:DEFAULT',
]);
How Basket Data Works
The symbols([...]) declaration fixes the basket universe and the index order.
const btcNow = symbol.close[0].get(0);
const btcPrev = symbol.close[0].get(1);
const ethNow = symbol.close[1].get(0);
You can also pass basket series directly into TA functions:
const btcFast = ta.ema(symbol.close[0], 8);
const btcSlow = ta.ema(symbol.close[0], 21);
ta.* calls return the current numeric indicator value. If the strategy needs history for a derived indicator, such as a crossover between two MAs, wrap each derived value with series(...) in a stable order before branching:
const btcFastSeries = series(ta.sma(symbol.close[0], fastLen));
const btcSlowSeries = series(ta.sma(symbol.close[0], slowLen));
const btcCrossedUp =
btcFastSeries.get(0) > btcSlowSeries.get(0)
&& btcFastSeries.get(1) <= btcSlowSeries.get(1);
Prefer the built-in TA library for standard indicators on basket data. Only write custom indicator helpers for calculations that are not already covered by ta.*.
How Orders Work
Basket orders should include both a stable trade id and the target symbol.
If qty is omitted, orderSize applies per leg. If qty is present, that explicit value overrides the basket default for that entry.
strategy.entry({
id: 'btc_leg',
type: 'long',
symbol: 'BTCUSDT:BINANCE:FUTURE:DEFAULT',
});
strategy.entry({
id: 'eth_leg',
type: 'long',
qty: 3,
symbol: 'ETHUSDT:BINANCE:FUTURE:DEFAULT',
});
strategy.exit({
id: 'btc_leg',
symbol: 'BTCUSDT:BINANCE:FUTURE:DEFAULT',
});
strategy.default_qty_value works the same way in basket mode, so scripts can read the header default before deciding whether to override it.
First-Signal Pattern
One common public pattern is "first signal wins".
- Scan every symbol in the declared order.
- If a position is already open, manage its exit first.
- If no position is open, let the first qualifying symbol take the trade.
- Keep the basket on one shared cash pool.
This is the pattern used by the built-in first-signal sample.
Good Practices
- keep the basket explicit with
symbols([...]) - keep symbol order stable after you map indexes to trade ids
- use readable ids such as
btc_legoreth_rotation - isolate one basket rule before adding ranking or rotation logic
- keep the header settings realistic for portfolio sizing and costs
What To Avoid
- mixing
$script,$symbols,$input, or$symbolinto new basket scripts - opening basket trades without a
symbolfield - changing the meaning of index
0,1,2, and so on after the script is written - treating basket strategies as separate single-symbol backtests
Public Learning Path
- start with Platform Settings
- then read Strategy Authoring
- use Scripting Reference for the surface area
- use Multi-Symbol Backtesting for the technical execution model