Market LabDocs
Scripting V2 - PythonPlain Python Files

Execution

Backtest and route Python V2 trades, raw orders, cancellations, and execution events.

Python V2 calls Market Lab's existing execution API with ordinary dictionaries:

on_data / on_execution -> ctx.trade / ctx.order / ctx.cancel -> simulator or mlabd

Live jobs connect their BULK, Hyperliquid, and HyperLink execution WebSockets before market-data processing begins.

Each trade or order names its execution exchange. The script does not inspect ctx.mode or branch between separate backtest and live APIs.

CommandWhat the request's exchange selects
script backtestThe historical simulator and matching exchange-specific price source
script runThe live execution exchange

Python V2 rejects --venue. Use one of the supported execution names in every ctx.trade(...) or ctx.order(...) request:

  • bulkf
  • hyperliquidf
  • hyperlinkf
  • hyperliquid
  • hyperlink

Core and HIP-3 perpetual execution both use hyperliquidf. A HIP-3 request puts the DEX in its symbol, such as xyz:TSLA. BULK and Hyperliquid use mainnet by default. The job-wide --testnet flag moves supported sources and execution requests to testnet. HyperLink uses hyperlink for Spot and hyperlinkf for core or HIP-3 perpetuals. Both are mainnet-only and cannot be used in a --testnet job.

Market data and execution are independent. A script may read a Binance source and execute on Hyperliquid, or read one symbol as a signal and trade another declared symbol. One Python job may also execute on multiple exchanges.

HyperLink execution uses ordinary Hyperliquid public market data. Declare the matching hyperliquidf source for a core or HIP-3 perpetual, then select hyperlinkf in the request:

price = history.source("btc@trades@hyperliquidf", 0)

ctx.trade(
    {
        "exchange": "hyperlinkf",
        "symbol": "BTC",
        "position": "open-long",
        "margin": 100,
        "leverage": 5,
    }
)

HIP-3 keeps the same DEX-scoped symbol on both sides:

history.source("xyz:tsla@candles@hyperliquidf:timeframe=60")

ctx.trade(
    {
        "exchange": "hyperlinkf",
        "symbol": "xyz:TSLA",
        "position": "open-long",
        "margin": 100,
        "leverage": 5,
    }
)

ctx.trade(request)

Use ctx.trade for an intentional perpetual position transition:

order = ctx.trade(
    {
        "exchange": "hyperliquidf",
        "symbol": "BTC",
        "position": "open-long",
        "margin": 100,
        "leverage": 5,
        "order": {"type": "limit", "price": 65000, "tif": "alo"},
        "sl": 63000,
        "tp": 69000,
    }
)

The call validates synchronously and returns a stable local reference:

{"id": "ord_..."}

It means the command was accepted by the local execution layer. It does not mean the exchange filled the order. Observe on_execution for the actual lifecycle.

HIP-3 uses the same request shape with a scoped symbol:

history.source("xyz:tsla@candles@hyperliquidf:timeframe=60")

ctx.trade(
    {
        "exchange": "hyperliquidf",
        "symbol": "xyz:TSLA",
        "position": "open-long",
        "margin": 100,
        "leverage": 5,
    }
)
FieldRequiredContract
exchangeYesExecution exchange for this request.
accountNomain by default, or a configured named subaccount.
symbolYesA symbol declared by one of the job's sources.
positionYesopen-long, open-short, close-long, or close-short.
sizeOpen: one ofPositive base quantity. Optional on close.
marginOpen: one ofPositive quote collateral. Invalid on close.
leverageNoAt least 1; defaults to 1. Invalid on close.
order.typeNomarket or limit; defaults to market.
order.priceLimit onlyPositive, tick-aligned limit price.
order.tifLimit onlygtc, ioc, or alo; defaults to gtc.
slOpen onlyNative stop-loss trigger price.
tpOpen onlyNative take-profit trigger price.
max_slippageMarket onlyMaximum slippage as a decimal fraction, such as 0.0005 for 5 bps.
max_slippage_bpsMarket onlyMaximum slippage in basis points. Do not combine with max_slippage.

An opening request needs exactly one of size or margin. A closing request is reduce-only automatically. Omit its size to close the full matching position:

ctx.trade(
    {
        "exchange": "hyperliquidf",
        "symbol": "BTC",
        "position": "close-long",
    }
)

Market Lab uses one-way position semantics. Close an opposite position before opening a new one; an open-long request does not silently reverse an existing short.

Named subaccounts

Set account on each trade or order that should use a named subaccount:

ctx.trade(
    {
        "exchange": "hyperliquidf",
        "account": "trading-2",
        "symbol": "BTC",
        "position": "open-long",
        "margin": 100,
        "leverage": 5,
    }
)

Read positions independently for each account:

main_positions = ctx.positions().open
same_main_positions = ctx.positions("main").open
subaccount_positions = ctx.positions("trading-2").open

Create names with mlab auth set <venue> --subaccount <name> before live execution. Named accounts are supported for BULK and Hyperliquid only; HyperLink requests must use main. Omitting account always selects main.

ctx.order(request)

Use ctx.order for a raw buy or sell:

