Drop `Position.expiry`, delegate to `.mkt: MktPair`
No point having duplicate data when we already stash the `expiry` on the mkt info type and can just read it (and cast to `datetime` obj). Further this fixes a regression caused by converting `._clears` to a list by adding a `._events: dict[str, Transaction]` which prevents double entering transactions based on checking the events table for the existing id.. Further add a sanity check that all events are popped (for now) after serializing the clearing table for the toml account file. In the longer run, ideally we don't have the separate sequences ._clears and ._events by choosing a better data structure (sorted unique set of mkt events) maybe a specially used `polars.DataFrame` (which we kind need eventually anyway)?account_tests
parent
87d6115954
commit
75f01e22d7
|
@ -103,9 +103,13 @@ class Position(Struct):
|
||||||
_clears: list[
|
_clears: list[
|
||||||
dict[str, Any], # transaction history summaries
|
dict[str, Any], # transaction history summaries
|
||||||
] = []
|
] = []
|
||||||
|
_events: dict[str, dict] = {}
|
||||||
first_clear_dt: datetime | None = None
|
first_clear_dt: datetime | None = None
|
||||||
|
|
||||||
expiry: datetime | None = None
|
@property
|
||||||
|
def expiry(self) -> datetime | None:
|
||||||
|
if exp := self.mkt.expiry:
|
||||||
|
return pendulum.parse(exp)
|
||||||
|
|
||||||
def __repr__(self) -> str:
|
def __repr__(self) -> str:
|
||||||
return pformat(self.to_dict())
|
return pformat(self.to_dict())
|
||||||
|
@ -121,7 +125,7 @@ class Position(Struct):
|
||||||
'''
|
'''
|
||||||
asdict = self.to_dict()
|
asdict = self.to_dict()
|
||||||
clears: list[dict] = asdict.pop('_clears')
|
clears: list[dict] = asdict.pop('_clears')
|
||||||
expiry = asdict.pop('expiry')
|
events: dict[str, Transaction] = asdict.pop('_events')
|
||||||
|
|
||||||
if self.split_ratio is None:
|
if self.split_ratio is None:
|
||||||
asdict.pop('split_ratio')
|
asdict.pop('split_ratio')
|
||||||
|
@ -148,10 +152,8 @@ class Position(Struct):
|
||||||
asdict['price_tick'] = mkt.price_tick
|
asdict['price_tick'] = mkt.price_tick
|
||||||
asdict['size_tick'] = mkt.size_tick
|
asdict['size_tick'] = mkt.size_tick
|
||||||
|
|
||||||
if self.expiry is None:
|
if exp := self.expiry:
|
||||||
asdict.pop('expiry', None)
|
asdict['expiry'] = exp.isoformat('T')
|
||||||
elif expiry:
|
|
||||||
asdict['expiry'] = str(expiry)
|
|
||||||
|
|
||||||
clears_table: tomlkit.Array = tomlkit.array()
|
clears_table: tomlkit.Array = tomlkit.array()
|
||||||
clears_table.multiline(
|
clears_table.multiline(
|
||||||
|
@ -165,8 +167,8 @@ class Position(Struct):
|
||||||
inline_table = tomlkit.inline_table()
|
inline_table = tomlkit.inline_table()
|
||||||
|
|
||||||
# serialize datetime to parsable `str`
|
# serialize datetime to parsable `str`
|
||||||
dtstr = inline_table['dt'] = entry['dt'].isoformat('T')
|
inline_table['dt'] = entry['dt'].isoformat('T')
|
||||||
assert 'Datetime' not in dtstr
|
# assert 'Datetime' not in inline_table['dt']
|
||||||
|
|
||||||
# insert optional clear fields in column order
|
# insert optional clear fields in column order
|
||||||
for k in ['ppu', 'accum_size']:
|
for k in ['ppu', 'accum_size']:
|
||||||
|
@ -177,11 +179,13 @@ class Position(Struct):
|
||||||
for k in ['price', 'size', 'cost']:
|
for k in ['price', 'size', 'cost']:
|
||||||
inline_table[k] = entry[k]
|
inline_table[k] = entry[k]
|
||||||
|
|
||||||
inline_table['tid'] = entry['tid']
|
tid: str = entry['tid']
|
||||||
|
events.pop(tid)
|
||||||
|
inline_table['tid'] = tid
|
||||||
clears_table.append(inline_table)
|
clears_table.append(inline_table)
|
||||||
|
|
||||||
|
assert not events
|
||||||
asdict['clears'] = clears_table
|
asdict['clears'] = clears_table
|
||||||
|
|
||||||
return fqme, asdict
|
return fqme, asdict
|
||||||
|
|
||||||
def ensure_state(self) -> None:
|
def ensure_state(self) -> None:
|
||||||
|
@ -502,6 +506,11 @@ class Position(Struct):
|
||||||
Inserts are always done in datetime sorted order.
|
Inserts are always done in datetime sorted order.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
tid: str = t.tid
|
||||||
|
if tid in self._events:
|
||||||
|
log.warning(f'{t} is already added?!')
|
||||||
|
return {}
|
||||||
|
|
||||||
clear: dict[str, float | str | int] = {
|
clear: dict[str, float | str | int] = {
|
||||||
'tid': t.tid,
|
'tid': t.tid,
|
||||||
'cost': t.cost,
|
'cost': t.cost,
|
||||||
|
@ -509,6 +518,7 @@ class Position(Struct):
|
||||||
'size': t.size,
|
'size': t.size,
|
||||||
'dt': t.dt
|
'dt': t.dt
|
||||||
}
|
}
|
||||||
|
self._events[tid] = t
|
||||||
|
|
||||||
insort(
|
insort(
|
||||||
self._clears,
|
self._clears,
|
||||||
|
@ -526,6 +536,8 @@ class Position(Struct):
|
||||||
self.size = clear['accum_size'] = self.calc_size()
|
self.size = clear['accum_size'] = self.calc_size()
|
||||||
self.ppu = clear['ppu'] = self.calc_ppu()
|
self.ppu = clear['ppu'] = self.calc_ppu()
|
||||||
|
|
||||||
|
assert len(self._events) == len(self._clears)
|
||||||
|
|
||||||
return clear
|
return clear
|
||||||
|
|
||||||
# TODO: once we have an `.events` table with diff
|
# TODO: once we have an `.events` table with diff
|
||||||
|
@ -580,7 +592,6 @@ class PpTable(Struct):
|
||||||
size=0.0,
|
size=0.0,
|
||||||
ppu=0.0,
|
ppu=0.0,
|
||||||
bs_mktid=bs_mktid,
|
bs_mktid=bs_mktid,
|
||||||
expiry=t.expiry,
|
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
# NOTE: if for some reason a "less resolved" mkt pair
|
# NOTE: if for some reason a "less resolved" mkt pair
|
||||||
|
@ -683,7 +694,7 @@ class PpTable(Struct):
|
||||||
|
|
||||||
# ONLY dict-serialize all active positions; those that are
|
# ONLY dict-serialize all active positions; those that are
|
||||||
# closed we don't store in the ``pps.toml``.
|
# closed we don't store in the ``pps.toml``.
|
||||||
to_toml_dict = {}
|
to_toml_dict: dict[str, Any] = {}
|
||||||
|
|
||||||
pos: Position
|
pos: Position
|
||||||
for bs_mktid, pos in active.items():
|
for bs_mktid, pos in active.items():
|
||||||
|
@ -919,10 +930,14 @@ def open_pps(
|
||||||
for clears_table in toml_clears_list:
|
for clears_table in toml_clears_list:
|
||||||
|
|
||||||
tid = clears_table.get('tid')
|
tid = clears_table.get('tid')
|
||||||
dtstr = clears_table['dt']
|
dt: tomlkit.items.DateTime | str = clears_table['dt']
|
||||||
dt = pendulum.parse(dtstr)
|
|
||||||
clears_table['dt'] = dt
|
|
||||||
|
|
||||||
|
# woa cool, `tomlkit` will actually load datetimes into
|
||||||
|
# native form B)
|
||||||
|
if isinstance(dt, str):
|
||||||
|
dt = pendulum.parse(dt)
|
||||||
|
|
||||||
|
clears_table['dt'] = dt
|
||||||
trans.append(Transaction(
|
trans.append(Transaction(
|
||||||
fqme=bs_mktid,
|
fqme=bs_mktid,
|
||||||
sym=mkt,
|
sym=mkt,
|
||||||
|
@ -944,8 +959,7 @@ def open_pps(
|
||||||
|
|
||||||
split_ratio = entry.get('split_ratio')
|
split_ratio = entry.get('split_ratio')
|
||||||
|
|
||||||
expiry = entry.get('expiry')
|
if expiry := entry.get('expiry'):
|
||||||
if expiry:
|
|
||||||
expiry = pendulum.parse(expiry)
|
expiry = pendulum.parse(expiry)
|
||||||
|
|
||||||
pp = pp_objs[bs_mktid] = Position(
|
pp = pp_objs[bs_mktid] = Position(
|
||||||
|
@ -953,7 +967,6 @@ def open_pps(
|
||||||
size=size,
|
size=size,
|
||||||
ppu=ppu,
|
ppu=ppu,
|
||||||
split_ratio=split_ratio,
|
split_ratio=split_ratio,
|
||||||
expiry=expiry,
|
|
||||||
bs_mktid=bs_mktid,
|
bs_mktid=bs_mktid,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue