Add `.data.validate` checker for live feed layer
More or less a replacement for what @guilledk did with the initial attempt at a "broker check" type script a while back except in this case we're going to always run this validation routine and it now uses a new `FeedInit` struct to ensure backends are delivering the right schema-ed data during startup. Also allows us to stick deprecation warnings / and or strict API compat errors all in one spot (at least for live feeds). Factors out a bunch of `MktPair` related adapter-logic into a new `.validate.valiate_backend()` which warns to the backend implementer via log msgs all the problems outstanding. Ideally we do our backend module endpoint scan-and-complain regarding missing feature support from here as well (eg. search, broker/trade ctl, ledger processing, etc.).rekt_pps
parent
d48b2c5b57
commit
4129d693be
|
@ -26,7 +26,7 @@ from collections import (
|
||||||
Counter,
|
Counter,
|
||||||
)
|
)
|
||||||
from contextlib import asynccontextmanager as acm
|
from contextlib import asynccontextmanager as acm
|
||||||
from decimal import Decimal
|
# from decimal import Decimal
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from functools import partial
|
from functools import partial
|
||||||
import time
|
import time
|
||||||
|
@ -55,8 +55,8 @@ import numpy as np
|
||||||
|
|
||||||
from ..brokers import get_brokermod
|
from ..brokers import get_brokermod
|
||||||
from ..calc import humanize
|
from ..calc import humanize
|
||||||
from ..log import (
|
from ._util import (
|
||||||
get_logger,
|
log,
|
||||||
get_console_log,
|
get_console_log,
|
||||||
)
|
)
|
||||||
from ..service import (
|
from ..service import (
|
||||||
|
@ -64,6 +64,10 @@ from ..service import (
|
||||||
check_for_service,
|
check_for_service,
|
||||||
)
|
)
|
||||||
from .flows import Flume
|
from .flows import Flume
|
||||||
|
from .validate import (
|
||||||
|
FeedInit,
|
||||||
|
validate_backend,
|
||||||
|
)
|
||||||
from ._sharedmem import (
|
from ._sharedmem import (
|
||||||
maybe_open_shm_array,
|
maybe_open_shm_array,
|
||||||
ShmArray,
|
ShmArray,
|
||||||
|
@ -72,10 +76,8 @@ from ._sharedmem import (
|
||||||
from .ingest import get_ingestormod
|
from .ingest import get_ingestormod
|
||||||
from .types import Struct
|
from .types import Struct
|
||||||
from ..accounting._mktinfo import (
|
from ..accounting._mktinfo import (
|
||||||
Asset,
|
|
||||||
MktPair,
|
MktPair,
|
||||||
unpack_fqme,
|
unpack_fqme,
|
||||||
Symbol,
|
|
||||||
)
|
)
|
||||||
from ._source import base_iohlc_dtype
|
from ._source import base_iohlc_dtype
|
||||||
from ..ui import _search
|
from ..ui import _search
|
||||||
|
@ -91,8 +93,6 @@ from ..brokers._util import (
|
||||||
if TYPE_CHECKING:
|
if TYPE_CHECKING:
|
||||||
from ..service.marketstore import Storage
|
from ..service.marketstore import Storage
|
||||||
|
|
||||||
log = get_logger(__name__)
|
|
||||||
|
|
||||||
|
|
||||||
class _FeedsBus(Struct):
|
class _FeedsBus(Struct):
|
||||||
'''
|
'''
|
||||||
|
@ -568,7 +568,7 @@ async def tsdb_backfill(
|
||||||
timeframe=timeframe,
|
timeframe=timeframe,
|
||||||
)
|
)
|
||||||
|
|
||||||
broker, symbol, expiry = unpack_fqme(fqsn)
|
broker, *_ = unpack_fqme(fqsn)
|
||||||
try:
|
try:
|
||||||
(
|
(
|
||||||
latest_start_dt,
|
latest_start_dt,
|
||||||
|
@ -790,13 +790,14 @@ async def manage_history(
|
||||||
# port = _runtime_vars['_root_mailbox'][1]
|
# port = _runtime_vars['_root_mailbox'][1]
|
||||||
|
|
||||||
uid = tractor.current_actor().uid
|
uid = tractor.current_actor().uid
|
||||||
suffix = '.'.join(uid)
|
name, uuid = uid
|
||||||
|
service = name.rstrip(f'.{mod.name}')
|
||||||
|
|
||||||
# (maybe) allocate shm array for this broker/symbol which will
|
# (maybe) allocate shm array for this broker/symbol which will
|
||||||
# be used for fast near-term history capture and processing.
|
# be used for fast near-term history capture and processing.
|
||||||
hist_shm, opened = maybe_open_shm_array(
|
hist_shm, opened = maybe_open_shm_array(
|
||||||
# key=f'{fqsn}_hist_p{port}',
|
# key=f'{fqsn}_hist_p{port}',
|
||||||
key=f'{fqsn}_hist.{suffix}',
|
key=f'piker.{service}[{uuid[:16]}.{fqsn}.hist',
|
||||||
|
|
||||||
# use any broker defined ohlc dtype:
|
# use any broker defined ohlc dtype:
|
||||||
dtype=getattr(mod, '_ohlc_dtype', base_iohlc_dtype),
|
dtype=getattr(mod, '_ohlc_dtype', base_iohlc_dtype),
|
||||||
|
@ -814,7 +815,8 @@ async def manage_history(
|
||||||
|
|
||||||
rt_shm, opened = maybe_open_shm_array(
|
rt_shm, opened = maybe_open_shm_array(
|
||||||
# key=f'{fqsn}_rt_p{port}',
|
# key=f'{fqsn}_rt_p{port}',
|
||||||
key=f'{fqsn}_rt.{suffix}',
|
# key=f'piker.{service}.{fqsn}_rt.{uuid}',
|
||||||
|
key=f'piker.{service}[{uuid[:16]}.{fqsn}.rt',
|
||||||
|
|
||||||
# use any broker defined ohlc dtype:
|
# use any broker defined ohlc dtype:
|
||||||
dtype=getattr(mod, '_ohlc_dtype', base_iohlc_dtype),
|
dtype=getattr(mod, '_ohlc_dtype', base_iohlc_dtype),
|
||||||
|
@ -933,24 +935,6 @@ async def manage_history(
|
||||||
await trio.sleep_forever()
|
await trio.sleep_forever()
|
||||||
|
|
||||||
|
|
||||||
class BackendInitMsg(Struct, frozen=True):
|
|
||||||
'''
|
|
||||||
A stringent data provider startup msg schema validator.
|
|
||||||
|
|
||||||
The fields defined here are matched with those absolutely required
|
|
||||||
from each backend broker/data provider.
|
|
||||||
|
|
||||||
'''
|
|
||||||
fqme: str
|
|
||||||
symbol_info: dict | None = None
|
|
||||||
mkt_info: MktPair | None = None
|
|
||||||
shm_write_opts: dict[str, Any] | None = None
|
|
||||||
|
|
||||||
|
|
||||||
def validate_init_msg() -> None:
|
|
||||||
...
|
|
||||||
|
|
||||||
|
|
||||||
async def allocate_persistent_feed(
|
async def allocate_persistent_feed(
|
||||||
bus: _FeedsBus,
|
bus: _FeedsBus,
|
||||||
sub_registered: trio.Event,
|
sub_registered: trio.Event,
|
||||||
|
@ -961,7 +945,7 @@ async def allocate_persistent_feed(
|
||||||
loglevel: str,
|
loglevel: str,
|
||||||
start_stream: bool = True,
|
start_stream: bool = True,
|
||||||
|
|
||||||
task_status: TaskStatus[trio.CancelScope] = trio.TASK_STATUS_IGNORED,
|
task_status: TaskStatus[FeedInit] = trio.TASK_STATUS_IGNORED,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
'''
|
'''
|
||||||
|
@ -991,22 +975,37 @@ async def allocate_persistent_feed(
|
||||||
some_data_ready = trio.Event()
|
some_data_ready = trio.Event()
|
||||||
feed_is_live = trio.Event()
|
feed_is_live = trio.Event()
|
||||||
|
|
||||||
symstr = symstr.lower()
|
|
||||||
|
|
||||||
# establish broker backend quote stream by calling
|
# establish broker backend quote stream by calling
|
||||||
# ``stream_quotes()``, which is a required broker backend endpoint.
|
# ``stream_quotes()``, a required broker backend endpoint.
|
||||||
|
init_msgs: (
|
||||||
|
list[FeedInit] # new
|
||||||
|
| dict[str, dict[str, str]] # legacy / deprecated
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO: probably make a struct msg type for this as well
|
||||||
|
# since eventually we do want to have more efficient IPC..
|
||||||
|
first_quote: dict[str, Any]
|
||||||
|
|
||||||
|
symstr = symstr.lower()
|
||||||
(
|
(
|
||||||
init_msg,
|
init_msgs,
|
||||||
first_quote,
|
first_quote,
|
||||||
) = await bus.nursery.start(
|
) = await bus.nursery.start(
|
||||||
partial(
|
partial(
|
||||||
mod.stream_quotes,
|
mod.stream_quotes,
|
||||||
send_chan=send,
|
send_chan=send,
|
||||||
feed_is_live=feed_is_live,
|
feed_is_live=feed_is_live,
|
||||||
|
|
||||||
|
# NOTE / TODO: eventualy we may support providing more then
|
||||||
|
# one input here such that a datad daemon can multiplex
|
||||||
|
# multiple live feeds from one task, instead of getting
|
||||||
|
# a new request (and thus new task) for each subscription.
|
||||||
symbols=[symstr],
|
symbols=[symstr],
|
||||||
|
|
||||||
loglevel=loglevel,
|
loglevel=loglevel,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: this is indexed by symbol for now since we've planned (for
|
# TODO: this is indexed by symbol for now since we've planned (for
|
||||||
# some time) to expect backends to handle single
|
# some time) to expect backends to handle single
|
||||||
# ``.stream_quotes()`` calls with multiple symbols inputs to just
|
# ``.stream_quotes()`` calls with multiple symbols inputs to just
|
||||||
|
@ -1029,58 +1028,15 @@ async def allocate_persistent_feed(
|
||||||
# a small streaming machine around the remote feed which can then
|
# a small streaming machine around the remote feed which can then
|
||||||
# do the normal work of sampling and writing shm buffers
|
# do the normal work of sampling and writing shm buffers
|
||||||
# (depending on if we want sampling done on the far end or not?)
|
# (depending on if we want sampling done on the far end or not?)
|
||||||
per_mkt_init_msg = init_msg[symstr]
|
init: FeedInit = validate_backend(
|
||||||
|
mod,
|
||||||
# the broker-specific fully qualified symbol name,
|
[symstr],
|
||||||
# but ensure it is lower-cased for external use.
|
init_msgs,
|
||||||
bs_mktid = per_mkt_init_msg['fqsn'].lower()
|
)
|
||||||
|
bs_mktid: str = init.bs_mktid
|
||||||
# true fqme including broker/provider suffix
|
mkt: MktPair = init.mkt_info
|
||||||
fqme = '.'.join((bs_mktid, brokername))
|
assert mkt.bs_mktid == bs_mktid
|
||||||
|
fqme: str = mkt.fqme
|
||||||
mktinfo = per_mkt_init_msg.get('mkt_info')
|
|
||||||
if not mktinfo:
|
|
||||||
|
|
||||||
log.warning(
|
|
||||||
f'BACKEND {brokername} is using old `Symbol` style API\n'
|
|
||||||
'IT SHOULD BE PORTED TO THE NEW `.accounting._mktinfo.MktPair`\n'
|
|
||||||
'STATTTTT!!!\n'
|
|
||||||
)
|
|
||||||
mktinfo = per_mkt_init_msg['symbol_info']
|
|
||||||
|
|
||||||
# TODO: read out renamed/new tick size fields in block below!
|
|
||||||
price_tick = mktinfo.get(
|
|
||||||
'price_tick_size',
|
|
||||||
Decimal('0.01'),
|
|
||||||
)
|
|
||||||
size_tick = mktinfo.get(
|
|
||||||
'lot_tick_size',
|
|
||||||
Decimal('0.0'),
|
|
||||||
)
|
|
||||||
|
|
||||||
log.warning(f'FQME: {fqme} -> backend needs port to `MktPair`')
|
|
||||||
mkt = MktPair.from_fqme(
|
|
||||||
fqme,
|
|
||||||
price_tick=price_tick,
|
|
||||||
size_tick=size_tick,
|
|
||||||
bs_mktid=bs_mktid,
|
|
||||||
|
|
||||||
_atype=mktinfo['asset_type']
|
|
||||||
)
|
|
||||||
|
|
||||||
symbol = Symbol.from_fqsn(
|
|
||||||
fqsn=fqme,
|
|
||||||
info=mktinfo,
|
|
||||||
)
|
|
||||||
|
|
||||||
else:
|
|
||||||
# the new msg-protocol is to expect an already packed
|
|
||||||
# ``Asset`` and ``MktPair`` object from the backend
|
|
||||||
symbol = mkt = mktinfo
|
|
||||||
assert isinstance(mkt, MktPair)
|
|
||||||
assert isinstance(mkt.dst, Asset)
|
|
||||||
|
|
||||||
assert mkt.type_key
|
|
||||||
|
|
||||||
# HISTORY storage, run 2 tasks:
|
# HISTORY storage, run 2 tasks:
|
||||||
# - a history loader / maintainer
|
# - a history loader / maintainer
|
||||||
|
@ -1116,7 +1072,7 @@ async def allocate_persistent_feed(
|
||||||
# TODO: we have to use this for now since currently the
|
# TODO: we have to use this for now since currently the
|
||||||
# MktPair above doesn't render the correct output key it seems
|
# MktPair above doesn't render the correct output key it seems
|
||||||
# when we provide the `MktInfo` here?..?
|
# when we provide the `MktInfo` here?..?
|
||||||
mkt=symbol,
|
mkt=mkt,
|
||||||
|
|
||||||
first_quote=first_quote,
|
first_quote=first_quote,
|
||||||
_rt_shm_token=rt_shm.token,
|
_rt_shm_token=rt_shm.token,
|
||||||
|
@ -1125,11 +1081,17 @@ async def allocate_persistent_feed(
|
||||||
izero_rt=izero_rt,
|
izero_rt=izero_rt,
|
||||||
)
|
)
|
||||||
|
|
||||||
# for ambiguous names we simply apply the retreived
|
# for ambiguous names we simply register the
|
||||||
|
# flume for all possible name (sub) sets.
|
||||||
# feed to that name (for now).
|
# feed to that name (for now).
|
||||||
bus.feeds[symstr] = bus.feeds[bs_mktid] = flume
|
bus.feeds.update({
|
||||||
|
symstr: flume,
|
||||||
|
fqme: flume,
|
||||||
|
mkt.bs_fqme: flume,
|
||||||
|
})
|
||||||
|
|
||||||
task_status.started()
|
# signal the ``open_feed_bus()`` caller task to continue
|
||||||
|
task_status.started(init)
|
||||||
|
|
||||||
if not start_stream:
|
if not start_stream:
|
||||||
await trio.sleep_forever()
|
await trio.sleep_forever()
|
||||||
|
@ -1140,9 +1102,7 @@ async def allocate_persistent_feed(
|
||||||
|
|
||||||
# NOTE: if not configured otherwise, we always sum tick volume
|
# NOTE: if not configured otherwise, we always sum tick volume
|
||||||
# values in the OHLCV sampler.
|
# values in the OHLCV sampler.
|
||||||
sum_tick_vlm: bool = init_msg.get(
|
sum_tick_vlm: bool = (init.shm_write_opts or {}).get('sum_tick_vlm', True)
|
||||||
'shm_write_opts', {}
|
|
||||||
).get('sum_tick_vlm', True)
|
|
||||||
|
|
||||||
# NOTE: if no high-freq sampled data has (yet) been loaded,
|
# NOTE: if no high-freq sampled data has (yet) been loaded,
|
||||||
# seed the buffer with a history datum - this is most handy
|
# seed the buffer with a history datum - this is most handy
|
||||||
|
@ -1218,7 +1178,6 @@ async def open_feed_bus(
|
||||||
# ensure we are who we think we are
|
# ensure we are who we think we are
|
||||||
servicename = tractor.current_actor().name
|
servicename = tractor.current_actor().name
|
||||||
assert 'brokerd' in servicename
|
assert 'brokerd' in servicename
|
||||||
|
|
||||||
assert brokername in servicename
|
assert brokername in servicename
|
||||||
|
|
||||||
bus = get_feed_bus(brokername)
|
bus = get_feed_bus(brokername)
|
||||||
|
@ -1573,7 +1532,7 @@ async def open_feed(
|
||||||
feed = Feed()
|
feed = Feed()
|
||||||
|
|
||||||
for fqsn in fqsns:
|
for fqsn in fqsns:
|
||||||
brokername, key, suffix = unpack_fqme(fqsn)
|
brokername, *_ = unpack_fqme(fqsn)
|
||||||
bfqsn = fqsn.replace('.' + brokername, '')
|
bfqsn = fqsn.replace('.' + brokername, '')
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -0,0 +1,201 @@
|
||||||
|
# piker: trading gear for hackers
|
||||||
|
# Copyright (C) Tyler Goodlet (in stewardship for pikers)
|
||||||
|
|
||||||
|
# This program is free software: you can redistribute it and/or modify
|
||||||
|
# it under the terms of the GNU Affero General Public License as published by
|
||||||
|
# the Free Software Foundation, either version 3 of the License, or
|
||||||
|
# (at your option) any later version.
|
||||||
|
|
||||||
|
# This program is distributed in the hope that it will be useful,
|
||||||
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
# GNU Affero General Public License for more details.
|
||||||
|
|
||||||
|
# You should have received a copy of the GNU Affero General Public License
|
||||||
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
'''
|
||||||
|
Data feed synchronization protocols, init msgs, and general
|
||||||
|
data-provider-backend-agnostic schema definitions.
|
||||||
|
|
||||||
|
'''
|
||||||
|
from decimal import Decimal
|
||||||
|
from pprint import pformat
|
||||||
|
from types import ModuleType
|
||||||
|
from typing import (
|
||||||
|
Any,
|
||||||
|
)
|
||||||
|
|
||||||
|
from .types import Struct
|
||||||
|
from ..accounting import (
|
||||||
|
Asset,
|
||||||
|
MktPair,
|
||||||
|
)
|
||||||
|
from ._util import log
|
||||||
|
|
||||||
|
|
||||||
|
class FeedInitializationError(ValueError):
|
||||||
|
'''
|
||||||
|
Live data feed setup failed due to API / msg incompatiblity!
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
class FeedInit(Struct, frozen=True):
|
||||||
|
'''
|
||||||
|
A stringent data provider startup msg schema validator.
|
||||||
|
|
||||||
|
The fields defined here are matched with those absolutely required
|
||||||
|
from each backend broker/data provider.
|
||||||
|
|
||||||
|
'''
|
||||||
|
# backend specific, market endpoint id
|
||||||
|
bs_mktid: str
|
||||||
|
mkt_info: MktPair
|
||||||
|
shm_write_opts: dict[str, Any] | None = None
|
||||||
|
|
||||||
|
|
||||||
|
def validate_backend(
|
||||||
|
mod: ModuleType,
|
||||||
|
syms: list[str],
|
||||||
|
init_msgs: list[FeedInit] | dict[str, dict[str, Any]],
|
||||||
|
|
||||||
|
# TODO: do a module method scan and report mismatches.
|
||||||
|
check_eps: bool = False,
|
||||||
|
|
||||||
|
api_log_msg_level: str = 'critical'
|
||||||
|
|
||||||
|
) -> FeedInit:
|
||||||
|
'''
|
||||||
|
Fail on malformed live quotes feed config/init or warn on changes
|
||||||
|
that haven't been implemented by this backend yet.
|
||||||
|
|
||||||
|
'''
|
||||||
|
if isinstance(init_msgs, dict):
|
||||||
|
for i, (sym_str, msg) in enumerate(init_msgs.items()):
|
||||||
|
init: FeedInit | dict[str, Any] = msg
|
||||||
|
|
||||||
|
# XXX: eventually this WILL NOT necessarily be true.
|
||||||
|
if i > 0:
|
||||||
|
assert not len(init_msgs) == 1
|
||||||
|
keys: set = set(init_msgs.keys()) - set(syms)
|
||||||
|
raise FeedInitializationError(
|
||||||
|
'TOO MANY INIT MSGS!\n'
|
||||||
|
f'Unexpected keys: {keys}\n'
|
||||||
|
'ALL MSGS:\n'
|
||||||
|
f'{pformat(init_msgs)}\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
# TODO: once all backends are updated we can remove this branching.
|
||||||
|
rx_msg: bool = False
|
||||||
|
warn_msg: str = ''
|
||||||
|
if not isinstance(init, FeedInit):
|
||||||
|
warn_msg += (
|
||||||
|
'\n'
|
||||||
|
'--------------------------\n'
|
||||||
|
':::DEPRECATED API STYLE:::\n'
|
||||||
|
'--------------------------\n'
|
||||||
|
f'`{mod.name}.stream_quotes()` should deliver '
|
||||||
|
'`.started(FeedInit)`\n'
|
||||||
|
f'|-> CURRENTLY it is using DEPRECATED `.started(dict)` style!\n'
|
||||||
|
f'|-> SEE `FeedInit` in `piker.data.validate`\n'
|
||||||
|
'--------------------------------------------\n'
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
rx_msg = True
|
||||||
|
|
||||||
|
# verify feed init state / schema
|
||||||
|
bs_mktid: str # backend specific (unique) market id
|
||||||
|
bs_fqme: str # backend specific fqme
|
||||||
|
mkt: MktPair
|
||||||
|
|
||||||
|
match init:
|
||||||
|
case {
|
||||||
|
'symbol_info': dict(symbol_info),
|
||||||
|
'fqsn': bs_fqme,
|
||||||
|
} | {
|
||||||
|
'mkt_info': dict(symbol_info),
|
||||||
|
'fqsn': bs_fqme,
|
||||||
|
}:
|
||||||
|
symbol_info: dict
|
||||||
|
warn_msg += (
|
||||||
|
'It may also be still using the legacy `Symbol` style API\n'
|
||||||
|
'IT SHOULD BE PORTED TO THE NEW '
|
||||||
|
'`.accounting._mktinfo.MktPair`\n'
|
||||||
|
'STATTTTT!!!\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
# XXX use default legacy (aka discrete precision) mkt
|
||||||
|
# price/size_ticks if none delivered.
|
||||||
|
price_tick = symbol_info.get(
|
||||||
|
'price_tick_size',
|
||||||
|
Decimal('0.01'),
|
||||||
|
)
|
||||||
|
size_tick = symbol_info.get(
|
||||||
|
'lot_tick_size',
|
||||||
|
Decimal('1'),
|
||||||
|
)
|
||||||
|
mkt = MktPair.from_fqme(
|
||||||
|
fqme=f'{bs_fqme}.{mod.name}',
|
||||||
|
|
||||||
|
price_tick=price_tick,
|
||||||
|
size_tick=size_tick,
|
||||||
|
|
||||||
|
bs_mktid=str(init['bs_mktid']),
|
||||||
|
_atype=symbol_info['asset_type']
|
||||||
|
)
|
||||||
|
|
||||||
|
case {
|
||||||
|
'mkt_info': MktPair(
|
||||||
|
dst=Asset(),
|
||||||
|
) as mkt,
|
||||||
|
'fqsn': bs_fqme,
|
||||||
|
}:
|
||||||
|
warn_msg += (
|
||||||
|
f'{mod.name} in API compat transition?\n'
|
||||||
|
"It's half dict, half man..\n"
|
||||||
|
'-------------------------------------\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
case FeedInit(
|
||||||
|
# bs_mktid=bs_mktid,
|
||||||
|
mkt_info=MktPair(dst=Asset()) as mkt,
|
||||||
|
shm_write_opts=dict(),
|
||||||
|
) as init:
|
||||||
|
log.info(
|
||||||
|
f'NICE JOB {mod.name} BACKEND!\n'
|
||||||
|
'You are fully up to API spec B):\n'
|
||||||
|
f'{init.to_dict()}'
|
||||||
|
)
|
||||||
|
|
||||||
|
case _:
|
||||||
|
raise FeedInitializationError(init)
|
||||||
|
|
||||||
|
# build a msg if we received a dict for input.
|
||||||
|
if not rx_msg:
|
||||||
|
init = FeedInit(
|
||||||
|
bs_mktid=mkt.bs_mktid,
|
||||||
|
mkt_info=mkt,
|
||||||
|
shm_write_opts=init.get('shm_write_opts'),
|
||||||
|
)
|
||||||
|
|
||||||
|
# `MktPair` value audits
|
||||||
|
mkt = init.mkt_info
|
||||||
|
assert bs_fqme in mkt.fqme
|
||||||
|
assert mkt.type_key
|
||||||
|
|
||||||
|
# `MktPair` wish list
|
||||||
|
if not isinstance(mkt.src, Asset):
|
||||||
|
warn_msg += (
|
||||||
|
f'ALSO, {mod.name.upper()} should try to deliver\n'
|
||||||
|
'the new `MktPair.src: Asset` field!\n'
|
||||||
|
'-----------------------------------------------\n'
|
||||||
|
)
|
||||||
|
|
||||||
|
# complain about any non-idealities
|
||||||
|
if warn_msg:
|
||||||
|
# TODO: would be nice to register an API_COMPAT or something in
|
||||||
|
# maybe cyan for this in general throughput piker no?
|
||||||
|
logmeth = getattr(log, api_log_msg_level)
|
||||||
|
logmeth(warn_msg)
|
||||||
|
|
||||||
|
return init.copy()
|
Loading…
Reference in New Issue