diff --git a/config/brokers.toml b/config/brokers.toml new file mode 100644 index 00000000..927bd302 --- /dev/null +++ b/config/brokers.toml @@ -0,0 +1,25 @@ +[questrade] +refresh_token = "" +access_token = "" +api_server = "https://api06.iq.questrade.com/" +expires_in = 1800 +token_type = "Bearer" +expires_at = 1616095326.355846 + +[kraken] +key_descr = "api_0" +public_key = "" +private_key = "" + +[ib.api] +ipaddr = "127.0.0.1" + +[ib.accounts] +margin = "" +registered = "" +paper = "" + +[ib.api.ports] +gw = 4002 +tws = 7497 +order = [ "gw", "tws",] diff --git a/piker/brokers/config.py b/piker/brokers/config.py index a8ba7ff2..9a8f6360 100644 --- a/piker/brokers/config.py +++ b/piker/brokers/config.py @@ -40,6 +40,16 @@ def _override_config_dir( def get_broker_conf_path(): + """Return the default config path normally under + ``~/.config/piker`` on linux. + + Contains files such as: + - brokers.toml + - watchlists.toml + - signals.toml + - strats.toml + + """ return os.path.join(_config_dir, _file_name) diff --git a/piker/brokers/ib.py b/piker/brokers/ib.py index cecc8be0..0d84020a 100644 --- a/piker/brokers/ib.py +++ b/piker/brokers/ib.py @@ -31,6 +31,7 @@ from pprint import pformat import inspect import itertools import logging +from random import randint import time import trio @@ -49,6 +50,7 @@ from ib_insync.client import Client as ib_Client from fuzzywuzzy import process as fuzzy import numpy as np +from . import config from ..log import get_logger, get_console_log from .._daemon import maybe_spawn_brokerd from ..data._source import from_df @@ -310,7 +312,8 @@ class Client: unique_sym = f'{con.symbol}.{con.primaryExchange}' as_dict = asdict(d) - # nested dataclass we probably don't need and that won't IPC serialize + # nested dataclass we probably don't need and that + # won't IPC serialize as_dict.pop('secIdList') details[unique_sym] = as_dict @@ -637,11 +640,29 @@ class Client: # default config ports _tws_port: int = 7497 _gw_port: int = 4002 -_try_ports = [_gw_port, _tws_port] -_client_ids = itertools.count() +_try_ports = [ + _gw_port, + _tws_port +] +# TODO: remove the randint stuff and use proper error checking in client +# factor below.. +_client_ids = itertools.count(randint(1, 100)) _client_cache = {} +def get_config() -> dict[str, Any]: + + conf, path = config.load() + + section = conf.get('ib') + + if not section: + log.warning(f'No config section found for ib in {path}') + return + + return section + + @asynccontextmanager async def _aio_get_client( host: str = '127.0.0.1', @@ -654,8 +675,9 @@ async def _aio_get_client( TODO: consider doing this with a ctx mngr eventually? """ - # first check cache for existing client + conf = get_config() + # first check cache for existing client try: if port: client = _client_cache[(host, port)] @@ -666,6 +688,7 @@ async def _aio_get_client( yield client except (KeyError, IndexError): + # TODO: in case the arbiter has no record # of existing brokerd we need to broadcast for one. @@ -675,9 +698,27 @@ async def _aio_get_client( client_id = next(_client_ids) ib = NonShittyIB() - ports = _try_ports if port is None else [port] + + # attempt to get connection info from config + ports = conf['api'].get( + 'ports', + { + # default order is to check for gw first + 'gw': 4002, + 'tws': 7497, + 'order': ['gw', 'tws'] + } + ) + order = ports['order'] + try_ports = [ports[key] for key in order] + ports = try_ports if port is None else [port] + + # TODO: support multiple clients allowing for execution on + # multiple accounts (including a paper instance running on the + # same machine) and switching between accounts in the EMs _err = None + for port in ports: try: log.info(f"Connecting to the EYEBEE on port {port}!") @@ -1360,7 +1401,7 @@ async def trades_dialogue( 'contract': asdict(fill.contract), 'execution': asdict(fill.execution), 'commissions': asdict(fill.commissionReport), - 'broker_time': execu.time, # supposedly IB server fill time + 'broker_time': execu.time, # supposedly server fill time 'name': 'ib', }