1
0

Added attributes system for asset ids

This commit is contained in:
Jon Michael Aanes 2024-09-01 19:35:44 +02:00
parent 3a96b50b68
commit fb07c495fa
Signed by: Jmaa
SSH Key Fingerprint: SHA256:Ab0GfHGCblESJx7JRE4fj4bFy/KRpeLhi41y4pF3sNA
2 changed files with 58 additions and 14 deletions

View File

@ -26,6 +26,19 @@ import enforce_typing
from ._version import __version__ # noqa: F401 from ._version import __version__ # noqa: F401
def parse_attr_data(attr_data: str) -> dict[str, str | int]:
d = {}
if attr_data is None:
return d
for s in attr_data.split(','):
s = s.strip()
if s == '': continue
(attr_key, attr_value) = s.split('=')
if re.match(r'^\d+$', attr_value):
attr_value = int(attr_value)
d[attr_key] = attr_value
return d
## Ids ## Ids
RE_TICKER_FORMAT = r'^[A-Z0-9_]+$' RE_TICKER_FORMAT = r'^[A-Z0-9_]+$'
@ -114,11 +127,13 @@ class Asset:
def to_string_id(self) -> str: def to_string_id(self) -> str:
"""Formats the asset id using the fin_defs asset format.""" """Formats the asset id using the fin_defs asset format."""
if isinstance(self, Stock): if isinstance(self, Stock):
return f'stock:{self.ticker}.{self.exchange.mic}' attrs_str = f'{{nordnet_id={self.nordnet_id}}}' if self.nordnet_id else ''
return f'stock:{self.ticker}.{self.exchange.mic}{attrs_str}'
if isinstance(self, FiatCurrency): if isinstance(self, FiatCurrency):
return f'fiat:{self.iso_code}' return f'fiat:{self.iso_code}'
if isinstance(self, CryptoCurrency): if isinstance(self, CryptoCurrency):
return f'crypto:{self.ccxt_symbol}' attrs_str = f'{{coingecko_id={self.coingecko_id}}}' if self.coingecko_id else ''
return f'crypto:{self.ccxt_symbol}{attrs_str}'
if isinstance(self, Index): if isinstance(self, Index):
return f'index:{self.ticker}' return f'index:{self.ticker}'
if isinstance(self, Commodity): if isinstance(self, Commodity):
@ -128,28 +143,35 @@ class Asset:
raise NotImplementedError(msg) raise NotImplementedError(msg)
@staticmethod @staticmethod
def from_string_id(asset_id: str) -> 'Asset': def from_string_id(asset_id: str, shortcut_well_known=True) -> 'Asset':
"""Parses an `Asset` using the fin_defs asset format.""" """Parses an `Asset` using the fin_defs asset format."""
if ':' in asset_id:
prefix, ticker = asset_id.split(':') m = re.match(r'^(?:(\w+):)?([^{]+)(?:\{(.*)\})?$', asset_id)
else: if m is None:
prefix, ticker = None, asset_id msg = f'Unsupported asset format: {asset_id}'
raise NotImplementedError(msg)
prefix = m.group(1)
ticker = m.group(2)
attr_data = m.group(3)
if known_symbol := WELL_KNOWN_SYMBOLS.get(ticker, None): if known_symbol := WELL_KNOWN_SYMBOLS.get(ticker, None):
return known_symbol return known_symbol
attrs: dict[str, str | int] = parse_attr_data(attr_data)
if prefix == 'stock': if prefix == 'stock':
if m := re.match(r'^(\w+)(?:\.(\w+))?$', ticker): if m := re.match(r'^(\w+)(?:\.(\w+))?$', ticker):
exchange = EXCHANGES_BY_IDS[m.group(2) or 'NYSE'] # TODO? exchange = EXCHANGES_BY_IDS[m.group(2) or 'NYSE'] # TODO?
return Stock(m.group(1), exchange) return Stock(m.group(1), exchange, **attrs)
if prefix == 'currency': if prefix == 'currency':
return FiatCurrency(ticker) return FiatCurrency(ticker, **attrs)
if prefix == 'fiat': if prefix == 'fiat':
return FiatCurrency(ticker) return FiatCurrency(ticker, **attrs)
if prefix == 'index': if prefix == 'index':
return Index(ticker) return Index(ticker, **attrs)
if prefix == 'crypto': if prefix == 'crypto':
return CryptoCurrency(ticker, None) # TODO return CryptoCurrency(ticker, **attrs)
msg = f'Unsupported asset format: {asset_id}' msg = f'Unsupported asset format: {asset_id}'
raise NotImplementedError(msg) raise NotImplementedError(msg)
@ -182,7 +204,7 @@ class FiatCurrency(Currency):
@dataclasses.dataclass(frozen=True, eq=True) @dataclasses.dataclass(frozen=True, eq=True)
class CryptoCurrency(Currency): class CryptoCurrency(Currency):
ccxt_symbol: str ccxt_symbol: str
coingecko_id: str | None coingecko_id: str | None = None
def __post_init__(self): def __post_init__(self):
"""TODO """TODO

View File

@ -3,11 +3,33 @@ import pytest
import fin_defs import fin_defs
def test_parse_attr():
assert fin_defs.parse_attr_data('') == {}
assert fin_defs.parse_attr_data(' ') == {}
assert fin_defs.parse_attr_data('abc=abc') == {'abc':'abc'}
assert fin_defs.parse_attr_data('abc=123') == {'abc':123}
assert fin_defs.parse_attr_data('abc=123,xyz=abc') == {'abc':123, 'xyz': 'abc'}
assert fin_defs.parse_attr_data(' abc=123 , xyz=abc ') == {'abc':123, 'xyz': 'abc'}
def test_from_nordnet():
derp = fin_defs.Asset.from_string_id('stock:NVO.NYSE{nordnet_id=123}')
assert isinstance(derp, fin_defs.Stock)
assert derp.ticker == 'NVO'
assert derp.nordnet_id == 123
@pytest.mark.parametrize('asset', fin_defs.WELL_KNOWN_SYMBOLS.values())
def test_to_from_string_id_shortcut(asset: fin_defs.Asset):
assert fin_defs.Asset.from_string_id(asset.to_string_id(), shortcut_well_known=True) == asset
@pytest.mark.parametrize('asset', fin_defs.WELL_KNOWN_SYMBOLS.values()) @pytest.mark.parametrize('asset', fin_defs.WELL_KNOWN_SYMBOLS.values())
def test_to_from_string_id(asset: fin_defs.Asset): def test_to_from_string_id(asset: fin_defs.Asset):
assert fin_defs.Asset.from_string_id(asset.to_string_id()) == asset assert fin_defs.Asset.from_string_id(asset.to_string_id(), shortcut_well_known=False) == asset
@pytest.mark.parametrize('asset', fin_defs.WELL_KNOWN_SYMBOLS.values()) @pytest.mark.parametrize('asset', fin_defs.WELL_KNOWN_SYMBOLS.values())
def test_to_from_polygon_id(asset: fin_defs.Asset): def test_to_from_polygon_id(asset: fin_defs.Asset):
if isinstance(asset, fin_defs.CryptoCurrency):
return
assert fin_defs.Asset.from_polygon_id(asset.to_polygon_id()) == asset assert fin_defs.Asset.from_polygon_id(asset.to_polygon_id()) == asset