ask = ctx.order(
    {
        "exchange": "hyperliquidf",
        "symbol": "BTC",
        "side": "sell",
        "size": 0.01,
        "leverage": 5,
        "reduceOnly": True,
        "order": {"type": "limit", "price": 66000, "tif": "alo"},
    }
)
FieldRequiredContract
exchangeYesExecution exchange for this request.
accountNomain by default, or a configured named subaccount.
symbolYesA symbol declared by one of the job's sources.
sideYesbuy or sell; long and short are accepted aliases.
sizeOne ofPositive base quantity.
marginOne ofPositive quote collateral used to derive size.
leverageNoPerpetuals only; at least 1, default 1.
reduceOnlyNoPerpetuals only; prevents increasing or flipping inventory.
order.typeNomarket or limit; defaults to market.
order.priceLimit onlyPositive, tick-aligned limit price.
order.tifLimit onlygtc, ioc, or alo; defaults to gtc.
max_slippageMarket onlyMaximum slippage as a decimal fraction.
max_slippage_bpsMarket onlyMaximum slippage in basis points. Do not combine with max_slippage.

Raw perpetual orders follow venue netting. A non-reduce-only sell can reduce a long, close it, or cross through flat into a short. Raw orders do not accept sl or tp because their eventual position effect depends on inventory when they fill.

Market-order slippage

ctx.trade and ctx.order accept one slippage unit for market orders:

ctx.trade(
    {
        "exchange": "bulkf",
        "symbol": "BTC",
        "position": "open-long",
        "margin": 100,
        "leverage": 5,
        "max_slippage_bps": 5,
    }
)

max_slippage_bps: 5 and max_slippage: 0.0005 both mean 5 bps. Market Lab converts bounded market orders into IOC limits at the worst accepted price on BULK and Hyperliquid. Supplying both fields, or adding either field to a limit order, is rejected.

For Hyperliquid or HyperLink Spot, use the exact market pair and omit leverage and reduce-only. The HyperLink route is:

ctx.order(
    {
        "exchange": "hyperlink",
        "symbol": "HYPE/USDC",
        "side": "buy",
        "margin": 100,
        "order": {"type": "limit", "price": 45, "tif": "alo"},
    }
)

A spot buy's margin is its quote-asset budget. A spot sell normally uses base-asset size. Replace hyperlink with hyperliquid for direct Hyperliquid execution.

HyperLink perpetual leverage comes from authenticated asset metadata and is cached in mlabd memory by account and asset. It is not persisted and is fetched again on demand after a daemon restart.

Hyperliquid outcome markets are also spot-like. Use an exact outcome-side symbol such as 1009:0, and use ctx.order to buy tokens or sell tokens already held:

ctx.order(
    {
        "exchange": "hyperliquid",
        "symbol": "1009:0",
        "side": "buy",
        "margin": 100,
        "order": {"type": "limit", "price": 0.42, "tif": "alo"},
    }
)

Outcome orders do not support naked shorting, leverage, reduce-only, attached SL/TP, or perpetual position-close operations. See Hyperliquid outcomes for the venue contract.

ctx.cancel(request)

Cancel a managed order with its stable local ID:

ctx.cancel(
    {
        "order": order["id"],
    }
)

The return value confirms that cancellation was queued; the later execution event confirms its venue result.

Do not add exchange to a cancellation request. Market Lab reads the exchange from the managed order referenced by order.

Multiple execution exchanges

Because routing belongs to each order, a single Python V2 job can execute both legs of an arbitrage without a job-wide venue:

bulk_bid = ctx.order(
    {
        "exchange": "bulkf",
        "symbol": "BTC",
        "side": "buy",
        "size": 0.01,
        "order": {"type": "limit", "price": bulk_price, "tif": "alo"},
    }
)

hyperliquid_ask = ctx.order(
    {
        "exchange": "hyperliquidf",
        "symbol": "BTC",
        "side": "sell",
        "size": 0.01,
        "order": {"type": "limit", "price": hl_price, "tif": "alo"},
    }
)

This makes routing deterministic: the data selector identifies a data stream, while exchange identifies the execution destination. Market Lab does not infer execution from the most recent event or source.

on_execution(ctx)

Use the optional execution hook to react to fills, cancellations, and rejections:

def on_execution(ctx):
    print(ctx.execution)

ctx.execution contains the current lifecycle update:

{
  "seq": 7,
  "jobId": "script_...",
  "tsMs": 1780000000000,
  "type": "order.fill",
  "orderId": "ord_...",
  "symbol": "BTC",
  "venue": "hyperliquidf",
  "venueOrderId": "...",
  "status": "filled",
  "terminal": false,
  "data": {}
}

Common event types include:

  • order.pending, order.accepted, order.updated, and order.terminal
  • order.fill, order.filled, order.cancel_requested, and order.cancelled
  • order.rejected, order.cancel_failed, and order.cancel_rejected
  • position.updated, position.closed, position.liquidated, and position.adl
  • account.margin_updated

tsMs is the daemon's journal time. A venue timestamp, when supplied, remains inside data.

Execution events are journaled and acknowledged only after the hook succeeds. Backtests generate equivalent simulated lifecycle events, so fill-driven logic can use the same hook in both environments.

Backtest execution prices

Every traded exchange and symbol pair needs its own price-bearing source. Market Lab uses the matching exchange-specific candle, order book, or other price-bearing stream as its execution reference. A BTC source from BULK cannot price a BTC order routed to Hyperliquid, and BTC data is never used as the fill price for a ZEC order.

OHLC candles do not reveal the order in which multiple prices were touched inside a bar. The simulator processes existing orders first and uses stable local order-ID order for orders submitted on the same event. This keeps the result deterministic without pretending to know the missing intrabar path.

On this page