From ca23825aff25c3134b6ffeec1b4ecd196df51ba9 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Wed, 16 Jun 2021 08:28:57 -0400 Subject: [PATCH] Start input handling **after** order mode is up --- piker/ui/_chart.py | 40 +++++----- piker/ui/order_mode.py | 168 +++++++++++++++++++++++------------------ 2 files changed, 115 insertions(+), 93 deletions(-) diff --git a/piker/ui/_chart.py b/piker/ui/_chart.py index 80ce283e..8337d75d 100644 --- a/piker/ui/_chart.py +++ b/piker/ui/_chart.py @@ -158,18 +158,20 @@ class GodWidget(QtGui.QWidget): # self.toolbar_layout.addWidget(self.strategy_box) def load_symbol( + self, providername: str, symbol_key: str, loglevel: str, ohlc: bool = True, reset: bool = False, - ) -> None: - """Load a new contract into the charting app. + + ) -> trio.Event: + '''Load a new contract into the charting app. Expects a ``numpy`` structured array containing all the ohlcv fields. - """ + ''' # our symbol key style is always lower case symbol_key = symbol_key.lower() @@ -178,6 +180,8 @@ class GodWidget(QtGui.QWidget): linkedsplits = self.get_chart_symbol(fqsn) + order_mode_started = trio.Event() + if not self.vbox.isEmpty(): # XXX: this is CRITICAL especially with pixel buffer caching self.linkedsplits.hide() @@ -200,10 +204,15 @@ class GodWidget(QtGui.QWidget): providername, symbol_key, loglevel, + order_mode_started, ) self.set_chart_symbol(fqsn, linkedsplits) + else: + # symbol is already loaded and ems ready + order_mode_started.set() + self.vbox.addWidget(linkedsplits) # chart is already in memory so just focus it @@ -223,6 +232,8 @@ class GodWidget(QtGui.QWidget): f'tick:{symbol.tick_size}' ) + return order_mode_started + class LinkedSplits(QtGui.QWidget): ''' @@ -1527,6 +1538,8 @@ async def display_symbol_data( sym: str, loglevel: str, + order_mode_started: trio.Event, + ) -> None: '''Spawn a real-time displayed and updated chart for provider symbol. @@ -1623,7 +1636,6 @@ async def display_symbol_data( }, }) - # load initial fsp chain (otherwise known as "indicators") n.start_soon( spawn_fsps, @@ -1644,6 +1656,7 @@ async def display_symbol_data( wap_in_history, ) + # TODO: instead we should start based on instrument trading hours? # wait for a first quote before we start any update tasks # quote = await feed.receive() # log.info(f'Received first quote {quote}') @@ -1651,24 +1664,11 @@ async def display_symbol_data( n.start_soon( check_for_new_bars, feed, - # delay, ohlcv, linkedsplits ) - # interactive testing - # n.start_soon( - # test_bed, - # ohlcv, - # chart, - # linkedsplits, - # ) - - # start async input handling for chart's view - # await godwidget._task_stack.enter_async_context( - async with chart._vb.open_async_input_handler(): - - await start_order_mode(chart, symbol, provider) + await start_order_mode(chart, symbol, provider, order_mode_started) async def load_providers( @@ -1773,7 +1773,7 @@ async def _async_main( symbol, _, provider = sym.rpartition('.') # this internally starts a ``display_symbol_data()`` task above - godwidget.load_symbol(provider, symbol, loglevel) + order_mode_ready = godwidget.load_symbol(provider, symbol, loglevel) # spin up a search engine for the local cached symbol set async with _search.register_symbol_search( @@ -1791,6 +1791,8 @@ async def _async_main( # the chart's select cache root_n.start_soon(load_providers, brokernames, loglevel) + await order_mode_ready.wait() + # start handling search bar kb inputs async with ( diff --git a/piker/ui/order_mode.py b/piker/ui/order_mode.py index 081a896e..78c4b693 100644 --- a/piker/ui/order_mode.py +++ b/piker/ui/order_mode.py @@ -26,8 +26,9 @@ from typing import Optional, Dict, Callable, Any import uuid import pyqtgraph as pg -import trio from pydantic import BaseModel +import trio +# from trio_typing import TaskStatus from ._graphics._lines import LevelLine, position_line from ._editors import LineEditor, ArrowEditor, _order_lines @@ -108,6 +109,10 @@ class OrderMode: """Set execution mode. """ + # not initialized yet + if not self.chart._cursor: + return + self._action = action self.lines.stage_line( @@ -306,10 +311,14 @@ async def open_order_mode( async def start_order_mode( + chart: 'ChartPlotWidget', # noqa symbol: Symbol, brokername: str, + # task_status: TaskStatus[trio.Event] = trio.TASK_STATUS_IGNORED, + started: trio.Event, + ) -> None: '''Activate chart-trader order mode loop: - connect to emsd @@ -322,7 +331,11 @@ async def start_order_mode( # spawn EMS actor-service async with ( open_ems(brokername, symbol) as (book, trades_stream, positions), - open_order_mode(symbol, chart, book) as order_mode + open_order_mode(symbol, chart, book) as order_mode, + + # # start async input handling for chart's view + # # await godwidget._task_stack.enter_async_context( + # chart._vb.open_async_input_handler(), ): # update any exising positions @@ -345,83 +358,90 @@ async def start_order_mode( # Begin order-response streaming done() - # this is where we receive **back** messages - # about executions **from** the EMS actor - async for msg in trades_stream: + # start async input handling for chart's view + async with chart._vb.open_async_input_handler(): - fmsg = pformat(msg) - log.info(f'Received order msg:\n{fmsg}') + # signal to top level symbol loading task we're ready + # to handle input since the ems connection is ready + started.set() - name = msg['name'] - if name in ( - 'position', - ): - # show line label once order is live - order_mode.on_position_update(msg) - continue + # this is where we receive **back** messages + # about executions **from** the EMS actor + async for msg in trades_stream: - resp = msg['resp'] - oid = msg['oid'] + fmsg = pformat(msg) + log.info(f'Received order msg:\n{fmsg}') - # 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', - 'broker_inactive', - 'dark_cancelled' - ): - # delete level line from view - order_mode.on_cancel(oid) - - elif resp in ( - 'dark_triggered' - ): - log.info(f'Dark order triggered for {fmsg}') - - elif resp in ( - 'alert_triggered' - ): - # should only be one "fill" for an alert - # add a triangle and remove the level line - 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',): - - known_order = book._sent_orders.get(oid) - if not known_order: - log.warning(f'order {oid} is unknown') + name = msg['name'] + if name in ( + 'position', + ): + # show line label once order is live + order_mode.on_position_update(msg) continue - action = known_order.action - details = msg['brokerd_msg'] + resp = msg['resp'] + oid = msg['oid'] - # TODO: some kinda progress system - order_mode.on_fill( - oid, - price=details['price'], - pointing='up' if action == 'buy' else 'down', + # response to 'action' request (buy/sell) + if resp in ( + 'dark_submitted', + 'broker_submitted' + ): - # TODO: put the actual exchange timestamp - arrow_index=get_index(details['broker_time']), - ) + # 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', + 'broker_inactive', + 'dark_cancelled' + ): + # delete level line from view + order_mode.on_cancel(oid) + + elif resp in ( + 'dark_triggered' + ): + log.info(f'Dark order triggered for {fmsg}') + + elif resp in ( + 'alert_triggered' + ): + # should only be one "fill" for an alert + # add a triangle and remove the level line + 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',): + + known_order = book._sent_orders.get(oid) + if not known_order: + log.warning(f'order {oid} is unknown') + continue + + action = known_order.action + details = msg['brokerd_msg'] + + # TODO: some kinda progress system + order_mode.on_fill( + oid, + price=details['price'], + pointing='up' if action == 'buy' else 'down', + + # TODO: put the actual exchange timestamp + arrow_index=get_index(details['broker_time']), + )