ib.feed: handle fiat (forex) pairs with `Asset`

Also finally adds full `FeedInit` and `MktPair` support for this backend
by handling:
- all "currency" fields for each `Contract` by constructing
  and `Asset` and setting the `MktPair.src` with `.atype='fiat'`.
- always render the `MktPair.src` name in the `.fqme` for fiat pairs
  (aka forex) but never for other instruments.
account_tests
Tyler Goodlet 2023-06-29 14:08:42 -04:00
parent 10ebc855e4
commit 745c144314
1 changed files with 24 additions and 33 deletions

View File

@ -140,32 +140,21 @@ async def open_history_client(
# memory. # memory.
# IB's internal symbology does not expect the "source asset" in # IB's internal symbology does not expect the "source asset" in
# the "symbol name", what we call the "market name". This is # the "symbol name", what we call the "market pair name". This is
# common in most legacy market brokers since it's presumed that # common in most legacy market brokers since it's presumed that
# given a certain stock exchange, listed assets are traded # given a certain stock exchange, listed assets are traded
# "from" a particular source fiat, normally something like USD. # "from" a particular source fiat, normally something like USD
if ( # on the given venue-provider, eg. nasdaq, nyse, etc.
mkt.src
and mkt.src.atype == 'fiat'
):
fqme_kwargs: dict[str, Any] = {} fqme_kwargs: dict[str, Any] = {}
if mkt.dst.atype != 'fiat':
if mkt.dst.atype == 'forex':
# XXX: for now we do need the src token kept in since
fqme_kwargs = { fqme_kwargs = {
'without_src': False, # default is True 'without_src': True, # default is True
'delim_char': '', # bc they would normally use a frickin `.` smh 'delim_char': '', # bc they would normally use a frickin `.` smh
} }
fqme: str = mkt.get_bs_fqme(**(fqme_kwargs)) fqme: str = mkt.get_bs_fqme(**(fqme_kwargs))
else:
fqme = mkt.bs_fqme
async with open_data_client() as proxy: async with open_data_client() as proxy:
max_timeout: float = 2. max_timeout: float = 2.
mean: float = 0 mean: float = 0
count: int = 0 count: int = 0
@ -178,7 +167,8 @@ async def open_history_client(
try: try:
head_dt = await proxy.get_head_time(fqme=fqme) head_dt = await proxy.get_head_time(fqme=fqme)
except RequestError: except RequestError:
head_dt = None log.warning(f'Unable to get head time: {fqme} ?')
pass
async def get_hist( async def get_hist(
timeframe: float, timeframe: float,
@ -576,7 +566,7 @@ _asset_type_map = {
'OPT': 'option', 'OPT': 'option',
'FUT': 'future', 'FUT': 'future',
'CONTFUT': 'continuous_future', 'CONTFUT': 'continuous_future',
'CASH': 'forex', 'CASH': 'fiat',
'IND': 'index', 'IND': 'index',
'CFD': 'cfd', 'CFD': 'cfd',
'BOND': 'bond', 'BOND': 'bond',
@ -837,7 +827,9 @@ async def get_mkt_info(
# if con.secType == 'STK': # if con.secType == 'STK':
size_tick = Decimal('1') size_tick = Decimal('1')
else: else:
size_tick: Decimal = Decimal(str(details.minSize).rstrip('0')) size_tick: Decimal = Decimal(
str(details.minSize).rstrip('0')
)
# |-> TODO: there is also the Contract.sizeIncrement, bt wtf is it? # |-> TODO: there is also the Contract.sizeIncrement, bt wtf is it?
# NOTE: this is duplicate from the .broker.norm_trade_records() # NOTE: this is duplicate from the .broker.norm_trade_records()
@ -853,10 +845,8 @@ async def get_mkt_info(
# we need to figure out how we're going to handle this (later?) # we need to figure out how we're going to handle this (later?)
# but likely we want all backends to eventually handle # but likely we want all backends to eventually handle
# ``dst/src.venue.`` style !? # ``dst/src.venue.`` style !?
src: str | Asset = ''
if atype == 'forex':
src = Asset( src = Asset(
name=str(con.currency), name=str(con.currency).lower(),
atype='fiat', atype='fiat',
tx_tick=Decimal('0.01'), # right? tx_tick=Decimal('0.01'), # right?
) )
@ -879,6 +869,7 @@ async def get_mkt_info(
# TODO: options contract info as str? # TODO: options contract info as str?
# contract_info=<optionsdetails> # contract_info=<optionsdetails>
_fqme_without_src=(atype != 'fiat'),
) )
return mkt, details return mkt, details
@ -920,7 +911,7 @@ async def stream_quotes(
init_msg = FeedInit(mkt_info=mkt) init_msg = FeedInit(mkt_info=mkt)
if mkt.dst.atype in { if mkt.dst.atype in {
'forex', 'fiat',
'index', 'index',
'commodity', 'commodity',
}: }:
@ -947,7 +938,7 @@ async def stream_quotes(
isnan(first_ticker.last) # last quote price value is nan isnan(first_ticker.last) # last quote price value is nan
and mkt.dst.atype not in { and mkt.dst.atype not in {
'commodity', 'commodity',
'forex', 'fiat',
'crypto', 'crypto',
} }
): ):