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

1# This file is part of scrilla: https://github.com/chinchalinchin/scrilla. 

2 

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. 

6 

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. 

11 

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 

20 

21import scrilla.util.outputter as outputter 

22 

23 

24class APIKeyError(Exception): 

25 pass 

26 

27 

28APP_DIR = os.path.dirname(os.path.abspath(__file__)) 

29"""Folder containing the root module of the project""" 

30 

31PROJECT_DIR = os.path.dirname(APP_DIR) 

32"""Folder containing the project source""" 

33 

34APP_NAME = "scrilla" 

35"""Name of the application""" 

36 

37APP_ENV = os.environ.setdefault('APP_ENV', 'local') 

38"""Application execution environment; Configured by environment variable of the same name, **APP_ENV**.""" 

39 

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) 

43 

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**""" 

47 

48IMG_EXT = os.environ.setdefault("IMG_EXT", "jpg") 

49"""Extension used to saved images; Configured by environment variable of the same name, **IMG_EXT**""" 

50 

51METADATA_FILE = os.path.join(APP_DIR, 'data', 'meta', 'data.json') 

52"""File containing metadata information about the application""" 

53 

54CACHE_DIR = os.path.join(APP_DIR, 'data', 'cache') 

55"""Directory containing cached prices, statistics and calculations""" 

56 

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***""" 

60 

61TEMP_DIR = os.path.join(APP_DIR, 'data', 'tmp') 

62"""Buffer directory for graphics generated while using the GUI.""" 

63 

64ASSET_DIR = os.path.join(APP_DIR, 'data', 'assets') 

65"""Directory containing application assets such as icons, HTML templates, jpegs, etc.""" 

66 

67STATIC_DIR = os.path.join(APP_DIR, 'data', 'static') 

68"""Directory containg static data, such as ticker symbols, statistic symbols, etc.""" 

69 

70STATIC_TICKERS_FILE = os.path.join(STATIC_DIR, f'tickers.{FILE_EXT}') 

71"""Location of file used to store equity ticker symbols""" 

72 

73STATIC_ECON_FILE = os.path.join(STATIC_DIR, f'economics.{FILE_EXT}') 

74"""Location of file used to store statistic symbols""" 

75 

76STATIC_CRYPTO_FILE = os.path.join(STATIC_DIR, f'crypto.{FILE_EXT}') 

77"""Location of file used to store crypto ticker symbols""" 

78 

79COMMON_DIR = os.path.join(APP_DIR, 'data', 'common') 

80"""Directory used to store common files, such as API keys, watchlist, etc. 

81 

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""" 

85 

86COMMON_WATCHLIST_FILE = os.path.join(COMMON_DIR, f'watchlist.{FILE_EXT}') 

87"""Location of file used to store watchlisted ticker symbols""" 

88 

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""" 

91 

92GUI_STYLESHEET_FILE = os.path.join(APP_DIR, 'gui', 'styles', 'app.qss') 

93"""Location of the stylesheet applied to the GUI""" 

94 

95GUI_THEME_FILE = os.path.join(APP_DIR, 'gui', 'styles', 'themes.json') 

96"""Location of the color schemes used to style components""" 

97 

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`""" 

100 

101GUI_TEMPLATE_DIR = os.path.join(APP_DIR, 'gui', 'templates') 

102"""Location where the HTML templates for certain ``PySide6.QtWidgets.QWidget`'s are stored.""" 

103 

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.""" 

106 

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**""" 

126 

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' 

134 

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' 

142 

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' 

150 

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' 

159 

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' 

167 

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' 

175 

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' 

183 

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' 

191 

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 

200 

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""" 

203 

204MARKET_PROXY = os.environ.setdefault('MARKET_PROXY', 'SPY') 

205"""Ticker symbol used as a proxy for the overall market rate of return""" 

206 

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""" 

209 

210ESTIMATION_METHOD = os.environ.setdefault( 

211 'DEFAULT_ESTIMATION_METHOD', 'moments') 

212"""Determines the default estimation method using in statistical estimations""" 

213 

214CACHE_MODE = os.environ.setdefault( 

215 'CACHE_MODE', 'sqlite') 

216"""Determines how caching is handled""" 

217 

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} 

227 

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""" 

232 

233AV_KEY = None 

234"""API Key used to query *AlphaVantage* service.""" 

235 

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/') 

242 

243 AV_KEY = os.environ.setdefault('ALPHA_VANTAGE_KEY', None) 

244 

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) 

252 

253# STAT_MANAGER CONFIGURATION 

254STAT_MANAGER = os.environ.setdefault('STAT_MANAGER', 'treasury') 

255"""Determines the service used to retrieve statistics data""" 

256 

257Q_KEY = None 

258"""API Key used to query *Quandl/Nasdaq* service""" 

259 

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') 

266 

267 Q_KEY = os.environ.setdefault('QUANDL_KEY', None) 

268 

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) 

276 

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("'") 

280 

281# DIVIDEND_MANAGER CONFIGURATION 

282DIV_MANAGER = os.environ.setdefault("DIV_MANAGER", 'iex') 

283"""Determines the service used to retrieve dividends data""" 

284 

285IEX_KEY = None 

286"""API Key used to query IEX service""" 

287 

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') 

291 

292 IEX_KEY = os.environ.setdefault("IEX_KEY", None) 

293 

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) 

301 

302 

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. 

305 

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 

314 

315 

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 

318 

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 

327 

328 

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 

331 

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