From e6724b65599b3243c03d5c1d3cabc6c8b71f7068 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Mon, 18 Jan 2021 20:28:37 -0500 Subject: [PATCH] Move order mode handling into charting code --- piker/ui/_chart.py | 90 +++++++++++++++++++++++++++++++++------- piker/ui/_interaction.py | 47 ++++++++++----------- 2 files changed, 99 insertions(+), 38 deletions(-) diff --git a/piker/ui/_chart.py b/piker/ui/_chart.py index 25636794..91f8332f 100644 --- a/piker/ui/_chart.py +++ b/piker/ui/_chart.py @@ -18,8 +18,10 @@ High level Qt chart widgets. """ +from pprint import pformat from typing import Tuple, Dict, Any, Optional, Callable from functools import partial +import time from PyQt5 import QtCore, QtGui import numpy as np @@ -950,23 +952,83 @@ async def _async_main( linked_charts ) - async with open_order_mode( - chart, - ) as order_mode: + # spawn EMS actor-service + async with open_ems( + brokername, + symbol, + ) as (book, trades_stream): - # TODO: this should probably be implicitly spawned - # inside the above mngr? + async with open_order_mode( + chart, + book, + ) as order_mode: - # spawn EMS actor-service - await n.start( - open_ems, - order_mode, - brokername, - symbol, - ) + def get_index(time: float): + # XXX: not sure why the time is so off here + # looks like we're gonna have to do some fixing.. + ohlc = chart._shm.array + indexes = ohlc['time'] >= time - # probably where we'll eventually start the user input loop - await trio.sleep_forever() + if any(indexes): + return ohlc['index'][indexes[-1]] + else: + return ohlc['index'][-1] + + # Begin order-response streaming + + # this is where we receive **back** messages + # about executions **from** the EMS actor + async for msg in trades_stream: + + fmsg = pformat(msg) + log.info(f'Received order msg: {fmsg}') + + # delete the line from view + oid = msg['oid'] + resp = msg['resp'] + + # response to 'action' request (buy/sell) + if resp in ('dark_submitted', 'broker_submitted'): + + # show line label once order is live + order_mode.on_submit(oid) + + # resp to 'cancel' request or error condition + # for action request + elif resp in ('broker_cancelled', 'dark_cancelled'): + + # delete level line from view + order_mode.on_cancel(oid) + + elif resp in ('dark_executed'): + log.info(f'Dark order filled for {fmsg}') + + # for alerts add a triangle and remove the + # level line + if msg['cmd']['action'] == 'alert': + + # should only be one "fill" for an alert + order_mode.on_fill( + oid, + price=msg['trigger_price'], + arrow_index=get_index(time.time()) + ) + await order_mode.on_exec(oid, msg) + + # response to completed 'action' request for buy/sell + elif resp in ('broker_executed',): + await order_mode.on_exec(oid, msg) + + # each clearing tick is responded individually + elif resp in ('broker_filled',): + action = msg['action'] + # TODO: some kinda progress system + order_mode.on_fill( + oid, + price=msg['price'], + arrow_index=get_index(msg['broker_time']), + pointing='up' if action == 'buy' else 'down', + ) async def chart_from_quotes( diff --git a/piker/ui/_interaction.py b/piker/ui/_interaction.py index deee2440..4ba6280d 100644 --- a/piker/ui/_interaction.py +++ b/piker/ui/_interaction.py @@ -33,7 +33,7 @@ import numpy as np from ..log import get_logger from ._style import _min_points_to_show, hcolor, _font from ._graphics._lines import level_line, LevelLine -from .._ems import get_orders, OrderBook +from .._ems import OrderBook log = get_logger(__name__) @@ -370,12 +370,16 @@ class ArrowEditor: x: float, y: float, color='default', - pointing: str = 'up', + pointing: Optional[str] = None, ) -> pg.ArrowItem: """Add an arrow graphic to view at given (x, y). """ - angle = 90 if pointing == 'up' else -90 + angle = { + 'up': 90, + 'down': -90, + None: 0, + }[pointing] yb = pg.mkBrush(hcolor(color)) arrow = pg.ArrowItem( @@ -431,34 +435,33 @@ class OrderMode: self.lines.stage_line(color=self._colors[name]) def on_submit(self, uuid: str) -> dict: + """On order submitted event, commit the order line + and registered order uuid, store ack time stamp. + + TODO: annotate order line with submission type ('live' vs. + 'dark'). + + """ self.lines.commit_line(uuid) req_msg = self.book._sent_orders.get(uuid) req_msg['ack_time_ns'] = time.time_ns() - # self.book._confirmed_orders[uuid] = req_msg + return req_msg def on_fill( self, uuid: str, - msg: Dict[str, Any], + price: float, + arrow_index: float, + pointing: Optional[str] = None ) -> None: - log.info(f'New fill\n{pformat(msg)}') + line = self.lines._order_lines[uuid] - - # XXX: not sure why the time is so off here - # looks like we're gonna have to do some fixing.. - ohlc = self.chart._shm.array - indexes = ohlc['time'] >= msg['broker_time'] - if any(indexes): - arrow_index = ohlc['index'][indexes[-1]] - else: - arrow_index = ohlc['index'][-1] - self.arrows.add( uuid, arrow_index, - msg['price'], - pointing='up' if msg['action'] == 'buy' else 'down', + price, + pointing=pointing, color=line.color ) @@ -526,11 +529,12 @@ class OrderMode: @asynccontextmanager async def open_order_mode( chart, + book: OrderBook, ): # global _order_lines view = chart._vb - book = get_orders() + # book = get_orders() lines = LineEditor(view=view, _order_lines=_order_lines, chart=chart) arrows = ArrowEditor(chart, {}) @@ -539,11 +543,6 @@ async def open_order_mode( mode = OrderMode(chart, book, lines, arrows) view.mode = mode - # # setup local ui event streaming channels for request/resp - # # streamging with EMS daemon - # global _to_ems, _from_order_book - # _to_ems, _from_order_book = trio.open_memory_channel(100) - try: yield mode