Vastly simplify order mode line management
- generate lines from staged `Order` msgs - apply level update callback to each order that dynamically updates the order size from the allocator calcs - pass order msg instances to the ems client for submission - update order size on line moves - add `Order` msg and `Symbol` refs to each dialogfsp_feeds
							parent
							
								
									ccad7cfc2a
								
							
						
					
					
						commit
						873d531521
					
				|  | @ -219,7 +219,10 @@ async def handle_viewmode_kb_inputs( | |||
| 
 | ||||
|             # XXX: order matters here for line style! | ||||
|             view.mode._trigger_type = trigger_type | ||||
|             view.mode.stage_order(action, trigger_type=trigger_type) | ||||
|             view.mode.stage_order( | ||||
|                 action, | ||||
|                 trigger_type=trigger_type, | ||||
|             ) | ||||
| 
 | ||||
|             prefix = trigger_type + '-' if action != 'alert' else '' | ||||
|             view._chart.window().set_mode_name(f'{prefix}{action}') | ||||
|  |  | |||
|  | @ -32,10 +32,11 @@ from ..clearing._client import open_ems, OrderBook | |||
| from ..data._source import Symbol | ||||
| from ..log import get_logger | ||||
| from ._editors import LineEditor, ArrowEditor | ||||
| from ._lines import LevelLine | ||||
| from ._lines import order_line, LevelLine | ||||
| from ._position import PositionTracker | ||||
| from ._window import MultiStatus | ||||
| from ._forms import FieldsForm | ||||
| from ..clearing._messages import Order | ||||
| # from ._forms import FieldsForm | ||||
| 
 | ||||
| 
 | ||||
| log = get_logger(__name__) | ||||
|  | @ -48,6 +49,8 @@ class OrderDialog(BaseModel): | |||
|     ''' | ||||
|     # TODO: use ``pydantic.UUID4`` field | ||||
|     uuid: str | ||||
|     order: Order | ||||
|     symbol: Symbol | ||||
|     line: LevelLine | ||||
|     last_status_close: Callable = lambda: None | ||||
|     msgs: dict[str, dict] = {} | ||||
|  | @ -84,54 +87,232 @@ class OrderMode: | |||
|     arrows: ArrowEditor | ||||
|     multistatus: MultiStatus | ||||
|     pp: PositionTracker | ||||
|     allocator: 'Allocator'  # noqa | ||||
| 
 | ||||
|     name: str = 'order' | ||||
|     dialogs: dict[str, OrderDialog] = field(default_factory=dict) | ||||
| 
 | ||||
|     _colors = { | ||||
|         'alert': 'alert_yellow', | ||||
|         'buy': 'buy_green', | ||||
|         'sell': 'sell_red', | ||||
|     } | ||||
|     _action: str = 'alert' | ||||
|     _exec_mode: str = 'dark' | ||||
|     _size: float = 100.0 | ||||
|     _staged_order: Optional[Order] = None | ||||
| 
 | ||||
|     dialogs: dict[str, OrderDialog] = field(default_factory=dict) | ||||
|     def line_from_order( | ||||
|         self, | ||||
| 
 | ||||
|     def uuid(self) -> str: | ||||
|         return str(uuid.uuid4()) | ||||
|         order: Order, | ||||
|         symbol: Symbol, | ||||
| 
 | ||||
|     @property | ||||
|     def pp_config(self) -> FieldsForm: | ||||
|         return self.chart.linked.godwidget.pp_config | ||||
|         **line_kwargs, | ||||
| 
 | ||||
|     def set_exec( | ||||
|     ) -> LevelLine: | ||||
| 
 | ||||
|         line = order_line( | ||||
| 
 | ||||
|             self.chart, | ||||
|             # TODO: convert these values into human-readable form | ||||
|             # (i.e. with k, m, M, B) type embedded suffixes | ||||
|             level=order.price, | ||||
|             # level_digits=symbol.digits(), | ||||
|             action=order.action, | ||||
| 
 | ||||
|             size=order.size, | ||||
|             # TODO: we need truncation checks in the EMS for this? | ||||
|             # size_digits=min(symbol.lot_digits(), 3), | ||||
|             color=self._colors[order.action], | ||||
| 
 | ||||
|             dotted=True if ( | ||||
|                 order.exec_mode == 'dark' and | ||||
|                 order.action != 'alert' | ||||
|             ) else False, | ||||
| 
 | ||||
|             **line_kwargs, | ||||
| 
 | ||||
|         ) | ||||
| 
 | ||||
|         # define a callback applied for each level change to the line | ||||
|         # which will recompute the order size based on allocator | ||||
|         # settings: | ||||
|         def update_from_size_calc(y: float) -> None: | ||||
| 
 | ||||
|             order_info = self.allocator.get_order_info( | ||||
|                 symbol=symbol, | ||||
|                 price=y, | ||||
|             ) | ||||
|             line.update_labels(order_info) | ||||
|             order.price = y | ||||
|             order.size = order_info['size'] | ||||
| 
 | ||||
|             # return is used to update labels implicitly | ||||
|             return order_info | ||||
| 
 | ||||
|         update_from_size_calc(order.price) | ||||
| 
 | ||||
|         # set level update cb | ||||
|         # line._on_level_change = partial(update_from_size_calc, order=order) | ||||
|         line._on_level_change = update_from_size_calc | ||||
| 
 | ||||
|         return line | ||||
| 
 | ||||
|     def stage_order( | ||||
|         self, | ||||
| 
 | ||||
|         action: str, | ||||
|         size: Optional[int] = None, | ||||
|         trigger_type: str, | ||||
| 
 | ||||
|     ) -> None: | ||||
|         ''' | ||||
|         Set execution mode. | ||||
|         '''Stage an order for submission. | ||||
| 
 | ||||
|         ''' | ||||
|         # not initialized yet | ||||
|         if not self.chart.linked.cursor: | ||||
|         chart = self.chart | ||||
|         cursor = chart.linked.cursor | ||||
|         if not (chart and cursor and cursor.active_plot): | ||||
|             return | ||||
| 
 | ||||
|         self._action = action | ||||
|         self.lines.stage_line( | ||||
|         chart = cursor.active_plot | ||||
|         price = cursor._datum_xy[1] | ||||
|         symbol = self.chart.linked.symbol | ||||
| 
 | ||||
|             color=self._colors[action], | ||||
|             # hl_on_hover=True if self._exec_mode == 'live' else False, | ||||
|             dotted=True if ( | ||||
|                 self._exec_mode == 'dark' and action != 'alert' | ||||
|             ) else False, | ||||
|             size=size or self._size, | ||||
|         order = self._staged_order = Order( | ||||
|             action=action, | ||||
|             price=price, | ||||
|             size=0, | ||||
|             symbol=symbol, | ||||
|             brokers=symbol.brokers, | ||||
|             oid='',  # filled in on submit | ||||
|             exec_mode=trigger_type,  # dark or live | ||||
|         ) | ||||
| 
 | ||||
|         line = self.line_from_order( | ||||
|             order, | ||||
|             symbol, | ||||
| 
 | ||||
|             show_markers=True, | ||||
|             # just for the stage line to avoid | ||||
|             # flickering while moving the cursor | ||||
|             # around where it might trigger highlight | ||||
|             # then non-highlight depending on sensitivity | ||||
|             always_show_labels=True, | ||||
|             # don't highlight the "staging" line | ||||
|             highlight_on_hover=False, | ||||
|             # prevent flickering of marker while moving/tracking cursor | ||||
|             only_show_markers_on_hover=False, | ||||
|         ) | ||||
| 
 | ||||
|         line = self.lines.stage_line(line) | ||||
| 
 | ||||
|         # hide crosshair y-line and label | ||||
|         cursor.hide_xhair() | ||||
| 
 | ||||
|         # add line to cursor trackers | ||||
|         cursor._trackers.add(line) | ||||
| 
 | ||||
|         return line | ||||
| 
 | ||||
|     def submit_order( | ||||
|         self, | ||||
| 
 | ||||
|     ) -> OrderDialog: | ||||
|         '''Send execution order to EMS return a level line to | ||||
|         represent the order on a chart. | ||||
| 
 | ||||
|         ''' | ||||
|         staged = self._staged_order | ||||
|         symbol = staged.symbol | ||||
|         oid = str(uuid.uuid4()) | ||||
| 
 | ||||
|         # format order data for ems | ||||
|         order = staged.copy( | ||||
|             update={ | ||||
|                 'symbol': symbol.key, | ||||
|                 'oid': oid, | ||||
|             } | ||||
|         ) | ||||
| 
 | ||||
|         line = self.line_from_order( | ||||
|             order, | ||||
|             symbol, | ||||
| 
 | ||||
|             show_markers=True, | ||||
|             only_show_markers_on_hover=True, | ||||
|         ) | ||||
| 
 | ||||
|         # register the "submitted" line under the cursor | ||||
|         # to be displayed when above order ack arrives | ||||
|         # (means the marker graphic doesn't show on screen until the | ||||
|         # order is live in the emsd). | ||||
| 
 | ||||
|         # TODO: update the line once an ack event comes back | ||||
|         # from the EMS! | ||||
|         # maybe place a grey line in "submission" mode | ||||
|         # which will be updated to it's appropriate action | ||||
|         # color once the submission ack arrives. | ||||
|         self.lines.submit_line( | ||||
|             line=line, | ||||
|             uuid=oid, | ||||
|         ) | ||||
| 
 | ||||
|         dialog = OrderDialog( | ||||
|             uuid=oid, | ||||
|             order=order, | ||||
|             symbol=symbol, | ||||
|             line=line, | ||||
|             last_status_close=self.multistatus.open_status( | ||||
|                 f'submitting {self._trigger_type}-{order.action}', | ||||
|                 final_msg=f'submitted {self._trigger_type}-{order.action}', | ||||
|                 clear_on_next=True, | ||||
|             ) | ||||
|         ) | ||||
| 
 | ||||
|         # TODO: create a new ``OrderLine`` with this optional var defined | ||||
|         line.dialog = dialog | ||||
| 
 | ||||
|         # enter submission which will be popped once a response | ||||
|         # from the EMS is received to move the order to a different# status | ||||
|         self.dialogs[oid] = dialog | ||||
| 
 | ||||
|         # hook up mouse drag handlers | ||||
|         line._on_drag_start = self.order_line_modify_start | ||||
|         line._on_drag_end = self.order_line_modify_complete | ||||
| 
 | ||||
|         # send order cmd to ems | ||||
|         self.book.send(order) | ||||
| 
 | ||||
|         return dialog | ||||
| 
 | ||||
|     # order-line modify handlers | ||||
| 
 | ||||
|     def order_line_modify_start( | ||||
|         self, | ||||
|         line: LevelLine, | ||||
| 
 | ||||
|     ) -> None: | ||||
| 
 | ||||
|         print(f'Line modify: {line}') | ||||
|         # cancel original order until new position is found? | ||||
|         # TODO: make a config option for this behaviour.. | ||||
| 
 | ||||
|     def order_line_modify_complete( | ||||
|         self, | ||||
|         line: LevelLine, | ||||
| 
 | ||||
|     ) -> None: | ||||
| 
 | ||||
|         level = line.value() | ||||
|         # updateb by level change callback set in ``.line_from_order()`` | ||||
|         size = line.dialog.order.size | ||||
| 
 | ||||
|         self.book.update( | ||||
|             uuid=line.dialog.uuid, | ||||
|             price=level, | ||||
|             size=size, | ||||
|         ) | ||||
| 
 | ||||
|     # ems response loop handlers | ||||
| 
 | ||||
|     def on_submit( | ||||
|         self, | ||||
|         uuid: str | ||||
|  | @ -234,80 +415,6 @@ class OrderMode: | |||
|                 f'Received cancel for unsubmitted order {pformat(msg)}' | ||||
|             ) | ||||
| 
 | ||||
|     def submit_exec( | ||||
|         self, | ||||
|         size: Optional[float] = None, | ||||
| 
 | ||||
|     ) -> OrderDialog: | ||||
|         """Send execution order to EMS return a level line to | ||||
|         represent the order on a chart. | ||||
| 
 | ||||
|         """ | ||||
|         # register the "staged" line under the cursor | ||||
|         # to be displayed when above order ack arrives | ||||
|         # (means the line graphic doesn't show on screen until the | ||||
|         # order is live in the emsd). | ||||
|         oid = str(uuid.uuid4()) | ||||
| 
 | ||||
|         size = size or self._size | ||||
| 
 | ||||
|         cursor = self.chart.linked.cursor | ||||
|         chart = cursor.active_plot | ||||
|         y = cursor._datum_xy[1] | ||||
| 
 | ||||
|         symbol = self.chart.linked.symbol | ||||
|         action = self._action | ||||
| 
 | ||||
|         # TODO: update the line once an ack event comes back | ||||
|         # from the EMS! | ||||
| 
 | ||||
|         # TODO: place a grey line in "submission" mode | ||||
|         # which will be updated to it's appropriate action | ||||
|         # color once the submission ack arrives. | ||||
| 
 | ||||
|         # make line graphic if order push was sucessful | ||||
|         line = self.lines.create_order_line( | ||||
|             oid, | ||||
|             level=y, | ||||
|             chart=chart, | ||||
|             size=size, | ||||
|             action=action, | ||||
|         ) | ||||
| 
 | ||||
|         dialog = OrderDialog( | ||||
|             uuid=oid, | ||||
|             line=line, | ||||
|             last_status_close=self.multistatus.open_status( | ||||
|                 f'submitting {self._exec_mode}-{action}', | ||||
|                 final_msg=f'submitted {self._exec_mode}-{action}', | ||||
|                 clear_on_next=True, | ||||
|             ) | ||||
|         ) | ||||
| 
 | ||||
|         # TODO: create a new ``OrderLine`` with this optional var defined | ||||
|         line.dialog = dialog | ||||
| 
 | ||||
|         # enter submission which will be popped once a response | ||||
|         # from the EMS is received to move the order to a different# status | ||||
|         self.dialogs[oid] = dialog | ||||
| 
 | ||||
|         # hook up mouse drag handlers | ||||
|         line._on_drag_start = self.order_line_modify_start | ||||
|         line._on_drag_end = self.order_line_modify_complete | ||||
| 
 | ||||
|         # send order cmd to ems | ||||
|         self.book.send( | ||||
|             uuid=oid, | ||||
|             symbol=symbol.key, | ||||
|             brokers=symbol.brokers, | ||||
|             price=y, | ||||
|             size=size, | ||||
|             action=action, | ||||
|             exec_mode=self._exec_mode, | ||||
|         ) | ||||
| 
 | ||||
|         return dialog | ||||
| 
 | ||||
|     def cancel_orders_under_cursor(self) -> list[str]: | ||||
|         return self.cancel_orders_from_lines( | ||||
|             self.lines.lines_under_cursor() | ||||
|  | @ -353,32 +460,6 @@ class OrderMode: | |||
| 
 | ||||
|         return ids | ||||
| 
 | ||||
|     # order-line modify handlers | ||||
| 
 | ||||
|     def order_line_modify_start( | ||||
|         self, | ||||
|         line: LevelLine, | ||||
| 
 | ||||
|     ) -> None: | ||||
| 
 | ||||
|         print(f'Line modify: {line}') | ||||
|         # cancel original order until new position is found | ||||
| 
 | ||||
|     def order_line_modify_complete( | ||||
|         self, | ||||
|         line: LevelLine, | ||||
| 
 | ||||
|     ) -> None: | ||||
| 
 | ||||
|         self.book.update( | ||||
|             uuid=line.dialog.uuid, | ||||
| 
 | ||||
|             # TODO: must adjust sizing | ||||
|             # - should we round this to a nearest tick here and how? | ||||
|             # - need to recompute the size from the pp allocator | ||||
|             price=line.value(), | ||||
|         ) | ||||
| 
 | ||||
| 
 | ||||
| async def run_order_mode( | ||||
| 
 | ||||
|  | @ -419,8 +500,10 @@ async def run_order_mode( | |||
| 
 | ||||
|         log.info("Opening order mode") | ||||
| 
 | ||||
|         alloc = chart.linked.godwidget.pp_pane.model | ||||
|         pp = PositionTracker(chart) | ||||
|         pp.hide() | ||||
|         alloc._position = pp | ||||
| 
 | ||||
|         mode = OrderMode( | ||||
|             chart, | ||||
|  | @ -429,6 +512,7 @@ async def run_order_mode( | |||
|             arrows, | ||||
|             multistatus, | ||||
|             pp, | ||||
|             allocator=alloc, | ||||
|         ) | ||||
| 
 | ||||
|         # TODO: create a mode "manager" of sorts? | ||||
|  | @ -475,7 +559,7 @@ async def run_order_mode( | |||
| 
 | ||||
|         # start async input handling for chart's view | ||||
|         async with ( | ||||
|             chart._vb.open_async_input_handler(), | ||||
|             chart.view.open_async_input_handler(), | ||||
| 
 | ||||
|             # TODO: config form handler nursery | ||||
| 
 | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue