SeekAlgo/Docs/Multi-Symbol Strategies
Docs/strategies/Multi-Symbol Strategies

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".

  1. Scan every symbol in the declared order.
  2. If a position is already open, manage its exit first.
  3. If no position is open, let the first qualifying symbol take the trade.
  4. 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_leg or eth_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 $symbol into new basket scripts
  • opening basket trades without a symbol field
  • 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