Skip to content

Commit 05af2b3

Browse files
committed
Rework .accounting.Position calcs to prep for polars
We're probably going to move to implementing all accounting using `polars.DataFrame` and friends and thus this rejig preps for a much more "stateless" implementation of our `Position` type and its internal pos-accounting metrics: `ppu` and `cumsize`. Summary: - wrt to `._pos.Position`: - rename `.size`/`.accum_size` to `.cumsize` to be more in line with `polars.DataFrame.cumsum()`. - make `Position.expiry` delegate to the underlying `.mkt: MktPair` handling (hopefully) all edge cases.. - change over to a new `._events: dict[str, Transaction]` in prep for #510 (and friends) and enforce a new `Transaction.etype: str` which is by default `clear`. - add `.iter_by_type()` which iterates, filters and sorts the entries in `._events` from above. - add `Position.clearsdict()` which returns the dict-ified and datetime-sorted table which can more-or-less be stored in the toml account file. - add `.minimized_clears()` a new (and close) version of the old method which always grabs at least one clear before a position-side-polarity-change. - mask-drop `.ensure_state()` since there is no more `.size`/`.price` state vars (per say) as we always re-calc the ppu and cumsize from the clears records on every read. - `.add_clear` no longer does bisec insorting since all sorting is done on position properties *reads*. - move the PPU (price per unit) calculator to a new `.accounting.calcs` as well as add in the `iter_by_dt()` clearing transaction sorted iterator. - also make some fixes to this to handle both lists of `Transaction` as well as `dict`s as before. - start rename of `PpTable` -> `Account` and make a note about adding a `.balances` table. - always `float()` the transaction size/price values since it seems if they get processed as `tomlkit.Integer` there's some suuper weird double negative on read-then-write to the clears table? - something like `cumsize = -1` -> `cumsize = --1` !?!? - make `load_pps_from_ledger()` work again but now includes some very very first draft `polars` df processing from a transaction ledger. - use this from the `accounting.cli.disect` subcmd which is also in *super early draft* mode ;) - obviously as mentioned in the `Position` section, add the new `.calcs` module with a `.ppu()` calculator func B)
1 parent 745c144 commit 05af2b3

File tree

6 files changed

+720
-520
lines changed

6 files changed

+720
-520
lines changed

piker/accounting/__init__.py

+3-18
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,10 @@
2121
'''
2222
from ..log import get_logger
2323

24-
from ._ledger import (
24+
from .calc import (
2525
iter_by_dt,
26+
)
27+
from ._ledger import (
2628
Transaction,
2729
TransactionLedger,
2830
open_trade_ledger,
@@ -100,20 +102,3 @@ def get_likely_pair(
100102
likely_dst = bs_mktid[:src_name_start]
101103
if likely_dst == dst:
102104
return bs_mktid
103-
104-
105-
if __name__ == '__main__':
106-
import sys
107-
from pprint import pformat
108-
109-
args = sys.argv
110-
assert len(args) > 1, 'Specifiy account(s) from `brokers.toml`'
111-
args = args[1:]
112-
for acctid in args:
113-
broker, name = acctid.split('.')
114-
trans, updated_pps = load_pps_from_ledger(broker, name)
115-
print(
116-
f'Processing transactions into pps for {broker}:{acctid}\n'
117-
f'{pformat(trans)}\n\n'
118-
f'{pformat(updated_pps)}'
119-
)

piker/accounting/_allocate.py

+3-5
Original file line numberDiff line numberDiff line change
@@ -118,9 +118,9 @@ def next_order_info(
118118
ld: int = mkt.size_tick_digits
119119

120120
size_unit = self.size_unit
121-
live_size = live_pp.size
121+
live_size = live_pp.cumsize
122122
abs_live_size = abs(live_size)
123-
abs_startup_size = abs(startup_pp.size)
123+
abs_startup_size = abs(startup_pp.cumsize)
124124

125125
u_per_slot, currency_per_slot = self.step_sizes()
126126

@@ -213,8 +213,6 @@ def next_order_info(
213213
slots_used = self.slots_used(
214214
Position(
215215
mkt=mkt,
216-
size=order_size,
217-
ppu=price,
218216
bs_mktid=mkt.bs_mktid,
219217
)
220218
)
@@ -241,7 +239,7 @@ def slots_used(
241239
Calc and return the number of slots used by this ``Position``.
242240
243241
'''
244-
abs_pp_size = abs(pp.size)
242+
abs_pp_size = abs(pp.cumsize)
245243

