From 5b923ae5771daeca402a2b1fe044ad906c8ee2df Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Thu, 26 Aug 2021 08:40:14 -0400 Subject: [PATCH] Update status UI in `.on_ui_settings_change()` Use this method to go through writing all allocator parameters and then reading all changes back into the order mode pane including updating the limit and step labels by the fill bar. Machinery changes: - add `.limit()` and `.step_sizes()` methods to the allocator to provide the appropriate data depending on the pp limit size unit (eg. currency vs. units) - humanize the label display text such that you have nice suffixes and a fixed precision - tweak the fill bar labels to be simpler since the values are now humanized - expect `.on_ui_settings_change()` to be called for every slots hotkey tweak --- piker/ui/_chart.py | 13 +++-- piker/ui/_forms.py | 17 +++--- piker/ui/_interaction.py | 5 +- piker/ui/_position.py | 120 +++++++++++++++++++++++++++++---------- 4 files changed, 108 insertions(+), 47 deletions(-) diff --git a/piker/ui/_chart.py b/piker/ui/_chart.py index a39365c7..a9223035 100644 --- a/piker/ui/_chart.py +++ b/piker/ui/_chart.py @@ -1427,6 +1427,10 @@ async def run_fsp( period=14, ) + async def settings_change(key: str, value: str) -> bool: + print(f'{key}: {value}') + return True + async with ( portal.open_stream_from( @@ -1445,7 +1449,8 @@ async def run_fsp( # TODO: open_form_input_handling( sidepane, - focus_next=linkedsplits.godwidget + focus_next=linkedsplits.godwidget, + on_value_change=settings_change, ), ): @@ -1538,14 +1543,14 @@ async def run_fsp( done() - i = 0 + # i = 0 # update chart graphics async for value in stream: # chart isn't actively shown so just skip render cycle if chart.linked.isHidden(): - print(f'{i} unseen fsp cyclce') - i += 1 + # print(f'{i} unseen fsp cyclce') + # i += 1 continue now = time.time() diff --git a/piker/ui/_forms.py b/piker/ui/_forms.py index 7263d7b8..c4a05c77 100644 --- a/piker/ui/_forms.py +++ b/piker/ui/_forms.py @@ -363,7 +363,7 @@ async def handle_field_input( key = widget._key value = widget.text() - await on_value_change(key, value) + on_value_change(key, value) def mk_form( @@ -539,8 +539,8 @@ def mk_fill_status_bar( # PnL on lhs bar_labels_lhs = QVBoxLayout(fields) left_label = fields.add_field_label( - dedent(f""" - -{30}% PnL + dedent(""" + {pnl}% PnL """), font_size=bar_label_font_size, font_color='gunmetal', @@ -567,16 +567,17 @@ def mk_fill_status_bar( # https://docs.python.org/3/library/string.html#grammar-token-precision top_label = fields.add_field_label( - dedent(f""" - {3.32:.1f}% port + # {limit:.1f} limit + dedent(""" + {limit} """), font_size=bar_label_font_size, font_color='gunmetal', ) bottom_label = fields.add_field_label( - dedent(f""" - {5e3/4/1e3:.2f}k step\n + dedent(""" + x = {step_size}\n """), font_size=bar_label_font_size, font_color='gunmetal', @@ -665,7 +666,7 @@ def mk_order_pane_layout( # _, h = form.width(), form.height() # print(f'w, h: {w, h}') - hbox, fill_bar, left_label, bottom_label, top_label = mk_fill_status_bar( + hbox, fill_bar, left_label, top_label, bottom_label = mk_fill_status_bar( form, pane_vbox=vbox ) # TODO: would be nice to have some better way of reffing these over diff --git a/piker/ui/_interaction.py b/piker/ui/_interaction.py index d65bbc22..022566d4 100644 --- a/piker/ui/_interaction.py +++ b/piker/ui/_interaction.py @@ -262,12 +262,9 @@ async def handle_viewmode_kb_inputs( # hot key to set order slots size num = int(text) pp_pane = order_mode.pane - pp_pane.alloc.slots = num - + pp_pane.on_ui_settings_change('slots', num) edit = pp_pane.form.fields['slots'] - edit.setText(text) edit.selectAll() - on_next_release = edit.deselect pp_pane.update_status_ui() diff --git a/piker/ui/_position.py b/piker/ui/_position.py index be1ef1fe..d5190b33 100644 --- a/piker/ui/_position.py +++ b/piker/ui/_position.py @@ -35,6 +35,7 @@ from ._anchors import ( pp_tight_and_right, # wanna keep it straight in the long run gpath_pin, ) +from ..calc import humanize from ..clearing._messages import BrokerdPosition, Status from ..data._source import Symbol from ._label import Label @@ -77,13 +78,17 @@ SizeUnit = Enum( class Allocator(BaseModel): - symbol: Symbol - class Config: validate_assignment = True copy_on_model_validation = False arbitrary_types_allowed = True + # required to get the account validator lookup working? + extra = 'allow' + # underscore_attrs_are_private = False + + symbol: Symbol + account: Optional[str] = 'paper' _accounts: bidict[str, Optional[str]] @@ -108,6 +113,21 @@ class Allocator(BaseModel): currency_limit: float slots: int + def step_sizes( + self, + ) -> dict[str, float]: + slots = self.slots + return { + 'units': self.units_limit / slots, + 'currency': self.currency_limit / slots, + } + + def limit(self) -> float: + if self.size_unit == 'currency': + return self.currency_limit + else: + return self.units_limit + def next_order_info( self, @@ -127,12 +147,17 @@ class Allocator(BaseModel): live_size = live_pp.size startup_size = startup_pp.size - step_size = self.units_limit / self.slots - l_sub_pp = self.units_limit - live_size + steps = self.step_sizes() - if self.size_unit == 'currency': + if self.size_unit == 'units': + # step_size = self.units_limit / self.slots + step_size = steps['units'] + l_sub_pp = self.units_limit - live_size + + elif self.size_unit == 'currency': startup_size = startup_pp.size * startup_pp.avg_price / price - step_size = self.currency_limit / self.slots / price + # step_size = self.currency_limit / self.slots / price + step_size = steps['currency'] / price l_sub_pp = ( self.currency_limit - live_size * live_pp.avg_price ) / price @@ -184,18 +209,19 @@ class OrderModePane: def on_selection_change( self, - key: str, + text: str, + key: str, ) -> None: '''Called on any order pane drop down selection change. ''' - print(f'{text}') + print(f'selection input: {text}') setattr(self.alloc, key, text) - print(self.alloc.dict()) + self.on_ui_settings_change(key, text) - async def on_ui_settings_change( + def on_ui_settings_change( self, key: str, @@ -205,7 +231,56 @@ class OrderModePane: '''Called on any order pane edit field value change. ''' - print(f'settings change: {key}:{value}') + print(f'settings change: {key}: {value}') + alloc = self.alloc + size_unit = alloc.size_unit + + # write any passed settings to allocator + if key == 'limit': + if size_unit == 'currency': + alloc.currency_limit = float(value) + else: + alloc.units_limit = float(value) + + elif key == 'slots': + alloc.slots = int(value) + + elif key == 'size_unit': + # TODO: if there's a limit size unit change re-compute + # the current settings in the new units + pass + + elif key == 'account': + print(f'TODO: change account -> {value}') + + else: + raise ValueError(f'Unknown setting {key}') + + # read out settings and update UI + + suffix = {'currency': ' $', 'units': ' u'}[size_unit] + limit = alloc.limit() + + # TODO: a reverse look up from the position to the equivalent + # account(s), if none then look to user config for default? + self.update_status_ui() + + step_size = alloc.step_sizes()[size_unit] + + self.step_label.format( + step_size=str(humanize(step_size)) + suffix + ) + self.limit_label.format( + limit=str(humanize(limit)) + suffix + ) + + # update size unit in UI + self.form.fields['size_unit'].setCurrentText( + alloc._size_units[alloc.size_unit] + ) + self.form.fields['slots'].setText(str(alloc.slots)) + self.form.fields['limit'].setText(str(limit)) + # TODO: maybe return a diff of settings so if we can an error we # can have general input handling code to report it through the # UI in some way? @@ -216,7 +291,7 @@ class OrderModePane: ): alloc = self.alloc asset_type = alloc.symbol.type_key - form = self.form + # form = self.form # TODO: pull from piker.toml # default config @@ -261,16 +336,7 @@ class OrderModePane: limit_text = alloc.units_limit - # update size unit in UI - form.fields['size_unit'].setCurrentText( - alloc._size_units[alloc.size_unit] - ) - form.fields['slots'].setText(str(alloc.slots)) - - form.fields['limit'].setText(str(limit_text)) - - # TODO: a reverse look up from the position to the equivalent - # account(s), if none then look to user config for default? + self.on_ui_settings_change('limit', limit_text) self.update_status_ui(size=startup_size) @@ -289,7 +355,7 @@ class OrderModePane: prop = live_currency_size / alloc.currency_limit else: - prop = size or live_pp.size / alloc.units_limit + prop = (size or live_pp.size) / alloc.units_limit # calculate proportion of position size limit # that exists and display in fill bar @@ -325,14 +391,6 @@ class OrderModePane: order.price = level order.size = order_info['size'] - def on_settings_change_update_ui( - self, - ) -> None: - # TODO: - # - recompute both units_limit and currency_limit - # - update fill bar slotting if necessary (only on slots?) - ... - class PositionTracker: '''Track and display a real-time position for a single symbol