Finally, support full `MktPair` + `Asset` msgs

Previously we weren't necessarily serializing mkt pairs (for IPC msging)
entirely bc the assets `.src/.dst` were being sent just by their
str-names. This now properly supports fully serializing `Asset`s as
`dict`-msgs such that use of `MktPair.to_dict()` can be transmitted over
`tractor.MsgStream`s and deserialized entirely back to struct from on
the receiver end.

Deats:
- implement `Asset.to_dict()` and `.from_msg()`
- adjust `MktPair.to_dict()` and `.from_msg()` to use these methods.
  - drop all the hacky "if .src/.dst is str" handling.
- add better `MktPair.from_fqme()` input handling for expiry and venue;
  ensure that either can be extracted from passed fqme *and* if so they
  are also popped from any duplicate passed in `**kwargs**`.
account_tests
Tyler Goodlet 2023-07-07 13:31:43 -04:00
parent c8c28df62f
commit 309b91676d
1 changed files with 56 additions and 26 deletions

View File

@ -130,8 +130,26 @@ class Asset(Struct, frozen=True):
# should not be explicitly required in our generic API.
info: dict | None = None
# TODO?
# _to_dict_skip = {'info'}
# `None` is not toml-compat so drop info
# if no extra data added..
def to_dict(self) -> dict:
dct = super().to_dict()
if (info := dct.pop('info', None)):
dct['info'] = info
assert dct['tx_tick']
return dct
@classmethod
def from_msg(
cls,
msg: dict[str, Any],
) -> Asset:
return Asset(
tx_tick=Decimal(str(msg.pop('tx_tick'))),
info=msg.pop('info', None),
**msg,
)
def __str__(self) -> str:
return self.name
@ -288,6 +306,8 @@ class MktPair(Struct, frozen=True):
# strike price, call or put, swap type, exercise model, etc.
contract_info: list[str] | None = None
# TODO: rename to sectype since all of these can
# be considered "securities"?
_atype: str = ''
# allow explicit disable of the src part of the market
@ -298,6 +318,18 @@ class MktPair(Struct, frozen=True):
def __str__(self) -> str:
return self.fqme
def to_dict(self) -> dict:
d = super().to_dict()
d['src'] = self.src.to_dict()
d['dst'] = self.dst.to_dict()
if self.contract_info is None:
d.pop('contract_info')
# d.pop('_fqme_without_src')
return d
@classmethod
def from_msg(
cls,
@ -309,35 +341,20 @@ class MktPair(Struct, frozen=True):
'''
dst_asset_msg = msg.pop('dst')
dst = Asset.from_msg(dst_asset_msg) # .copy()
src_asset_msg = msg.pop('src')
src = Asset.from_msg(src_asset_msg) # .copy()
if isinstance(dst_asset_msg, str):
src: str = str(src_asset_msg)
assert isinstance(src, str)
return cls.from_fqme(
dst_asset_msg,
src=src,
**msg,
)
else:
# NOTE: we call `.copy()` here to ensure
# type casting!
dst = Asset(**dst_asset_msg).copy()
if not isinstance(src_asset_msg, str):
src = Asset(**src_asset_msg).copy()
else:
src = str(src_asset_msg)
return cls(
dst=dst,
src=src,
**msg,
# XXX NOTE: ``msgspec`` can encode `Decimal`
# but it doesn't decide to it by default since
# we aren't spec-cing these msgs as structs, SO
# we have to ensure we do a struct type case (which `.copy()`
# does) to ensure we get the right type!
return cls(
dst=dst,
src=src,
**msg,
).copy()
@property
@ -365,7 +382,20 @@ class MktPair(Struct, frozen=True):
):
_fqme = f'{fqme}.{broker}'
broker, mkt_ep_key, venue, suffix = unpack_fqme(_fqme)
broker, mkt_ep_key, venue, expiry = unpack_fqme(_fqme)
kven: str = kwargs.pop('venue', venue)
if venue:
assert venue == kven
else:
venue = kven
exp: str = kwargs.pop('expiry', expiry)
if expiry:
assert exp == expiry
else:
expiry = exp
dst: Asset = Asset.guess_from_mkt_ep_key(
mkt_ep_key,
atype=kwargs.get('_atype'),
@ -384,7 +414,7 @@ class MktPair(Struct, frozen=True):
venue=venue,
# XXX NOTE: we presume this token
# if the expiry for now!
expiry=suffix,
expiry=expiry,
price_tick=price_tick,
size_tick=size_tick,