Change over the UI layer to use `MktPair`

Including changing to `LinkedSplits.mkt: MktPair` and adding an explicit
setter method for setting it and being sure that nothing breaks
in the display system init!

For this commit we leave in warning access to `LinkedSplits.symbol` but
will remove in following commit.
master
Tyler Goodlet 2023-05-24 15:30:17 -04:00
parent 39af215d61
commit 1b577eebf6
9 changed files with 120 additions and 86 deletions

View File

@ -907,7 +907,7 @@ async def open_feed(
for fqme, flume_msg in flumes_msg_dict.items(): for fqme, flume_msg in flumes_msg_dict.items():
flume = Flume.from_msg(flume_msg) flume = Flume.from_msg(flume_msg)
# assert flume.symbol.fqme == fqme # assert flume.mkt.fqme == fqme
feed.flumes[fqme] = flume feed.flumes[fqme] = flume
# TODO: do we need this? # TODO: do we need this?

View File

@ -68,7 +68,10 @@ from ..data.feed import (
Feed, Feed,
Flume, Flume,
) )
from ..accounting._mktinfo import Symbol from ..accounting import (
MktPair,
Symbol,
)
from ..log import get_logger from ..log import get_logger
from ._interaction import ChartView from ._interaction import ChartView
from ._forms import FieldsForm from ._forms import FieldsForm
@ -287,7 +290,7 @@ class GodWidget(QWidget):
pp_nav.hide() pp_nav.hide()
# set window titlebar info # set window titlebar info
symbol = self.rt_linked.symbol symbol = self.rt_linked.mkt
if symbol is not None: if symbol is not None:
self.window.setWindowTitle( self.window.setWindowTitle(
f'{symbol.fqme} ' f'{symbol.fqme} '
@ -452,7 +455,7 @@ class LinkedSplits(QWidget):
# update the UI for a given "chart instance". # update the UI for a given "chart instance".
self.display_state: DisplayState | None = None self.display_state: DisplayState | None = None
self._symbol: Symbol = None self._mkt: MktPair | Symbol = None
def on_splitter_adjust( def on_splitter_adjust(
self, self,
@ -474,9 +477,20 @@ class LinkedSplits(QWidget):
**kwargs, **kwargs,
) )
def set_mkt_info(
self,
mkt: MktPair,
) -> None:
self._mkt = mkt
@property @property
def symbol(self) -> Symbol: def mkt(self) -> MktPair:
return self._symbol return self._mkt
@property
def symbol(self) -> Symbol | MktPair:
log.warning(f'{type(self)}.symbol is now deprecated use .mkt!')
return self.mkt
def set_split_sizes( def set_split_sizes(
self, self,
@ -521,7 +535,7 @@ class LinkedSplits(QWidget):
def plot_ohlc_main( def plot_ohlc_main(
self, self,
symbol: Symbol, mkt: MktPair,
shm: ShmArray, shm: ShmArray,
flume: Flume, flume: Flume,
sidepane: FieldsForm, sidepane: FieldsForm,
@ -540,7 +554,7 @@ class LinkedSplits(QWidget):
# add crosshairs # add crosshairs
self.cursor = Cursor( self.cursor = Cursor(
linkedsplits=self, linkedsplits=self,
digits=symbol.tick_size_digits, digits=mkt.price_tick_digits,
) )
# NOTE: atm the first (and only) OHLC price chart for the symbol # NOTE: atm the first (and only) OHLC price chart for the symbol
@ -548,7 +562,7 @@ class LinkedSplits(QWidget):
# be no distinction since we will have multiple symbols per # be no distinction since we will have multiple symbols per
# view as part of "aggregate feeds". # view as part of "aggregate feeds".
self.chart = self.add_plot( self.chart = self.add_plot(
name=symbol.fqme, name=mkt.fqme,
shm=shm, shm=shm,
flume=flume, flume=flume,
style=style, style=style,
@ -1030,7 +1044,7 @@ class ChartPlotWidget(pg.PlotWidget):
''' '''
view = vb or self.view view = vb or self.view
viz = self.main_viz viz = self.main_viz
l, r = viz.view_range() left, right = viz.view_range()
x_shift = viz.index_step() * datums x_shift = viz.index_step() * datums
if datums >= 300: if datums >= 300:
@ -1040,8 +1054,8 @@ class ChartPlotWidget(pg.PlotWidget):
# should trigger broadcast on all overlays right? # should trigger broadcast on all overlays right?
view.setXRange( view.setXRange(
min=l + x_shift, min=left + x_shift,
max=r + x_shift, max=right + x_shift,
# TODO: holy shit, wtf dude... why tf would this not be 0 by # TODO: holy shit, wtf dude... why tf would this not be 0 by
# default... speechless. # default... speechless.
@ -1227,7 +1241,7 @@ class ChartPlotWidget(pg.PlotWidget):
# if the sticky is for our symbol # if the sticky is for our symbol
# use the tick size precision for display # use the tick size precision for display
name = name or pi.name name = name or pi.name
sym = self.linked.symbol sym = self.linked.mkt
digits = None digits = None
if name == sym.key: if name == sym.key:
digits = sym.tick_size_digits digits = sym.tick_size_digits

View File

@ -228,7 +228,7 @@ class ContentsLabel(pg.LabelItem):
'bar_wap', 'bar_wap',
] ]
], ],
name=name, # name=name,
index=ix, index=ix,
) )
) )
@ -363,7 +363,7 @@ class Cursor(pg.GraphicsObject):
# value used for rounding y-axis discreet tick steps # value used for rounding y-axis discreet tick steps
# computing once, up front, here cuz why not # computing once, up front, here cuz why not
mkt = self.linked._symbol mkt = self.linked.mkt
self._y_tick_mult = 1/float(mkt.price_tick) self._y_tick_mult = 1/float(mkt.price_tick)
# line width in view coordinates # line width in view coordinates

View File

@ -436,12 +436,12 @@ class Viz(Struct):
else: else:
if x_range is None: if x_range is None:
( (
l, xl,
_, _,
lbar, lbar,
rbar, rbar,
_, _,
r, xr,
) = self.datums_range() ) = self.datums_range()
profiler(f'{self.name} got bars range') profiler(f'{self.name} got bars range')
@ -585,12 +585,12 @@ class Viz(Struct):
Return a range tuple for the datums present in view. Return a range tuple for the datums present in view.
''' '''
l, r = view_range or self.view_range() xl, xr = view_range or self.view_range()
index_field: str = index_field or self.index_field index_field: str = index_field or self.index_field
if index_field == 'index': if index_field == 'index':
l: int = round(l) xl: int = round(xl)
r: int = round(r) xr: int = round(xr)
if array is None: if array is None:
array = self.shm.array array = self.shm.array
@ -601,12 +601,12 @@ class Viz(Struct):
# invalid view state # invalid view state
if ( if (
r < l xr < xl
or l < 0 or xl < 0
or r < 0 or xr < 0
or ( or (
l > last xl > last
and r > last and xr > last
) )
): ):
leftmost: int = first leftmost: int = first
@ -616,12 +616,12 @@ class Viz(Struct):
# determine first and last datums in view determined by # determine first and last datums in view determined by
# l -> r view range. # l -> r view range.
rightmost = max( rightmost = max(
min(last, ceil(r)), min(last, ceil(xr)),
first, first,
) )
leftmost = min( leftmost = min(
max(first, floor(l)), max(first, floor(xl)),
last, last,
rightmost - 1, rightmost - 1,
) )
@ -632,12 +632,12 @@ class Viz(Struct):
self.vs.xrange = leftmost, rightmost self.vs.xrange = leftmost, rightmost
return ( return (
l, # left x-in-view xl, # left x-in-view
first, # first datum first, # first datum
leftmost, leftmost,
rightmost, rightmost,
last, # last_datum last, # last_datum
r, # right-x-in-view xr, # right-x-in-view
) )
def read( def read(
@ -665,12 +665,12 @@ class Viz(Struct):
profiler('self.shm.array READ') profiler('self.shm.array READ')
( (
l, xl,
ifirst, ifirst,
lbar, lbar,
rbar, rbar,
ilast, ilast,
r, xr,
) = self.datums_range( ) = self.datums_range(
index_field=index_field, index_field=index_field,
array=array, array=array,
@ -715,8 +715,8 @@ class Viz(Struct):
# a uniform time stamp step size? # a uniform time stamp step size?
else: else:
# get read-relative indices adjusting for master shm index. # get read-relative indices adjusting for master shm index.
lbar_i = max(l, ifirst) - ifirst lbar_i = max(xl, ifirst) - ifirst
rbar_i = min(r, ilast) - ifirst rbar_i = min(xr, ilast) - ifirst
# NOTE: the slice here does NOT include the extra ``+ 1`` # NOTE: the slice here does NOT include the extra ``+ 1``
# BUT the ``in_view`` slice DOES.. # BUT the ``in_view`` slice DOES..
@ -1244,18 +1244,25 @@ class Viz(Struct):
''' '''
# get most recent right datum index in-view # get most recent right datum index in-view
l, start, datum_start, datum_stop, stop, r = self.datums_range() (
xl,
start,
datum_start,
datum_stop,
stop,
xr,
) = self.datums_range()
lasts = self.shm.array[-1] lasts = self.shm.array[-1]
i_step = lasts['index'] # last index-specific step. i_step = lasts['index'] # last index-specific step.
i_step_t = lasts['time'] # last time step. i_step_t = lasts['time'] # last time step.
# fqme = self.flume.symbol.fqme # fqme = self.flume.mkt.fqme
# check if "last (is) in view" -> is a real-time update necessary? # check if "last (is) in view" -> is a real-time update necessary?
if self.index_field == 'index': if self.index_field == 'index':
liv = (r >= i_step) liv = (xr >= i_step)
else: else:
liv = (r >= i_step_t) liv = (xr >= i_step_t)
# compute the first available graphic obj's x-units-per-pixel # compute the first available graphic obj's x-units-per-pixel
# TODO: make this not loop through all vizs each time! # TODO: make this not loop through all vizs each time!

View File

@ -37,6 +37,9 @@ import pyqtgraph as pg
from msgspec import field from msgspec import field
# from .. import brokers # from .. import brokers
from ..accounting import (
MktPair,
)
from ..data.feed import ( from ..data.feed import (
open_feed, open_feed,
Feed, Feed,
@ -319,8 +322,8 @@ async def graphics_update_loop(
for fqme, flume in feed.flumes.items(): for fqme, flume in feed.flumes.items():
ohlcv = flume.rt_shm ohlcv = flume.rt_shm
hist_ohlcv = flume.hist_shm hist_ohlcv = flume.hist_shm
symbol = flume.mkt mkt = flume.mkt
fqme = symbol.fqme fqme = mkt.fqme
# update last price sticky # update last price sticky
fast_viz = fast_chart._vizs[fqme] fast_viz = fast_chart._vizs[fqme]
@ -360,13 +363,13 @@ async def graphics_update_loop(
last, volume = ohlcv.array[-1][['close', 'volume']] last, volume = ohlcv.array[-1][['close', 'volume']]
symbol = flume.mkt mkt = flume.mkt
l1 = L1Labels( l1 = L1Labels(
fast_pi, fast_pi,
# determine precision/decimal lengths # determine precision/decimal lengths
digits=symbol.tick_size_digits, digits=mkt.price_tick_digits,
size_digits=symbol.lot_size_digits, size_digits=mkt.size_tick_digits,
) )
# TODO: # TODO:
@ -449,7 +452,7 @@ async def graphics_update_loop(
and quote_rate >= display_rate and quote_rate >= display_rate
): ):
pass pass
# log.warning(f'High quote rate {symbol.key}: {quote_rate}') # log.warning(f'High quote rate {mkt.fqme}: {quote_rate}')
last_quote_s = time.time() last_quote_s = time.time()
@ -1224,7 +1227,7 @@ async def display_symbol_data(
# tf_key = tf_in_1s[step_size_s] # tf_key = tf_in_1s[step_size_s]
godwidget.window.setWindowTitle( godwidget.window.setWindowTitle(
f'{fqmes} ' f'{fqmes} '
# f'tick:{symbol.tick_size} ' # f'tick:{mkt.tick_size} '
# f'step:{tf_key} ' # f'step:{tf_key} '
) )
# generate order mode side-pane UI # generate order mode side-pane UI
@ -1234,8 +1237,8 @@ async def display_symbol_data(
godwidget.pp_pane = pp_pane godwidget.pp_pane = pp_pane
# create top history view chart above the "main rt chart". # create top history view chart above the "main rt chart".
rt_linked = godwidget.rt_linked rt_linked: LinkedSplits = godwidget.rt_linked
hist_linked = godwidget.hist_linked hist_linked: LinkedSplits = godwidget.hist_linked
# NOTE: here we insert the slow-history chart set into # NOTE: here we insert the slow-history chart set into
# the fast chart's splitter -> so it's a splitter of charts # the fast chart's splitter -> so it's a splitter of charts
@ -1279,17 +1282,17 @@ async def display_symbol_data(
# TODO NOTE: THIS CONTROLS WHAT SYMBOL IS USED FOR ORDER MODE # TODO NOTE: THIS CONTROLS WHAT SYMBOL IS USED FOR ORDER MODE
# SUBMISSIONS, we need to make this switch based on selection. # SUBMISSIONS, we need to make this switch based on selection.
rt_linked._symbol = flume.mkt rt_linked.set_mkt_info(flume.mkt)
hist_linked._symbol = flume.mkt hist_linked.set_mkt_info(flume.mkt)
ohlcv: ShmArray = flume.rt_shm ohlcv: ShmArray = flume.rt_shm
hist_ohlcv: ShmArray = flume.hist_shm hist_ohlcv: ShmArray = flume.hist_shm
symbol = flume.mkt mkt: MktPair = flume.mkt
fqme = symbol.fqme fqme = mkt.fqme
hist_chart = hist_linked.plot_ohlc_main( hist_chart = hist_linked.plot_ohlc_main(
symbol, mkt,
hist_ohlcv, hist_ohlcv,
flume, flume,
# in the case of history chart we explicitly set `False` # in the case of history chart we explicitly set `False`
@ -1311,7 +1314,7 @@ async def display_symbol_data(
hist_linked.cursor.always_show_xlabel = False hist_linked.cursor.always_show_xlabel = False
rt_chart = rt_linked.plot_ohlc_main( rt_chart = rt_linked.plot_ohlc_main(
symbol, mkt,
ohlcv, ohlcv,
flume, flume,
# in the case of history chart we explicitly set `False` # in the case of history chart we explicitly set `False`
@ -1378,8 +1381,8 @@ async def display_symbol_data(
ohlcv: ShmArray = flume.rt_shm ohlcv: ShmArray = flume.rt_shm
hist_ohlcv: ShmArray = flume.hist_shm hist_ohlcv: ShmArray = flume.hist_shm
symbol = flume.mkt mkt = flume.mkt
fqme = symbol.fqme fqme = mkt.fqme
hist_pi = hist_chart.overlay_plotitem( hist_pi = hist_chart.overlay_plotitem(
name=fqme, name=fqme,

View File

@ -29,7 +29,6 @@ from typing import (
Any, Any,
) )
import numpy as np
import msgspec import msgspec
import tractor import tractor
import pyqtgraph as pg import pyqtgraph as pg
@ -428,7 +427,7 @@ class FspAdmin:
in self._flow_registry.items() in self._flow_registry.items()
], ],
) as (ctx, last_index), ) as (ctx, _),
ctx.open_stream() as stream, ctx.open_stream() as stream,
): ):
@ -486,8 +485,10 @@ class FspAdmin:
readonly=True, readonly=True,
) )
portal = self.cluster.get(worker_name) or self.rr_next_portal() portal: tractor.Portal = (
provider_tag = portal.channel.uid self.cluster.get(worker_name)
or self.rr_next_portal()
)
# TODO: this should probably be turned into a # TODO: this should probably be turned into a
# ``Cascade`` type which describes the routing # ``Cascade`` type which describes the routing

View File

@ -45,7 +45,10 @@ from ..calc import (
pnl, pnl,
puterize, puterize,
) )
from ..accounting._allocate import Allocator from ..accounting import (
Allocator,
MktPair,
)
from ..accounting import ( from ..accounting import (
Position, Position,
) )
@ -244,7 +247,7 @@ class SettingsPane:
# a ``brokerd`) then error and switch back to the last # a ``brokerd`) then error and switch back to the last
# selection. # selection.
if tracker is None: if tracker is None:
sym = old_tracker.charts[0].linked.symbol.key sym: str = old_tracker.charts[0].linked.mkt.fqme
log.error( log.error(
f'Account `{account_name}` can not be set for {sym}' f'Account `{account_name}` can not be set for {sym}'
) )
@ -415,9 +418,10 @@ class SettingsPane:
''' '''
mode = self.order_mode mode = self.order_mode
sym = mode.chart.linked.symbol mkt: MktPair = mode.chart.linked.mkt
size = tracker.live_pp.size size = tracker.live_pp.size
flume: Feed = mode.feed.flumes[sym.fqme] fqme: str = mkt.fqme
flume: Feed = mode.feed.flumes[fqme]
pnl_value = 0 pnl_value = 0
if size: if size:
@ -430,7 +434,6 @@ class SettingsPane:
# maybe start update task # maybe start update task
global _pnl_tasks global _pnl_tasks
fqme = sym.fqme
if fqme not in _pnl_tasks: if fqme not in _pnl_tasks:
_pnl_tasks[fqme] = True _pnl_tasks[fqme] = True
self.order_mode.nursery.start_soon( self.order_mode.nursery.start_soon(
@ -555,7 +558,7 @@ class Nav(Struct):
''' '''
for key, chart in self.charts.items(): for key, chart in self.charts.items():
size_digits = size_digits or chart.linked.symbol.lot_size_digits size_digits = size_digits or chart.linked.mkt.size_tick_digits
line = self.lines.get(key) line = self.lines.get(key)
level_marker = self.level_markers[key] level_marker = self.level_markers[key]
pp_label = self.pp_labels[key] pp_label = self.pp_labels[key]

View File

@ -23,7 +23,10 @@ WARNING: this code likely doesn't work at all (yet)
""" """
import numpy as np import numpy as np
import pyqtgraph as pg import pyqtgraph as pg
from PyQt5 import QtCore, QtGui, QtWidgets from PyQt5 import (
QtCore,
QtWidgets,
)
from .quantdom.charts import CenteredTextItem from .quantdom.charts import CenteredTextItem
from .quantdom.base import Quotes from .quantdom.base import Quotes

View File

@ -36,17 +36,18 @@ import trio
from PyQt5.QtCore import Qt from PyQt5.QtCore import Qt
from .. import config from .. import config
from ..accounting import Position from ..accounting import (
from ..accounting._allocate import ( Allocator,
Position,
mk_allocator, mk_allocator,
MktPair,
Symbol,
) )
from ..clearing._client import ( from ..clearing._client import (
open_ems, open_ems,
OrderClient, OrderClient,
) )
from ._style import _font from ._style import _font
from ..accounting._mktinfo import Symbol
from ..accounting import MktPair
from ..data.feed import ( from ..data.feed import (
Feed, Feed,
Flume, Flume,
@ -93,7 +94,7 @@ class Dialog(Struct):
order: Order order: Order
symbol: str symbol: str
lines: list[LevelLine] lines: list[LevelLine]
last_status_close: Callable = lambda: None last_status_close: Callable | None = None
msgs: dict[str, dict] = {} msgs: dict[str, dict] = {}
fills: dict[str, Any] = {} fills: dict[str, Any] = {}
@ -288,10 +289,10 @@ class OrderMode:
# since that's illogical / a no-op. # since that's illogical / a no-op.
return return
symbol = self.chart.linked.symbol mkt: MktPair = self.chart.linked.mkt
# NOTE : we could also use instead, # NOTE : we could also use instead,
# symbol.quantize(price, quantity_type='price') # mkt.quantize(price, quantity_type='price')
# but it returns a Decimal and it's probably gonna # but it returns a Decimal and it's probably gonna
# be slower? # be slower?
# TODO: should we be enforcing this precision # TODO: should we be enforcing this precision
@ -301,7 +302,7 @@ class OrderMode:
price = round( price = round(
price, price,
ndigits=symbol.tick_size_digits, ndigits=mkt.size_tick_digits,
) )
order = self._staged_order = Order( order = self._staged_order = Order(
@ -309,8 +310,8 @@ class OrderMode:
price=price, price=price,
account=self.current_pp.alloc.account, account=self.current_pp.alloc.account,
size=0, size=0,
symbol=symbol, symbol=mkt.fqme,
brokers=[symbol.broker], brokers=[mkt.broker],
oid='', # filled in on submit oid='', # filled in on submit
exec_mode=trigger_type, # dark or live exec_mode=trigger_type, # dark or live
) )
@ -457,10 +458,10 @@ class OrderMode:
the EMS, adjust mirrored level line on secondary chart. the EMS, adjust mirrored level line on secondary chart.
''' '''
mktinfo = self.chart.linked.symbol mktinfo: MktPair = self.chart.linked.mkt
level = round( level = round(
line.value(), line.value(),
ndigits=mktinfo.tick_size_digits, ndigits=mktinfo.size_tick_digits,
) )
# updated by level change callback set in ``.new_line_from_order()`` # updated by level change callback set in ``.new_line_from_order()``
dialog = line.dialog dialog = line.dialog
@ -497,7 +498,9 @@ class OrderMode:
# a submission is the start of a new order dialog # a submission is the start of a new order dialog
dialog = self.dialogs[uuid] dialog = self.dialogs[uuid]
dialog.lines = lines dialog.lines = lines
dialog.last_status_close() cls: Callable | None = dialog.last_status_close
if cls:
cls()
for line in lines: for line in lines:
@ -549,7 +552,7 @@ class OrderMode:
# XXX: seems to fail on certain types of races? # XXX: seems to fail on certain types of races?
# assert len(lines) == 2 # assert len(lines) == 2
if lines: if lines:
flume: Flume = self.feed.flumes[chart.linked.symbol.fqme] flume: Flume = self.feed.flumes[chart.linked.mkt.fqme]
_, _, ratio = flume.get_ds_info() _, _, ratio = flume.get_ds_info()
for chart, shm in [ for chart, shm in [
@ -740,15 +743,15 @@ async def open_order_mode(
lines = LineEditor(godw=godw) lines = LineEditor(godw=godw)
arrows = ArrowEditor(godw=godw) arrows = ArrowEditor(godw=godw)
# symbol id # market endpoint info
symbol = chart.linked.symbol mkt: MktPair = chart.linked.mkt
# map of per-provider account keys to position tracker instances # map of per-provider account keys to position tracker instances
trackers: dict[str, PositionTracker] = {} trackers: dict[str, PositionTracker] = {}
# load account names from ``brokers.toml`` # load account names from ``brokers.toml``
accounts_def = config.load_accounts( accounts_def = config.load_accounts(
providers=[symbol.broker], providers=[mkt.broker],
) )
# XXX: ``brokerd`` delivers a set of account names that it # XXX: ``brokerd`` delivers a set of account names that it
@ -771,17 +774,17 @@ async def open_order_mode(
# net-zero pp # net-zero pp
startup_pp = Position( startup_pp = Position(
mkt=symbol, mkt=mkt,
size=0, size=0,
ppu=0, ppu=0,
# XXX: BLEH, do we care about this on the client side? # XXX: BLEH, do we care about this on the client side?
bs_mktid=symbol.key, bs_mktid=mkt.key,
) )
# allocator config # allocator config
alloc = mk_allocator( alloc: Allocator = mk_allocator(
symbol=symbol, mkt=mkt,
account=account_name, account=account_name,
# if this startup size is greater the allocator limit, # if this startup size is greater the allocator limit,