Merge pull request #237 from pikers/unit_select_fixes

Order pane fixes
simpler_quote_throttle_logic
goodboy 2021-11-12 15:54:34 -05:00 committed by GitHub
commit 3e302f8445
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 106 additions and 48 deletions

View File

@ -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
@ -204,7 +215,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
@ -259,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,
@ -274,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)
@ -287,6 +305,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 +327,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

View File

@ -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
@ -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,
@ -449,7 +452,7 @@ async def open_brokerd_trades_dialogue(
relay = TradesRelay(
brokerd_dialogue=brokerd_trades_stream,
positions=pps,
accounts=set(accounts),
accounts=accounts,
consumers=1,
)

View File

@ -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
@ -648,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

View File

@ -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):

View File

@ -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:
@ -152,7 +154,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,30 +211,31 @@ 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}')
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 +262,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?
@ -339,6 +345,7 @@ class SettingsPane:
update_pnl_from_feed,
feed,
mode,
tracker,
)
# immediately display in status label

View File

@ -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'

View File

@ -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``
@ -571,7 +578,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.
@ -617,8 +627,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()
@ -802,12 +812,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