Compare commits

..

No commits in common. "75faf8300447f5464f8dda459f3ad2068456dcc7" and "30dfb8f03d4cf2d7f178b70c27ed75a11864de05" have entirely different histories.

10 changed files with 58 additions and 117 deletions

View File

@ -1,28 +0,0 @@
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.

View File

@ -1157,11 +1157,6 @@ 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?!")

View File

@ -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, list(relay.accounts))) await ems_ctx.started((pp_msgs, 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

View File

@ -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(os.path.realpath(__file__))) dirname(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(), 'config', 'brokers.toml'), os.path.join(repodir(), 'data/brokers.toml'),
path, path,
) )

View File

@ -172,7 +172,6 @@ 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():
@ -185,8 +184,12 @@ 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
@ -258,7 +261,8 @@ 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?
@ -268,6 +272,10 @@ 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))
@ -283,12 +291,8 @@ async def uniform_rate_send(
) -> None: ) -> None:
sleep_period = 1/rate - 0.000616 sleep_period = 1/rate - 0.0001 # 100us
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:
@ -308,21 +312,33 @@ 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()
diff = now - last_send rate = 1 / (now - last_send)
rate = 1 / diff if diff else float('inf')
last_send = now
# log.info(f'{rate} Hz sending quotes') # \n{first_quote}') log.debug(
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

View File

@ -800,11 +800,14 @@ 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:

View File

@ -61,9 +61,7 @@ _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
@ -184,8 +182,6 @@ 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()

View File

@ -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,7 +112,6 @@ 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))
@ -127,10 +126,6 @@ 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)
@ -157,41 +152,24 @@ 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_to_results(self): def resize(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
# we should figure out the exact number of rows to allow self.setMinimumSize(self.width(), rows * row_px)
# inclusive of search bar and header "rows", in pixel terms. self.setMaximumSize(self.width() + 10, rows * row_px)
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()
@ -356,7 +334,7 @@ class CompleterView(QTreeView):
else: else:
model.setItem(idx.row(), 1, QStandardItem()) model.setItem(idx.row(), 1, QStandardItem())
self.resize_to_results() self.resize()
return idx return idx
else: else:
@ -426,7 +404,7 @@ class CompleterView(QTreeView):
def show_matches(self) -> None: def show_matches(self) -> None:
self.show() self.show()
self.resize_to_results() self.resize()
class SearchBar(Edit): class SearchBar(Edit):
@ -479,7 +457,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.Expanding, QtWidgets.QSizePolicy.Fixed,
) )
self.godwidget = godwidget self.godwidget = godwidget

View File

@ -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,8 +163,7 @@ 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:
@ -268,22 +267,6 @@ 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

View File

@ -22,7 +22,6 @@ 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
@ -430,17 +429,16 @@ class OrderMode:
# TODO: make this not trash. # TODO: make this not trash.
# XXX: linux only for now # XXX: linux only for now
if platform.system() != "Windows": result = await trio.run_process(
result = await trio.run_process( [
[ 'notify-send',
'notify-send', '-u', 'normal',
'-u', 'normal', '-t', '10000',
'-t', '10000', 'piker',
'piker', f'alert: {msg}',
f'alert: {msg}', ],
], )
) log.runtime(result)
log.runtime(result)
def on_cancel( def on_cancel(
self, self,