# Script API Reference (/scripting/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 [#complete-shape]

```js
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 or simulated execution lifecycle events.
}
```

## `script` Manifest [#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]

```js
name: 'funding-arbitrage'
```

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

### `version` [#version]

```js
version: '1'
```

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

### `sources` [#sources]

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

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

```bash
--source btc@candles@binancef@mmt:timeframe=60
--source btc@orderbook@bulkf:depth=20
--source btc@orderbook@hyperliquidf:depth=20
--source btc@oi@hyperliquidf@mmt:timeframe=30
--source btc@trades@bulkf
```

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

### `description` [#description]

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

This is metadata only. It does not affect execution.

### `lookback` [#lookback]

```js
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:

```js
const candles = history.source('btc@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:

```js
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](/scripting/history#retention) for more examples.

### `params` [#params]

```js
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:

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

Read them through `ctx.params`:

```js
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)` [#ondatactx-input-history]

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

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

Filter by the selector that should drive a calculation:

```js
export function onData(ctx, input, history) {
  if (input.source !== 'btc@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]

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

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

### `input.source_configs` [#inputsource_configs]

Configuration is keyed by exact selector:

```js
input.source_configs['btc@candles@binancef@mmt'].timeframe_sec
input.source_configs['btc@orderbook@bulkf'].depth
input.source_configs['btc@vd@hyperliquidf@mmt'].bucket
```

### `input.positions.open` [#inputpositionsopen]

Live execution and backtests expose current positions for the script's declared symbols:

```js
const open = input.positions.open.find((position) => position.symbol === 'BTC')

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

Analysis-only jobs return an empty list. See [Position Data](/scripting/data-types#positions) for the full shape.

## `history.source` [#historysource]

```ts
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:

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

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

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

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

Live trade sources use the same API with their exact selector:

```js
const trades = history.source('btc@trades@bulkf')
const latest = history.source('btc@trades@bulkf', 0)
```

Each trade contains only `price` and `size`. Trade sources are live-only and are rejected by `script backtest`.

## `ctx.params` [#ctxparams]

`ctx.params` contains the resolved manifest parameters:

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

Defaults have already been applied before the hook begins.

## `ctx.study` [#ctxstudy]

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

```js
const candles = history.source('btc@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('btc@orderbook@bulkf', 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](/scripting/built-ins) for arguments, return values, and examples for every study.

## `ctx.trade(request)` [#ctxtraderequest]

`ctx.trade` expresses perpetual position changes: open long, open short, close long, or close short. It submits a simulated order in backtests or a live order when the job is deployed with `--venue bulkf`, `--venue hyperliquidf`, `--venue hyperliquidf-{dex}`, or `--venue hyperlinkf`. Every request requires a base-only futures `symbol` declared by one of the script's sources. Hyperliquid venues use mainnet by default and testnet when the script command includes `--testnet`; HyperLink is mainnet-only.

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

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

Close the full long position:

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

Close only part of it:

```js
ctx.trade({
  symbol: 'btc',
  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:

```js
order.id
order.key
```

See [Script Execution](/scripting/execution#ctxtrade) for all request fields and validation rules.

## `ctx.order(request)` [#ctxorderrequest]

`ctx.order` places a raw buy or sell for its required `symbol`. The symbol must be declared by one of the script's sources. Use it for Hyperliquid Spot, or when a perpetual order should follow the venue's netting rules instead of declaring an `open-*` or `close-*` transition:

```js
const ask = ctx.order({
  symbol: 'btc',
  key: `ask-${candle.t}`,
  side: 'sell',
  size: 0.001,
  leverage: 5,
  order: { type: 'limit', price: 65000, tif: 'alo' },
})
```

Canonical sides are `buy` and `sell`; `long` aliases `buy` and `short` aliases `sell`. A non-reduce-only order may increase, reduce, close, or flip the net position according to inventory when it fills. Set `reduceOnly: true` when it may reduce to flat but must never increase or flip inventory.

For Hyperliquid Spot, declare an exact pair source such as `hype/usdc@candles@hyperliquid`, deploy with `--venue hyperliquid`, and use the same exact pair in the request:

```js
const buy = ctx.order({
  symbol: 'hype/usdc',
  key: `spot-buy-${candle.t}`,
  side: 'buy',
  margin: 100,
  order: { type: 'limit', price: 45, tif: 'alo' },
})
```

Spot orders do not accept `leverage`, `reduceOnly`, `sl`, or `tp`. For a spot buy, `margin` is the quote-asset budget. A spot sell normally uses `size` in the base asset.

Perpetual raw orders accept exactly one of `size` or `margin`, optional `leverage`, and the same market/limit order object used by `ctx.trade`. They do not accept `sl` or `tp`. The return value is the same stable `{ id, key }` reference.

See [Script Execution](/scripting/execution#ctxorder) for the complete contract and netting examples.

## `ctx.cancel(request)` [#ctxcancelrequest]

Cancel a managed limit order using its returned ID:

```js
const order = ctx.trade({
  symbol: 'btc',
  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)` [#onexecutionctx-event]

`onExecution` is optional. It receives asynchronous venue lifecycle events in live jobs and equivalent simulated order lifecycle events in backtests.

```js
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`, `ctx.order`, or `ctx.cancel`. Backtests apply fills using one-way netting and expose the resulting state through `input.positions.open`.

## Hook Return Values [#hook-return-values]

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

Return diagnostics when they are useful:

```js
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.
