mts1b_foundation.symbology — full reference
The canonical symbol format across the MTS1B ecosystem. Symbol is a str subclass with extra structure.
Quick examples
from mts1b_foundation.symbology import Symbol, AssetClass, normalize, to_native
# Build canonical Symbols
aapl = Symbol("AAPL")
btc = Symbol("BTC-USD")
eur = Symbol("EUR-USD")
spy_put = Symbol("SPY 20260823P550")
es_fut = Symbol("ES 202609")
# Introspect
btc.asset_class # "crypto"
btc.base # "BTC"
btc.quote # "USD"
# Normalize external strings to canonical
normalize("BTCUSD", venue="coinbase") # Symbol("BTC-USD")
normalize("BTC/USD") # Symbol("BTC-USD")
normalize("AAPL") # Symbol("AAPL")
# Render to a venue's wire format
to_native(btc, venue="binance") # "BTCUSD"
to_native(btc, venue="ibkr_paxos") # "BTC"
to_native(btc, venue="kraken") # "BTC/USD"
Canonical formats
| Asset class | Format | Examples |
|---|---|---|
| Equities | Ticker | AAPL, MSFT, BRK.B |
| Crypto | BASE-QUOTE | BTC-USD, ETH-USDT, MATIC-USD |
| FX | BASE-QUOTE (both 3-char ISO) | EUR-USD, GBP-JPY |
| Options | OCC-style `ROOT YYYYMMDDP | Cstrike` |
| Futures | ROOT YYYYMM | ES 202609, CL 202604 |
class Symbol(str)
A str subclass — anywhere a str is accepted, a Symbol works (in dicts as keys, in JSON, in pickle, in pandas).
sym = Symbol("BTC-USD")
# str behavior
print(sym) # "BTC-USD"
print(sym.upper()) # "BTC-USD"
print(sym[:3]) # "BTC"
# Hashable + dict key
positions: dict[Symbol, Decimal] = {sym: Decimal("0.5")}
# JSON-friendly (the model_config serializes Symbol → string)
import json
print(json.dumps({"symbol": sym})) # {"symbol": "BTC-USD"}
Properties
sym.asset_class # "crypto", "equities", "fx", "options", "futures", or "unknown"
sym.base # "BTC" for "BTC-USD"; "AAPL" for "AAPL"; root for futures/options
sym.quote # "USD" for "BTC-USD"; None for equities
Construction validates
Symbol("") # raises ValueError("Symbol cannot be empty")
Symbol(" AAPL ") # OK — whitespace stripped → "AAPL"
Symbol(123) # raises TypeError
Asset class detection examples
Symbol("AAPL").asset_class # "equities"
Symbol("BRK.B").asset_class # "equities"
Symbol("BTC-USD").asset_class # "crypto"
Symbol("ETH-USDT").asset_class # "crypto"
Symbol("EUR-USD").asset_class # "fx" (both 3-char ISO currencies)
Symbol("GBP-JPY").asset_class # "fx"
Symbol("USD-MXN").asset_class # "fx"
Symbol("SPY 20260823P550").asset_class # "options"
Symbol("AAPL 20260920C200").asset_class # "options"
Symbol("ES 202609").asset_class # "futures"
Symbol("CL 202404").asset_class # "futures"
Symbol("FOO-BAR").asset_class # "crypto" (heuristic for unknown pairs)
class AssetClass(str, Enum)
class AssetClass(str, Enum):
EQUITIES = "equities"
OPTIONS = "options"
FX = "fx"
FUTURES = "futures"
CRYPTO = "crypto"
PREDICTION = "prediction"
SPORTS = "sports"
Use the enum for switch-case / route-by-class logic:
def route_broker(symbol: Symbol) -> str:
match symbol.asset_class:
case AssetClass.EQUITIES.value:
return "ibkr"
case AssetClass.OPTIONS.value:
return "ibkr"
case AssetClass.CRYPTO.value:
return "coinbase"
case AssetClass.FX.value:
return "ibkr_fx"
case AssetClass.FUTURES.value:
return "ibkr"
case _:
raise ValueError(f"no broker route for {symbol}")
class Venue(str, Enum)
class Venue(str, Enum):
NYSE = "NYSE"
NASDAQ = "NASDAQ"
BATS = "BATS"
CME = "CME"
CBOT = "CBOT"
NYMEX = "NYMEX"
CBOE = "CBOE"
COINBASE = "COINBASE"
BINANCE = "BINANCE"
KRAKEN = "KRAKEN"
BYBIT = "BYBIT"
OKX = "OKX"
OANDA = "OANDA"
IBKR_FX = "IBKR_FX"
IBKR = "IBKR"
SCHWAB = "SCHWAB"
normalize(symbol, *, venue=None) -> Symbol
Normalize an external symbol into canonical form. Handles common venue-specific quirks.
# Slash form (Kraken-style)
normalize("BTC/USD") # Symbol("BTC-USD")
# Concatenated (Binance / Coinbase wire format)
normalize("BTCUSD", venue="coinbase") # Symbol("BTC-USD")
normalize("BTCUSDT", venue="binance") # Symbol("BTC-USDT")
normalize("ETHUSDC", venue="binance") # Symbol("ETH-USDC")
# Already canonical: pass-through
normalize("BTC-USD") # Symbol("BTC-USD")
# Lowercase normalized
normalize("aapl") # Symbol("AAPL")
normalize("btc/usd") # Symbol("BTC-USD")
Edge cases
# Multi-letter currencies (rare but real: e.g., USDT, USDC)
normalize("BTCUSDT", venue="binance") # Symbol("BTC-USDT") ✓
normalize("BTCUSDC", venue="binance") # Symbol("BTC-USDC") ✓
# Without venue hint, BTCUSD is ambiguous:
normalize("BTCUSD") # Symbol("BTCUSD") (unchanged — heuristic falls through)
# → use venue hint or explicit dash:
normalize("BTC-USD") # Symbol("BTC-USD") ✓
When to call normalize
At every adapter boundary — TradingView webhook, broker raw fill, news feed. The internal codebase always works with canonical Symbols.
# tv-bridge inbound
raw_tv = "BINANCE:BTCUSDT"
venue_str, native = raw_tv.split(":")
canonical = normalize(native, venue=venue_str.lower())
# Symbol("BTC-USDT")
to_native(symbol, venue) -> str
Render a canonical Symbol to a venue's wire format. Inverse of normalize.
sym = Symbol("BTC-USD")
to_native(sym, venue="binance") # "BTCUSD"
to_native(sym, venue="kraken") # "BTC/USD"
to_native(sym, venue="ibkr_paxos") # "BTC" — IBKR's PAXOS crypto uses bare 3-char
to_native(sym, venue="coinbase") # "BTC-USD"
to_native(sym, venue="unknown") # "BTC-USD" — default pass-through
# Equities are pass-through for most venues
to_native(Symbol("AAPL"), venue="nyse") # "AAPL"
Use at adapter outbound boundary
# Coinbase adapter places an order
from mts1b_foundation.orders import Order
from mts1b_foundation.symbology import to_native
async def submit_to_coinbase(order: Order) -> str:
payload = {
"product_id": to_native(order.symbol, venue="coinbase"), # "BTC-USD"
"side": order.side.value,
"size": str(order.quantity),
"type": order.order_type.value,
...
}
response = await http.post("/orders", json=payload)
return response.json()["order_id"]
Common idioms
Sorting symbols
syms = [Symbol("BTC-USD"), Symbol("ETH-USD"), Symbol("AAPL"), Symbol("MSFT")]
syms.sort()
# [Symbol("AAPL"), Symbol("BTC-USD"), Symbol("ETH-USD"), Symbol("MSFT")]
# (alphabetical, as expected from str ordering)
Filtering by asset class
def crypto_only(symbols: list[Symbol]) -> list[Symbol]:
return [s for s in symbols if s.asset_class == "crypto"]
universe = [Symbol("AAPL"), Symbol("BTC-USD"), Symbol("ETH-USD"), Symbol("MSFT")]
crypto_only(universe) # [Symbol("BTC-USD"), Symbol("ETH-USD")]
Grouping orders by venue
from collections import defaultdict
def group_by_venue(orders: list[Order]) -> dict[str, list[Order]]:
"""Group orders by their target broker/venue."""
groups: dict[str, list[Order]] = defaultdict(list)
for o in orders:
groups[o.broker].append(o)
return dict(groups)
# Output: {"ibkr": [...], "coinbase": [...], "paper": [...]}
Pretty-print a Symbol
def pretty(sym: Symbol) -> str:
cls = sym.asset_class
if cls == "crypto":
return f"💰 {sym}"
elif cls == "equities":
return f"📈 {sym}"
elif cls == "options":
return f"🎰 {sym}"
elif cls == "futures":
return f"⏳ {sym}"
elif cls == "fx":
return f"💱 {sym}"
return str(sym)
Pydantic integration
Symbol defines __get_pydantic_core_schema__, so it works seamlessly as a field type:
from pydantic import BaseModel
from mts1b_foundation.symbology import Symbol
class MyOrder(BaseModel):
symbol: Symbol
m = MyOrder(symbol="BTC-USD") # str → Symbol via validation
print(type(m.symbol)) # <class 'mts1b_foundation.symbology.Symbol'>
print(m.symbol.asset_class) # "crypto"
# Serialization preserves as string
print(m.model_dump_json()) # {"symbol":"BTC-USD"}
Edge cases + common mistakes
"AAPL" vs "AAPL.US"
Some data providers (Finnhub, Yahoo's full-format) use AAPL.US for the U.S. exchange. We normalize to AAPL:
# Add a custom normalizer if you encounter "AAPL.US"
def normalize_yahoo(s: str) -> Symbol:
if s.endswith(".US"):
return Symbol(s[:-3])
return normalize(s)
BRK.B vs BRK-B
Berkshire Hathaway B shares: U.S. brokers use BRK.B, some feeds use BRK-B. We keep BRK.B as canonical (the dot is for share-class, not pair):
Symbol("BRK.B").asset_class # "equities"
Symbol("BRK-B").asset_class # "crypto" ← MISLEADING
# To normalize a feed using BRK-B form:
def fix_berkshire(s: str) -> str:
if s in ("BRK-B", "BRK-A"):
return s.replace("-", ".")
return s
Options OCC parsing
import re
OPT = re.compile(r"^([A-Z]{1,6})\s+(\d{8})([CP])(\d+(?:\.\d+)?)$")
def parse_option(sym: Symbol) -> dict:
m = OPT.match(str(sym))
if not m:
raise ValueError(f"not an OCC option: {sym}")
root, expiry_str, right, strike = m.groups()
return {
"root": root,
"expiry": expiry_str, # YYYYMMDD
"right": right, # C or P
"strike": float(strike),
}
parse_option(Symbol("SPY 20260823P550"))
# {"root": "SPY", "expiry": "20260823", "right": "P", "strike": 550.0}
See also
platform.symbology.normalize— the live normalizer in mts1b-platform (deduped from 3 monorepo copies)orders— usesSymbolas a field typepositions— keyed bySymbol- Tutorial: Add a broker — example of using
to_nativeat adapter boundary