ui.order_mode: prioritize mkt-match on `.bs_mktid`
For backends which opt to set the new `BrokerdPosition.bs_mktid` field, give (matching logic) priority to it such that even if the `.symbol` field doesn't match the mkt currently focussed on chart, it will always match on a provider's own internal asset-mapping-id. The original fallback logic for `.fqme` matching is left as is. As an example with IB, a qqq.nasdaq.ib txn may have been filled on a non-primary venue as qqq.directedea.ib, in this case if the mkt is displayed and focused on chart we want the **entire position info** to be overlayed by the `OrderMode` UX without discrepancy. Other refinements, - improve logging and add a detailed edge-case-comment around the `.on_fill()` handler to clarify where if a benign 'error' msg is relayed from a backend it will cause the UI to operate as though the order **was not-cleared/cancelled** since the `.on_cancel()` handler will have likely been called just before, popping the `.dialogs` entry. Return `bool` to indicate whether the UI removed-lines / added-fill-arrows. - inverse the `return` branching logic in `.on_cancel()` to reduce indent. - add a very loud `log.error()` in `Status(resp='error')` case-block ensuring the console yells about the order being cancelled, also a todo for the weird msg-field recursion nonsense..qt_w_graceful_SIGINT
parent
ad3fe65bd9
commit
3f48098c55
|
@ -555,14 +555,13 @@ class OrderMode:
|
||||||
|
|
||||||
def on_fill(
|
def on_fill(
|
||||||
self,
|
self,
|
||||||
|
|
||||||
uuid: str,
|
uuid: str,
|
||||||
price: float,
|
price: float,
|
||||||
time_s: float,
|
time_s: float,
|
||||||
|
|
||||||
pointing: str | None = None,
|
pointing: str | None = None,
|
||||||
|
|
||||||
) -> None:
|
) -> bool:
|
||||||
'''
|
'''
|
||||||
Fill msg handler.
|
Fill msg handler.
|
||||||
|
|
||||||
|
@ -575,60 +574,83 @@ class OrderMode:
|
||||||
- update fill bar size
|
- update fill bar size
|
||||||
|
|
||||||
'''
|
'''
|
||||||
dialog = self.dialogs[uuid]
|
# XXX WARNING XXX
|
||||||
|
# if a `Status(resp='error')` arrives *before* this
|
||||||
|
# fill-status, the `.dialogs` entry may have already been
|
||||||
|
# popped and thus the below will skipped.
|
||||||
|
#
|
||||||
|
# NOTE, to avoid this confusing scenario ensure that any
|
||||||
|
# errors delivered thru from the broker-backend are not just
|
||||||
|
# "noisy reporting" (like is very common from IB..) and are
|
||||||
|
# instead ONLY errors-causing-order-dialog-cancellation!
|
||||||
|
if not (dialog := self.dialogs.get(uuid)):
|
||||||
|
log.warning(
|
||||||
|
f'Order was already cleared from `.dialogs` ??\n'
|
||||||
|
f'uuid: {uuid!r}\n'
|
||||||
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
lines = dialog.lines
|
lines = dialog.lines
|
||||||
chart = self.chart
|
chart = self.chart
|
||||||
|
|
||||||
# XXX: seems to fail on certain types of races?
|
if not lines:
|
||||||
# assert len(lines) == 2
|
|
||||||
if lines:
|
|
||||||
flume: Flume = self.feed.flumes[chart.linked.mkt.fqme]
|
|
||||||
_, _, ratio = flume.get_ds_info()
|
|
||||||
|
|
||||||
for chart, shm in [
|
|
||||||
(self.chart, flume.rt_shm),
|
|
||||||
(self.hist_chart, flume.hist_shm),
|
|
||||||
]:
|
|
||||||
viz = chart.get_viz(chart.name)
|
|
||||||
index_field = viz.index_field
|
|
||||||
arr = shm.array
|
|
||||||
|
|
||||||
# TODO: borked for int index based..
|
|
||||||
index = flume.get_index(time_s, arr)
|
|
||||||
|
|
||||||
# get absolute index for arrow placement
|
|
||||||
arrow_index = arr[index_field][index]
|
|
||||||
|
|
||||||
self.arrows.add(
|
|
||||||
chart.plotItem,
|
|
||||||
uuid,
|
|
||||||
arrow_index,
|
|
||||||
price,
|
|
||||||
pointing=pointing,
|
|
||||||
color=lines[0].color
|
|
||||||
)
|
|
||||||
else:
|
|
||||||
log.warn("No line(s) for order {uuid}!?")
|
log.warn("No line(s) for order {uuid}!?")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# update line state(s)
|
||||||
|
#
|
||||||
|
# ?XXX this fails on certain types of races?
|
||||||
|
# assert len(lines) == 2
|
||||||
|
flume: Flume = self.feed.flumes[chart.linked.mkt.fqme]
|
||||||
|
_, _, ratio = flume.get_ds_info()
|
||||||
|
|
||||||
|
for chart, shm in [
|
||||||
|
(self.chart, flume.rt_shm),
|
||||||
|
(self.hist_chart, flume.hist_shm),
|
||||||
|
]:
|
||||||
|
viz = chart.get_viz(chart.name)
|
||||||
|
index_field = viz.index_field
|
||||||
|
arr = shm.array
|
||||||
|
|
||||||
|
# TODO: borked for int index based..
|
||||||
|
index = flume.get_index(time_s, arr)
|
||||||
|
|
||||||
|
# get absolute index for arrow placement
|
||||||
|
arrow_index = arr[index_field][index]
|
||||||
|
|
||||||
|
self.arrows.add(
|
||||||
|
chart.plotItem,
|
||||||
|
uuid,
|
||||||
|
arrow_index,
|
||||||
|
price,
|
||||||
|
pointing=pointing,
|
||||||
|
color=lines[0].color
|
||||||
|
)
|
||||||
|
|
||||||
def on_cancel(
|
def on_cancel(
|
||||||
self,
|
self,
|
||||||
uuid: str
|
uuid: str,
|
||||||
|
|
||||||
) -> None:
|
) -> bool:
|
||||||
|
|
||||||
msg: Order = self.client._sent_orders.pop(uuid, None)
|
msg: Order|None = self.client._sent_orders.pop(uuid, None)
|
||||||
|
if msg is None:
|
||||||
if msg is not None:
|
|
||||||
self.lines.remove_line(uuid=uuid)
|
|
||||||
self.chart.linked.cursor.show_xhair()
|
|
||||||
|
|
||||||
dialog = self.dialogs.pop(uuid, None)
|
|
||||||
if dialog:
|
|
||||||
dialog.last_status_close()
|
|
||||||
else:
|
|
||||||
log.warning(
|
log.warning(
|
||||||
f'Received cancel for unsubmitted order {pformat(msg)}'
|
f'Received cancel for unsubmitted order {pformat(msg)}'
|
||||||
)
|
)
|
||||||
|
return False
|
||||||
|
|
||||||
|
# remove GUI line, show cursor.
|
||||||
|
self.lines.remove_line(uuid=uuid)
|
||||||
|
self.chart.linked.cursor.show_xhair()
|
||||||
|
|
||||||
|
# remove msg dialog (history)
|
||||||
|
dialog: Dialog|None = self.dialogs.pop(uuid, None)
|
||||||
|
if dialog:
|
||||||
|
dialog.last_status_close()
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
|
||||||
def cancel_orders_under_cursor(self) -> list[str]:
|
def cancel_orders_under_cursor(self) -> list[str]:
|
||||||
return self.cancel_orders(
|
return self.cancel_orders(
|
||||||
|
@ -1057,13 +1079,23 @@ async def process_trade_msg(
|
||||||
if name in (
|
if name in (
|
||||||
'position',
|
'position',
|
||||||
):
|
):
|
||||||
sym: MktPair = mode.chart.linked.mkt
|
mkt: MktPair = mode.chart.linked.mkt
|
||||||
pp_msg_symbol = msg['symbol'].lower()
|
pp_msg_symbol = msg['symbol'].lower()
|
||||||
fqme = sym.fqme
|
pp_msg_bsmktid = msg['bs_mktid']
|
||||||
broker = sym.broker
|
fqme = mkt.fqme
|
||||||
|
broker = mkt.broker
|
||||||
if (
|
if (
|
||||||
|
# match on any backed-specific(-unique)-ID first!
|
||||||
|
(
|
||||||
|
pp_msg_bsmktid
|
||||||
|
and
|
||||||
|
mkt.bs_mktid == pp_msg_bsmktid
|
||||||
|
)
|
||||||
|
or
|
||||||
|
# OW try against what's provided as an FQME..
|
||||||
pp_msg_symbol == fqme
|
pp_msg_symbol == fqme
|
||||||
or pp_msg_symbol == fqme.removesuffix(f'.{broker}')
|
or
|
||||||
|
pp_msg_symbol == fqme.removesuffix(f'.{broker}')
|
||||||
):
|
):
|
||||||
log.info(
|
log.info(
|
||||||
f'Loading position for `{fqme}`:\n'
|
f'Loading position for `{fqme}`:\n'
|
||||||
|
@ -1086,7 +1118,7 @@ async def process_trade_msg(
|
||||||
return
|
return
|
||||||
|
|
||||||
msg = Status(**msg)
|
msg = Status(**msg)
|
||||||
resp = msg.resp
|
# resp: str = msg.resp
|
||||||
oid = msg.oid
|
oid = msg.oid
|
||||||
dialog: Dialog = mode.dialogs.get(oid)
|
dialog: Dialog = mode.dialogs.get(oid)
|
||||||
|
|
||||||
|
@ -1150,19 +1182,32 @@ async def process_trade_msg(
|
||||||
mode.on_submit(oid)
|
mode.on_submit(oid)
|
||||||
|
|
||||||
case Status(resp='error'):
|
case Status(resp='error'):
|
||||||
|
|
||||||
# do all the things for a cancel:
|
|
||||||
# - drop order-msg dialog from client table
|
|
||||||
# - delete level line from view
|
|
||||||
mode.on_cancel(oid)
|
|
||||||
|
|
||||||
# TODO: parse into broker-side msg, or should we
|
# TODO: parse into broker-side msg, or should we
|
||||||
# expect it to just be **that** msg verbatim (since
|
# expect it to just be **that** msg verbatim (since
|
||||||
# we'd presumably have only 1 `Error` msg-struct)
|
# we'd presumably have only 1 `Error` msg-struct)
|
||||||
broker_msg: dict = msg.brokerd_msg
|
broker_msg: dict = msg.brokerd_msg
|
||||||
|
|
||||||
|
# XXX NOTE, this presumes the rxed "error" is
|
||||||
|
# order-dialog-cancel-causing, THUS backends much ONLY
|
||||||
|
# relay errors of this "severity"!!
|
||||||
log.error(
|
log.error(
|
||||||
f'Order {oid}->{resp} with:\n{pformat(broker_msg)}'
|
f'Order errored ??\n'
|
||||||
|
f'oid: {oid!r}\n'
|
||||||
|
f'\n'
|
||||||
|
f'{pformat(broker_msg)}\n'
|
||||||
|
f'\n'
|
||||||
|
f'=> CANCELLING ORDER DIALOG <=\n'
|
||||||
|
|
||||||
|
# from tractor.devx.pformat import ppfmt
|
||||||
|
# !TODO LOL, wtf the msg is causing
|
||||||
|
# a recursion bug!
|
||||||
|
# -[ ] get this shit on msgspec stat!
|
||||||
|
# f'{ppfmt(broker_msg)}'
|
||||||
)
|
)
|
||||||
|
# do all the things for a cancel:
|
||||||
|
# - drop order-msg dialog from client table
|
||||||
|
# - delete level line from view
|
||||||
|
mode.on_cancel(oid)
|
||||||
|
|
||||||
case Status(resp='canceled'):
|
case Status(resp='canceled'):
|
||||||
# delete level line from view
|
# delete level line from view
|
||||||
|
@ -1178,10 +1223,10 @@ async def process_trade_msg(
|
||||||
# TODO: UX for a "pending" clear/live order
|
# TODO: UX for a "pending" clear/live order
|
||||||
log.info(f'Dark order triggered for {fmtmsg}')
|
log.info(f'Dark order triggered for {fmtmsg}')
|
||||||
|
|
||||||
|
# TODO: do the struct-msg version, blah blah..
|
||||||
|
# req=Order(exec_mode='live', action='alert') as req,
|
||||||
case Status(
|
case Status(
|
||||||
resp='triggered',
|
resp='triggered',
|
||||||
# TODO: do the struct-msg version, blah blah..
|
|
||||||
# req=Order(exec_mode='live', action='alert') as req,
|
|
||||||
req={
|
req={
|
||||||
'exec_mode': 'live',
|
'exec_mode': 'live',
|
||||||
'action': 'alert',
|
'action': 'alert',
|
||||||
|
|
Loading…
Reference in New Issue