MarketLab Docs
Scripting

Script API Reference

Reference for the script manifest, hooks, input, history, studies, and execution functions.

Script API Reference

This page explains the complete JavaScript surface in the order a script uses it: manifest, hooks, runtime arguments, history, studies, and execution.

Complete Shape

export const script = {
  name: 'sma-cross',
  version: '1',
  description: 'Trade an SMA crossover with spread protection',
  sources: ['candles', 'orderbook'],
  lookback: 9,
  params: {
    margin: { type: 'number', required: false, default: 100 },
    leverage: { type: 'number', required: false, default: 5 },
    max_spread_bps: { type: 'number', required: true },
  },
}

export function onData(ctx, input, history) {
  // Called for every accepted source event.
}

export function onExecution(ctx, event) {
  // Optional: called for live execution lifecycle events.
}

script Manifest

The exported script object is inspected before Market Lab connects to a source or runs a hook.

FieldRequiredMeaning
nameYesHuman-readable script name
versionYesScripting contract version; currently '1'
sourcesYesBase source kinds the script requires
descriptionNoHuman-readable explanation
lookbackNoMaximum retained records per exact source selector
paramsNoFlat runtime parameter definitions

name

name: 'funding-arbitrage'

The name appears in job and backtest output. It does not select a strategy or file.

version

version: '1'

This identifies the JavaScript contract understood by the runtime. It is not the release version of your trading logic.

sources

sources: ['candles', 'orderbook', 'oi']

The manifest declares base data kinds, not exchanges or providers. Runtime flags choose the concrete streams:

--source candles@binancef@mmt:timeframe=60
--source orderbook@bulk:depth=20
--source oi@hyperliquid@mmt:timeframe=30

If the manifest requires candles, at least one configured candle selector must be present. Multiple exchanges can satisfy the same base kind.

description

description: 'Opens with an SMA crossover and checks BULK spread before trading'

This is metadata only. It does not affect execution.

lookback

lookback: 9

lookback limits how many records are retained at one time for every exact selector. With one candle source and one orderbook source, lookback: 9 permits nine candles and nine orderbook snapshots.

It does not preload records when a live script starts. History begins empty and grows as data arrives:

const candles = history.source('candles@binancef@mmt')
if (candles.length < 9) return // warm-up is not complete

An SMA with window: 8 requires eight candles for latest. Reading both latest and previous requires nine:

const slow = ctx.study.sma(candles, { window: 8 })
// slow.latest   uses the newest 8 records
// slow.previous needs one additional older record

The effective minimum is two records, the maximum is 5,000, and omitting the field currently defaults to 5,000. See Source History for more examples.

params

params: {
  margin: { type: 'number', required: false, default: 100 },
  armed: { type: 'boolean', required: false, default: false },
  market: { type: 'string', required: true },
}

Supported types are number, boolean, and string. A required parameter cannot also have a default.

Pass values through CLI flags:

--param margin=250 --param armed=true --param market=btc

Read them through ctx.params:

ctx.params.margin
ctx.params.armed
ctx.params.market

Unknown parameters, missing required values, and invalid types are rejected before onData runs.

onData(ctx, input, history)

export function onData(ctx, input, history) {
  // strategy logic
}

Market Lab invokes onData once for every accepted source event. With candle, orderbook, and OI sources, any of those streams can trigger the function.

Filter by the selector that should drive a calculation:

export function onData(ctx, input, history) {
  if (input.source !== 'candles@binancef@mmt') return

  const candle = history.source(input.source, 0)
  // Runs once for each accepted Binance candle event.
}

The history for the triggering selector is updated before the hook runs. Other selectors retain their latest known records.

input

input describes the event and runtime state. Market records themselves are read through history.

input.source // 'candles@binancef@mmt'
input.source_type // 'candles'
input.provider // 'mmt'
input.exchange // 'binancef'
input.symbol // 'BTC/USDT'
input.source_configs
input.positions.open

