Compare commits
14 Commits
update_las
...
310_plus
Author | SHA1 | Date |
---|---|---|
Tyler Goodlet | ac0f43dc98 | |
goodboy | 3977f1cc7e | |
Tyler Goodlet | e45cb9d08a | |
Tyler Goodlet | 27c523ca74 | |
Tyler Goodlet | b8b76a32a6 | |
Tyler Goodlet | dcee0ddd55 | |
goodboy | 67eab85f06 | |
Tyler Goodlet | afc95b8592 | |
Tyler Goodlet | 14c98d82ee | |
goodboy | b87aa30031 | |
Tyler Goodlet | 958f53d8e9 | |
Tyler Goodlet | ba43b54175 | |
Tyler Goodlet | de970755d7 | |
goodboy | 7ddebf6773 |
|
@ -35,7 +35,7 @@ log = get_logger(__name__)
|
||||||
|
|
||||||
_root_dname = 'pikerd'
|
_root_dname = 'pikerd'
|
||||||
|
|
||||||
_registry_addr = ('127.0.0.1', 1616)
|
_registry_addr = ('127.0.0.1', 6116)
|
||||||
_tractor_kwargs: dict[str, Any] = {
|
_tractor_kwargs: dict[str, Any] = {
|
||||||
# use a different registry addr then tractor's default
|
# use a different registry addr then tractor's default
|
||||||
'arbiter_addr': _registry_addr
|
'arbiter_addr': _registry_addr
|
||||||
|
|
|
@ -640,6 +640,7 @@ class Client:
|
||||||
ready = ticker.updateEvent
|
ready = ticker.updateEvent
|
||||||
|
|
||||||
# ensure a last price gets filled in before we deliver quote
|
# ensure a last price gets filled in before we deliver quote
|
||||||
|
warnset: bool = False
|
||||||
for _ in range(100):
|
for _ in range(100):
|
||||||
if isnan(ticker.last):
|
if isnan(ticker.last):
|
||||||
|
|
||||||
|
@ -650,17 +651,21 @@ class Client:
|
||||||
if ready in done:
|
if ready in done:
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
if not warnset:
|
||||||
log.warning(
|
log.warning(
|
||||||
f'Quote for {symbol} timed out: market is closed?'
|
f'Quote for {symbol} timed out: market is closed?'
|
||||||
)
|
)
|
||||||
|
warnset = True
|
||||||
|
|
||||||
else:
|
else:
|
||||||
log.info(f'Got first quote for {symbol}')
|
log.info(f'Got first quote for {symbol}')
|
||||||
break
|
break
|
||||||
else:
|
else:
|
||||||
|
if not warnset:
|
||||||
log.warning(
|
log.warning(
|
||||||
f'Symbol {symbol} is not returning a quote '
|
f'Symbol {symbol} is not returning a quote '
|
||||||
'it may be outside trading hours?')
|
'it may be outside trading hours?')
|
||||||
|
warnset = True
|
||||||
|
|
||||||
return contract, ticker, details
|
return contract, ticker, details
|
||||||
|
|
||||||
|
|
|
@ -19,6 +19,7 @@ Supervisor for docker with included specific-image service helpers.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
import os
|
import os
|
||||||
|
import time
|
||||||
from typing import (
|
from typing import (
|
||||||
Optional,
|
Optional,
|
||||||
Callable,
|
Callable,
|
||||||
|
@ -186,45 +187,65 @@ class Container:
|
||||||
|
|
||||||
async def cancel(
|
async def cancel(
|
||||||
self,
|
self,
|
||||||
|
stop_msg: str,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
cid = self.cntr.id
|
cid = self.cntr.id
|
||||||
|
# first try a graceful cancel
|
||||||
|
log.cancel(
|
||||||
|
f'SIGINT cancelling container: {cid}\n'
|
||||||
|
f'waiting on stop msg: "{stop_msg}"'
|
||||||
|
)
|
||||||
self.try_signal('SIGINT')
|
self.try_signal('SIGINT')
|
||||||
|
|
||||||
with trio.move_on_after(0.5) as cs:
|
start = time.time()
|
||||||
cs.shield = True
|
for _ in range(30):
|
||||||
await self.process_logs_until('initiating graceful shutdown')
|
|
||||||
await self.process_logs_until('exiting...',)
|
|
||||||
|
|
||||||
for _ in range(10):
|
|
||||||
with trio.move_on_after(0.5) as cs:
|
with trio.move_on_after(0.5) as cs:
|
||||||
cs.shield = True
|
cs.shield = True
|
||||||
await self.process_logs_until('exiting...',)
|
await self.process_logs_until(stop_msg)
|
||||||
|
|
||||||
|
# if we aren't cancelled on above checkpoint then we
|
||||||
|
# assume we read the expected stop msg and terminated.
|
||||||
break
|
break
|
||||||
|
|
||||||
if cs.cancelled_caught:
|
|
||||||
# get out the big guns, bc apparently marketstore
|
|
||||||
# doesn't actually know how to terminate gracefully
|
|
||||||
# :eyeroll:...
|
|
||||||
self.try_signal('SIGKILL')
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
log.info('Waiting on container shutdown: {cid}')
|
log.info(f'Polling for container shutdown:\n{cid}')
|
||||||
|
|
||||||
|
if self.cntr.status not in {'exited', 'not-running'}:
|
||||||
self.cntr.wait(
|
self.cntr.wait(
|
||||||
timeout=0.1,
|
timeout=0.1,
|
||||||
condition='not-running',
|
condition='not-running',
|
||||||
)
|
)
|
||||||
|
|
||||||
break
|
break
|
||||||
|
|
||||||
except (
|
except (
|
||||||
ReadTimeout,
|
ReadTimeout,
|
||||||
|
):
|
||||||
|
log.info(f'Still waiting on container:\n{cid}')
|
||||||
|
continue
|
||||||
|
|
||||||
|
except (
|
||||||
|
docker.errors.APIError,
|
||||||
ConnectionError,
|
ConnectionError,
|
||||||
):
|
):
|
||||||
log.error(f'failed to wait on container {cid}')
|
log.exception('Docker connection failure')
|
||||||
raise
|
break
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise RuntimeError('Failed to cancel container {cid}')
|
delay = time.time() - start
|
||||||
|
log.error(
|
||||||
|
f'Failed to kill container {cid} after {delay}s\n'
|
||||||
|
'sending SIGKILL..'
|
||||||
|
)
|
||||||
|
# get out the big guns, bc apparently marketstore
|
||||||
|
# doesn't actually know how to terminate gracefully
|
||||||
|
# :eyeroll:...
|
||||||
|
self.try_signal('SIGKILL')
|
||||||
|
self.cntr.wait(
|
||||||
|
timeout=3,
|
||||||
|
condition='not-running',
|
||||||
|
)
|
||||||
|
|
||||||
log.cancel(f'Container stopped: {cid}')
|
log.cancel(f'Container stopped: {cid}')
|
||||||
|
|
||||||
|
@ -245,13 +266,16 @@ async def open_ahabd(
|
||||||
# params, etc. passing to ``Containter.run()``?
|
# params, etc. passing to ``Containter.run()``?
|
||||||
# call into endpoint for container config/init
|
# call into endpoint for container config/init
|
||||||
ep_func = NamespacePath(endpoint).load_ref()
|
ep_func = NamespacePath(endpoint).load_ref()
|
||||||
dcntr, cntr_config = ep_func(client)
|
(
|
||||||
|
dcntr,
|
||||||
|
cntr_config,
|
||||||
|
start_msg,
|
||||||
|
stop_msg,
|
||||||
|
) = ep_func(client)
|
||||||
cntr = Container(dcntr)
|
cntr = Container(dcntr)
|
||||||
|
|
||||||
with trio.move_on_after(1):
|
with trio.move_on_after(1):
|
||||||
found = await cntr.process_logs_until(
|
found = await cntr.process_logs_until(start_msg)
|
||||||
"launching tcp listener for all services...",
|
|
||||||
)
|
|
||||||
|
|
||||||
if not found and cntr not in client.containers.list():
|
if not found and cntr not in client.containers.list():
|
||||||
raise RuntimeError(
|
raise RuntimeError(
|
||||||
|
@ -271,16 +295,9 @@ async def open_ahabd(
|
||||||
# callers to have root perms?
|
# callers to have root perms?
|
||||||
await trio.sleep_forever()
|
await trio.sleep_forever()
|
||||||
|
|
||||||
except (
|
finally:
|
||||||
BaseException,
|
|
||||||
# trio.Cancelled,
|
|
||||||
# KeyboardInterrupt,
|
|
||||||
):
|
|
||||||
|
|
||||||
with trio.CancelScope(shield=True):
|
with trio.CancelScope(shield=True):
|
||||||
await cntr.cancel()
|
await cntr.cancel(stop_msg)
|
||||||
|
|
||||||
raise
|
|
||||||
|
|
||||||
|
|
||||||
async def start_ahab(
|
async def start_ahab(
|
||||||
|
|
|
@ -127,10 +127,15 @@ def start_marketstore(
|
||||||
import os
|
import os
|
||||||
import docker
|
import docker
|
||||||
from .. import config
|
from .. import config
|
||||||
|
|
||||||
get_console_log('info', name=__name__)
|
get_console_log('info', name=__name__)
|
||||||
|
|
||||||
yml_file = os.path.join(config._config_dir, 'mkts.yml')
|
mktsdir = os.path.join(config._config_dir, 'marketstore')
|
||||||
|
|
||||||
|
# create when dne
|
||||||
|
if not os.path.isdir(mktsdir):
|
||||||
|
os.mkdir(mktsdir)
|
||||||
|
|
||||||
|
yml_file = os.path.join(mktsdir, 'mkts.yml')
|
||||||
if not os.path.isfile(yml_file):
|
if not os.path.isfile(yml_file):
|
||||||
log.warning(
|
log.warning(
|
||||||
f'No `marketstore` config exists?: {yml_file}\n'
|
f'No `marketstore` config exists?: {yml_file}\n'
|
||||||
|
@ -143,14 +148,14 @@ def start_marketstore(
|
||||||
# create a mount from user's local piker config dir into container
|
# create a mount from user's local piker config dir into container
|
||||||
config_dir_mnt = docker.types.Mount(
|
config_dir_mnt = docker.types.Mount(
|
||||||
target='/etc',
|
target='/etc',
|
||||||
source=config._config_dir,
|
source=mktsdir,
|
||||||
type='bind',
|
type='bind',
|
||||||
)
|
)
|
||||||
|
|
||||||
# create a user config subdir where the marketstore
|
# create a user config subdir where the marketstore
|
||||||
# backing filesystem database can be persisted.
|
# backing filesystem database can be persisted.
|
||||||
persistent_data_dir = os.path.join(
|
persistent_data_dir = os.path.join(
|
||||||
config._config_dir, 'data',
|
mktsdir, 'data',
|
||||||
)
|
)
|
||||||
if not os.path.isdir(persistent_data_dir):
|
if not os.path.isdir(persistent_data_dir):
|
||||||
os.mkdir(persistent_data_dir)
|
os.mkdir(persistent_data_dir)
|
||||||
|
@ -180,7 +185,14 @@ def start_marketstore(
|
||||||
init=True,
|
init=True,
|
||||||
# remove=True,
|
# remove=True,
|
||||||
)
|
)
|
||||||
return dcntr, _config
|
return (
|
||||||
|
dcntr,
|
||||||
|
_config,
|
||||||
|
|
||||||
|
# expected startup and stop msgs
|
||||||
|
"launching tcp listener for all services...",
|
||||||
|
"exiting...",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
_tick_tbk_ids: tuple[str, str] = ('1Sec', 'TICK')
|
_tick_tbk_ids: tuple[str, str] = ('1Sec', 'TICK')
|
||||||
|
@ -383,7 +395,12 @@ class Storage:
|
||||||
]:
|
]:
|
||||||
|
|
||||||
first_tsdb_dt, last_tsdb_dt = None, None
|
first_tsdb_dt, last_tsdb_dt = None, None
|
||||||
tsdb_arrays = await self.read_ohlcv(fqsn)
|
tsdb_arrays = await self.read_ohlcv(
|
||||||
|
fqsn,
|
||||||
|
# on first load we don't need to pull the max
|
||||||
|
# history per request size worth.
|
||||||
|
limit=3000,
|
||||||
|
)
|
||||||
log.info(f'Loaded tsdb history {tsdb_arrays}')
|
log.info(f'Loaded tsdb history {tsdb_arrays}')
|
||||||
|
|
||||||
if tsdb_arrays:
|
if tsdb_arrays:
|
||||||
|
@ -401,6 +418,7 @@ class Storage:
|
||||||
fqsn: str,
|
fqsn: str,
|
||||||
timeframe: Optional[Union[int, str]] = None,
|
timeframe: Optional[Union[int, str]] = None,
|
||||||
end: Optional[int] = None,
|
end: Optional[int] = None,
|
||||||
|
limit: int = int(800e3),
|
||||||
|
|
||||||
) -> tuple[
|
) -> tuple[
|
||||||
MarketstoreClient,
|
MarketstoreClient,
|
||||||
|
@ -423,7 +441,7 @@ class Storage:
|
||||||
|
|
||||||
# TODO: figure the max limit here given the
|
# TODO: figure the max limit here given the
|
||||||
# ``purepc`` msg size limit of purerpc: 33554432
|
# ``purepc`` msg size limit of purerpc: 33554432
|
||||||
limit=int(800e3),
|
limit=limit,
|
||||||
)
|
)
|
||||||
|
|
||||||
if timeframe is None:
|
if timeframe is None:
|
||||||
|
|
|
@ -361,7 +361,7 @@ async def cascade(
|
||||||
) -> tuple[TaskTracker, int]:
|
) -> tuple[TaskTracker, int]:
|
||||||
# TODO: adopt an incremental update engine/approach
|
# TODO: adopt an incremental update engine/approach
|
||||||
# where possible here eventually!
|
# where possible here eventually!
|
||||||
log.warning(f're-syncing fsp {func_name} to source')
|
log.debug(f're-syncing fsp {func_name} to source')
|
||||||
tracker.cs.cancel()
|
tracker.cs.cancel()
|
||||||
await tracker.complete.wait()
|
await tracker.complete.wait()
|
||||||
tracker, index = await n.start(fsp_target)
|
tracker, index = await n.start(fsp_target)
|
||||||
|
|
|
@ -750,8 +750,15 @@ class Flow(msgspec.Struct): # , frozen=True):
|
||||||
y = y[-uppx:]
|
y = y[-uppx:]
|
||||||
ymn, ymx = y.min(), y.max()
|
ymn, ymx = y.min(), y.max()
|
||||||
# print(f'drawing uppx={uppx} mxmn line: {ymn}, {ymx}')
|
# print(f'drawing uppx={uppx} mxmn line: {ymn}, {ymx}')
|
||||||
|
try:
|
||||||
|
iuppx = x[-uppx]
|
||||||
|
except IndexError:
|
||||||
|
# we're less then an x-px wide so just grab the start
|
||||||
|
# datum index.
|
||||||
|
iuppx = x[0]
|
||||||
|
|
||||||
dsg._last_line = QLineF(
|
dsg._last_line = QLineF(
|
||||||
x[-uppx], ymn,
|
iuppx, ymn,
|
||||||
x[-1], ymx,
|
x[-1], ymx,
|
||||||
)
|
)
|
||||||
# print(f'updating DS curve {self.name}')
|
# print(f'updating DS curve {self.name}')
|
||||||
|
|
|
@ -440,7 +440,7 @@ class FspAdmin:
|
||||||
# if the chart isn't hidden try to update
|
# if the chart isn't hidden try to update
|
||||||
# the data on screen.
|
# the data on screen.
|
||||||
if not self.linked.isHidden():
|
if not self.linked.isHidden():
|
||||||
log.info(f'Re-syncing graphics for fsp: {ns_path}')
|
log.debug(f'Re-syncing graphics for fsp: {ns_path}')
|
||||||
self.linked.graphics_cycle(
|
self.linked.graphics_cycle(
|
||||||
trigger_all=True,
|
trigger_all=True,
|
||||||
prepend_update_index=info['first'],
|
prepend_update_index=info['first'],
|
||||||
|
|
23
setup.py
23
setup.py
|
@ -57,6 +57,7 @@ setup(
|
||||||
# from github currently (see requirements.txt)
|
# from github currently (see requirements.txt)
|
||||||
# 'trimeter', # not released yet..
|
# 'trimeter', # not released yet..
|
||||||
# 'tractor',
|
# 'tractor',
|
||||||
|
# asyncvnc,
|
||||||
|
|
||||||
# brokers
|
# brokers
|
||||||
'asks==2.4.8',
|
'asks==2.4.8',
|
||||||
|
@ -71,32 +72,34 @@ setup(
|
||||||
|
|
||||||
# UI
|
# UI
|
||||||
'PyQt5',
|
'PyQt5',
|
||||||
'pyqtgraph',
|
# 'pyqtgraph', from our fork see reqs.txt
|
||||||
'qdarkstyle >= 3.0.2',
|
'qdarkstyle >= 3.0.2', # themeing
|
||||||
# fuzzy search
|
'fuzzywuzzy[speedup]', # fuzzy search
|
||||||
'fuzzywuzzy[speedup]',
|
|
||||||
|
|
||||||
# tsdbs
|
# tsdbs
|
||||||
'pymarketstore',
|
# anyio-marketstore # from gh see reqs.txt
|
||||||
],
|
],
|
||||||
extras_require={
|
extras_require={
|
||||||
|
|
||||||
# serialization
|
|
||||||
'tsdb': [
|
'tsdb': [
|
||||||
'docker',
|
'docker',
|
||||||
],
|
],
|
||||||
|
|
||||||
},
|
},
|
||||||
tests_require=['pytest'],
|
tests_require=['pytest'],
|
||||||
python_requires=">=3.9", # literally for ``datetime.datetime.fromisoformat``...
|
python_requires=">=3.10",
|
||||||
keywords=["async", "trading", "finance", "quant", "charting"],
|
keywords=[
|
||||||
|
"async",
|
||||||
|
"trading",
|
||||||
|
"finance",
|
||||||
|
"quant",
|
||||||
|
"charting",
|
||||||
|
],
|
||||||
classifiers=[
|
classifiers=[
|
||||||
'Development Status :: 3 - Alpha',
|
'Development Status :: 3 - Alpha',
|
||||||
'License :: OSI Approved :: ',
|
'License :: OSI Approved :: ',
|
||||||
'Operating System :: POSIX :: Linux',
|
'Operating System :: POSIX :: Linux',
|
||||||
"Programming Language :: Python :: Implementation :: CPython",
|
"Programming Language :: Python :: Implementation :: CPython",
|
||||||
"Programming Language :: Python :: 3 :: Only",
|
"Programming Language :: Python :: 3 :: Only",
|
||||||
"Programming Language :: Python :: 3.9",
|
|
||||||
"Programming Language :: Python :: 3.10",
|
"Programming Language :: Python :: 3.10",
|
||||||
'Intended Audience :: Financial and Insurance Industry',
|
'Intended Audience :: Financial and Insurance Industry',
|
||||||
'Intended Audience :: Science/Research',
|
'Intended Audience :: Science/Research',
|
||||||
|
|
Loading…
Reference in New Issue