Add a client side order dialog type for tracking flows in the UI

pause_feeds_on_sym_switch
Tyler Goodlet 2021-07-13 15:28:19 -04:00
parent bd754b740d
commit 0dc18598fb
2 changed files with 118 additions and 80 deletions

View File

@ -237,7 +237,6 @@ class LineEditor:
log.warning(f'No line for {uuid} could be found?') log.warning(f'No line for {uuid} could be found?')
return return
else: else:
assert line.oid == uuid
line.show_labels() line.show_labels()
# TODO: other flashy things to indicate the order is active # TODO: other flashy things to indicate the order is active
@ -260,18 +259,16 @@ class LineEditor:
self, self,
line: LevelLine = None, line: LevelLine = None,
uuid: str = None, uuid: str = None,
) -> LevelLine:
"""Remove a line by refernce or uuid. ) -> Optional[LevelLine]:
'''Remove a line by refernce or uuid.
If no lines or ids are provided remove all lines under the If no lines or ids are provided remove all lines under the
cursor position. cursor position.
""" '''
if line:
uuid = line.oid
# try to look up line from our registry # try to look up line from our registry
line = self._order_lines.pop(uuid, None) line = self._order_lines.pop(uuid, line)
if line: if line:
# if hovered remove from cursor set # if hovered remove from cursor set
@ -284,7 +281,12 @@ class LineEditor:
# just because we never got a un-hover event # just because we never got a un-hover event
cursor.show_xhair() cursor.show_xhair()
log.debug(f'deleting {line} with oid: {uuid}')
line.delete() line.delete()
else:
log.warning(f'Could not find line for {line}')
return line return line

View File

@ -41,12 +41,31 @@ log = get_logger(__name__)
class Position(BaseModel): class Position(BaseModel):
'''Basic pp representation with attached fills history.
'''
symbol: Symbol symbol: Symbol
size: float size: float
avg_price: float avg_price: float # TODO: contextual pricing
fills: Dict[str, Any] = {} fills: Dict[str, Any] = {}
class OrderDialog(BaseModel):
'''Trade dialogue meta-data describing the lifetime
of an order submission to ``emsd`` from a chart.
'''
uuid: str
line: LevelLine
last_status_close: Callable = lambda: None
msgs: dict[str, dict] = {}
fills: Dict[str, Any] = {}
class Config:
arbitrary_types_allowed = True
underscore_attrs_are_private = False
@dataclass @dataclass
class OrderMode: class OrderMode:
'''Major mode for placing orders on a chart view. '''Major mode for placing orders on a chart view.
@ -60,8 +79,8 @@ class OrderMode:
Current manual: Current manual:
a -> alert a -> alert
s/ctrl -> submission type modifier {on: live, off: dark} s/ctrl -> submission type modifier {on: live, off: dark}
f (fill) -> buy limit order f (fill) -> 'buy' limit order
d (dump) -> sell limit order d (dump) -> 'sell' limit order
c (cancel) -> cancel order under cursor c (cancel) -> cancel order under cursor
cc -> cancel all submitted orders on chart cc -> cancel all submitted orders on chart
mouse click and drag -> modify current order under cursor mouse click and drag -> modify current order under cursor
@ -85,8 +104,7 @@ class OrderMode:
_position: Dict[str, Any] = field(default_factory=dict) _position: Dict[str, Any] = field(default_factory=dict)
_position_line: dict = None _position_line: dict = None
_pending_submissions: dict[str, (LevelLine, Callable)] = field( dialogs: dict[str, OrderDialog] = field(default_factory=dict)
default_factory=dict)
def on_position_update( def on_position_update(
self, self,
@ -139,33 +157,34 @@ class OrderMode:
action=action, action=action,
) )
def on_submit(self, uuid: str) -> dict: def on_submit(self, uuid: str) -> OrderDialog:
"""On order submitted event, commit the order line '''Order submitted status event handler.
and registered order uuid, store ack time stamp.
TODO: annotate order line with submission type ('live' vs. Commit the order line and registered order uuid, store ack time stamp.
'dark').
""" '''
line = self.lines.commit_line(uuid) line = self.lines.commit_line(uuid)
pending = self._pending_submissions.get(uuid) # a submission is the start of a new order dialog
if pending: dialog = self.dialogs[uuid]
order_line, func = pending dialog.line = line
assert order_line is line dialog.last_status_close()
func()
return line return dialog
def on_fill( def on_fill(
self, self,
uuid: str, uuid: str,
price: float, price: float,
arrow_index: float, arrow_index: float,
pointing: Optional[str] = None pointing: Optional[str] = None,
# delete_line: bool = False,
) -> None: ) -> None:
line = self.lines._order_lines.get(uuid) dialog = self.dialogs[uuid]
line = dialog.line
if line: if line:
self.arrows.add( self.arrows.add(
uuid, uuid,
@ -174,6 +193,8 @@ class OrderMode:
pointing=pointing, pointing=pointing,
color=line.color color=line.color
) )
else:
log.warn("No line for order {uuid}!?")
async def on_exec( async def on_exec(
self, self,
@ -181,11 +202,6 @@ class OrderMode:
msg: Dict[str, Any], msg: Dict[str, Any],
) -> None: ) -> None:
# only once all fills have cleared and the execution
# is complet do we remove our "order line"
line = self.lines.remove_line(uuid=uuid)
log.debug(f'deleting {line} with oid: {uuid}')
# DESKTOP NOTIFICATIONS # DESKTOP NOTIFICATIONS
# #
# TODO: this in another task? # TODO: this in another task?
@ -212,10 +228,9 @@ class OrderMode:
self.lines.remove_line(uuid=uuid) self.lines.remove_line(uuid=uuid)
self.chart.linked.cursor.show_xhair() self.chart.linked.cursor.show_xhair()
pending = self._pending_submissions.pop(uuid, None) dialog = self.dialogs.pop(uuid, None)
if pending: if dialog:
order_line, func = pending dialog.last_status_close()
func()
else: else:
log.warning( log.warning(
f'Received cancel for unsubmitted order {pformat(msg)}' f'Received cancel for unsubmitted order {pformat(msg)}'
@ -225,7 +240,7 @@ class OrderMode:
self, self,
size: Optional[float] = None, size: Optional[float] = None,
) -> LevelLine: ) -> OrderDialog:
"""Send execution order to EMS return a level line to """Send execution order to EMS return a level line to
represent the order on a chart. represent the order on a chart.
@ -234,7 +249,7 @@ class OrderMode:
# to be displayed when above order ack arrives # to be displayed when above order ack arrives
# (means the line graphic doesn't show on screen until the # (means the line graphic doesn't show on screen until the
# order is live in the emsd). # order is live in the emsd).
uid = str(uuid.uuid4()) oid = str(uuid.uuid4())
size = size or self._size size = size or self._size
@ -246,9 +261,46 @@ class OrderMode:
action = self._action 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.status_bar.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 # send order cmd to ems
self.book.send( self.book.send(
uuid=uid, uuid=oid,
symbol=symbol.key, symbol=symbol.key,
brokers=symbol.brokers, brokers=symbol.brokers,
price=y, price=y,
@ -257,36 +309,7 @@ class OrderMode:
exec_mode=self._exec_mode, exec_mode=self._exec_mode,
) )
# TODO: update the line once an ack event comes back return dialog
# from the EMS!
# make line graphic if order push was
# sucessful
line = self.lines.create_order_line(
uid,
level=y,
chart=chart,
size=size,
action=action,
)
line.oid = uid
# enter submission which will be popped once a response
# from the EMS is received to move the order to a different# status
self._pending_submissions[uid] = (
line,
self.status_bar.open_status(
f'submitting {self._exec_mode}-{action}',
final_msg=f'submitted {self._exec_mode}-{action}',
clear_on_next=True,
)
)
# hook up mouse drag handlers
line._on_drag_start = self.order_line_modify_start
line._on_drag_end = self.order_line_modify_complete
return line
def cancel_orders_under_cursor(self) -> list[str]: def cancel_orders_under_cursor(self) -> list[str]:
return self.cancel_orders_from_lines( return self.cancel_orders_from_lines(
@ -317,16 +340,16 @@ class OrderMode:
# cancel all active orders and triggers # cancel all active orders and triggers
for line in lines: for line in lines:
oid = getattr(line, 'oid', None) dialog = getattr(line, 'dialog', None)
if oid: if dialog:
self._pending_submissions[oid] = ( oid = dialog.uuid
line,
self.status_bar.open_status( cancel_status_close = self.status_bar.open_status(
f'cancelling order {oid[:6]}', f'cancelling order {oid[:6]}',
group_key=key, group_key=key,
),
) )
dialog.last_status_close = cancel_status_close
ids.append(oid) ids.append(oid)
self.book.cancel(uuid=oid) self.book.cancel(uuid=oid)
@ -338,16 +361,20 @@ class OrderMode:
def order_line_modify_start( def order_line_modify_start(
self, self,
line: LevelLine, line: LevelLine,
) -> None: ) -> None:
print(f'Line modify: {line}') print(f'Line modify: {line}')
# cancel original order until new position is found # cancel original order until new position is found
def order_line_modify_complete( def order_line_modify_complete(
self, self,
line: LevelLine, line: LevelLine,
) -> None: ) -> None:
self.book.update( self.book.update(
uuid=line.oid, uuid=line.dialog.uuid,
# TODO: should we round this to a nearest tick here? # TODO: should we round this to a nearest tick here?
price=line.value(), price=line.value(),
@ -464,6 +491,10 @@ async def start_order_mode(
resp = msg['resp'] resp = msg['resp']
oid = msg['oid'] oid = msg['oid']
dialog = order_mode.dialogs[oid]
# record message to dialog tracking
dialog.msgs[oid] = msg
# response to 'action' request (buy/sell) # response to 'action' request (buy/sell)
if resp in ( if resp in (
'dark_submitted', 'dark_submitted',
@ -496,16 +527,21 @@ async def start_order_mode(
order_mode.on_fill( order_mode.on_fill(
oid, oid,
price=msg['trigger_price'], price=msg['trigger_price'],
arrow_index=get_index(time.time()) arrow_index=get_index(time.time()),
) )
order_mode.lines.remove_line(uuid=oid)
await order_mode.on_exec(oid, msg) await order_mode.on_exec(oid, msg)
# response to completed 'action' request for buy/sell # response to completed 'action' request for buy/sell
elif resp in ( elif resp in (
'broker_executed', 'broker_executed',
): ):
# right now this is just triggering a system alert
await order_mode.on_exec(oid, msg) await order_mode.on_exec(oid, msg)
if msg['brokerd_msg']['remaining'] == 0:
order_mode.lines.remove_line(uuid=oid)
# each clearing tick is responded individually # each clearing tick is responded individually
elif resp in ('broker_filled',): elif resp in ('broker_filled',):