Compare commits
10 Commits
30dfb8f03d
...
75faf83004
Author | SHA1 | Date |
---|---|---|
wattygetlood | 75faf83004 | |
wattygetlood | a270b2e033 | |
wattygetlood | 09fd8ef742 | |
wattygetlood | 422f203fc3 | |
wattygetlood | 8fbd0cd067 | |
wattygetlood | 68ed1164a1 | |
wattygetlood | 2f73f809f1 | |
wattygetlood | ba7b01b704 | |
Tyler Goodlet | 83299c3a8b | |
Tyler Goodlet | 0d17f4ff4c |
|
@ -0,0 +1,28 @@
|
||||||
|
Notes to self
|
||||||
|
=============
|
||||||
|
chicken scratch we shan't forget, consider this staging
|
||||||
|
for actual feature issues on wtv git wrapper-provider we're
|
||||||
|
using (no we shan't stick with GH long term likely).
|
||||||
|
|
||||||
|
|
||||||
|
cool chart features
|
||||||
|
-------------------
|
||||||
|
- allow right-click to spawn shell with current in view
|
||||||
|
data passed to the new process via ``msgpack-numpy``.
|
||||||
|
- expand OHLC datum to lower time frame.
|
||||||
|
- auto-highlight current time range on tick feed
|
||||||
|
|
||||||
|
|
||||||
|
features from IB charting
|
||||||
|
-------------------------
|
||||||
|
- vlm diffing from ticks and compare when bar arrives from historical
|
||||||
|
- should help isolate dark vlm / trades
|
||||||
|
|
||||||
|
|
||||||
|
chart ux ideas
|
||||||
|
--------------
|
||||||
|
- hotkey to zoom to order intersection (horizontal line) with previous
|
||||||
|
price levels (+ some margin obvs).
|
||||||
|
- L1 "lines" (queue size repr) should normalize to some fixed x width
|
||||||
|
such that when levels with more vlm appear other smaller levels are
|
||||||
|
scaled down giving an immediate indication of the liquidity diff.
|
|
@ -1157,6 +1157,11 @@ async def backfill_bars(
|
||||||
https://github.com/pikers/piker/issues/128
|
https://github.com/pikers/piker/issues/128
|
||||||
|
|
||||||
"""
|
"""
|
||||||
|
if platform.system() == 'Windows':
|
||||||
|
log.warning(
|
||||||
|
'Decreasing history query count to 4 since, windows...')
|
||||||
|
count = 4
|
||||||
|
|
||||||
out, fails = await get_bars(sym)
|
out, fails = await get_bars(sym)
|
||||||
if out is None:
|
if out is None:
|
||||||
raise RuntimeError("Could not pull currrent history?!")
|
raise RuntimeError("Could not pull currrent history?!")
|
||||||
|
|
|
@ -1046,7 +1046,7 @@ async def _emsd_main(
|
||||||
|
|
||||||
# signal to client that we're started and deliver
|
# signal to client that we're started and deliver
|
||||||
# all known pps and accounts for this ``brokerd``.
|
# all known pps and accounts for this ``brokerd``.
|
||||||
await ems_ctx.started((pp_msgs, relay.accounts))
|
await ems_ctx.started((pp_msgs, list(relay.accounts)))
|
||||||
|
|
||||||
# establish 2-way stream with requesting order-client and
|
# establish 2-way stream with requesting order-client and
|
||||||
# begin handling inbound order requests and updates
|
# begin handling inbound order requests and updates
|
||||||
|
|
|
@ -60,7 +60,7 @@ def repodir():
|
||||||
"""
|
"""
|
||||||
dirpath = os.path.abspath(
|
dirpath = os.path.abspath(
|
||||||
# we're 3 levels down in **this** module file
|
# we're 3 levels down in **this** module file
|
||||||
dirname(dirname(dirname(os.path.realpath(__file__))))
|
dirname(dirname(os.path.realpath(__file__)))
|
||||||
)
|
)
|
||||||
return dirpath
|
return dirpath
|
||||||
|
|
||||||
|
@ -73,7 +73,7 @@ def load(
|
||||||
path = path or get_broker_conf_path()
|
path = path or get_broker_conf_path()
|
||||||
if not os.path.isfile(path):
|
if not os.path.isfile(path):
|
||||||
shutil.copyfile(
|
shutil.copyfile(
|
||||||
os.path.join(repodir(), 'data/brokers.toml'),
|
os.path.join(repodir(), 'config', 'brokers.toml'),
|
||||||
path,
|
path,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -172,6 +172,7 @@ async def sample_and_broadcast(
|
||||||
|
|
||||||
# iterate stream delivered by broker
|
# iterate stream delivered by broker
|
||||||
async for quotes in quote_stream:
|
async for quotes in quote_stream:
|
||||||
|
|
||||||
# TODO: ``numba`` this!
|
# TODO: ``numba`` this!
|
||||||
for sym, quote in quotes.items():
|
for sym, quote in quotes.items():
|
||||||
|
|
||||||
|
@ -184,12 +185,8 @@ async def sample_and_broadcast(
|
||||||
|
|
||||||
# start writing the shm buffer with appropriate
|
# start writing the shm buffer with appropriate
|
||||||
# trade data
|
# trade data
|
||||||
|
for tick in quote['ticks']:
|
||||||
|
|
||||||
# TODO: we should probably not write every single
|
|
||||||
# value to an OHLC sample stream XD
|
|
||||||
# for a tick stream sure.. but this is excessive..
|
|
||||||
ticks = quote['ticks']
|
|
||||||
for tick in ticks:
|
|
||||||
ticktype = tick['type']
|
ticktype = tick['type']
|
||||||
|
|
||||||
# write trade events to shm last OHLC sample
|
# write trade events to shm last OHLC sample
|
||||||
|
@ -261,8 +258,7 @@ async def sample_and_broadcast(
|
||||||
|
|
||||||
except (
|
except (
|
||||||
trio.BrokenResourceError,
|
trio.BrokenResourceError,
|
||||||
trio.ClosedResourceError,
|
trio.ClosedResourceError
|
||||||
trio.EndOfChannel,
|
|
||||||
):
|
):
|
||||||
# XXX: do we need to deregister here
|
# XXX: do we need to deregister here
|
||||||
# if it's done in the fee bus code?
|
# if it's done in the fee bus code?
|
||||||
|
@ -272,10 +268,6 @@ async def sample_and_broadcast(
|
||||||
f'{stream._ctx.chan.uid} dropped '
|
f'{stream._ctx.chan.uid} dropped '
|
||||||
'`brokerd`-quotes-feed connection'
|
'`brokerd`-quotes-feed connection'
|
||||||
)
|
)
|
||||||
if tick_throttle:
|
|
||||||
assert stream.closed()
|
|
||||||
# await stream.aclose()
|
|
||||||
|
|
||||||
subs.remove((stream, tick_throttle))
|
subs.remove((stream, tick_throttle))
|
||||||
|
|
||||||
|
|
||||||
|
@ -291,8 +283,12 @@ async def uniform_rate_send(
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
sleep_period = 1/rate - 0.0001 # 100us
|
sleep_period = 1/rate - 0.000616
|
||||||
last_send = time.time()
|
last_send = time.time()
|
||||||
|
aname = stream._ctx.chan.uid[0]
|
||||||
|
fsp = False
|
||||||
|
if 'fsp' in aname:
|
||||||
|
fsp = True
|
||||||
|
|
||||||
while True:
|
while True:
|
||||||
|
|
||||||
|
@ -312,33 +308,21 @@ async def uniform_rate_send(
|
||||||
sym, next_quote = quote_stream.receive_nowait()
|
sym, next_quote = quote_stream.receive_nowait()
|
||||||
ticks = next_quote.get('ticks')
|
ticks = next_quote.get('ticks')
|
||||||
|
|
||||||
# XXX: idea for frame type data structure we could use on the
|
|
||||||
# wire instead of a simple list?
|
|
||||||
# frames = {
|
|
||||||
# 'index': ['type_a', 'type_c', 'type_n', 'type_n'],
|
|
||||||
|
|
||||||
# 'type_a': [tick0, tick1, tick2, .., tickn],
|
|
||||||
# 'type_b': [tick0, tick1, tick2, .., tickn],
|
|
||||||
# 'type_c': [tick0, tick1, tick2, .., tickn],
|
|
||||||
# ...
|
|
||||||
# 'type_n': [tick0, tick1, tick2, .., tickn],
|
|
||||||
# }
|
|
||||||
if ticks:
|
if ticks:
|
||||||
first_quote['ticks'].extend(ticks)
|
first_quote['ticks'].extend(ticks)
|
||||||
|
|
||||||
except trio.WouldBlock:
|
except trio.WouldBlock:
|
||||||
now = time.time()
|
now = time.time()
|
||||||
rate = 1 / (now - last_send)
|
diff = now - last_send
|
||||||
|
rate = 1 / diff if diff else float('inf')
|
||||||
|
last_send = now
|
||||||
|
|
||||||
log.debug(
|
# log.info(f'{rate} Hz sending quotes') # \n{first_quote}')
|
||||||
f'`{sym}` throttled send hz: {round(rate, ndigits=1)}'
|
|
||||||
)
|
|
||||||
|
|
||||||
# TODO: now if only we could sync this to the display
|
# TODO: now if only we could sync this to the display
|
||||||
# rate timing exactly lul
|
# rate timing exactly lul
|
||||||
try:
|
try:
|
||||||
await stream.send({sym: first_quote})
|
await stream.send({sym: first_quote})
|
||||||
last_send = now
|
|
||||||
break
|
break
|
||||||
except trio.ClosedResourceError:
|
except trio.ClosedResourceError:
|
||||||
# if the feed consumer goes down then drop
|
# if the feed consumer goes down then drop
|
||||||
|
|
|
@ -800,14 +800,11 @@ async def update_chart_from_fsp(
|
||||||
profiler.finish()
|
profiler.finish()
|
||||||
|
|
||||||
# update chart graphics
|
# update chart graphics
|
||||||
i = 0
|
|
||||||
last = time.time()
|
last = time.time()
|
||||||
async for value in stream:
|
async for value in stream:
|
||||||
|
|
||||||
# chart isn't actively shown so just skip render cycle
|
# chart isn't actively shown so just skip render cycle
|
||||||
if chart.linked.isHidden():
|
if chart.linked.isHidden():
|
||||||
print(f'{i} unseen fsp cyclce')
|
|
||||||
i += 1
|
|
||||||
continue
|
continue
|
||||||
|
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -61,7 +61,9 @@ _do_overrides()
|
||||||
# XXX: pretty sure none of this shit works on linux as per:
|
# XXX: pretty sure none of this shit works on linux as per:
|
||||||
# https://bugreports.qt.io/browse/QTBUG-53022
|
# https://bugreports.qt.io/browse/QTBUG-53022
|
||||||
# it seems to work on windows.. no idea wtf is up.
|
# it seems to work on windows.. no idea wtf is up.
|
||||||
|
is_windows = False
|
||||||
if platform.system() == "Windows":
|
if platform.system() == "Windows":
|
||||||
|
is_windows = True
|
||||||
|
|
||||||
# Proper high DPI scaling is available in Qt >= 5.6.0. This attibute
|
# Proper high DPI scaling is available in Qt >= 5.6.0. This attibute
|
||||||
# must be set before creating the application
|
# must be set before creating the application
|
||||||
|
@ -182,6 +184,8 @@ def run_qtractor(
|
||||||
|
|
||||||
window.main_widget = main_widget
|
window.main_widget = main_widget
|
||||||
window.setCentralWidget(instance)
|
window.setCentralWidget(instance)
|
||||||
|
if is_windows:
|
||||||
|
window.configure_to_desktop()
|
||||||
|
|
||||||
# actually render to screen
|
# actually render to screen
|
||||||
window.show()
|
window.show()
|
||||||
|
|
|
@ -49,7 +49,7 @@ from PyQt5 import QtCore
|
||||||
from PyQt5 import QtWidgets
|
from PyQt5 import QtWidgets
|
||||||
from PyQt5.QtCore import (
|
from PyQt5.QtCore import (
|
||||||
Qt,
|
Qt,
|
||||||
# QSize,
|
QSize,
|
||||||
QModelIndex,
|
QModelIndex,
|
||||||
QItemSelectionModel,
|
QItemSelectionModel,
|
||||||
)
|
)
|
||||||
|
@ -112,6 +112,7 @@ class CompleterView(QTreeView):
|
||||||
|
|
||||||
model = QStandardItemModel(self)
|
model = QStandardItemModel(self)
|
||||||
self.labels = labels
|
self.labels = labels
|
||||||
|
self._last_window_h: Optional[int] = None
|
||||||
|
|
||||||
# a std "tabular" config
|
# a std "tabular" config
|
||||||
self.setItemDelegate(FontScaledDelegate(self))
|
self.setItemDelegate(FontScaledDelegate(self))
|
||||||
|
@ -126,6 +127,10 @@ class CompleterView(QTreeView):
|
||||||
# self.setSizeAdjustPolicy(QAbstractScrollArea.AdjustIgnored)
|
# self.setSizeAdjustPolicy(QAbstractScrollArea.AdjustIgnored)
|
||||||
|
|
||||||
# ux settings
|
# ux settings
|
||||||
|
self.setSizePolicy(
|
||||||
|
QtWidgets.QSizePolicy.Expanding,
|
||||||
|
QtWidgets.QSizePolicy.Expanding,
|
||||||
|
)
|
||||||
self.setItemsExpandable(True)
|
self.setItemsExpandable(True)
|
||||||
self.setExpandsOnDoubleClick(False)
|
self.setExpandsOnDoubleClick(False)
|
||||||
self.setAnimated(False)
|
self.setAnimated(False)
|
||||||
|
@ -152,24 +157,41 @@ class CompleterView(QTreeView):
|
||||||
self._font_size = size
|
self._font_size = size
|
||||||
|
|
||||||
self.setStyleSheet(f"font: {size}px")
|
self.setStyleSheet(f"font: {size}px")
|
||||||
|
|
||||||
|
#def resizeEvent(self, event: 'QEvent') -> None:
|
||||||
|
# self.resize_to_results()
|
||||||
|
# super().resizeEvent(event)
|
||||||
|
|
||||||
def resize(self):
|
def resize_to_results(self):
|
||||||
model = self.model()
|
model = self.model()
|
||||||
cols = model.columnCount()
|
cols = model.columnCount()
|
||||||
|
|
||||||
for i in range(cols):
|
for i in range(cols):
|
||||||
self.resizeColumnToContents(i)
|
self.resizeColumnToContents(i)
|
||||||
|
|
||||||
# inclusive of search bar and header "rows" in pixel terms
|
|
||||||
rows = 100
|
|
||||||
# max_rows = 8 # 6 + search and headers
|
|
||||||
row_px = self.rowHeight(self.currentIndex())
|
row_px = self.rowHeight(self.currentIndex())
|
||||||
# print(f'font_h: {font_h}\n px_height: {px_height}')
|
|
||||||
|
|
||||||
# TODO: probably make this more general / less hacky
|
# TODO: probably make this more general / less hacky
|
||||||
self.setMinimumSize(self.width(), rows * row_px)
|
# we should figure out the exact number of rows to allow
|
||||||
self.setMaximumSize(self.width() + 10, rows * row_px)
|
# inclusive of search bar and header "rows", in pixel terms.
|
||||||
|
window_h = self.window().height()
|
||||||
|
rows = round(window_h * 0.5 / row_px) - 4
|
||||||
|
|
||||||
|
# TODO: the problem here is that this view widget is **not** resizing/scaling
|
||||||
|
# when the parent layout is adjusted, not sure what exactly is up...
|
||||||
|
# only "scale up" the results view when the window size has increased/
|
||||||
|
if not self._last_window_h or self._last_window_h < window_h:
|
||||||
|
self.setMaximumSize(self.width(), rows * row_px)
|
||||||
|
self.setMinimumSize(self.width(), rows * row_px)
|
||||||
|
|
||||||
|
#elif not self._last_window_h or self._last_window_h > window_h:
|
||||||
|
# self.setMinimumSize(self.width(), rows * row_px)
|
||||||
|
# self.setMaximumSize(self.width(), rows * row_px)
|
||||||
|
|
||||||
|
self.resize(self.width(), rows * row_px)
|
||||||
|
self._last_window_h = window_h
|
||||||
self.setFixedWidth(333)
|
self.setFixedWidth(333)
|
||||||
|
self.update()
|
||||||
|
|
||||||
def is_selecting_d1(self) -> bool:
|
def is_selecting_d1(self) -> bool:
|
||||||
cidx = self.selectionModel().currentIndex()
|
cidx = self.selectionModel().currentIndex()
|
||||||
|
@ -334,7 +356,7 @@ class CompleterView(QTreeView):
|
||||||
else:
|
else:
|
||||||
model.setItem(idx.row(), 1, QStandardItem())
|
model.setItem(idx.row(), 1, QStandardItem())
|
||||||
|
|
||||||
self.resize()
|
self.resize_to_results()
|
||||||
|
|
||||||
return idx
|
return idx
|
||||||
else:
|
else:
|
||||||
|
@ -404,7 +426,7 @@ class CompleterView(QTreeView):
|
||||||
|
|
||||||
def show_matches(self) -> None:
|
def show_matches(self) -> None:
|
||||||
self.show()
|
self.show()
|
||||||
self.resize()
|
self.resize_to_results()
|
||||||
|
|
||||||
|
|
||||||
class SearchBar(Edit):
|
class SearchBar(Edit):
|
||||||
|
@ -457,7 +479,7 @@ class SearchWidget(QtWidgets.QWidget):
|
||||||
# size it as we specify
|
# size it as we specify
|
||||||
self.setSizePolicy(
|
self.setSizePolicy(
|
||||||
QtWidgets.QSizePolicy.Fixed,
|
QtWidgets.QSizePolicy.Fixed,
|
||||||
QtWidgets.QSizePolicy.Fixed,
|
QtWidgets.QSizePolicy.Expanding,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.godwidget = godwidget
|
self.godwidget = godwidget
|
||||||
|
|
|
@ -151,8 +151,8 @@ class MainWindow(QtGui.QMainWindow):
|
||||||
# XXX: for tiling wms this should scale
|
# XXX: for tiling wms this should scale
|
||||||
# with the alloted window size.
|
# with the alloted window size.
|
||||||
# TODO: detect for tiling and if untrue set some size?
|
# TODO: detect for tiling and if untrue set some size?
|
||||||
# size = (300, 500)
|
size = (300, 500)
|
||||||
size = (0, 0)
|
#size = (0, 0)
|
||||||
|
|
||||||
title = 'piker chart (ur symbol is loading bby)'
|
title = 'piker chart (ur symbol is loading bby)'
|
||||||
|
|
||||||
|
@ -163,7 +163,8 @@ class MainWindow(QtGui.QMainWindow):
|
||||||
|
|
||||||
self._status_bar: QStatusBar = None
|
self._status_bar: QStatusBar = None
|
||||||
self._status_label: QLabel = None
|
self._status_label: QLabel = None
|
||||||
|
self._size: Optional[tuple[int, int]] = None
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mode_label(self) -> QtGui.QLabel:
|
def mode_label(self) -> QtGui.QLabel:
|
||||||
|
|
||||||
|
@ -267,6 +268,22 @@ class MainWindow(QtGui.QMainWindow):
|
||||||
assert screen, "Wow Qt is dumb as shit and has no screen..."
|
assert screen, "Wow Qt is dumb as shit and has no screen..."
|
||||||
return screen
|
return screen
|
||||||
|
|
||||||
|
def configure_to_desktop(
|
||||||
|
self,
|
||||||
|
size: Optional[tuple[int, int]] = None,
|
||||||
|
) -> None:
|
||||||
|
# https://stackoverflow.com/a/18975846
|
||||||
|
if not size and not self._size:
|
||||||
|
app = QtGui.QApplication.instance()
|
||||||
|
geo = self.current_screen().geometry()
|
||||||
|
h, w = geo.height(), geo.width()
|
||||||
|
self.setMaximumSize(w, h)
|
||||||
|
# use approx 1/3 of the area of the screen by default
|
||||||
|
self._size = round(w * .666), round(h * .666)
|
||||||
|
|
||||||
|
self.resize(*size or self._size)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
# singleton app per actor
|
# singleton app per actor
|
||||||
_qt_win: QtGui.QMainWindow = None
|
_qt_win: QtGui.QMainWindow = None
|
||||||
|
|
|
@ -22,6 +22,7 @@ from contextlib import asynccontextmanager
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from functools import partial
|
from functools import partial
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
|
import platform
|
||||||
import time
|
import time
|
||||||
from typing import Optional, Dict, Callable, Any
|
from typing import Optional, Dict, Callable, Any
|
||||||
import uuid
|
import uuid
|
||||||
|
@ -429,16 +430,17 @@ class OrderMode:
|
||||||
|
|
||||||
# TODO: make this not trash.
|
# TODO: make this not trash.
|
||||||
# XXX: linux only for now
|
# XXX: linux only for now
|
||||||
result = await trio.run_process(
|
if platform.system() != "Windows":
|
||||||
[
|
result = await trio.run_process(
|
||||||
'notify-send',
|
[
|
||||||
'-u', 'normal',
|
'notify-send',
|
||||||
'-t', '10000',
|
'-u', 'normal',
|
||||||
'piker',
|
'-t', '10000',
|
||||||
f'alert: {msg}',
|
'piker',
|
||||||
],
|
f'alert: {msg}',
|
||||||
)
|
],
|
||||||
log.runtime(result)
|
)
|
||||||
|
log.runtime(result)
|
||||||
|
|
||||||
def on_cancel(
|
def on_cancel(
|
||||||
self,
|
self,
|
||||||
|
|
Loading…
Reference in New Issue