MarketLab Docs
Scripting

Study Scripting

Write custom Market Lab studies in JavaScript.

Study Scripting

A study script measures market data and returns metrics.

Studies should not return buy or sell decisions. Decisions belong in strategies.

Run a Study Script

mlab study run ./studies/buy-pressure.js \
  --provider mmt \
  --exchange bybitf \
  --symbol BTC/USDT \
  --timeframe 60 \
  --from 1704067200000 \
  --to 1704067800000 \
  --input min_vbuy=50000 \
  --output json

Runtime flags such as --provider, --exchange, --symbol, --timeframe, --from, --to, and --stream are owned by Market Lab.

Study-specific values are passed with repeated --input key=value flags.

Manifest

Every study script exports a study manifest:

export const study = {
  name: "buy-pressure-filter",
  version: "1",
  source: "candles",
  modes: ["window"],
  inputs: {
    min_vbuy: {
      type: "number",
      required: true,
      description: "Ignore candles where buy volume is below this value"
    },
    min_delta: {
      type: "number",
      required: false,
      default: 0
    }
  }
}

Required manifest fields:

  • name
  • version
  • source
  • modes

Input types:

  • string
  • number
  • boolean

Input names should use snake_case.

Input names cannot reuse Market Lab runtime flag names. Reserved names include:

  • provider
  • exchange
  • symbol
  • source
  • timeframe
  • from
  • to
  • stream
  • depth
  • bucket
  • output
  • verbose

Data Hook

Study scripts export onData.

study.source determines which market payload Market Lab passes into input.

export function onData(ctx, input) {
  const candles = input.candles

  return {
    metrics: {
      points: candles.length,
      latest_close: candles[candles.length - 1].c
    }
  }
}

Window payload:

{
  mode: "window",
  candles: [...]
}

Stream payload:

{
  mode: "stream",
  candle: {...}
}

Use input.mode when one script supports both modes.

Context

The first hook argument is ctx.

ctx.inputs.min_vbuy
ctx.inputs.min_delta

ctx.inputs contains validated values from the script manifest and CLI --input flags.

Unknown inputs, missing required inputs, and invalid input types are rejected before the hook runs.

See Data Types for shared script types and Candles for candle field shapes.

Return Value

Return a JSON-serializable object with metrics.

return {
  metrics: {
    qualifying_candles: 8,
    avg_delta: 120400.5
  }
}

meta is optional:

return {
  metrics: {
    qualifying_candles: 8
  },
  meta: {
    note: "filtered by minimum buy volume"
  }
}

Do not return the full Market Lab envelope from your script. Market Lab wraps the result.

Full Example

export const study = {
  name: "buy-pressure-filter",
  version: "1",
  source: "candles",
  modes: ["window"],
  inputs: {
    min_vbuy: { type: "number", required: true },
    min_delta: { type: "number", required: false, default: 0 }
  }
}

export function onData(ctx, input) {
  if (input.mode !== "window") {
    return null
  }

  const filtered = input.candles.filter((c) => {
    return c.vb >= ctx.inputs.min_vbuy && c.vb - c.vs >= ctx.inputs.min_delta
  })

  const avgDelta =
    filtered.length === 0
      ? 0
      : filtered.reduce((sum, c) => sum + (c.vb - c.vs), 0) / filtered.length

  return {
    metrics: {
      qualifying_candles: filtered.length,
      avg_delta: avgDelta,
      latest_close: input.candles[input.candles.length - 1].c
    }
  }
}

Run it:

mlab study run ./studies/buy-pressure.js \
  --provider mmt \
  --exchange bybitf \
  --symbol BTC/USDT \
  --timeframe 60 \
  --from 1704067200000 \
  --to 1704067800000 \
  --input min_vbuy=50000 \
  --input min_delta=0 \
  --output json

Output

Market Lab wraps the script result in the standard study envelope.

{
  "type": "study.buy-pressure-filter.result",
  "version": "1",
  "provider": "mmt",
  "exchange": "bybitf",
  "symbol": "BTC/USDT",
  "ts_ms": 1704067800000,
  "stream": false,
  "study": {
    "name": "buy-pressure-filter",
    "kind": "window",
    "source": "custom"
  },
  "inputs": {
    "min_vbuy": 50000,
    "min_delta": 0
  },
  "metrics": {
    "qualifying_candles": 4,
    "avg_delta": 120500.25,
    "latest_close": 42100
  }
}

Validation

Market Lab validates:

  • the script can be loaded
  • study is exported
  • manifest fields are valid
  • onData(ctx, input) exists
  • declared inputs are known and typed
  • the hook returns JSON-serializable metrics

On this page