From a10d20c0410cf5ecdc692b678d5c5f68e57fb38c Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Fri, 19 Mar 2021 13:23:33 -0400 Subject: [PATCH 1/5] Add ib config section support --- config/brokers.toml | 25 +++++++++++++++++++ piker/brokers/config.py | 10 ++++++++ piker/brokers/ib.py | 53 ++++++++++++++++++++++++++++++++++++----- 3 files changed, 82 insertions(+), 6 deletions(-) create mode 100644 config/brokers.toml 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', } From 12c8d26906d156c6fd29e328af11b2a1cb2ccc6a Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Tue, 24 Aug 2021 10:23:53 -0400 Subject: [PATCH 2/5] Update brokers.toml schema --- data/brokers.toml | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/data/brokers.toml b/data/brokers.toml index 54b3df16..22bba455 100644 --- a/data/brokers.toml +++ b/data/brokers.toml @@ -1,9 +1,25 @@ -[binance] +[questrade] +refresh_token = "" +access_token = "" +api_server = "https://api02.iq.questrade.com/" +expires_in = 1800 +token_type = "Bearer" +expires_at = 1629487755.7784228 [kraken] +key_descr = "api_0" +public_key = "" +private_key = "" +[ib] +host = "127.0.0.1" -# [ib] +[ib.ports] +gw = 4002 +tws = 7497 +order = [ "gw", "tws",] - -# [questrade] +[accounts.ib] +main = "" +reg = "" +paper = "" From d5394ac6778cba1979fd52c670747d4073af6e3a Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Mon, 5 Jul 2021 09:53:42 -0400 Subject: [PATCH 3/5] Fix TWS triggered trades msg packing --- piker/brokers/ib.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/piker/brokers/ib.py b/piker/brokers/ib.py index 0d84020a..c7533a96 100644 --- a/piker/brokers/ib.py +++ b/piker/brokers/ib.py @@ -1442,14 +1442,14 @@ async def trades_dialogue( if getattr(msg, 'reqid', 0) < -1: # it's a trade event generated by TWS usage. - log.warning(f"TWS triggered trade:\n{pformat(msg)}") + log.info(f"TWS triggered trade\n{pformat(msg.dict())}") msg.reqid = 'tws-' + str(-1 * msg.reqid) # mark msg as from "external system" # TODO: probably something better then this.. and start # considering multiplayer/group trades tracking - msg.external = True + msg.broker_details['external_src'] = 'tws' continue # XXX: we always serialize to a dict for msgpack From 89b20895622d572f7b2825be666bdffa78eb52f7 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Tue, 27 Jul 2021 08:28:44 -0400 Subject: [PATCH 4/5] Fixup missing ib section handling; drop `.api` subsection --- config/brokers.toml | 6 +++--- piker/brokers/ib.py | 21 ++++++++++++++------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/config/brokers.toml b/config/brokers.toml index 927bd302..e14fdf20 100644 --- a/config/brokers.toml +++ b/config/brokers.toml @@ -11,15 +11,15 @@ key_descr = "api_0" public_key = "" private_key = "" -[ib.api] -ipaddr = "127.0.0.1" +[ib] +host = "127.0.0.1" [ib.accounts] margin = "" registered = "" paper = "" -[ib.api.ports] +[ib.ports] gw = 4002 tws = 7497 order = [ "gw", "tws",] diff --git a/piker/brokers/ib.py b/piker/brokers/ib.py index c7533a96..f604fc93 100644 --- a/piker/brokers/ib.py +++ b/piker/brokers/ib.py @@ -656,25 +656,28 @@ def get_config() -> dict[str, Any]: section = conf.get('ib') - if not section: + if section is None: log.warning(f'No config section found for ib in {path}') - return + return {} return section @asynccontextmanager async def _aio_get_client( + host: str = '127.0.0.1', port: int = None, + client_id: Optional[int] = None, + ) -> Client: - """Return an ``ib_insync.IB`` instance wrapped in our client API. + '''Return an ``ib_insync.IB`` instance wrapped in our client API. Client instances are cached for later use. TODO: consider doing this with a ctx mngr eventually? - """ + ''' conf = get_config() # first check cache for existing client @@ -699,17 +702,21 @@ async def _aio_get_client( ib = NonShittyIB() - # attempt to get connection info from config - ports = conf['api'].get( + # attempt to get connection info from config; if no .toml entry + # exists, we try to load from a default localhost connection. + host = conf.get('host', '127.0.0.1') + ports = conf.get( 'ports', + + # default order is to check for gw first { - # 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] From c21d29919334de398975b000e6a94a6b8c6d1047 Mon Sep 17 00:00:00 2001 From: Tyler Goodlet Date: Tue, 24 Aug 2021 10:32:01 -0400 Subject: [PATCH 5/5] Drop data/ version of config --- data/brokers.toml | 25 ------------------------- 1 file changed, 25 deletions(-) delete mode 100644 data/brokers.toml diff --git a/data/brokers.toml b/data/brokers.toml deleted file mode 100644 index 22bba455..00000000 --- a/data/brokers.toml +++ /dev/null @@ -1,25 +0,0 @@ -[questrade] -refresh_token = "" -access_token = "" -api_server = "https://api02.iq.questrade.com/" -expires_in = 1800 -token_type = "Bearer" -expires_at = 1629487755.7784228 - -[kraken] -key_descr = "api_0" -public_key = "" -private_key = "" - -[ib] -host = "127.0.0.1" - -[ib.ports] -gw = 4002 -tws = 7497 -order = [ "gw", "tws",] - -[accounts.ib] -main = "" -reg = "" -paper = ""