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 {
t.tid: t for t in self.iter_txns(symcache=symcache)
}
txns: dict[str, Transaction] = {}
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:
'''
@ -386,7 +393,7 @@ def open_trade_ledger(
account=account,
mod=mod,
symcache=symcache,
tx_sort=tx_sort,
tx_sort=getattr(mod, 'tx_sort', tx_sort),
)
try:
yield ledger

View File

@ -144,12 +144,11 @@ class Position(Struct):
# def bep() -> float:
# ...
def clearsdict(self) -> dict[str, dict]:
clears: dict[str, dict] = ppu(
def clearsitems(self) -> list[(str, dict)]:
return ppu(
self.iter_by_type('clear'),
as_ledger=True
)
return clears
def iter_by_type(
self,
@ -195,7 +194,7 @@ class Position(Struct):
cumsize: float = 0
clears_since_zero: list[dict] = []
for tid, cleardict in self.clearsdict().items():
for tid, cleardict in self.clearsitems():
cumsize = float(
# self.mkt.quantize(cumsize + cleardict['tx'].size
self.mkt.quantize(cleardict['cumsize'])
@ -295,6 +294,8 @@ class Position(Struct):
) -> None:
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
# summary transaction (for now) until we either pass THIS
@ -303,13 +304,16 @@ class Position(Struct):
t = Transaction(
fqme=mkt.fqme,
bs_mktid=mkt.bs_mktid,
tid='unknown',
size=msg['size'],
price=msg['avg_price'],
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!
dt=now(),
dt=now_dt,
)
self.add_clear(t)
@ -342,11 +346,11 @@ class Position(Struct):
Inserts are always done in datetime sorted order.
'''
added: bool = False
# added: bool = False
tid: str = t.tid
if tid in self._events:
log.warning(f'{t} is already added?!')
return added
# return added
# TODO: apparently this IS possible with a dict but not
# common and probably not that beneficial unless we're also
@ -390,9 +394,9 @@ class Position(Struct):
if self.expired():
return 0.
clears: list[dict] = list(self.clearsdict().values())
clears: list[(str, dict)] = self.clearsitems()
if clears:
return clears[-1]['cumsize']
return clears[-1][1]['cumsize']
else:
return 0.

View File

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