Change `Position.clearsdict()` -> `.clearsitems()`

Since apparently rendering to dict from a sorted generator func clearly
doesn't preserve the order when using a `dict`-comprehension.. Further,
there's really no reason to strictly return a `dict`. Adjust
`.calc.ppu()` to make the return value instead a `list[tuple[str,
dict]]`; this results in the current df cumsum values matching the
original impl and the existing `binance.paper` unit tests now passing XD

Other details that fix a variety of nonsense..
- adjust all `.clearsitems()` consumers to the new list output.
- use `str(pendulum.now())` in `Position.from_msg()` since adding
  multiples with an `unknown` str will obviously discard them, facepalm.
- fix `.calc.ppu()` to NOT short circuit when `accum_size` is 0; it's
  been causing all sorts of incorrect size outputs in the clearing
  table.. lel, this is what fixed the unit test!
account_tests
Tyler Goodlet 2023-07-18 21:00:19 -04:00
parent fe78277948
commit 8a10cbf6ab
3 changed files with 40 additions and 34 deletions

View File

@ -211,9 +211,16 @@ class TransactionLedger(UserDict):
Return entire output from ``.iter_txns()`` in a ``dict``. Return entire output from ``.iter_txns()`` in a ``dict``.
''' '''
return { txns: dict[str, Transaction] = {}
t.tid: t for t in self.iter_txns(symcache=symcache) for t in self.iter_txns(symcache=symcache):
}
if not t:
log.warning(f'{self.mod.name}:{self.account} TXN is -> {t}')
continue
txns[t.tid] = t
return txns
def write_config(self) -> None: def write_config(self) -> None:
''' '''
@ -386,7 +393,7 @@ def open_trade_ledger(
account=account, account=account,
mod=mod, mod=mod,
symcache=symcache, symcache=symcache,
tx_sort=tx_sort, tx_sort=getattr(mod, 'tx_sort', tx_sort),
) )
try: try:
yield ledger yield ledger

View File

