commit
bc59d476b1
|
@ -517,9 +517,9 @@ class Client:
|
||||||
contract, ticker, details = await self.get_sym_details(symbol)
|
contract, ticker, details = await self.get_sym_details(symbol)
|
||||||
|
|
||||||
# ensure a last price gets filled in before we deliver quote
|
# ensure a last price gets filled in before we deliver quote
|
||||||
for _ in range(1):
|
for _ in range(100):
|
||||||
if isnan(ticker.last):
|
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?')
|
log.warning(f'Quote for {symbol} timed out: market is closed?')
|
||||||
ticker = await ticker.updateEvent
|
ticker = await ticker.updateEvent
|
||||||
else:
|
else:
|
||||||
|
@ -792,7 +792,7 @@ async def load_aio_clients(
|
||||||
try_ports = list(ports.values())
|
try_ports = list(ports.values())
|
||||||
ports = try_ports if port is None else [port]
|
ports = try_ports if port is None else [port]
|
||||||
# we_connected = []
|
# we_connected = []
|
||||||
connect_timeout = 1 if platform.system() != 'Windows' else 2
|
connect_timeout = 2
|
||||||
combos = list(itertools.product(hosts, ports))
|
combos = list(itertools.product(hosts, ports))
|
||||||
|
|
||||||
# allocate new and/or reload disconnected but cached clients
|
# allocate new and/or reload disconnected but cached clients
|
||||||
|
@ -1428,8 +1428,14 @@ async def stream_quotes(
|
||||||
'''
|
'''
|
||||||
# TODO: support multiple subscriptions
|
# TODO: support multiple subscriptions
|
||||||
sym = symbols[0]
|
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(
|
contract, first_ticker, details = await _trio_run_client_method(
|
||||||
method='get_quote',
|
method='get_quote',
|
||||||
symbol=sym,
|
symbol=sym,
|
||||||
|
|
|
@ -29,7 +29,8 @@ from ._messages import BrokerdPosition, Status
|
||||||
|
|
||||||
|
|
||||||
class Position(BaseModel):
|
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?
|
This type should be IPC wire ready?
|
||||||
|
|
||||||
|
@ -61,6 +62,15 @@ class Position(BaseModel):
|
||||||
self.avg_price = avg_price
|
self.avg_price = avg_price
|
||||||
self.size = size
|
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({
|
_size_units = bidict({
|
||||||
'currency': '$ size',
|
'currency': '$ size',
|
||||||
|
@ -114,7 +124,8 @@ class Allocator(BaseModel):
|
||||||
def step_sizes(
|
def step_sizes(
|
||||||
self,
|
self,
|
||||||
) -> (float, float):
|
) -> (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
|
slots = self.slots
|
||||||
|
@ -142,7 +153,8 @@ class Allocator(BaseModel):
|
||||||
action: str,
|
action: str,
|
||||||
|
|
||||||
) -> dict:
|
) -> 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.
|
depending on position / order entry config.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
@ -250,7 +262,8 @@ class Allocator(BaseModel):
|
||||||
pp: Position,
|
pp: Position,
|
||||||
|
|
||||||
) -> float:
|
) -> 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)
|
abs_pp_size = abs(pp.size)
|
||||||
|
|
|
@ -21,6 +21,7 @@ Text entry "forms" widgets (mostly for configuration and UI user input).
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
from contextlib import asynccontextmanager
|
from contextlib import asynccontextmanager
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from math import floor
|
||||||
from typing import (
|
from typing import (
|
||||||
Optional, Any, Callable, Awaitable
|
Optional, Any, Callable, Awaitable
|
||||||
)
|
)
|
||||||
|
@ -542,7 +543,7 @@ class FillStatusBar(QProgressBar):
|
||||||
|
|
||||||
'''
|
'''
|
||||||
border_px: int = 2
|
border_px: int = 2
|
||||||
slot_margin_px: int = 2
|
slot_margin_px: int = 1
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -553,7 +554,11 @@ class FillStatusBar(QProgressBar):
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(parent=parent)
|
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.font_size = font_size
|
||||||
|
|
||||||
self.setFormat('') # label format
|
self.setFormat('') # label format
|
||||||
|
@ -567,17 +572,12 @@ class FillStatusBar(QProgressBar):
|
||||||
|
|
||||||
) -> None:
|
) -> 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)
|
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(
|
self.setStyleSheet(
|
||||||
f"""
|
f"""
|
||||||
QProgressBar {{
|
QProgressBar {{
|
||||||
|
@ -592,21 +592,28 @@ class FillStatusBar(QProgressBar):
|
||||||
border: {self.border_px}px solid {hcolor('default_light')};
|
border: {self.border_px}px solid {hcolor('default_light')};
|
||||||
border-radius: 2px;
|
border-radius: 2px;
|
||||||
}}
|
}}
|
||||||
|
|
||||||
QProgressBar::chunk {{
|
QProgressBar::chunk {{
|
||||||
|
|
||||||
background-color: {hcolor('default_spotlight')};
|
background-color: {hcolor('default_spotlight')};
|
||||||
color: {hcolor('bracket')};
|
color: {hcolor('bracket')};
|
||||||
|
|
||||||
border-radius: 2px;
|
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-bottom: {slot_margin_px*2}px;
|
||||||
# margin-top: {slot_margin_px*2}px;
|
# margin-top: {slot_margin_px*2}px;
|
||||||
# color: #19232D;
|
# color: #19232D;
|
||||||
|
|
|
@ -71,10 +71,10 @@ async def update_pnl_from_feed(
|
||||||
log.info(f'Starting pnl display for {pp.alloc.account}')
|
log.info(f'Starting pnl display for {pp.alloc.account}')
|
||||||
|
|
||||||
if live.size < 0:
|
if live.size < 0:
|
||||||
types = ('ask', 'last', 'last', 'utrade')
|
types = ('ask', 'last', 'last', 'dark_trade')
|
||||||
|
|
||||||
elif live.size > 0:
|
elif live.size > 0:
|
||||||
types = ('bid', 'last', 'last', 'utrade')
|
types = ('bid', 'last', 'last', 'dark_trade')
|
||||||
|
|
||||||
else:
|
else:
|
||||||
log.info(f'No position (yet) for {tracker.alloc.account}@{key}')
|
log.info(f'No position (yet) for {tracker.alloc.account}@{key}')
|
||||||
|
@ -119,7 +119,8 @@ async def update_pnl_from_feed(
|
||||||
|
|
||||||
@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
|
||||||
order entry sizes and position limits per tradable instrument.
|
order entry sizes and position limits per tradable instrument.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
@ -151,7 +152,8 @@ class SettingsPane:
|
||||||
key: str,
|
key: str,
|
||||||
|
|
||||||
) -> None:
|
) -> 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}')
|
log.info(f'selection input {key}:{text}')
|
||||||
|
@ -164,7 +166,8 @@ class SettingsPane:
|
||||||
value: str,
|
value: str,
|
||||||
|
|
||||||
) -> bool:
|
) -> bool:
|
||||||
'''Called on any order pane edit field value change.
|
'''
|
||||||
|
Called on any order pane edit field value change.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
mode = self.order_mode
|
mode = self.order_mode
|
||||||
|
@ -219,16 +222,36 @@ class SettingsPane:
|
||||||
else:
|
else:
|
||||||
value = puterize(value)
|
value = puterize(value)
|
||||||
if key == 'limit':
|
if key == 'limit':
|
||||||
|
pp = mode.current_pp.live_pp
|
||||||
|
|
||||||
if size_unit == 'currency':
|
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
|
alloc.currency_limit = value
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
size = pp.size
|
||||||
|
if size > value:
|
||||||
|
log.error(
|
||||||
|
f'limit must > then current pp: {size}'
|
||||||
|
)
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
alloc.units_limit = value
|
alloc.units_limit = value
|
||||||
|
|
||||||
elif key == 'slots':
|
elif key == 'slots':
|
||||||
|
if value <= 0:
|
||||||
|
raise ValueError('slots must be > 0')
|
||||||
alloc.slots = int(value)
|
alloc.slots = int(value)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise ValueError(f'Unknown setting {key}')
|
log.error(f'Unknown setting {key}')
|
||||||
|
raise ValueError
|
||||||
|
|
||||||
log.info(f'settings change: {key}: {value}')
|
log.info(f'settings change: {key}: {value}')
|
||||||
|
|
||||||
|
@ -363,7 +386,8 @@ def position_line(
|
||||||
marker: Optional[LevelMarker] = None,
|
marker: Optional[LevelMarker] = None,
|
||||||
|
|
||||||
) -> LevelLine:
|
) -> 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,
|
aka the acro for a,
|
||||||
"{piker, private, personal, puny, <place your p-word here>} position".
|
"{piker, private, personal, puny, <place your p-word here>} position".
|
||||||
|
|
||||||
|
@ -417,7 +441,8 @@ def position_line(
|
||||||
|
|
||||||
|
|
||||||
class PositionTracker:
|
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.
|
over multiple accounts on a single chart.
|
||||||
|
|
||||||
Graphically composed of a level line and marker as well as labels
|
Graphically composed of a level line and marker as well as labels
|
||||||
|
@ -497,7 +522,8 @@ class PositionTracker:
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def pane(self) -> FieldsForm:
|
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
|
return self.chart.linked.godwidget.pp_pane
|
||||||
|
@ -507,7 +533,8 @@ class PositionTracker:
|
||||||
marker: LevelMarker
|
marker: LevelMarker
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
'''Update all labels.
|
'''
|
||||||
|
Update all labels.
|
||||||
|
|
||||||
Meant to be called from the maker ``.paint()``
|
Meant to be called from the maker ``.paint()``
|
||||||
for immediate, lag free label draws.
|
for immediate, lag free label draws.
|
||||||
|
|
|
@ -107,7 +107,8 @@ def on_level_change_update_next_order_info(
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
class OrderMode:
|
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".
|
called, "chart trading".
|
||||||
|
|
||||||
This is the other "main" mode that pairs with "view mode" (when
|
This is the other "main" mode that pairs with "view mode" (when
|
||||||
|
|
Loading…
Reference in New Issue