From 97dd7e766aebe22c848e1ce9d910cecc7779aacc Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Tue, 18 Jun 2024 10:00:18 -0400 Subject: [PATCH] ib: more trade record edge case handling - timestamps came as `'date'`-keyed from 2022 and before but now are `'datetime'`.. - some symbols seem to have no commission field, so handle that.. - when no `'price'` field found return `None` from `norm_trade()`. - add a warn log on mid-fill commission updates. --- piker/brokers/ib/broker.py | 9 ++++- piker/brokers/ib/ledger.py | 74 +++++++++++++++++++++++++++----------- 2 files changed, 62 insertions(+), 21 deletions(-) diff --git a/piker/brokers/ib/broker.py b/piker/brokers/ib/broker.py index b317da22..ddda9020 100644 --- a/piker/brokers/ib/broker.py +++ b/piker/brokers/ib/broker.py @@ -1183,7 +1183,14 @@ async def deliver_trade_events( pos and fill ): - assert fill.commissionReport == cr + now_cr: CommissionReport = fill.commissionReport + if (now_cr != cr): + log.warning( + 'UhhHh ib updated the commission report mid-fill..?\n' + f'was: {pformat(cr)}\n' + f'now: {pformat(now_cr)}\n' + ) + await emit_pp_update( ems_stream, accounts_def, diff --git a/piker/brokers/ib/ledger.py b/piker/brokers/ib/ledger.py index a767d551..d62b4ba7 100644 --- a/piker/brokers/ib/ledger.py +++ b/piker/brokers/ib/ledger.py @@ -31,7 +31,11 @@ from typing import ( ) from bidict import bidict -import pendulum +from pendulum import ( + DateTime, + parse, + from_timestamp, +) from ib_insync import ( Contract, Commodity, @@ -66,10 +70,11 @@ tx_sort: Callable = partial( iter_by_dt, parsers={ 'dateTime': parse_flex_dt, - 'datetime': pendulum.parse, - # for some some fucking 2022 and - # back options records...fuck me. - 'date': pendulum.parse, + 'datetime': parse, + + # XXX: for some some fucking 2022 and + # back options records.. f@#$ me.. + 'date': parse, } ) @@ -89,15 +94,38 @@ def norm_trade( conid: int = str(record.get('conId') or record['conid']) bs_mktid: str = str(conid) - comms = record.get('commission') - if comms is None: - comms = -1*record['ibCommission'] - price = record.get('price') or record['tradePrice'] + # NOTE: sometimes weird records (like BTTX?) + # have no field for this? + comms: float = -1 * ( + record.get('commission') + or record.get('ibCommission') + or 0 + ) + if not comms: + log.warning( + 'No commissions found for record?\n' + f'{pformat(record)}\n' + ) + + price: float = ( + record.get('price') + or record.get('tradePrice') + ) + if price is None: + log.warning( + 'No `price` field found in record?\n' + 'Skipping normalization..\n' + f'{pformat(record)}\n' + ) + return None # the api doesn't do the -/+ on the quantity for you but flex # records do.. are you fucking serious ib...!? - size = record.get('quantity') or record['shares'] * { + size: float|int = ( + record.get('quantity') + or record['shares'] + ) * { 'BOT': 1, 'SLD': -1, }[record['side']] @@ -128,26 +156,31 @@ def norm_trade( # otype = tail[6] # strike = tail[7:] - print(f'skipping opts contract {symbol}') + log.warning( + f'Skipping option contract -> NO SUPPORT YET!\n' + f'{symbol}\n' + ) return None # timestamping is way different in API records - dtstr = record.get('datetime') - date = record.get('date') - flex_dtstr = record.get('dateTime') + dtstr: str = record.get('datetime') + date: str = record.get('date') + flex_dtstr: str = record.get('dateTime') if dtstr or date: - dt = pendulum.parse(dtstr or date) + dt: DateTime = parse(dtstr or date) elif flex_dtstr: # probably a flex record with a wonky non-std timestamp.. - dt = parse_flex_dt(record['dateTime']) + dt: DateTime = parse_flex_dt(record['dateTime']) # special handling of symbol extraction from # flex records using some ad-hoc schema parsing. - asset_type: str = record.get( - 'assetCategory' - ) or record.get('secType', 'STK') + asset_type: str = ( + record.get('assetCategory') + or record.get('secType') + or 'STK' + ) if (expiry := ( record.get('lastTradeDateOrContractMonth') @@ -357,6 +390,7 @@ def norm_trade_records( if txn is None: continue + # inject txns sorted by datetime insort( records, txn, @@ -405,7 +439,7 @@ def api_trades_to_ledger_entries( txn_dict[attr_name] = val tid = str(txn_dict['execId']) - dt = pendulum.from_timestamp(txn_dict['time']) + dt = from_timestamp(txn_dict['time']) txn_dict['datetime'] = str(dt) acctid = accounts[txn_dict['acctNumber']]