# Source History (/scripting/history)



The third `onData` argument is the only market-record access API. Its list and index behavior is the same in live runs and backtests, but each source must support the selected mode. The `trades` source is live-only.

```js
export function onData(ctx, input, history) {
  const candles = history.source('btc@candles@binancef@mmt')
  const current = history.source('btc@candles@binancef@mmt', 0)
  const previous = history.source('btc@candles@binancef@mmt', 1)
}
```

## `history.source` [#historysource]

```ts
history.source(selector: string): SourceRecord[]
history.source(selector: string, index: number): SourceRecord | undefined
```

* `selector` must exactly match a configured source, such as `btc@candles@binancef@mmt` or `btc@orderbook@bulkf`.
* Without an index, it returns the retained list ordered from oldest to newest. A source with no data returns `[]`.
* With an index, `0` is the most recent record, `1` is the previous record, and so on.
* A missing selector record or out-of-range index returns `undefined`.
* Lists and records are deeply frozen and cannot be mutated.

The two forms are intentionally complementary:

```js
const candles = history.source('btc@candles@binancef@mmt')
const newestFromList = candles.at(-1)
const newestByIndex = history.source('btc@candles@binancef@mmt', 0)
```

## Retention [#retention]

The manifest `lookback` is the maximum number of records Market Lab can hold at one time for every exact selector:

```js
export const script = {
  name: 'momentum-with-book',
  version: '1',
  sources: ['candles', 'orderbook'],
  lookback: 100,
  params: {},
}
```

Live history retains at least two records so current and previous state can be read. The maximum lookback is 5,000.

It does not mean “load 100 records before execution.” A live session begins without history and fills each selector's buffer as new records arrive. A backtest begins at the requested `from` time and fills the same buffer event by event.

For example, after three completed candles with `lookback: 3`:

```js
const candles = history.source('btc@candles@binancef@mmt')
// [oldest, middle, newest]

history.source('btc@candles@binancef@mmt', 0) // newest
history.source('btc@candles@binancef@mmt', 1) // middle
history.source('btc@candles@binancef@mmt', 2) // oldest
```

When a fourth candle arrives, the oldest is discarded and the returned list remains three records long.

### Choosing a value [#choosing-a-value]

Choose the smallest limit that satisfies the calculation:

| Script usage                                        | Useful `lookback` |
| --------------------------------------------------- | ----------------- |
| Current and previous raw record                     | `2`               |
| Latest SMA with `window: 20`                        | `20`              |
| Latest and previous SMA with `window: 20`           | `21`              |
| Compare current value with the value 99 records ago | `100`             |

Example for an SMA crossover:

```js
export const script = {
  name: 'sma-cross',
  version: '1',
  sources: ['candles'],
  lookback: 9,
  params: {},
}

export function onData(ctx, input, history) {
  const candles = history.source('btc@candles@binancef@mmt')
  if (candles.length < 9) return

  const fast = ctx.study.sma(candles, { window: 3 })
  const slow = ctx.study.sma(candles, { window: 8 })

  const crossedUp = fast.previous <= slow.previous && fast.latest > slow.latest
}
```

The largest window is eight, and crossover detection reads its previous value, so the script retains `8 + 1 = 9` candles.

If `lookback` is omitted, Market Lab currently retains up to 5,000 records per selector. That is convenient but can waste memory and make whole-list calls more expensive, particularly for deep orderbooks.

Backtests merge every configured source into a timestamp-ordered event sequence. Each record is appended immediately before its `onData` call, so a script never sees a future record. When multiple records share a timestamp, configured source order breaks the tie. The same `lookback` bound applies to every selector.

## Multiple Sources and Exchanges [#multiple-sources-and-exchanges]

Every selector has independent history:

```js
const binanceCandles = history.source('btc@candles@binancef@mmt')
const hyperliquidCandles = history.source('btc@candles@hyperliquidf@mmt')
const bulkBook = history.source('btc@orderbook@bulkf', 0)
const bulkTrades = history.source('btc@trades@bulkf')
```

When one source triggers `onData`, the latest retained records from all other selectors remain available. `input.source` identifies which selector caused the current call in both live execution and backtesting.

```js
export function onData(ctx, input, history) {
  const candle = history.source('btc@candles@binancef@mmt', 0)
  const book = history.source('btc@orderbook@bulkf', 0)

  if (!candle || !book) return
  if (input.source !== 'btc@candles@binancef@mmt') return

  // Evaluate once per completed Binance candle using the latest BULK book.
}
```

## Trade History in Live Mode [#trade-history-in-live-mode]

Trade sources use the same exact-selector history API:

```js
export function onData(ctx, input, history) {
  const trades = history.source('btc@trades@bulkf')
  const latest = history.source('btc@trades@bulkf', 0)

  if (!latest) return
}
```

The list is ordered from oldest to newest, and index `0` is the newest trade. Each record contains only:

```js
{
  price: 1927.5,
  size: 0.25,
}
```

A live script receives one `onData` call for every trade. Trade timestamps and aggressor side remain internal. The `trades` source cannot be used in a backtest; use historical candles instead.

See [Trades Source](/scripting/sources/trades) for selectors and provider behavior.

## Candle History in Live Mode [#candle-history-in-live-mode]

Live candles are completed bars derived from raw trades. A candle is appended once, after its interval closes. Market Lab does not place an in-progress partial candle at index `0`.

That means:

* index `0` is the most recently completed candle;
* index `1` is the completed candle before it;
* the startup partial interval is never inserted;
* the first candle history entry appears only after one clean full interval has completed.

See [Candles Source](/scripting/sources/candles#startup-alignment) for exact boundary examples.

## Other Source Histories [#other-source-histories]

Orderbook snapshots and event-style records advance history whenever an accepted update arrives. Provider candle-like OI, VD, and volume-profile updates with the same timestamp replace the newest record instead of shifting the list, keeping index `1` pointed at the previous period.
