Allow accounting (file) dir override via kwarg

For testing (and probably hacking) it's handy to be able to point
somewhere other the default user-config dir for a ledger or account file
to test offline processing apis from `.accounting` subsystems. For now
it's a private optional named-arg: `_fp: Path` and it's obviously passed
down into the `load_account()` config getter.

Note that in the non-paper account case `Account.update_from_ledger()`
will use the ledger's `.symcache` and `.iter_txns()` method to acquite
actual txn-structs to compute positions.
account_tests
Tyler Goodlet 2023-07-14 20:17:24 -04:00
parent 803f4a6354
commit b9fec091ca
1 changed files with 44 additions and 28 deletions

View File

@ -51,7 +51,7 @@ from ._mktinfo import (
) )
from .calc import ( from .calc import (
ppu, ppu,
iter_by_dt, # iter_by_dt,
) )
from .. import config from .. import config
from ..clearing._messages import ( from ..clearing._messages import (
@ -145,6 +145,7 @@ class Position(Struct):
def iter_by_type( def iter_by_type(
self, self,
etype: str, etype: str,
) -> Iterator[dict | Transaction]: ) -> Iterator[dict | Transaction]:
''' '''
Iterate the internally managed ``._events: dict`` table in Iterate the internally managed ``._events: dict`` table in
@ -152,12 +153,12 @@ class Position(Struct):
''' '''
# sort on the expected datetime field # sort on the expected datetime field
for event in iter_by_dt( # for event in iter_by_dt(
for event in sorted(
self._events.values(), self._events.values(),
key=lambda entry: key=lambda entry: entry.dt
getattr(entry, 'dt', None)
or entry.get('dt'),
): ):
# if event.etype == etype:
match event: match event:
case ( case (
{'etype': _etype} | {'etype': _etype} |
@ -465,7 +466,7 @@ class Account(Struct):
def update_from_ledger( def update_from_ledger(
self, self,
ledger: TransactionLedger, ledger: TransactionLedger | dict[str, Transaction],
cost_scalar: float = 2, cost_scalar: float = 2,
symcache: SymbologyCache | None = None, symcache: SymbologyCache | None = None,
@ -478,31 +479,34 @@ class Account(Struct):
''' '''
if ( if (
not isinstance(ledger, TransactionLedger) not isinstance(ledger, TransactionLedger)
and symcache is None
): ):
if symcache is None:
raise RuntimeError( raise RuntimeError(
'No ledger provided!\n' 'No ledger provided!\n'
'We can not determine the `MktPair`s without a symcache..\n' 'We can not determine the `MktPair`s without a symcache..\n'
'Please provide `symcache: SymbologyCache` when ' 'Please provide `symcache: SymbologyCache` when '
'processing NEW positions!' 'processing NEW positions!'
) )
itertxns = sorted(
ledger.values(),
key=lambda t: t.dt,
)
else:
itertxns = ledger.iter_txns()
symcache = ledger.symcache
pps = self.pps pps = self.pps
updated: dict[str, Position] = {} updated: dict[str, Position] = {}
# lifo update all pps from records, ensuring # lifo update all pps from records, ensuring
# we compute the PPU and size sorted in time! # we compute the PPU and size sorted in time!
for tid, txn in ledger.iter_txns(): for txn in itertxns:
# for t in sorted(
# trans.values(),
# key=lambda t: t.dt,
# ):
fqme: str = txn.fqme fqme: str = txn.fqme
bs_mktid: str = txn.bs_mktid bs_mktid: str = txn.bs_mktid
# template the mkt-info presuming a legacy market ticks # template the mkt-info presuming a legacy market ticks
# if no info exists in the transactions.. # if no info exists in the transactions..
mkt: MktPair = ledger._symcache.mktmaps[fqme] mkt: MktPair = symcache.mktmaps[fqme]
if not (pos := pps.get(bs_mktid)): if not (pos := pps.get(bs_mktid)):
@ -522,12 +526,13 @@ class Account(Struct):
# update clearing acnt! # update clearing acnt!
# NOTE: likely you'll see repeats of the same # NOTE: likely you'll see repeats of the same
# ``Transaction`` passed in here if/when you are restarting # ``Transaction`` passed in here if/when you are
# a ``brokerd.ib`` where the API will re-report trades from # restarting a ``brokerd.ib`` where the API will
# the current session, so we need to make sure we don't # re-report trades from the current session, so we need
# "double count" these in pp calculations; # to make sure we don't "double count" these in pp
# `Position.add_clear()` stores txs in a `dict[tid, # calculations; `Position.add_clear()` stores txs in
# tx]` which should always ensure this is true B) # a `._events: dict[tid, tx]` which should always
# ensure this is true!
pos.add_clear(txn) pos.add_clear(txn)
updated[txn.bs_mktid] = pos updated[txn.bs_mktid] = pos
@ -679,6 +684,8 @@ def load_account(
brokername: str, brokername: str,
acctid: str, acctid: str,
dirpath: Path | None = None,
) -> tuple[dict, Path]: ) -> tuple[dict, Path]:
''' '''
Load a accounting (with positions) file from Load a accounting (with positions) file from
@ -692,7 +699,7 @@ def load_account(
legacy_fn: str = f'pps.{brokername}.{acctid}.toml' legacy_fn: str = f'pps.{brokername}.{acctid}.toml'
fn: str = f'account.{brokername}.{acctid}.toml' fn: str = f'account.{brokername}.{acctid}.toml'
dirpath: Path = config._config_dir / 'accounting' dirpath: Path = dirpath or (config._config_dir / 'accounting')
if not dirpath.is_dir(): if not dirpath.is_dir():
dirpath.mkdir() dirpath.mkdir()
@ -743,6 +750,9 @@ def open_account(
acctid: str, acctid: str,
write_on_exit: bool = False, write_on_exit: bool = False,
# for testing or manual load from file
_fp: Path | None = None,
) -> Generator[Account, None, None]: ) -> Generator[Account, None, None]:
''' '''
Read out broker-specific position entries from Read out broker-specific position entries from
@ -751,7 +761,11 @@ def open_account(
''' '''
conf: dict conf: dict
conf_path: Path conf_path: Path
conf, conf_path = load_account(brokername, acctid) conf, conf_path = load_account(
brokername,
acctid,
dirpath=_fp,
)
if brokername in conf: if brokername in conf:
log.warning( log.warning(
@ -909,6 +923,7 @@ def load_account_from_ledger(
filter_by_ids: dict[str, list[str]] | None = None, filter_by_ids: dict[str, list[str]] | None = None,
ledger: TransactionLedger | None = None, ledger: TransactionLedger | None = None,
**kwargs,
) -> Account: ) -> Account:
''' '''
@ -919,9 +934,10 @@ def load_account_from_ledger(
''' '''
acnt: Account acnt: Account
with open_pps( with open_account(
brokername, brokername,
acctname, acctname,
**kwargs,
) as acnt: ) as acnt:
if ledger is not None: if ledger is not None:
acnt.update_from_ledger(ledger) acnt.update_from_ledger(ledger)