Add multi ingestor support and update to new feed API
							parent
							
								
									e2ce341f93
								
							
						
					
					
						commit
						5d539b7c49
					
				| 
						 | 
					@ -136,7 +136,7 @@ def ingest(config, name, test_file, tl, url):
 | 
				
			||||||
    """Ingest real-time broker quotes and ticks to a marketstore instance.
 | 
					    """Ingest real-time broker quotes and ticks to a marketstore instance.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    # global opts
 | 
					    # global opts
 | 
				
			||||||
    brokermod = config['brokermod']
 | 
					    brokermods = config['brokermods']
 | 
				
			||||||
    loglevel = config['loglevel']
 | 
					    loglevel = config['loglevel']
 | 
				
			||||||
    tractorloglevel = config['tractorloglevel']
 | 
					    tractorloglevel = config['tractorloglevel']
 | 
				
			||||||
    # log = config['log']
 | 
					    # log = config['log']
 | 
				
			||||||
| 
						 | 
					@ -145,15 +145,25 @@ def ingest(config, name, test_file, tl, url):
 | 
				
			||||||
    watchlists = wl.merge_watchlist(watchlist_from_file, wl._builtins)
 | 
					    watchlists = wl.merge_watchlist(watchlist_from_file, wl._builtins)
 | 
				
			||||||
    symbols = watchlists[name]
 | 
					    symbols = watchlists[name]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    tractor.run(
 | 
					    grouped_syms = {}
 | 
				
			||||||
        partial(
 | 
					    for sym in symbols:
 | 
				
			||||||
            ingest_quote_stream,
 | 
					        symbol, _, provider = sym.rpartition('.')
 | 
				
			||||||
            symbols,
 | 
					        if provider not in grouped_syms:
 | 
				
			||||||
            brokermod.name,
 | 
					            grouped_syms[provider] = []
 | 
				
			||||||
            tries=1,
 | 
					
 | 
				
			||||||
            loglevel=loglevel,
 | 
					        grouped_syms[provider].append(symbol)
 | 
				
			||||||
        ),
 | 
					
 | 
				
			||||||
        name='ingest_marketstore',
 | 
					    async def entry_point():
 | 
				
			||||||
        loglevel=tractorloglevel,
 | 
					        async with tractor.open_nursery() as n:
 | 
				
			||||||
        debug_mode=True,
 | 
					            for provider, symbols in grouped_syms.items(): 
 | 
				
			||||||
    )
 | 
					                await n.run_in_actor(
 | 
				
			||||||
 | 
					                    ingest_quote_stream,
 | 
				
			||||||
 | 
					                    name='ingest_marketstore',
 | 
				
			||||||
 | 
					                    symbols=symbols,
 | 
				
			||||||
 | 
					                    brokername=provider,
 | 
				
			||||||
 | 
					                    tries=1,
 | 
				
			||||||
 | 
					                    actorloglevel=loglevel,
 | 
				
			||||||
 | 
					                    loglevel=tractorloglevel
 | 
				
			||||||
 | 
					                )
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    tractor.run(entry_point)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -24,7 +24,7 @@
 | 
				
			||||||
- todo: docker container management automation
 | 
					- todo: docker container management automation
 | 
				
			||||||
"""
 | 
					"""
 | 
				
			||||||
from contextlib import asynccontextmanager
 | 
					from contextlib import asynccontextmanager
 | 
				
			||||||
from typing import Dict, Any, List, Callable, Tuple
 | 
					from typing import Dict, Any, List, Callable, Tuple, Optional
 | 
				
			||||||
import time
 | 
					import time
 | 
				
			||||||
from math import isnan
 | 
					from math import isnan
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -49,25 +49,16 @@ _quote_dt = [
 | 
				
			||||||
    ('Epoch', 'i8'),
 | 
					    ('Epoch', 'i8'),
 | 
				
			||||||
    ('Nanoseconds', 'i4'),
 | 
					    ('Nanoseconds', 'i4'),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    ('Tick', 'i4'),  # (-1, 0, 1) = (on bid, same, on ask)
 | 
					    ('Tick', 'i4'),
 | 
				
			||||||
    # ('fill_time', 'f4'),
 | 
					 | 
				
			||||||
    ('Last', 'f4'),
 | 
					    ('Last', 'f4'),
 | 
				
			||||||
    ('Bid', 'f4'),
 | 
					    ('Bid', 'f4'),
 | 
				
			||||||
    ('Bsize', 'i8'),
 | 
					    ('Bsize', 'f4'),
 | 
				
			||||||
    ('Asize', 'i8'),
 | 
					    ('Asize', 'f4'),
 | 
				
			||||||
    ('Ask', 'f4'),
 | 
					    ('Ask', 'f4'),
 | 
				
			||||||
    ('Size', 'i8'),
 | 
					    ('Size', 'i8'),
 | 
				
			||||||
    ('Volume', 'i8'),
 | 
					    ('Volume', 'f4'),
 | 
				
			||||||
    # ('brokerd_ts', 'i64'),
 | 
					 | 
				
			||||||
    # ('VWAP', 'f4')
 | 
					 | 
				
			||||||
]
 | 
					]
 | 
				
			||||||