246244
if self.size_unit == 'currency':
247245
# live_currency_size = size or (abs_pp_size * pp.ppu)

piker/accounting/_ledger.py

+15-49
Original file line numberDiff line numberDiff line change
@@ -25,22 +25,22 @@
2525
from typing import (
2626
Any,
2727
Callable,
28-
Iterator,
29-
Union,
3028
Generator
3129
)
3230

3331
from pendulum import (
3432
datetime,
3533
DateTime,
36-
from_timestamp,
3734
parse,
3835
)
3936
import tomli_w # for fast ledger writing
4037

4138
from .. import config
4239
from ..data.types import Struct
4340
from ..log import get_logger
41+
from .calc import (
42+
iter_by_dt,
43+
)
4444
from ._mktinfo import (
4545
Symbol, # legacy
4646
MktPair,
@@ -56,13 +56,14 @@ class Transaction(Struct, frozen=True):
5656
# once we have that as a required field,
5757
# we don't really need the fqme any more..
5858
fqme: str
59-
60-
tid: Union[str, int] # unique transaction id
59+
tid: str | int # unique transaction id
6160
size: float
6261
price: float
6362
cost: float # commisions or other additional costs
6463
dt: datetime
6564

65+
etype: str = 'clear'
66+
6667
# TODO: we can drop this right since we
6768
# can instead expect the backend to provide this
6869
# via the `MktPair`?
@@ -159,9 +160,9 @@ def iter_trans(
159160
# and instead call it for each entry incrementally:
160161
# normer = mod.norm_trade_record(txdict)
161162

162-
# TODO: use tx_sort here yah?
163+
# datetime-sort and pack into txs
163164
for txdict in self.tx_sort(self.data.values()):
164-
# for tid, txdict in self.data.items():
165+
165166
# special field handling for datetimes
166167
# to ensure pendulum is used!
167168
tid: str = txdict['tid']
@@ -186,6 +187,7 @@ def iter_trans(
186187
# TODO: change to .sys!
187188
sym=mkt,
188189
expiry=parse(expiry) if expiry else None,
190+
etype='clear',
189191
)
190192
yield tid, tx
191193

@@ -208,62 +210,26 @@ def write_config(
208210
Render the self.data ledger dict to it's TOML file form.
209211
210212
'''
211-
cpy = self.data.copy()
212213
towrite: dict[str, Any] = {}
213-
for tid, trans in cpy.items():
214+
for tid, txdict in self.tx_sort(self.data.copy()):
214215

215-
# drop key for non-expiring assets
216-
txdict = towrite[tid] = self.data[tid]
216+
# write blank-str expiry for non-expiring assets
217217
if (
218218
'expiry' in txdict
219219
and txdict['expiry'] is None
220220
):
221-
txdict.pop('expiry')
221+
txdict['expiry'] = ''
222222

223223
# re-write old acro-key
224-
fqme = txdict.get('fqsn')
225-
if fqme:
224+
if fqme := txdict.get('fqsn'):
226225
txdict['fqme'] = fqme
227226

227+
towrite[tid] = txdict
228+
228229
with self.file_path.open(mode='wb') as fp:
229230
tomli_w.dump(towrite, fp)
230231

231232

232-
def iter_by_dt(
233-
records: dict[str, dict[str, Any]] | list[dict],
234-
235-
# NOTE: parsers are looked up in the insert order
236-
# so if you know that the record stats show some field
237-
# is more common then others, stick it at the top B)
238-
parsers: dict[tuple[str], Callable] = {
239-
'dt': None, # parity case
240-
'datetime': parse, # datetime-str
241-
'time': from_timestamp, # float epoch
242-
},
243-
key: Callable | None = None,
244-
245-
) -> Iterator[tuple[str, dict]]:
246-
'''
247-
Iterate entries of a ``records: dict`` table sorted by entry recorded
248-
datetime presumably set at the ``'dt'`` field in each entry.
249-
250-
'''
251-
def dyn_parse_to_dt(txdict: dict[str, Any]) -> DateTime:
252-
k, v, parser = next(
253-
(k, txdict[k], parsers[k]) for k in parsers if k in txdict
254-
)
255-
return parser(v) if parser else v
256-
257-
if isinstance(records, dict):
258-
records = records.values()
259-
260-
for entry in sorted(
261-
records,
262-
key=key or dyn_parse_to_dt,
263-
):
264-
yield entry
265-
266-
267233
def load_ledger(
268234
brokername: str,
269235
acctid: str,

0 commit comments

Comments
 (0)