Coverage for src/scrilla/settings.py: 61%
196 statements
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-18 18:14 +0000
« prev ^ index » next coverage.py v6.4.2, created at 2022-07-18 18:14 +0000
1# This file is part of scrilla: https://github.com/chinchalinchin/scrilla.
3# scrilla is free software: you can redistribute it and/or modify
4# it under the terms of the GNU General Public License version 3
5# as published by the Free Software Foundation.
7# scrilla is distributed in the hope that it will be useful,
8# but WITHOUT ANY WARRANTY; without even the implied warranty of
9# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10# GNU General Public License for more details.
12# You should have received a copy of the GNU General Public License
13# along with scrilla. If not, see <https://www.gnu.org/licenses/>
14# or <https://github.com/chinchalinchin/scrilla/blob/develop/main/LICENSE>.
15"""
16Application-wide configuration settings.
17"""
18import os
19import json
21import scrilla.util.outputter as outputter
24class APIKeyError(Exception):
25 pass
28APP_DIR = os.path.dirname(os.path.abspath(__file__))
29"""Folder containing the root module of the project"""
31PROJECT_DIR = os.path.dirname(APP_DIR)
32"""Folder containing the project source"""
34APP_NAME = "scrilla"
35"""Name of the application"""
37APP_ENV = os.environ.setdefault('APP_ENV', 'local')
38"""Application execution environment; Configured by environment variable of the same name, **APP_ENV**."""
40LOG_LEVEL = str(os.environ.setdefault("LOG_LEVEL", "info")).lower()
41"""Application log level output; Configured by environment of the same name, **LOG_LEVEL**."""
42logger = outputter.Logger('settings', LOG_LEVEL)
44# TODO: save formatting only supports JSON currently. Future file extensions: csv and txt.
45FILE_EXT = os.environ.setdefault("FILE_EXT", "json")
46"""Extension used to save files; Configured by environment variable of the same name, **FILE_EXT**"""
48IMG_EXT = os.environ.setdefault("IMG_EXT", "jpg")
49"""Extension used to saved images; Configured by environment variable of the same name, **IMG_EXT**"""
51METADATA_FILE = os.path.join(APP_DIR, 'data', 'meta', 'data.json')
52"""File containing metadata information about the application"""
54CACHE_DIR = os.path.join(APP_DIR, 'data', 'cache')
55"""Directory containing cached prices, statistics and calculations"""
57CACHE_SQLITE_FILE = os.environ.setdefault(
58 'SQLITE_FILE', os.path.join(CACHE_DIR, 'scrilla.db'))
59"""Location of the SQLite database flat file; Configured by environment variable **SQLITE_FILE***"""
61TEMP_DIR = os.path.join(APP_DIR, 'data', 'tmp')
62"""Buffer directory for graphics generated while using the GUI."""
64ASSET_DIR = os.path.join(APP_DIR, 'data', 'assets')
65"""Directory containing application assets such as icons, HTML templates, jpegs, etc."""
67STATIC_DIR = os.path.join(APP_DIR, 'data', 'static')
68"""Directory containg static data, such as ticker symbols, statistic symbols, etc."""
70STATIC_TICKERS_FILE = os.path.join(STATIC_DIR, f'tickers.{FILE_EXT}')
71"""Location of file used to store equity ticker symbols"""
73STATIC_ECON_FILE = os.path.join(STATIC_DIR, f'economics.{FILE_EXT}')
74"""Location of file used to store statistic symbols"""
76STATIC_CRYPTO_FILE = os.path.join(STATIC_DIR, f'crypto.{FILE_EXT}')
77"""Location of file used to store crypto ticker symbols"""
79COMMON_DIR = os.path.join(APP_DIR, 'data', 'common')
80"""Directory used to store common files, such as API keys, watchlist, etc.
82.. notes::
83 * It is recommended API keys are not stored in this directory, as they will be stored unencrypted. A better option is storing the keys as environment variables in your current session. See the documentation for more information.
84"""
86COMMON_WATCHLIST_FILE = os.path.join(COMMON_DIR, f'watchlist.{FILE_EXT}')
87"""Location of file used to store watchlisted ticker symbols"""
89MEMORY_FILE = os.path.join(COMMON_DIR, f'memory.{FILE_EXT}')
90"""Location to file used to persist flags that inform the application it has already initialized data"""
92GUI_STYLESHEET_FILE = os.path.join(APP_DIR, 'gui', 'styles', 'app.qss')
93"""Location of the stylesheet applied to the GUI"""
95GUI_THEME_FILE = os.path.join(APP_DIR, 'gui', 'styles', 'themes.json')
96"""Location of the color schemes used to style components"""
98GUI_ICON_FILE = os.path.join(APP_DIR, 'gui', 'styles', 'icons.json')
99"""Location of the icon filenames used as icons for `PySide6.QtWidgets.QPushButtons`"""
101GUI_TEMPLATE_DIR = os.path.join(APP_DIR, 'gui', 'templates')
102"""Location where the HTML templates for certain ``PySide6.QtWidgets.QWidget`'s are stored."""
104GUI_DARK_MODE = os.environ.setdefault('DARK_MODE', 'true').lower() == 'true'
105"""Flag determining the theme of the GUI, i.e. light mode or dark mode."""
107# OPTIONAL USER CONFIGURATION
108DATE_FORMAT = None
109"""datetime.strptime format for parsing date strings; Configured by environment of same name, **DATE_FORMAT**"""
110GUI_WIDTH = None
111"""Width of main Graphical User Interface window; Configured by environment variable of same name, **GUI_WIDTH**"""
112GUI_HEIGHT = None
113"""Height of main Graphical User Interface window; Configured by environment variable of same name, **GUI_HEIGHT**."""
114FRONTIER_STEPS = None
115"""Number of data points used to trace out the efficient frontier; Configured by environment variable of same name, **FRONTIER_STEPS** """
116MA_1_PERIOD = None
117"""Number of data points in first moving average period; Configured by environment variable, **MA_1**"""
118MA_2_PERIOD = None
119"""Number of data points in second moving average period; Configured by environment variable, **MA_2**"""
120MA_3_PERIOD = None
121"""Number of data points in third moving average period; Configured by environment variable, **MA_3***"""
122ITO_STEPS = None
123"""Number of iterations used to approximate an Ito integral; Configured by environment variable of same name, **ITO_STEPS**"""
124DEFAULT_ANALYSIS_PERIOD = None
125"""Number of days used in a historical sample, if no date range is specified; Configured by environment variable of same name, **DEFAULT_ANALYSIS_PERIOD**"""
127try:
128 DATE_FORMAT = str(os.environ.setdefault('DATE_FORMAT', '%Y-%m-%d'))
129except (ValueError, TypeError) as ParseError:
130 logger.debug(
131 'Failed to parse DATE_FORMAT from environment. Setting to default value of 1024.', 'line_127')
132 DATE_FORMAT = '%Y-%m-%d'
133 os.environ['GUI_WIDTH'] = '%Y-%m-%d'
135try:
136 GUI_WIDTH = int(os.environ.setdefault('GUI_WIDTH', '1024'))
137except (ValueError, TypeError) as ParseError:
138 logger.debug(
139 'Failed to parse GUI_WIDTH from environment. Setting to default value of 1024.', 'line_135')
140 GUI_WIDTH = 1024
141 os.environ['GUI_WIDTH'] = '1024'
143try:
144 GUI_HEIGHT = int(os.environ.setdefault('GUI_HEIGHT', '768'))
145except (ValueError, TypeError) as ParseError:
146 logger.debug(
147 'Failed to parse GUI_HEIGHT from enviroment. Setting to default value of 768.', 'line_143')
148 GUI_HEIGHT = 768
149 os.environ['GUI_HEIGHT'] = '768'
151# FINANCIAL ALGORITHM CONFIGURATION
152try:
153 FRONTIER_STEPS = int(os.environ.setdefault('FRONTIER_STEPS', '5'))
154except (ValueError, TypeError) as ParseError:
155 logger.debug(
156 'Failed to parse FRONTIER_STEPS from enviroment. Setting to default value of 5.', 'line_152')
157 FRONTIER_STEPS = 5
158 os.environ['FRONTIER_STEPS'] = '5'
160try:
161 MA_1_PERIOD = int(os.environ.setdefault('MA_1', '20'))
162except (ValueError, TypeError) as ParseError:
163 logger.debug(
164 'Failed to parse MA_1 from environment. Setting to default value of 20.', 'line_160')
165 MA_1_PERIOD = 20
166 os.environ['MA_1'] = '20'
168try:
169 MA_2_PERIOD = int(os.environ.setdefault('MA_2', '60'))
170except (ValueError, TypeError) as ParseError:
171 logger.debug(
172 'Failed to parse MA_2 from environment. Setting to default value of 60.', 'line_168')
173 MA_2_PERIOD = 60
174 os.environ['MA_2'] = '60'
176try:
177 MA_3_PERIOD = int(os.environ.setdefault('MA_3', '100'))
178except (ValueError, TypeError) as ParseError:
179 logger.debug(
180 'Failed to parse MA_3 from environment. Setting to default value of 100.', 'line_176')
181 MA_3_PERIOD = 100
182 os.environ['MA_3'] = '100'
184try:
185 ITO_STEPS = int(os.environ.setdefault('ITO_STEPS', '10000'))
186except (ValueError, TypeError) as ParseError:
187 logger.debug(
188 'Failed to parse ITO_STEPS from environment. Setting to default of 10000.', 'line_184')
189 ITO_STEPS = 10000
190 os.environ['ITO_STEPS'] = '10000'
192try:
193 DEFAULT_ANALYSIS_PERIOD = int(
194 os.environ.setdefault('DEFAULT_ANALYSIS_PERIOD', '100'))
195except (ValueError, TypeError) as ParseError:
196 logger.debug(
197 'Failed to parse DEFAULT_ANALYSIS_PERIOD from environment. Setting to default of 100.', 'line_194')
198 DEFAULT_ANALYSIS_PERIOD = 100
199 os.environ['DEFAULT_ANALYSIS_PERIOD'] = 100
201RISK_FREE_RATE = os.environ.setdefault("RISK_FREE", "TEN_YEAR").strip("\"")
202"""Maturity of the US Treasury Yield Curve used as a proxy for the risk free rate"""
204MARKET_PROXY = os.environ.setdefault('MARKET_PROXY', 'SPY')
205"""Ticker symbol used as a proxy for the overall market rate of return"""
207ANALYSIS_MODE = os.environ.setdefault('ANALYSIS_MODE', 'geometric')
208"""Determines the asset price process and thus, the underlying probability distribution of an asset's return"""
210ESTIMATION_METHOD = os.environ.setdefault(
211 'DEFAULT_ESTIMATION_METHOD', 'moments')
212"""Determines the default estimation method using in statistical estimations"""
214CACHE_MODE = os.environ.setdefault(
215 'CACHE_MODE', 'sqlite')
216"""Determines how caching is handled"""
218DYNAMO_CONF = {
219 'BillingMode': 'PAY_PER_REQUEST' # PAY_PER_REQUEST | PROVISIONED
220 # If PROVISIONED, the following lines need uncommented and configured:
221 #
222 # 'ProvisionedThroughput' : {
223 # 'ReadCapacityUnits': 123,
224 # 'WriteCapacityUnits': 123
225 # },
226}
228# SERVICE CONFIGURATION
229# PRICE_MANAGER CONFIGRUATION
230PRICE_MANAGER = os.environ.setdefault('PRICE_MANAGER', 'alpha_vantage')
231"""Determines the service used to retrieve price data"""
233AV_KEY = None
234"""API Key used to query *AlphaVantage* service."""
236# ALPHAVANTAGE CONFIGURATION
237if PRICE_MANAGER == 'alpha_vantage': 237 ↛ 254line 237 didn't jump to line 254, because the condition on line 237 was never false
238 AV_URL = os.environ.setdefault(
239 'ALPHA_VANTAGE_URL', 'https://www.alphavantage.co/query').strip("\"").strip("'")
240 AV_CRYPTO_LIST = os.environ.setdefault(
241 'ALPHA_VANTAGE_CRYPTO_META_URL', 'https://www.alphavantage.co/digital_currency_list/')
243 AV_KEY = os.environ.setdefault('ALPHA_VANTAGE_KEY', None)
245 if AV_KEY is None: 245 ↛ 246line 245 didn't jump to line 246, because the condition on line 245 was never true
246 keystore = os.path.join(COMMON_DIR, f'ALPHA_VANTAGE_KEY.{FILE_EXT}')
247 if os.path.isfile(keystore):
248 with open(keystore, 'r') as infile:
249 if FILE_EXT == "json":
250 AV_KEY = json.load(infile)['ALPHA_VANTAGE_KEY']
251 os.environ['ALPHA_VANTAGE_KEY'] = str(AV_KEY)
253# STAT_MANAGER CONFIGURATION
254STAT_MANAGER = os.environ.setdefault('STAT_MANAGER', 'treasury')
255"""Determines the service used to retrieve statistics data"""
257Q_KEY = None
258"""API Key used to query *Quandl/Nasdaq* service"""
260# QUANDL CONFIGURAITON / technically NASDAQ now. perhaps one day i will update the names...
261if STAT_MANAGER == "quandl": 261 ↛ 262line 261 didn't jump to line 262, because the condition on line 261 was never true
262 Q_URL = os.environ.setdefault(
263 'QUANDL_URL', 'https://data.nasdaq.com/api/v3/datasets').strip("\"").strip("'")
264 Q_META_URL = os.environ.setdefault(
265 'QUANDL_META_URL', 'https://data.nasdaq.com/api/v3/databases')
267 Q_KEY = os.environ.setdefault('QUANDL_KEY', None)
269 if Q_KEY is None:
270 keystore = os.path.join(COMMON_DIR, f'QUANDL_KEY.{FILE_EXT}')
271 if os.path.isfile(keystore):
272 with open(keystore, 'r') as infile:
273 if FILE_EXT == "json":
274 Q_KEY = json.load(infile)['QUANDL_KEY']
275 os.environ['QUANDL_KEY'] = str(Q_KEY)
277elif STAT_MANAGER == 'treasury': 277 ↛ 282line 277 didn't jump to line 282, because the condition on line 277 was never false
278 TR_URL = os.environ.setdefault(
279 'TREASURY_URL', 'https://home.treasury.gov/resource-center/data-chart-center').strip("\"").strip("'")
281# DIVIDEND_MANAGER CONFIGURATION
282DIV_MANAGER = os.environ.setdefault("DIV_MANAGER", 'iex')
283"""Determines the service used to retrieve dividends data"""
285IEX_KEY = None
286"""API Key used to query IEX service"""
288if DIV_MANAGER == "iex": 288 ↛ 303line 288 didn't jump to line 303, because the condition on line 288 was never false
289 IEX_URL = os.environ.setdefault(
290 "IEX_URL", 'https://cloud.iexapis.com/stable/stock')
292 IEX_KEY = os.environ.setdefault("IEX_KEY", None)
294 if IEX_KEY is None: 294 ↛ 295line 294 didn't jump to line 295, because the condition on line 294 was never true
295 keystore = os.path.join(COMMON_DIR, f'IEX_KEY.{FILE_EXT}')
296 if os.path.isfile(keystore):
297 with open(keystore, 'r') as infile:
298 if FILE_EXT == "json":
299 IEX_KEY = json.load(infile)['IEX_KEY']
300 os.environ['IEX_KEY'] = str(IEX_KEY)
303def q_key() -> str:
304 """Wraps access to the `scrilla.settings.Q_KEY` in an `scrilla.settings.APIKeyError`. Exception is thrown if `scrilla.settings.Q_KEY` cannot be parsed from the environment or the local data directory.
306 Raises
307 ------
308 1. **scrilla.settings.APIKeyError**
309 """
310 if not Q_KEY:
311 raise APIKeyError(
312 'Quandl API Key not found. Either set QUANDL_KEY environment variable or use "-store" CLI function to save key.')
313 return Q_KEY
316def iex_key() -> str:
317 """Wraps access to the `scrilla.settings.IEX_KEY` in an `scrilla.settings.APIKeyError`. Exception is thrown if `scrilla.settings.IEX_KEY` cannot be parsed from the environment or the local data directory
319 Raises
320 ------
321 1. **scrilla.settings.APIKeyError**
322 """
323 if not IEX_KEY: 323 ↛ 324line 323 didn't jump to line 324, because the condition on line 323 was never true
324 raise APIKeyError(
325 'IEX API Key cannot be found. Either set IEX_KEY environment variable or use "-store" CLI function to save key.')
326 return IEX_KEY
329def av_key() -> str:
330 """Wraps access to the `scrilla.settings.AV_KEY` in an `scrilla.settings.APIKeyError`. Exception is thrown if `scrilla.settings.AV_KEY` cannot be parsed from the environment or the local data directory
332 Raises
333 ------
334 1. **scrilla.settings.APIKeyError**
335 """
336 if not AV_KEY: 336 ↛ 337line 336 didn't jump to line 337, because the condition on line 336 was never true
337 raise APIKeyError(
338 'Alpha Vantage API Key not found. Either set ALPHA_VANTAGE_KEY environment variable or use "-store" CLI function to save key.')
339 return AV_KEY