Implement updates and write to config: `pps.toml`

Begins the position tracking incremental update API which supports both
constructing a `pps.toml` both from trade ledgers as well diff-oriented
incremental update from an existing config assumed to be previously
generated from some prior ledger.

New set of routines includes:
- `_split_active()` a helper to split a position table into the active
  and closed positions (aka pps of size 0) for determining entry updates
  in the `pps.toml`.
- `update_pps_conf()` to maybe load a `pps.toml` and update it from
   an input trades ledger including necessary (de)serialization to and
   from `Position` object form(s).
- `load_pps_from_ledger()` a ledger parser-loader which constructs
  a table of pps strictly from the broker-account ledger data without
  any consideration for any existing pps file.

Each "entry" in `pps.toml` also contains a `fills: list` attr (name may
change) which references the set of trade records which make up its
state since the last net-zero position in the instrument.
lifo_pps_ib
Tyler Goodlet 2022-06-10 17:50:29 -04:00
parent 2a641ab8b4
commit dd05ed1371
1 changed files with 92 additions and 27 deletions

View File

@ -144,24 +144,28 @@ class Position(Struct):
def update_pps(
brokername: str,
ledger: dict[str, Union[str, float]],
pps: Optional[dict[str, TradeRecord]] = None
records: dict[str, TradeRecord],
) -> dict[str, TradeRecord]:
pps: Optional[dict[str, Position]] = None
) -> dict[str, Position]:
'''
Compile a set of positions from a trades ledger.
'''
brokermod = get_brokermod(brokername)
pps: dict[str, Position] = pps or {}
records = brokermod.norm_trade_records(ledger)
# lifo update all pps from records
for r in records:
key = r.symkey or r.fqsn
pp = pps.setdefault(
key,
Position(
Symbol.from_fqsn(r.fqsn, info={}),
Symbol.from_fqsn(
r.fqsn,
info={},
),
size=0.0,
avg_price=0.0,
)
@ -171,10 +175,30 @@ def update_pps(
pp.lifo_update(r.size, r.price)
pp.fills.append(r.tid)
assert len(set(pp.fills)) == len(pp.fills)
return pps
async def load_pps_from_ledger(
def _split_active(
pps: dict[str, Position],
) -> tuple[dict, dict]:
active = {}
closed = {}
for k, pp in pps.items():
fqsn = pp.symbol.front_fqsn()
asdict = pp.to_dict()
if pp.size == 0:
closed[fqsn] = asdict
else:
active[fqsn] = asdict
return active, closed
def load_pps_from_ledger(
brokername: str,
acctname: str,
@ -187,31 +211,72 @@ async def load_pps_from_ledger(
) as ledger:
pass # readonly
pps = update_pps(brokername, ledger)
active_pps = {}
for k, pp in pps.items():
if pp.size == 0:
continue
active_pps[pp.symbol.front_fqsn()] = pp.to_dict()
# pprint({pp.symbol.front_fqsn(): pp.to_dict() for k, pp in pps.items()})
from pprint import pprint
pprint(active_pps)
# pprint({pp.symbol.front_fqsn(): pp.to_dict() for k, pp in pps.items()})
brokermod = get_brokermod(brokername)
records = brokermod.norm_trade_records(ledger)
pps = update_pps(
brokername,
records,
)
return _split_active(pps)
def update_pps_conf(
trade_records: list[TradeRecord],
brokername: str,
acctid: str,
trade_records: Optional[list[TradeRecord]] = None,
):
conf, path = config.load('pp')
conf, path = config.load('pps')
brokersection = conf.setdefault(brokername, {})
entries = brokersection.setdefault(acctid, {})
if not entries:
# no pps entry yet for this broker/account
active, closed = load_pps_from_ledger(
brokername,
acctid,
)
elif trade_records:
# table for map-back to object form
pps = {}
# load ``pps.toml`` config entries back into object form.
for fqsn, entry in entries.items():
pps[fqsn] = Position(
Symbol.from_fqsn(fqsn, info={}),
size=entry['size'],
avg_price=entry['avg_price'],
)
pps = update_pps(
brokername,
trade_records,
pps=pps,
)
active, closed = _split_active(pps)
for fqsn in closed:
print(f'removing closed pp: {fqsn}')
entries.pop(fqsn, None)
for fqsn, pp_dict in active.items():
print(f'Updating active pp: {fqsn}')
# normalize to a simpler flat dict format
_ = pp_dict.pop('symbol')
entries[fqsn.rstrip(f'.{brokername}')] = pp_dict
config.write(
conf,
'pps',
encoder=config.toml.Encoder(preserve=True),
)
from pprint import pprint
pprint(conf)
if __name__ == '__main__':
import trio
trio.run(
load_pps_from_ledger, 'ib', 'algopaper',
)
update_pps_conf('ib', 'algopaper')