WIP add a lambda-QFrame to get per chart sidpanes for each linkedsplits row
parent
825680b8c6
commit
63138ccbf4
|
@ -26,6 +26,11 @@ from functools import partial
|
||||||
from PyQt5 import QtCore, QtGui, QtWidgets
|
from PyQt5 import QtCore, QtGui, QtWidgets
|
||||||
from PyQt5.QtCore import Qt
|
from PyQt5.QtCore import Qt
|
||||||
from PyQt5.QtCore import QEvent
|
from PyQt5.QtCore import QEvent
|
||||||
|
from PyQt5.QtWidgets import (
|
||||||
|
QFrame,
|
||||||
|
QWidget,
|
||||||
|
# QSizePolicy,
|
||||||
|
)
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import pyqtgraph as pg
|
import pyqtgraph as pg
|
||||||
import tractor
|
import tractor
|
||||||
|
@ -68,13 +73,13 @@ 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 ..data import feed
|
||||||
from ._forms import FieldsForm, open_form
|
from ._forms import FieldsForm, open_form, mk_health_bar
|
||||||
|
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class GodWidget(QtWidgets.QWidget):
|
class GodWidget(QWidget):
|
||||||
'''
|
'''
|
||||||
"Our lord and savior, the holy child of window-shua, there is no
|
"Our lord and savior, the holy child of window-shua, there is no
|
||||||
widget above thee." - 6|6
|
widget above thee." - 6|6
|
||||||
|
@ -236,7 +241,7 @@ class GodWidget(QtWidgets.QWidget):
|
||||||
return order_mode_started
|
return order_mode_started
|
||||||
|
|
||||||
|
|
||||||
class LinkedSplits(QtWidgets.QWidget):
|
class LinkedSplits(QWidget):
|
||||||
'''
|
'''
|
||||||
Widget that holds a central chart plus derived
|
Widget that holds a central chart plus derived
|
||||||
subcharts computed from the original data set apart
|
subcharts computed from the original data set apart
|
||||||
|
@ -338,12 +343,16 @@ class LinkedSplits(QtWidgets.QWidget):
|
||||||
linkedsplits=self,
|
linkedsplits=self,
|
||||||
digits=symbol.digits(),
|
digits=symbol.digits(),
|
||||||
)
|
)
|
||||||
|
|
||||||
self.chart = self.add_plot(
|
self.chart = self.add_plot(
|
||||||
|
|
||||||
name=symbol.key,
|
name=symbol.key,
|
||||||
array=array,
|
array=array,
|
||||||
# xaxis=self.xaxis,
|
# xaxis=self.xaxis,
|
||||||
style=style,
|
style=style,
|
||||||
_is_main=True,
|
_is_main=True,
|
||||||
|
|
||||||
|
sidepane=self.godwidget.pp_config,
|
||||||
)
|
)
|
||||||
# add crosshair graphic
|
# add crosshair graphic
|
||||||
self.chart.addItem(self.cursor)
|
self.chart.addItem(self.cursor)
|
||||||
|
@ -353,7 +362,10 @@ class LinkedSplits(QtWidgets.QWidget):
|
||||||
self.chart.hideAxis('bottom')
|
self.chart.hideAxis('bottom')
|
||||||
|
|
||||||
# style?
|
# style?
|
||||||
self.chart.setFrameStyle(QtWidgets.QFrame.StyledPanel | QtWidgets.QFrame.Plain)
|
self.chart.setFrameStyle(
|
||||||
|
QtWidgets.QFrame.StyledPanel |
|
||||||
|
QtWidgets.QFrame.Plain
|
||||||
|
)
|
||||||
|
|
||||||
return self.chart
|
return self.chart
|
||||||
|
|
||||||
|
@ -368,6 +380,8 @@ class LinkedSplits(QtWidgets.QWidget):
|
||||||
style: str = 'line',
|
style: str = 'line',
|
||||||
_is_main: bool = False,
|
_is_main: bool = False,
|
||||||
|
|
||||||
|
sidepane: Optional[QWidget] = None,
|
||||||
|
|
||||||
**cpw_kwargs,
|
**cpw_kwargs,
|
||||||
|
|
||||||
) -> 'ChartPlotWidget':
|
) -> 'ChartPlotWidget':
|
||||||
|
@ -398,6 +412,53 @@ class LinkedSplits(QtWidgets.QWidget):
|
||||||
self.xaxis.hide()
|
self.xaxis.hide()
|
||||||
self.xaxis = xaxis
|
self.xaxis = xaxis
|
||||||
|
|
||||||
|
if sidepane:
|
||||||
|
|
||||||
|
class ChartandPane(QFrame):
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
parent=None,
|
||||||
|
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
super().__init__(parent)
|
||||||
|
|
||||||
|
# self.chart = cpw
|
||||||
|
self.sidepane = sidepane
|
||||||
|
# cpw.chart_and_pane = self
|
||||||
|
# self.setStyleSheet(
|
||||||
|
# f"""QFrame {{
|
||||||
|
# color : {hcolor('default_darkest')};
|
||||||
|
# background-color : {hcolor('default_darkest')};
|
||||||
|
# }}
|
||||||
|
# """
|
||||||
|
# )
|
||||||
|
|
||||||
|
hbox = self.hbox = QtGui.QHBoxLayout(self)
|
||||||
|
hbox.setAlignment(Qt.AlignTop | Qt.AlignLeft)
|
||||||
|
hbox.setContentsMargins(0, 0, 0, 0)
|
||||||
|
hbox.setSpacing(3)
|
||||||
|
|
||||||
|
def sizeHint(self) -> QtCore.QSize:
|
||||||
|
return self.chart.sizeHint() + sidepane.sizeHint()
|
||||||
|
|
||||||
|
paned_chart = ChartandPane(parent=self.splitter)
|
||||||
|
|
||||||
|
# splitter_widget = QWidget(self)
|
||||||
|
# splitter_widget.setSizePolicy(
|
||||||
|
# QSizePolicy.Expanding,
|
||||||
|
# QSizePolicy.Expanding,
|
||||||
|
# )
|
||||||
|
|
||||||
|
# hbox = QtGui.QHBoxLayout(splitter_widget)
|
||||||
|
# hbox.setAlignment(Qt.AlignTop | Qt.AlignRight)
|
||||||
|
# hbox.setContentsMargins(0, 0, 0, 0)
|
||||||
|
# hbox.setSpacing(3)
|
||||||
|
|
||||||
|
# else:
|
||||||
|
# splitter_widget = cpw
|
||||||
|
|
||||||
cpw = ChartPlotWidget(
|
cpw = ChartPlotWidget(
|
||||||
|
|
||||||
# this name will be used to register the primary
|
# this name will be used to register the primary
|
||||||
|
@ -406,7 +467,8 @@ class LinkedSplits(QtWidgets.QWidget):
|
||||||
data_key=array_key or name,
|
data_key=array_key or name,
|
||||||
|
|
||||||
array=array,
|
array=array,
|
||||||
parent=self.splitter,
|
# parent=self.splitter,
|
||||||
|
parent=paned_chart if sidepane else self.splitter,
|
||||||
linkedsplits=self,
|
linkedsplits=self,
|
||||||
axisItems={
|
axisItems={
|
||||||
'bottom': xaxis,
|
'bottom': xaxis,
|
||||||
|
@ -417,14 +479,38 @@ class LinkedSplits(QtWidgets.QWidget):
|
||||||
**cpw_kwargs,
|
**cpw_kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if sidepane:
|
||||||
|
paned_chart.chart = cpw
|
||||||
|
paned_chart.hbox.addWidget(cpw)
|
||||||
|
# hbox.addWidget(cpw)
|
||||||
|
paned_chart.hbox.addWidget(
|
||||||
|
sidepane,
|
||||||
|
alignment=Qt.AlignTop
|
||||||
|
)
|
||||||
|
cpw.sidepane = sidepane
|
||||||
|
|
||||||
|
# splitter_widget.setMinimumHeight(cpw.height())
|
||||||
|
# splitter_widget.setMinimumWidth(cpw.width())
|
||||||
|
# splitter_widget.show()
|
||||||
|
|
||||||
|
# hbox.addWidget(cpw)
|
||||||
|
# hbox.addWidget(sidepane)
|
||||||
|
|
||||||
|
# cpw.sidepane = sidepane
|
||||||
|
# cpw.hbox = hbox
|
||||||
|
|
||||||
# give viewbox as reference to chart
|
# give viewbox as reference to chart
|
||||||
# allowing for kb controls and interactions on **this** widget
|
# allowing for kb controls and interactions on **this** widget
|
||||||
# (see our custom view mode in `._interactions.py`)
|
# (see our custom view mode in `._interactions.py`)
|
||||||
cv.chart = cpw
|
cv.chart = cpw
|
||||||
|
|
||||||
cpw.plotItem.vb.linkedsplits = self
|
cpw.plotItem.vb.linkedsplits = self
|
||||||
cpw.setFrameStyle(QtWidgets.QFrame.StyledPanel) # | QtWidgets.QFrame.Plain)
|
cpw.setFrameStyle(
|
||||||
|
QtWidgets.QFrame.StyledPanel
|
||||||
|
# | QtWidgets.QFrame.Plain)
|
||||||
|
)
|
||||||
cpw.hideButtons()
|
cpw.hideButtons()
|
||||||
|
|
||||||
# XXX: gives us outline on backside of y-axis
|
# XXX: gives us outline on backside of y-axis
|
||||||
cpw.getPlotItem().setContentsMargins(*CHART_MARGINS)
|
cpw.getPlotItem().setContentsMargins(*CHART_MARGINS)
|
||||||
|
|
||||||
|
@ -448,8 +534,8 @@ class LinkedSplits(QtWidgets.QWidget):
|
||||||
# track by name
|
# track by name
|
||||||
self.subplots[name] = cpw
|
self.subplots[name] = cpw
|
||||||
|
|
||||||
# XXX: we need this right?
|
sidepane.setMinimumWidth(self.chart.sidepane.width())
|
||||||
self.splitter.addWidget(cpw)
|
self.splitter.addWidget(paned_chart if sidepane else cpw)
|
||||||
|
|
||||||
# scale split regions
|
# scale split regions
|
||||||
self.set_split_sizes()
|
self.set_split_sizes()
|
||||||
|
@ -832,7 +918,11 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
# one place to dig around this might be the `QBackingStore`
|
# one place to dig around this might be the `QBackingStore`
|
||||||
# https://doc.qt.io/qt-5/qbackingstore.html
|
# https://doc.qt.io/qt-5/qbackingstore.html
|
||||||
# curve.setData(y=array[name], x=array['index'], **kwargs)
|
# curve.setData(y=array[name], x=array['index'], **kwargs)
|
||||||
curve.update_from_array(x=array['index'], y=array[data_key], **kwargs)
|
curve.update_from_array(
|
||||||
|
x=array['index'],
|
||||||
|
y=array[data_key],
|
||||||
|
**kwargs
|
||||||
|
)
|
||||||
|
|
||||||
return curve
|
return curve
|
||||||
|
|
||||||
|
@ -969,7 +1059,7 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
self.scene().leaveEvent(ev)
|
self.scene().leaveEvent(ev)
|
||||||
|
|
||||||
|
|
||||||
_clear_throttle_rate: int = 50 # Hz
|
_clear_throttle_rate: int = 35 # Hz
|
||||||
_book_throttle_rate: int = 16 # Hz
|
_book_throttle_rate: int = 16 # Hz
|
||||||
|
|
||||||
|
|
||||||
|
@ -1279,19 +1369,41 @@ async def run_fsp(
|
||||||
group_key=group_status_key,
|
group_key=group_status_key,
|
||||||
)
|
)
|
||||||
|
|
||||||
async with portal.open_stream_from(
|
async with (
|
||||||
|
portal.open_stream_from(
|
||||||
|
|
||||||
# subactor entrypoint
|
# subactor entrypoint
|
||||||
fsp.cascade,
|
fsp.cascade,
|
||||||
|
|
||||||
# name as title of sub-chart
|
# name as title of sub-chart
|
||||||
brokername=brokermod.name,
|
brokername=brokermod.name,
|
||||||
src_shm_token=src_shm.token,
|
src_shm_token=src_shm.token,
|
||||||
dst_shm_token=conf['shm'].token,
|
dst_shm_token=conf['shm'].token,
|
||||||
symbol=sym,
|
symbol=sym,
|
||||||
fsp_func_name=fsp_func_name,
|
fsp_func_name=fsp_func_name,
|
||||||
|
|
||||||
) as stream:
|
) as stream,
|
||||||
|
|
||||||
|
open_form(
|
||||||
|
godwidget=linkedsplits.godwidget,
|
||||||
|
parent=linkedsplits.godwidget,
|
||||||
|
fields={
|
||||||
|
'name': {
|
||||||
|
'key': '**fsp**:',
|
||||||
|
'type': 'select',
|
||||||
|
'default_value': [
|
||||||
|
f'{display_name}'
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'period': {
|
||||||
|
'key': '**period (bars)**:',
|
||||||
|
'type': 'edit',
|
||||||
|
'default_value': 14,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
) as sidepane,
|
||||||
|
|
||||||
|
):
|
||||||
|
|
||||||
# receive last index for processed historical
|
# receive last index for processed historical
|
||||||
# data-array as first msg
|
# data-array as first msg
|
||||||
|
@ -1315,6 +1427,7 @@ async def run_fsp(
|
||||||
array=shm.array,
|
array=shm.array,
|
||||||
|
|
||||||
array_key=conf['fsp_func_name'],
|
array_key=conf['fsp_func_name'],
|
||||||
|
sidepane=sidepane,
|
||||||
|
|
||||||
# curve by default
|
# curve by default
|
||||||
ohlc=False,
|
ohlc=False,
|
||||||
|
@ -1349,7 +1462,11 @@ async def run_fsp(
|
||||||
|
|
||||||
# works also for overlays in which case data is looked up from
|
# works also for overlays in which case data is looked up from
|
||||||
# internal chart array set....
|
# internal chart array set....
|
||||||
chart.update_curve_from_array(display_name, shm.array, array_key=fsp_func_name)
|
chart.update_curve_from_array(
|
||||||
|
display_name,
|
||||||
|
shm.array,
|
||||||
|
array_key=fsp_func_name
|
||||||
|
)
|
||||||
|
|
||||||
# TODO: figure out if we can roll our own `FillToThreshold` to
|
# TODO: figure out if we can roll our own `FillToThreshold` to
|
||||||
# get brush filled polygons for OS/OB conditions.
|
# get brush filled polygons for OS/OB conditions.
|
||||||
|
@ -1707,10 +1824,68 @@ async def _async_main(
|
||||||
sbar = godwidget.window.status_bar
|
sbar = godwidget.window.status_bar
|
||||||
starting_done = sbar.open_status('starting ze sexy chartz')
|
starting_done = sbar.open_status('starting ze sexy chartz')
|
||||||
|
|
||||||
async with trio.open_nursery() as root_n:
|
async with (
|
||||||
|
trio.open_nursery() as root_n,
|
||||||
|
open_form(
|
||||||
|
godwidget=godwidget,
|
||||||
|
parent=godwidget,
|
||||||
|
fields={
|
||||||
|
'account': {
|
||||||
|
'key': '**account**:',
|
||||||
|
'type': 'select',
|
||||||
|
'default_value': [
|
||||||
|
'piker.paper',
|
||||||
|
# 'ib.margin',
|
||||||
|
# 'ib.paper',
|
||||||
|
],
|
||||||
|
},
|
||||||
|
'disti_policy': {
|
||||||
|
'key': '**entry policy**:',
|
||||||
|
'type': 'select',
|
||||||
|
'default_value': ['uniform'],
|
||||||
|
},
|
||||||
|
'slots': {
|
||||||
|
'key': '**slots**:',
|
||||||
|
'type': 'edit',
|
||||||
|
'default_value': 4,
|
||||||
|
},
|
||||||
|
'allocator': {
|
||||||
|
'key': '**allocator**:',
|
||||||
|
'type': 'select',
|
||||||
|
'default_value': ['$ size', '% of port', '# shares'],
|
||||||
|
},
|
||||||
|
'dollar_size': {
|
||||||
|
'key': '**$size**:',
|
||||||
|
'type': 'edit',
|
||||||
|
'default_value': '5k',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
) as pp_config,
|
||||||
|
):
|
||||||
|
pp_config: FieldsForm
|
||||||
|
mk_health_bar(pp_config)
|
||||||
|
pp_config.show()
|
||||||
|
|
||||||
|
# add as next-to-y-axis pane
|
||||||
|
godwidget.pp_config = pp_config
|
||||||
|
|
||||||
|
# godwidget.hbox.insertWidget(
|
||||||
|
# 1,
|
||||||
|
# pp_config,
|
||||||
|
|
||||||
|
# # alights to top and uses minmial space based on
|
||||||
|
# # search bar size hint (i think?)
|
||||||
|
# alignment=Qt.AlignTop
|
||||||
|
# )
|
||||||
|
|
||||||
|
godwidget.hbox.setAlignment(Qt.AlignTop)
|
||||||
|
|
||||||
|
# sb = god_widget.window.statusBar()
|
||||||
|
# sb.insertPermanentWidget(0, pp_config)
|
||||||
|
# pp_config.show()
|
||||||
# set root nursery and task stack for spawning other charts/feeds
|
# set root nursery and task stack for spawning other charts/feeds
|
||||||
# that run cached in the bg
|
# that run cached in the bg
|
||||||
|
|
||||||
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
|
||||||
|
@ -1761,63 +1936,7 @@ async def _async_main(
|
||||||
# let key repeats pass through for search
|
# let key repeats pass through for search
|
||||||
filter_auto_repeats=False,
|
filter_auto_repeats=False,
|
||||||
),
|
),
|
||||||
|
|
||||||
open_form(
|
|
||||||
godwidget=godwidget,
|
|
||||||
parent=godwidget,
|
|
||||||
fields={
|
|
||||||
'account': {
|
|
||||||
'key': '**account**:',
|
|
||||||
'type': 'select',
|
|
||||||
'default_value': [
|
|
||||||
'piker.paper',
|
|
||||||
# 'ib.margin',
|
|
||||||
# 'ib.paper',
|
|
||||||
],
|
|
||||||
},
|
|
||||||
'disti_policy': {
|
|
||||||
'key': '**entry policy**:',
|
|
||||||
'type': 'select',
|
|
||||||
'default_value': ['uniform'],
|
|
||||||
},
|
|
||||||
'slots': {
|
|
||||||
'key': '**slots**:',
|
|
||||||
'type': 'edit',
|
|
||||||
'default_value': 4,
|
|
||||||
},
|
|
||||||
'allocator': {
|
|
||||||
'key': '**allocator**:',
|
|
||||||
'type': 'select',
|
|
||||||
'default_value': ['$ size', '% of port', '# shares'],
|
|
||||||
},
|
|
||||||
'dollar_size': {
|
|
||||||
'key': '**$size**:',
|
|
||||||
'type': 'edit',
|
|
||||||
'default_value': '5k',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
) as pp_config,
|
|
||||||
):
|
):
|
||||||
pp_config: FieldsForm
|
|
||||||
pp_config.show()
|
|
||||||
|
|
||||||
# add as next-to-y-axis pane
|
|
||||||
godwidget.pp_config = pp_config
|
|
||||||
|
|
||||||
godwidget.hbox.insertWidget(
|
|
||||||
1,
|
|
||||||
pp_config,
|
|
||||||
|
|
||||||
# alights to top and uses minmial space based on
|
|
||||||
# search bar size hint (i think?)
|
|
||||||
alignment=Qt.AlignTop
|
|
||||||
)
|
|
||||||
|
|
||||||
godwidget.hbox.setAlignment(Qt.AlignTop)
|
|
||||||
# sb = god_widget.window.statusBar()
|
|
||||||
# sb.insertPermanentWidget(0, pp_config)
|
|
||||||
# pp_config.show()
|
|
||||||
|
|
||||||
# remove startup status text
|
# remove startup status text
|
||||||
starting_done()
|
starting_done()
|
||||||
await trio.sleep_forever()
|
await trio.sleep_forever()
|
||||||
|
|
Loading…
Reference in New Issue