input.source_configs

Configuration is keyed by exact selector:

input.source_configs['candles@binancef@mmt'].timeframe_sec
input.source_configs['orderbook@bulk'].depth
input.source_configs['vd@hyperliquid@mmt'].bucket

input.positions.open

Live execution and backtests expose the current position for the script symbol:

const open = input.positions.open[0]

if (open?.side === 'long') {
  ctx.trade({
    key: `close-${input.source}-${open.id}`,
    position: 'close-long',
  })
}

Analysis-only jobs return an empty list. See Position Data for the full shape.

history.source

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

Without an index, the function returns the retained list from oldest to newest:

const candles = history.source('candles@binancef@mmt')
const newest = candles.at(-1)

With an index, it reads backward from the newest record:

const current = history.source('candles@binancef@mmt', 0)
const previous = history.source('candles@binancef@mmt', 1)
const tenRecordsAgo = history.source('candles@binancef@mmt', 9)

An unknown selector returns [] in list form and undefined in indexed form. Returned records are frozen.

ctx.params

ctx.params contains the resolved manifest parameters:

const exposure = ctx.params.margin * ctx.params.leverage

Defaults have already been applied before the hook begins.

ctx.study

Study helpers execute the same Rust calculations used by the CLI.

const candles = history.source('candles@binancef@mmt')
const fast = ctx.study.sma(candles, { window: 3 })
const slow = ctx.study.ema(candles, { field: 'c', window: 8 })

const book = history.source('orderbook@bulk', 0)
const spread = ctx.study.spread(book)

Available groups include:

  • candle studies: sma, ema
  • volume delta: cvd
  • orderbook studies: spread, depth, imbalance, slippage, vamp

See Built-In Functions for arguments, return values, and examples for every study.

ctx.trade(request)

ctx.trade submits a simulated order in backtests or a live order when the job is deployed with --venue bulk.

Open a long using $100 margin and 5x leverage:

const order = ctx.trade({
  key: `open-${candle.t}`,
  position: 'open-long',
  margin: 100,
  leverage: 5,
  order: { type: 'market' },
})

Close the full long position:

ctx.trade({
  key: `close-${candle.t}`,
  position: 'close-long',
})

Close only part of it:

ctx.trade({
  key: `partial-close-${candle.t}`,
  position: 'close-long',
  size: 0.001,
})

Position operations are open-long, open-short, close-long, and close-short. Closing operations are reduce-only automatically.

The call returns a stable local reference immediately:

order.id
order.key

See Script Execution for all request fields and validation rules.

ctx.cancel(request)

Cancel a managed limit order using its returned ID:

const order = ctx.trade({
  key: `bid-${candle.t}`,
  position: 'open-long',
  size: 0.001,
  order: { type: 'limit', price: 63000, tif: 'alo' },
})

ctx.cancel({
  key: `cancel-${candle.t}`,
  order: order.id,
})

The cancellation key is also idempotent.

onExecution(ctx, event)

onExecution is optional and live-only. It receives asynchronous order, fill, position, and account lifecycle events.

export function onExecution(ctx, event) {
  if (event.type === 'order.rejected') {
    return {
      metrics: {
        rejected_order: event.orderId,
        reason: event.data,
      },
    }
  }
}

It uses the same persistent QuickJS session as onData, and it may call ctx.trade or ctx.cancel. Backtests apply fills directly and expose the resulting state through input.positions.open.

Hook Return Values

Hooks do not need to return anything. Trading occurs only through ctx.trade and ctx.cancel.

Return diagnostics when they are useful:

return {
  metrics: {
    fast: fast.latest,
    slow: slow.latest,
  },
  meta: {
    regime: fast.latest > slow.latest ? 'bullish' : 'bearish',
  },
}

Only metrics and meta are accepted, and both must be objects. Returned signal or intent values are not execution instructions.

On this page