Compare commits
3 Commits
a12fcacf77
...
a322770205
Author | SHA1 | Date | |
---|---|---|---|
a322770205 | |||
6856343f4c | |||
b995d3ebe1 |
|
@ -27,7 +27,7 @@ import enforce_typing
|
||||||
|
|
||||||
from ._version import __version__
|
from ._version import __version__
|
||||||
|
|
||||||
__all__ = ['__version__']
|
__all__ = ['__version__', 'StockExchange', 'AssetAmount', 'Asset', 'ExchangeRateSample']
|
||||||
|
|
||||||
|
|
||||||
def parse_id_attr_key_value_pair(attr_datum: str) -> tuple[str, str | int] | None:
|
def parse_id_attr_key_value_pair(attr_datum: str) -> tuple[str, str | int] | None:
|
||||||
|
@ -113,8 +113,8 @@ class Asset:
|
||||||
return f'X:{self.ccxt_symbol}USD'
|
return f'X:{self.ccxt_symbol}USD'
|
||||||
|
|
||||||
# Else
|
# Else
|
||||||
msg = f'Unknown self type: {self}'
|
msg = f'Unknown type: {type(self).__name__}'
|
||||||
raise Exception(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_polygon_id(polygon_id: str) -> 'Asset':
|
def from_polygon_id(polygon_id: str) -> 'Asset':
|
||||||
|
@ -160,8 +160,8 @@ class Asset:
|
||||||
if isinstance(self, UnknownAsset):
|
if isinstance(self, UnknownAsset):
|
||||||
return 'unknown:XXX'
|
return 'unknown:XXX'
|
||||||
|
|
||||||
msg = f'Unsupported asset type: {self:?}'
|
msg = f'Unsupported asset type: {type(self).__name__}'
|
||||||
raise ValueError(msg)
|
raise TypeError(msg)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_string_id(asset_id: str) -> 'Asset':
|
def from_string_id(asset_id: str) -> 'Asset':
|
||||||
|
@ -184,9 +184,7 @@ class Asset:
|
||||||
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, **attrs)
|
return Stock(m.group(1), exchange, **attrs)
|
||||||
if prefix == 'currency':
|
if prefix in {'currency', 'fiat'}:
|
||||||
return FiatCurrency(ticker, **attrs)
|
|
||||||
if prefix == 'fiat':
|
|
||||||
return FiatCurrency(ticker, **attrs)
|
return FiatCurrency(ticker, **attrs)
|
||||||
if prefix == 'index':
|
if prefix == 'index':
|
||||||
return Index(ticker, **attrs)
|
return Index(ticker, **attrs)
|
||||||
|
@ -207,18 +205,24 @@ class Asset:
|
||||||
@enforce_typing.enforce_types
|
@enforce_typing.enforce_types
|
||||||
@dataclasses.dataclass(frozen=True)
|
@dataclasses.dataclass(frozen=True)
|
||||||
class UnknownAsset(Asset):
|
class UnknownAsset(Asset):
|
||||||
pass
|
"""An asset that does not exist."""
|
||||||
|
|
||||||
|
@abc.abstractmethod
|
||||||
|
def raw_short_name(self) -> str:
|
||||||
|
return '???'
|
||||||
|
|
||||||
|
|
||||||
@enforce_typing.enforce_types
|
@enforce_typing.enforce_types
|
||||||
@dataclasses.dataclass(frozen=True)
|
@dataclasses.dataclass(frozen=True)
|
||||||
class Currency(Asset):
|
class Currency(Asset):
|
||||||
pass
|
"""Either a Fiat or a Crypto Currency."""
|
||||||
|
|
||||||
|
|
||||||
@enforce_typing.enforce_types
|
@enforce_typing.enforce_types
|
||||||
@dataclasses.dataclass(frozen=True, eq=True, order=True)
|
@dataclasses.dataclass(frozen=True, eq=True, order=True)
|
||||||
class FiatCurrency(Currency):
|
class FiatCurrency(Currency):
|
||||||
|
"""Fiat Currency."""
|
||||||
|
|
||||||
iso_code: str
|
iso_code: str
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
|
@ -251,11 +255,13 @@ FiatCurrency.JPY = FiatCurrency('JPY')
|
||||||
@enforce_typing.enforce_types
|
@enforce_typing.enforce_types
|
||||||
@dataclasses.dataclass(frozen=True, eq=True)
|
@dataclasses.dataclass(frozen=True, eq=True)
|
||||||
class CryptoCurrency(Currency):
|
class CryptoCurrency(Currency):
|
||||||
|
"""Crypto Currency."""
|
||||||
|
|
||||||
ccxt_symbol: str
|
ccxt_symbol: str
|
||||||
coingecko_id: str | None = None
|
coingecko_id: str | None = None
|
||||||
|
|
||||||
def __post_init__(self) -> None:
|
def __post_init__(self) -> None:
|
||||||
if not re.match('^.*$', self.ccxt_symbol):
|
if not re.match(r'^\S.*\S$', self.ccxt_symbol):
|
||||||
msg = f'ccxt_symbol was not in correct format: {self.ccxt_symbol}'
|
msg = f'ccxt_symbol was not in correct format: {self.ccxt_symbol}'
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
@ -276,8 +282,8 @@ class Stock(Asset):
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_polygon_id(polygon_id: str, stock_exchange: StockExchange) -> 'Stock':
|
def from_polygon_id(polygon_id: str) -> 'Stock':
|
||||||
return Stock(polygon_id, stock_exchange)
|
return Stock(polygon_id, NYSE) # TODO: Add support for non-NYSE exchanges.
|
||||||
|
|
||||||
def raw_short_name(self) -> str:
|
def raw_short_name(self) -> str:
|
||||||
return self.ticker
|
return self.ticker
|
||||||
|
@ -470,9 +476,6 @@ if True:
|
||||||
def add_by_id(exchange: StockExchange, key: str):
|
def add_by_id(exchange: StockExchange, key: str):
|
||||||
exchange_id = getattr(exchange, key)
|
exchange_id = getattr(exchange, key)
|
||||||
if exchange_id is not None:
|
if exchange_id is not None:
|
||||||
if exchange_id in EXCHANGES_BY_IDS:
|
|
||||||
msg = f'Exchange {exchange_id} is already present'
|
|
||||||
raise ValueError(msg)
|
|
||||||
EXCHANGES_BY_IDS[exchange_id] = exchange
|
EXCHANGES_BY_IDS[exchange_id] = exchange
|
||||||
|
|
||||||
for exchange in EXCHANGES:
|
for exchange in EXCHANGES:
|
||||||
|
@ -518,23 +521,25 @@ class AssetAmount:
|
||||||
return self.human_readable_str()
|
return self.human_readable_str()
|
||||||
|
|
||||||
def human_readable_str(self) -> str:
|
def human_readable_str(self) -> str:
|
||||||
specificity = '2' if self.amount >= THREE_DECIMALS_UNDER_THIS_AMOUNT else '3'
|
abs_amount = abs(self.amount)
|
||||||
|
specificity = '2' if abs_amount >= THREE_DECIMALS_UNDER_THIS_AMOUNT else '3'
|
||||||
prefix = ASSET_PREFIX.get(self.asset, '')
|
prefix = ASSET_PREFIX.get(self.asset, '')
|
||||||
return ('{}{:.' + specificity + 'f} {}').format(
|
return ('{sign}{prefix}{amount:.' + specificity + 'f} {name}').format(
|
||||||
prefix,
|
sign='-' if self.amount < 0 else '',
|
||||||
self.amount,
|
prefix=prefix,
|
||||||
self.asset.raw_short_name(),
|
amount=abs_amount,
|
||||||
|
name=self.asset.raw_short_name(),
|
||||||
)
|
)
|
||||||
|
|
||||||
def __mul__(self, other: Decimal) -> 'AssetAmount':
|
def __mul__(self, other: Decimal) -> 'AssetAmount':
|
||||||
if not isinstance(other, Decimal):
|
if not isinstance(other, Decimal):
|
||||||
msg = f'other must be decimal, but was {type(other)}'
|
msg = f'other must be Decimal, but was {type(other).__name__}'
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
return AssetAmount(self.asset, self.amount * other)
|
return AssetAmount(self.asset, self.amount * other)
|
||||||
|
|
||||||
def __add__(self, other: 'AssetAmount') -> 'AssetAmount':
|
def __add__(self, other: 'AssetAmount') -> 'AssetAmount':
|
||||||
if not isinstance(other, AssetAmount):
|
if not isinstance(other, AssetAmount):
|
||||||
msg = f'other must be AssetAmount, but was {type(other)}'
|
msg = f'other must be AssetAmount, but was {type(other).__name__}'
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
if self.is_zero():
|
if self.is_zero():
|
||||||
return other
|
return other
|
||||||
|
@ -549,8 +554,9 @@ class AssetAmount:
|
||||||
return AssetAmount(self.asset, self.amount + other.amount)
|
return AssetAmount(self.asset, self.amount + other.amount)
|
||||||
|
|
||||||
def __sub__(self, other: 'AssetAmount') -> 'AssetAmount':
|
def __sub__(self, other: 'AssetAmount') -> 'AssetAmount':
|
||||||
|
# TODO: Implement using add?
|
||||||
if not isinstance(other, AssetAmount):
|
if not isinstance(other, AssetAmount):
|
||||||
msg = f'other must be AssetAmount, but was {type(other)}'
|
msg = f'other must be AssetAmount, but was {type(other).__name__}'
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
if self.is_zero():
|
if self.is_zero():
|
||||||
return -other
|
return -other
|
||||||
|
@ -576,7 +582,7 @@ class AssetAmount:
|
||||||
|
|
||||||
def cmp(self, other) -> Decimal:
|
def cmp(self, other) -> Decimal:
|
||||||
if not isinstance(other, AssetAmount):
|
if not isinstance(other, AssetAmount):
|
||||||
msg = f'other must be AssetAmount, but was {type(other)}'
|
msg = f'other must be AssetAmount, but was {type(other).__name__}'
|
||||||
raise TypeError(msg)
|
raise TypeError(msg)
|
||||||
if self.is_zero() or other.is_zero():
|
if self.is_zero() or other.is_zero():
|
||||||
return self.amount - other.amount
|
return self.amount - other.amount
|
||||||
|
|
|
@ -1,8 +1,122 @@
|
||||||
from decimal import Decimal
|
from decimal import Decimal
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
import fin_defs
|
import fin_defs
|
||||||
|
|
||||||
|
USD_0 = fin_defs.AssetAmount(fin_defs.USD, Decimal(0))
|
||||||
|
USD_10 = fin_defs.AssetAmount(fin_defs.USD, Decimal(10))
|
||||||
|
USD_11 = fin_defs.AssetAmount(fin_defs.USD, Decimal(11))
|
||||||
|
USD_20 = fin_defs.AssetAmount(fin_defs.USD, Decimal(20))
|
||||||
|
USD_21 = fin_defs.AssetAmount(fin_defs.USD, Decimal(21))
|
||||||
|
|
||||||
|
DKK_0 = fin_defs.AssetAmount(fin_defs.DKK, Decimal(0))
|
||||||
|
DKK_21 = fin_defs.AssetAmount(fin_defs.DKK, Decimal(21))
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_asset():
|
||||||
|
assert USD_10 + USD_11 == USD_21
|
||||||
|
assert USD_10 + USD_0 == USD_10
|
||||||
|
assert DKK_0 + USD_10 == USD_10
|
||||||
|
|
||||||
|
|
||||||
|
def test_sub_asset():
|
||||||
|
assert USD_21 - USD_10 == USD_11
|
||||||
|
assert USD_10 - USD_0 == USD_10
|
||||||
|
assert DKK_0 - USD_10 == -USD_10
|
||||||
|
|
||||||
|
|
||||||
|
def test_div_asset():
|
||||||
|
assert USD_20 / USD_10 == 2
|
||||||
|
assert USD_10 / USD_10 == 1
|
||||||
|
assert USD_10 / USD_20 == 0.5
|
||||||
|
|
||||||
|
|
||||||
|
def test_div_amount():
|
||||||
|
assert USD_20 / Decimal(2) == USD_10
|
||||||
|
|
||||||
|
|
||||||
|
def test_mult():
|
||||||
|
assert USD_10 * Decimal(2) == USD_20
|
||||||
|
|
||||||
|
|
||||||
|
def test_negate():
|
||||||
|
assert USD_20 + (-USD_10) == USD_10
|
||||||
|
|
||||||
|
|
||||||
|
def test_compare():
|
||||||
|
assert USD_10 < USD_20
|
||||||
|
assert USD_10 <= USD_20
|
||||||
|
assert not (USD_10 > USD_20)
|
||||||
|
assert not (USD_10 >= USD_20)
|
||||||
|
|
||||||
|
|
||||||
|
def test_compare_zero():
|
||||||
|
assert DKK_0 < USD_20
|
||||||
|
assert DKK_0 <= USD_20
|
||||||
|
assert not (DKK_0 > USD_20)
|
||||||
|
assert not (DKK_0 >= USD_20)
|
||||||
|
|
||||||
|
|
||||||
|
def test_is_zero():
|
||||||
|
assert USD_0.is_zero()
|
||||||
|
assert not USD_10.is_zero()
|
||||||
|
|
||||||
|
|
||||||
def test_str():
|
def test_str():
|
||||||
amount = fin_defs.AssetAmount(fin_defs.USD, Decimal(10))
|
assert str(USD_0) == '$0.000 USD'
|
||||||
assert str(amount) == '$10.00 USD'
|
assert str(USD_10) == '$10.00 USD'
|
||||||
|
assert str(USD_11) == '$11.00 USD'
|
||||||
|
assert str(-USD_10) == '-$10.00 USD'
|
||||||
|
|
||||||
|
|
||||||
|
def test_mul_wrong():
|
||||||
|
with pytest.raises(TypeError, match='other must be Decimal, but was AssetAmount'):
|
||||||
|
assert USD_20 * USD_10
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_wrong_type():
|
||||||
|
with pytest.raises(TypeError, match='other must be AssetAmount, but was Decimal'):
|
||||||
|
assert USD_20 + Decimal(1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_add_wrong_asset():
|
||||||
|
with pytest.raises(
|
||||||
|
ValueError,
|
||||||
|
match='AssetAmount must have same asset, but: fiat:USD != fiat:DKK',
|
||||||
|
):
|
||||||
|
assert USD_20 + DKK_21
|
||||||
|
|
||||||
|
|
||||||
|
def test_sub_wrong_type():
|
||||||
|
with pytest.raises(TypeError, match='other must be AssetAmount, but was Decimal'):
|
||||||
|
assert USD_20 - Decimal(1)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sub_wrong_asset():
|
||||||
|
with pytest.raises(
|
||||||
|
ValueError,
|
||||||
|
match='AssetAmount must have same asset, but: fiat:USD != fiat:DKK',
|
||||||
|
):
|
||||||
|
assert USD_20 - DKK_21
|
||||||
|
|
||||||
|
|
||||||
|
def test_cmp_wrong_type():
|
||||||
|
with pytest.raises(TypeError, match='other must be AssetAmount, but was Decimal'):
|
||||||
|
assert Decimal(1) > USD_20
|
||||||
|
|
||||||
|
|
||||||
|
def test_cmp_wrong_asset():
|
||||||
|
with pytest.raises(
|
||||||
|
ValueError,
|
||||||
|
match='AssetAmount must have same asset, but: fiat:USD != fiat:DKK',
|
||||||
|
):
|
||||||
|
assert USD_20 < DKK_21
|
||||||
|
|
||||||
|
|
||||||
|
def test_div_wrong_asset():
|
||||||
|
with pytest.raises(
|
||||||
|
ValueError,
|
||||||
|
match='AssetAmount must have same asset, but: fiat:USD != fiat:DKK',
|
||||||
|
):
|
||||||
|
assert USD_21 / DKK_21
|
||||||
|
|
10
test/test_currency.py
Normal file
10
test/test_currency.py
Normal file
|
@ -0,0 +1,10 @@
|
||||||
|
import fin_defs
|
||||||
|
|
||||||
|
|
||||||
|
def test_from_currency_symbol():
|
||||||
|
assert fin_defs.FiatCurrency.from_currency_symbol('$') == fin_defs.USD
|
||||||
|
assert fin_defs.FiatCurrency.from_currency_symbol('TEST') is None
|
||||||
|
|
||||||
|
|
||||||
|
def test_currency_symbol():
|
||||||
|
assert fin_defs.USD.to_currency_symbol() == '$'
|
|
@ -2,24 +2,28 @@ import pytest
|
||||||
|
|
||||||
import fin_defs
|
import fin_defs
|
||||||
|
|
||||||
VALID_TICKERS = ['TEST123', '123', 'TEST.EUR']
|
VALID_STOCK_TICKERS = ['TEST123', '123', 'TEST.EUR']
|
||||||
BAD_TICKERS = ['TEST:EUR', 'EUR:TEST']
|
BAD_STOCK_TICKERS = ['TEST:EUR', 'EUR:TEST']
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('ticker', VALID_TICKERS)
|
@pytest.mark.parametrize('ticker', VALID_STOCK_TICKERS)
|
||||||
def test_valid_tickers(ticker: str):
|
def test_valid_tickers(ticker: str):
|
||||||
fin_defs.Stock(ticker, exchange=fin_defs.EXCHANGES_BY_IDS['NYSE'])
|
asset = fin_defs.Stock(ticker, exchange=fin_defs.EXCHANGES_BY_IDS['NYSE'])
|
||||||
|
assert asset.names() is not None
|
||||||
|
assert asset.to_polygon_id() is not None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('ticker', BAD_TICKERS)
|
@pytest.mark.parametrize('ticker', BAD_STOCK_TICKERS)
|
||||||
def test_bad_tickers(ticker: str):
|
def test_bad_tickers(ticker: str):
|
||||||
with pytest.raises(ValueError, match='ticker was not in correct format:.*'):
|
with pytest.raises(ValueError, match='ticker was not in correct format:.*'):
|
||||||
fin_defs.Stock(ticker, exchange=fin_defs.EXCHANGES_BY_IDS['NYSE'])
|
fin_defs.Stock(ticker, exchange=fin_defs.EXCHANGES_BY_IDS['NYSE'])
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('ticker', BAD_TICKERS)
|
@pytest.mark.parametrize('ticker', BAD_STOCK_TICKERS)
|
||||||
def test_crypto_tickers(ticker):
|
def test_crypto_tickers(ticker):
|
||||||
fin_defs.CryptoCurrency(ticker, 'not-known')
|
asset = fin_defs.CryptoCurrency(ticker, 'not-known')
|
||||||
|
assert asset.names() is not None
|
||||||
|
assert asset.to_polygon_id().startswith('X:')
|
||||||
|
|
||||||
|
|
||||||
def test_str():
|
def test_str():
|
||||||
|
|
37
test/test_exchange_rate_sample.py
Normal file
37
test/test_exchange_rate_sample.py
Normal file
|
@ -0,0 +1,37 @@
|
||||||
|
import datetime
|
||||||
|
from decimal import Decimal
|
||||||
|
|
||||||
|
import fin_defs
|
||||||
|
|
||||||
|
NOW = datetime.datetime.now(tz=datetime.UTC)
|
||||||
|
THEN = NOW - datetime.timedelta(days=1)
|
||||||
|
|
||||||
|
SAMPLE_AVERAGE = fin_defs.ExchangeRateSample(
|
||||||
|
NOW,
|
||||||
|
(fin_defs.FiatCurrency.DKK, fin_defs.FiatCurrency.USD),
|
||||||
|
_average=Decimal(1),
|
||||||
|
)
|
||||||
|
|
||||||
|
SAMPLE_HIGH_LOW = fin_defs.ExchangeRateSample(
|
||||||
|
NOW,
|
||||||
|
(fin_defs.FiatCurrency.DKK, fin_defs.FiatCurrency.USD),
|
||||||
|
high=Decimal(1),
|
||||||
|
low=Decimal('0.5'),
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_sample_average():
|
||||||
|
assert SAMPLE_AVERAGE.average == 1
|
||||||
|
assert SAMPLE_HIGH_LOW.average == Decimal('0.75')
|
||||||
|
|
||||||
|
|
||||||
|
def test_invert_sample():
|
||||||
|
inverted = SAMPLE_HIGH_LOW.invert_exchange_rate()
|
||||||
|
assert inverted.asset_pair == (fin_defs.FiatCurrency.USD, fin_defs.FiatCurrency.DKK)
|
||||||
|
assert inverted.high == 2
|
||||||
|
assert inverted.low == 1
|
||||||
|
assert inverted.average == 1.5
|
||||||
|
|
||||||
|
|
||||||
|
def test_replace_timestamp():
|
||||||
|
assert SAMPLE_HIGH_LOW.replace_timestamp(THEN).timestamp == THEN
|
|
@ -2,17 +2,38 @@ import pytest
|
||||||
|
|
||||||
import fin_defs
|
import fin_defs
|
||||||
|
|
||||||
|
NVO_ID = 'stock:NVO.NYSE{nordnet_id=16256554}'
|
||||||
|
NVO = fin_defs.Asset.from_string_id(NVO_ID)
|
||||||
|
|
||||||
VALID_IDS = [
|
VALID_IDS = [
|
||||||
'stock:NVO.NYSE',
|
'stock:NVO.NYSE',
|
||||||
'stock:NVO.NYSE{nordnet_id=16256554}',
|
NVO_ID,
|
||||||
'currency:USD',
|
'currency:USD',
|
||||||
'fiat:USD',
|
'fiat:USD',
|
||||||
'index:NDX',
|
'index:NDX',
|
||||||
'crypto:BTC',
|
'crypto:BTC',
|
||||||
'crypto:BTC{coingecko_id=bitcoin}',
|
'crypto:BTC{coingecko_id=bitcoin}',
|
||||||
|
'crypto:MPC',
|
||||||
|
'crypto:LOLCOIN',
|
||||||
'commodity:ALUMINUM',
|
'commodity:ALUMINUM',
|
||||||
|
'unknown:XXX',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
ASSETS = list(fin_defs.WELL_KNOWN_SYMBOLS.values()) + [
|
||||||
|
fin_defs.Asset.from_string_id(vid) for vid in VALID_IDS
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
ASSETS_POLYGON_PRESERVES_FULL_ID = frozenset(
|
||||||
|
a
|
||||||
|
for a in ASSETS
|
||||||
|
if not isinstance(
|
||||||
|
a,
|
||||||
|
fin_defs.CryptoCurrency | fin_defs.Commodity | fin_defs.UnknownAsset,
|
||||||
|
)
|
||||||
|
) - frozenset([NVO])
|
||||||
|
|
||||||
INVALID_IDS = [
|
INVALID_IDS = [
|
||||||
'NVO',
|
'NVO',
|
||||||
'NVO.NYSE',
|
'NVO.NYSE',
|
||||||
|
@ -20,6 +41,11 @@ INVALID_IDS = [
|
||||||
'fiat:TEST:TEST',
|
'fiat:TEST:TEST',
|
||||||
'index:TEST:TEST',
|
'index:TEST:TEST',
|
||||||
'commodity:TEST:TEST',
|
'commodity:TEST:TEST',
|
||||||
|
'crypto: ',
|
||||||
|
'!!!!',
|
||||||
|
'::::',
|
||||||
|
'',
|
||||||
|
' ',
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -36,15 +62,15 @@ def test_parse_attr():
|
||||||
|
|
||||||
|
|
||||||
def test_from_nordnet():
|
def test_from_nordnet():
|
||||||
derp = fin_defs.Asset.from_string_id('stock:NVO.NYSE{nordnet_id=16256554}')
|
asset = NVO
|
||||||
assert isinstance(derp, fin_defs.Stock)
|
assert isinstance(asset, fin_defs.Stock)
|
||||||
assert derp.ticker == 'NVO'
|
assert asset.ticker == 'NVO'
|
||||||
assert derp.nordnet_id == 16256554
|
assert asset.nordnet_id == 16256554
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('asset_id', VALID_IDS)
|
@pytest.mark.parametrize('asset', ASSETS)
|
||||||
def test_from_string_id_shortcut(asset_id: str):
|
def test_raw_short_name(asset: fin_defs.Asset):
|
||||||
assert fin_defs.Asset.from_string_id(asset_id)
|
assert asset.raw_short_name() is not None
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('asset_id', VALID_IDS)
|
@pytest.mark.parametrize('asset_id', VALID_IDS)
|
||||||
|
@ -58,18 +84,28 @@ def test_from_string_id_invalid(asset_id: str):
|
||||||
fin_defs.Asset.from_string_id(asset_id)
|
fin_defs.Asset.from_string_id(asset_id)
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('asset', fin_defs.WELL_KNOWN_SYMBOLS.values())
|
@pytest.mark.parametrize('asset', ASSETS)
|
||||||
def test_to_from_string_id_shortcut(asset: fin_defs.Asset):
|
|
||||||
assert fin_defs.Asset.from_string_id(asset.to_string_id()) == asset
|
|
||||||
|
|
||||||
|
|
||||||
@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()) == asset
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize('asset', fin_defs.WELL_KNOWN_SYMBOLS.values())
|
@pytest.mark.parametrize('asset', ASSETS)
|
||||||
def test_to_from_polygon_id(asset: fin_defs.Asset):
|
def test_to_from_polygon_id_works(asset: fin_defs.Asset):
|
||||||
if isinstance(asset, fin_defs.CryptoCurrency):
|
if isinstance(asset, fin_defs.Commodity | fin_defs.UnknownAsset):
|
||||||
return
|
return
|
||||||
|
assert fin_defs.Asset.from_polygon_id(asset.to_polygon_id()) is not None
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize('asset', ASSETS_POLYGON_PRESERVES_FULL_ID)
|
||||||
|
def test_to_from_polygon_id_preserves_id(asset: fin_defs.Asset):
|
||||||
assert fin_defs.Asset.from_polygon_id(asset.to_polygon_id()) == asset
|
assert fin_defs.Asset.from_polygon_id(asset.to_polygon_id()) == asset
|
||||||
|
|
||||||
|
|
||||||
|
def test_to_polygon_id_fail_for_commodity():
|
||||||
|
with pytest.raises(TypeError, match='Unknown type: Commodity'):
|
||||||
|
assert fin_defs.Commodity.CORN.to_polygon_id()
|
||||||
|
|
||||||
|
|
||||||
|
def test_to_string_id_wrong_type():
|
||||||
|
with pytest.raises(TypeError, match='Unsupported asset type: int'):
|
||||||
|
assert fin_defs.Asset.to_string_id(1)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user