piker/piker/ui/_app.py

202 lines
5.6 KiB
Python
Raw Normal View History

# piker: trading gear for hackers
# Copyright (C) Tyler Goodlet (in stewardship for pikers)
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU Affero General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU Affero General Public License for more details.
# You should have received a copy of the GNU Affero General Public License
# along with this program. If not, see <https://www.gnu.org/licenses/>.
'''
Main app startup and run.
'''
from functools import partial
from types import ModuleType
from PyQt5.QtCore import QEvent
import trio
from .._daemon import maybe_spawn_brokerd
from . import _event
from ._exec import run_qtractor
from ..data.feed import install_brokerd_search
from ..data._source import unpack_fqsn
from . import _search
from ._chart import GodWidget
from ..log import get_logger
log = get_logger(__name__)
async def load_provider_search(
brokermod: str,
loglevel: str,
) -> None:
name = brokermod.name
log.info(f'loading brokerd for {name}..')
async with (
maybe_spawn_brokerd(
name,
loglevel=loglevel
) as portal,
install_brokerd_search(
portal,
brokermod,
),
):
# keep search engine stream up until cancelled
await trio.sleep_forever()
async def _async_main(
# implicit required argument provided by ``qtractor_run()``
main_widget: GodWidget,
syms: list[str],
brokers: dict[str, ModuleType],
loglevel: str,
) -> None:
"""
Main Qt-trio routine invoked by the Qt loop with the widgets ``dict``.
Provision the "main" widget with initial symbol data and root nursery.
"""
from . import _display
from ._pg_overrides import _do_overrides
_do_overrides()
godwidget = main_widget
# attempt to configure DPI aware font size
screen = godwidget.window.current_screen()
# configure graphics update throttling based on display refresh rate
Process framed ticks by type in main graphics loop We are already packing framed ticks in extended lists from the `.data._sampling.uniform_rate_send()` task so the natural solution to avoid needless graphics cycles for HFT-ish feeds (like binance) is to unpack those frames and for most cases only update graphics with the "latest" data per loop iteration. Unpacking in this way also lessens nested-iterations per tick type. Btw, this also effectively solves all remaining issues of fast tick feeds over-triggering the graphics loop renders as long as the original quote stream is throttled appropriately, usually to the local display rate. Relates to #183, #192 Dirty deats: - drop all per-tick rate checks, they were always somewhat pointless when iterating a frame of ticks per render cycle XD. - unpack tick frame into ticks per frame type, and last of each type; the lasts are used to update each part of the UI/graphics by class. - only skip the label update if we can't retrieve the last from from a graphics source array; it seems `chart.update_curve_from_array()` already does a `len` check internally. - add some draft commented code for tick type classes and a possible wire framed tick data structure. - move `chart_maxmin()` range computer to module level, bind a chart to it with a `partial.` - only check rate limits in main quote loop thus reporting actual overages - add in commented logic for only updating the "last" cleared price from the most recent framed value if we want to eventually (right now seems like this is only relevant to ib and it's dark trades: `utrade`). - rename `_clear_throttle_rate` -> `_quote_throttle_rate`, drop `_book_throttle_rate`.
2021-09-28 11:56:14 +00:00
_display._quote_throttle_rate = min(
round(screen.refreshRate()),
Process framed ticks by type in main graphics loop We are already packing framed ticks in extended lists from the `.data._sampling.uniform_rate_send()` task so the natural solution to avoid needless graphics cycles for HFT-ish feeds (like binance) is to unpack those frames and for most cases only update graphics with the "latest" data per loop iteration. Unpacking in this way also lessens nested-iterations per tick type. Btw, this also effectively solves all remaining issues of fast tick feeds over-triggering the graphics loop renders as long as the original quote stream is throttled appropriately, usually to the local display rate. Relates to #183, #192 Dirty deats: - drop all per-tick rate checks, they were always somewhat pointless when iterating a frame of ticks per render cycle XD. - unpack tick frame into ticks per frame type, and last of each type; the lasts are used to update each part of the UI/graphics by class. - only skip the label update if we can't retrieve the last from from a graphics source array; it seems `chart.update_curve_from_array()` already does a `len` check internally. - add some draft commented code for tick type classes and a possible wire framed tick data structure. - move `chart_maxmin()` range computer to module level, bind a chart to it with a `partial.` - only check rate limits in main quote loop thus reporting actual overages - add in commented logic for only updating the "last" cleared price from the most recent framed value if we want to eventually (right now seems like this is only relevant to ib and it's dark trades: `utrade`). - rename `_clear_throttle_rate` -> `_quote_throttle_rate`, drop `_book_throttle_rate`.
2021-09-28 11:56:14 +00:00
_display._quote_throttle_rate,
)
Process framed ticks by type in main graphics loop We are already packing framed ticks in extended lists from the `.data._sampling.uniform_rate_send()` task so the natural solution to avoid needless graphics cycles for HFT-ish feeds (like binance) is to unpack those frames and for most cases only update graphics with the "latest" data per loop iteration. Unpacking in this way also lessens nested-iterations per tick type. Btw, this also effectively solves all remaining issues of fast tick feeds over-triggering the graphics loop renders as long as the original quote stream is throttled appropriately, usually to the local display rate. Relates to #183, #192 Dirty deats: - drop all per-tick rate checks, they were always somewhat pointless when iterating a frame of ticks per render cycle XD. - unpack tick frame into ticks per frame type, and last of each type; the lasts are used to update each part of the UI/graphics by class. - only skip the label update if we can't retrieve the last from from a graphics source array; it seems `chart.update_curve_from_array()` already does a `len` check internally. - add some draft commented code for tick type classes and a possible wire framed tick data structure. - move `chart_maxmin()` range computer to module level, bind a chart to it with a `partial.` - only check rate limits in main quote loop thus reporting actual overages - add in commented logic for only updating the "last" cleared price from the most recent framed value if we want to eventually (right now seems like this is only relevant to ib and it's dark trades: `utrade`). - rename `_clear_throttle_rate` -> `_quote_throttle_rate`, drop `_book_throttle_rate`.
2021-09-28 11:56:14 +00:00
log.info(f'Set graphics update rate to {_display._quote_throttle_rate} Hz')
# TODO: do styling / themeing setup
# _style.style_ze_sheets(godwidget)
sbar = godwidget.window.status_bar
starting_done = sbar.open_status('starting ze sexy chartz')
needed_brokermods: dict[str, ModuleType] = {}
for fqsn in syms:
brokername, *_ = unpack_fqsn(fqsn)
needed_brokermods[brokername] = brokers[brokername]
async with (
trio.open_nursery() as root_n,
):
# set root nursery and task stack for spawning other charts/feeds
# that run cached in the bg
godwidget._root_n = root_n
# setup search widget and focus main chart view at startup
# search widget is a singleton alongside the godwidget
search = _search.SearchWidget(godwidget=godwidget)
2022-09-07 21:50:10 +00:00
# search.bar.unfocus()
# godwidget.hbox.addWidget(search)
godwidget.search = search
symbols: list[str] = []
for sym in syms:
symbol, _, provider = sym.rpartition('.')
symbols.append(symbol)
# this internally starts a ``display_symbol_data()`` task above
order_mode_ready = await godwidget.load_symbols(
provider,
symbols,
loglevel
)
# spin up a search engine for the local cached symbol set
async with _search.register_symbol_search(
provider_name='cache',
search_routine=partial(
_search.search_simple_dict,
source=godwidget._chart_cache,
),
# cache is super fast so debounce on super short period
pause_period=0.01,
):
# load other providers into search **after**
# the chart's select cache
for brokername, mod in needed_brokermods.items():
root_n.start_soon(
load_provider_search,
mod,
loglevel,
)
await order_mode_ready.wait()
# start handling peripherals input for top level widgets
async with (
# search bar kb input handling
_event.open_handlers(
[search.bar],
event_types={
QEvent.KeyPress,
},
async_handler=_search.handle_keyboard_input,
filter_auto_repeats=False, # let repeats passthrough
),
# completer view mouse click signal handling
_event.open_signal_handler(
search.view.pressed,
search.view.on_pressed,
),
):
# remove startup status text
starting_done()
await trio.sleep_forever()
def _main(
syms: list[str],
brokermods: list[ModuleType],
piker_loglevel: str,
tractor_kwargs,
) -> None:
2021-12-07 20:10:37 +00:00
'''
Sync entry point to start a chart: a ``tractor`` + Qt runtime
entry point
2021-12-07 20:10:37 +00:00
'''
run_qtractor(
func=_async_main,
args=(
syms,
{mod.name: mod for mod in brokermods},
piker_loglevel,
),
main_widget_type=GodWidget,
tractor_kwargs=tractor_kwargs,
)