diff --git a/piker/ui/_forms.py b/piker/ui/_forms.py index d804023d..4df3dd54 100644 --- a/piker/ui/_forms.py +++ b/piker/ui/_forms.py @@ -44,6 +44,7 @@ import pydantic from ._event import open_handlers from ._style import hcolor, _font, _font_small, DpiAwareFont +from .. import brokers class FontAndChartAwareLineEdit(QLineEdit): @@ -366,11 +367,15 @@ async def handle_field_input( # process field input if key in (Qt.Key_Enter, Qt.Key_Return): + value = widget.text() key = widget._key + old = getattr(model, key) + try: setattr(model, key, value) + except pydantic.error_wrappers.ValidationError: setattr(model, key, old) widget.setText(str(old)) @@ -380,16 +385,14 @@ async def handle_field_input( def mk_form( - model: pydantic.BaseModel, parent: QWidget, fields_schema: dict, ) -> FieldsForm: - form = FieldsForm(parent=parent) # TODO: generate components from model # instead of schema dict (aka use an ORM) - form.model = model + form = FieldsForm(parent=parent) # generate sub-components from schema dict for key, config in fields_schema.items(): @@ -413,18 +416,6 @@ def mk_form( values ) - def write_model(text: str, key: str): - print(f'{text}') - setattr(form.model, key, text) - print(form.model) - - w.currentTextChanged.connect( - partial( - write_model, - key=key, - ) - ) - w._key = key return form @@ -438,7 +429,7 @@ async def open_form_input_handling( ) -> FieldsForm: - assert form.model, f'{form} must define a `.model`' + # assert form.model, f'{form} must define a `.model`' async with open_handlers( @@ -630,31 +621,27 @@ def mk_fill_status_bar( slots = 4 bar.set_slots(slots, value=0) - return hbox, bar + return hbox, bar, left_label, top_label, bottom_label def mk_order_pane_layout( parent: QWidget, + # accounts: dict[str, Optional[str]], font_size: int = _font_small.px_size - 2 ) -> FieldsForm: - from ._position import mk_alloc - # TODO: some kinda pydantic sub-type - # that enforces a composite widget attr er sumthin.. - # as part of our ORM thingers. - alloc = mk_alloc() + accounts = brokers.config.load_accounts() # TODO: maybe just allocate the whole fields form here # and expect an async ctx entry? form = mk_form( parent=parent, - model=alloc, fields_schema={ 'account': { 'type': 'select', - 'default_value': alloc._accounts.keys() + 'default_value': accounts.keys(), }, 'size_unit': { 'label': '**allocate**:', @@ -670,8 +657,8 @@ def mk_order_pane_layout( 'type': 'select', 'default_value': ['uniform'], }, - 'size': { - 'label': '**size**:', + 'limit': { + 'label': '**limit**:', 'type': 'edit', 'default_value': 5000, }, @@ -683,7 +670,6 @@ def mk_order_pane_layout( }, ) form._font_size = font_size - alloc._widget = form # top level pane layout # XXX: see ``FieldsForm.__init__()`` for why we can't do basic @@ -693,8 +679,15 @@ def mk_order_pane_layout( # _, h = form.width(), form.height() # print(f'w, h: {w, h}') - hbox, fill_bar = mk_fill_status_bar(form, pane_vbox=vbox) + hbox, fill_bar, left_label, bottom_label, top_label = mk_fill_status_bar( + form, pane_vbox=vbox + ) + # TODO: would be nice to have some better way of reffing these over + # monkey patching... form.fill_bar = fill_bar + form.left_label = left_label + form.bottom_label = bottom_label + form.top_label = top_label # add pp fill bar + spacing vbox.addLayout(hbox, stretch=1/3) @@ -702,9 +695,9 @@ def mk_order_pane_layout( feed_label = form.add_field_label( dedent(""" brokerd.ib\n - |_@localhost:8509\n - |_consumers: 4\n - |_streams: 9\n + |_@{host}:{port}\n + |_consumers: {cons}\n + |_streams: {streams}\n """), font_size=_font.px_size - 5, ) @@ -722,5 +715,4 @@ def mk_order_pane_layout( vbox.setSpacing(36) form.show() - return form diff --git a/piker/ui/_interaction.py b/piker/ui/_interaction.py index 6c4d2ce9..7533790d 100644 --- a/piker/ui/_interaction.py +++ b/piker/ui/_interaction.py @@ -261,14 +261,16 @@ async def handle_viewmode_kb_inputs( ): # hot key to set order slots size num = int(text) - pp = order_mode.pp - pp_pane = pp.pane - pp_pane.model.slots = num - edit = pp_pane.fields['slots'] + pp_pane = order_mode.pane + pp_pane.alloc.slots = num + + edit = pp_pane.form.fields['slots'] edit.setText(text) edit.selectAll() + on_next_release = edit.deselect - pp.init_status_ui() + + pp_pane.init_status_ui() else: # none active diff --git a/piker/ui/order_mode.py b/piker/ui/order_mode.py index 0ccbb33d..1475af56 100644 --- a/piker/ui/order_mode.py +++ b/piker/ui/order_mode.py @@ -19,6 +19,7 @@ Chart trading, the only way to scalp. """ from dataclasses import dataclass, field +from functools import partial from pprint import pformat import time from typing import Optional, Dict, Callable, Any @@ -28,15 +29,16 @@ from pydantic import BaseModel import tractor import trio +from .. import brokers from ..clearing._client import open_ems, OrderBook from ..data._source import Symbol from ..log import get_logger from ._editors import LineEditor, ArrowEditor from ._lines import order_line, LevelLine -from ._position import PositionTracker +from ._position import PositionTracker, OrderModePane, mk_alloc from ._window import MultiStatus from ..clearing._messages import Order -# from ._forms import FieldsForm +from ._forms import open_form_input_handling log = get_logger(__name__) @@ -88,6 +90,8 @@ class OrderMode: multistatus: MultiStatus pp: PositionTracker allocator: 'Allocator' # noqa + pane: OrderModePane + active: bool = False name: str = 'order' @@ -127,31 +131,16 @@ class OrderMode: ) else False, **line_kwargs, - ) - # define a callback applied for each level change to the line - # which will recompute the order size based on allocator - # settings: - def update_from_size_calc(y: float) -> None: - - order_info = self.allocator.get_order_info( - symbol=symbol, - price=y, - action=order.action, - ) - line.update_labels(order_info) - order.price = y - order.size = order_info['size'] - - # return is used to update labels implicitly - return order_info - - update_from_size_calc(order.price) - - # set level update cb - # line._on_level_change = partial(update_from_size_calc, order=order) - line._on_level_change = update_from_size_calc + # set level update callback to order pane method and update once + # immediately + line._on_level_change = partial( + self.pane.on_level_change_update_next_order_info, + line=line, + order=order, + ) + line._on_level_change(order.price) return line @@ -493,16 +482,52 @@ async def run_order_mode( ), ): + log.info(f'Opening order mode for {brokername}.{symbol.key}') + view = chart.view + + # annotations editors lines = LineEditor(chart=chart) arrows = ArrowEditor(chart, {}) - log.info("Opening order mode") + # allocator + alloc = mk_alloc( + symbol=symbol, + accounts=brokers.config.load_accounts() + ) + + # form = chart.linked.godwidget.pp_pane.model + form = chart.sidepane + form.model = alloc - alloc = chart.linked.godwidget.pp_pane.model pp_tracker = PositionTracker(chart, alloc=alloc) pp_tracker.hide() - alloc._position = pp_tracker + + # order pane widgets and allocation model + order_pane = OrderModePane( + tracker=pp_tracker, + form=form, + alloc=alloc, + fill_bar=form.fill_bar, + pnl_label=form.left_label, + step_label=form.bottom_label, + limit_label=form.top_label, + ) + + for key in ('account', 'size_unit', 'disti_weight'): + w = form.fields[key] + + def write_model(text: str, key: str): + print(f'{text}') + setattr(alloc, key, text) + print(alloc) + + w.currentTextChanged.connect( + partial( + write_model, + key=key, + ) + ) mode = OrderMode( chart, @@ -512,6 +537,7 @@ async def run_order_mode( multistatus, pp_tracker, allocator=alloc, + pane=order_pane, ) # TODO: create a mode "manager" of sorts? @@ -529,7 +555,6 @@ async def run_order_mode( pp_tracker.update(msg, position=pp_tracker.startup_pp) pp_tracker.update(msg) - # default entry sizing if asset_type in ('stock', 'crypto', 'forex'): alloc.size_unit = '$ size' @@ -544,11 +569,11 @@ async def run_order_mode( pp_tracker.pane.fields['slots'].setText(str(alloc.slots)) # make entry step 1.0 - alloc.size = slots - pp_tracker.pane.fields['size'].setText(str(alloc.size)) + alloc.units_size = slots + pp_tracker.pane.fields['size'].setText(str(alloc.units_size)) # make fill bar and positioning snapshot - pp_tracker.init_status_ui() + order_pane.init_status_ui() # TODO: this should go onto some sort of # data-view strimg thinger..right? @@ -572,7 +597,11 @@ async def run_order_mode( async with ( chart.view.open_async_input_handler(), - # TODO: config form handler nursery + # pp pane kb inputs + open_form_input_handling( + form, + focus_next=chart.linked.godwidget, + ), ):