Multi-Symbol Backtesting
Technical execution notes for the basket backtest engine and portfolio-level multi-symbol behavior.
Multi-Symbol Backtesting
This page is the technical companion to Multi-Symbol Strategies.
Read that guide first if your goal is to write or configure a basket strategy. Read this page when you want the execution model behind it.
Multi-symbol backtesting is portfolio-first.
- one backtest shares one cash pool across the whole basket
- each leg is identified by symbol and trade id
- the basket universe is explicit and fixed in the script
- the modern syntax uses
symbols([...])andsymbol.*
Canonical Script Shape
script({
type: 'strategy',
priceStudy: true,
initialCapital: 100000,
orderSize: 50,
orderType: 'poe',
commission: 0.05,
commissionType: 'per',
slippage: 0,
marketType: 'crypto',
resolution: '15',
tradeBelowCash: false,
});
symbols([
"BTCUSDT:BINANCE:FUTURE:DEFAULT",
"ETHUSDT:BINANCE:FUTURE:DEFAULT",
"SOLUSDT:BINANCE:FUTURE:DEFAULT",
"BNBUSDT:BINANCE:FUTURE:DEFAULT"
]);
const fastLen = input.integer("Fast EMA", 8);
const slowLen = input.integer("Slow EMA", 21);
const qty = input.integer("Qty", 1);
If a basket entry omits qty, the engine applies orderSize per leg using orderType. If a basket entry provides qty, that explicit value overrides the header default for that leg.
How Basket Data Is Read
The symbol namespace is the basket data surface.
const btcClose = symbol.close[0].get(0);
const btcPrev = symbol.close[0].get(1);
const ethClose = symbol.close[1].get(0);
The index order matches the order in symbols([...]).
Trading Pattern
The current basket samples use a fixed symbol list plus stable trade ids.
const SPECS = [
"BTCUSDT:BINANCE:FUTURE:DEFAULT",
"ETHUSDT:BINANCE:FUTURE:DEFAULT",
"SOLUSDT:BINANCE:FUTURE:DEFAULT",
"BNBUSDT:BINANCE:FUTURE:DEFAULT"
];
const IDS = ["btc_ema", "eth_ema", "sol_ema", "bnb_ema"];
Each leg can use the same rule set:
- read the symbol series at
symbol.close[index] - detect the signal
- check
strategy.param_by_id(id, "position") - send
strategy.entry({ id, type, symbol })for the default size - send
strategy.entry({ id, type, qty, symbol })for an explicit override - send
strategy.exit({ id, symbol })
You can also read strategy.default_qty_value and strategy.default_qty_type inside basket scripts when you want the rule to depend on the configured defaults.
Single Position Across The Basket
The current first-signal sample keeps only one open trade at a time.
- if a leg is already open, the script looks for its exit condition
- if no leg is open, the first qualifying signal gets the entry
- this concentrates capital on the first valid setup
What To Avoid
- do not mix the old
$script,$symbols,$input, and$symbolforms into new scripts - do not use an implicit basket universe
- do not change the symbol order after you start mapping trade ids to indexes
Current Compatibility Note
Older basket scripts may still run through compatibility layers.
New scripts should use the modern syntax only:
script(...)symbols([...])input.*(...)symbol.*[index]
What The Multi-Symbol UI Shows
- a portfolio timeline
- symbol-level trade drilldown
- aggregate performance
- symbol attribution and exposure
Good Basket Strategy Candidates
- crossovers across a fixed universe
- trend strength ranking
- breakout selection
- first-signal wins logic
- one-position portfolio rotation