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
|
||||
|
||||
"""
|
||||
if platform.system() == 'Windows':
|
||||
log.warning(
|
||||
'Decreasing history query count to 4 since, windows...')
|
||||
count = 4
|
||||
|
||||
out, fails = await get_bars(sym)
|
||||
if out is None:
|
||||
raise RuntimeError("Could not pull currrent history?!")
|
||||
|
|
|
@ -1046,7 +1046,7 @@ async def _emsd_main(
|
|||
|
||||
# signal to client that we're started and deliver
|
||||
# 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
|
||||
# begin handling inbound order requests and updates
|
||||
|
|
|
@ -60,7 +60,7 @@ def repodir():
|
|||
"""
|
||||
dirpath = os.path.abspath(
|
||||
# we're 3 levels down in **this** module file
|
||||
dirname(dirname(dirname(os.path.realpath(__file__))))
|
||||
dirname(dirname(os.path.realpath(__file__)))
|
||||
)
|
||||
return dirpath
|
||||
|
||||
|
@ -73,7 +73,7 @@ def load(
|
|||
path = path or get_broker_conf_path()
|
||||
if not os.path.isfile(path):
|
||||
shutil.copyfile(
|
||||
os.path.join(repodir(), 'data/brokers.toml'),
|
||||
os.path.join(repodir(), 'config', 'brokers.toml'),
|
||||
path,
|
||||
)
|
||||
|
||||
|
|
|
@ -172,6 +172,7 @@ async def sample_and_broadcast(
|
|||
|
||||
# iterate stream delivered by broker
|
||||
async for quotes in quote_stream:
|
||||
|
||||
# TODO: ``numba`` this!
|
||||
for sym, quote in quotes.items():
|
||||
|
||||
|
@ -184,12 +185,8 @@ async def sample_and_broadcast(
|
|||
|
||||
# start writing the shm buffer with appropriate
|
||||
# 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']
|
||||
|
||||
# write trade events to shm last OHLC sample
|
||||
|
@ -261,8 +258,7 @@ async def sample_and_broadcast(
|
|||
|
||||
except (
|
||||
trio.BrokenResourceError,
|
||||
trio.ClosedResourceError,
|
||||
trio.EndOfChannel,
|
||||
trio.ClosedResourceError
|
||||
):
|
||||
# XXX: do we need to deregister here
|
||||
# if it's done in the fee bus code?
|
||||
|
@ -272,10 +268,6 @@ async def sample_and_broadcast(
|
|||
f'{stream._ctx.chan.uid} dropped '
|
||||
'`brokerd`-quotes-feed connection'
|
||||
)
|
||||
if tick_throttle:
|
||||
assert stream.closed()
|
||||
# await stream.aclose()
|
||||
|
||||
subs.remove((stream, tick_throttle))
|
||||
|
||||
|
||||
|
@ -291,8 +283,12 @@ async def uniform_rate_send(
|
|||
|
||||
) -> None:
|
||||
|
||||
sleep_period = 1/rate - 0.0001 # 100us
|
||||
sleep_period = 1/rate - 0.000616
|
||||
last_send = time.time()
|
||||
aname = stream._ctx.chan.uid[0]
|
||||
fsp = False
|
||||
if 'fsp' in aname:
|
||||
fsp = True
|
||||
|
||||
while True:
|
||||
|
||||
|
@ -312,33 +308,21 @@ async def uniform_rate_send(
|
|||
sym, next_quote = quote_stream.receive_nowait()
|
||||
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:
|
||||
first_quote['ticks'].extend(ticks)
|
||||
|
||||
except trio.WouldBlock:
|
||||
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(
|
||||
f'`{sym}` throttled send hz: {round(rate, ndigits=1)}'
|
||||
)
|
||||
# log.info(f'{rate} Hz sending quotes') # \n{first_quote}')
|
||||
|
||||
# TODO: now if only we could sync this to the display
|
||||
# rate timing exactly lul
|
||||
try:
|
||||
await stream.send({sym: first_quote})
|
||||
last_send = now
|
||||
break
|
||||
except trio.ClosedResourceError:
|
||||
# if the feed consumer goes down then drop
|
||||
|
|
|
@ -800,14 +800,11 @@ async def update_chart_from_fsp(
|
|||
profiler.finish()
|
||||
|
||||
# update chart graphics
|
||||
i = 0
|
||||
last = time.time()
|
||||
async for value in stream:
|
||||
|
||||
# chart isn't actively shown so just skip render cycle
|
||||
if chart.linked.isHidden():
|
||||
print(f'{i} unseen fsp cyclce')
|
||||
i += 1
|
||||
continue
|
||||
|
||||
else:
|
||||
|
|
|
@ -61,7 +61,9 @@ _do_overrides()
|
|||
# XXX: pretty sure none of this shit works on linux as per:
|
||||
# https://bugreports.qt.io/browse/QTBUG-53022
|
||||
# it seems to work on windows.. no idea wtf is up.
|
||||
is_windows = False
|
||||
if platform.system() == "Windows":
|
||||
is_windows = True
|
||||
|
||||
# Proper high DPI scaling is available in Qt >= 5.6.0. This attibute
|
||||
# must be set before creating the application
|
||||
|
@ -182,6 +184,8 @@ def run_qtractor(
|
|||
|
||||
window.main_widget = main_widget
|
||||
window.setCentralWidget(instance)
|
||||
if is_windows:
|
||||
window.configure_to_desktop()
|
||||
|
||||
# actually render to screen
|
||||
window.show()
|
||||
|
|
|
@ -49,7 +49,7 @@ from PyQt5 import QtCore
|
|||
from PyQt5 import QtWidgets
|
||||
from PyQt5.QtCore import (
|
||||
Qt,
|
||||
# QSize,
|
||||
QSize,
|
||||
QModelIndex,
|
||||
QItemSelectionModel,
|
||||
)
|
||||
|
@ -112,6 +112,7 @@ class CompleterView(QTreeView):
|
|||
|
||||
model = QStandardItemModel(self)
|
||||
self.labels = labels
|
||||
self._last_window_h: Optional[int] = None
|
||||
|
||||
# a std "tabular" config
|
||||
self.setItemDelegate(FontScaledDelegate(self))
|
||||
|
@ -126,6 +127,10 @@ class CompleterView(QTreeView):
|
|||
# self.setSizeAdjustPolicy(QAbstractScrollArea.AdjustIgnored)
|
||||
|
||||
# ux settings
|
||||
self.setSizePolicy(
|
||||
QtWidgets.QSizePolicy.Expanding,
|
||||
QtWidgets.QSizePolicy.Expanding,
|
||||
)
|
||||
self.setItemsExpandable(True)
|
||||
self.setExpandsOnDoubleClick(False)
|
||||
self.setAnimated(False)
|
||||
|
@ -152,24 +157,41 @@ class CompleterView(QTreeView):
|
|||
self._font_size = size
|
||||
|
||||
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()
|
||||
cols = model.columnCount()
|
||||
|
||||
for i in range(cols):
|
||||
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())
|
||||
# print(f'font_h: {font_h}\n px_height: {px_height}')
|
||||
|
||||
# TODO: probably make this more general / less hacky
|
||||
self.setMinimumSize(self.width(), rows * row_px)
|
||||
self.setMaximumSize(self.width() + 10, rows * row_px)
|
||||
# we should figure out the exact number of rows to allow
|
||||
# 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.update()
|
||||
|
||||
def is_selecting_d1(self) -> bool:
|
||||
cidx = self.selectionModel().currentIndex()
|
||||
|
@ -334,7 +356,7 @@ class CompleterView(QTreeView):
|
|||
else:
|
||||
model.setItem(idx.row(), 1, QStandardItem())
|
||||
|
||||
self.resize()
|
||||
self.resize_to_results()
|
||||
|
||||
return idx
|
||||
else:
|
||||
|
@ -404,7 +426,7 @@ class CompleterView(QTreeView):
|
|||
|
||||
def show_matches(self) -> None:
|
||||
self.show()
|
||||
self.resize()
|
||||
self.resize_to_results()
|
||||
|
||||
|
||||
class SearchBar(Edit):
|
||||
|
@ -457,7 +479,7 @@ class SearchWidget(QtWidgets.QWidget):
|
|||
# size it as we specify
|
||||
self.setSizePolicy(
|
||||
QtWidgets.QSizePolicy.Fixed,
|
||||
QtWidgets.QSizePolicy.Fixed,
|
||||
QtWidgets.QSizePolicy.Expanding,
|
||||
)
|
||||
|
||||
self.godwidget = godwidget
|
||||
|
|
|
@ -151,8 +151,8 @@ class MainWindow(QtGui.QMainWindow):
|
|||
# XXX: for tiling wms this should scale
|
||||
# with the alloted window size.
|
||||
# TODO: detect for tiling and if untrue set some size?
|
||||
# size = (300, 500)
|
||||
size = (0, 0)
|
||||
size = (300, 500)
|
||||
#size = (0, 0)
|
||||
|
||||
title = 'piker chart (ur symbol is loading bby)'
|
||||
|
||||
|
@ -163,7 +163,8 @@ class MainWindow(QtGui.QMainWindow):
|
|||
|
||||
self._status_bar: QStatusBar = None
|
||||
self._status_label: QLabel = None
|
||||
|
||||
self._size: Optional[tuple[int, int]] = None
|
||||
|
||||
@property
|
||||
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..."
|
||||
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
|
||||
_qt_win: QtGui.QMainWindow = None
|
||||
|
|
|
@ -22,6 +22,7 @@ from contextlib import asynccontextmanager
|
|||
from dataclasses import dataclass, field
|
||||
from functools import partial
|
||||
from pprint import pformat
|
||||
import platform
|
||||
import time
|
||||
from typing import Optional, Dict, Callable, Any
|
||||
import uuid
|
||||
|
@ -429,16 +430,17 @@ class OrderMode:
|
|||
|
||||
# TODO: make this not trash.
|
||||
# XXX: linux only for now
|
||||
result = await trio.run_process(
|
||||
[
|
||||
'notify-send',
|
||||
'-u', 'normal',
|
||||
'-t', '10000',
|
||||
'piker',
|
||||
f'alert: {msg}',
|
||||
],
|
||||
)
|
||||
log.runtime(result)
|
||||
if platform.system() != "Windows":
|
||||
result = await trio.run_process(
|
||||
[
|
||||
'notify-send',
|
||||
'-u', 'normal',
|
||||
'-t', '10000',
|
||||
'piker',
|
||||
f'alert: {msg}',
|
||||
],
|
||||
)
|
||||
log.runtime(result)
|
||||
|
||||
def on_cancel(
|
||||
self,
|
||||
|
|
Loading…
Reference in New Issue