Skip to main content

mts1b-oms — public API surface

OMS state machine, broker routing, position store. REST + gRPC + NATS interfaces.

Python client

from mts1b_oms import OmsClient

oms = OmsClient(base_url="http://localhost:8001")

# Submit an order
order = Order(...)
result = await oms.submit(order)

# Cancel
ok = await oms.cancel(order.order_id)

# Inspect
order = await oms.get_order(order.order_id)
positions = await oms.get_positions(fund_id="paper-momentum")
open_orders = await oms.list_open_orders(fund_id="paper-momentum")

# Stream updates (NATS-backed websocket)
async for update in oms.stream_updates(fund_id="paper-momentum"):
print(update)

REST endpoints

POST /v1/orders — submit

POST /v1/orders
{
"order_id": "ord-abc",
"idempotency_key": "strategy-v7-2026-05-23-AAPL-long",
"symbol": "AAPL",
"side": "buy",
"quantity": "100",
"order_type": "limit",
"limit_price": "180.50",
"tif": "day",
"fund_id": "paper-momentum",
"strategy_id": "momentum_v1",
"broker": "paper",
"actor": "research_signal_executor",
"created_at": "2026-05-23T16:00:00Z"
}
→ 202 Accepted
{
"order_id": "ord-abc",
"state": "PENDING_RISK",
"submitted_at": null
}

DELETE /v1/orders/{order_id} — cancel

DELETE /v1/orders/ord-abc
→ 200 OK
{
"order_id": "ord-abc",
"state": "CANCELED",
"canceled_at": "2026-05-23T16:01:00Z"
}

GET /v1/orders/{order_id} — fetch

GET /v1/orders/ord-abc
→ 200 OK
{
"order_id": "ord-abc",
"state": "FILLED",
"submitted_at": "2026-05-23T16:00:01Z",
"accepted_at": "2026-05-23T16:00:01Z",
...
}

GET /v1/positions?fund_id=X — list positions

GET /v1/positions?fund_id=paper-momentum
→ 200 OK
[
{"symbol": "AAPL", "quantity": "100", "avg_price": "180.50", ...},
...
]

GET /v1/funds/{fund_id}/nav — current NAV

GET /v1/funds/paper-momentum/nav
→ 200 OK
{
"fund_id": "paper-momentum",
"asof": "2026-05-23",
"nav_usd": "102400.00",
...
}

gRPC

service Oms {
rpc Submit(Order) returns (Order);
rpc Cancel(CancelRequest) returns (Order);
rpc GetOrder(OrderId) returns (Order);
rpc GetPositions(FundId) returns (PositionList);
rpc StreamUpdates(FundId) returns (stream OrderUpdate);
}
from grpc import aio
import oms_pb2, oms_pb2_grpc

async with aio.insecure_channel("localhost:50051") as ch:
stub = oms_pb2_grpc.OmsStub(ch)
request = oms_pb2.Order(order_id="ord-abc", ...)
response = await stub.Submit(request)

NATS subjects

Subscribed (consumed)

SubjectPayload
mts.v1.brokers.<broker>.fills.rawbroker-native fill dict
mts.v1.risk.envelope.updatedRiskEnvelope (reloads on change)
mts.v1.risk.halt.requestedHaltRequest (stops accepting orders)

Published

SubjectPayload
mts.v1.oms.orders.createdOrder (initial state)
mts.v1.oms.orders.acceptedOrder (passed risk)
mts.v1.oms.orders.submittedOrder (sent to broker)
mts.v1.oms.orders.filledOrder (terminal)
mts.v1.oms.orders.partialOrder (partial fill)
mts.v1.oms.orders.canceledOrder
mts.v1.oms.orders.rejectedOrder (with rejected_reason)
mts.v1.oms.orders.closedOrder (terminal cleanup)
mts.v1.oms.fills.createdFill

State machine

CREATED ──▶ PENDING_RISK ──┬─▶ REJECTED (terminal)


ACCEPTED ──┬─▶ CANCELED (before submit)
│ │
▼ │
SUBMITTED ──┴─▶ FILLED → CLOSED (terminal)
└─▶ PARTIAL → FILLED → CLOSED
└─▶ EXPIRED (terminal)

Transitions are owned by OMS exclusively. Other services see them via NATS events.

Routing

# Configuration (mts1b.config or per-fund)
routing:
default_broker: paper
large_order_threshold_usd: 100000
use_execalgos:
enabled: true
default_algo: vwap
horizon_minutes: 30

Large orders auto-route through mts1b-oms-algos (VWAP/TWAP/Almgren-Chriss/Iceberg/IS). Each child order goes through the full pipeline including risk gates.

Position store

# Internal (used by OMS workers)
from mts1b_oms.positions.store import PositionStore

store = PositionStore(dsn=...)
pos = await store.get(fund_id="paper-momentum", symbol=Symbol("AAPL"))
await store.apply_fill(fill)

See also