_quote_tmp = {}.fromkeys(dict(_quote_dt).keys(), np.nan)
 | 
					_quote_tmp = {}.fromkeys(dict(_quote_dt).keys(), np.nan)
 | 
				
			||||||
_tick_map = {
 | 
					 | 
				
			||||||
    'Up': 1,
 | 
					 | 
				
			||||||
    'Equal': 0,
 | 
					 | 
				
			||||||
    'Down': -1,
 | 
					 | 
				
			||||||
    None: np.nan,
 | 
					 | 
				
			||||||
}
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class MarketStoreError(Exception):
 | 
					class MarketStoreError(Exception):
 | 
				
			||||||
| 
						 | 
					@ -87,18 +78,20 @@ def err_on_resp(response: dict) -> None:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def quote_to_marketstore_structarray(
 | 
					def quote_to_marketstore_structarray(
 | 
				
			||||||
    quote: Dict[str, Any],
 | 
					    quote: Dict[str, Any],
 | 
				
			||||||
    last_fill: str,
 | 
					    last_fill: Optional[float],
 | 
				
			||||||
 | 
					
 | 
				
			||||||
) -> np.array:
 | 
					) -> np.array:
 | 
				
			||||||
    """Return marketstore writeable structarray from quote ``dict``.
 | 
					    """Return marketstore writeable structarray from quote ``dict``.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    if last_fill:
 | 
					    if last_fill:
 | 
				
			||||||
        # new fill bby
 | 
					        # new fill bby
 | 
				
			||||||
        now = timestamp(last_fill)
 | 
					        now = timestamp(last_fill, unit='s')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    else:
 | 
					    else:
 | 
				
			||||||
        # this should get inserted upstream by the broker-client to
 | 
					        # this should get inserted upstream by the broker-client to
 | 
				
			||||||
        # subtract from IPC latency
 | 
					        # subtract from IPC latency
 | 
				
			||||||
        now = time.time_ns()
 | 
					        now = time.time_ns()
 | 
				
			||||||
 | 
					        
 | 
				
			||||||
    secs, ns = now / 10**9, now % 10**9
 | 
					    secs, ns = now / 10**9, now % 10**9
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # pack into List[Tuple[str, Any]]
 | 
					    # pack into List[Tuple[str, Any]]
 | 
				
			||||||
