Add symbol search to ib
parent
82a8e0a7b6
commit
ef1b0911f3
|
@ -45,7 +45,9 @@ from ib_insync.objects import Position
|
|||
import ib_insync as ibis
|
||||
from ib_insync.wrapper import Wrapper
|
||||
from ib_insync.client import Client as ib_Client
|
||||
from fuzzywuzzy import process as fuzzy
|
||||
|
||||
from .api import open_cached_client
|
||||
from ..log import get_logger, get_console_log
|
||||
from .._daemon import maybe_spawn_brokerd
|
||||
from ..data._source import from_df
|
||||
|
@ -322,7 +324,7 @@ class Client:
|
|||
sym, exch = symbol.upper().rsplit('.', maxsplit=1)
|
||||
except ValueError:
|
||||
# likely there's an embedded `.` for a forex pair
|
||||
await tractor.breakpoint()
|
||||
breakpoint()
|
||||
|
||||
# futes
|
||||
if exch in ('GLOBEX', 'NYMEX', 'CME', 'CMECRYPTO'):
|
||||
|
@ -350,10 +352,13 @@ class Client:
|
|||
|
||||
if exch in ('PURE', 'TSE'): # non-yankee
|
||||
currency = 'CAD'
|
||||
if exch in ('PURE', 'TSE'):
|
||||
# stupid ib...
|
||||
primaryExchange = exch
|
||||
exch = 'SMART'
|
||||
# stupid ib...
|
||||
primaryExchange = exch
|
||||
exch = 'SMART'
|
||||
|
||||
else:
|
||||
exch = 'SMART'
|
||||
primaryExchange = exch
|
||||
|
||||
con = ibis.Stock(
|
||||
symbol=sym,
|
||||
|
@ -994,23 +999,31 @@ async def stream_quotes(
|
|||
}
|
||||
}
|
||||
|
||||
con = first_ticker.contract
|
||||
|
||||
# should be real volume for this contract by default
|
||||
calc_price = False
|
||||
|
||||
# check for special contract types
|
||||
if type(first_ticker.contract) not in (ibis.Commodity, ibis.Forex):
|
||||
suffix = 'exchange'
|
||||
# should be real volume for this contract
|
||||
calc_price = False
|
||||
|
||||
suffix = con.primaryExchange
|
||||
if not suffix:
|
||||
suffix = con.exchange
|
||||
|
||||
else:
|
||||
# commodities and forex don't have an exchange name and
|
||||
# no real volume so we have to calculate the price
|
||||
suffix = 'secType'
|
||||
suffix = con.secType
|
||||
# no real volume on this tract
|
||||
calc_price = True
|
||||
|
||||
# pass first quote asap
|
||||
quote = normalize(first_ticker, calc_price=calc_price)
|
||||
con = quote['contract']
|
||||
topic = '.'.join((con['symbol'], con[suffix])).lower()
|
||||
topic = '.'.join((con['symbol'], suffix)).lower()
|
||||
quote['symbol'] = topic
|
||||
|
||||
# pass first quote asap
|
||||
first_quote = {topic: quote}
|
||||
|
||||
# ugh, clear ticks since we've consumed them
|
||||
|
@ -1022,50 +1035,50 @@ async def stream_quotes(
|
|||
task_status.started((init_msgs, first_quote))
|
||||
|
||||
if type(first_ticker.contract) not in (ibis.Commodity, ibis.Forex):
|
||||
suffix = 'exchange'
|
||||
calc_price = False # should be real volume for contract
|
||||
# suffix = 'exchange'
|
||||
# calc_price = False # should be real volume for contract
|
||||
|
||||
# wait for real volume on feed (trading might be closed)
|
||||
async with aclosing(stream):
|
||||
while True:
|
||||
|
||||
async for ticker in stream:
|
||||
|
||||
# for a real volume contract we rait for the first
|
||||
# "real" trade to take place
|
||||
if not calc_price and not ticker.rtTime:
|
||||
# spin consuming tickers until we get a real market datum
|
||||
log.debug(f"New unsent ticker: {ticker}")
|
||||
continue
|
||||
else:
|
||||
log.debug("Received first real volume tick")
|
||||
# ugh, clear ticks since we've consumed them
|
||||
# (ahem, ib_insync is truly stateful trash)
|
||||
ticker.ticks = []
|
||||
|
||||
# XXX: this works because we don't use
|
||||
# ``aclosing()`` above?
|
||||
break
|
||||
|
||||
# tell caller quotes are now coming in live
|
||||
feed_is_live.set()
|
||||
|
||||
async for ticker in stream:
|
||||
|
||||
# print(ticker.vwap)
|
||||
quote = normalize(
|
||||
ticker,
|
||||
calc_price=calc_price
|
||||
)
|
||||
|
||||
con = quote['contract']
|
||||
topic = '.'.join((con['symbol'], con[suffix])).lower()
|
||||
quote['symbol'] = topic
|
||||
|
||||
await send_chan.send({topic: quote})
|
||||
ticker = await stream.receive()
|
||||
|
||||
# for a real volume contract we rait for the first
|
||||
# "real" trade to take place
|
||||
if not calc_price and not ticker.rtTime:
|
||||
# spin consuming tickers until we get a real market datum
|
||||
log.debug(f"New unsent ticker: {ticker}")
|
||||
continue
|
||||
else:
|
||||
log.debug("Received first real volume tick")
|
||||
# ugh, clear ticks since we've consumed them
|
||||
# (ahem, ib_insync is truly stateful trash)
|
||||
ticker.ticks = []
|
||||
|
||||
# XXX: this works because we don't use
|
||||
# ``aclosing()`` above?
|
||||
break
|
||||
|
||||
# tell caller quotes are now coming in live
|
||||
feed_is_live.set()
|
||||
|
||||
async with aclosing(stream):
|
||||
async for ticker in stream:
|
||||
|
||||
# print(ticker.vwap)
|
||||
quote = normalize(
|
||||
ticker,
|
||||
calc_price=calc_price
|
||||
)
|
||||
|
||||
# con = quote['contract']
|
||||
# topic = '.'.join((con['symbol'], suffix)).lower()
|
||||
quote['symbol'] = topic
|
||||
await send_chan.send({topic: quote})
|
||||
|
||||
# ugh, clear ticks since we've consumed them
|
||||
ticker.ticks = []
|
||||
|
||||
|
||||
def pack_position(pos: Position) -> Dict[str, Any]:
|
||||
con = pos.contract
|
||||
|
@ -1183,3 +1196,33 @@ async def stream_trades(
|
|||
continue
|
||||
|
||||
yield {'local_trades': (event_name, msg)}
|
||||
|
||||
|
||||
@tractor.context
|
||||
async def open_symbol_search(
|
||||
ctx: tractor.Context,
|
||||
) -> Client:
|
||||
async with open_cached_client('ib') as client:
|
||||
|
||||
# load all symbols locally for fast search
|
||||
await ctx.started({})
|
||||
|
||||
async with ctx.open_stream() as stream:
|
||||
|
||||
async for pattern in stream:
|
||||
|
||||
if not pattern:
|
||||
# will get error on empty request
|
||||
continue
|
||||
|
||||
results = await client.search_stocks(pattern=pattern, upto=5)
|
||||
|
||||
matches = fuzzy.extractBests(
|
||||
pattern,
|
||||
results,
|
||||
score_cutoff=50,
|
||||
)
|
||||
await stream.send(
|
||||
{item[2]: item[0]
|
||||
for item in matches}
|
||||
)
|
||||
|
|
Loading…
Reference in New Issue