| 
									
										
										
										
											2018-11-11 23:53:45 +00:00
										 |  |  | """
 | 
					
						
							|  |  |  | Questrade broker testing | 
					
						
							|  |  |  | """
 | 
					
						
							| 
									
										
										
										
											2018-11-13 18:23:05 +00:00
										 |  |  | import time | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import trio | 
					
						
							| 
									
										
										
										
											2018-11-11 23:53:45 +00:00
										 |  |  | from trio.testing import trio_test | 
					
						
							|  |  |  | from piker.brokers import questrade as qt | 
					
						
							| 
									
										
										
										
											2018-11-23 00:12:14 +00:00
										 |  |  | import pytest | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  | import tractor | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-20 20:11:49 +00:00
										 |  |  | import piker | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  | from piker.brokers import get_brokermod | 
					
						
							|  |  |  | from piker.brokers.data import DataFeed | 
					
						
							| 
									
										
										
										
											2018-11-23 00:12:14 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-20 20:11:49 +00:00
										 |  |  | log = piker.log.get_logger('tests') | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | pytestmark = pytest.mark.skipif( | 
					
						
							|  |  |  |     True, | 
					
						
							|  |  |  |     reason="questrade tests can only be run locally with an API key", | 
					
						
							|  |  |  | ) | 
					
						
							| 
									
										
										
										
											2019-02-04 05:16:16 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-28 20:09:27 +00:00
										 |  |  | # TODO: this module was removed from tractor into it's | 
					
						
							|  |  |  | # tests/conftest.py, we need to rewrite the below tests | 
					
						
							|  |  |  | # to use the `open_pikerd_runtime()` to make these work again | 
					
						
							|  |  |  | # (if we're not just gonna junk em). | 
					
						
							|  |  |  | # from tractor.testing import tractor_test | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-04 05:16:16 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:46 +00:00
										 |  |  | # stock quote | 
					
						
							| 
									
										
										
										
											2018-11-30 13:18:54 +00:00
										 |  |  | _ex_quotes = { | 
					
						
							|  |  |  |     'stock': { | 
					
						
							|  |  |  |         "VWAP": 7.383792, | 
					
						
							|  |  |  |         "askPrice": 7.56, | 
					
						
							|  |  |  |         "askSize": 2, | 
					
						
							|  |  |  |         "bidPrice": 6.1, | 
					
						
							|  |  |  |         "bidSize": 2, | 
					
						
							|  |  |  |         "delay": 0, | 
					
						
							|  |  |  |         "high52w": 9.68, | 
					
						
							|  |  |  |         "highPrice": 8, | 
					
						
							|  |  |  |         "key": "EMH.VN", | 
					
						
							|  |  |  |         "isHalted": 'false', | 
					
						
							|  |  |  |         "lastTradePrice": 6.96, | 
					
						
							|  |  |  |         "lastTradePriceTrHrs": 6.97, | 
					
						
							|  |  |  |         "lastTradeSize": 2000, | 
					
						
							|  |  |  |         "lastTradeTick": "Down", | 
					
						
							|  |  |  |         "lastTradeTime": "2018-02-07T15:59:59.259000-05:00", | 
					
						
							|  |  |  |         "low52w": 1.03, | 
					
						
							|  |  |  |         "lowPrice": 6.88, | 
					
						
							|  |  |  |         "openPrice": 7.64, | 
					
						
							|  |  |  |         "symbol": "EMH.VN", | 
					
						
							|  |  |  |         "symbolId": 10164524, | 
					
						
							|  |  |  |         "tier": "", | 
					
						
							|  |  |  |         "volume": 5357805 | 
					
						
							|  |  |  |     }, | 
					
						
							|  |  |  |     'option': { | 
					
						
							|  |  |  |         'VWAP': 0, | 
					
						
							|  |  |  |         'askPrice': None, | 
					
						
							|  |  |  |         'askSize': 0, | 
					
						
							|  |  |  |         'bidPrice': None, | 
					
						
							|  |  |  |         'bidSize': 0, | 
					
						
							| 
									
										
										
										
											2018-12-11 22:10:36 +00:00
										 |  |  |         'contract_type': 'call', | 
					
						
							| 
									
										
										
										
											2018-11-30 13:18:54 +00:00
										 |  |  |         'delay': 0, | 
					
						
							|  |  |  |         'delta': -0.212857, | 
					
						
							| 
									
										
										
										
											2018-12-02 05:37:27 +00:00
										 |  |  |         "expiry": "2021-01-15T00:00:00.000000-05:00", | 
					
						
							| 
									
										
										
										
											2018-11-30 13:18:54 +00:00
										 |  |  |         'gamma': 0.003524, | 
					
						
							|  |  |  |         'highPrice': 0, | 
					
						
							|  |  |  |         'isHalted': False, | 
					
						
							|  |  |  |         "key": ["WEED.TO", '2018-10-23T00:00:00.000000-04:00'], | 
					
						
							|  |  |  |         'lastTradePrice': 22, | 
					
						
							|  |  |  |         'lastTradePriceTrHrs': None, | 
					
						
							|  |  |  |         'lastTradeSize': 0, | 
					
						
							|  |  |  |         'lastTradeTick': 'Equal', | 
					
						
							|  |  |  |         'lastTradeTime': '2018-10-23T00:00:00.000000-04:00', | 
					
						
							|  |  |  |         'lowPrice': 0, | 
					
						
							|  |  |  |         'openInterest': 1, | 
					
						
							|  |  |  |         'openPrice': 0, | 
					
						
							|  |  |  |         'rho': -0.891868, | 
					
						
							| 
									
										
										
										
											2018-12-02 05:37:27 +00:00
										 |  |  |         "strike": 8, | 
					
						
							| 
									
										
										
										
											2018-11-30 13:18:54 +00:00
										 |  |  |         'symbol': 'WEED15Jan21P54.00.MX', | 
					
						
							|  |  |  |         'symbolId': 22739148, | 
					
						
							|  |  |  |         'theta': -0.012911, | 
					
						
							|  |  |  |         'underlying': 'WEED.TO', | 
					
						
							|  |  |  |         'underlyingId': 16529510, | 
					
						
							|  |  |  |         'vega': 0.220885, | 
					
						
							|  |  |  |         'volatility': 75.514171, | 
					
						
							|  |  |  |         'volume': 0 | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2018-11-11 23:53:45 +00:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-30 13:18:54 +00:00
										 |  |  | def match_packet(symbols, quotes, feed_type='stock'): | 
					
						
							| 
									
										
										
										
											2018-11-11 23:53:45 +00:00
										 |  |  |     """Verify target ``symbols`` match keys in ``quotes`` packet.
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     assert len(quotes) == len(symbols) | 
					
						
							| 
									
										
										
										
											2018-11-30 13:18:54 +00:00
										 |  |  |     # for ticker in symbols: | 
					
						
							|  |  |  |     for quote in quotes.copy(): | 
					
						
							|  |  |  |         assert quote['key'] in symbols | 
					
						
							|  |  |  |         quotes.remove(quote) | 
					
						
							| 
									
										
										
										
											2018-11-11 23:53:45 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # verify the quote packet format hasn't changed | 
					
						
							| 
									
										
										
										
											2018-11-30 13:18:54 +00:00
										 |  |  |         for key in _ex_quotes[feed_type]: | 
					
						
							| 
									
										
										
										
											2018-11-11 23:53:45 +00:00
										 |  |  |             quote.pop(key) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # no additional fields either | 
					
						
							|  |  |  |         assert not quote | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     # not more quotes then in target set | 
					
						
							|  |  |  |     assert not quotes | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-21 14:35:38 +00:00
										 |  |  | @pytest.fixture | 
					
						
							|  |  |  | def us_symbols(): | 
					
						
							|  |  |  |     return ['TSLA', 'AAPL', 'CGC', 'CRON'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @pytest.fixture | 
					
						
							|  |  |  | def tmx_symbols(): | 
					
						
							|  |  |  |     return ['APHA.TO', 'WEED.TO', 'ACB.TO'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @pytest.fixture | 
					
						
							|  |  |  | def cse_symbols(): | 
					
						
							|  |  |  |     return ['TRUL.CN', 'CWEB.CN', 'SNN.CN'] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-10-28 20:09:27 +00:00
										 |  |  | # @tractor_test | 
					
						
							| 
									
										
										
										
											2019-02-04 05:16:16 +00:00
										 |  |  | async def test_concurrent_tokens_refresh(us_symbols, loglevel): | 
					
						
							| 
									
										
										
										
											2019-02-10 02:58:49 +00:00
										 |  |  |     """Verify that concurrent requests from mulitple tasks work alongside
 | 
					
						
							|  |  |  |     random token refreshing which simulates an access token expiry + refresh | 
					
						
							|  |  |  |     scenario. | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     The API does not support concurrent requests when refreshing tokens | 
					
						
							|  |  |  |     (i.e. when hitting the auth endpoint). This tests ensures that when | 
					
						
							|  |  |  |     multiple tasks use the same client concurrency works and access | 
					
						
							|  |  |  |     token expiry will result in a reliable token set update. | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2019-02-04 05:16:16 +00:00
										 |  |  |     async with qt.get_client() as client: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         # async with tractor.open_nursery() as n: | 
					
						
							|  |  |  |         #     await n.run_in_actor('other', intermittently_refresh_tokens) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         async with trio.open_nursery() as n: | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             quoter = await qt.stock_quoter(client, us_symbols) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             async def get_quotes(): | 
					
						
							| 
									
										
										
										
											2019-02-26 03:40:19 +00:00
										 |  |  |                 for tries in range(15): | 
					
						
							| 
									
										
										
										
											2019-02-04 05:16:16 +00:00
										 |  |  |                     log.info(f"{tries}: GETTING QUOTES!") | 
					
						
							|  |  |  |                     quotes = await quoter(us_symbols) | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  |                     assert quotes | 
					
						
							| 
									
										
										
										
											2019-02-26 03:40:19 +00:00
										 |  |  |                     await trio.sleep(0.2) | 
					
						
							| 
									
										
										
										
											2019-02-04 05:16:16 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-10 02:58:49 +00:00
										 |  |  |             async def intermittently_refresh_tokens(client): | 
					
						
							|  |  |  |                 while True: | 
					
						
							|  |  |  |                     try: | 
					
						
							| 
									
										
										
										
											2019-02-26 01:23:20 +00:00
										 |  |  |                         await client.ensure_access( | 
					
						
							|  |  |  |                             force_refresh=True, ask_user=False) | 
					
						
							| 
									
										
										
										
											2019-02-10 02:58:49 +00:00
										 |  |  |                         log.info(f"last token data is {client.access_data}") | 
					
						
							|  |  |  |                         await trio.sleep(1) | 
					
						
							|  |  |  |                     except Exception: | 
					
						
							|  |  |  |                         log.exception("Token refresh failed") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             n.start_soon(intermittently_refresh_tokens, client) | 
					
						
							|  |  |  |             # run 2 quote polling tasks | 
					
						
							|  |  |  |             n.start_soon(get_quotes) | 
					
						
							| 
									
										
										
										
											2019-02-04 05:16:16 +00:00
										 |  |  |             await get_quotes() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |             # shutdown | 
					
						
							|  |  |  |             # await n.cancel() | 
					
						
							|  |  |  |             n.cancel_scope.cancel() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-11-11 23:53:45 +00:00
										 |  |  | @trio_test | 
					
						
							|  |  |  | async def test_batched_stock_quote(us_symbols): | 
					
						
							|  |  |  |     """Use the client stock quote api and verify quote response format.
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     async with qt.get_client() as client: | 
					
						
							|  |  |  |         quotes = await client.quote(us_symbols) | 
					
						
							|  |  |  |         assert len(quotes) == len(us_symbols) | 
					
						
							|  |  |  |         match_packet(us_symbols, quotes) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @trio_test | 
					
						
							| 
									
										
										
										
											2018-11-30 13:18:54 +00:00
										 |  |  | async def test_stock_quoter_context(us_symbols): | 
					
						
							| 
									
										
										
										
											2018-11-11 23:53:45 +00:00
										 |  |  |     """Test that a quoter "context" used by the data feed daemon.
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     async with qt.get_client() as client: | 
					
						
							| 
									
										
										
										
											2018-11-30 13:18:54 +00:00
										 |  |  |         quoter = await qt.stock_quoter(client, us_symbols) | 
					
						
							| 
									
										
										
										
											2018-11-11 23:53:45 +00:00
										 |  |  |         quotes = await quoter(us_symbols) | 
					
						
							|  |  |  |         match_packet(us_symbols, quotes) | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @trio_test | 
					
						
							|  |  |  | async def test_option_contracts(tmx_symbols): | 
					
						
							|  |  |  |     """Verify we can retrieve contracts by expiry.
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     async with qt.get_client() as client: | 
					
						
							|  |  |  |         for symbol in tmx_symbols: | 
					
						
							| 
									
										
										
										
											2018-11-30 13:18:54 +00:00
										 |  |  |             contracts = await client.symbol2contracts(symbol) | 
					
						
							|  |  |  |             key, byroot = next(iter(contracts.items())) | 
					
						
							|  |  |  |             assert isinstance(key.id, int) | 
					
						
							|  |  |  |             assert isinstance(byroot, dict) | 
					
						
							|  |  |  |             for key in contracts: | 
					
						
							|  |  |  |                 # check that datetime is same as reported in contract | 
					
						
							|  |  |  |                 assert key.expiry.isoformat( | 
					
						
							|  |  |  |                     timespec='microseconds') == contracts[key]['expiryDate'] | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:46 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @trio_test | 
					
						
							|  |  |  | async def test_option_chain(tmx_symbols): | 
					
						
							|  |  |  |     """Verify we can retrieve all option chains for a list of symbols.
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     async with qt.get_client() as client: | 
					
						
							|  |  |  |         # contract lookup - should be cached | 
					
						
							| 
									
										
										
										
											2018-11-30 13:18:54 +00:00
										 |  |  |         contracts = await client.get_all_contracts([tmx_symbols[0]]) | 
					
						
							| 
									
										
										
										
											2018-11-12 02:06:46 +00:00
										 |  |  |         # chains quote for all symbols | 
					
						
							| 
									
										
										
										
											2018-11-13 17:57:46 +00:00
										 |  |  |         quotes = await client.option_chains(contracts) | 
					
						
							| 
									
										
										
										
											2018-11-30 13:18:54 +00:00
										 |  |  |         # verify contents match what we expect | 
					
						
							|  |  |  |         for quote in quotes: | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  |             underlying = quote['underlying'] | 
					
						
							|  |  |  |             # XXX: sometimes it's '' for old expiries? | 
					
						
							|  |  |  |             if underlying: | 
					
						
							|  |  |  |                 assert underlying in tmx_symbols | 
					
						
							| 
									
										
										
										
											2018-11-30 13:18:54 +00:00
										 |  |  |             for key in _ex_quotes['option']: | 
					
						
							|  |  |  |                 quote.pop(key) | 
					
						
							|  |  |  |             assert not quote | 
					
						
							| 
									
										
										
										
											2018-11-13 18:23:05 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @trio_test | 
					
						
							|  |  |  | async def test_option_quote_latency(tmx_symbols): | 
					
						
							|  |  |  |     """Audit option quote latencies.
 | 
					
						
							|  |  |  |     """
 | 
					
						
							|  |  |  |     async with qt.get_client() as client: | 
					
						
							|  |  |  |         # all contracts lookup - should be cached | 
					
						
							| 
									
										
										
										
											2018-11-22 14:44:47 +00:00
										 |  |  |         contracts = await client.get_all_contracts(['WEED.TO']) | 
					
						
							| 
									
										
										
										
											2018-11-13 18:23:05 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # build single expriry contract | 
					
						
							|  |  |  |         id, by_expiry = next(iter(contracts.items())) | 
					
						
							|  |  |  |         dt, by_strike = next(iter(by_expiry.items())) | 
					
						
							| 
									
										
										
										
											2018-11-22 14:44:47 +00:00
										 |  |  |         single = {id: {dt: None}} | 
					
						
							| 
									
										
										
										
											2018-11-13 18:23:05 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         for expected_latency, contract in [ | 
					
						
							| 
									
										
										
										
											2018-11-22 14:44:47 +00:00
										 |  |  |             # NOTE: request latency is usually 2x faster that these | 
					
						
							|  |  |  |             (5, contracts), (0.5, single) | 
					
						
							| 
									
										
										
										
											2018-11-13 18:23:05 +00:00
										 |  |  |         ]: | 
					
						
							| 
									
										
										
										
											2018-11-30 13:18:54 +00:00
										 |  |  |             for _ in range(3): | 
					
						
							| 
									
										
										
										
											2018-11-13 18:23:05 +00:00
										 |  |  |                 # chains quote for all symbols | 
					
						
							|  |  |  |                 start = time.time() | 
					
						
							| 
									
										
										
										
											2018-11-23 00:12:14 +00:00
										 |  |  |                 await client.option_chains(contract) | 
					
						
							| 
									
										
										
										
											2018-11-13 18:23:05 +00:00
										 |  |  |                 took = time.time() - start | 
					
						
							|  |  |  |                 print(f"Request took {took}") | 
					
						
							|  |  |  |                 assert took <= expected_latency | 
					
						
							|  |  |  |                 await trio.sleep(0.1) | 
					
						
							| 
									
										
										
										
											2018-11-30 13:18:54 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  | async def stream_option_chain(feed, symbols): | 
					
						
							| 
									
										
										
										
											2018-12-01 21:14:33 +00:00
										 |  |  |     """Start up an option quote stream.
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ``symbols`` arg is ignored here. | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2018-12-24 02:27:47 +00:00
										 |  |  |     symbol = symbols[0] | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  |     contracts = await feed.call_client( | 
					
						
							|  |  |  |         'get_all_contracts', symbols=[symbol]) | 
					
						
							| 
									
										
										
										
											2018-12-01 21:14:33 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |     contractkey = next(iter(contracts)) | 
					
						
							|  |  |  |     subs_keys = list( | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  |         # map(lambda item: (item.symbol, item.expiry), contracts)) | 
					
						
							|  |  |  |         map(lambda item: (item[0], item[2]), contracts)) | 
					
						
							| 
									
										
										
										
											2018-12-01 21:14:33 +00:00
										 |  |  |     sub = subs_keys[0] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-24 02:27:47 +00:00
										 |  |  |     # latency arithmetic | 
					
						
							|  |  |  |     loops = 8 | 
					
						
							| 
									
										
										
										
											2023-02-21 14:35:38 +00:00
										 |  |  |     # period = 1/3.   # 3 rps | 
					
						
							|  |  |  |     timeout = float('inf')  # loops / period | 
					
						
							| 
									
										
										
										
											2018-12-24 02:27:47 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-01 21:14:33 +00:00
										 |  |  |     try: | 
					
						
							|  |  |  |         # it'd sure be nice to have an asyncitertools here... | 
					
						
							| 
									
										
										
										
											2018-12-24 02:27:47 +00:00
										 |  |  |         with trio.fail_after(timeout): | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  |             stream, first_quotes = await feed.open_stream( | 
					
						
							|  |  |  |                 [sub], 'option', rate=4, diff_cached=False, | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2018-12-01 21:14:33 +00:00
										 |  |  |             count = 0 | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  |             async for quotes in stream: | 
					
						
							| 
									
										
										
										
											2018-12-01 21:14:33 +00:00
										 |  |  |                 # print(f'got quotes for {quotes.keys()}') | 
					
						
							|  |  |  |                 # we should receive all calls and puts | 
					
						
							|  |  |  |                 assert len(quotes) == len(contracts[contractkey]) * 2 | 
					
						
							|  |  |  |                 for symbol, quote in quotes.items(): | 
					
						
							|  |  |  |                     assert quote['key'] == sub | 
					
						
							|  |  |  |                     for key in _ex_quotes['option']: | 
					
						
							|  |  |  |                         quote.pop(key) | 
					
						
							|  |  |  |                     assert not quote | 
					
						
							|  |  |  |                 count += 1 | 
					
						
							|  |  |  |                 if count == loops: | 
					
						
							|  |  |  |                     break | 
					
						
							| 
									
										
										
										
											2018-12-11 22:10:36 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  |         # switch the subscription and make sure | 
					
						
							|  |  |  |         # stream is still working | 
					
						
							|  |  |  |         sub = subs_keys[1] | 
					
						
							| 
									
										
										
										
											2018-12-24 02:27:47 +00:00
										 |  |  |         with trio.fail_after(timeout): | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  |             stream, first_quotes = await feed.open_stream( | 
					
						
							|  |  |  |                 [sub], 'option', rate=4, diff_cached=False, | 
					
						
							|  |  |  |             ) | 
					
						
							| 
									
										
										
										
											2018-12-11 22:10:36 +00:00
										 |  |  |             count = 0 | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  |             async for quotes in stream: | 
					
						
							| 
									
										
										
										
											2018-12-11 22:10:36 +00:00
										 |  |  |                 for symbol, quote in quotes.items(): | 
					
						
							|  |  |  |                     assert quote['key'] == sub | 
					
						
							|  |  |  |                 count += 1 | 
					
						
							|  |  |  |                 if count == loops: | 
					
						
							|  |  |  |                     break | 
					
						
							|  |  |  |     finally: | 
					
						
							|  |  |  |         # unsub | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  |         await stream.aclose() | 
					
						
							| 
									
										
										
										
											2018-12-11 22:10:36 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-01 21:14:33 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  | async def stream_stocks(feed, symbols): | 
					
						
							| 
									
										
										
										
											2018-12-01 21:14:33 +00:00
										 |  |  |     """Start up a stock quote stream.
 | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  |     stream, first_quotes = await feed.open_stream( | 
					
						
							|  |  |  |         symbols, 'stock', rate=3, diff_cached=False) | 
					
						
							|  |  |  |     # latency arithmetic | 
					
						
							|  |  |  |     loops = 8 | 
					
						
							| 
									
										
										
										
											2023-02-21 14:35:38 +00:00
										 |  |  |     # period = 1/3.   # 3 rps | 
					
						
							|  |  |  |     # timeout = loops / period | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-01 21:14:33 +00:00
										 |  |  |     try: | 
					
						
							|  |  |  |         # it'd sure be nice to have an asyncitertools here... | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  |         count = 0 | 
					
						
							|  |  |  |         async for quotes in stream: | 
					
						
							| 
									
										
										
										
											2018-12-01 21:14:33 +00:00
										 |  |  |             assert quotes | 
					
						
							|  |  |  |             for key in quotes: | 
					
						
							|  |  |  |                 assert key in symbols | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  |             count += 1 | 
					
						
							|  |  |  |             if count == loops: | 
					
						
							|  |  |  |                 break | 
					
						
							| 
									
										
										
										
											2018-12-01 21:14:33 +00:00
										 |  |  |     finally: | 
					
						
							|  |  |  |         # unsub | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  |         await stream.aclose() | 
					
						
							| 
									
										
										
										
											2018-12-01 21:14:33 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | @pytest.mark.parametrize( | 
					
						
							|  |  |  |     'stream_what', | 
					
						
							|  |  |  |     [ | 
					
						
							|  |  |  |         (stream_stocks,), | 
					
						
							|  |  |  |         (stream_option_chain,), | 
					
						
							|  |  |  |         (stream_stocks, stream_option_chain), | 
					
						
							| 
									
										
										
										
											2018-12-24 02:27:47 +00:00
										 |  |  |         (stream_stocks, stream_stocks), | 
					
						
							|  |  |  |         (stream_option_chain, stream_option_chain), | 
					
						
							|  |  |  |     ], | 
					
						
							|  |  |  |     ids=[ | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  |         'stocks', | 
					
						
							|  |  |  |         'options', | 
					
						
							|  |  |  |         'stocks_and_options', | 
					
						
							|  |  |  |         'stocks_and_stocks', | 
					
						
							| 
									
										
										
										
											2018-12-24 02:27:47 +00:00
										 |  |  |         'options_and_options', | 
					
						
							| 
									
										
										
										
											2018-12-01 21:14:33 +00:00
										 |  |  |     ], | 
					
						
							|  |  |  | ) | 
					
						
							| 
									
										
										
										
											2022-10-28 20:09:27 +00:00
										 |  |  | # @tractor_test | 
					
						
							| 
									
										
										
										
											2018-12-01 21:14:33 +00:00
										 |  |  | async def test_quote_streaming(tmx_symbols, loglevel, stream_what): | 
					
						
							| 
									
										
										
										
											2018-11-30 13:18:54 +00:00
										 |  |  |     """Set up option streaming using the broker daemon.
 | 
					
						
							|  |  |  |     """
 | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  |     brokermod = get_brokermod('questrade') | 
					
						
							| 
									
										
										
										
											2018-11-30 13:18:54 +00:00
										 |  |  |     async with tractor.find_actor('brokerd') as portal: | 
					
						
							|  |  |  |         async with tractor.open_nursery() as nursery: | 
					
						
							|  |  |  |             # only one per host address, spawns an actor if None | 
					
						
							|  |  |  |             if not portal: | 
					
						
							|  |  |  |                 # no brokerd actor found | 
					
						
							|  |  |  |                 portal = await nursery.start_actor( | 
					
						
							|  |  |  |                     'data_feed', | 
					
						
							|  |  |  |                     rpc_module_paths=[ | 
					
						
							|  |  |  |                         'piker.brokers.data', | 
					
						
							|  |  |  |                         'piker.brokers.core' | 
					
						
							|  |  |  |                     ], | 
					
						
							|  |  |  |                 ) | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  |             feed = DataFeed(portal, brokermod) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-24 02:27:47 +00:00
										 |  |  |             if len(stream_what) > 1: | 
					
						
							|  |  |  |                 # stream disparate symbol sets per task | 
					
						
							|  |  |  |                 first, *tail = tmx_symbols | 
					
						
							|  |  |  |                 symbols = ([first], tail) | 
					
						
							|  |  |  |             else: | 
					
						
							|  |  |  |                 symbols = [tmx_symbols] | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-11 22:10:36 +00:00
										 |  |  |             async with trio.open_nursery() as n: | 
					
						
							| 
									
										
										
										
											2018-12-24 02:27:47 +00:00
										 |  |  |                 for syms, func in zip(symbols, stream_what): | 
					
						
							| 
									
										
										
										
											2019-02-22 04:10:24 +00:00
										 |  |  |                     n.start_soon(func, feed, syms) | 
					
						
							| 
									
										
										
										
											2018-11-30 13:18:54 +00:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2018-12-01 21:14:33 +00:00
										 |  |  |             # stop all spawned subactors | 
					
						
							|  |  |  |             await nursery.cancel() |