222 lines
6.2 KiB
Python
222 lines
6.2 KiB
Python
# piker: trading gear for hackers
|
|
# Copyright (C) 2018-present Tyler Goodlet (in stewardship of piker0)
|
|
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero General Public License as published by
|
|
# the Free Software Foundation, either version 3 of the License, or
|
|
# (at your option) any later version.
|
|
|
|
# This program is distributed in the hope that it will be useful,
|
|
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
# GNU Affero General Public License for more details.
|
|
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
"""
|
|
Broker configuration mgmt.
|
|
"""
|
|
import platform
|
|
import sys
|
|
import os
|
|
from os.path import dirname
|
|
import shutil
|
|
from typing import Optional
|
|
|
|
from bidict import bidict
|
|
import toml
|
|
|
|
from .log import get_logger
|
|
|
|
log = get_logger('broker-config')
|
|
|
|
|
|
# taken from ``click`` since apparently they have some
|
|
# super weirdness with sigint and sudo..no clue
|
|
def get_app_dir(app_name, roaming=True, force_posix=False):
|
|
r"""Returns the config folder for the application. The default behavior
|
|
is to return whatever is most appropriate for the operating system.
|
|
|
|
To give you an idea, for an app called ``"Foo Bar"``, something like
|
|
the following folders could be returned:
|
|
|
|
Mac OS X:
|
|
``~/Library/Application Support/Foo Bar``
|
|
Mac OS X (POSIX):
|
|
``~/.foo-bar``
|
|
Unix:
|
|
``~/.config/foo-bar``
|
|
Unix (POSIX):
|
|
``~/.foo-bar``
|
|
Win XP (roaming):
|
|
``C:\Documents and Settings\<user>\Local Settings\Application Data\Foo Bar``
|
|
Win XP (not roaming):
|
|
``C:\Documents and Settings\<user>\Application Data\Foo Bar``
|
|
Win 7 (roaming):
|
|
``C:\Users\<user>\AppData\Roaming\Foo Bar``
|
|
Win 7 (not roaming):
|
|
``C:\Users\<user>\AppData\Local\Foo Bar``
|
|
|
|
.. versionadded:: 2.0
|
|
|
|
:param app_name: the application name. This should be properly capitalized
|
|
and can contain whitespace.
|
|
:param roaming: controls if the folder should be roaming or not on Windows.
|
|
Has no affect otherwise.
|
|
:param force_posix: if this is set to `True` then on any POSIX system the
|
|
folder will be stored in the home folder with a leading
|
|
dot instead of the XDG config home or darwin's
|
|
application support folder.
|
|
"""
|
|
|
|
def _posixify(name):
|
|
return "-".join(name.split()).lower()
|
|
|
|
# if WIN:
|
|
if platform.system() == 'Windows':
|
|
key = "APPDATA" if roaming else "LOCALAPPDATA"
|
|
folder = os.environ.get(key)
|
|
if folder is None:
|
|
folder = os.path.expanduser("~")
|
|
return os.path.join(folder, app_name)
|
|
if force_posix:
|
|
return os.path.join(os.path.expanduser("~/.{}".format(_posixify(app_name))))
|
|
if sys.platform == "darwin":
|
|
return os.path.join(
|
|
os.path.expanduser("~/Library/Application Support"), app_name
|
|
)
|
|
return os.path.join(
|
|
os.environ.get("XDG_CONFIG_HOME", os.path.expanduser("~/.config")),
|
|
_posixify(app_name),
|
|
)
|
|
|
|
|
|
_config_dir = _click_config_dir = get_app_dir('piker')
|
|
_parent_user = os.environ.get('SUDO_USER')
|
|
|
|
if _parent_user:
|
|
non_root_user_dir = os.path.expanduser(
|
|
f'~{_parent_user}'
|
|
)
|
|
root = 'root'
|
|
_config_dir = (
|
|
non_root_user_dir +
|
|
_click_config_dir[
|
|
_click_config_dir.rfind(root) + len(root):
|
|
]
|
|
)
|
|
|
|
_file_name = 'brokers.toml'
|
|
_watchlists_data_path = os.path.join(_config_dir, 'watchlists.json')
|
|
_context_defaults = dict(
|
|
default_map={
|
|
# Questrade specific quote poll rates
|
|
'monitor': {
|
|
'rate': 3,
|
|
},
|
|
'optschain': {
|
|
'rate': 1,
|
|
},
|
|
}
|
|
)
|
|
|
|
|
|
def _override_config_dir(
|
|
path: str
|
|
) -> None:
|
|
global _config_dir
|
|
_config_dir = path
|
|
|
|
|
|
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)
|
|
|
|
|
|
def repodir():
|
|
"""Return the abspath to the repo directory.
|
|
"""
|
|
dirpath = os.path.abspath(
|
|
# we're 3 levels down in **this** module file
|
|
dirname(dirname(os.path.realpath(__file__)))
|
|
)
|
|
return dirpath
|
|
|
|
|
|
def load(
|
|
path: str = None
|
|
) -> (dict, str):
|
|
"""Load broker config.
|
|
"""
|
|
path = path or get_broker_conf_path()
|
|
if not os.path.isfile(path):
|
|
shutil.copyfile(
|
|
os.path.join(repodir(), 'config', 'brokers.toml'),
|
|
path,
|
|
)
|
|
|
|
config = toml.load(path)
|
|
log.debug(f"Read config file {path}")
|
|
return config, path
|
|
|
|
|
|
def write(
|
|
config: dict, # toml config as dict
|
|
path: str = None,
|
|
) -> None:
|
|
"""Write broker config to disk.
|
|
|
|
Create a ``brokers.ini`` file if one does not exist.
|
|
"""
|
|
path = path or get_broker_conf_path()
|
|
dirname = os.path.dirname(path)
|
|
if not os.path.isdir(dirname):
|
|
log.debug(f"Creating config dir {_config_dir}")
|
|
os.makedirs(dirname)
|
|
|
|
if not config:
|
|
raise ValueError(
|
|
"Watch out you're trying to write a blank config!")
|
|
|
|
log.debug(f"Writing config file {path}")
|
|
with open(path, 'w') as cf:
|
|
return toml.dump(config, cf)
|
|
|
|
|
|
def load_accounts(
|
|
|
|
providers: Optional[list[str]] = None
|
|
|
|
) -> bidict[str, Optional[str]]:
|
|
|
|
conf, path = load()
|
|
accounts = bidict()
|
|
for provider_name, section in conf.items():
|
|
accounts_section = section.get('accounts')
|
|
if (
|
|
providers is None or
|
|
providers and provider_name in providers
|
|
):
|
|
if accounts_section is None:
|
|
log.warning(f'No accounts named for {provider_name}?')
|
|
continue
|
|
else:
|
|
for label, value in accounts_section.items():
|
|
accounts[
|
|
f'{provider_name}.{label}'
|
|
] = value
|
|
|
|
# our default paper engine entry
|
|
accounts['paper'] = None
|
|
return accounts
|