diff --git a/piker/brokers/ib/broker.py b/piker/brokers/ib/broker.py index 263e9868..c032870f 100644 --- a/piker/brokers/ib/broker.py +++ b/piker/brokers/ib/broker.py @@ -312,9 +312,14 @@ async def trades_dialogue( accounts.add(account) pp_msgs = {} + + # process pp value reported from ib's system. we only use these + # to cross-check sizing since average pricing on their end uses + # the so called (bs) "FIFO" style which more or less results in + # a price that's not useful for traders who want to not lose + # money.. xb for client in aioclients.values(): for pos in client.positions(): - msg = pack_position(pos) msg.account = accounts_def.inverse[msg.account] pp_msgs[msg.symbol] = msg @@ -322,8 +327,14 @@ async def trades_dialogue( assert msg.account in accounts, ( f'Position for unknown account: {msg.account}') + # built-out piker pps from trade ledger, underneath using + # LIFO style breakeven pricing calcs. trades_by_account: dict = {} conf = get_config() + + # retreive new trade executions from the last session + # and/or day's worth of trading and convert into trade + # records suitable for a local ledger file. for proxy in proxies.values(): trade_entries = await proxy.trades() records = trades_to_records( @@ -332,18 +343,27 @@ async def trades_dialogue( ) trades_by_account.update(records) + # write recent session's trades to the user's (local) ledger + # file. for acctid, trades_by_id in trades_by_account.items(): with config.open_trade_ledger('ib', acctid) as ledger: ledger.update(trades_by_id) + # (incrementally) update the user's pps in mem and + # in the `pps.toml`. records = norm_trade_records(trades_by_id) active = update_pps_conf('ib', acctid, records) + + # relay re-formatted pps as msgs to the ems. for fqsn, pp in active.items(): ibppmsg = pp_msgs[fqsn.rstrip('.ib')] msg = BrokerdPosition( broker='ib', # account=acctid + '.ib', + # XXX: ok so this is annoying, we're relaying + # an account name with the backend suffix prefixed + # but when reading accounts from ledgers account=ibppmsg.account, # XXX: the `.ib` is stripped..? symbol=ibppmsg.symbol, @@ -614,6 +634,7 @@ def norm_trade_records( def trades_to_records( + accounts: bidict, trade_entries: list[object], source_type: str = 'api', diff --git a/piker/pp.py b/piker/pp.py index b300e7d8..784192c8 100644 --- a/piker/pp.py +++ b/piker/pp.py @@ -21,7 +21,6 @@ that doesn't try to cuk most humans who prefer to not lose their moneys.. ''' from typing import ( - Any, Optional, Union, ) @@ -153,14 +152,15 @@ def update_pps( Compile a set of positions from a trades ledger. ''' - pps: dict[str, Position] = pps or {} # lifo update all pps from records for r in records: - key = r.fqsn or r.symkey + pp = pps.setdefault( - key, + r.fqsn or r.symkey, + + # if no existing pp, allocate fresh one. Position( Symbol.from_fqsn( r.fqsn, @@ -173,6 +173,11 @@ def update_pps( # don't do updates for ledger records we already have # included in the current pps state. if r.tid in pp.fills: + # NOTE: likely you'll see repeats of the same + # ``TradeRecord`` passed in here if/when you are restarting + # a ``brokerd.ib`` where the API will re-report trades from + # the current session, so we need to make sure we don't + # "double count" these in pp calculations. continue # lifo style average price calc @@ -187,7 +192,16 @@ def _split_active( pps: dict[str, Position], ) -> tuple[dict, dict]: + ''' + Split pps into those that are "active" (non-zero size) and "closed" + (zero size) and return in 2 dicts. + Returning the "closed" set is important for updating the pps state + in any ``pps.toml`` such that we remove entries which are no longer + part of any "VaR" set (well presumably, except of course your liquidity + asset could be full of "risk" XD ). + + ''' active = {} closed = {} @@ -207,8 +221,14 @@ def load_pps_from_ledger( brokername: str, acctname: str, -) -> dict[str, Any]: +) -> tuple[dict, dict]: + ''' + Open a ledger file by broker name and account and read in and + process any trade records into our normalized ``TradeRecord`` + form and then pass these into the position processing routine + and deliver the two dict-sets of the active and closed pps. + ''' with config.open_trade_ledger( brokername, acctname, @@ -217,10 +237,8 @@ def load_pps_from_ledger( brokermod = get_brokermod(brokername) records = brokermod.norm_trade_records(ledger) - pps = update_pps( - brokername, - records, - ) + pps = update_pps(brokername, records) + return _split_active(pps) @@ -282,14 +300,12 @@ def update_pps_conf( config.write( conf, 'pps', + # TODO: make nested tables and/or inline tables work? # encoder=config.toml.Encoder(preserve=True), ) return active - # from pprint import pprint - # pprint(conf) - if __name__ == '__main__': update_pps_conf('ib', 'algopaper')