Many, many `ib` trade log schema hackz
I don't want to rant too much any more since it's pretty clear `ib` has either zero concern for its (api) user's or a severely terrible data management team and/or general inter-team coordination system, but this patch more or less hacks the flex report records to be similar enough to API "execution" / "fill" records such that they can be similarly normalized and stored as well as processed for position calculations.. Dirty deats, - use the `IB.fills()` method for pulling current session trade events since it's both recommended in the docs and does seem to capture more extensive meta-data. - add a `update_ledger_from_api()` helper which does all the insane work of making sure api trade entries are usable both within piker's global fqsn system but also compatible with incremental updates of positions computed from trade ledgers derived from ib's "flex reports". - add "auditting" of `ib`'s reported positioning API messages by comparison with piker's new "traders first" breakeven price style and complain via logging on mismatches. - handle buy vs. sell arithmetic (via a +ve or -ve multiplier) to make "size" arithmetic work for API trade entries.. - draft out options contract transaction parsing but skip in pps generation for now. - always use the "execution id" as ledger keys both in flex and api trade processing. - for whatever weird reason `ib_insync` doesn't include the so called "primary exchange" in contracts reported in fill events, so do manual contract lookups in such cases such that pps entries can be placed in the right fqsn section... Still ToDo: - incremental update on trade clears / position updates - pps audit from ledger depending on user config?lifo_pps_ib
parent
05a1a4e3d8
commit
82b718d5a3
|
@ -26,6 +26,7 @@ from typing import (
|
||||||
Any,
|
Any,
|
||||||
Optional,
|
Optional,
|
||||||
AsyncIterator,
|
AsyncIterator,
|
||||||
|
Union,
|
||||||
)
|
)
|
||||||
|
|
||||||
from bidict import bidict
|
from bidict import bidict
|
||||||
|
@ -48,8 +49,7 @@ from ib_insync.objects import Position
|
||||||
import pendulum
|
import pendulum
|
||||||
|
|
||||||
from piker import config
|
from piker import config
|
||||||
from piker.pp import update_pps_conf
|
from piker import pp
|
||||||
from piker.pp import TradeRecord
|
|
||||||
from piker.log import get_console_log
|
from piker.log import get_console_log
|
||||||
from piker.clearing._messages import (
|
from piker.clearing._messages import (
|
||||||
BrokerdOrder,
|
BrokerdOrder,
|
||||||
|
@ -68,6 +68,7 @@ from .api import (
|
||||||
get_config,
|
get_config,
|
||||||
open_client_proxies,
|
open_client_proxies,
|
||||||
Client,
|
Client,
|
||||||
|
MethodProxy,
|
||||||
)
|
)
|
||||||
# from .feed import open_data_client
|
# from .feed import open_data_client
|
||||||
|
|
||||||
|
@ -87,27 +88,30 @@ def pack_position(
|
||||||
symbol = con.symbol.lower()
|
symbol = con.symbol.lower()
|
||||||
|
|
||||||
exch = (con.primaryExchange or con.exchange).lower()
|
exch = (con.primaryExchange or con.exchange).lower()
|
||||||
symkey = '.'.join((symbol, exch))
|
fqsn = '.'.join((symbol, exch))
|
||||||
if not exch:
|
if not exch:
|
||||||
# attempt to lookup the symbol from our
|
# attempt to lookup the symbol from our
|
||||||
# hacked set..
|
# hacked set..
|
||||||
for sym in _adhoc_futes_set:
|
for sym in _adhoc_futes_set:
|
||||||
if symbol in sym:
|
if symbol in sym:
|
||||||
symkey = sym
|
fqsn = sym
|
||||||
break
|
break
|
||||||
|
|
||||||
expiry = con.lastTradeDateOrContractMonth
|
expiry = con.lastTradeDateOrContractMonth
|
||||||
if expiry:
|
if expiry:
|
||||||
symkey += f'.{expiry}'
|
fqsn += f'.{expiry}'
|
||||||
|
|
||||||
# TODO: options contracts into a sane format..
|
# TODO: options contracts into a sane format..
|
||||||
return BrokerdPosition(
|
return (
|
||||||
|
con.conId,
|
||||||
|
BrokerdPosition(
|
||||||
broker='ib',
|
broker='ib',
|
||||||
account=pos.account,
|
account=pos.account,
|
||||||
symbol=symkey,
|
symbol=fqsn,
|
||||||
currency=con.currency,
|
currency=con.currency,
|
||||||
size=float(pos.position),
|
size=float(pos.position),
|
||||||
avg_price=float(pos.avgCost) / float(con.multiplier or 1.0),
|
avg_price=float(pos.avgCost) / float(con.multiplier or 1.0),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -262,6 +266,70 @@ async def recv_trade_updates(
|
||||||
await client.ib.disconnectedEvent
|
await client.ib.disconnectedEvent
|
||||||
|
|
||||||
|
|
||||||
|
async def update_ledger_from_api_trades(
|
||||||
|
clients: list[Union[Client, MethodProxy]],
|
||||||
|
ib_pp_msgs: dict[int, BrokerdPosition], # conid -> msg
|
||||||
|
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
|
||||||
|
# construct piker pps from trade ledger, underneath using
|
||||||
|
# LIFO style breakeven pricing calcs.
|
||||||
|
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.
|
||||||
|
trades_by_account: dict = {}
|
||||||
|
for client in clients:
|
||||||
|
|
||||||
|
trade_entries = await client.trades()
|
||||||
|
|
||||||
|
# XXX; ERRGGG..
|
||||||
|
# pack in the "primary/listing exchange" value from a
|
||||||
|
# contract lookup since it seems this isn't available by
|
||||||
|
# default from the `.fills()` method endpoint...
|
||||||
|
for entry in trade_entries:
|
||||||
|
condict = entry['contract']
|
||||||
|
conid = condict['conId']
|
||||||
|
pexch = condict['primaryExchange']
|
||||||
|
|
||||||
|
if not pexch:
|
||||||
|
con = (await client.get_con(conid=conid))[0]
|
||||||
|
pexch = con.primaryExchange
|
||||||
|
|
||||||
|
entry['listingExchange'] = pexch
|
||||||
|
|
||||||
|
records = trades_to_records(
|
||||||
|
conf['accounts'].inverse,
|
||||||
|
trade_entries,
|
||||||
|
)
|
||||||
|
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 pp.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)
|
||||||
|
|
||||||
|
# remap stupid ledger fqsns (which are often
|
||||||
|
# filled with lesser venue/exchange values) to
|
||||||
|
# the ones we pull from the API via ib's reported
|
||||||
|
# positioning messages.
|
||||||
|
for r in records:
|
||||||
|
normed_msg = ib_pp_msgs[r.bsuid]
|
||||||
|
if normed_msg.symbol != r.fqsn:
|
||||||
|
log.warning(
|
||||||
|
f'Remapping ledger fqsn: {r.fqsn} -> {normed_msg.symbol}'
|
||||||
|
)
|
||||||
|
r.fqsn = normed_msg.symbol
|
||||||
|
|
||||||
|
pp.update_pps_conf('ib', acctid, records)
|
||||||
|
|
||||||
|
|
||||||
@tractor.context
|
@tractor.context
|
||||||
async def trades_dialogue(
|
async def trades_dialogue(
|
||||||
|
|
||||||
|
@ -311,7 +379,7 @@ async def trades_dialogue(
|
||||||
assert account in accounts_def
|
assert account in accounts_def
|
||||||
accounts.add(account)
|
accounts.add(account)
|
||||||
|
|
||||||
pp_msgs = {}
|
cids2pps = {}
|
||||||
|
|
||||||
# process pp value reported from ib's system. we only use these
|
# process pp value reported from ib's system. we only use these
|
||||||
# to cross-check sizing since average pricing on their end uses
|
# to cross-check sizing since average pricing on their end uses
|
||||||
|
@ -320,65 +388,65 @@ 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():
|
||||||
msg = pack_position(pos)
|
|
||||||
msg.account = accounts_def.inverse[msg.account]
|
|
||||||
pp_msgs[msg.symbol] = msg
|
|
||||||
|
|
||||||
|
cid, msg = pack_position(pos)
|
||||||
|
msg.account = accounts_def.inverse[msg.account]
|
||||||
|
cids2pps[cid] = msg
|
||||||
assert msg.account in accounts, (
|
assert msg.account in accounts, (
|
||||||
f'Position for unknown account: {msg.account}')
|
f'Position for unknown account: {msg.account}')
|
||||||
|
|
||||||
# built-out piker pps from trade ledger, underneath using
|
# update trades ledgers for all accounts from
|
||||||
# LIFO style breakeven pricing calcs.
|
# connected api clients.
|
||||||
trades_by_account: dict = {}
|
await update_ledger_from_api_trades(
|
||||||
conf = get_config()
|
proxies.values(),
|
||||||
|
cids2pps, # pass these in to map to correct fqsns..
|
||||||
# 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(
|
|
||||||
conf['accounts'].inverse,
|
|
||||||
trade_entries,
|
|
||||||
)
|
)
|
||||||
trades_by_account.update(records)
|
|
||||||
|
|
||||||
# write recent session's trades to the user's (local) ledger
|
# load all positions from `pps.toml`, cross check with ib's
|
||||||
# file.
|
# positions data, and relay re-formatted pps as msgs to the ems.
|
||||||
for acctid, trades_by_id in trades_by_account.items():
|
for acctid, by_fqsn in pp.get_pps('ib').items():
|
||||||
with config.open_trade_ledger('ib', acctid) as ledger:
|
for fqsn, posdict in by_fqsn.items():
|
||||||
ledger.update(trades_by_id)
|
ibppmsg = cids2pps[posdict['bsuid']]
|
||||||
|
|
||||||
# (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(
|
msg = BrokerdPosition(
|
||||||
broker='ib',
|
broker='ib',
|
||||||
# account=acctid + '.ib',
|
# account=acctid + '.ib',
|
||||||
# XXX: ok so this is annoying, we're relaying
|
# XXX: ok so this is annoying, we're relaying
|
||||||
# an account name with the backend suffix prefixed
|
# an account name with the backend suffix prefixed
|
||||||
# but when reading accounts from ledgers
|
# but when reading accounts from ledgers we don't
|
||||||
|
# need it and/or it's prefixed in the section
|
||||||
|
# table..
|
||||||
account=ibppmsg.account,
|
account=ibppmsg.account,
|
||||||
# XXX: the `.ib` is stripped..?
|
# XXX: the `.ib` is stripped..?
|
||||||
symbol=ibppmsg.symbol,
|
symbol=ibppmsg.symbol,
|
||||||
currency=ibppmsg.currency,
|
currency=ibppmsg.currency,
|
||||||
size=pp['size'],
|
size=posdict['size'],
|
||||||
avg_price=pp['avg_price'],
|
avg_price=posdict['avg_price'],
|
||||||
)
|
)
|
||||||
assert ibppmsg.size == msg.size
|
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:
|
if ibppmsg.avg_price != msg.avg_price:
|
||||||
|
|
||||||
# TODO: make this a "propoganda" log level?
|
# TODO: make this a "propoganda" log level?
|
||||||
log.warning(
|
log.warning(
|
||||||
'The mega-cucks at IB want you to believe with their '
|
'The mega-cucks at IB want you to believe with their '
|
||||||
'"FIFO" positioning the following:\n'
|
f'"FIFO" positioning for {msg.symbol}:\n'
|
||||||
f'"ib" mega-cucker avg price: {ibppmsg.avg_price}\n'
|
f'"ib" mega-cucker avg price: {ibppmsg.avg_price}\n'
|
||||||
f'piker, legitamous-ness, LIFO avg price: {msg.avg_price}'
|
f'piker, LIFO breakeven PnL price: {msg.avg_price}'
|
||||||
)
|
)
|
||||||
|
|
||||||
all_positions.append(msg.dict())
|
all_positions.append(msg.dict())
|
||||||
|
@ -545,7 +613,7 @@ async def deliver_trade_events(
|
||||||
continue
|
continue
|
||||||
|
|
||||||
elif event_name == 'position':
|
elif event_name == 'position':
|
||||||
msg = pack_position(item)
|
cid, msg = pack_position(item)
|
||||||
msg.account = accounts_def.inverse[msg.account]
|
msg.account = accounts_def.inverse[msg.account]
|
||||||
|
|
||||||
elif event_name == 'event':
|
elif event_name == 'event':
|
||||||
|
@ -579,25 +647,49 @@ async def deliver_trade_events(
|
||||||
def norm_trade_records(
|
def norm_trade_records(
|
||||||
ledger: dict[str, Any],
|
ledger: dict[str, Any],
|
||||||
|
|
||||||
) -> dict[str, list[TradeRecord]]:
|
) -> dict[str, list[pp.Transaction]]:
|
||||||
'''
|
'''
|
||||||
Normalize a flex report or API retrieved executions
|
Normalize a flex report or API retrieved executions
|
||||||
ledger into our standard record format.
|
ledger into our standard record format.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
records: list[TradeRecord] = []
|
records: list[pp.Transaction] = []
|
||||||
# async with open_data_client() as proxy:
|
|
||||||
for tid, record in ledger.items():
|
for tid, record in ledger.items():
|
||||||
# date, time = record['dateTime']
|
# date, time = record['dateTime']
|
||||||
# cost = record['cost']
|
# cost = record['cost']
|
||||||
# action = record['buySell']
|
# action = record['buySell']
|
||||||
conid = record.get('conId') or record['conid']
|
conid = record.get('conId') or record['conid']
|
||||||
comms = record.get('ibCommission', 0)
|
comms = record.get('commission') or -1*record['ibCommission']
|
||||||
price = record.get('price') or record['tradePrice']
|
price = record.get('price') or record['tradePrice']
|
||||||
size = record.get('shares') or record['quantity']
|
|
||||||
|
|
||||||
|
# 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'] * {
|
||||||
|
'BOT': 1,
|
||||||
|
'SLD': -1,
|
||||||
|
}[record['side']]
|
||||||
|
|
||||||
|
exch = record['exchange']
|
||||||
|
lexch = record.get('listingExchange')
|
||||||
|
|
||||||
|
suffix = lexch or exch
|
||||||
symbol = record['symbol']
|
symbol = record['symbol']
|
||||||
|
|
||||||
|
# likely an opts contract record from a flex report..
|
||||||
|
# TODO: no idea how to parse ^ the strike part from flex..
|
||||||
|
# (00010000 any, or 00007500 tsla, ..)
|
||||||
|
# we probably must do the contract lookup for this?
|
||||||
|
if ' ' in symbol or '--' in exch:
|
||||||
|
underlying, _, tail = symbol.partition(' ')
|
||||||
|
suffix = exch = 'opt'
|
||||||
|
expiry = tail[:6]
|
||||||
|
# otype = tail[6]
|
||||||
|
# strike = tail[7:]
|
||||||
|
|
||||||
|
print(f'skipping opts contract {symbol}')
|
||||||
|
continue
|
||||||
|
|
||||||
# special handling of symbol extraction from
|
# special handling of symbol extraction from
|
||||||
# flex records using some ad-hoc schema parsing.
|
# flex records using some ad-hoc schema parsing.
|
||||||
instr = record.get('assetCategory')
|
instr = record.get('assetCategory')
|
||||||
|
@ -605,15 +697,16 @@ def norm_trade_records(
|
||||||
symbol = record['description'][:3]
|
symbol = record['description'][:3]
|
||||||
|
|
||||||
# try to build out piker fqsn from record.
|
# try to build out piker fqsn from record.
|
||||||
expiry = record.get('lastTradeDateOrContractMonth') or record['expiry']
|
expiry = record.get(
|
||||||
exch = record.get('listingExchange') or record['exchange']
|
'lastTradeDateOrContractMonth') or record.get('expiry')
|
||||||
|
if expiry:
|
||||||
|
expiry = str(expiry).strip(' ')
|
||||||
|
suffix = f'{exch}.{expiry}'
|
||||||
|
|
||||||
fqsn = Symbol.from_broker_info(
|
fqsn = Symbol.from_fqsn(
|
||||||
broker='ib',
|
fqsn=f'{symbol}.{suffix}.ib',
|
||||||
symbol=symbol,
|
|
||||||
suffix=f'{exch}.{expiry}',
|
|
||||||
info={},
|
info={},
|
||||||
).front_fqsn()
|
).front_fqsn().rstrip('.ib')
|
||||||
|
|
||||||
# NOTE: for flex records the normal fields won't be available so
|
# NOTE: for flex records the normal fields won't be available so
|
||||||
# we have to do a lookup at some point to reverse map the conid
|
# we have to do a lookup at some point to reverse map the conid
|
||||||
|
@ -621,41 +714,50 @@ def norm_trade_records(
|
||||||
|
|
||||||
# con = await proxy.get_con(conid)
|
# con = await proxy.get_con(conid)
|
||||||
|
|
||||||
records.append(TradeRecord(
|
records.append(pp.Transaction(
|
||||||
fqsn=fqsn,
|
fqsn=fqsn,
|
||||||
tid=tid,
|
tid=tid,
|
||||||
size=size,
|
size=size,
|
||||||
price=price,
|
price=price,
|
||||||
cost=comms,
|
cost=comms,
|
||||||
symkey=conid,
|
bsuid=conid,
|
||||||
))
|
))
|
||||||
|
|
||||||
return records
|
return records
|
||||||
|
|
||||||
|
|
||||||
def trades_to_records(
|
def trades_to_records(
|
||||||
|
|
||||||
accounts: bidict,
|
accounts: bidict,
|
||||||
trade_entries: list[object],
|
trade_entries: list[object],
|
||||||
source_type: str = 'api',
|
source_type: str = 'api',
|
||||||
|
|
||||||
) -> dict:
|
) -> dict:
|
||||||
|
'''
|
||||||
|
Convert either of API execution objects or flex report
|
||||||
|
entry objects into ``dict`` form, pretty much straight up
|
||||||
|
without modification.
|
||||||
|
|
||||||
|
'''
|
||||||
trades_by_account = {}
|
trades_by_account = {}
|
||||||
|
|
||||||
for t in trade_entries:
|
for t in trade_entries:
|
||||||
if source_type == 'flex':
|
if source_type == 'flex':
|
||||||
entry = t.__dict__
|
entry = t.__dict__
|
||||||
|
|
||||||
|
# XXX: LOL apparently ``toml`` has a bug
|
||||||
|
# where a section key error will show up in the write
|
||||||
|
# if you leave a table key as an `int`? So i guess
|
||||||
|
# cast to strs for all keys..
|
||||||
|
|
||||||
# oddly for some so-called "BookTrade" entries
|
# oddly for some so-called "BookTrade" entries
|
||||||
# this field seems to be blank, no cuckin clue.
|
# this field seems to be blank, no cuckin clue.
|
||||||
# trade['ibExecID']
|
# trade['ibExecID']
|
||||||
|
tid = str(entry.get('ibExecID') or entry['tradeID'])
|
||||||
# XXX: LOL apparently ``toml`` has a bug
|
|
||||||
# where a section key error will show up in the write
|
|
||||||
# if you leave this as an ``int``?
|
|
||||||
tid = str(entry['tradeID'])
|
|
||||||
# date = str(entry['tradeDate'])
|
# date = str(entry['tradeDate'])
|
||||||
|
|
||||||
|
# XXX: is it going to cause problems if a account name
|
||||||
|
# get's lost? The user should be able to find it based
|
||||||
|
# on the actual exec history right?
|
||||||
acctid = accounts[str(entry['accountId'])]
|
acctid = accounts[str(entry['accountId'])]
|
||||||
|
|
||||||
elif source_type == 'api':
|
elif source_type == 'api':
|
||||||
|
@ -667,17 +769,19 @@ def trades_to_records(
|
||||||
# 'time': 1654801166.0
|
# 'time': 1654801166.0
|
||||||
# }
|
# }
|
||||||
|
|
||||||
|
# flatten all sub-dicts and values into one top level entry.
|
||||||
entry = {}
|
entry = {}
|
||||||
for section, obj in t.items():
|
for section, val in t.items():
|
||||||
match section:
|
match section:
|
||||||
case 'commisionReport' | 'execution':
|
case 'contract' | 'execution' | 'commissionReport':
|
||||||
entry.update(asdict(obj))
|
# sub-dict cases
|
||||||
|
entry.update(val)
|
||||||
case 'contract':
|
case _:
|
||||||
entry.update(obj)
|
entry[section] = val
|
||||||
|
|
||||||
tid = str(entry['execId'])
|
tid = str(entry['execId'])
|
||||||
dt = pendulum.from_timestamp(entry['time'])
|
dt = pendulum.from_timestamp(entry['time'])
|
||||||
|
# TODO: why isn't this showing seconds in the str?
|
||||||
entry['date'] = str(dt)
|
entry['date'] = str(dt)
|
||||||
acctid = accounts[entry['acctNumber']]
|
acctid = accounts[entry['acctNumber']]
|
||||||
|
|
||||||
|
@ -691,7 +795,7 @@ def trades_to_records(
|
||||||
def load_flex_trades(
|
def load_flex_trades(
|
||||||
path: Optional[str] = None,
|
path: Optional[str] = None,
|
||||||
|
|
||||||
) -> dict[str, str]:
|
) -> dict[str, Any]:
|
||||||
|
|
||||||
from ib_insync import flexreport, util
|
from ib_insync import flexreport, util
|
||||||
|
|
||||||
|
@ -728,6 +832,10 @@ def load_flex_trades(
|
||||||
report = flexreport.FlexReport(path=path)
|
report = flexreport.FlexReport(path=path)
|
||||||
|
|
||||||
trade_entries = report.extract('Trade')
|
trade_entries = report.extract('Trade')
|
||||||
|
ln = len(trade_entries)
|
||||||
|
# log.info(f'Loaded {ln} trades from flex query')
|
||||||
|
print(f'Loaded {ln} trades from flex query')
|
||||||
|
|
||||||
trades_by_account = trades_to_records(
|
trades_by_account = trades_to_records(
|
||||||
# get reverse map to user account names
|
# get reverse map to user account names
|
||||||
conf['accounts'].inverse,
|
conf['accounts'].inverse,
|
||||||
|
@ -735,13 +843,15 @@ def load_flex_trades(
|
||||||
source_type='flex',
|
source_type='flex',
|
||||||
)
|
)
|
||||||
|
|
||||||
# ln = len(trades)
|
ledgers = {}
|
||||||
# log.info(f'Loaded {ln} trades from flex query')
|
|
||||||
|
|
||||||
for acctid, trades_by_id in trades_by_account.items():
|
for acctid, trades_by_id in trades_by_account.items():
|
||||||
with config.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)
|
||||||
|
|
||||||
|
ledgers[acctid] = ledger
|
||||||
|
|
||||||
|
return ledgers
|
||||||
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
import sys
|
import sys
|
||||||
|
|
Loading…
Reference in New Issue