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
fsp_feeds
Tyler Goodlet 2021-08-26 08:40:14 -04:00
parent 0589c3c5b7
commit 5b923ae577
4 changed files with 108 additions and 47 deletions

View File

@ -1427,6 +1427,10 @@ async def run_fsp(
period=14, period=14,
) )
async def settings_change(key: str, value: str) -> bool:
print(f'{key}: {value}')
return True
async with ( async with (
portal.open_stream_from( portal.open_stream_from(
@ -1445,7 +1449,8 @@ async def run_fsp(
# TODO: # TODO:
open_form_input_handling( open_form_input_handling(
sidepane, sidepane,
focus_next=linkedsplits.godwidget focus_next=linkedsplits.godwidget,
on_value_change=settings_change,
), ),
): ):
@ -1538,14 +1543,14 @@ async def run_fsp(
done() done()
i = 0 # i = 0
# update chart graphics # update chart graphics
async for value in stream: async for value in stream:
# 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():
print(f'{i} unseen fsp cyclce') # print(f'{i} unseen fsp cyclce')
i += 1 # i += 1
continue continue
now = time.time() now = time.time()

View File

@ -363,7 +363,7 @@ async def handle_field_input(
key = widget._key key = widget._key
value = widget.text() value = widget.text()
await on_value_change(key, value) on_value_change(key, value)
def mk_form( def mk_form(
@ -539,8 +539,8 @@ def mk_fill_status_bar(
# PnL on lhs # PnL on lhs
bar_labels_lhs = QVBoxLayout(fields) bar_labels_lhs = QVBoxLayout(fields)
left_label = fields.add_field_label( left_label = fields.add_field_label(
dedent(f""" dedent("""
-{30}% PnL {pnl}% PnL
"""), """),
font_size=bar_label_font_size, font_size=bar_label_font_size,
font_color='gunmetal', font_color='gunmetal',
@ -567,16 +567,17 @@ def mk_fill_status_bar(
# https://docs.python.org/3/library/string.html#grammar-token-precision # https://docs.python.org/3/library/string.html#grammar-token-precision
top_label = fields.add_field_label( top_label = fields.add_field_label(
dedent(f""" # {limit:.1f} limit
{3.32:.1f}% port dedent("""
{limit}
"""), """),
font_size=bar_label_font_size, font_size=bar_label_font_size,
font_color='gunmetal', font_color='gunmetal',
) )
bottom_label = fields.add_field_label( bottom_label = fields.add_field_label(
dedent(f""" dedent("""
{5e3/4/1e3:.2f}k step\n x = {step_size}\n
"""), """),
font_size=bar_label_font_size, font_size=bar_label_font_size,
font_color='gunmetal', font_color='gunmetal',
@ -665,7 +666,7 @@ def mk_order_pane_layout(
# _, h = form.width(), form.height() # _, h = form.width(), form.height()
# print(f'w, h: {w, h}') # 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 form, pane_vbox=vbox
) )
# TODO: would be nice to have some better way of reffing these over # TODO: would be nice to have some better way of reffing these over

View File

@ -262,12 +262,9 @@ async def handle_viewmode_kb_inputs(
# hot key to set order slots size # hot key to set order slots size
num = int(text) num = int(text)
pp_pane = order_mode.pane 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 = pp_pane.form.fields['slots']
edit.setText(text)
edit.selectAll() edit.selectAll()
on_next_release = edit.deselect on_next_release = edit.deselect
pp_pane.update_status_ui() pp_pane.update_status_ui()

View File

@ -35,6 +35,7 @@ from ._anchors import (
pp_tight_and_right, # wanna keep it straight in the long run pp_tight_and_right, # wanna keep it straight in the long run
gpath_pin, gpath_pin,
) )
from ..calc import humanize
from ..clearing._messages import BrokerdPosition, Status from ..clearing._messages import BrokerdPosition, Status
from ..data._source import Symbol from ..data._source import Symbol
from ._label import Label from ._label import Label
@ -77,13 +78,17 @@ SizeUnit = Enum(
class Allocator(BaseModel): class Allocator(BaseModel):
symbol: Symbol
class Config: class Config:
validate_assignment = True validate_assignment = True
copy_on_model_validation = False copy_on_model_validation = False
arbitrary_types_allowed = True 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' account: Optional[str] = 'paper'
_accounts: bidict[str, Optional[str]] _accounts: bidict[str, Optional[str]]
@ -108,6 +113,21 @@ class Allocator(BaseModel):
currency_limit: float currency_limit: float
slots: int 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( def next_order_info(
self, self,
@ -127,12 +147,17 @@ class Allocator(BaseModel):
live_size = live_pp.size live_size = live_pp.size
startup_size = startup_pp.size startup_size = startup_pp.size
step_size = self.units_limit / self.slots steps = self.step_sizes()
l_sub_pp = self.units_limit - live_size
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 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 = ( l_sub_pp = (
self.currency_limit - live_size * live_pp.avg_price self.currency_limit - live_size * live_pp.avg_price
) / price ) / price
@ -184,18 +209,19 @@ class OrderModePane:
def on_selection_change( def on_selection_change(
self, self,
key: str,
text: str, text: str,
key: str,
) -> None: ) -> None:
'''Called on any order pane drop down selection change. '''Called on any order pane drop down selection change.
''' '''
print(f'{text}') print(f'selection input: {text}')
setattr(self.alloc, key, 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, self,
key: str, key: str,
@ -205,7 +231,56 @@ class OrderModePane:
'''Called on any order pane edit field value change. '''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 # 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 # can have general input handling code to report it through the
# UI in some way? # UI in some way?
@ -216,7 +291,7 @@ class OrderModePane:
): ):
alloc = self.alloc alloc = self.alloc
asset_type = alloc.symbol.type_key asset_type = alloc.symbol.type_key
form = self.form # form = self.form
# TODO: pull from piker.toml # TODO: pull from piker.toml
# default config # default config
@ -261,16 +336,7 @@ class OrderModePane:
limit_text = alloc.units_limit limit_text = alloc.units_limit
# update size unit in UI self.on_ui_settings_change('limit', limit_text)
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.update_status_ui(size=startup_size) self.update_status_ui(size=startup_size)
@ -289,7 +355,7 @@ class OrderModePane:
prop = live_currency_size / alloc.currency_limit prop = live_currency_size / alloc.currency_limit
else: 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 # calculate proportion of position size limit
# that exists and display in fill bar # that exists and display in fill bar
@ -325,14 +391,6 @@ class OrderModePane:
order.price = level order.price = level
order.size = order_info['size'] 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: class PositionTracker:
'''Track and display a real-time position for a single symbol '''Track and display a real-time position for a single symbol