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.
| Field | Required | Meaning |
|---|---|---|
name | Yes | Human-readable script name |
version | Yes | Scripting contract version; currently '1' |
sources | Yes | Base source kinds the script requires |
description | No | Human-readable explanation |
lookback | No | Maximum retained records per exact source selector |
params | No | Flat 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=30If 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: 9lookback 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 completeAn 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 recordThe 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=btcRead them through ctx.params:
ctx.params.margin
ctx.params.armed
ctx.params.marketUnknown 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.openinput.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'].bucketinput.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 | undefinedWithout 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.leverageDefaults 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.keySee 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.