Make `get_cached_feed()` an asynccontextmanager
Adjust feed locking around internal manager `yields` to make this work. Also, change quote publisher to deliver a list of quotes for each retrieved batch. This was actually broken for option streaming since each quote was being overwritten due to a common `key` value for all expiries. Asjust the `packetizer` function accordingly to work for both options and stocks.kivy_mainline_and_py3.8
parent
9b37607b04
commit
e91a50a1ba
|
@ -15,6 +15,7 @@ from operator import itemgetter
|
||||||
|
|
||||||
import trio
|
import trio
|
||||||
import tractor
|
import tractor
|
||||||
|
from async_generator import asynccontextmanager
|
||||||
|
|
||||||
from ..log import get_logger, get_console_log
|
from ..log import get_logger, get_console_log
|
||||||
from . import get_brokermod
|
from . import get_brokermod
|
||||||
|
@ -127,11 +128,16 @@ async def stream_quotes(
|
||||||
log.info(
|
log.info(
|
||||||
f"New quote {quote['symbol']}:\n{new}")
|
f"New quote {quote['symbol']}:\n{new}")
|
||||||
_cache[symbol] = quote
|
_cache[symbol] = quote
|
||||||
new_quotes[quote['key']] = quote
|
# XXX: we append to a list for the options case where the
|
||||||
|
# subscription topic (key) is the same for all
|
||||||
|
# expiries even though this is uncessary for the
|
||||||
|
# stock case (different topic [i.e. symbol] for each
|
||||||
|
# quote).
|
||||||
|
new_quotes.setdefault(quote['key'], []).append(quote)
|
||||||
else:
|
else:
|
||||||
log.info(f"Delivering quotes:\n{quotes}")
|
log.info(f"Delivering quotes:\n{quotes}")
|
||||||
for quote in quotes:
|
for quote in quotes:
|
||||||
new_quotes[quote['symbol']] = quote
|
new_quotes.setdefault(quote['key'], []).append(quote)
|
||||||
|
|
||||||
yield new_quotes
|
yield new_quotes
|
||||||
|
|
||||||
|
@ -153,8 +159,8 @@ async def stream_quotes(
|
||||||
async def symbol_data(broker: str, tickers: List[str]):
|
async def symbol_data(broker: str, tickers: List[str]):
|
||||||
"""Retrieve baseline symbol info from broker.
|
"""Retrieve baseline symbol info from broker.
|
||||||
"""
|
"""
|
||||||
feed = await get_cached_feed(broker)
|
async with get_cached_feed(broker) as feed:
|
||||||
return await feed.client.symbol_data(tickers)
|
return await feed.client.symbol_data(tickers)
|
||||||
|
|
||||||
|
|
||||||
async def smoke_quote(get_quotes, tickers, broker):
|
async def smoke_quote(get_quotes, tickers, broker):
|
||||||
|
@ -193,7 +199,7 @@ async def smoke_quote(get_quotes, tickers, broker):
|
||||||
|
|
||||||
# report any unknown/invalid symbols (QT specific)
|
# report any unknown/invalid symbols (QT specific)
|
||||||
if quote.get('low52w', False) is None:
|
if quote.get('low52w', False) is None:
|
||||||
log.warn(
|
log.error(
|
||||||
f"{symbol} seems to be defunct")
|
f"{symbol} seems to be defunct")
|
||||||
|
|
||||||
payload[symbol] = quote
|
payload[symbol] = quote
|
||||||
|
@ -204,6 +210,7 @@ async def smoke_quote(get_quotes, tickers, broker):
|
||||||
###########################################
|
###########################################
|
||||||
|
|
||||||
|
|
||||||
|
@asynccontextmanager
|
||||||
async def get_cached_feed(
|
async def get_cached_feed(
|
||||||
brokername: str,
|
brokername: str,
|
||||||
) -> BrokerFeed:
|
) -> BrokerFeed:
|
||||||
|
@ -213,24 +220,29 @@ async def get_cached_feed(
|
||||||
ss = tractor.current_actor().statespace
|
ss = tractor.current_actor().statespace
|
||||||
feeds = ss.setdefault('feeds', {'_lock': trio.Lock()})
|
feeds = ss.setdefault('feeds', {'_lock': trio.Lock()})
|
||||||
lock = feeds['_lock']
|
lock = feeds['_lock']
|
||||||
async with lock:
|
try:
|
||||||
try:
|
try:
|
||||||
feed = feeds[brokername]
|
async with lock:
|
||||||
log.info(f"Subscribing with existing `{brokername}` daemon")
|
feed = feeds[brokername]
|
||||||
return feed
|
log.info(f"Subscribing with existing `{brokername}` daemon")
|
||||||
|
yield feed
|
||||||
except KeyError:
|
except KeyError:
|
||||||
log.info(f"Creating new client for broker {brokername}")
|
async with lock:
|
||||||
brokermod = get_brokermod(brokername)
|
log.info(f"Creating new client for broker {brokername}")
|
||||||
exit_stack = contextlib.AsyncExitStack()
|
brokermod = get_brokermod(brokername)
|
||||||
client = await exit_stack.enter_async_context(
|
exit_stack = contextlib.AsyncExitStack()
|
||||||
brokermod.get_client())
|
client = await exit_stack.enter_async_context(
|
||||||
feed = BrokerFeed(
|
brokermod.get_client())
|
||||||
mod=brokermod,
|
feed = BrokerFeed(
|
||||||
client=client,
|
mod=brokermod,
|
||||||
exit_stack=exit_stack,
|
client=client,
|
||||||
)
|
exit_stack=exit_stack,
|
||||||
feeds[brokername] = feed
|
)
|
||||||
return feed
|
feeds[brokername] = feed
|
||||||
|
yield feed
|
||||||
|
finally:
|
||||||
|
# destroy the API client
|
||||||
|
await feed.exit_stack.aclose()
|
||||||
|
|
||||||
|
|
||||||
async def start_quote_stream(
|
async def start_quote_stream(
|
||||||
|
@ -256,40 +268,39 @@ async def start_quote_stream(
|
||||||
log.info(
|
log.info(
|
||||||
f"{ctx.chan.uid} subscribed to {broker} for symbols {symbols}")
|
f"{ctx.chan.uid} subscribed to {broker} for symbols {symbols}")
|
||||||
# another actor task may have already created it
|
# another actor task may have already created it
|
||||||
feed = await get_cached_feed(broker)
|
async with get_cached_feed(broker) as feed:
|
||||||
symbols2ctxs = feed.subscriptions[feed_type]
|
# function to format packets delivered to subscribers
|
||||||
packetizer = None
|
packetizer = None
|
||||||
|
|
||||||
if feed_type == 'stock':
|
if feed_type == 'stock':
|
||||||
get_quotes = feed.quoters.setdefault(
|
get_quotes = feed.quoters.setdefault(
|
||||||
'stock',
|
'stock',
|
||||||
await feed.mod.stock_quoter(feed.client, symbols)
|
await feed.mod.stock_quoter(feed.client, symbols)
|
||||||
)
|
)
|
||||||
# do a smoke quote (note this mutates the input list and filters
|
# do a smoke quote (note this mutates the input list and filters
|
||||||
# out bad symbols for now)
|
# out bad symbols for now)
|
||||||
payload = await smoke_quote(get_quotes, symbols, broker)
|
payload = await smoke_quote(get_quotes, symbols, broker)
|
||||||
|
|
||||||
elif feed_type == 'option':
|
elif feed_type == 'option':
|
||||||
# FIXME: yeah we need maybe a more general way to specify
|
# FIXME: yeah we need maybe a more general way to specify
|
||||||
# the arg signature for the option feed beasides a symbol
|
# the arg signature for the option feed beasides a symbol
|
||||||
# + expiry date.
|
# + expiry date.
|
||||||
get_quotes = feed.quoters.setdefault(
|
get_quotes = feed.quoters.setdefault(
|
||||||
'option',
|
'option',
|
||||||
await feed.mod.option_quoter(feed.client, symbols)
|
await feed.mod.option_quoter(feed.client, symbols)
|
||||||
)
|
)
|
||||||
# packetize
|
# packetize
|
||||||
payload = {
|
payload = {
|
||||||
quote['symbol']: quote
|
quote['symbol']: quote
|
||||||
for quote in await get_quotes(symbols)
|
for quote in await get_quotes(symbols)
|
||||||
}
|
}
|
||||||
|
|
||||||
def packetizer(topic, quote):
|
def packetizer(topic, quotes):
|
||||||
return {quote['symbol']: quote}
|
return {quote['symbol']: quote for quote in quotes}
|
||||||
|
|
||||||
# push initial smoke quote response for client initialization
|
# push initial smoke quote response for client initialization
|
||||||
await ctx.send_yield(payload)
|
await ctx.send_yield(payload)
|
||||||
|
|
||||||
try:
|
|
||||||
await stream_quotes(
|
await stream_quotes(
|
||||||
|
|
||||||
# pub required kwargs
|
# pub required kwargs
|
||||||
|
@ -306,14 +317,6 @@ async def start_quote_stream(
|
||||||
)
|
)
|
||||||
log.info(
|
log.info(
|
||||||
f"Terminating stream quoter task for {feed.mod.name}")
|
f"Terminating stream quoter task for {feed.mod.name}")
|
||||||
finally:
|
|
||||||
# if there are truly no more subscriptions with this broker
|
|
||||||
# drop from broker subs dict
|
|
||||||
if not any(symbols2ctxs.values()):
|
|
||||||
log.info(f"No more subscriptions for broker {broker}")
|
|
||||||
|
|
||||||
# destroy the API client
|
|
||||||
await feed.exit_stack.aclose()
|
|
||||||
|
|
||||||
|
|
||||||
class DataFeed:
|
class DataFeed:
|
||||||
|
@ -377,7 +380,6 @@ class DataFeed:
|
||||||
# get first quotes response
|
# get first quotes response
|
||||||
log.debug(f"Waiting on first quote for {symbols}...")
|
log.debug(f"Waiting on first quote for {symbols}...")
|
||||||
quotes = {}
|
quotes = {}
|
||||||
# with trio.move_on_after(5):
|
|
||||||
quotes = await quote_gen.__anext__()
|
quotes = await quote_gen.__anext__()
|
||||||
|
|
||||||
self.quote_gen = quote_gen
|
self.quote_gen = quote_gen
|
||||||
|
|
Loading…
Reference in New Issue