Skip to main content

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 classFormatExamples
EquitiesTickerAAPL, MSFT, BRK.B
CryptoBASE-QUOTEBTC-USD, ETH-USDT, MATIC-USD
FXBASE-QUOTE (both 3-char ISO)EUR-USD, GBP-JPY
OptionsOCC-style `ROOT YYYYMMDDPCstrike`
FuturesROOT YYYYMMES 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