Unify contract->fqsn translation with new cached-helper
parent
070b9f3dc1
commit
a27431c34f
|
@ -424,23 +424,15 @@ class Client:
|
||||||
# one set per future result
|
# one set per future result
|
||||||
details = {}
|
details = {}
|
||||||
for details_set in results:
|
for details_set in results:
|
||||||
|
|
||||||
# XXX: if there is more then one entry in the details list
|
# XXX: if there is more then one entry in the details list
|
||||||
# then the contract is so called "ambiguous".
|
# then the contract is so called "ambiguous".
|
||||||
for d in details_set:
|
for d in details_set:
|
||||||
con = d.contract
|
|
||||||
|
|
||||||
key = '.'.join([
|
# nested dataclass we probably don't need and that won't
|
||||||
con.symbol,
|
# IPC serialize..
|
||||||
con.primaryExchange or con.exchange,
|
|
||||||
])
|
|
||||||
expiry = con.lastTradeDateOrContractMonth
|
|
||||||
if expiry:
|
|
||||||
key += f'.{expiry}'
|
|
||||||
|
|
||||||
# nested dataclass we probably don't need and that
|
|
||||||
# won't IPC serialize..
|
|
||||||
d.secIdList = ''
|
d.secIdList = ''
|
||||||
|
key, calc_price = con2fqsn(d.contract)
|
||||||
details[key] = d
|
details[key] = d
|
||||||
|
|
||||||
return details
|
return details
|
||||||
|
@ -470,7 +462,7 @@ class Client:
|
||||||
self,
|
self,
|
||||||
pattern: str,
|
pattern: str,
|
||||||
# how many contracts to search "up to"
|
# how many contracts to search "up to"
|
||||||
upto: int = 3,
|
upto: int = 6,
|
||||||
asdicts: bool = True,
|
asdicts: bool = True,
|
||||||
|
|
||||||
) -> dict[str, ContractDetails]:
|
) -> dict[str, ContractDetails]:
|
||||||
|
@ -522,10 +514,14 @@ class Client:
|
||||||
elif sectype == 'CASH':
|
elif sectype == 'CASH':
|
||||||
dst, src = tract.localSymbol.split('.')
|
dst, src = tract.localSymbol.split('.')
|
||||||
pair_key = "/".join([dst, src])
|
pair_key = "/".join([dst, src])
|
||||||
tract.exchange = 'FOREX'
|
exch = tract.exchange.lower()
|
||||||
results[f'{pair_key}.forex'] = tract
|
results[f'{pair_key}.{exch}'] = tract
|
||||||
results.pop(key)
|
results.pop(key)
|
||||||
|
|
||||||
|
# XXX: again seems to trigger the weird tractor
|
||||||
|
# bug with the debugger..
|
||||||
|
# assert 0
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
async def get_fute(
|
async def get_fute(
|
||||||
|
@ -595,12 +591,11 @@ class Client:
|
||||||
|
|
||||||
# another hack for forex pairs lul.
|
# another hack for forex pairs lul.
|
||||||
if (
|
if (
|
||||||
'.forex' in symbol
|
'.idealpro' in symbol
|
||||||
# and not '.ib' in pattern
|
|
||||||
# or '/' in symbol
|
# or '/' in symbol
|
||||||
):
|
):
|
||||||
exch = 'FOREX'
|
exch = 'IDEALPRO'
|
||||||
symbol = symbol.removesuffix('.forex')
|
symbol = symbol.removesuffix('.idealpro')
|
||||||
if '/' in symbol:
|
if '/' in symbol:
|
||||||
symbol, currency = symbol.split('/')
|
symbol, currency = symbol.split('/')
|
||||||
|
|
||||||
|
@ -665,7 +660,7 @@ class Client:
|
||||||
)
|
)
|
||||||
|
|
||||||
elif (
|
elif (
|
||||||
exch in ('FOREX')
|
exch in ('IDEALPRO')
|
||||||
or sectype == 'CASH'
|
or sectype == 'CASH'
|
||||||
):
|
):
|
||||||
# if '/' in symbol:
|
# if '/' in symbol:
|
||||||
|
@ -948,6 +943,58 @@ class Client:
|
||||||
return self.ib.positions(account=account)
|
return self.ib.positions(account=account)
|
||||||
|
|
||||||
|
|
||||||
|
def con2fqsn(
|
||||||
|
con: Contract,
|
||||||
|
_cache: dict[int, (str, bool)] = {}
|
||||||
|
|
||||||
|
) -> tuple[str, bool]:
|
||||||
|
'''
|
||||||
|
Convert contracts to fqsn-style strings to be used both in symbol-search
|
||||||
|
matching and as feed tokens passed to the front end data deed layer.
|
||||||
|
|
||||||
|
Previously seen contracts are cached by id.
|
||||||
|
|
||||||
|
'''
|
||||||
|
# should be real volume for this contract by default
|
||||||
|
calc_price = False
|
||||||
|
if con.conId:
|
||||||
|
try:
|
||||||
|
return _cache[con.conId]
|
||||||
|
except KeyError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
suffix = con.primaryExchange or con.exchange
|
||||||
|
symbol = con.symbol
|
||||||
|
expiry = con.lastTradeDateOrContractMonth or ''
|
||||||
|
|
||||||
|
match con:
|
||||||
|
case ibis.Commodity():
|
||||||
|
# commodities and forex don't have an exchange name and
|
||||||
|
# no real volume so we have to calculate the price
|
||||||
|
suffix = con.secType
|
||||||
|
|
||||||
|
# no real volume on this tract
|
||||||
|
calc_price = True
|
||||||
|
|
||||||
|
case ibis.Forex() | ibis.Contract(secType='CASH'):
|
||||||
|
dst, src = con.localSymbol.split('.')
|
||||||
|
symbol = ''.join([dst, src])
|
||||||
|
suffix = con.exchange
|
||||||
|
|
||||||
|
# no real volume on forex feeds..
|
||||||
|
calc_price = True
|
||||||
|
|
||||||
|
# append a `.<suffix>` to the returned symbol
|
||||||
|
# key for derivatives that normally is the expiry
|
||||||
|
# date key.
|
||||||
|
if expiry:
|
||||||
|
suffix += f'.{expiry}'
|
||||||
|
|
||||||
|
fqsn_key = '.'.join((symbol, suffix)).lower()
|
||||||
|
_cache[con.conId] = fqsn_key, calc_price
|
||||||
|
return fqsn_key, calc_price
|
||||||
|
|
||||||
|
|
||||||
# per-actor API ep caching
|
# per-actor API ep caching
|
||||||
_client_cache: dict[tuple[str, int], Client] = {}
|
_client_cache: dict[tuple[str, int], Client] = {}
|
||||||
_scan_ignore: set[tuple[str, int]] = set()
|
_scan_ignore: set[tuple[str, int]] = set()
|
||||||
|
|
|
@ -42,6 +42,7 @@ from piker.data._sharedmem import ShmArray
|
||||||
from .._util import SymbolNotFound, NoData
|
from .._util import SymbolNotFound, NoData
|
||||||
from .api import (
|
from .api import (
|
||||||
# _adhoc_futes_set,
|
# _adhoc_futes_set,
|
||||||
|
con2fqsn,
|
||||||
log,
|
log,
|
||||||
load_aio_clients,
|
load_aio_clients,
|
||||||
ibis,
|
ibis,
|
||||||
|
@ -559,47 +560,18 @@ async def open_aio_quote_stream(
|
||||||
|
|
||||||
|
|
||||||
# TODO: cython/mypyc/numba this!
|
# TODO: cython/mypyc/numba this!
|
||||||
|
# or we can at least cache a majority of the values
|
||||||
|
# except for the ones we expect to change?..
|
||||||
def normalize(
|
def normalize(
|
||||||
ticker: Ticker,
|
ticker: Ticker,
|
||||||
calc_price: bool = False
|
calc_price: bool = False
|
||||||
|
|
||||||
) -> dict:
|
) -> dict:
|
||||||
|
|
||||||
# should be real volume for this contract by default
|
|
||||||
calc_price = False
|
|
||||||
|
|
||||||
# check for special contract types
|
# check for special contract types
|
||||||
con = ticker.contract
|
con = ticker.contract
|
||||||
symbol = con.symbol
|
|
||||||
|
|
||||||
if type(con) in (
|
fqsn, calc_price = con2fqsn(con)
|
||||||
ibis.Commodity,
|
|
||||||
):
|
|
||||||
# commodities and forex don't have an exchange name and
|
|
||||||
# no real volume so we have to calculate the price
|
|
||||||
suffix = con.secType
|
|
||||||
# no real volume on this tract
|
|
||||||
calc_price = True
|
|
||||||
|
|
||||||
elif type(con) in (
|
|
||||||
ibis.Forex,
|
|
||||||
):
|
|
||||||
suffix = 'forex'
|
|
||||||
symbol = con.pair()
|
|
||||||
# no real volume on forex feeds..
|
|
||||||
calc_price = True
|
|
||||||
|
|
||||||
else:
|
|
||||||
suffix = con.primaryExchange
|
|
||||||
if not suffix:
|
|
||||||
suffix = con.exchange
|
|
||||||
|
|
||||||
# append a `.<suffix>` to the returned symbol
|
|
||||||
# key for derivatives that normally is the expiry
|
|
||||||
# date key.
|
|
||||||
expiry = con.lastTradeDateOrContractMonth
|
|
||||||
if expiry:
|
|
||||||
suffix += f'.{expiry}'
|
|
||||||
|
|
||||||
# convert named tuples to dicts so we send usable keys
|
# convert named tuples to dicts so we send usable keys
|
||||||
new_ticks = []
|
new_ticks = []
|
||||||
|
@ -631,9 +603,7 @@ def normalize(
|
||||||
|
|
||||||
# generate fqsn with possible specialized suffix
|
# generate fqsn with possible specialized suffix
|
||||||
# for derivatives, note the lowercase.
|
# for derivatives, note the lowercase.
|
||||||
data['symbol'] = data['fqsn'] = '.'.join(
|
data['symbol'] = data['fqsn'] = fqsn
|
||||||
(symbol, suffix)
|
|
||||||
).lower()
|
|
||||||
|
|
||||||
# convert named tuples to dicts for transport
|
# convert named tuples to dicts for transport
|
||||||
tbts = data.get('tickByTicks')
|
tbts = data.get('tickByTicks')
|
||||||
|
|
Loading…
Reference in New Issue