ib: rework client-internal contract caching
Add new `Client` attr tables to better stash `Contract` lookup results normally mapped from some in put FQME; - `._contracts: dict[str, Contract]` for any input pattern (fqme). - `._cons: dict[str, Contract] = {}` for the `.conId: int` inputs. - `_cons2mkts: bidict[Contract, MktPair]` for mapping back and forth between ib and piker internal pair types. Further, - type out as many ib_insync internal types as possible mostly for contract related objects. - change `Client.trades()` -> `.get_fills()` and return directly the result from `IB.fill()`.account_tests
parent
897c20bd4a
commit
50b221f788
|
@ -34,16 +34,15 @@ from functools import (
|
||||||
)
|
)
|
||||||
import itertools
|
import itertools
|
||||||
from math import isnan
|
from math import isnan
|
||||||
from typing import (
|
|
||||||
Any,
|
|
||||||
Callable,
|
|
||||||
Optional,
|
|
||||||
Union,
|
|
||||||
)
|
|
||||||
import asyncio
|
import asyncio
|
||||||
from pprint import pformat
|
from pprint import pformat
|
||||||
import inspect
|
import inspect
|
||||||
import time
|
import time
|
||||||
|
from typing import (
|
||||||
|
Any,
|
||||||
|
Callable,
|
||||||
|
Union,
|
||||||
|
)
|
||||||
from types import SimpleNamespace
|
from types import SimpleNamespace
|
||||||
|
|
||||||
from bidict import bidict
|
from bidict import bidict
|
||||||
|
@ -56,26 +55,20 @@ from ib_insync import (
|
||||||
client as ib_client,
|
client as ib_client,
|
||||||
IB,
|
IB,
|
||||||
Contract,
|
Contract,
|
||||||
|
ContractDetails,
|
||||||
Crypto,
|
Crypto,
|
||||||
Commodity,
|
Commodity,
|
||||||
Forex,
|
Forex,
|
||||||
Future,
|
Future,
|
||||||
ContFuture,
|
ContFuture,
|
||||||
Stock,
|
Stock,
|
||||||
)
|
Order,
|
||||||
from ib_insync.contract import (
|
Ticker,
|
||||||
ContractDetails,
|
|
||||||
)
|
|
||||||
from ib_insync.order import Order
|
|
||||||
from ib_insync.ticker import Ticker
|
|
||||||
from ib_insync.objects import (
|
|
||||||
BarDataList,
|
BarDataList,
|
||||||
Position,
|
Position,
|
||||||
Fill,
|
Fill,
|
||||||
Execution,
|
# Execution,
|
||||||
CommissionReport,
|
# CommissionReport,
|
||||||
)
|
|
||||||
from ib_insync.wrapper import (
|
|
||||||
Wrapper,
|
Wrapper,
|
||||||
RequestError,
|
RequestError,
|
||||||
)
|
)
|
||||||
|
@ -85,6 +78,7 @@ import numpy as np
|
||||||
# non-relative for backends so that non-builting backends
|
# non-relative for backends so that non-builting backends
|
||||||
# can be easily modelled after this style B)
|
# can be easily modelled after this style B)
|
||||||
from piker import config
|
from piker import config
|
||||||
|
from piker.accounting import MktPair
|
||||||
from .symbols import (
|
from .symbols import (
|
||||||
con2fqme,
|
con2fqme,
|
||||||
parse_patt2fqme,
|
parse_patt2fqme,
|
||||||
|
@ -264,7 +258,13 @@ class Client:
|
||||||
Note: this client requires running inside an ``asyncio`` loop.
|
Note: this client requires running inside an ``asyncio`` loop.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
|
# keyed by fqmes
|
||||||
_contracts: dict[str, Contract] = {}
|
_contracts: dict[str, Contract] = {}
|
||||||
|
# keyed by conId
|
||||||
|
_cons: dict[str, Contract] = {}
|
||||||
|
|
||||||
|
# for going between ib and piker types
|
||||||
|
_cons2mkts: bidict[Contract, MktPair] = bidict({})
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
|
@ -282,26 +282,16 @@ class Client:
|
||||||
self.ib = ib
|
self.ib = ib
|
||||||
self.ib.RaiseRequestErrors: bool = True
|
self.ib.RaiseRequestErrors: bool = True
|
||||||
|
|
||||||
# contract cache
|
async def get_fills(self) -> list[Fill]:
|
||||||
self._cons: dict[str, Contract] = {}
|
|
||||||
|
|
||||||
async def trades(self) -> list[dict]:
|
|
||||||
'''
|
'''
|
||||||
Return list of trade-fills from current session in ``dict``.
|
Return list of rents `Fills` from trading session.
|
||||||
|
|
||||||
|
In theory this can be configured for dumping clears from multiple
|
||||||
|
days but can't member where to set that..
|
||||||
|
|
||||||
'''
|
'''
|
||||||
norm_fills: list[dict] = []
|
|
||||||
fills: list[Fill] = self.ib.fills()
|
fills: list[Fill] = self.ib.fills()
|
||||||
for fill in fills:
|
return fills
|
||||||
fill = fill._asdict() # namedtuple
|
|
||||||
for key, val in fill.items():
|
|
||||||
match val:
|
|
||||||
case Contract() | Execution() | CommissionReport():
|
|
||||||
fill[key] = asdict(val)
|
|
||||||
|
|
||||||
norm_fills.append(fill)
|
|
||||||
|
|
||||||
return norm_fills
|
|
||||||
|
|
||||||
async def orders(self) -> list[Order]:
|
async def orders(self) -> list[Order]:
|
||||||
return await self.ib.reqAllOpenOrdersAsync(
|
return await self.ib.reqAllOpenOrdersAsync(
|
||||||
|
@ -347,7 +337,7 @@ class Client:
|
||||||
|
|
||||||
_enters += 1
|
_enters += 1
|
||||||
|
|
||||||
contract = (await self.find_contracts(fqme))[0]
|
contract: Contract = (await self.find_contracts(fqme))[0]
|
||||||
bars_kwargs.update(getattr(contract, 'bars_kwargs', {}))
|
bars_kwargs.update(getattr(contract, 'bars_kwargs', {}))
|
||||||
|
|
||||||
bars = await self.ib.reqHistoricalDataAsync(
|
bars = await self.ib.reqHistoricalDataAsync(
|
||||||
|
@ -575,7 +565,8 @@ class Client:
|
||||||
|
|
||||||
) -> Contract:
|
) -> Contract:
|
||||||
'''
|
'''
|
||||||
Get an unqualifed contract for the current "continous" future.
|
Get an unqualifed contract for the current "continous"
|
||||||
|
future.
|
||||||
|
|
||||||
'''
|
'''
|
||||||
# it's the "front" contract returned here
|
# it's the "front" contract returned here
|
||||||
|
@ -606,13 +597,13 @@ class Client:
|
||||||
con: Contract = await self.ib.qualifyContractsAsync(
|
con: Contract = await self.ib.qualifyContractsAsync(
|
||||||
Contract(conId=conid)
|
Contract(conId=conid)
|
||||||
)
|
)
|
||||||
self._cons[conid] = con
|
self._cons[str(conid)] = con[0]
|
||||||
return con
|
return con
|
||||||
|
|
||||||
async def find_contracts(
|
async def find_contracts(
|
||||||
self,
|
self,
|
||||||
pattern: Optional[str] = None,
|
pattern: str | None = None,
|
||||||
contract: Optional[Contract] = None,
|
contract: Contract | None = None,
|
||||||
qualify: bool = True,
|
qualify: bool = True,
|
||||||
err_on_qualify: bool = True,
|
err_on_qualify: bool = True,
|
||||||
|
|
||||||
|
@ -622,21 +613,23 @@ class Client:
|
||||||
symbol, currency, exch, expiry = parse_patt2fqme(
|
symbol, currency, exch, expiry = parse_patt2fqme(
|
||||||
pattern,
|
pattern,
|
||||||
)
|
)
|
||||||
sectype = ''
|
sectype: str = ''
|
||||||
|
exch: str = exch.upper()
|
||||||
|
|
||||||
else:
|
else:
|
||||||
assert contract
|
assert contract
|
||||||
symbol = contract.symbol
|
symbol: str = contract.symbol
|
||||||
sectype = contract.secType
|
sectype: str = contract.secType
|
||||||
exch = contract.exchange or contract.primaryExchange
|
exch: str = contract.exchange or contract.primaryExchange
|
||||||
expiry = contract.lastTradeDateOrContractMonth
|
expiry: str = contract.lastTradeDateOrContractMonth
|
||||||
currency = contract.currency
|
currency: str = contract.currency
|
||||||
|
|
||||||
# contract searching stage
|
# contract searching stage
|
||||||
# ------------------------
|
# ------------------------
|
||||||
|
|
||||||
# futes
|
# futes, ensure exch/venue is uppercase for matching
|
||||||
if exch in _futes_venues:
|
# our adhoc set.
|
||||||
|
if exch.upper() in _futes_venues:
|
||||||
if expiry:
|
if expiry:
|
||||||
# get the "front" contract
|
# get the "front" contract
|
||||||
con = await self.get_fute(
|
con = await self.get_fute(
|
||||||
|
@ -704,10 +697,12 @@ class Client:
|
||||||
)
|
)
|
||||||
exch = 'SMART' if not exch else exch
|
exch = 'SMART' if not exch else exch
|
||||||
|
|
||||||
contracts = [con]
|
contracts: list[Contract] = [con]
|
||||||
if qualify:
|
if qualify:
|
||||||
try:
|
try:
|
||||||
contracts = await self.ib.qualifyContractsAsync(con)
|
contracts: list[Contract] = (
|
||||||
|
await self.ib.qualifyContractsAsync(con)
|
||||||
|
)
|
||||||
except RequestError as err:
|
except RequestError as err:
|
||||||
msg = err.message
|
msg = err.message
|
||||||
if (
|
if (
|
||||||
|
@ -725,14 +720,21 @@ class Client:
|
||||||
|
|
||||||
# pack all contracts into cache
|
# pack all contracts into cache
|
||||||
for tract in contracts:
|
for tract in contracts:
|
||||||
exch: str = tract.primaryExchange or tract.exchange or exch
|
exch: str = (
|
||||||
pattern = f'{symbol}.{exch}'
|
tract.primaryExchange
|
||||||
expiry = tract.lastTradeDateOrContractMonth
|
or tract.exchange
|
||||||
|
or exch
|
||||||
|
)
|
||||||
|
pattern: str = f'{symbol}.{exch}'
|
||||||
|
expiry: str = tract.lastTradeDateOrContractMonth
|
||||||
# add an entry with expiry suffix if available
|
# add an entry with expiry suffix if available
|
||||||
if expiry:
|
if expiry:
|
||||||
pattern += f'.{expiry}'
|
pattern += f'.{expiry}'
|
||||||
|
|
||||||
self._contracts[pattern.lower()] = tract
|
# directly cache the input pattern to the output
|
||||||
|
# contract match as well as by the IB-internal conId.
|
||||||
|
self._contracts[pattern] = tract
|
||||||
|
self._cons[str(tract.conId)] = tract
|
||||||
|
|
||||||
return contracts
|
return contracts
|
||||||
|
|
||||||
|
@ -755,21 +757,21 @@ class Client:
|
||||||
|
|
||||||
async def get_sym_details(
|
async def get_sym_details(
|
||||||
self,
|
self,
|
||||||
symbol: str,
|
fqme: str,
|
||||||
|
|
||||||
) -> tuple[
|
) -> tuple[
|
||||||
Contract,
|
Contract,
|
||||||
ContractDetails,
|
ContractDetails,
|
||||||
]:
|
]:
|
||||||
'''
|
'''
|
||||||
Get summary (meta) data for a given symbol str including
|
Return matching contracts for a given ``fqme: str`` including
|
||||||
``Contract`` and its details and a (first snapshot of the)
|
``Contract`` and matching ``ContractDetails``.
|
||||||
``Ticker``.
|
|
||||||
|
|
||||||
'''
|
'''
|
||||||
contract = (await self.find_contracts(symbol))[0]
|
contract: Contract = (await self.find_contracts(fqme))[0]
|
||||||
details_fute = self.ib.reqContractDetailsAsync(contract)
|
details: ContractDetails = (
|
||||||
details = (await details_fute)[0]
|
await self.ib.reqContractDetailsAsync(contract)
|
||||||
|
)[0]
|
||||||
return contract, details
|
return contract, details
|
||||||
|
|
||||||
async def get_quote(
|
async def get_quote(
|
||||||
|
@ -842,7 +844,7 @@ class Client:
|
||||||
|
|
||||||
'''
|
'''
|
||||||
try:
|
try:
|
||||||
contract = self._contracts[symbol]
|
con: Contract = self._contracts[symbol]
|
||||||
except KeyError:
|
except KeyError:
|
||||||
# require that the symbol has been previously cached by
|
# require that the symbol has been previously cached by
|
||||||
# a data feed request - ensure we aren't making orders
|
# a data feed request - ensure we aren't making orders
|
||||||
|
@ -851,7 +853,7 @@ class Client:
|
||||||
|
|
||||||
try:
|
try:
|
||||||
trade = self.ib.placeOrder(
|
trade = self.ib.placeOrder(
|
||||||
contract,
|
con,
|
||||||
Order(
|
Order(
|
||||||
orderId=reqid or 0, # stupid api devs..
|
orderId=reqid or 0, # stupid api devs..
|
||||||
action=action.upper(), # BUY/SELL
|
action=action.upper(), # BUY/SELL
|
||||||
|
@ -908,7 +910,7 @@ class Client:
|
||||||
reqId: int,
|
reqId: int,
|
||||||
errorCode: int,
|
errorCode: int,
|
||||||
errorString: str,
|
errorString: str,
|
||||||
contract: Contract,
|
con: Contract,
|
||||||
|
|
||||||
) -> None:
|
) -> None:
|
||||||
|
|
||||||
|
@ -933,7 +935,7 @@ class Client:
|
||||||
'reqid': reqId,
|
'reqid': reqId,
|
||||||
'reason': reason,
|
'reason': reason,
|
||||||
'error_code': errorCode,
|
'error_code': errorCode,
|
||||||
'contract': contract,
|
'contract': con,
|
||||||
}
|
}
|
||||||
))
|
))
|
||||||
except trio.BrokenResourceError:
|
except trio.BrokenResourceError:
|
||||||
|
|
Loading…
Reference in New Issue