commit
3e302f8445
|
@ -87,13 +87,21 @@ class Allocator(BaseModel):
|
||||||
|
|
||||||
symbol: Symbol
|
symbol: Symbol
|
||||||
account: Optional[str] = 'paper'
|
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
|
_size_units: dict[str, Optional[str]] = _size_units
|
||||||
|
|
||||||
@validator('size_unit')
|
@validator('size_unit', pre=True)
|
||||||
def lookup_key(cls, v):
|
def maybe_lookup_key(cls, v):
|
||||||
# apply the corresponding enum key for the text "description" value
|
# 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
|
# TODO: if we ever want ot support non-uniform entry-slot-proportion
|
||||||
# "sizes"
|
# "sizes"
|
||||||
|
@ -157,6 +165,9 @@ class Allocator(BaseModel):
|
||||||
slot_size = currency_per_slot / price
|
slot_size = currency_per_slot / price
|
||||||
l_sub_pp = (self.currency_limit - live_cost_basis) / 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)
|
# an entry (adding-to or starting a pp)
|
||||||
if (
|
if (
|
||||||
action == 'buy' and live_size > 0 or
|
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
|
# **without** going past a net-zero pp. if the pp is
|
||||||
# > 1.5x a slot size, then front load: exit a slot's and
|
# > 1.5x a slot size, then front load: exit a slot's and
|
||||||
# expect net-zero to be acquired on the final exit.
|
# 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
|
order_size = abs_live_size
|
||||||
|
|
||||||
|
@ -259,7 +277,7 @@ def mk_allocator(
|
||||||
# default allocation settings
|
# default allocation settings
|
||||||
defaults: dict[str, float] = {
|
defaults: dict[str, float] = {
|
||||||
'account': None, # select paper by default
|
'account': None, # select paper by default
|
||||||
'size_unit': _size_units['currency'],
|
'size_unit': 'currency', #_size_units['currency'],
|
||||||
'units_limit': 400,
|
'units_limit': 400,
|
||||||
'currency_limit': 5e3,
|
'currency_limit': 5e3,
|
||||||
'slots': 4,
|
'slots': 4,
|
||||||
|
@ -274,8 +292,8 @@ def mk_allocator(
|
||||||
# load and retreive user settings for default allocations
|
# load and retreive user settings for default allocations
|
||||||
# ``config.toml``
|
# ``config.toml``
|
||||||
user_def = {
|
user_def = {
|
||||||
'currency_limit': 5e3,
|
'currency_limit': 6e3,
|
||||||
'slots': 4,
|
'slots': 6,
|
||||||
}
|
}
|
||||||
|
|
||||||
defaults.update(user_def)
|
defaults.update(user_def)
|
||||||
|
@ -287,6 +305,7 @@ def mk_allocator(
|
||||||
|
|
||||||
asset_type = symbol.type_key
|
asset_type = symbol.type_key
|
||||||
|
|
||||||
|
|
||||||
# specific configs by asset class / type
|
# specific configs by asset class / type
|
||||||
|
|
||||||
if asset_type in ('future', 'option', 'futures_option'):
|
if asset_type in ('future', 'option', 'futures_option'):
|
||||||
|
@ -308,9 +327,12 @@ def mk_allocator(
|
||||||
alloc.currency_limit = round(startup_size, ndigits=2)
|
alloc.currency_limit = round(startup_size, ndigits=2)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
startup_size = startup_pp.size
|
startup_size = abs(startup_pp.size)
|
||||||
|
|
||||||
if startup_size > alloc.units_limit:
|
if startup_size > alloc.units_limit:
|
||||||
alloc.units_limit = startup_size
|
alloc.units_limit = startup_size
|
||||||
|
|
||||||
|
if asset_type in ('future', 'option', 'futures_option'):
|
||||||
|
alloc.slots = alloc.units_limit
|
||||||
|
|
||||||
return alloc
|
return alloc
|
||||||
|
|
|
@ -269,7 +269,7 @@ class TradesRelay:
|
||||||
positions: dict[str, dict[str, BrokerdPosition]]
|
positions: dict[str, dict[str, BrokerdPosition]]
|
||||||
|
|
||||||
# allowed account names
|
# allowed account names
|
||||||
accounts: set[str]
|
accounts: tuple[str]
|
||||||
|
|
||||||
# count of connected ems clients for this ``brokerd``
|
# count of connected ems clients for this ``brokerd``
|
||||||
consumers: int = 0
|
consumers: int = 0
|
||||||
|
@ -414,6 +414,9 @@ async def open_brokerd_trades_dialogue(
|
||||||
)
|
)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
positions: list[BrokerdPosition]
|
||||||
|
accounts: tuple[str]
|
||||||
|
|
||||||
async with (
|
async with (
|
||||||
open_trades_endpoint as (brokerd_ctx, (positions, accounts,)),
|
open_trades_endpoint as (brokerd_ctx, (positions, accounts,)),
|
||||||
brokerd_ctx.open_stream() as brokerd_trades_stream,
|
brokerd_ctx.open_stream() as brokerd_trades_stream,
|
||||||
|
@ -449,7 +452,7 @@ async def open_brokerd_trades_dialogue(
|
||||||
relay = TradesRelay(
|
relay = TradesRelay(
|
||||||
brokerd_dialogue=brokerd_trades_stream,
|
brokerd_dialogue=brokerd_trades_stream,
|
||||||
positions=pps,
|
positions=pps,
|
||||||
accounts=set(accounts),
|
accounts=accounts,
|
||||||
consumers=1,
|
consumers=1,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ from ._style import hcolor, _font, _font_small, DpiAwareFont
|
||||||
from ._label import FormatLabel
|
from ._label import FormatLabel
|
||||||
|
|
||||||
|
|
||||||
class FontAndChartAwareLineEdit(QLineEdit):
|
class Edit(QLineEdit):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
|
|
||||||
|
@ -369,13 +369,14 @@ class FieldsForm(QWidget):
|
||||||
key: str,
|
key: str,
|
||||||
label_name: str,
|
label_name: str,
|
||||||
value: str,
|
value: str,
|
||||||
|
readonly: bool = False,
|
||||||
|
|
||||||
) -> FontAndChartAwareLineEdit:
|
) -> Edit:
|
||||||
|
|
||||||
# TODO: maybe a distint layout per "field" item?
|
# TODO: maybe a distint layout per "field" item?
|
||||||
label = self.add_field_label(label_name)
|
label = self.add_field_label(label_name)
|
||||||
|
|
||||||
edit = FontAndChartAwareLineEdit(
|
edit = Edit(
|
||||||
parent=self,
|
parent=self,
|
||||||
# width_in_chars=6,
|
# width_in_chars=6,
|
||||||
)
|
)
|
||||||
|
@ -386,6 +387,7 @@ class FieldsForm(QWidget):
|
||||||
}}
|
}}
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
|
edit.setReadOnly(readonly)
|
||||||
edit.setText(str(value))
|
edit.setText(str(value))
|
||||||
self.form.addRow(label, edit)
|
self.form.addRow(label, edit)
|
||||||
|
|
||||||
|
@ -478,13 +480,15 @@ def mk_form(
|
||||||
for key, conf in fields_schema.items():
|
for key, conf in fields_schema.items():
|
||||||
wtype = conf['type']
|
wtype = conf['type']
|
||||||
label = str(conf.get('label', key))
|
label = str(conf.get('label', key))
|
||||||
|
kwargs = conf.get('kwargs', {})
|
||||||
|
|
||||||
# plain (line) edit field
|
# plain (line) edit field
|
||||||
if wtype == 'edit':
|
if wtype == 'edit':
|
||||||
w = form.add_edit_field(
|
w = form.add_edit_field(
|
||||||
key,
|
key,
|
||||||
label,
|
label,
|
||||||
conf['default_value']
|
conf['default_value'],
|
||||||
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
# drop-down selection
|
# drop-down selection
|
||||||
|
@ -493,7 +497,8 @@ def mk_form(
|
||||||
w = form.add_select_field(
|
w = form.add_select_field(
|
||||||
key,
|
key,
|
||||||
label,
|
label,
|
||||||
values
|
values,
|
||||||
|
**kwargs,
|
||||||
)
|
)
|
||||||
|
|
||||||
w._key = key
|
w._key = key
|
||||||
|
@ -648,11 +653,21 @@ def mk_fill_status_bar(
|
||||||
font_size=bar_label_font_size,
|
font_size=bar_label_font_size,
|
||||||
font_color='gunmetal',
|
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.addSpacing(5/8 * bar_h)
|
||||||
bar_labels_lhs.addWidget(
|
bar_labels_lhs.addWidget(
|
||||||
left_label,
|
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
|
# this hbox is added as a layout by the paner maker/caller
|
||||||
|
|
|
@ -36,7 +36,7 @@ from PyQt5.QtWidgets import (
|
||||||
|
|
||||||
from ._forms import (
|
from ._forms import (
|
||||||
# FontScaledDelegate,
|
# FontScaledDelegate,
|
||||||
FontAndChartAwareLineEdit,
|
Edit,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@ -97,7 +97,7 @@ class Selection(Field[DataType], Generic[DataType]):
|
||||||
class Edit(Field[DataType], Generic[DataType]):
|
class Edit(Field[DataType], Generic[DataType]):
|
||||||
'''An edit field which takes a number.
|
'''An edit field which takes a number.
|
||||||
'''
|
'''
|
||||||
widget_factory = FontAndChartAwareLineEdit
|
widget_factory = Edit
|
||||||
|
|
||||||
|
|
||||||
class AllocatorPane(BaseModel):
|
class AllocatorPane(BaseModel):
|
||||||
|
|
|
@ -54,6 +54,7 @@ async def update_pnl_from_feed(
|
||||||
|
|
||||||
feed: Feed,
|
feed: Feed,
|
||||||
order_mode: OrderMode, # noqa
|
order_mode: OrderMode, # noqa
|
||||||
|
tracker: PositionTracker,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
'''Real-time display the current pp's PnL in the appropriate label.
|
'''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')
|
types = ('bid', 'last', 'last', 'utrade')
|
||||||
|
|
||||||
else:
|
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
|
# real-time update pnl on the status pane
|
||||||
try:
|
try:
|
||||||
|
@ -152,7 +154,7 @@ class SettingsPane:
|
||||||
'''Called on any order pane drop down selection change.
|
'''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)
|
self.on_ui_settings_change(key, text)
|
||||||
|
|
||||||
def on_ui_settings_change(
|
def on_ui_settings_change(
|
||||||
|
@ -209,6 +211,12 @@ class SettingsPane:
|
||||||
|
|
||||||
# WRITE any settings to current pp's allocator
|
# WRITE any settings to current pp's allocator
|
||||||
try:
|
try:
|
||||||
|
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:
|
||||||
value = puterize(value)
|
value = puterize(value)
|
||||||
if key == 'limit':
|
if key == 'limit':
|
||||||
if size_unit == 'currency':
|
if size_unit == 'currency':
|
||||||
|
@ -219,11 +227,6 @@ class SettingsPane:
|
||||||
elif key == 'slots':
|
elif key == 'slots':
|
||||||
alloc.slots = int(value)
|
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
|
|
||||||
alloc.size_unit = value
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise ValueError(f'Unknown setting {key}')
|
raise ValueError(f'Unknown setting {key}')
|
||||||
|
|
||||||
|
@ -232,7 +235,7 @@ class SettingsPane:
|
||||||
except ValueError:
|
except ValueError:
|
||||||
log.error(f'Invalid value for `{key}`: {value}')
|
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]
|
suffix = {'currency': ' $', 'units': ' u'}[size_unit]
|
||||||
limit = alloc.limit()
|
limit = alloc.limit()
|
||||||
|
|
||||||
|
@ -259,6 +262,9 @@ class SettingsPane:
|
||||||
self.form.fields['slots'].setText(str(alloc.slots))
|
self.form.fields['slots'].setText(str(alloc.slots))
|
||||||
self.form.fields['limit'].setText(str(limit))
|
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
|
# 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
|
# can have general input handling code to report it through the
|
||||||
# UI in some way?
|
# UI in some way?
|
||||||
|
@ -339,6 +345,7 @@ class SettingsPane:
|
||||||
update_pnl_from_feed,
|
update_pnl_from_feed,
|
||||||
feed,
|
feed,
|
||||||
mode,
|
mode,
|
||||||
|
tracker,
|
||||||
)
|
)
|
||||||
|
|
||||||
# immediately display in status label
|
# immediately display in status label
|
||||||
|
|
|
@ -72,7 +72,7 @@ from ._style import (
|
||||||
_font,
|
_font,
|
||||||
hcolor,
|
hcolor,
|
||||||
)
|
)
|
||||||
from ._forms import FontAndChartAwareLineEdit, FontScaledDelegate
|
from ._forms import Edit, FontScaledDelegate
|
||||||
|
|
||||||
|
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
@ -407,7 +407,7 @@ class CompleterView(QTreeView):
|
||||||
self.resize()
|
self.resize()
|
||||||
|
|
||||||
|
|
||||||
class SearchBar(FontAndChartAwareLineEdit):
|
class SearchBar(Edit):
|
||||||
|
|
||||||
mode_name: str = 'search'
|
mode_name: str = 'search'
|
||||||
|
|
||||||
|
|
|
@ -47,7 +47,7 @@ from ._position import (
|
||||||
)
|
)
|
||||||
from ._label import FormatLabel
|
from ._label import FormatLabel
|
||||||
from ._window import MultiStatus
|
from ._window import MultiStatus
|
||||||
from ..clearing._messages import Order
|
from ..clearing._messages import Order, BrokerdPosition
|
||||||
from ._forms import open_form_input_handling
|
from ._forms import open_form_input_handling
|
||||||
|
|
||||||
|
|
||||||
|
@ -529,7 +529,12 @@ async def open_order_mode(
|
||||||
|
|
||||||
book: OrderBook
|
book: OrderBook
|
||||||
trades_stream: tractor.MsgStream
|
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
|
# spawn EMS actor-service
|
||||||
async with (
|
async with (
|
||||||
|
@ -563,7 +568,9 @@ async def open_order_mode(
|
||||||
providers=symbol.brokers
|
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 = {}
|
accounts = {}
|
||||||
for name in brokerd_accounts:
|
for name in brokerd_accounts:
|
||||||
# ensure name is in ``brokers.toml``
|
# 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
|
# first account listed is the one we select at startup
|
||||||
# (aka order based selection).
|
# (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
|
# NOTE: requires the backend exactly specifies
|
||||||
# the expected symbol key in its positions msg.
|
# the expected symbol key in its positions msg.
|
||||||
|
@ -617,8 +627,8 @@ async def open_order_mode(
|
||||||
# alloc?
|
# alloc?
|
||||||
pp_tracker.update_from_pp()
|
pp_tracker.update_from_pp()
|
||||||
|
|
||||||
|
# on existing position, show pp tracking graphics
|
||||||
if pp_tracker.startup_pp.size != 0:
|
if pp_tracker.startup_pp.size != 0:
|
||||||
# if no position, don't show pp tracking graphics
|
|
||||||
pp_tracker.show()
|
pp_tracker.show()
|
||||||
pp_tracker.hide_info()
|
pp_tracker.hide_info()
|
||||||
|
|
||||||
|
@ -802,10 +812,11 @@ async def process_trades_and_update_ui(
|
||||||
|
|
||||||
tracker = mode.trackers[msg['account']]
|
tracker = mode.trackers[msg['account']]
|
||||||
tracker.live_pp.update_from_msg(msg)
|
tracker.live_pp.update_from_msg(msg)
|
||||||
tracker.update_from_pp()
|
|
||||||
|
|
||||||
# update order pane widgets
|
# update order pane widgets
|
||||||
|
tracker.update_from_pp()
|
||||||
mode.pane.update_status_ui(tracker)
|
mode.pane.update_status_ui(tracker)
|
||||||
|
|
||||||
|
if tracker.live_pp.size:
|
||||||
# display pnl
|
# display pnl
|
||||||
mode.pane.display_pnl(tracker)
|
mode.pane.display_pnl(tracker)
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue