diff --git a/piker/brokers/ib.py b/piker/brokers/ib.py index 9f1bd49b..ab440ffe 100644 --- a/piker/brokers/ib.py +++ b/piker/brokers/ib.py @@ -517,9 +517,9 @@ class Client: contract, ticker, details = await self.get_sym_details(symbol) # ensure a last price gets filled in before we deliver quote - for _ in range(1): + for _ in range(100): if isnan(ticker.last): - await asyncio.sleep(0.1) + await asyncio.sleep(0.01) log.warning(f'Quote for {symbol} timed out: market is closed?') ticker = await ticker.updateEvent else: @@ -792,7 +792,7 @@ async def load_aio_clients( try_ports = list(ports.values()) ports = try_ports if port is None else [port] # we_connected = [] - connect_timeout = 1 if platform.system() != 'Windows' else 2 + connect_timeout = 2 combos = list(itertools.product(hosts, ports)) # allocate new and/or reload disconnected but cached clients @@ -1428,8 +1428,14 @@ async def stream_quotes( ''' # TODO: support multiple subscriptions sym = symbols[0] + details: Optional[dict] = None - with trio.fail_after(16): + contract, first_ticker, details = await _trio_run_client_method( + method='get_sym_details', + symbol=sym, + ) + + with trio.move_on_after(1): contract, first_ticker, details = await _trio_run_client_method( method='get_quote', symbol=sym, diff --git a/piker/clearing/_allocate.py b/piker/clearing/_allocate.py index 7b61a4bd..6ef38692 100644 --- a/piker/clearing/_allocate.py +++ b/piker/clearing/_allocate.py @@ -29,7 +29,8 @@ from ._messages import BrokerdPosition, Status class Position(BaseModel): - '''Basic pp (personal position) model with attached fills history. + ''' + Basic pp (personal position) model with attached fills history. This type should be IPC wire ready? @@ -61,6 +62,15 @@ class Position(BaseModel): self.avg_price = avg_price self.size = size + @property + def dsize(self) -> float: + ''' + The "dollar" size of the pp, normally in trading (fiat) unit + terms. + + ''' + return self.avg_price * self.size + _size_units = bidict({ 'currency': '$ size', @@ -114,7 +124,8 @@ class Allocator(BaseModel): def step_sizes( self, ) -> (float, float): - '''Return the units size for each unit type as a tuple. + ''' + Return the units size for each unit type as a tuple. ''' slots = self.slots @@ -142,7 +153,8 @@ class Allocator(BaseModel): action: str, ) -> dict: - '''Generate order request info for the "next" submittable order + ''' + Generate order request info for the "next" submittable order depending on position / order entry config. ''' @@ -250,7 +262,8 @@ class Allocator(BaseModel): pp: Position, ) -> float: - '''Calc and return the number of slots used by this ``Position``. + ''' + Calc and return the number of slots used by this ``Position``. ''' abs_pp_size = abs(pp.size) diff --git a/piker/ui/_forms.py b/piker/ui/_forms.py index 1f1a7cd5..3b33e032 100644 --- a/piker/ui/_forms.py +++ b/piker/ui/_forms.py @@ -21,6 +21,7 @@ Text entry "forms" widgets (mostly for configuration and UI user input). from __future__ import annotations from contextlib import asynccontextmanager from functools import partial +from math import floor from typing import ( Optional, Any, Callable, Awaitable ) @@ -542,7 +543,7 @@ class FillStatusBar(QProgressBar): ''' border_px: int = 2 - slot_margin_px: int = 2 + slot_margin_px: int = 1 def __init__( self, @@ -553,7 +554,11 @@ class FillStatusBar(QProgressBar): ) -> None: super().__init__(parent=parent) - self.approx_h = approx_height_px + + self.approx_h = int(round(approx_height_px)) + self.setMinimumHeight(self.approx_h) + self.setMaximumHeight(self.approx_h) + self.font_size = font_size self.setFormat('') # label format @@ -567,17 +572,12 @@ class FillStatusBar(QProgressBar): ) -> None: - approx_h = self.approx_h - # TODO: compute "used height" thus far and mostly fill the rest - tot_slot_h, r = divmod( - approx_h, - slots, - ) - clipped = int(slots * tot_slot_h + 2*self.border_px) - self.setMaximumHeight(clipped) - slot_height_px = tot_slot_h - 2*self.slot_margin_px - self.setOrientation(Qt.Vertical) + h = self.height() + + # TODO: compute "used height" thus far and mostly fill the rest + tot_slot_h, r = divmod(h, slots) + self.setStyleSheet( f""" QProgressBar {{ @@ -592,21 +592,28 @@ class FillStatusBar(QProgressBar): border: {self.border_px}px solid {hcolor('default_light')}; border-radius: 2px; }} - QProgressBar::chunk {{ background-color: {hcolor('default_spotlight')}; color: {hcolor('bracket')}; border-radius: 2px; - - margin: {self.slot_margin_px}px; - height: {slot_height_px}px; - }} """ ) + # to set a discrete "block" per slot... + # XXX: couldn't get the discrete math to work here such + # that it was always correctly showing a discretized value + # up to the limit; not sure if it's the ``.setRange()`` + # / ``.setValue()`` api or not but i was able to get something + # close screwing with the divmod above above but after so large + # a value it would always be less chunks then the correct + # value.. + # margin: {self.slot_margin_px}px; + # height: {slot_height_px}px; + + # margin-bottom: {slot_margin_px*2}px; # margin-top: {slot_margin_px*2}px; # color: #19232D; diff --git a/piker/ui/_position.py b/piker/ui/_position.py index e057154e..0abb6459 100644 --- a/piker/ui/_position.py +++ b/piker/ui/_position.py @@ -71,10 +71,10 @@ async def update_pnl_from_feed( log.info(f'Starting pnl display for {pp.alloc.account}') if live.size < 0: - types = ('ask', 'last', 'last', 'utrade') + types = ('ask', 'last', 'last', 'dark_trade') elif live.size > 0: - types = ('bid', 'last', 'last', 'utrade') + types = ('bid', 'last', 'last', 'dark_trade') else: log.info(f'No position (yet) for {tracker.alloc.account}@{key}') @@ -119,7 +119,8 @@ async def update_pnl_from_feed( @dataclass class SettingsPane: - '''Composite set of widgets plus an allocator model for configuring + ''' + Composite set of widgets plus an allocator model for configuring order entry sizes and position limits per tradable instrument. ''' @@ -151,7 +152,8 @@ class SettingsPane: key: str, ) -> None: - '''Called on any order pane drop down selection change. + ''' + Called on any order pane drop down selection change. ''' log.info(f'selection input {key}:{text}') @@ -164,7 +166,8 @@ class SettingsPane: value: str, ) -> bool: - '''Called on any order pane edit field value change. + ''' + Called on any order pane edit field value change. ''' mode = self.order_mode @@ -219,16 +222,36 @@ class SettingsPane: else: value = puterize(value) if key == 'limit': + pp = mode.current_pp.live_pp + if size_unit == 'currency': + dsize = pp.dsize + if dsize > value: + log.error( + f'limit must > then current pp: {dsize}' + ) + raise ValueError + alloc.currency_limit = value + else: + size = pp.size + if size > value: + log.error( + f'limit must > then current pp: {size}' + ) + raise ValueError + alloc.units_limit = value elif key == 'slots': + if value <= 0: + raise ValueError('slots must be > 0') alloc.slots = int(value) else: - raise ValueError(f'Unknown setting {key}') + log.error(f'Unknown setting {key}') + raise ValueError log.info(f'settings change: {key}: {value}') @@ -363,7 +386,8 @@ def position_line( marker: Optional[LevelMarker] = None, ) -> LevelLine: - '''Convenience routine to create a line graphic representing a "pp" + ''' + Convenience routine to create a line graphic representing a "pp" aka the acro for a, "{piker, private, personal, puny, } position". @@ -417,7 +441,8 @@ def position_line( class PositionTracker: - '''Track and display real-time positions for a single symbol + ''' + Track and display real-time positions for a single symbol over multiple accounts on a single chart. Graphically composed of a level line and marker as well as labels @@ -497,7 +522,8 @@ class PositionTracker: @property def pane(self) -> FieldsForm: - '''Return handle to pp side pane form. + ''' + Return handle to pp side pane form. ''' return self.chart.linked.godwidget.pp_pane @@ -507,7 +533,8 @@ class PositionTracker: marker: LevelMarker ) -> None: - '''Update all labels. + ''' + Update all labels. Meant to be called from the maker ``.paint()`` for immediate, lag free label draws. diff --git a/piker/ui/order_mode.py b/piker/ui/order_mode.py index 437e4ca5..9f4dbadb 100644 --- a/piker/ui/order_mode.py +++ b/piker/ui/order_mode.py @@ -107,7 +107,8 @@ def on_level_change_update_next_order_info( @dataclass class OrderMode: - '''Major UX mode for placing orders on a chart view providing so + ''' + Major UX mode for placing orders on a chart view providing so called, "chart trading". This is the other "main" mode that pairs with "view mode" (when