Source code for dccd.domain.symbol
"""Symbol — canonical trading pair representation."""
from __future__ import annotations
from typing import Literal
from pydantic import BaseModel, field_validator
__all__ = ["Symbol"]
_ALIASES: dict[str, str] = {"XBT": "BTC"}
[docs]
class Symbol(BaseModel, frozen=True):
"""Canonical trading pair (base/quote, spot by default).
Examples
--------
>>> Symbol(base='BTC', quote='USDT')
Symbol(base='BTC', quote='USDT', market='spot')
>>> str(Symbol(base='BTC', quote='USDT'))
'BTC/USDT'
>>> Symbol.parse('BTC/USDT')
Symbol(base='BTC', quote='USDT', market='spot')
>>> Symbol.parse('XBT/USD')
Symbol(base='BTC', quote='USD', market='spot')
"""
base: str
quote: str
market: Literal["spot"] = "spot"
@field_validator("base", "quote", mode="before")
@classmethod
def _normalize(cls, v: str) -> str:
v = v.upper().strip()
return _ALIASES.get(v, v)
def __str__(self) -> str:
return f"{self.base}/{self.quote}"
def __repr__(self) -> str:
return f"Symbol(base={self.base!r}, quote={self.quote!r}, market={self.market!r})"
[docs]
@classmethod
def parse(cls, raw: str, market: Literal["spot"] = "spot") -> "Symbol":
"""Parse a pair string with an optional separator.
Accepted formats: ``BTC/USDT``, ``BTC-USDT``, ``BTCUSDT`` (ambiguous,
use explicit separator when possible).
Examples
--------
>>> Symbol.parse('ETH-USD')
Symbol(base='ETH', quote='USD', market='spot')
"""
for sep in ("/", "-", "_"):
if sep in raw:
base, quote = raw.split(sep, 1)
return cls(base=base, quote=quote, market=market)
raise ValueError(
f"Cannot parse symbol {raw!r}: use a separator like '/' or '-'"
)