From 7cbcfc5525d6f981de92ffd4109fbd1668f722f9 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Thu, 23 Sep 2021 10:06:47 -0400 Subject: [PATCH 01/10] Update pp size label on settings changes Resolves #232 --- piker/ui/_position.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/piker/ui/_position.py b/piker/ui/_position.py index 9208fbd1..bdd9064e 100644 --- a/piker/ui/_position.py +++ b/piker/ui/_position.py @@ -232,7 +232,7 @@ class SettingsPane: except ValueError: log.error(f'Invalid value for `{key}`: {value}') - # READ out settings and update UI + # READ out settings and update the status UI / settings widgets suffix = {'currency': ' $', 'units': ' u'}[size_unit] limit = alloc.limit() @@ -259,6 +259,9 @@ class SettingsPane: self.form.fields['slots'].setText(str(alloc.slots)) self.form.fields['limit'].setText(str(limit)) + # update of level marker size label based on any new settings + tracker.update_from_pp() + # 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? From d7cfe4dcb3b16726431d34b1608f3c76ef44dfd5 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Thu, 23 Sep 2021 10:10:59 -0400 Subject: [PATCH 02/10] Shorten edit name, passthrough kwargs to adder methods --- piker/ui/_forms.py | 15 ++++++++++----- piker/ui/_orm.py | 4 ++-- piker/ui/_search.py | 4 ++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/piker/ui/_forms.py b/piker/ui/_forms.py index 38184ca4..993073a9 100644 --- a/piker/ui/_forms.py +++ b/piker/ui/_forms.py @@ -48,7 +48,7 @@ from ._style import hcolor, _font, _font_small, DpiAwareFont from ._label import FormatLabel -class FontAndChartAwareLineEdit(QLineEdit): +class Edit(QLineEdit): def __init__( @@ -369,13 +369,14 @@ class FieldsForm(QWidget): key: str, label_name: str, value: str, + readonly: bool = False, - ) -> FontAndChartAwareLineEdit: + ) -> Edit: # TODO: maybe a distint layout per "field" item? label = self.add_field_label(label_name) - edit = FontAndChartAwareLineEdit( + edit = Edit( parent=self, # width_in_chars=6, ) @@ -386,6 +387,7 @@ class FieldsForm(QWidget): }} """ ) + edit.setReadOnly(readonly) edit.setText(str(value)) self.form.addRow(label, edit) @@ -478,13 +480,15 @@ def mk_form( for key, conf in fields_schema.items(): wtype = conf['type'] label = str(conf.get('label', key)) + kwargs = conf.get('kwargs', {}) # plain (line) edit field if wtype == 'edit': w = form.add_edit_field( key, label, - conf['default_value'] + conf['default_value'], + **kwargs, ) # drop-down selection @@ -493,7 +497,8 @@ def mk_form( w = form.add_select_field( key, label, - values + values, + **kwargs, ) w._key = key diff --git a/piker/ui/_orm.py b/piker/ui/_orm.py index 152da505..67050e95 100644 --- a/piker/ui/_orm.py +++ b/piker/ui/_orm.py @@ -36,7 +36,7 @@ from PyQt5.QtWidgets import ( from ._forms import ( # FontScaledDelegate, - FontAndChartAwareLineEdit, + Edit, ) @@ -97,7 +97,7 @@ class Selection(Field[DataType], Generic[DataType]): class Edit(Field[DataType], Generic[DataType]): '''An edit field which takes a number. ''' - widget_factory = FontAndChartAwareLineEdit + widget_factory = Edit class AllocatorPane(BaseModel): diff --git a/piker/ui/_search.py b/piker/ui/_search.py index 48561a24..94a2fd56 100644 --- a/piker/ui/_search.py +++ b/piker/ui/_search.py @@ -72,7 +72,7 @@ from ._style import ( _font, hcolor, ) -from ._forms import FontAndChartAwareLineEdit, FontScaledDelegate +from ._forms import Edit, FontScaledDelegate log = get_logger(__name__) @@ -407,7 +407,7 @@ class CompleterView(QTreeView): self.resize() -class SearchBar(FontAndChartAwareLineEdit): +class SearchBar(Edit): mode_name: str = 'search' From 1f1f0d390944604c7f42dcc7339abb3b83c3b0e4 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Thu, 23 Sep 2021 14:17:08 -0400 Subject: [PATCH 03/10] Force min pnl label width to avoid resizes on magnitude steps --- piker/ui/_forms.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/piker/ui/_forms.py b/piker/ui/_forms.py index 993073a9..45d68317 100644 --- a/piker/ui/_forms.py +++ b/piker/ui/_forms.py @@ -653,11 +653,21 @@ def mk_fill_status_bar( font_size=bar_label_font_size, font_color='gunmetal', ) + # size according to dpi scaled fonted contents to avoid + # resizes on magnitude changes (eg. 9 -> 10 %) + min_w = _font.boundingRect('1000.0M% pnl').width() + left_label.setMinimumWidth(min_w) + left_label.resize( + min_w, + left_label.size().height(), + ) bar_labels_lhs.addSpacing(5/8 * bar_h) bar_labels_lhs.addWidget( left_label, - alignment=Qt.AlignLeft | Qt.AlignTop, + # XXX: doesn't seem to actually push up against + # the status bar? + alignment=Qt.AlignRight | Qt.AlignTop, ) # this hbox is added as a layout by the paner maker/caller From d706f35668b9ac6b5e5b464c18f46b5d11ea1da4 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Fri, 24 Sep 2021 11:47:38 -0400 Subject: [PATCH 04/10] Keep slots ratio of 1 on derivs at startup --- piker/clearing/_allocate.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/piker/clearing/_allocate.py b/piker/clearing/_allocate.py index f6c9eee1..c14b92d5 100644 --- a/piker/clearing/_allocate.py +++ b/piker/clearing/_allocate.py @@ -287,6 +287,7 @@ def mk_allocator( asset_type = symbol.type_key + # specific configs by asset class / type if asset_type in ('future', 'option', 'futures_option'): @@ -308,9 +309,12 @@ def mk_allocator( alloc.currency_limit = round(startup_size, ndigits=2) else: - startup_size = startup_pp.size + startup_size = abs(startup_pp.size) if startup_size > alloc.units_limit: alloc.units_limit = startup_size + if asset_type in ('future', 'option', 'futures_option'): + alloc.slots = alloc.units_limit + return alloc From 8f70398d88b84e760842cea92b42dbebf2ab21bf Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Wed, 6 Oct 2021 12:59:36 -0400 Subject: [PATCH 05/10] Fix exit-slot-edge-case when only one discrete unit remains --- piker/clearing/_allocate.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/piker/clearing/_allocate.py b/piker/clearing/_allocate.py index c14b92d5..35c79e08 100644 --- a/piker/clearing/_allocate.py +++ b/piker/clearing/_allocate.py @@ -204,7 +204,14 @@ class Allocator(BaseModel): # **without** going past a net-zero pp. if the pp is # > 1.5x a slot size, then front load: exit a slot's and # expect net-zero to be acquired on the final exit. - slot_size < pp_size < round((1.5*slot_size), ndigits=ld) + slot_size < pp_size < round((1.5*slot_size), ndigits=ld) or + + # underlying requires discrete (int) units (eg. stocks) + # and thus our slot size (based on our limit) would + # exit a fractional unit's worth so, presuming we aren't + # supporting a fractional-units-style broker, we need + # exit the final unit. + ld == 0 and abs_live_size == 1 ): order_size = abs_live_size From c737de7c74c2354523cc0d9d66a49febbd872302 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Tue, 12 Oct 2021 10:33:12 -0400 Subject: [PATCH 06/10] Rage drop the limit size unit enum --- piker/clearing/_allocate.py | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/piker/clearing/_allocate.py b/piker/clearing/_allocate.py index 35c79e08..7b61a4bd 100644 --- a/piker/clearing/_allocate.py +++ b/piker/clearing/_allocate.py @@ -87,13 +87,21 @@ class Allocator(BaseModel): symbol: Symbol account: Optional[str] = 'paper' - size_unit: SizeUnit = 'currency' + # TODO: for enums this clearly doesn't fucking work, you can't set + # a default at startup by passing in a `dict` but yet you can set + # that value through assignment..for wtv cucked reason.. honestly, pure + # unintuitive garbage. + size_unit: str = 'currency' _size_units: dict[str, Optional[str]] = _size_units - @validator('size_unit') - def lookup_key(cls, v): + @validator('size_unit', pre=True) + def maybe_lookup_key(cls, v): # apply the corresponding enum key for the text "description" value - return v.name + if v not in _size_units: + return _size_units.inverse[v] + + assert v in _size_units + return v # TODO: if we ever want ot support non-uniform entry-slot-proportion # "sizes" @@ -157,6 +165,9 @@ class Allocator(BaseModel): slot_size = currency_per_slot / price l_sub_pp = (self.currency_limit - live_cost_basis) / price + else: + raise ValueError(f"Not valid size unit '{size}'") + # an entry (adding-to or starting a pp) if ( action == 'buy' and live_size > 0 or @@ -266,7 +277,7 @@ def mk_allocator( # default allocation settings defaults: dict[str, float] = { 'account': None, # select paper by default - 'size_unit': _size_units['currency'], + 'size_unit': 'currency', #_size_units['currency'], 'units_limit': 400, 'currency_limit': 5e3, 'slots': 4, @@ -281,8 +292,8 @@ def mk_allocator( # load and retreive user settings for default allocations # ``config.toml`` user_def = { - 'currency_limit': 5e3, - 'slots': 4, + 'currency_limit': 6e3, + 'slots': 6, } defaults.update(user_def) From 75fddb249c0d9918eb11c06131f931d3ed987d48 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Tue, 12 Oct 2021 10:33:51 -0400 Subject: [PATCH 07/10] Avoid value error on puterizing unit name --- piker/ui/_position.py | 33 +++++++++++++++++---------------- 1 file changed, 17 insertions(+), 16 deletions(-) diff --git a/piker/ui/_position.py b/piker/ui/_position.py index bdd9064e..1936c1ca 100644 --- a/piker/ui/_position.py +++ b/piker/ui/_position.py @@ -152,7 +152,7 @@ class SettingsPane: '''Called on any order pane drop down selection change. ''' - log.info(f'selection input: {text}') + log.info(f'selection input {key}:{text}') self.on_ui_settings_change(key, text) def on_ui_settings_change( @@ -209,23 +209,24 @@ class SettingsPane: # WRITE any settings to current pp's allocator try: - value = puterize(value) - if key == 'limit': - if size_unit == 'currency': - alloc.currency_limit = value - else: - alloc.units_limit = 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 + if key == 'size_unit': + # implicit re-write of value if input + # is the "text name" of the units. + # yah yah, i know this is badd.. alloc.size_unit = value - else: - raise ValueError(f'Unknown setting {key}') + value = puterize(value) + if key == 'limit': + if size_unit == 'currency': + alloc.currency_limit = value + else: + alloc.units_limit = value + + elif key == 'slots': + alloc.slots = int(value) + + else: + raise ValueError(f'Unknown setting {key}') log.info(f'settings change: {key}: {value}') From 46e85e2e4b7cf393cd58db525bbc53c852fe0ba0 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Tue, 12 Oct 2021 10:34:54 -0400 Subject: [PATCH 08/10] Comment on default account load order --- piker/ui/order_mode.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/piker/ui/order_mode.py b/piker/ui/order_mode.py index 4c971fe2..594f0826 100644 --- a/piker/ui/order_mode.py +++ b/piker/ui/order_mode.py @@ -571,7 +571,10 @@ async def open_order_mode( # first account listed is the one we select at startup # (aka order based selection). - pp_account = next(iter(accounts.keys())) if accounts else 'paper' + pp_account = next( + # choose first account based on line order from `brokers.toml`. + iter(accounts.keys()) + ) if accounts else 'paper' # NOTE: requires the backend exactly specifies # the expected symbol key in its positions msg. From de0cc6d81a8f4eab311bb8118455737216765287 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Fri, 22 Oct 2021 12:58:12 -0400 Subject: [PATCH 09/10] Expect accounts as tuple, don't start rt pnl on no live pp --- piker/clearing/_ems.py | 4 ++-- piker/ui/_position.py | 5 ++++- piker/ui/order_mode.py | 11 ++++++----- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/piker/clearing/_ems.py b/piker/clearing/_ems.py index 8ff2b0e2..c35e2e5f 100644 --- a/piker/clearing/_ems.py +++ b/piker/clearing/_ems.py @@ -269,7 +269,7 @@ class TradesRelay: positions: dict[str, dict[str, BrokerdPosition]] # allowed account names - accounts: set[str] + accounts: tuple[str] # count of connected ems clients for this ``brokerd`` consumers: int = 0 @@ -449,7 +449,7 @@ async def open_brokerd_trades_dialogue( relay = TradesRelay( brokerd_dialogue=brokerd_trades_stream, positions=pps, - accounts=set(accounts), + accounts=accounts, consumers=1, ) diff --git a/piker/ui/_position.py b/piker/ui/_position.py index 1936c1ca..e057154e 100644 --- a/piker/ui/_position.py +++ b/piker/ui/_position.py @@ -54,6 +54,7 @@ async def update_pnl_from_feed( feed: Feed, order_mode: OrderMode, # noqa + tracker: PositionTracker, ) -> None: '''Real-time display the current pp's PnL in the appropriate label. @@ -76,7 +77,8 @@ async def update_pnl_from_feed( types = ('bid', 'last', 'last', 'utrade') else: - raise RuntimeError('No pp?!?!') + log.info(f'No position (yet) for {tracker.alloc.account}@{key}') + return # real-time update pnl on the status pane try: @@ -343,6 +345,7 @@ class SettingsPane: update_pnl_from_feed, feed, mode, + tracker, ) # immediately display in status label diff --git a/piker/ui/order_mode.py b/piker/ui/order_mode.py index 594f0826..432c4801 100644 --- a/piker/ui/order_mode.py +++ b/piker/ui/order_mode.py @@ -620,8 +620,8 @@ async def open_order_mode( # alloc? pp_tracker.update_from_pp() + # on existing position, show pp tracking graphics if pp_tracker.startup_pp.size != 0: - # if no position, don't show pp tracking graphics pp_tracker.show() pp_tracker.hide_info() @@ -805,12 +805,13 @@ async def process_trades_and_update_ui( tracker = mode.trackers[msg['account']] tracker.live_pp.update_from_msg(msg) - tracker.update_from_pp() - # update order pane widgets + tracker.update_from_pp() mode.pane.update_status_ui(tracker) - # display pnl - mode.pane.display_pnl(tracker) + + if tracker.live_pp.size: + # display pnl + mode.pane.display_pnl(tracker) # short circuit to next msg to avoid # unnecessary msg content lookups From 6825ad4804e2cdad8333a5b5863e5940032c90b8 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Fri, 29 Oct 2021 16:05:50 -0400 Subject: [PATCH 10/10] Add some type annots around pp msg handling --- piker/clearing/_ems.py | 3 +++ piker/ui/order_mode.py | 13 ++++++++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/piker/clearing/_ems.py b/piker/clearing/_ems.py index c35e2e5f..f802b8a3 100644 --- a/piker/clearing/_ems.py +++ b/piker/clearing/_ems.py @@ -414,6 +414,9 @@ async def open_brokerd_trades_dialogue( ) try: + positions: list[BrokerdPosition] + accounts: tuple[str] + async with ( open_trades_endpoint as (brokerd_ctx, (positions, accounts,)), brokerd_ctx.open_stream() as brokerd_trades_stream, diff --git a/piker/ui/order_mode.py b/piker/ui/order_mode.py index 432c4801..48be0384 100644 --- a/piker/ui/order_mode.py +++ b/piker/ui/order_mode.py @@ -47,7 +47,7 @@ from ._position import ( ) from ._label import FormatLabel from ._window import MultiStatus -from ..clearing._messages import Order +from ..clearing._messages import Order, BrokerdPosition from ._forms import open_form_input_handling @@ -529,7 +529,12 @@ async def open_order_mode( book: OrderBook trades_stream: tractor.MsgStream - position_msgs: dict + + # The keys in this dict **must** be in set our set of "normalized" + # symbol names (i.e. the same names you'd get back in search + # results) in order for position msgs to correctly trigger the + # display of a position indicator on screen. + position_msgs: dict[str, list[BrokerdPosition]] # spawn EMS actor-service async with ( @@ -563,7 +568,9 @@ async def open_order_mode( providers=symbol.brokers ) - # use only loaded accounts according to brokerd + # XXX: ``brokerd`` delivers a set of account names that it allows + # use of but the user also can define the accounts they'd like + # to use, in order, in their `brokers.toml` file. accounts = {} for name in brokerd_accounts: # ensure name is in ``brokers.toml``