@ -144,12 +144,11 @@ class Position(Struct):
# def bep() -> float: # def bep() -> float:
# ... # ...
def clearsdict(self) -> dict[str, dict]: def clearsitems(self) -> list[(str, dict)]:
clears: dict[str, dict] = ppu( return ppu(
self.iter_by_type('clear'), self.iter_by_type('clear'),
as_ledger=True as_ledger=True
) )
return clears
def iter_by_type( def iter_by_type(
self, self,
@ -195,7 +194,7 @@ class Position(Struct):
cumsize: float = 0 cumsize: float = 0
clears_since_zero: list[dict] = [] clears_since_zero: list[dict] = []
for tid, cleardict in self.clearsdict().items(): for tid, cleardict in self.clearsitems():
cumsize = float( cumsize = float(
# self.mkt.quantize(cumsize + cleardict['tx'].size # self.mkt.quantize(cumsize + cleardict['tx'].size
self.mkt.quantize(cleardict['cumsize']) self.mkt.quantize(cleardict['cumsize'])
@ -295,6 +294,8 @@ class Position(Struct):
) -> None: ) -> None:
mkt: MktPair = self.mkt mkt: MktPair = self.mkt
now_dt: pendulum.DateTime = now()
now_str: str = str(now_dt)
# NOTE WARNING XXX: we summarize the pos with a single # NOTE WARNING XXX: we summarize the pos with a single
# summary transaction (for now) until we either pass THIS # summary transaction (for now) until we either pass THIS
@ -303,13 +304,16 @@ class Position(Struct):
t = Transaction( t = Transaction(
fqme=mkt.fqme, fqme=mkt.fqme,
bs_mktid=mkt.bs_mktid, bs_mktid=mkt.bs_mktid,
tid='unknown',
size=msg['size'], size=msg['size'],
price=msg['avg_price'], price=msg['avg_price'],
cost=0, cost=0,
# NOTE: special provisions required!
# - tid needs to be unique or this txn will be ignored!!
tid=now_str,
# TODO: also figure out how to avoid this! # TODO: also figure out how to avoid this!
dt=now(), dt=now_dt,
) )
self.add_clear(t) self.add_clear(t)
@ -342,11 +346,11 @@ class Position(Struct):
Inserts are always done in datetime sorted order. Inserts are always done in datetime sorted order.
''' '''
added: bool = False # added: bool = False
tid: str = t.tid tid: str = t.tid
if tid in self._events: if tid in self._events:
log.warning(f'{t} is already added?!') log.warning(f'{t} is already added?!')
return added # return added
# TODO: apparently this IS possible with a dict but not # TODO: apparently this IS possible with a dict but not
# common and probably not that beneficial unless we're also # common and probably not that beneficial unless we're also
@ -390,9 +394,9 @@ class Position(Struct):
if self.expired(): if self.expired():
return 0. return 0.
clears: list[dict] = list(self.clearsdict().values()) clears: list[(str, dict)] = self.clearsitems()
if clears: if clears:
return clears[-1]['cumsize'] return clears[-1][1]['cumsize']
else: else:
return 0. return 0.

View File

@ -43,6 +43,7 @@ if TYPE_CHECKING:
TransactionLedger, TransactionLedger,
) )
def ppu( def ppu(
clears: Iterator[Transaction], clears: Iterator[Transaction],
@ -56,7 +57,7 @@ def ppu(
# new position fields inserted alongside each entry. # new position fields inserted alongside each entry.
as_ledger: bool = False, as_ledger: bool = False,
) -> float: ) -> float | list[(str, dict)]:
''' '''
Compute the "price-per-unit" price for the given non-zero sized Compute the "price-per-unit" price for the given non-zero sized
rolling position. rolling position.
@ -86,7 +87,8 @@ def ppu(
''' '''
asize_h: list[float] = [] # historical accumulative size asize_h: list[float] = [] # historical accumulative size
ppu_h: list[float] = [] # historical price-per-unit ppu_h: list[float] = [] # historical price-per-unit
ledger: dict[str, dict] = {} # ledger: dict[str, dict] = {}
ledger: list[dict] = []
t: Transaction t: Transaction
for t in clears: for t in clears:
@ -95,16 +97,10 @@ def ppu(
is_clear: bool = not isinstance(clear_price, str) is_clear: bool = not isinstance(clear_price, str)
last_accum_size = asize_h[-1] if asize_h else 0 last_accum_size = asize_h[-1] if asize_h else 0
accum_size = last_accum_size + clear_size accum_size: float = last_accum_size + clear_size
accum_sign = copysign(1, accum_size) accum_sign = copysign(1, accum_size)
sign_change: bool = False sign_change: bool = False
if accum_size == 0:
ppu_h.append(0)
asize_h.append(0)
continue
# on transfers we normally write some non-valid # on transfers we normally write some non-valid
# price since withdrawal to another account/wallet # price since withdrawal to another account/wallet
# has nothing to do with inter-asset-market prices. # has nothing to do with inter-asset-market prices.
@ -170,9 +166,6 @@ def ppu(
else: else:
ppu = cost_basis / abs_new_size ppu = cost_basis / abs_new_size
# ppu_h.append(ppu)
# asize_h.append(accum_size)
else: else:
# TODO: for PPU we should probably handle txs out # TODO: for PPU we should probably handle txs out
# (aka withdrawals) similarly by simply not having # (aka withdrawals) similarly by simply not having
@ -185,8 +178,6 @@ def ppu(
# need to be updated since the ppu remains constant # need to be updated since the ppu remains constant
# and gets weighted by the new size. # and gets weighted by the new size.
ppu: float = ppu_h[-1] # set to previous value ppu: float = ppu_h[-1] # set to previous value
# ppu_h.append(ppu_h[-1])
# asize_h.append(accum_size)
# extend with new rolling metric for this step # extend with new rolling metric for this step
ppu_h.append(ppu) ppu_h.append(ppu)
@ -194,13 +185,17 @@ def ppu(
# ledger[t.tid] = { # ledger[t.tid] = {
# 'txn': t, # 'txn': t,
ledger[t.tid] = t.to_dict() | { # ledger[t.tid] = t.to_dict() | {
ledger.append((
t.tid,
t.to_dict() | {
'ppu': ppu, 'ppu': ppu,
'cumsize': accum_size, 'cumsize': accum_size,
'sign_change': sign_change, 'sign_change': sign_change,
# TODO: cum_pnl, bep # TODO: cum_pnl, bep
} }
))
final_ppu = ppu_h[-1] if ppu_h else 0 final_ppu = ppu_h[-1] if ppu_h else 0
# TODO: once we have etypes in all ledger entries.. # TODO: once we have etypes in all ledger entries..