| 
						 | 
					@ -123,11 +116,11 @@ def quote_to_marketstore_structarray(
 | 
				
			||||||
    return np.array([tuple(array_input)], dtype=_quote_dt)
 | 
					    return np.array([tuple(array_input)], dtype=_quote_dt)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def timestamp(datestr: str) -> int:
 | 
					def timestamp(date, **kwargs) -> int:
 | 
				
			||||||
    """Return marketstore compatible 'Epoch' integer in nanoseconds
 | 
					    """Return marketstore compatible 'Epoch' integer in nanoseconds
 | 
				
			||||||
    from a date formatted str.
 | 
					    from a date formatted str.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    return int(pd.Timestamp(datestr).value)
 | 
					    return int(pd.Timestamp(date, **kwargs).value)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
def mk_tbk(keys: Tuple[str, str, str]) -> str:
 | 
					def mk_tbk(keys: Tuple[str, str, str]) -> str:
 | 
				
			||||||
| 
						 | 
					@ -206,46 +199,71 @@ async def ingest_quote_stream(
 | 
				
			||||||
    symbols: List[str],
 | 
					    symbols: List[str],
 | 
				
			||||||
    brokername: str,
 | 
					    brokername: str,
 | 
				
			||||||
    tries: int = 1,
 | 
					    tries: int = 1,
 | 
				
			||||||
    loglevel: str = None,
 | 
					    actorloglevel: str = None,
 | 
				
			||||||
) -> None:
 | 
					) -> None:
 | 
				
			||||||
    """Ingest a broker quote stream into marketstore in (sampled) tick format.
 | 
					    """Ingest a broker quote stream into marketstore in (sampled) tick format.
 | 
				
			||||||
    """
 | 
					    """
 | 
				
			||||||
    async with open_feed(
 | 
					    async with open_feed(
 | 
				
			||||||
        brokername,
 | 
					        brokername,
 | 
				
			||||||
        symbols,
 | 
					        symbols,
 | 
				
			||||||
        loglevel=loglevel,
 | 
					        loglevel=actorloglevel,
 | 
				
			||||||
    ) as (first_quotes, qstream):
 | 
					    ) as feed:
 | 
				
			||||||
 | 
					 | 
				
			||||||
        quote_cache = first_quotes.copy()
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        async with get_client() as ms_client:
 | 
					        async with get_client() as ms_client:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            # _quote_dt = [
 | 
				
			||||||
 | 
					            #     # these two are required for as a "primary key"
 | 
				
			||||||
 | 
					            #    ('Epoch', 'i8'),
 | 
				
			||||||
 | 
					            #    ('Nanoseconds', 'i4'),
 | 
				
			||||||
 | 
					            #    ('Tick', 'i4'),
 | 
				
			||||||
 | 
					            #
 | 
				
			||||||
 | 
					            #    ('Last', 'f4'),
 | 
				
			||||||
 | 
					            #    ('Bid', 'f4'),
 | 
				
			||||||
 | 
					            #    ('Bsize', 'f4'),
 | 
				
			||||||
 | 
					            #    ('Asize', 'f4'),
 | 
				
			||||||
 | 
					            #    ('Ask', 'f4'),
 | 
				
			||||||
 | 
					            #    ('Size', 'i8'),
 | 
				
			||||||
 | 
					            #    ('Volume', 'f4'),
 | 
				
			||||||
 | 
					            # ]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					            quote_cache = {
 | 
				
			||||||
 | 
					                'size': 0,
 | 
				
			||||||
 | 
					                'tick': 0
 | 
				
			||||||
 | 
					            }
 | 
				
			||||||
            # start ingest to marketstore
 | 
					            # start ingest to marketstore
 | 
				
			||||||
            async for quotes in qstream:
 | 
					            async for quotes in feed.stream:
 | 
				
			||||||
                log.info(quotes)
 | 
					                log.info(quotes)
 | 
				
			||||||
                for symbol, quote in quotes.items():
 | 
					                for symbol, quote in quotes.items():
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    # remap tick strs to ints
 | 
					                    for tick in quote.get('ticks', ()):
 | 
				
			||||||
                    quote['tick'] = _tick_map[quote.get('tick', 'Equal')]
 | 
					                        ticktype = tick.get('type')
 | 
				
			||||||
 | 
					                        price = tick.get('price')
 | 
				
			||||||
 | 
					                        size = tick.get('size')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    # check for volume update (i.e. did trades happen
 | 
					                        if ticktype == 'n/a' or price == -1:
 | 
				
			||||||
                    # since last quote)
 | 
					                            # okkk..
 | 
				
			||||||
                    new_vol = quote.get('volume', None)
 | 
					                            continue
 | 
				
			||||||
                    if new_vol is None:
 | 
					 | 
				
			||||||
                        log.debug(f"No fills for {symbol}")
 | 
					 | 
				
			||||||
                        if new_vol == quote_cache.get('volume'):
 | 
					 | 
				
			||||||
                            # should never happen due to field diffing
 | 
					 | 
				
			||||||
                            # on sender side
 | 
					 | 
				
			||||||
                            log.error(
 | 
					 | 
				
			||||||
                                f"{symbol}: got same volume as last quote?")
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    quote_cache.update(quote)
 | 
					                        # clearing price event
 | 
				
			||||||
 | 
					                        if ticktype == 'trade':
 | 
				
			||||||
 | 
					                            quote_cache['volume'] = quote['volume']
 | 
				
			||||||
 | 
					                            quote_cache['last'] = price
 | 
				
			||||||
 | 
					                            # quote_cache['broker_ts'] = quote['broker_ts']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        # l1 book events
 | 
				
			||||||
 | 
					                        elif ticktype in ('ask', 'asize'):
 | 
				
			||||||
 | 
					                            quote_cache['ask'] = price
 | 
				
			||||||
 | 
					                            quote_cache['asize'] = size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					                        elif ticktype in ('bid', 'bsize'):
 | 
				
			||||||
 | 
					                            quote_cache['bid'] = price
 | 
				
			||||||
 | 
					                            quote_cache['bsize'] = size
 | 
				
			||||||
 | 
					
 | 
				
			||||||
                    a = quote_to_marketstore_structarray(
 | 
					                    a = quote_to_marketstore_structarray(
 | 
				
			||||||
                        quote,
 | 
					                        quote_cache,
 | 
				
			||||||
                        # TODO: check this closer to the broker query api
 | 
					                        last_fill=quote.get('broker_ts', None)
 | 
				
			||||||
                        last_fill=quote.get('fill_time', '')
 | 
					 | 
				
			||||||
                    )
 | 
					                    )
 | 
				
			||||||
 | 
					                    log.info(a)
 | 
				
			||||||
 | 
					                    # breakpoint()
 | 
				
			||||||
                    await ms_client.write(symbol, a)
 | 
					                    await ms_client.write(symbol, a)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue