Port chart code to new subsys apis
- make `GodWidget.load_symbol()` async - track loaded feeds with a private `._feeds` dict - add methods to pause/resume all feeds when chart is (un)focussed - add some commented test code for 2nd feed consumer task and rsi2 fsp - load async signal handler for view clickingfsp_feeds
parent
1cb311602c
commit
a1d4e61fc2
|
@ -63,17 +63,16 @@ from ._style import (
|
||||||
)
|
)
|
||||||
from . import _search
|
from . import _search
|
||||||
from . import _event
|
from . import _event
|
||||||
|
from ..data import maybe_open_shm_array
|
||||||
|
from ..data.feed import open_feed, Feed, install_brokerd_search
|
||||||
from ..data._source import Symbol
|
from ..data._source import Symbol
|
||||||
from ..data._sharedmem import ShmArray
|
from ..data._sharedmem import ShmArray
|
||||||
from ..data import maybe_open_shm_array
|
|
||||||
from .. import brokers
|
from .. import brokers
|
||||||
from .. import data
|
|
||||||
from ..log import get_logger
|
from ..log import get_logger
|
||||||
from ._exec import run_qtractor
|
from ._exec import run_qtractor
|
||||||
from ._interaction import ChartView
|
from ._interaction import ChartView
|
||||||
from .order_mode import run_order_mode
|
from .order_mode import run_order_mode
|
||||||
from .. import fsp
|
from .. import fsp
|
||||||
from ..data import feed
|
|
||||||
from ._forms import (
|
from ._forms import (
|
||||||
FieldsForm,
|
FieldsForm,
|
||||||
mk_form,
|
mk_form,
|
||||||
|
@ -169,13 +168,13 @@ class GodWidget(QWidget):
|
||||||
# self.strategy_box = StrategyBoxWidget(self)
|
# self.strategy_box = StrategyBoxWidget(self)
|
||||||
# self.toolbar_layout.addWidget(self.strategy_box)
|
# self.toolbar_layout.addWidget(self.strategy_box)
|
||||||
|
|
||||||
def load_symbol(
|
async def load_symbol(
|
||||||
|
|
||||||
self,
|
self,
|
||||||
|
|
||||||
providername: str,
|
providername: str,
|
||||||
symbol_key: str,
|
symbol_key: str,
|
||||||
loglevel: str,
|
loglevel: str,
|
||||||
ohlc: bool = True,
|
|
||||||
reset: bool = False,
|
reset: bool = False,
|
||||||
|
|
||||||
) -> trio.Event:
|
) -> trio.Event:
|
||||||
|
@ -226,12 +225,16 @@ class GodWidget(QWidget):
|
||||||
# symbol is already loaded and ems ready
|
# symbol is already loaded and ems ready
|
||||||
order_mode_started.set()
|
order_mode_started.set()
|
||||||
|
|
||||||
# TODO: we'll probably want per-instrument/provider state here?
|
# TODO:
|
||||||
|
# - we'll probably want per-instrument/provider state here?
|
||||||
# change the order config form over to the new chart
|
# change the order config form over to the new chart
|
||||||
|
|
||||||
# XXX: since the pp config is a singleton widget we have to
|
# XXX: since the pp config is a singleton widget we have to
|
||||||
# also switch it over to the new chart's interal-layout
|
# also switch it over to the new chart's interal-layout
|
||||||
self.linkedsplits.chart.qframe.hbox.removeWidget(self.pp_pane)
|
self.linkedsplits.chart.qframe.hbox.removeWidget(self.pp_pane)
|
||||||
linkedsplits.chart.qframe.hbox.addWidget(
|
chart = linkedsplits.chart
|
||||||
|
await chart.resume_all_feeds()
|
||||||
|
chart.qframe.hbox.addWidget(
|
||||||
self.pp_pane,
|
self.pp_pane,
|
||||||
alignment=Qt.AlignTop
|
alignment=Qt.AlignTop
|
||||||
)
|
)
|
||||||
|
@ -627,6 +630,8 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
self._graphics = {} # registry of underlying graphics
|
self._graphics = {} # registry of underlying graphics
|
||||||
self._overlays = set() # registry of overlay curve names
|
self._overlays = set() # registry of overlay curve names
|
||||||
|
|
||||||
|
self._feeds: dict[Symbol, Feed] = {}
|
||||||
|
|
||||||
self._labels = {} # registry of underlying graphics
|
self._labels = {} # registry of underlying graphics
|
||||||
self._ysticks = {} # registry of underlying graphics
|
self._ysticks = {} # registry of underlying graphics
|
||||||
|
|
||||||
|
@ -654,6 +659,14 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
# for when the splitter(s) are resized
|
# for when the splitter(s) are resized
|
||||||
self._vb.sigResized.connect(self._set_yrange)
|
self._vb.sigResized.connect(self._set_yrange)
|
||||||
|
|
||||||
|
async def resume_all_feeds(self):
|
||||||
|
for feed in self._feeds.values():
|
||||||
|
await feed.resume()
|
||||||
|
|
||||||
|
async def pause_all_feeds(self):
|
||||||
|
for feed in self._feeds.values():
|
||||||
|
await feed.pause()
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def view(self) -> ChartView:
|
def view(self) -> ChartView:
|
||||||
return self._vb
|
return self._vb
|
||||||
|
@ -1067,7 +1080,7 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
self.scene().leaveEvent(ev)
|
self.scene().leaveEvent(ev)
|
||||||
|
|
||||||
|
|
||||||
_clear_throttle_rate: int = 50 # Hz
|
_clear_throttle_rate: int = 60 # Hz
|
||||||
_book_throttle_rate: int = 16 # Hz
|
_book_throttle_rate: int = 16 # Hz
|
||||||
|
|
||||||
|
|
||||||
|
@ -1154,6 +1167,7 @@ async def chart_from_quotes(
|
||||||
|
|
||||||
# chart isn't actively shown so just skip render cycle
|
# chart isn't actively shown so just skip render cycle
|
||||||
if chart.linked.isHidden():
|
if chart.linked.isHidden():
|
||||||
|
await chart.pause_all_feeds()
|
||||||
continue
|
continue
|
||||||
|
|
||||||
for sym, quote in quotes.items():
|
for sym, quote in quotes.items():
|
||||||
|
@ -1377,6 +1391,7 @@ async def run_fsp(
|
||||||
group_key=group_status_key,
|
group_key=group_status_key,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# make sidepane config widget
|
||||||
class FspConfig(BaseModel):
|
class FspConfig(BaseModel):
|
||||||
|
|
||||||
class Config:
|
class Config:
|
||||||
|
@ -1423,7 +1438,10 @@ async def run_fsp(
|
||||||
) as stream,
|
) as stream,
|
||||||
|
|
||||||
# TODO:
|
# TODO:
|
||||||
# open_form_input_handling(sidepane),
|
open_form_input_handling(
|
||||||
|
sidepane,
|
||||||
|
focus_next=linkedsplits.godwidget
|
||||||
|
),
|
||||||
|
|
||||||
):
|
):
|
||||||
|
|
||||||
|
@ -1577,7 +1595,6 @@ async def check_for_new_bars(feed, ohlcv, linkedsplits):
|
||||||
|
|
||||||
async with feed.index_stream() as stream:
|
async with feed.index_stream() as stream:
|
||||||
async for index in stream:
|
async for index in stream:
|
||||||
|
|
||||||
# update chart historical bars graphics by incrementing
|
# update chart historical bars graphics by incrementing
|
||||||
# a time step and drawing the history and new bar
|
# a time step and drawing the history and new bar
|
||||||
|
|
||||||
|
@ -1654,7 +1671,7 @@ async def display_symbol_data(
|
||||||
# )
|
# )
|
||||||
|
|
||||||
async with(
|
async with(
|
||||||
data.feed.open_feed(
|
open_feed(
|
||||||
provider,
|
provider,
|
||||||
[sym],
|
[sym],
|
||||||
loglevel=loglevel,
|
loglevel=loglevel,
|
||||||
|
@ -1665,19 +1682,19 @@ async def display_symbol_data(
|
||||||
) as feed,
|
) as feed,
|
||||||
trio.open_nursery() as n,
|
trio.open_nursery() as n,
|
||||||
):
|
):
|
||||||
async def print_quotes():
|
# async def print_quotes():
|
||||||
async with feed.stream.subscribe() as bstream:
|
# async with feed.stream.subscribe() as bstream:
|
||||||
last_tick = time.time()
|
# last_tick = time.time()
|
||||||
async for quotes in bstream:
|
# async for quotes in bstream:
|
||||||
now = time.time()
|
# now = time.time()
|
||||||
period = now - last_tick
|
# period = now - last_tick
|
||||||
for sym, quote in quotes.items():
|
# for sym, quote in quotes.items():
|
||||||
ticks = quote.get('ticks', ())
|
# ticks = quote.get('ticks', ())
|
||||||
if ticks:
|
# if ticks:
|
||||||
# print(f'{1/period} Hz')
|
# # print(f'{1/period} Hz')
|
||||||
last_tick = time.time()
|
# last_tick = time.time()
|
||||||
|
|
||||||
n.start_soon(print_quotes)
|
# n.start_soon(print_quotes)
|
||||||
|
|
||||||
ohlcv: ShmArray = feed.shm
|
ohlcv: ShmArray = feed.shm
|
||||||
bars = ohlcv.array
|
bars = ohlcv.array
|
||||||
|
@ -1693,6 +1710,7 @@ async def display_symbol_data(
|
||||||
linkedsplits._symbol = symbol
|
linkedsplits._symbol = symbol
|
||||||
|
|
||||||
chart = linkedsplits.plot_ohlc_main(symbol, bars)
|
chart = linkedsplits.plot_ohlc_main(symbol, bars)
|
||||||
|
chart._feeds[symbol.key] = feed
|
||||||
chart.setFocus()
|
chart.setFocus()
|
||||||
|
|
||||||
# plot historical vwap if available
|
# plot historical vwap if available
|
||||||
|
@ -1723,13 +1741,13 @@ async def display_symbol_data(
|
||||||
'static_yrange': (0, 100),
|
'static_yrange': (0, 100),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'rsi2': {
|
# 'rsi2': {
|
||||||
'fsp_func_name': 'rsi',
|
# 'fsp_func_name': 'rsi',
|
||||||
'period': 14,
|
# 'period': 14,
|
||||||
'chart_kwargs': {
|
# 'chart_kwargs': {
|
||||||
'static_yrange': (0, 100),
|
# 'static_yrange': (0, 100),
|
||||||
},
|
# },
|
||||||
},
|
# },
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1811,7 +1829,7 @@ async def load_provider_search(
|
||||||
loglevel=loglevel
|
loglevel=loglevel
|
||||||
) as portal,
|
) as portal,
|
||||||
|
|
||||||
feed.install_brokerd_search(
|
install_brokerd_search(
|
||||||
portal,
|
portal,
|
||||||
get_brokermod(broker),
|
get_brokermod(broker),
|
||||||
),
|
),
|
||||||
|
@ -1872,23 +1890,21 @@ async def _async_main(
|
||||||
godwidget._root_n = root_n
|
godwidget._root_n = root_n
|
||||||
|
|
||||||
# setup search widget and focus main chart view at startup
|
# setup search widget and focus main chart view at startup
|
||||||
|
# search widget is a singleton alongside the godwidget
|
||||||
search = _search.SearchWidget(godwidget=godwidget)
|
search = _search.SearchWidget(godwidget=godwidget)
|
||||||
search.bar.unfocus()
|
search.bar.unfocus()
|
||||||
|
|
||||||
# add search singleton to global chart-space widget
|
godwidget.hbox.addWidget(search)
|
||||||
godwidget.hbox.addWidget(
|
|
||||||
search,
|
|
||||||
|
|
||||||
# alights to top and uses minmial space based on
|
|
||||||
# search bar size hint (i think?)
|
|
||||||
alignment=Qt.AlignTop
|
|
||||||
)
|
|
||||||
godwidget.search = search
|
godwidget.search = search
|
||||||
|
|
||||||
symbol, _, provider = sym.rpartition('.')
|
symbol, _, provider = sym.rpartition('.')
|
||||||
|
|
||||||
# this internally starts a ``display_symbol_data()`` task above
|
# this internally starts a ``display_symbol_data()`` task above
|
||||||
order_mode_ready = godwidget.load_symbol(provider, symbol, loglevel)
|
order_mode_ready = await godwidget.load_symbol(
|
||||||
|
provider,
|
||||||
|
symbol,
|
||||||
|
loglevel
|
||||||
|
)
|
||||||
|
|
||||||
# spin up a search engine for the local cached symbol set
|
# spin up a search engine for the local cached symbol set
|
||||||
async with _search.register_symbol_search(
|
async with _search.register_symbol_search(
|
||||||
|
@ -1912,17 +1928,27 @@ async def _async_main(
|
||||||
# start handling peripherals input for top level widgets
|
# start handling peripherals input for top level widgets
|
||||||
async with (
|
async with (
|
||||||
|
|
||||||
# search bar kb inputs
|
# search bar kb input handling
|
||||||
_event.open_handlers(
|
_event.open_handlers(
|
||||||
[search.bar],
|
[search.bar],
|
||||||
event_types={QEvent.KeyPress},
|
event_types={
|
||||||
|
QEvent.KeyPress,
|
||||||
|
},
|
||||||
async_handler=_search.handle_keyboard_input,
|
async_handler=_search.handle_keyboard_input,
|
||||||
# let key repeats pass through for search
|
filter_auto_repeats=False, # let repeats passthrough
|
||||||
filter_auto_repeats=False,
|
),
|
||||||
|
|
||||||
|
# completer view mouse click signal handling
|
||||||
|
_event.open_signal_handler(
|
||||||
|
search.view.pressed,
|
||||||
|
search.view.on_pressed,
|
||||||
),
|
),
|
||||||
|
|
||||||
# pp pane kb inputs
|
# pp pane kb inputs
|
||||||
open_form_input_handling(pp_pane),
|
open_form_input_handling(
|
||||||
|
pp_pane,
|
||||||
|
focus_next=godwidget,
|
||||||
|
)
|
||||||
):
|
):
|
||||||
# remove startup status text
|
# remove startup status text
|
||||||
starting_done()
|
starting_done()
|
||||||
|
|
Loading…
Reference in New Issue