Add `update_and_audit()` in prep for rt per-trade-event pp udpates

lifo_pps_ib
Tyler Goodlet 2022-06-15 11:56:49 -04:00
parent 7b2e8f1ba5
commit 3991d8f911
1 changed files with 102 additions and 72 deletions

View File

@ -267,8 +267,9 @@ async def recv_trade_updates(
async def update_ledger_from_api_trades( async def update_ledger_from_api_trades(
clients: list[Union[Client, MethodProxy]], trade_entries: dict[str, Any],
ib_pp_msgs: dict[int, BrokerdPosition], # conid -> msg ib_pp_msgs: dict[int, BrokerdPosition], # conid -> msg
client: Union[Client, MethodProxy],
) -> dict[str, Any]: ) -> dict[str, Any]:
@ -279,34 +280,34 @@ async def update_ledger_from_api_trades(
# retreive new trade executions from the last session # retreive new trade executions from the last session
# and/or day's worth of trading and convert into trade # and/or day's worth of trading and convert into trade
# records suitable for a local ledger file. # records suitable for a local ledger file.
trades_by_account: dict = {} # trades_by_account: dict = {}
for client in clients: # for client in clients:
trade_entries = await client.trades() # trade_entries = await client.trades()
# XXX; ERRGGG.. # XXX; ERRGGG..
# pack in the "primary/listing exchange" value from a # pack in the "primary/listing exchange" value from a
# contract lookup since it seems this isn't available by # contract lookup since it seems this isn't available by
# default from the `.fills()` method endpoint... # default from the `.fills()` method endpoint...
for entry in trade_entries: for entry in trade_entries:
condict = entry['contract'] condict = entry['contract']
conid = condict['conId'] conid = condict['conId']
pexch = condict['primaryExchange'] pexch = condict['primaryExchange']
if not pexch: if not pexch:
con = (await client.get_con(conid=conid))[0] con = (await client.get_con(conid=conid))[0]
pexch = con.primaryExchange pexch = con.primaryExchange
entry['listingExchange'] = pexch entry['listingExchange'] = pexch
records = trades_to_records( records = trades_to_records(
conf['accounts'].inverse, conf['accounts'].inverse,
trade_entries, trade_entries,
) )
trades_by_account.update(records) # trades_by_account.update(records)
# write recent session's trades to the user's (local) ledger file. # write recent session's trades to the user's (local) ledger file.
for acctid, trades_by_id in trades_by_account.items(): for acctid, trades_by_id in records.items():
with pp.open_trade_ledger('ib', acctid) as ledger: with pp.open_trade_ledger('ib', acctid) as ledger:
ledger.update(trades_by_id) ledger.update(trades_by_id)
@ -327,7 +328,76 @@ async def update_ledger_from_api_trades(
) )
r.fqsn = normed_msg.symbol r.fqsn = normed_msg.symbol
pp.update_pps_conf('ib', acctid, records) active = pp.update_pps_conf('ib', acctid, records)
return active
async def update_and_audit(
by_fqsn: dict[str, pp.Position],
cids2pps: dict[int, BrokerdPosition],
) -> list[BrokerdPosition]:
msgs: list[BrokerdPosition] = []
pps: dict[int, pp.Position] = {}
for fqsn, p in by_fqsn.items():
bsuid = p.bsuid
# build trade-session-actor local table
# of pps from unique symbol ids.
pps[bsuid] = p
# retreive equivalent ib reported position message
# for comparison/audit versus the piker equivalent
# breakeven pp calcs.
ibppmsg = cids2pps[bsuid]
msg = BrokerdPosition(
broker='ib',
# XXX: ok so this is annoying, we're relaying
# an account name with the backend suffix prefixed
# but when reading accounts from ledgers we don't
# need it and/or it's prefixed in the section
# table..
account=ibppmsg.account,
# XXX: the `.ib` is stripped..?
symbol=ibppmsg.symbol,
currency=ibppmsg.currency,
size=p.size,
avg_price=p.avg_price,
)
ibsize = ibppmsg.size
pikersize = msg.size
diff = pikersize - ibsize
# if ib reports a lesser pp it's not as bad since we can
# presume we're at least not more in the shit then we
# thought.
if diff:
raise ValueError(
f'POSITION MISMATCH ib <-> piker ledger:\n'
f'ib: {msg}\n'
f'piker: {ibppmsg}\n'
'YOU SHOULD FIGURE OUT WHY TF YOUR LEDGER IS OFF!?!?'
)
msg.size = ibsize
if ibppmsg.avg_price != msg.avg_price:
# TODO: make this a "propoganda" log level?
log.warning(
'The mega-cucks at IB want you to believe with their '
f'"FIFO" positioning for {msg.symbol}:\n'
f'"ib" mega-cucker avg price: {ibppmsg.avg_price}\n'
f'piker, LIFO breakeven PnL price: {msg.avg_price}'
)
msgs.append(msg)
return msgs
@tractor.context @tractor.context
@ -389,7 +459,6 @@ async def trades_dialogue(
# money.. xb # money.. xb
for client in aioclients.values(): for client in aioclients.values():
for pos in client.positions(): for pos in client.positions():
cid, msg = pack_position(pos) cid, msg = pack_position(pos)
acctid = msg.account = accounts_def.inverse[msg.account] acctid = msg.account = accounts_def.inverse[msg.account]
used_accounts.add(acctid) used_accounts.add(acctid)
@ -399,61 +468,21 @@ async def trades_dialogue(
# update trades ledgers for all accounts from # update trades ledgers for all accounts from
# connected api clients. # connected api clients.
await update_ledger_from_api_trades( for account, proxy in proxies.items():
proxies.values(), await update_ledger_from_api_trades(
cids2pps, # pass these in to map to correct fqsns.. await proxy.trades(),
) cids2pps, # pass these in to map to correct fqsns..
proxy,
)
# load all positions from `pps.toml`, cross check with ib's # load all positions from `pps.toml`, cross check with ib's
# positions data, and relay re-formatted pps as msgs to the ems. # positions data, and relay re-formatted pps as msgs to the ems.
for acctid, by_fqsn in pp.get_pps( for acctid, by_fqsn in pp.get_pps(
'ib', acctids=used_accounts, 'ib', acctids=used_accounts,
).items(): ).items():
for fqsn, posdict in by_fqsn.items():
ibppmsg = cids2pps[posdict['bsuid']]
msg = BrokerdPosition(
broker='ib',
# XXX: ok so this is annoying, we're relaying msgs = await update_and_audit(by_fqsn, cids2pps)
# an account name with the backend suffix prefixed all_positions.extend(msg.dict() for msg in msgs)
# but when reading accounts from ledgers we don't
# need it and/or it's prefixed in the section
# table..
account=ibppmsg.account,
# XXX: the `.ib` is stripped..?
symbol=ibppmsg.symbol,
currency=ibppmsg.currency,
size=posdict['size'],
avg_price=posdict['avg_price'],
)
print(msg)
ibsize = ibppmsg.size
pikersize = msg.size
diff = pikersize - ibsize
# if ib reports a lesser pp it's not as bad since we can
# presume we're at least not more in the shit then we
# thought.
if diff:
raise ValueError(
f'POSITION MISMATCH ib <-> piker ledger:\n'
f'ib: {ibsize}\n'
f'piker: {pikersize}\n'
'YOU SHOULD FIGURE OUT WHY TF YOUR LEDGER IS OFF!?!?'
)
msg.size = ibsize
if ibppmsg.avg_price != msg.avg_price:
# TODO: make this a "propoganda" log level?
log.warning(
'The mega-cucks at IB want you to believe with their '
f'"FIFO" positioning for {msg.symbol}:\n'
f'"ib" mega-cucker avg price: {ibppmsg.avg_price}\n'
f'piker, LIFO breakeven PnL price: {msg.avg_price}'
)
all_positions.append(msg.dict())
if not all_positions and cids2pps: if not all_positions and cids2pps:
raise RuntimeError( raise RuntimeError(
@ -621,6 +650,7 @@ async def deliver_trade_events(
continue continue
elif event_name == 'position': elif event_name == 'position':
cid, msg = pack_position(item) cid, msg = pack_position(item)
msg.account = accounts_def.inverse[msg.account] msg.account = accounts_def.inverse[msg.account]