Use shm array in chart-fsp task
Just like for the source OHLC, we now have the chart parent actor create an fsp shm array and use it to read back signal data for plotting. Some tweaks to get the price chart (and sub-charts) to load historical datums immediately instead of waiting on an initial quote.bar_select
parent
ba4261f974
commit
4383579cd0
|
@ -2,7 +2,6 @@
|
||||||
High level Qt chart widgets.
|
High level Qt chart widgets.
|
||||||
"""
|
"""
|
||||||
from typing import Tuple, Dict, Any, Optional
|
from typing import Tuple, Dict, Any, Optional
|
||||||
import time
|
|
||||||
|
|
||||||
from PyQt5 import QtCore, QtGui
|
from PyQt5 import QtCore, QtGui
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
@ -20,7 +19,10 @@ from ._style import _xaxis_at, _min_points_to_show, hcolor
|
||||||
from ._source import Symbol
|
from ._source import Symbol
|
||||||
from .. import brokers
|
from .. import brokers
|
||||||
from .. import data
|
from .. import data
|
||||||
from ..data._normalize import iterticks
|
from ..data import (
|
||||||
|
iterticks,
|
||||||
|
maybe_open_shm_array,
|
||||||
|
)
|
||||||
from ..log import get_logger
|
from ..log import get_logger
|
||||||
from ._exec import run_qtractor
|
from ._exec import run_qtractor
|
||||||
from ._interaction import ChartView
|
from ._interaction import ChartView
|
||||||
|
@ -542,7 +544,7 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
ylow = np.nanmin(bars['low'])
|
ylow = np.nanmin(bars['low'])
|
||||||
yhigh = np.nanmax(bars['high'])
|
yhigh = np.nanmax(bars['high'])
|
||||||
# std = np.std(bars['close'])
|
# std = np.std(bars['close'])
|
||||||
except IndexError:
|
except (IndexError, ValueError):
|
||||||
# must be non-ohlc array?
|
# must be non-ohlc array?
|
||||||
ylow = np.nanmin(bars)
|
ylow = np.nanmin(bars)
|
||||||
yhigh = np.nanmax(bars)
|
yhigh = np.nanmax(bars)
|
||||||
|
@ -587,53 +589,6 @@ class ChartPlotWidget(pg.PlotWidget):
|
||||||
self.scene().leaveEvent(ev)
|
self.scene().leaveEvent(ev)
|
||||||
|
|
||||||
|
|
||||||
async def check_for_new_bars(incr_stream, ohlcv, linked_charts):
|
|
||||||
"""Task which updates from new bars in the shared ohlcv buffer every
|
|
||||||
``delay_s`` seconds.
|
|
||||||
"""
|
|
||||||
# TODO: right now we'll spin printing bars if the last time
|
|
||||||
# stamp is before a large period of no market activity.
|
|
||||||
# Likely the best way to solve this is to make this task
|
|
||||||
# aware of the instrument's tradable hours?
|
|
||||||
|
|
||||||
price_chart = linked_charts.chart
|
|
||||||
|
|
||||||
async for index in incr_stream:
|
|
||||||
# TODO: generalize this increment logic
|
|
||||||
for name, chart in linked_charts.subplots.items():
|
|
||||||
data = chart._array
|
|
||||||
chart._array = np.append(
|
|
||||||
data,
|
|
||||||
np.array(data[-1], dtype=data.dtype)
|
|
||||||
)
|
|
||||||
|
|
||||||
# update chart historical bars graphics
|
|
||||||
price_chart.update_from_array(
|
|
||||||
price_chart.name,
|
|
||||||
ohlcv.array,
|
|
||||||
# When appending a new bar, in the time between the insert
|
|
||||||
# here and the Qt render call the underlying price data may
|
|
||||||
# have already been updated, thus make sure to also update
|
|
||||||
# the last bar if necessary on this render cycle.
|
|
||||||
# just_history=True
|
|
||||||
)
|
|
||||||
# resize view
|
|
||||||
price_chart._set_yrange()
|
|
||||||
|
|
||||||
for name, curve in price_chart._overlays.items():
|
|
||||||
# TODO: standard api for signal lookups per plot
|
|
||||||
if name in price_chart._array.dtype.fields:
|
|
||||||
# should have already been incremented above
|
|
||||||
price_chart.update_from_array(
|
|
||||||
name,
|
|
||||||
price_chart._array[name],
|
|
||||||
)
|
|
||||||
|
|
||||||
for name, chart in linked_charts.subplots.items():
|
|
||||||
chart.update_from_array(chart.name, chart._array)
|
|
||||||
chart._set_yrange()
|
|
||||||
|
|
||||||
|
|
||||||
async def _async_main(
|
async def _async_main(
|
||||||
sym: str,
|
sym: str,
|
||||||
brokername: str,
|
brokername: str,
|
||||||
|
@ -656,8 +611,9 @@ async def _async_main(
|
||||||
brokername,
|
brokername,
|
||||||
[sym],
|
[sym],
|
||||||
loglevel=loglevel,
|
loglevel=loglevel,
|
||||||
) as (fquote, stream, incr_stream, ohlcv):
|
) as feed:
|
||||||
|
|
||||||
|
ohlcv = feed.shm
|
||||||
bars = ohlcv.array
|
bars = ohlcv.array
|
||||||
|
|
||||||
# load in symbol's ohlc data
|
# load in symbol's ohlc data
|
||||||
|
@ -673,6 +629,8 @@ async def _async_main(
|
||||||
overlay=True,
|
overlay=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
chart._set_yrange()
|
||||||
|
|
||||||
async with trio.open_nursery() as n:
|
async with trio.open_nursery() as n:
|
||||||
|
|
||||||
# load initial fsp chain (otherwise known as "indicators")
|
# load initial fsp chain (otherwise known as "indicators")
|
||||||
|
@ -681,7 +639,7 @@ async def _async_main(
|
||||||
linked_charts,
|
linked_charts,
|
||||||
'rsi', # eventually will be n-compose syntax
|
'rsi', # eventually will be n-compose syntax
|
||||||
sym,
|
sym,
|
||||||
bars,
|
ohlcv,
|
||||||
brokermod,
|
brokermod,
|
||||||
loglevel,
|
loglevel,
|
||||||
)
|
)
|
||||||
|
@ -692,21 +650,22 @@ async def _async_main(
|
||||||
*ohlcv.array[-1][['index', 'close']]
|
*ohlcv.array[-1][['index', 'close']]
|
||||||
)
|
)
|
||||||
|
|
||||||
# wait for a first quote before we start any update tasks
|
|
||||||
quote = await stream.__anext__()
|
|
||||||
log.info(f'Received first quote {quote}')
|
|
||||||
|
|
||||||
# start graphics update loop(s)after receiving first live quote
|
# start graphics update loop(s)after receiving first live quote
|
||||||
n.start_soon(
|
n.start_soon(
|
||||||
chart_from_quotes,
|
chart_from_quotes,
|
||||||
chart,
|
chart,
|
||||||
stream,
|
feed.stream,
|
||||||
ohlcv,
|
ohlcv,
|
||||||
vwap_in_history,
|
vwap_in_history,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# wait for a first quote before we start any update tasks
|
||||||
|
quote = await feed.receive()
|
||||||
|
log.info(f'Received first quote {quote}')
|
||||||
|
|
||||||
n.start_soon(
|
n.start_soon(
|
||||||
check_for_new_bars,
|
check_for_new_bars,
|
||||||
incr_stream,
|
feed,
|
||||||
# delay,
|
# delay,
|
||||||
ohlcv,
|
ohlcv,
|
||||||
linked_charts
|
linked_charts
|
||||||
|
@ -743,20 +702,14 @@ async def chart_from_quotes(
|
||||||
# - in theory we should be able to read buffer data
|
# - in theory we should be able to read buffer data
|
||||||
# faster then msgs arrive.. needs some tinkering and
|
# faster then msgs arrive.. needs some tinkering and
|
||||||
# testing
|
# testing
|
||||||
start = time.time()
|
|
||||||
array = ohlcv.array
|
array = ohlcv.array
|
||||||
diff = time.time() - start
|
|
||||||
print(f'read time: {diff}')
|
|
||||||
# last = ohlcv.array[-1]
|
|
||||||
last = array[-1]
|
last = array[-1]
|
||||||
chart.update_from_array(
|
chart.update_from_array(
|
||||||
chart.name,
|
chart.name,
|
||||||
# ohlcv.array,
|
|
||||||
array,
|
array,
|
||||||
)
|
)
|
||||||
# update sticky(s)
|
# update sticky(s)
|
||||||
last_price_sticky.update_from_data(
|
last_price_sticky.update_from_data(*last[['index', 'close']])
|
||||||
*last[['index', 'close']])
|
|
||||||
chart._set_yrange()
|
chart._set_yrange()
|
||||||
|
|
||||||
vwap = quote.get('vwap')
|
vwap = quote.get('vwap')
|
||||||
|
@ -764,17 +717,14 @@ async def chart_from_quotes(
|
||||||
last['vwap'] = vwap
|
last['vwap'] = vwap
|
||||||
print(f"vwap: {quote['vwap']}")
|
print(f"vwap: {quote['vwap']}")
|
||||||
# update vwap overlay line
|
# update vwap overlay line
|
||||||
chart.update_from_array(
|
chart.update_from_array('vwap', ohlcv.array['vwap'])
|
||||||
'vwap',
|
|
||||||
ohlcv.array['vwap'],
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
async def chart_from_fsp(
|
async def chart_from_fsp(
|
||||||
linked_charts,
|
linked_charts,
|
||||||
func_name,
|
func_name,
|
||||||
sym,
|
sym,
|
||||||
bars,
|
src_shm,
|
||||||
brokermod,
|
brokermod,
|
||||||
loglevel,
|
loglevel,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -782,14 +732,31 @@ async def chart_from_fsp(
|
||||||
|
|
||||||
Pass target entrypoint and historical data.
|
Pass target entrypoint and historical data.
|
||||||
"""
|
"""
|
||||||
|
name = f'fsp.{func_name}'
|
||||||
|
# TODO: load function here and introspect
|
||||||
|
# return stream type(s)
|
||||||
|
fsp_dtype = np.dtype([('index', int), (func_name, float)])
|
||||||
|
|
||||||
async with tractor.open_nursery() as n:
|
async with tractor.open_nursery() as n:
|
||||||
|
key = f'{sym}.' + name
|
||||||
|
|
||||||
|
shm, opened = maybe_open_shm_array(
|
||||||
|
key,
|
||||||
|
# TODO: create entry for each time frame
|
||||||
|
dtype=fsp_dtype,
|
||||||
|
readonly=True,
|
||||||
|
)
|
||||||
|
assert opened
|
||||||
|
|
||||||
|
# start fsp sub-actor
|
||||||
portal = await n.run_in_actor(
|
portal = await n.run_in_actor(
|
||||||
f'fsp.{func_name}', # name as title of sub-chart
|
name, # name as title of sub-chart
|
||||||
|
|
||||||
# subactor entrypoint
|
# subactor entrypoint
|
||||||
fsp.stream_and_process,
|
fsp.cascade,
|
||||||
bars=bars,
|
|
||||||
brokername=brokermod.name,
|
brokername=brokermod.name,
|
||||||
|
src_shm_token=src_shm.token,
|
||||||
|
dst_shm_token=shm.token,
|
||||||
symbol=sym,
|
symbol=sym,
|
||||||
fsp_func_name=func_name,
|
fsp_func_name=func_name,
|
||||||
|
|
||||||
|
@ -799,55 +766,77 @@ async def chart_from_fsp(
|
||||||
|
|
||||||
stream = await portal.result()
|
stream = await portal.result()
|
||||||
|
|
||||||
# receive processed historical data-array as first message
|
# receive last index for processed historical
|
||||||
history = (await stream.__anext__())
|
# data-array as first msg
|
||||||
|
_ = await stream.receive()
|
||||||
# TODO: enforce type checking here?
|
|
||||||
newbars = np.array(history)
|
|
||||||
|
|
||||||
chart = linked_charts.add_plot(
|
chart = linked_charts.add_plot(
|
||||||
name=func_name,
|
name=func_name,
|
||||||
array=newbars,
|
|
||||||
|
# TODO: enforce type checking here?
|
||||||
|
array=shm.array,
|
||||||
)
|
)
|
||||||
|
|
||||||
# check for data length mis-allignment and fill missing values
|
array = shm.array[func_name]
|
||||||
diff = len(chart._array) - len(linked_charts.chart._array)
|
value = array[-1]
|
||||||
if diff <= 0:
|
|
||||||
data = chart._array
|
|
||||||
chart._array = np.append(
|
|
||||||
data,
|
|
||||||
np.full(abs(diff), data[-1], dtype=data.dtype)
|
|
||||||
)
|
|
||||||
|
|
||||||
# XXX: hack to get curves aligned with bars graphics: prepend
|
|
||||||
# a copy of the first datum..
|
|
||||||
# TODO: talk to ``pyqtgraph`` core about proper way to solve this
|
|
||||||
data = chart._array
|
|
||||||
chart._array = np.append(
|
|
||||||
np.array(data[0], dtype=data.dtype),
|
|
||||||
data,
|
|
||||||
)
|
|
||||||
|
|
||||||
value = chart._array[-1]
|
|
||||||
last_val_sticky = chart._ysticks[chart.name]
|
last_val_sticky = chart._ysticks[chart.name]
|
||||||
last_val_sticky.update_from_data(-1, value)
|
last_val_sticky.update_from_data(-1, value)
|
||||||
chart.update_from_array(chart.name, chart._array)
|
chart.update_from_array(chart.name, array)
|
||||||
|
|
||||||
chart._set_yrange(yrange=(0, 100))
|
chart._set_yrange(yrange=(0, 100))
|
||||||
|
|
||||||
|
chart._shm = shm
|
||||||
|
|
||||||
# update chart graphics
|
# update chart graphics
|
||||||
async for value in stream:
|
async for value in stream:
|
||||||
|
array = shm.array[func_name]
|
||||||
# start = time.time()
|
value = array[-1]
|
||||||
chart._array[-1] = value
|
|
||||||
# diff = time.time() - start
|
|
||||||
# print(f'FSP array append took {diff}')
|
|
||||||
|
|
||||||
last_val_sticky.update_from_data(-1, value)
|
last_val_sticky.update_from_data(-1, value)
|
||||||
chart.update_from_array(chart.name, chart._array)
|
chart.update_from_array(chart.name, array)
|
||||||
# chart._set_yrange()
|
# chart._set_yrange()
|
||||||
|
|
||||||
|
|
||||||
|
async def check_for_new_bars(feed, ohlcv, linked_charts):
|
||||||
|
"""Task which updates from new bars in the shared ohlcv buffer every
|
||||||
|
``delay_s`` seconds.
|
||||||
|
"""
|
||||||
|
# TODO: right now we'll spin printing bars if the last time
|
||||||
|
# stamp is before a large period of no market activity.
|
||||||
|
# Likely the best way to solve this is to make this task
|
||||||
|
# aware of the instrument's tradable hours?
|
||||||
|
|
||||||
|
price_chart = linked_charts.chart
|
||||||
|
|
||||||
|
async for index in await feed.index_stream():
|
||||||
|
|
||||||
|
# update chart historical bars graphics
|
||||||
|
price_chart.update_from_array(
|
||||||
|
price_chart.name,
|
||||||
|
ohlcv.array,
|
||||||
|
# When appending a new bar, in the time between the insert
|
||||||
|
# here and the Qt render call the underlying price data may
|
||||||
|
# have already been updated, thus make sure to also update
|
||||||
|
# the last bar if necessary on this render cycle which is
|
||||||
|
# why we **don't** set:
|
||||||
|
# just_history=True
|
||||||
|
)
|
||||||
|
# resize view
|
||||||
|
price_chart._set_yrange()
|
||||||
|
|
||||||
|
for name, curve in price_chart._overlays.items():
|
||||||
|
# TODO: standard api for signal lookups per plot
|
||||||
|
if name in price_chart._array.dtype.fields:
|
||||||
|
# should have already been incremented above
|
||||||
|
price_chart.update_from_array(
|
||||||
|
name,
|
||||||
|
price_chart._array[name],
|
||||||
|
)
|
||||||
|
|
||||||
|
for name, chart in linked_charts.subplots.items():
|
||||||
|
chart.update_from_array(chart.name, chart._shm.array[chart.name])
|
||||||
|
chart._set_yrange()
|
||||||
|
|
||||||
|
|
||||||
def _main(
|
def _main(
|
||||||
sym: str,
|
sym: str,
|
||||||
brokername: str,
|
brokername: str,
|
||||||
|
|
Loading…
Reference in New Issue