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:
| Component | What it does |
|---|---|
| Nodes | Individual order intents. Each node has a symbol, side, quantity, and execution policy. |
| Edges | Dependencies between nodes. "Execute node B after node A reaches 50% fill." |
| Risk | Four-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:
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:
{
"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:
{
"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:
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:
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:
| Trigger | Fires when |
|---|---|
on_accepted | Parent order accepted by SOR |
on_first_fill | First fill arrives |
on_fill | Every fill (streaming hedge) |
on_fill_ratio | Fill ratio reaches threshold (use with condition.fill_ratio_gte) |
on_full_fill | 100% filled |
on_cancel | Parent cancelled |
on_done | Any terminal state |
on_timeout | Time elapsed since parent activation |
on_price | Price condition met (take-profit, stop-loss) |
Sizing transforms
Control how the child node's quantity derives from the parent's fill:
| Sizing | Behavior |
|---|---|
parent_filled_qty | Match parent's cumulative filled quantity |
parent_incremental_fill_qty | Only the new fill delta (streaming hedge) |
linear_hedge | Multiply by factor. -1.0 = opposite-side hedge |
scaled_notional | Scale by notional with optional USD cap |
residual | Parent target minus filled (the remainder) |
fixed | Explicit quantity, ignores parent |
Execution policies
Each node has a policy that controls how the SOR executes it:
| Policy | Behavior |
|---|---|
sor | Smart order routing — SOR picks venues and splits (default) |
ioc_sweep | Single IOC sweep across venues |
passive_limit | Post at the touch, hold until TTL |
aggressive_chase | Cross spread, widen limit on timeout |
passive_ladder | Post passively, then escalate after horizon_ms |
time_drip | SOR picks tactic dynamically based on urgency + spread |
wasm_algo | Run a WASM algorithm on a single venue edge |
mesh_algo | Run WASM across multiple edges with cross-venue messaging |
Risk model (4 layers)
{
"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"
}
}
}| Layer | When | What it checks |
|---|---|---|
| Admission | Once, at graph submission | Can this graph start? (notional, leverage, venue access) |
| Pre-trade | Per node, before SOR dispatch | Is this node safe to send? (balance, margin, slippage) |
| Runtime | Every fill + 1Hz tick | Has aggregate exposure become unsafe? (delta, loss, drawdown) |
| Failure | On terminal events | What to do when things break (cancel, pause, flatten) |
Graph lifecycle
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:
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}")