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( | ||||
|         self, | ||||
| 
 | ||||
|         uuid: str, | ||||
|         price: float, | ||||
|         time_s: float, | ||||
| 
 | ||||
|         pointing: str | None = None, | ||||
| 
 | ||||
|     ) -> None: | ||||
|     ) -> bool: | ||||
|         ''' | ||||
|         Fill msg handler. | ||||
| 
 | ||||
|  | @ -575,60 +574,83 @@ class OrderMode: | |||
|         - 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 | ||||
|         chart = self.chart | ||||
| 
 | ||||
|         # XXX: seems to fail on certain types of races? | ||||
|         # 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: | ||||
|         if not lines: | ||||
|             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( | ||||
|         self, | ||||
|         uuid: str | ||||
|         uuid: str, | ||||
| 
 | ||||
|     ) -> None: | ||||
|     ) -> bool: | ||||
| 
 | ||||
|         msg: Order = self.client._sent_orders.pop(uuid, 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: | ||||
|         msg: Order|None = self.client._sent_orders.pop(uuid, None) | ||||
|         if msg is None: | ||||
|             log.warning( | ||||
|                 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]: | ||||
|         return self.cancel_orders( | ||||
|  | @ -1057,13 +1079,23 @@ async def process_trade_msg( | |||
|     if name in ( | ||||
|         'position', | ||||
|     ): | ||||
|         sym: MktPair = mode.chart.linked.mkt | ||||
|         mkt: MktPair = mode.chart.linked.mkt | ||||
|         pp_msg_symbol = msg['symbol'].lower() | ||||
|         fqme = sym.fqme | ||||
|         broker = sym.broker | ||||
|         pp_msg_bsmktid = msg['bs_mktid'] | ||||
|         fqme = mkt.fqme | ||||
|         broker = mkt.broker | ||||
|         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 | ||||
|             or pp_msg_symbol == fqme.removesuffix(f'.{broker}') | ||||
|             or | ||||
|             pp_msg_symbol == fqme.removesuffix(f'.{broker}') | ||||
|         ): | ||||
|             log.info( | ||||
|                 f'Loading position for `{fqme}`:\n' | ||||
|  | @ -1086,7 +1118,7 @@ async def process_trade_msg( | |||
|         return | ||||
| 
 | ||||
|     msg = Status(**msg) | ||||
|     resp = msg.resp | ||||
|     # resp: str = msg.resp | ||||
|     oid = msg.oid | ||||
|     dialog: Dialog = mode.dialogs.get(oid) | ||||
| 
 | ||||
|  | @ -1150,19 +1182,32 @@ async def process_trade_msg( | |||
|                         mode.on_submit(oid) | ||||
| 
 | ||||
|         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 | ||||
|             # expect it to just be **that** msg verbatim (since | ||||
|             # we'd presumably have only 1 `Error` msg-struct) | ||||
|             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( | ||||
|                 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'): | ||||
|             # delete level line from view | ||||
|  | @ -1178,10 +1223,10 @@ async def process_trade_msg( | |||
|             # TODO: UX for a "pending" clear/live order | ||||
|             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( | ||||
|             resp='triggered', | ||||
|             # TODO: do the struct-msg version, blah blah.. | ||||
|             # req=Order(exec_mode='live', action='alert') as req, | ||||
|             req={ | ||||
|                 'exec_mode': 'live', | ||||
|                 'action': 'alert', | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue