WIP: refactor ib pp load init
parent
3e5da64571
commit
c59ec77d9c
|
@ -59,7 +59,7 @@ from piker.accounting import (
|
||||||
open_pps,
|
open_pps,
|
||||||
PpTable,
|
PpTable,
|
||||||
)
|
)
|
||||||
from piker.log import get_console_log
|
from .._util import get_console_log
|
||||||
from piker.clearing._messages import (
|
from piker.clearing._messages import (
|
||||||
Order,
|
Order,
|
||||||
Status,
|
Status,
|
||||||
|
@ -281,18 +281,21 @@ async def recv_trade_updates(
|
||||||
async def update_ledger_from_api_trades(
|
async def update_ledger_from_api_trades(
|
||||||
trade_entries: list[dict[str, Any]],
|
trade_entries: list[dict[str, Any]],
|
||||||
client: Union[Client, MethodProxy],
|
client: Union[Client, MethodProxy],
|
||||||
|
accounts_def_inv: bidict[str, str],
|
||||||
|
|
||||||
) -> tuple[
|
) -> tuple[
|
||||||
dict[str, Transaction],
|
dict[str, Transaction],
|
||||||
dict[str, dict],
|
dict[str, dict],
|
||||||
]:
|
]:
|
||||||
|
|
||||||
# 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']
|
||||||
|
# print(
|
||||||
|
# f"{condict['symbol']}: GETTING CONTRACT INFO!\n"
|
||||||
|
# )
|
||||||
conid = condict['conId']
|
conid = condict['conId']
|
||||||
pexch = condict['primaryExchange']
|
pexch = condict['primaryExchange']
|
||||||
|
|
||||||
|
@ -310,9 +313,8 @@ async def update_ledger_from_api_trades(
|
||||||
# pack in the ``Contract.secType``
|
# pack in the ``Contract.secType``
|
||||||
entry['asset_type'] = condict['secType']
|
entry['asset_type'] = condict['secType']
|
||||||
|
|
||||||
conf = get_config()
|
|
||||||
entries = api_trades_to_ledger_entries(
|
entries = api_trades_to_ledger_entries(
|
||||||
conf['accounts'].inverse,
|
accounts_def_inv,
|
||||||
trade_entries,
|
trade_entries,
|
||||||
)
|
)
|
||||||
# normalize recent session's trades to the `Transaction` type
|
# normalize recent session's trades to the `Transaction` type
|
||||||
|
@ -340,9 +342,16 @@ async def update_and_audit_msgs(
|
||||||
# retreive equivalent ib reported position message
|
# retreive equivalent ib reported position message
|
||||||
# for comparison/audit versus the piker equivalent
|
# for comparison/audit versus the piker equivalent
|
||||||
# breakeven pp calcs.
|
# breakeven pp calcs.
|
||||||
|
# if (
|
||||||
|
# acctid == 'reg'
|
||||||
|
# and bs_mktid == 36285627
|
||||||
|
# ):
|
||||||
|
# await tractor.breakpoint()
|
||||||
|
|
||||||
ibppmsg = cids2pps.get((acctid, bs_mktid))
|
ibppmsg = cids2pps.get((acctid, bs_mktid))
|
||||||
|
|
||||||
if ibppmsg:
|
if ibppmsg:
|
||||||
|
symbol = ibppmsg.symbol
|
||||||
msg = BrokerdPosition(
|
msg = BrokerdPosition(
|
||||||
broker='ib',
|
broker='ib',
|
||||||
|
|
||||||
|
@ -353,7 +362,7 @@ async def update_and_audit_msgs(
|
||||||
# table..
|
# table..
|
||||||
account=ibppmsg.account,
|
account=ibppmsg.account,
|
||||||
# XXX: the `.ib` is stripped..?
|
# XXX: the `.ib` is stripped..?
|
||||||
symbol=ibppmsg.symbol,
|
symbol=symbol,
|
||||||
currency=ibppmsg.currency,
|
currency=ibppmsg.currency,
|
||||||
size=p.size,
|
size=p.size,
|
||||||
avg_price=p.ppu,
|
avg_price=p.ppu,
|
||||||
|
@ -432,6 +441,81 @@ async def update_and_audit_msgs(
|
||||||
return msgs
|
return msgs
|
||||||
|
|
||||||
|
|
||||||
|
async def aggr_open_orders(
|
||||||
|
order_msgs: list[Status],
|
||||||
|
client: Client,
|
||||||
|
proxy: MethodProxy,
|
||||||
|
accounts_def: bidict[str, str],
|
||||||
|
|
||||||
|
) -> None:
|
||||||
|
'''
|
||||||
|
Collect all open orders from client and fill in `order_msgs: list`.
|
||||||
|
|
||||||
|
'''
|
||||||
|
trades: list[Trade] = client.ib.openTrades()
|
||||||
|
for trade in trades:
|
||||||
|
order = trade.order
|
||||||
|
quant = trade.order.totalQuantity
|
||||||
|
action = order.action.lower()
|
||||||
|
size = {
|
||||||
|
'sell': -1,
|
||||||
|
'buy': 1,
|
||||||
|
}[action] * quant
|
||||||
|
con = trade.contract
|
||||||
|
|
||||||
|
# TODO: in the case of the SMART venue (aka ib's
|
||||||
|
# router-clearing sys) we probably should handle
|
||||||
|
# showing such orders overtop of the fqsn for the
|
||||||
|
# primary exchange, how to map this easily is going
|
||||||
|
# to be a bit tricky though?
|
||||||
|
deats = await proxy.con_deats(contracts=[con])
|
||||||
|
fqsn = list(deats)[0]
|
||||||
|
|
||||||
|
reqid = order.orderId
|
||||||
|
|
||||||
|
# TODO: maybe embed a ``BrokerdOrder`` instead
|
||||||
|
# since then we can directly load it on the client
|
||||||
|
# side in the order mode loop?
|
||||||
|
msg = Status(
|
||||||
|
time_ns=time.time_ns(),
|
||||||
|
resp='open',
|
||||||
|
oid=str(reqid),
|
||||||
|
reqid=reqid,
|
||||||
|
|
||||||
|
# embedded order info
|
||||||
|
req=Order(
|
||||||
|
action=action,
|
||||||
|
exec_mode='live',
|
||||||
|
oid=str(reqid),
|
||||||
|
symbol=fqsn,
|
||||||
|
account=accounts_def.inverse[order.account],
|
||||||
|
price=order.lmtPrice,
|
||||||
|
size=size,
|
||||||
|
),
|
||||||
|
src='ib',
|
||||||
|
)
|
||||||
|
order_msgs.append(msg)
|
||||||
|
|
||||||
|
return order_msgs
|
||||||
|
|
||||||
|
|
||||||
|
# proxy wrapper for starting trade event stream
|
||||||
|
async def open_trade_event_stream(
|
||||||
|
client: Client,
|
||||||
|
task_status: TaskStatus[
|
||||||
|
trio.abc.ReceiveChannel
|
||||||
|
] = trio.TASK_STATUS_IGNORED,
|
||||||
|
):
|
||||||
|
# each api client has a unique event stream
|
||||||
|
async with tractor.to_asyncio.open_channel_from(
|
||||||
|
recv_trade_updates,
|
||||||
|
client=client,
|
||||||
|
) as (first, trade_event_stream):
|
||||||
|
|
||||||
|
task_status.started(trade_event_stream)
|
||||||
|
await trio.sleep_forever()
|
||||||
|
|
||||||
|
|
||||||
@tractor.context
|
@tractor.context
|
||||||
async def trades_dialogue(
|
async def trades_dialogue(
|
||||||
|
|
||||||
|
@ -465,7 +549,10 @@ async def trades_dialogue(
|
||||||
# we might also want to delegate a specific actor for
|
# we might also want to delegate a specific actor for
|
||||||
# ledger writing / reading for speed?
|
# ledger writing / reading for speed?
|
||||||
async with (
|
async with (
|
||||||
open_client_proxies() as (proxies, aioclients),
|
open_client_proxies() as (
|
||||||
|
proxies,
|
||||||
|
aioclients,
|
||||||
|
),
|
||||||
):
|
):
|
||||||
# Open a trade ledgers stack for appending trade records over
|
# Open a trade ledgers stack for appending trade records over
|
||||||
# multiple accounts.
|
# multiple accounts.
|
||||||
|
@ -473,6 +560,9 @@ async def trades_dialogue(
|
||||||
ledgers: dict[str, dict] = {}
|
ledgers: dict[str, dict] = {}
|
||||||
tables: dict[str, PpTable] = {}
|
tables: dict[str, PpTable] = {}
|
||||||
order_msgs: list[Status] = []
|
order_msgs: list[Status] = []
|
||||||
|
conf = get_config()
|
||||||
|
accounts_def_inv = conf['accounts'].inverse
|
||||||
|
|
||||||
with (
|
with (
|
||||||
ExitStack() as lstack,
|
ExitStack() as lstack,
|
||||||
):
|
):
|
||||||
|
@ -491,7 +581,17 @@ async def trades_dialogue(
|
||||||
acctid,
|
acctid,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
table = tables[acctid] = lstack.enter_context(
|
|
||||||
|
# load all positions from `pps.toml`, cross check with
|
||||||
|
# ib's positions data, and relay re-formatted pps as
|
||||||
|
# msgs to the ems.
|
||||||
|
# __2 cases__:
|
||||||
|
# - new trades have taken place this session that we want to
|
||||||
|
# always reprocess indempotently,
|
||||||
|
# - no new trades yet but we want to reload and audit any
|
||||||
|
# positions reported by ib's sys that may not yet be in
|
||||||
|
# piker's ``pps.toml`` state-file.
|
||||||
|
tables[acctid] = lstack.enter_context(
|
||||||
open_pps(
|
open_pps(
|
||||||
'ib',
|
'ib',
|
||||||
acctid,
|
acctid,
|
||||||
|
@ -501,57 +601,54 @@ async def trades_dialogue(
|
||||||
|
|
||||||
for account, proxy in proxies.items():
|
for account, proxy in proxies.items():
|
||||||
client = aioclients[account]
|
client = aioclients[account]
|
||||||
trades: list[Trade] = client.ib.openTrades()
|
|
||||||
for trade in trades:
|
|
||||||
order = trade.order
|
|
||||||
quant = trade.order.totalQuantity
|
|
||||||
action = order.action.lower()
|
|
||||||
size = {
|
|
||||||
'sell': -1,
|
|
||||||
'buy': 1,
|
|
||||||
}[action] * quant
|
|
||||||
con = trade.contract
|
|
||||||
|
|
||||||
# TODO: in the case of the SMART venue (aka ib's
|
# order_msgs is filled in by this helper
|
||||||
# router-clearing sys) we probably should handle
|
await aggr_open_orders(
|
||||||
# showing such orders overtop of the fqsn for the
|
order_msgs,
|
||||||
# primary exchange, how to map this easily is going
|
client,
|
||||||
# to be a bit tricky though?
|
proxy,
|
||||||
deats = await proxy.con_deats(contracts=[con])
|
accounts_def,
|
||||||
fqsn = list(deats)[0]
|
)
|
||||||
|
acctid: str = account.strip('ib.')
|
||||||
|
ledger: dict = ledgers[acctid]
|
||||||
|
table: PpTable = tables[acctid]
|
||||||
|
|
||||||
reqid = order.orderId
|
# update trades ledgers for all accounts from connected
|
||||||
|
# api clients which report trades for **this session**.
|
||||||
# TODO: maybe embed a ``BrokerdOrder`` instead
|
trades = await proxy.trades()
|
||||||
# since then we can directly load it on the client
|
if trades:
|
||||||
# side in the order mode loop?
|
(
|
||||||
msg = Status(
|
trans_by_acct,
|
||||||
time_ns=time.time_ns(),
|
api_to_ledger_entries,
|
||||||
resp='open',
|
) = await update_ledger_from_api_trades(
|
||||||
oid=str(reqid),
|
trades,
|
||||||
reqid=reqid,
|
proxy,
|
||||||
|
accounts_def_inv,
|
||||||
# embedded order info
|
|
||||||
req=Order(
|
|
||||||
action=action,
|
|
||||||
exec_mode='live',
|
|
||||||
oid=str(reqid),
|
|
||||||
symbol=fqsn,
|
|
||||||
account=accounts_def.inverse[order.account],
|
|
||||||
price=order.lmtPrice,
|
|
||||||
size=size,
|
|
||||||
),
|
|
||||||
src='ib',
|
|
||||||
)
|
)
|
||||||
order_msgs.append(msg)
|
|
||||||
|
|
||||||
# process pp value reported from ib's system. we only use these
|
# if new trades are detected from the API, prepare
|
||||||
# to cross-check sizing since average pricing on their end uses
|
# them for the ledger file and update the pptable.
|
||||||
# the so called (bs) "FIFO" style which more or less results in
|
if api_to_ledger_entries:
|
||||||
# a price that's not useful for traders who want to not lose
|
trade_entries = api_to_ledger_entries.get(acctid)
|
||||||
# money.. xb
|
|
||||||
|
if trade_entries:
|
||||||
|
# write ledger with all new trades
|
||||||
|
# **AFTER** we've updated the
|
||||||
|
# `pps.toml` from the original
|
||||||
|
# ledger state! (i.e. this is
|
||||||
|
# currently done on exit)
|
||||||
|
ledger.update(trade_entries)
|
||||||
|
|
||||||
|
trans = trans_by_acct.get(acctid)
|
||||||
|
if trans:
|
||||||
|
table.update_from_trans(trans)
|
||||||
|
|
||||||
|
# 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 pos in client.positions():
|
for pos in client.positions():
|
||||||
|
|
||||||
# collect all ib-pp reported positions so that we can be
|
# collect all ib-pp reported positions so that we can be
|
||||||
# sure know which positions to update from the ledger if
|
# sure know which positions to update from the ledger if
|
||||||
# any are missing from the ``pps.toml``
|
# any are missing from the ``pps.toml``
|
||||||
|
@ -560,13 +657,14 @@ async def trades_dialogue(
|
||||||
acctid = msg.account = accounts_def.inverse[msg.account]
|
acctid = msg.account = accounts_def.inverse[msg.account]
|
||||||
acctid = acctid.strip('ib.')
|
acctid = acctid.strip('ib.')
|
||||||
cids2pps[(acctid, bs_mktid)] = msg
|
cids2pps[(acctid, bs_mktid)] = 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}')
|
||||||
|
|
||||||
ledger = ledgers[acctid]
|
ledger: dict = ledgers[acctid]
|
||||||
table = tables[acctid]
|
table: PpTable = tables[acctid]
|
||||||
|
pp: Position = table.pps.get(bs_mktid)
|
||||||
|
|
||||||
pp = table.pps.get(bs_mktid)
|
|
||||||
if (
|
if (
|
||||||
not pp
|
not pp
|
||||||
or pp.size != msg.size
|
or pp.size != msg.size
|
||||||
|
@ -574,33 +672,6 @@ async def trades_dialogue(
|
||||||
trans = norm_trade_records(ledger)
|
trans = norm_trade_records(ledger)
|
||||||
table.update_from_trans(trans)
|
table.update_from_trans(trans)
|
||||||
|
|
||||||
# update trades ledgers for all accounts from connected
|
|
||||||
# api clients which report trades for **this session**.
|
|
||||||
trades = await proxy.trades()
|
|
||||||
(
|
|
||||||
trans_by_acct,
|
|
||||||
api_to_ledger_entries,
|
|
||||||
) = await update_ledger_from_api_trades(
|
|
||||||
trades,
|
|
||||||
proxy,
|
|
||||||
)
|
|
||||||
|
|
||||||
# if new trades are detected from the API, prepare
|
|
||||||
# them for the ledger file and update the pptable.
|
|
||||||
if api_to_ledger_entries:
|
|
||||||
trade_entries = api_to_ledger_entries.get(acctid)
|
|
||||||
|
|
||||||
if trade_entries:
|
|
||||||
# write ledger with all new trades **AFTER**
|
|
||||||
# we've updated the `pps.toml` from the
|
|
||||||
# original ledger state! (i.e. this is
|
|
||||||
# currently done on exit)
|
|
||||||
ledger.update(trade_entries)
|
|
||||||
|
|
||||||
trans = trans_by_acct.get(acctid)
|
|
||||||
if trans:
|
|
||||||
table.update_from_trans(trans)
|
|
||||||
|
|
||||||
# XXX: not sure exactly why it wouldn't be in
|
# XXX: not sure exactly why it wouldn't be in
|
||||||
# the updated output (maybe this is a bug?) but
|
# the updated output (maybe this is a bug?) but
|
||||||
# if you create a pos from TWS and then load it
|
# if you create a pos from TWS and then load it
|
||||||
|
@ -630,17 +701,12 @@ async def trades_dialogue(
|
||||||
f'piker: {pp.size}\n'
|
f'piker: {pp.size}\n'
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# iterate all (newly) updated pps tables for every
|
||||||
|
# client-account and build out position msgs to deliver to
|
||||||
|
# EMS.
|
||||||
|
for acctid, table in tables.items():
|
||||||
active_pps, closed_pps = table.dump_active()
|
active_pps, closed_pps = table.dump_active()
|
||||||
|
|
||||||
# load all positions from `pps.toml`, cross check with
|
|
||||||
# ib's positions data, and relay re-formatted pps as
|
|
||||||
# msgs to the ems.
|
|
||||||
# __2 cases__:
|
|
||||||
# - new trades have taken place this session that we want to
|
|
||||||
# always reprocess indempotently,
|
|
||||||
# - no new trades yet but we want to reload and audit any
|
|
||||||
# positions reported by ib's sys that may not yet be in
|
|
||||||
# piker's ``pps.toml`` state-file.
|
|
||||||
for pps in [active_pps, closed_pps]:
|
for pps in [active_pps, closed_pps]:
|
||||||
msgs = await update_and_audit_msgs(
|
msgs = await update_and_audit_msgs(
|
||||||
acctid,
|
acctid,
|
||||||
|
@ -661,22 +727,6 @@ async def trades_dialogue(
|
||||||
tuple(name for name in accounts_def if name in accounts),
|
tuple(name for name in accounts_def if name in accounts),
|
||||||
))
|
))
|
||||||
|
|
||||||
# proxy wrapper for starting trade event stream
|
|
||||||
async def open_trade_event_stream(
|
|
||||||
client: Client,
|
|
||||||
task_status: TaskStatus[
|
|
||||||
trio.abc.ReceiveChannel
|
|
||||||
] = trio.TASK_STATUS_IGNORED,
|
|
||||||
):
|
|
||||||
# each api client has a unique event stream
|
|
||||||
async with tractor.to_asyncio.open_channel_from(
|
|
||||||
recv_trade_updates,
|
|
||||||
client=client,
|
|
||||||
) as (first, trade_event_stream):
|
|
||||||
|
|
||||||
task_status.started(trade_event_stream)
|
|
||||||
await trio.sleep_forever()
|
|
||||||
|
|
||||||
async with (
|
async with (
|
||||||
ctx.open_stream() as ems_stream,
|
ctx.open_stream() as ems_stream,
|
||||||
trio.open_nursery() as n,
|
trio.open_nursery() as n,
|
||||||
|
@ -723,7 +773,7 @@ async def trades_dialogue(
|
||||||
async def emit_pp_update(
|
async def emit_pp_update(
|
||||||
ems_stream: tractor.MsgStream,
|
ems_stream: tractor.MsgStream,
|
||||||
trade_entry: dict,
|
trade_entry: dict,
|
||||||
accounts_def: bidict,
|
accounts_def: bidict[str, str],
|
||||||
proxies: dict,
|
proxies: dict,
|
||||||
cids2pps: dict,
|
cids2pps: dict,
|
||||||
|
|
||||||
|
@ -733,16 +783,16 @@ async def emit_pp_update(
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
# compute and relay incrementally updated piker pp
|
# compute and relay incrementally updated piker pp
|
||||||
acctid = accounts_def.inverse[trade_entry['execution']['acctNumber']]
|
accounts_def_inv: bidict[str, str] = accounts_def.inverse
|
||||||
|
acctid = accounts_def_inv[trade_entry['execution']['acctNumber']]
|
||||||
proxy = proxies[acctid]
|
proxy = proxies[acctid]
|
||||||
|
|
||||||
acctid = acctid.strip('ib.')
|
|
||||||
(
|
(
|
||||||
records_by_acct,
|
records_by_acct,
|
||||||
api_to_ledger_entries,
|
api_to_ledger_entries,
|
||||||
) = await update_ledger_from_api_trades(
|
) = await update_ledger_from_api_trades(
|
||||||
[trade_entry],
|
[trade_entry],
|
||||||
proxy,
|
proxy,
|
||||||
|
accounts_def_inv,
|
||||||
)
|
)
|
||||||
trans = records_by_acct[acctid]
|
trans = records_by_acct[acctid]
|
||||||
r = list(trans.values())[0]
|
r = list(trans.values())[0]
|
||||||
|
@ -1244,7 +1294,7 @@ def parse_flex_dt(
|
||||||
|
|
||||||
|
|
||||||
def api_trades_to_ledger_entries(
|
def api_trades_to_ledger_entries(
|
||||||
accounts: bidict,
|
accounts: bidict[str, str],
|
||||||
|
|
||||||
# TODO: maybe we should just be passing through the
|
# TODO: maybe we should just be passing through the
|
||||||
# ``ib_insync.order.Trade`` instance directly here
|
# ``ib_insync.order.Trade`` instance directly here
|
||||||
|
|
Loading…
Reference in New Issue