Skip to main content

mts1b-riskengine — public API surface

7-gate pre-trade pipeline + synthetic stops + broker-exit reconciler + drawdown halt.

gRPC interface (primary)

service RiskEngine {
rpc CheckOrder(Order) returns (RiskDecision);
rpc GetEnvelope(EnvelopeRequest) returns (RiskEnvelope);
rpc UpdateEnvelope(RiskEnvelope) returns (RiskEnvelope);
rpc RequestHalt(HaltRequest) returns (HaltResponse);
rpc ResumeHalt(ResumeRequest) returns (HaltStatus);
}

OMS calls CheckOrder synchronously for every order; p99 ≤ 5ms.

Python client

from mts1b_riskengine import RiskEngineClient

re = RiskEngineClient(grpc_addr="localhost:50052")

# Check an order (does NOT submit)
decision = await re.check_order(order, envelope)
# RiskDecision(approved=True/False, rejecting_gate=str, code=str, reason=str, ...)

# Read envelope
env = await re.get_envelope(fund_id="paper-momentum")

# Update envelope (gated; rejected if loosening during halt)
new_env = env.model_copy(update={"max_position_pct": 0.07,
"version": env.version + 1})
applied = await re.update_envelope(new_env)

The 7 gates

#GateCheck
1idempotencyOrder.idempotency_key not seen in 5-min window
2schemapydantic validates
3staticbroker / order_type / asset_class allowed; notional ≤ max
4position_riskresulting position pct, gross/net exposure, sector
5drawdown_haltfund not in halt state
6short_sideshorting enabled; borrow fee acceptable
7cro_veto (optional)LLM CRO persona doesn't veto

Each gate publishes outcome to NATS:

mts.v1.risk.gate.passed → dict with order_id, gate, latency_ms
mts.v1.risk.gate.failed → OrderRejection

NATS subjects

Subscribed

SubjectWhy
mts.v1.oms.orders.createdobserve new orders for stats
mts.v1.oms.fills.createdupdate position store for next gate check
mts.v1.treasury.nav.updatedrecompute drawdown halt conditions

Published

SubjectPayload
mts.v1.risk.envelope.updatedRiskEnvelope
mts.v1.risk.gate.passeddict with order_id, gate, latency
mts.v1.risk.gate.failedOrderRejection
mts.v1.risk.halt.requestedHaltRequest (auto-halts)

Synthetic stops

# Configuration
synthetic_stop:
enabled: true
poll_interval_s: 5 # crypto
poll_interval_equities_s: 60

Worker polls quotes every N seconds. When position price crosses fill_price × (1 ± synthetic_stop_pct), the worker emits a closing order via OMS (same path, same gates).

Honors stops even on broker outage or after-hours gap.

Broker-exit reconciler

broker_exit_reconciler:
enabled: true
interval_s: 120
alert_threshold: 1 # alert on any discrepancy

Every 120s: compare OMS positions ↔ broker positions. Differences indicate:

  • Lost fill
  • Broker-side reconciliation lag
  • Manual broker trade (off-system)
  • Cancellation race

Alerts go to Telegram via mts1b-platform.messaging. Auto-reconcile (configurable) updates OMS to match broker.

Drawdown halt — the kill switch

Triggers:

TriggerSeverity
daily_loss_halt_pct breachedFUND_HALT
weekly_loss_halt_pct breachedFUND_HALT
monthly_loss_halt_pct breachedFUND_HALT
Manual mts cmd haltFIRM_HALT
news_spike watchdogFIRM_HALT
Per-strategy drift < -2.0σSTRATEGY_HALT

Halts are sticky — only operator can resume:

mts cmd resume <fund_id> # requires confirmation: type RESUME
mts cmd resume <strategy_id>
mts cmd resume # firm-wide; requires 2FA

CLI

# Show envelope
mts mts1b-riskengine envelope show --fund-id paper-momentum

# Tighten envelope (loosening during halt is rejected)
mts mts1b-riskengine envelope set --fund-id paper-momentum \
--max-position-pct 0.07 \
--daily-loss-halt-pct 0.02

# List recent rejections
mts mts1b-riskengine rejections list --fund-id paper-momentum --hours 24

# Force reconciliation
mts mts1b-riskengine reconcile --fund-id paper-momentum --force

# Halt status
mts mts1b-riskengine halts list

See also