Move allocator components to clearing sub-pkg
parent
343cb4b0ae
commit
214c622328
|
@ -0,0 +1,301 @@
|
||||||
|
# piker: trading gear for hackers
|
||||||
|
# Copyright (C) Tyler Goodlet (in stewardship for piker0)
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
'''
|
||||||
|
Position allocation logic and protocols.
|
||||||
|
|
||||||
|
'''
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
from bidict import bidict
|
||||||
|
from pydantic import BaseModel, validator
|
||||||
|
|
||||||
|
from ..data._source import Symbol
|
||||||
|
from ._messages import BrokerdPosition, Status
|
||||||
|
|
||||||
|
|
||||||
|
class Position(BaseModel):
|
||||||
|
'''Basic pp (personal position) model with attached fills history.
|
||||||
|
|
||||||
|
This type should be IPC wire ready?
|
||||||
|
|
||||||
|
'''
|
||||||
|
symbol: Symbol
|
||||||
|
|
||||||
|
# last size and avg entry price
|
||||||
|
size: float
|
||||||
|
avg_price: float # TODO: contextual pricing
|
||||||
|
|
||||||
|
# ordered record of known constituent trade messages
|
||||||
|
fills: list[Status] = []
|
||||||
|
|
||||||
|
def update_from_msg(
|
||||||
|
self,
|
||||||
|
msg: BrokerdPosition,
|
||||||
|
|
||||||
|
) -> None:
|
||||||
|
|
||||||
|
# XXX: better place to do this?
|
||||||
|
symbol = self.symbol
|
||||||
|
|
||||||
|
lot_size_digits = symbol.lot_size_digits
|
||||||
|
avg_price, size = (
|
||||||
|
round(msg['avg_price'], ndigits=symbol.tick_size_digits),
|
||||||
|
round(msg['size'], ndigits=lot_size_digits),
|
||||||
|
)
|
||||||
|
|
||||||
|
self.avg_price = avg_price
|
||||||
|
self.size = size
|
||||||
|
|
||||||
|
|
||||||
|
_size_units = bidict({
|
||||||
|
'currency': '$ size',
|
||||||
|
'units': '# units',
|
||||||
|
# TODO: but we'll need a `<brokermod>.get_accounts()` or something
|
||||||
|
# 'percent_of_port': '% of port',
|
||||||
|
})
|
||||||
|
SizeUnit = Enum(
|
||||||
|
'SizeUnit',
|
||||||
|
_size_units,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Allocator(BaseModel):
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
validate_assignment = True
|
||||||
|
copy_on_model_validation = False
|
||||||
|
arbitrary_types_allowed = True
|
||||||
|
|
||||||
|
# required to get the account validator lookup working?
|
||||||
|
extra = 'allow'
|
||||||
|
# underscore_attrs_are_private = False
|
||||||
|
|
||||||
|
symbol: Symbol
|
||||||
|
|
||||||
|
account: Optional[str] = 'paper'
|
||||||
|
_accounts: bidict[str, Optional[str]]
|
||||||
|
|
||||||
|
@validator('account', pre=True)
|
||||||
|
def set_account(cls, v, values):
|
||||||
|
if v:
|
||||||
|
return values['_accounts'][v]
|
||||||
|
|
||||||
|
size_unit: SizeUnit = 'currency'
|
||||||
|
_size_units: dict[str, Optional[str]] = _size_units
|
||||||
|
|
||||||
|
@validator('size_unit')
|
||||||
|
def lookup_key(cls, v):
|
||||||
|
# apply the corresponding enum key for the text "description" value
|
||||||
|
return v.name
|
||||||
|
|
||||||
|
# TODO: if we ever want ot support non-uniform entry-slot-proportion
|
||||||
|
# "sizes"
|
||||||
|
# disti_weight: str = 'uniform'
|
||||||
|
|
||||||
|
units_limit: float
|
||||||
|
currency_limit: float
|
||||||
|
slots: int
|
||||||
|
|
||||||
|
def step_sizes(
|
||||||
|
self,
|
||||||
|
) -> (float, float):
|
||||||
|
'''Return the units size for each unit type as a tuple.
|
||||||
|
|
||||||
|
'''
|
||||||
|
slots = self.slots
|
||||||
|
return (
|
||||||
|
self.units_limit / slots,
|
||||||
|
self.currency_limit / slots,
|
||||||
|
)
|
||||||
|
|
||||||
|
def limit(self) -> float:
|
||||||
|
if self.size_unit == 'currency':
|
||||||
|
return self.currency_limit
|
||||||
|
else:
|
||||||
|
return self.units_limit
|
||||||
|
|
||||||
|
def next_order_info(
|
||||||
|
self,
|
||||||
|
|
||||||
|
startup_pp: Position,
|
||||||
|
live_pp: Position,
|
||||||
|
price: float,
|
||||||
|
action: str,
|
||||||
|
|
||||||
|
) -> dict:
|
||||||
|
'''Generate order request info for the "next" submittable order
|
||||||
|
depending on position / order entry config.
|
||||||
|
|
||||||
|
'''
|
||||||
|
sym = self.symbol
|
||||||
|
ld = sym.lot_size_digits
|
||||||
|
|
||||||
|
size_unit = self.size_unit
|
||||||
|
live_size = live_pp.size
|
||||||
|
abs_live_size = abs(live_size)
|
||||||
|
abs_startup_size = abs(startup_pp.size)
|
||||||
|
|
||||||
|
u_per_slot, currency_per_slot = self.step_sizes()
|
||||||
|
|
||||||
|
if size_unit == 'units':
|
||||||
|
slot_size = u_per_slot
|
||||||
|
l_sub_pp = self.units_limit - abs_live_size
|
||||||
|
|
||||||
|
elif size_unit == 'currency':
|
||||||
|
live_cost_basis = abs_live_size * live_pp.avg_price
|
||||||
|
slot_size = currency_per_slot / price
|
||||||
|
l_sub_pp = (self.currency_limit - live_cost_basis) / price
|
||||||
|
|
||||||
|
# an entry (adding-to or starting a pp)
|
||||||
|
if (
|
||||||
|
action == 'buy' and live_size > 0 or
|
||||||
|
action == 'sell' and live_size < 0 or
|
||||||
|
live_size == 0
|
||||||
|
):
|
||||||
|
|
||||||
|
order_size = min(slot_size, l_sub_pp)
|
||||||
|
|
||||||
|
# an exit (removing-from or going to net-zero pp)
|
||||||
|
else:
|
||||||
|
# when exiting a pp we always try to slot the position
|
||||||
|
# in the instrument's units, since doing so in a derived
|
||||||
|
# size measure (eg. currency value, percent of port) would
|
||||||
|
# result in a mis-mapping of slots sizes in unit terms
|
||||||
|
# (i.e. it would take *more* slots to exit at a profit and
|
||||||
|
# *less* slots to exit at a loss).
|
||||||
|
pp_size = max(abs_startup_size, abs_live_size)
|
||||||
|
slotted_pp = pp_size / self.slots
|
||||||
|
|
||||||
|
if size_unit == 'currency':
|
||||||
|
# compute the "projected" limit's worth of units at the
|
||||||
|
# current pp (weighted) price:
|
||||||
|
slot_size = currency_per_slot / live_pp.avg_price
|
||||||
|
|
||||||
|
else:
|
||||||
|
slot_size = u_per_slot
|
||||||
|
|
||||||
|
# if our position is greater then our limit setting
|
||||||
|
# we'll want to use slot sizes which are larger then what
|
||||||
|
# the limit would normally determine
|
||||||
|
order_size = max(slotted_pp, slot_size)
|
||||||
|
|
||||||
|
if (
|
||||||
|
abs_live_size < slot_size or
|
||||||
|
|
||||||
|
# NOTE: front/back "loading" heurstic:
|
||||||
|
# if the remaining pp is in between 0-1.5x a slot's
|
||||||
|
# worth, dump the whole position in this last exit
|
||||||
|
# therefore conducting so called "back loading" but
|
||||||
|
# **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)
|
||||||
|
):
|
||||||
|
order_size = abs_live_size
|
||||||
|
|
||||||
|
slots_used = 1.0 # the default uniform policy
|
||||||
|
if order_size < slot_size:
|
||||||
|
# compute a fractional slots size to display
|
||||||
|
slots_used = self.slots_used(
|
||||||
|
Position(symbol=sym, size=order_size, avg_price=price)
|
||||||
|
)
|
||||||
|
|
||||||
|
return {
|
||||||
|
'size': abs(round(order_size, ndigits=ld)),
|
||||||
|
'size_digits': ld,
|
||||||
|
|
||||||
|
# TODO: incorporate multipliers for relevant derivatives
|
||||||
|
'fiat_size': round(order_size * price, ndigits=2),
|
||||||
|
'slots_used': slots_used,
|
||||||
|
}
|
||||||
|
|
||||||
|
def slots_used(
|
||||||
|
self,
|
||||||
|
pp: Position,
|
||||||
|
|
||||||
|
) -> float:
|
||||||
|
'''Calc and return the number of slots used by this ``Position``.
|
||||||
|
|
||||||
|
'''
|
||||||
|
abs_pp_size = abs(pp.size)
|
||||||
|
|
||||||
|
if self.size_unit == 'currency':
|
||||||
|
# live_currency_size = size or (abs_pp_size * pp.avg_price)
|
||||||
|
live_currency_size = abs_pp_size * pp.avg_price
|
||||||
|
prop = live_currency_size / self.currency_limit
|
||||||
|
|
||||||
|
else:
|
||||||
|
# return (size or abs_pp_size) / alloc.units_limit
|
||||||
|
prop = abs_pp_size / self.units_limit
|
||||||
|
|
||||||
|
# TODO: REALLY need a way to show partial slots..
|
||||||
|
# for now we round at the midway point between slots
|
||||||
|
return round(prop * self.slots)
|
||||||
|
|
||||||
|
|
||||||
|
def mk_allocator(
|
||||||
|
|
||||||
|
alloc: Allocator,
|
||||||
|
startup_pp: Position,
|
||||||
|
|
||||||
|
) -> (float, Allocator):
|
||||||
|
|
||||||
|
asset_type = alloc.symbol.type_key
|
||||||
|
|
||||||
|
# load and retreive user settings for default allocations
|
||||||
|
# ``config.toml``
|
||||||
|
slots = 4
|
||||||
|
currency_limit = 5e3
|
||||||
|
|
||||||
|
alloc.slots = slots
|
||||||
|
alloc.currency_limit = currency_limit
|
||||||
|
|
||||||
|
# default entry sizing
|
||||||
|
if asset_type in ('stock', 'crypto', 'forex'):
|
||||||
|
|
||||||
|
alloc.size_unit = '$ size'
|
||||||
|
|
||||||
|
elif asset_type in ('future', 'option', 'futures_option'):
|
||||||
|
|
||||||
|
# since it's harder to know how currency "applies" in this case
|
||||||
|
# given leverage properties
|
||||||
|
alloc.size_unit = '# units'
|
||||||
|
|
||||||
|
# set units limit to slots size thus making make the next
|
||||||
|
# entry step 1.0
|
||||||
|
alloc.units_limit = slots
|
||||||
|
|
||||||
|
# if the current position is already greater then the limit
|
||||||
|
# settings, increase the limit to the current position
|
||||||
|
if alloc.size_unit == 'currency':
|
||||||
|
startup_size = startup_pp.size * startup_pp.avg_price
|
||||||
|
|
||||||
|
if startup_size > alloc.currency_limit:
|
||||||
|
alloc.currency_limit = round(startup_size, ndigits=2)
|
||||||
|
|
||||||
|
limit_text = alloc.currency_limit
|
||||||
|
|
||||||
|
else:
|
||||||
|
startup_size = startup_pp.size
|
||||||
|
|
||||||
|
if startup_size > alloc.units_limit:
|
||||||
|
alloc.units_limit = startup_size
|
||||||
|
|
||||||
|
limit_text = alloc.units_limit
|
||||||
|
|
||||||
|
return limit_text, alloc
|
|
@ -20,15 +20,12 @@ Position info and display
|
||||||
"""
|
"""
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from math import floor
|
from math import floor
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
|
||||||
from bidict import bidict
|
|
||||||
from pyqtgraph import functions as fn
|
from pyqtgraph import functions as fn
|
||||||
from pydantic import BaseModel, validator
|
|
||||||
|
|
||||||
from ._annotate import LevelMarker
|
from ._annotate import LevelMarker
|
||||||
from ._anchors import (
|
from ._anchors import (
|
||||||
|
@ -36,8 +33,7 @@ from ._anchors import (
|
||||||
gpath_pin,
|
gpath_pin,
|
||||||
)
|
)
|
||||||
from ..calc import humanize
|
from ..calc import humanize
|
||||||
from ..clearing._messages import BrokerdPosition, Status
|
from ..clearing._allocate import Allocator, Position
|
||||||
from ..data._source import Symbol
|
|
||||||
from ._label import Label
|
from ._label import Label
|
||||||
from ._lines import LevelLine, order_line
|
from ._lines import LevelLine, order_line
|
||||||
from ._style import _font
|
from ._style import _font
|
||||||
|
@ -48,280 +44,6 @@ from ..clearing._messages import Order
|
||||||
log = get_logger(__name__)
|
log = get_logger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class Position(BaseModel):
|
|
||||||
'''Basic pp (personal position) model with attached fills history.
|
|
||||||
|
|
||||||
This type should be IPC wire ready?
|
|
||||||
|
|
||||||
'''
|
|
||||||
symbol: Symbol
|
|
||||||
|
|
||||||
# last size and avg entry price
|
|
||||||
size: float
|
|
||||||
avg_price: float # TODO: contextual pricing
|
|
||||||
|
|
||||||
# ordered record of known constituent trade messages
|
|
||||||
fills: list[Status] = []
|
|
||||||
|
|
||||||
def update_from_msg(
|
|
||||||
self,
|
|
||||||
msg: BrokerdPosition,
|
|
||||||
|
|
||||||
) -> None:
|
|
||||||
|
|
||||||
# XXX: better place to do this?
|
|
||||||
symbol = self.symbol
|
|
||||||
|
|
||||||
lot_size_digits = symbol.lot_size_digits
|
|
||||||
avg_price, size = (
|
|
||||||
round(msg['avg_price'], ndigits=symbol.tick_size_digits),
|
|
||||||
round(msg['size'], ndigits=lot_size_digits),
|
|
||||||
)
|
|
||||||
|
|
||||||
self.avg_price = avg_price
|
|
||||||
self.size = size
|
|
||||||
|
|
||||||
|
|
||||||
_size_units = bidict({
|
|
||||||
'currency': '$ size',
|
|
||||||
'units': '# units',
|
|
||||||
# TODO: but we'll need a `<brokermod>.get_accounts()` or something
|
|
||||||
# 'percent_of_port': '% of port',
|
|
||||||
})
|
|
||||||
SizeUnit = Enum(
|
|
||||||
'SizeUnit',
|
|
||||||
_size_units,
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class Allocator(BaseModel):
|
|
||||||
|
|
||||||
class Config:
|
|
||||||
validate_assignment = True
|
|
||||||
copy_on_model_validation = False
|
|
||||||
arbitrary_types_allowed = True
|
|
||||||
|
|
||||||
# required to get the account validator lookup working?
|
|
||||||
extra = 'allow'
|
|
||||||
# underscore_attrs_are_private = False
|
|
||||||
|
|
||||||
symbol: Symbol
|
|
||||||
|
|
||||||
account: Optional[str] = 'paper'
|
|
||||||
_accounts: bidict[str, Optional[str]]
|
|
||||||
|
|
||||||
@validator('account', pre=True)
|
|
||||||
def set_account(cls, v, values):
|
|
||||||
if v:
|
|
||||||
return values['_accounts'][v]
|
|
||||||
|
|
||||||
size_unit: SizeUnit = 'currency'
|
|
||||||
_size_units: dict[str, Optional[str]] = _size_units
|
|
||||||
|
|
||||||
@validator('size_unit')
|
|
||||||
def lookup_key(cls, v):
|
|
||||||
# apply the corresponding enum key for the text "description" value
|
|
||||||
return v.name
|
|
||||||
|
|
||||||
# TODO: if we ever want ot support non-uniform entry-slot-proportion
|
|
||||||
# "sizes"
|
|
||||||
# disti_weight: str = 'uniform'
|
|
||||||
|
|
||||||
units_limit: float
|
|
||||||
currency_limit: float
|
|
||||||
slots: int
|
|
||||||
|
|
||||||
def step_sizes(
|
|
||||||
self,
|
|
||||||
) -> (float, float):
|
|
||||||
'''Return the units size for each unit type as a tuple.
|
|
||||||
|
|
||||||
'''
|
|
||||||
slots = self.slots
|
|
||||||
return (
|
|
||||||
self.units_limit / slots,
|
|
||||||
self.currency_limit / slots,
|
|
||||||
)
|
|
||||||
|
|
||||||
def limit(self) -> float:
|
|
||||||
if self.size_unit == 'currency':
|
|
||||||
return self.currency_limit
|
|
||||||
else:
|
|
||||||
return self.units_limit
|
|
||||||
|
|
||||||
def next_order_info(
|
|
||||||
self,
|
|
||||||
|
|
||||||
startup_pp: Position,
|
|
||||||
live_pp: Position,
|
|
||||||
price: float,
|
|
||||||
action: str,
|
|
||||||
|
|
||||||
) -> dict:
|
|
||||||
'''Generate order request info for the "next" submittable order
|
|
||||||
depending on position / order entry config.
|
|
||||||
|
|
||||||
'''
|
|
||||||
sym = self.symbol
|
|
||||||
ld = sym.lot_size_digits
|
|
||||||
|
|
||||||
size_unit = self.size_unit
|
|
||||||
live_size = live_pp.size
|
|
||||||
abs_live_size = abs(live_size)
|
|
||||||
abs_startup_size = abs(startup_pp.size)
|
|
||||||
|
|
||||||
u_per_slot, currency_per_slot = self.step_sizes()
|
|
||||||
|
|
||||||
if size_unit == 'units':
|
|
||||||
slot_size = u_per_slot
|
|
||||||
l_sub_pp = self.units_limit - abs_live_size
|
|
||||||
|
|
||||||
elif size_unit == 'currency':
|
|
||||||
live_cost_basis = abs_live_size * live_pp.avg_price
|
|
||||||
slot_size = currency_per_slot / price
|
|
||||||
l_sub_pp = (self.currency_limit - live_cost_basis) / price
|
|
||||||
|
|
||||||
# an entry (adding-to or starting a pp)
|
|
||||||
if (
|
|
||||||
action == 'buy' and live_size > 0 or
|
|
||||||
action == 'sell' and live_size < 0 or
|
|
||||||
live_size == 0
|
|
||||||
):
|
|
||||||
|
|
||||||
order_size = min(slot_size, l_sub_pp)
|
|
||||||
|
|
||||||
# an exit (removing-from or going to net-zero pp)
|
|
||||||
else:
|
|
||||||
# when exiting a pp we always try to slot the position
|
|
||||||
# in the instrument's units, since doing so in a derived
|
|
||||||
# size measure (eg. currency value, percent of port) would
|
|
||||||
# result in a mis-mapping of slots sizes in unit terms
|
|
||||||
# (i.e. it would take *more* slots to exit at a profit and
|
|
||||||
# *less* slots to exit at a loss).
|
|
||||||
pp_size = max(abs_startup_size, abs_live_size)
|
|
||||||
slotted_pp = pp_size / self.slots
|
|
||||||
|
|
||||||
if size_unit == 'currency':
|
|
||||||
# compute the "projected" limit's worth of units at the
|
|
||||||
# current pp (weighted) price:
|
|
||||||
slot_size = currency_per_slot / live_pp.avg_price
|
|
||||||
|
|
||||||
else:
|
|
||||||
slot_size = u_per_slot
|
|
||||||
|
|
||||||
# if our position is greater then our limit setting
|
|
||||||
# we'll want to use slot sizes which are larger then what
|
|
||||||
# the limit would normally determine
|
|
||||||
order_size = max(slotted_pp, slot_size)
|
|
||||||
|
|
||||||
if (
|
|
||||||
abs_live_size < slot_size or
|
|
||||||
|
|
||||||
# NOTE: front/back "loading" heurstic:
|
|
||||||
# if the remaining pp is in between 0-1.5x a slot's
|
|
||||||
# worth, dump the whole position in this last exit
|
|
||||||
# therefore conducting so called "back loading" but
|
|
||||||
# **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)
|
|
||||||
):
|
|
||||||
order_size = abs_live_size
|
|
||||||
|
|
||||||
slots_used = 1.0 # the default uniform policy
|
|
||||||
if order_size < slot_size:
|
|
||||||
# compute a fractional slots size to display
|
|
||||||
slots_used = self.slots_used(
|
|
||||||
Position(symbol=sym, size=order_size, avg_price=price)
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
|
||||||
'size': abs(round(order_size, ndigits=ld)),
|
|
||||||
'size_digits': ld,
|
|
||||||
|
|
||||||
# TODO: incorporate multipliers for relevant derivatives
|
|
||||||
'fiat_size': round(order_size * price, ndigits=2),
|
|
||||||
'slots_used': slots_used,
|
|
||||||
}
|
|
||||||
|
|
||||||
def slots_used(
|
|
||||||
self,
|
|
||||||
pp: Position,
|
|
||||||
|
|
||||||
) -> float:
|
|
||||||
'''Calc and return the number of slots used by this ``Position``.
|
|
||||||
|
|
||||||
'''
|
|
||||||
abs_pp_size = abs(pp.size)
|
|
||||||
|
|
||||||
if self.size_unit == 'currency':
|
|
||||||
# live_currency_size = size or (abs_pp_size * pp.avg_price)
|
|
||||||
live_currency_size = abs_pp_size * pp.avg_price
|
|
||||||
prop = live_currency_size / self.currency_limit
|
|
||||||
|
|
||||||
else:
|
|
||||||
# return (size or abs_pp_size) / alloc.units_limit
|
|
||||||
prop = abs_pp_size / self.units_limit
|
|
||||||
|
|
||||||
# TODO: REALLY need a way to show partial slots..
|
|
||||||
# for now we round at the midway point between slots
|
|
||||||
return round(prop * self.slots)
|
|
||||||
|
|
||||||
|
|
||||||
def mk_allocator(
|
|
||||||
|
|
||||||
alloc: Allocator,
|
|
||||||
startup_pp: Position,
|
|
||||||
config_section: dict = {},
|
|
||||||
|
|
||||||
) -> (float, Allocator):
|
|
||||||
|
|
||||||
asset_type = alloc.symbol.type_key
|
|
||||||
|
|
||||||
# load and retreive user settings for default allocations
|
|
||||||
# ``config.toml``
|
|
||||||
slots = 4
|
|
||||||
currency_limit = 5e3
|
|
||||||
|
|
||||||
alloc.slots = slots
|
|
||||||
alloc.currency_limit = currency_limit
|
|
||||||
|
|
||||||
# default entry sizing
|
|
||||||
if asset_type in ('stock', 'crypto', 'forex'):
|
|
||||||
|
|
||||||
alloc.size_unit = '$ size'
|
|
||||||
|
|
||||||
elif asset_type in ('future', 'option', 'futures_option'):
|
|
||||||
|
|
||||||
# since it's harder to know how currency "applies" in this case
|
|
||||||
# given leverage properties
|
|
||||||
alloc.size_unit = '# units'
|
|
||||||
|
|
||||||
# set units limit to slots size thus making make the next
|
|
||||||
# entry step 1.0
|
|
||||||
alloc.units_limit = slots
|
|
||||||
|
|
||||||
# if the current position is already greater then the limit
|
|
||||||
# settings, increase the limit to the current position
|
|
||||||
if alloc.size_unit == 'currency':
|
|
||||||
startup_size = startup_pp.size * startup_pp.avg_price
|
|
||||||
|
|
||||||
if startup_size > alloc.currency_limit:
|
|
||||||
alloc.currency_limit = round(startup_size, ndigits=2)
|
|
||||||
|
|
||||||
limit_text = alloc.currency_limit
|
|
||||||
|
|
||||||
else:
|
|
||||||
startup_size = startup_pp.size
|
|
||||||
|
|
||||||
if startup_size > alloc.units_limit:
|
|
||||||
alloc.units_limit = startup_size
|
|
||||||
|
|
||||||
limit_text = alloc.units_limit
|
|
||||||
|
|
||||||
return limit_text, alloc
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class SettingsPane:
|
class SettingsPane:
|
||||||
'''Composite set of widgets plus an allocator model for configuring
|
'''Composite set of widgets plus an allocator model for configuring
|
||||||
|
|
|
@ -35,6 +35,12 @@ import trio
|
||||||
from .. import config
|
from .. import config
|
||||||
from ..calc import pnl
|
from ..calc import pnl
|
||||||
from ..clearing._client import open_ems, OrderBook
|
from ..clearing._client import open_ems, OrderBook
|
||||||
|
from ..clearing._allocate import (
|
||||||
|
Allocator,
|
||||||
|
mk_allocator,
|
||||||
|
Position,
|
||||||
|
_size_units,
|
||||||
|
)
|
||||||
from ..data._source import Symbol
|
from ..data._source import Symbol
|
||||||
from ..data._normalize import iterticks
|
from ..data._normalize import iterticks
|
||||||
from ..data.feed import Feed
|
from ..data.feed import Feed
|
||||||
|
@ -42,12 +48,8 @@ from ..log import get_logger
|
||||||
from ._editors import LineEditor, ArrowEditor
|
from ._editors import LineEditor, ArrowEditor
|
||||||
from ._lines import order_line, LevelLine
|
from ._lines import order_line, LevelLine
|
||||||
from ._position import (
|
from ._position import (
|
||||||
Position,
|
|
||||||
Allocator,
|
|
||||||
mk_allocator,
|
|
||||||
PositionTracker,
|
PositionTracker,
|
||||||
SettingsPane,
|
SettingsPane,
|
||||||
_size_units,
|
|
||||||
)
|
)
|
||||||
from ._window import MultiStatus
|
from ._window import MultiStatus
|
||||||
from ..clearing._messages import Order
|
from ..clearing._messages import Order
|
||||||
|
@ -545,6 +547,7 @@ async def open_order_mode(
|
||||||
|
|
||||||
# allocator
|
# allocator
|
||||||
limit_value, alloc = mk_allocator(
|
limit_value, alloc = mk_allocator(
|
||||||
|
|
||||||
alloc=Allocator(
|
alloc=Allocator(
|
||||||
symbol=symbol,
|
symbol=symbol,
|
||||||
account=None, # select paper by default
|
account=None, # select paper by default
|
||||||
|
@ -571,6 +574,7 @@ async def open_order_mode(
|
||||||
|
|
||||||
# order pane widgets and allocation model
|
# order pane widgets and allocation model
|
||||||
order_pane = SettingsPane(
|
order_pane = SettingsPane(
|
||||||
|
|
||||||
tracker=pp_tracker,
|
tracker=pp_tracker,
|
||||||
form=form,
|
form=form,
|
||||||
alloc=alloc,
|
alloc=alloc,
|
||||||
|
@ -581,14 +585,10 @@ async def open_order_mode(
|
||||||
step_label=form.bottom_label,
|
step_label=form.bottom_label,
|
||||||
limit_label=form.top_label,
|
limit_label=form.top_label,
|
||||||
)
|
)
|
||||||
# make fill bar and positioning snapshot
|
|
||||||
# XXX: this need to be called *before* the first
|
# set startup limit value read during alloc init
|
||||||
# pp tracker update(s) below to ensure the limit size unit has
|
|
||||||
# been correctly set prior to updating the line's pp size label
|
|
||||||
# (the one on the RHS).
|
|
||||||
# TODO: should probably split out the alloc config from the UI
|
|
||||||
# config startup steps..
|
|
||||||
order_pane.on_ui_settings_change('limit', limit_value)
|
order_pane.on_ui_settings_change('limit', limit_value)
|
||||||
|
# make fill bar and positioning snapshot
|
||||||
order_pane.update_status_ui(size=startup_pp.size)
|
order_pane.update_status_ui(size=startup_pp.size)
|
||||||
|
|
||||||
# top level abstraction which wraps all this crazyness into
|
# top level abstraction which wraps all this crazyness into
|
||||||
|
|
Loading…
Reference in New Issue