Source History
Read retained source records in live Market Lab scripts and backtests.
Source History
The third onData argument is the only market-record access API. It works the same way in live runs and backtests.
export function onData(ctx, input, history) {
const candles = history.source('candles@binancef@mmt')
const current = history.source('candles@binancef@mmt', 0)
const previous = history.source('candles@binancef@mmt', 1)
}history.source
history.source(selector: string): SourceRecord[]
history.source(selector: string, index: number): SourceRecord | undefinedselectormust exactly match a configured source, such ascandles@binancef@mmtororderbook@bulk.- Without an index, it returns the retained list ordered from oldest to newest. A source with no data returns
[]. - With an index,
0is the most recent record,1is 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:
const candles = history.source('candles@binancef@mmt')
const newestFromList = candles.at(-1)
const newestByIndex = history.source('candles@binancef@mmt', 0)Retention
The manifest lookback is the maximum number of records Market Lab can hold at one time for every exact selector:
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:
const candles = history.source('candles@binancef@mmt')
// [oldest, middle, newest]
history.source('candles@binancef@mmt', 0) // newest
history.source('candles@binancef@mmt', 1) // middle
history.source('candles@binancef@mmt', 2) // oldestWhen a fourth candle arrives, the oldest is discarded and the returned list remains three records long.
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:
export const script = {
name: 'sma-cross',
version: '1',
sources: ['candles'],
lookback: 9,
params: {},
}
export function onData(ctx, input, history) {
const candles = history.source('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
Every selector has independent history:
const binanceCandles = history.source('candles@binancef@mmt')
const hyperliquidCandles = history.source('candles@hyperliquid@mmt')
const bulkBook = history.source('orderbook@bulk', 0)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.
export function onData(ctx, input, history) {
const candle = history.source('candles@binancef@mmt', 0)
const book = history.source('orderbook@bulk', 0)
if (!candle || !book) return
if (input.source !== 'candles@binancef@mmt') return
// Evaluate once per completed Binance candle using the latest BULK book.
}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
0is the most recently completed candle; - index
1is 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 for exact boundary examples.
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.