From 6a995d640639e965c9eaa2c43a9f6e4ac8a53939 Mon Sep 17 00:00:00 2001 From: Jon Michael Aanes Date: Fri, 2 Aug 2024 07:27:17 +0200 Subject: [PATCH] Standardized __str__ format and id format --- fin_defs/__init__.py | 80 +++++++++++++++++++++++++++----------------- test/test_ids.py | 12 +++++++ 2 files changed, 61 insertions(+), 31 deletions(-) create mode 100644 test/test_ids.py diff --git a/fin_defs/__init__.py b/fin_defs/__init__.py index 620e136..3b0cfca 100644 --- a/fin_defs/__init__.py +++ b/fin_defs/__init__.py @@ -1,6 +1,19 @@ """# Finance Definitions. Python library defining base types for financial processing. + +Defines a base `Asset` type, and various subtypes, for universal representation +of these assets. + +Defined hierarchy: + +* `Asset` + - `Currency` + + `FiatCurrency` + + `CryptoCurrency` + - `Stock` + - `Index` + - `Commodity` """ import dataclasses @@ -22,7 +35,16 @@ RE_CRYPTO_TICKER_FORMAT = r'^\S+$' @enforce_typing.enforce_types @dataclasses.dataclass(frozen=True) class StockExchange: - """Unified Stock Exchange identifiers.""" + """Unified Stock Exchange identifiers. + + Fields: + - `name`: Human-readable name. + - `mic`: Official MIC identifier. + - `bloomberg_id`: Identifier for lookup on Bloomberg. + - `crunchbase_id`: Identifier for lookup on Crunchbase. + - `yahoo_id`: Identifier for lookup on Yahoo! Finance. + - `eod_id`: Identifier for lookup on EOD. + """ name: str mic: str @@ -42,6 +64,7 @@ class Asset: """ def names(self) -> list[str]: + """Returns a list of human readable names for this `Asset`.""" return COMMON_NAMES.get(self, []) def to_polygon_id(self) -> str: @@ -85,13 +108,15 @@ class Asset: def to_string_id(self) -> str: """Formats the asset id using the fin_defs asset format.""" if isinstance(self, Stock): - return f'stock:{self}' + return f'stock:{self.ticker}.{self.exchange.mic}' if isinstance(self, FiatCurrency): - return f'fiat:{self}' + return f'fiat:{self.iso_code}' if isinstance(self, CryptoCurrency): - return f'crypto:{self}' + return f'crypto:{self.ccxt_symbol}' if isinstance(self, Index): - return f'index:{self}' + return f'index:{self.ticker}' + if isinstance(self, Commodity): + return f'commodity:{self.alpha_vantage_id}' raise NotImplementedError('Unsupported asset type: ' + str(self)) @@ -122,6 +147,9 @@ class Asset: msg = f'Unsupported asset format: {asset_id}' raise NotImplementedError(msg) + def __str__(self): + return self.to_string_id() + @enforce_typing.enforce_types @dataclasses.dataclass(frozen=True) @@ -139,9 +167,6 @@ class FiatCurrency(Currency): msg = f'iso_code was not in correct format: {self.iso_code}' raise ValueError(msg) - def __str__(self): - return self.iso_code - @enforce_typing.enforce_types @dataclasses.dataclass(frozen=True, eq=True) @@ -156,10 +181,6 @@ class CryptoCurrency(Currency): raise ValueError(msg) """ - def __str__(self): - return self.ccxt_symbol - - @enforce_typing.enforce_types @dataclasses.dataclass(frozen=True, eq=True) class Stock(Asset): @@ -172,9 +193,6 @@ class Stock(Asset): msg = f'ticker was not in correct format: {self.ticker}' raise ValueError(msg) - def __str__(self): - return f'{self.ticker}.{self.exchange.mic}' - @staticmethod def from_polygon_id(polygon_id: str, stock_exchange: StockExchange) -> 'Stock': return Stock(polygon_id, stock_exchange) @@ -190,9 +208,6 @@ class Index(Asset): msg = f'ticker was not in correct format: {self.ticker}' raise ValueError(msg) - def __str__(self): - return self.ticker - @enforce_typing.enforce_types @dataclasses.dataclass(frozen=True, eq=True) @@ -204,9 +219,6 @@ class Commodity(Asset): msg = f'alpha_vantage_id was not in correct format: {self.alpha_vantage_id}' raise ValueError(msg) - def __str__(self): - return self.alpha_vantage_id - Commodity.CRUDE_OIL_WTI = Commodity('WTI') Commodity.CRUDE_OIL_BRENT = Commodity('BRENT') @@ -227,7 +239,7 @@ BTC = CryptoCurrency('BTC', coingecko_id='bitcoin') MPC = CryptoCurrency('MPC', coingecko_id='partisia-blockchain') SPX = Index('SPX') NDX = Index('NDX') -USDT = CryptoCurrency('USDT', 'tether') +USDT = CryptoCurrency('USDT', coingecko_id='tether') COMMON_NAMES: dict[Asset, list[str]] = { FiatCurrency('USD'): ['U.S. Dollar'], @@ -246,15 +258,15 @@ COMMON_NAMES: dict[Asset, list[str]] = { NDX: ['Nasdaq 100'], BTC: ['Bitcoin BTC'], MPC: ['Partisia Blockchain MPC Token'], - CryptoCurrency('ETH', 'ethereum'): ['Ethereum Ether'], - CryptoCurrency('DOGE', 'dogecoin'): ['Dogecoin'], - CryptoCurrency('SOL', 'solana'): ['Solana SOL'], - CryptoCurrency('ADA', 'cardano'): ['Cardano ADA'], - CryptoCurrency('BNB', 'bnb'): ['Binance BNB'], - CryptoCurrency('MATIC', 'polygon'): ['Polygon MATIC'], + CryptoCurrency('ETH', coingecko_id='ethereum'): ['Ethereum Ether'], + CryptoCurrency('DOGE', coingecko_id='dogecoin'): ['Dogecoin'], + CryptoCurrency('SOL', coingecko_id='solana'): ['Solana SOL'], + CryptoCurrency('ADA', coingecko_id='cardano'): ['Cardano ADA'], + CryptoCurrency('BNB', coingecko_id='bnb'): ['Binance BNB'], + CryptoCurrency('MATIC', coingecko_id='polygon'): ['Polygon MATIC'], # Stable-coins - CryptoCurrency('DAI', 'dai'): ['DAI'], - CryptoCurrency('USDC', 'usdc'): ['USD Coin'], + CryptoCurrency('DAI', coingecko_id='dai'): ['DAI'], + CryptoCurrency('USDC', coingecko_id='usdc'): ['USD Coin'], USDT: ['Tether USDT'], } @@ -384,7 +396,12 @@ class AssetInformation: @enforce_typing.enforce_types @dataclasses.dataclass(frozen=True, eq=True, slots=True) class AssetAmount: - """Decimal with associated asset unit.""" + """Decimal with associated asset unit. + + Fields: + - `asset`: Asset unit of amount. + - `amount`: Amount of given asset. + """ asset: Asset amount: Decimal @@ -447,6 +464,7 @@ class ExchangeRateSample(Asset): """Single exchange rate sample with a timestamp and various stats.""" timestamp: datetime.date | datetime.datetime + base_asset: Asset _average: Decimal | None = None last: Decimal | None = None open: Decimal | None = None diff --git a/test/test_ids.py b/test/test_ids.py new file mode 100644 index 0000000..85ac0b2 --- /dev/null +++ b/test/test_ids.py @@ -0,0 +1,12 @@ +import pytest + +import fin_defs + +@pytest.mark.parametrize('asset', fin_defs.WELL_KNOWN_SYMBOLS.values()) +def test_to_from_string_id(asset: fin_defs.Asset): + assert fin_defs.Asset.from_string_id(asset.to_string_id()) == asset +import fin_defs + +@pytest.mark.parametrize('asset', fin_defs.WELL_KNOWN_SYMBOLS.values()) +def test_to_from_polygon_id(asset: fin_defs.Asset): + assert fin_defs.Asset.from_polygon_id(asset.to_polygon_id()) == asset