Sequence/docs

Execution Graphs

Every order in Sequence is an execution graph. A market buy is a 1-node graph. A spot+perp hedge is a 2-node graph with dependency edges. A TWAP is a 1-node graph that the engine expands into N time-chained slices.

One primitive, composed. No special cases.


Anatomy of a graph

An execution graph has three parts:

ComponentWhat it does
NodesIndividual order intents. Each node has a symbol, side, quantity, and execution policy.
EdgesDependencies between nodes. "Execute node B after node A reaches 50% fill."
RiskFour-layer risk model evaluated at admission, pre-trade, runtime, and on failure.

Simple order (1 node)

A market buy is the simplest graph — one node, no edges:

bash
curl -X POST https://api.sequencemkts.com/v1/orders \
  -H "Authorization: Bearer $SEQ_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{"symbol": "ETH-USD", "side": "buy", "qty": 0.5}'

This auto-wraps into a 1-node execution graph with ExecutionPolicy::Sor. The SOR picks the best venue.


TWAP (pacing)

Buy 1 BTC over 5 minutes in 10 equal slices:

json
{
  "nodes": [{
    "node_id": "twap",
    "instrument": {"symbol": "BTC-USD", "type": "spot"},
    "side": "buy",
    "order_type": "market",
    "quantity": {"fixed": {"qty_1e8": 100000000}},
    "execution": {
      "pacing": {"uniform": {"num_slices": 10, "interval_ms": 30000}}
    },
    "activation": "root"
  }],
  "edges": []
}

The engine expands this into 11 nodes + 10 timeout-chained edges. Each slice executes via SOR after the previous slice's timeout fires.


Spot + perp hedge (2 nodes, 1 edge)

Buy 200 ETH spot, then sell 200 ETH-PERP after the spot leg is 50% filled:

json
{
  "nodes": [
    {
      "node_id": "spot",
      "instrument": {"symbol": "ETH-USD", "type": "spot"},
      "side": "buy",
      "order_type": "market",
      "quantity": {"fixed": {"qty_1e8": 20000000000}},
      "execution": {"policy": "sor", "urgency": "medium"},
      "activation": "root"
    },
    {
      "node_id": "hedge",
      "instrument": {"symbol": "ETH-USD-PERP", "type": "perp", "venue": "hyperliquid"},
      "side": "sell",
      "order_type": "market",
      "quantity": {"fixed": {"qty_1e8": 20000000000}},
      "execution": {"policy": "aggressive_chase"},
      "activation": "wait"
    }
  ],
  "edges": [
    {
      "edge_id": "e0",
      "from_node": "spot",
      "to_node": "hedge",
      "trigger": "on_fill_ratio",
      "condition": {"fill_ratio_gte": 0.5}
    }
  ]
}

With the Python SDK:

python
seq.graph(
    nodes=[
        seq.node("spot", "ETH-USD", "buy", 200, urgency="medium"),
        seq.node("hedge", "ETH-USD-PERP", "sell", 200, instrument_type="perp"),
    ],
    edges=[seq.edge("spot", "hedge", trigger="fill_pct", value=0.5)],
)

Bracket order (3 nodes, 2 edges)

Entry + take-profit + stop-loss. The TP and SL legs wait for the entry to fill, then activate on different conditions:

python
seq.graph(
    nodes=[
        seq.node("entry", "BTC-USD", "buy", 0.1, urgency="high"),
        seq.node("tp", "BTC-USD", "sell", 0.1, limit_price=80000),
        seq.node("sl", "BTC-USD", "sell", 0.1, urgency="high"),
    ],
    edges=[
        seq.edge("entry", "tp", trigger="full_fill"),
        # Stop-loss triggers if BTC drops 5% below entry price:
        {"edge_id": "e_sl", "from_node": "entry", "to_node": "sl",
         "trigger": {"on_price": {"symbol": "BTC-USD", "direction": "below",
                     "reference": "graph_entry_price", "offset_pct": -5.0}}}
    ],
)

Edge triggers

Edges define when a child node activates:

TriggerFires when
on_acceptedParent order accepted by SOR
on_first_fillFirst fill arrives
on_fillEvery fill (streaming hedge)
on_fill_ratioFill ratio reaches threshold (use with condition.fill_ratio_gte)
on_full_fill100% filled
on_cancelParent cancelled
on_doneAny terminal state
on_timeoutTime elapsed since parent activation
on_pricePrice condition met (take-profit, stop-loss)

Sizing transforms

Control how the child node's quantity derives from the parent's fill:

SizingBehavior
parent_filled_qtyMatch parent's cumulative filled quantity
parent_incremental_fill_qtyOnly the new fill delta (streaming hedge)
linear_hedgeMultiply by factor. -1.0 = opposite-side hedge
scaled_notionalScale by notional with optional USD cap
residualParent target minus filled (the remainder)
fixedExplicit quantity, ignores parent

Execution policies

Each node has a policy that controls how the SOR executes it:

PolicyBehavior
sorSmart order routing — SOR picks venues and splits (default)
ioc_sweepSingle IOC sweep across venues
passive_limitPost at the touch, hold until TTL
aggressive_chaseCross spread, widen limit on timeout
passive_ladderPost passively, then escalate after horizon_ms
time_dripSOR picks tactic dynamically based on urgency + spread
wasm_algoRun a WASM algorithm on a single venue edge
mesh_algoRun WASM across multiple edges with cross-venue messaging

Risk model (4 layers)

json
{
  "risk": {
    "admission": {
      "max_notional_usd": 500000,
      "max_leverage": 2.0,
      "required_venues": ["binance", "coinbase"]
    },
    "pre_trade": {
      "max_slippage_bps": 50,
      "require_balance_check": true
    },
    "runtime": {
      "max_unhedged_qty_1e8": 500000000,
      "max_loss_usd": 10000,
      "max_drawdown_usd": 5000
    },
    "failure": {
      "on_runtime_breach": "cancel_all_open",
      "on_disconnect": "pause_graph"
    }
  }
}
LayerWhenWhat it checks
AdmissionOnce, at graph submissionCan this graph start? (notional, leverage, venue access)
Pre-tradePer node, before SOR dispatchIs this node safe to send? (balance, margin, slippage)
RuntimeEvery fill + 1Hz tickHas aggregate exposure become unsafe? (delta, loss, drawdown)
FailureOn terminal eventsWhat to do when things break (cancel, pause, flatten)

Graph lifecycle

code
submitted → active → [nodes execute] → completed
                                     → cancelled (user/kill switch)
                                     → failed (all nodes rejected)

Track status via GET /v1/execution_graphs/{graph_id} or with the SDK:

python
status = seq.graph_status("graph_7d769...")
for node_id, node in status["nodes"].items():
    print(f"{node_id}: {node['status']} filled={node['filled_qty_1e8']/1e8}")