commit
3e302f8445
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
)
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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):
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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'
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue