Foundation types
mts1b-foundation is the only repo with zero runtime dependencies other than pydantic and typing-extensions. Every other repo in the ecosystem depends on it. This page explains why and what's inside.
What's in foundation
| Category | Contents | Audience |
|---|---|---|
| Pydantic models | Order, Fill, Position, Quote, Bar, Candle, Trade, MarketSnapshot, RiskEnvelope, FundConfig, ... | Every repo |
| NATS subject schemas | mts.orders.*, mts.fills.*, mts.quotes.*, mts.signals.*, mts.risk.* | Any repo using the eventbus |
| OpenAPI specs | Order, Fund, Strategy schemas re-exportable to FastAPI | Service repos |
| Python Protocols | BrokerProtocol, MarketDataProtocol, RiskGate, Sizer, Allocator, Signal | Adapter + plugin authors |
Why a separate repo?
Without mts1b-foundation:
- Every service defines its own
Ordershape. Migrations become "guess what every other service expects." - Two services for the same broker (IBKR) end up with subtly different
Positionshapes — and you find out at runtime. - A plugin needs to re-implement
BrokerProtocolbecause the protocol is buried in a service it doesn't want to depend on.
With mts1b-foundation:
- One
Ordermodel, validated once. Type-checkers + pydantic guarantee shape. - Cross-repo refactors are gated by CI: change a field, every consumer's CI fails until they update.
- Plugin authors import the protocol and that's all they need.
Example: the Order type
from datetime import datetime
from decimal import Decimal
from enum import Enum
from typing import Annotated
from pydantic import BaseModel, Field, PositiveInt
from typing_extensions import Self
from mts1b_foundation.symbology import Symbol
class Side(str, Enum):
BUY = "buy"
SELL = "sell"
SHORT = "short"
COVER = "cover"
class OrderType(str, Enum):
MARKET = "market"
LIMIT = "limit"
STOP = "stop"
STOP_LIMIT = "stop_limit"
MARKETABLE_LIMIT = "marketable_limit"
class TimeInForce(str, Enum):
DAY = "day"
GTC = "gtc"
IOC = "ioc"
FOK = "fok"
OPG = "opg"
class Order(BaseModel):
"""A trading order. All currency amounts in account base currency."""
# Identity
order_id: str = Field(..., description="UUID assigned by mts1b-oms")
idempotency_key: str = Field(..., description="Caller-provided dedupe key")
# What
symbol: Symbol
side: Side
quantity: Decimal = Field(..., gt=0)
order_type: OrderType
limit_price: Decimal | None = None
stop_price: Decimal | None = None
tif: TimeInForce = TimeInForce.DAY
# Routing
fund_id: str
strategy_id: str
broker: str = Field(..., description="paper, ibkr, coinbase, ...")
actor: str = Field(..., description="Who/what created this order (audit trail)")
# Optional
extended_hours: bool = False
nav_usd: Decimal | None = None
thesis: str | None = None
# State (mts1b-oms owns transitions)
created_at: datetime
submitted_at: datetime | None = None
accepted_at: datetime | None = None
canceled_at: datetime | None = None
rejected_reason: str | None = None
The model is frozen for the wire format. Mutations happen via model_copy(update=...), never in place. State transitions are owned by mts1b-oms exclusively.
Example: the Broker Protocol
from typing import Protocol, runtime_checkable
from mts1b_foundation.orders import Order, Fill, Position
@runtime_checkable
class BrokerProtocol(Protocol):
"""Every broker adapter implements this."""
@property
def name(self) -> str: ...
async def submit(self, order: Order) -> Order: ...
async def cancel(self, order_id: str) -> bool: ...
async def get_open_orders(self) -> list[Order]: ...
async def get_positions(self) -> list[Position]: ...
async def stream_fills(self) -> "AsyncIterator[Fill]": ...
The paper broker, IbkrClient, CoinbaseClient, etc. all implement this — and CI verifies via isinstance(adapter, BrokerProtocol) in mts1b-brokers/tests/.
NATS subject conventions
mts.<repo>.<noun>.<verb>[.fund.<fund_id>]
Examples:
| Subject | Payload | Publisher |
|---|---|---|
mts.oms.orders.created | Order | mts1b-oms |
mts.oms.orders.filled | Fill | mts1b-oms |
mts.research.signals.published | Signal | mts1b-research |
mts.risk.envelope.updated | RiskEnvelope | mts1b-riskengine |
mts.marketdata.quotes.<symbol> | Quote | mts1b-marketdata |
mts.treasury.nav.<fund_id> | NavSnapshot | mts1b-treasury |
Schemas are versioned: mts.v2.oms.orders.created. Consumers must declare which versions they understand; the producer emits the highest version supported by all consumers (negotiated at startup).
Versioning + backwards compatibility
| Change | SemVer | Migration |
|---|---|---|
| Add optional field | minor | None |
| Add required field | major | Provide a 6-month default + migration shim |
| Rename field | major | Keep alias for 6 months |
| Tighten validation (e.g. add regex) | major | Audit consumer compliance first |
| Remove field | major | Removed only after the alias period |
CI enforces: every repo declares mts1b-foundation>=A.B,<C and contract-tests against that range. If a consumer pins <2.0 and we ship 2.0, the consumer's CI catches it.
What's NOT in foundation
- Implementations of anything (those go to
mts1b-quantkit,mts1b-portfolio, etc.) - Logging, HTTP clients, DB pools (those are
mts1b-platform) - Broker code (those are
mts1b-brokers) - Anything that imports a network library
The rule: if it has runtime side effects, it's not foundation. Foundation is types + schemas + protocols, nothing else.
Local development
pip install -e ".[dev]" mts1b-foundation
python -c "from mts1b_foundation.orders import Order; print(Order.model_json_schema())"
The full JSON-Schema export feeds the OpenAPI generation in every service that exposes a REST API.
Where to next
- Eventbus — how NATS subjects are wired across repos
- Dedupe contract — the 17 protected symbols
mts1b-foundationrepo spec — full API reference