diff --git a/src/scrilla/analysis/optimizer.py b/src/scrilla/analysis/optimizer.py index b7bc76aa..87420cc7 100644 --- a/src/scrilla/analysis/optimizer.py +++ b/src/scrilla/analysis/optimizer.py @@ -1,4 +1,5 @@ import scipy.optimize as optimize +from scrilla import static import settings import util.outputter as outputter @@ -41,7 +42,7 @@ def optimize_portfolio_variance(portfolio, target_return=None): portfolio_constraints = equity_constraint allocation = optimize.minimize(fun = portfolio.volatility_function, x0 = init_guess, - method=settings.OPTIMIZATION_METHOD, bounds=equity_bounds, + method=static.constants['OPTIMIZATION_METHOD'], bounds=equity_bounds, constraints=portfolio_constraints, options={'disp': False}) return allocation.x @@ -80,7 +81,7 @@ def optimize_conditional_value_at_risk(portfolio, prob, expiry, target_return=No allocation = optimize.minimize(fun = lambda x: portfolio.conditional_value_at_risk_function(x, expiry, prob), x0 = init_guess, - method=settings.OPTIMIZATION_METHOD, bounds=equity_bounds, + method=static.constants['OPTIMIZATION_METHOD'], bounds=equity_bounds, constraints=portfolio_constraints, options={'disp': False}) return allocation.x @@ -122,7 +123,7 @@ def maximize_sharpe_ratio(portfolio, target_return=None): allocation = optimize.minimize(fun = lambda x: (-1)*portfolio.sharpe_ratio_function(x), x0 = init_guess, - method=settings.OPTIMIZATION_METHOD, bounds=equity_bounds, + method=static.constants['OPTIMIZATION_METHOD'], bounds=equity_bounds, constraints=portfolio_constraints, options={'disp': False}) return allocation.x @@ -148,7 +149,7 @@ def maximize_portfolio_return(portfolio): logger.debug(f'Maximizing {tickers} Portfolio Return') allocation = optimize.minimize(fun = lambda x: (-1)*portfolio.return_function(x), - x0 = init_guess, method=settings.OPTIMIZATION_METHOD, + x0 = init_guess, method=static.constants['OPTIMIZATION_METHOD'], bounds=equity_bounds, constraints=equity_constraint, options={'disp': False}) diff --git a/src/scrilla/files.py b/src/scrilla/files.py index 7c64c6e6..72c5129e 100644 --- a/src/scrilla/files.py +++ b/src/scrilla/files.py @@ -187,7 +187,7 @@ def get_static_data(static_type): else: path = settings.STATIC_TICKERS_FILE - elif static_type == settings.STAT_ECON: + elif static_type == static.keys['ASSETS']['STAT']: if static_econ_blob: blob = static_econ_blob else: @@ -214,7 +214,7 @@ def get_static_data(static_type): static_crypto_blob = symbols elif static_type == static.keys['ASSETS']['EQUITY']: static_tickers_blob = symbols - elif static_type == settings.STAT_ECON: + elif static_type == static.keys['ASSETS']['STAT']: static_econ_blob = symbols return symbols @@ -341,11 +341,11 @@ def format_allocation(allocation, portfolio, investment=None): for j, item in enumerate(portfolio.tickers): holding = {} holding['ticker'] = item - holding['allocation'] = round(allocation[j], settings.ACCURACY) + holding['allocation'] = round(allocation[j], static.constants['ACCURACY']) if investment is not None: holding['shares'] = float(shares[j]) - holding['annual_return'] = round(portfolio.mean_return[j], settings.ACCURACY) - holding['annual_volatility'] = round(portfolio.sample_vol[j], settings.ACCURACY) + holding['annual_return'] = round(portfolio.mean_return[j], static.constants['ACCURACY']) + holding['annual_volatility'] = round(portfolio.sample_vol[j], static.constants['ACCURACY']) allocation_format.append(holding) json_format = {} @@ -450,7 +450,7 @@ def clear_directory(directory, retain=True): for f in filelist: filename = os.path.basename(f) - if retain and filename == settings.KEEP_EXT: + if retain and filename == static.constants['KEEP_FILE']: continue os.remove(os.path.join(directory, f)) diff --git a/src/scrilla/objects/cashflow.py b/src/scrilla/objects/cashflow.py index 946d270d..aaeda944 100644 --- a/src/scrilla/objects/cashflow.py +++ b/src/scrilla/objects/cashflow.py @@ -1,4 +1,5 @@ import datetime +from scrilla import static import util.helper as helper import util.outputter as outputter @@ -216,7 +217,7 @@ def calculate_net_present_value(self): self.NPV += self.get_growth_function(current_time) / ((1 + self.discount_rate)**current_time) - if self.NPV - previous_value < settings.NPV_DELTA_TOLERANCE: + if self.NPV - previous_value < static.keys['NPV_DELTA_TOLERANCE']: calculating = False i += 1 diff --git a/src/scrilla/services.py b/src/scrilla/services.py index d053d9f6..edbc8dea 100644 --- a/src/scrilla/services.py +++ b/src/scrilla/services.py @@ -16,7 +16,7 @@ def __init__(self, type): self.type = type def construct_url(self, symbol, start_date, end_date): - if self.type == static.keys['SERVICE']['STATISTICS']['QUANDL']: + if self.type == static.keys['SERVICES']['STATISTICS']['QUANDL']['MANAGER']: url = f'{settings.Q_URL}/' query = f'{settings.PATH_Q_FRED}/{symbol}?' @@ -39,7 +39,7 @@ def get_stats(self, symbol, start_date, end_date): url = self.construct_url(symbol, start_date, end_date) response = requests.get(url).json() - if self.type == static.keys['SERVICE']['STATISTICS']['QUANDL']: + if self.type == static.keys['SERVICES']['STATISTICS']['QUANDL']['MANAGER']: raw_stat = response[settings.Q_FIRST_LAYER][settings.Q_SECOND_LAYER] formatted_stat = {} @@ -154,13 +154,13 @@ def __init__(self, type): self.type = type def construct_url(self, ticker, asset_type): - if self.type == static.keys['SERVICE']['PRICES']['ALPHA_VANTAGE']: + if self.type == static.keys['SERVICES']['PRICES']['ALPHA_VANTAGE']['MANAGER']: query = f'{settings.PARAM_AV_TICKER}={ticker}' if asset_type == static.keys['ASSETS']['EQUITY']: query += f'&{settings.PARAM_AV_FUNC}={settings.ARG_AV_FUNC_EQUITY_DAILY}' elif asset_type == static.keys['ASSETS']['CRYPTO']: - query += f'&{settings.PARAM_AV_FUNC}={settings.ARG_AV_FUNC_CRYPTO_DAILY}&{settings.PARAM_AV_DENOM}={settings.DENOMINATION}' + query += f'&{settings.PARAM_AV_FUNC}={settings.ARG_AV_FUNC_CRYPTO_DAILY}&{settings.PARAM_AV_DENOM}={static.constants["DENOMINATION"]}' # NOTE: only need to modify EQUITY query, CRYPTO always returns full history if (asset_type == static.keys['ASSETS']['EQUITY']): @@ -178,7 +178,7 @@ def get_prices(self, ticker, start_date, end_date, asset_type): url = self.construct_url(ticker, asset_type) response = requests.get(url).json() - if self.type == static.keys['SERVICE']['PRICES']['ALPHA_VANTAGE']: + if self.type == static.keys['SERVICES']['PRICES']['ALPHA_VANTAGE']['MANAGER']: first_element = helper.get_first_json_key(response) # end function is daily rate limit is reached if first_element == settings.AV_RES_DAY_LIMIT: @@ -197,7 +197,7 @@ def get_prices(self, ticker, start_date, end_date, asset_type): else: logger.debug('Waiting.') - time.sleep(settings.BACKOFF_PERIOD) + time.sleep(static.constants['BACKOFF_PERIOD']) response = requests.get(url).json() first_element = helper.get_first_json_key(response) @@ -210,7 +210,7 @@ def get_prices(self, ticker, start_date, end_date, asset_type): def slice_prices(self, start_date, end_date, asset_type, prices): # NOTE: only really needed for `alpha_vantage` responses so far, due to the fact AlphaVantage either returns everything or 100 days or prices. - if self.type == static.keys['SERVICE']['PRICES']['ALPHA_VANTAGE']: + if self.type == static.keys['SERVICES']['PRICES']['ALPHA_VANTAGE']['MANAGER']: # NOTE: Remember AlphaVantage is ordered current to earliest. END_INDEX is # actually the beginning of slice and START_INDEX is actually end of slice. try: diff --git a/src/scrilla/settings.py b/src/scrilla/settings.py index d5913ff9..4cbc6f60 100644 --- a/src/scrilla/settings.py +++ b/src/scrilla/settings.py @@ -1,4 +1,5 @@ import os, sys, dotenv, json +from scrilla import static APP_DIR = os.path.dirname(os.path.abspath(__file__)) PROJECT_DIR = os.path.dirname(APP_DIR) @@ -27,32 +28,21 @@ def __init__(self, message): 3. APP_DIR: Folder containing this file. \n \n 4. APP_ENV: Application environment. \n \n 5. LOG_LEVEL: Debug output level. \n \n -6. CONFIG_FILE: Location of secondary credentials file. \n \n 7. CACHE_DIR: Folder where cached price histories reside. \n \n -8. CACHE_PRO_KEY: File name where risk-profile calculations are saved. \n \n 9. FILE_EXT: File extension used in CACHE_DIR. \n \n 10. STATIC_DIR: Folder where static data reside. \n \n 11. FILE_EXT: File extension used in STATIC_DIR. \n \n 12. STATIC_TICKERS_FILE: File containing a list of all equity ticker symbols with price histories that can be retrieved from external services. \n \n 13. STATIC_ECON_FILE: File containg a list of all economic statistics with sample histories that can be retrieved from external services. \n \n 14. STATIC_CRYPTO_FILE: File containing a list of all crypto ticker symbols with price histories that can be retrieved from external services. \n \n -15. ACCURACY: Number of decimals place saved in calculations. \n \n 18. GUI_WIDTH: Width of root widget in GUI. \n \n 19. GUI_HEIGHT: Height of root widget in GUI. \n \n -20. OPTIMIZATION_METHOD: Scipy method used to optimize portfolios. \n \n -21. INVESTMENT_MODE: Determines if output is percentage or absolute. \n \n 22. FRONTIER_STEPS: Number of points in efficient frontier output. \n \n 23. MA_1_PERIOD: Number of days in first moving average period. \n \n 24. MA_2_PERIOD: Number of days in second moving average period. \n \n 25. MA_3_PERIOD: Number of days in first moving average period. \n \n -26. ONE_TRADING_DAY: Length of trading day in years. \n \n -27. PRICE_YEAR_CUTOFF: Earliest year considered in price histories. \n \n -28. DENOMINATION: Denomination in which prices are quoted. \n \n -29. NPV_DELTA_TOLERANCE: NPV calculations stop when the next value adds less than this amount. \n \n 29. RISK_FREE_RATE: Interest rate used for cashflow valuations. \n \n 30. MARKET_PROXY: Ticker symbol used to calculate market rate of return -30. STAT_ECON: Constant for economic statistics. \n \n -31. INIT: Flag to initialize STATIC_DIR \n \n 32. PRICE_MANAGER: Service in charge of price histories. \n \n 33. STAT_MANAGER: Service in charge of statistic histories. \n \n 34. DIVIDEND_MANAGER: Service in charge of dividend payment histories. \n \n @@ -93,8 +83,6 @@ def __init__(self, message): APP_NAME="scrilla" -VERSION="0.0.1" - APP_ENV = os.environ.setdefault('APP_ENV', 'local') # NOTE: Load in local.env file if not running application container. Container should @@ -108,18 +96,10 @@ def __init__(self, message): # TODO: CACHE only supports JSON currently. Future file extensions: csv and txt. FILE_EXT = os.environ.setdefault("FILE_EXT", "json") -KEEP_EXT = ".gitkeep" CACHE_DIR = os.path.join(APP_DIR, 'data', 'cache') CACHE_SQLITE_FILE = os.path.join(CACHE_DIR, 'scrilla.db') -CACHE_PRO_KEY="profile" -CACHE_PRICE_KEY="prices" -CACHE_COR_KEY="correlation" -CACHE_DIV_KEY="dividends" -CACHE_STAT_KEY="statistic" -CACHE_EQUITY_KEY="equity_statistic" - STATIC_DIR = os.path.join(APP_DIR, 'data', 'static') STATIC_TICKERS_FILE = os.path.join(STATIC_DIR, f'tickers.{FILE_EXT}') @@ -129,8 +109,6 @@ def __init__(self, message): COMMON_DIR=os.path.join(APP_DIR, 'data', 'common') COMMON_WATCHLIST_FILE=os.path.join(COMMON_DIR, f'watchlist.{FILE_EXT}') -ACCURACY, BACKOFF_PERIOD=5, 30 - # See .sample.env for more information. LOCAL_CACHE = os.environ.setdefault('LOCAL_CACHE_ENABLED', 'true').strip().lower() == 'true' @@ -150,9 +128,6 @@ def __init__(self, message): os.environ['GUI_HEIGHT'] = '800' ## FINANCIAL ALGORITHM CONFIGURATION - -OPTIMIZATION_METHOD="SLSQP" - try: FRONTIER_STEPS = int(os.environ.setdefault('FRONTIER_STEPS', '5')) except (ValueError, TypeError) as ParseError: @@ -195,15 +170,6 @@ def __init__(self, message): DEFAULT_ANALYSIS_PERIOD=100 os.environ['DEFAULT_ANALYSIS_PERIOD']=100 -# Number of days - -# TODO: candidates for static.py -PRICE_YEAR_CUTOFF=1950 -DENOMINATION = "USD" -NPV_DELTA_TOLERANCE = 0.0000001 -STAT_ECON="statistic" - - # SEE: ARG_Q_YIELD_CURVE for allowabled values RISK_FREE_RATE=os.environ.setdefault("RISK_FREE", "10-Year").strip("\"") @@ -240,8 +206,8 @@ def __init__(self, message): AV_RES_EQUITY_KEY="symbol" AV_RES_CRYPTO_FIRST_LAYER='Time Series (Digital Currency Daily)' AV_RES_CRYPTO_KEY="currency code" - AV_RES_CRYPTO_CLOSE_PRICE=f'4a. close ({DENOMINATION})' - AV_RES_CRYPTO_OPEN_PRICE=f'1a. open ({DENOMINATION})' + AV_RES_CRYPTO_CLOSE_PRICE=f'4a. close ({static.constants["DENOMINATION"]})' + AV_RES_CRYPTO_OPEN_PRICE=f'1a. open ({static.constants["DENOMINATION"]})' AV_RES_ERROR='Error Message' AV_RES_LIMIT='Note' AV_RES_DAY_LIMIT='Information' diff --git a/src/scrilla/static.py b/src/scrilla/static.py index 3ffbff99..ef306512 100644 --- a/src/scrilla/static.py +++ b/src/scrilla/static.py @@ -13,7 +13,8 @@ }, 'ASSETS':{ 'EQUITY': 'equity', - 'CRYPTO': 'crypto' + 'CRYPTO': 'crypto', + 'STAT': 'statistics' }, 'CACHE':{ 'PRICES': 'prices', @@ -28,15 +29,21 @@ 'START':'start_date', 'END':'end_date' }, - 'SERVICE':{ + 'SERVICES':{ 'PRICES':{ - 'ALPHA_VANTAGE': 'alpha_vantage', + 'ALPHA_VANTAGE': { + 'MANAGER': 'alpha_vantage' + } }, 'STATISTICS': { - 'QUANDL': 'quandl' + 'QUANDL': { + 'MANAGER': 'quandl' + } }, 'DIVIDENDS': { - 'IEX': 'iex' + 'IEX': { + 'MANAGER': 'iex' + } } } } @@ -44,7 +51,15 @@ 'ONE_TRADING_DAY': { 'EQUITY': (1/252), 'CRYPTO': (1/365) - } + }, + 'ACCURACY': 5, + 'BACKOFF_PERIOD': 30, + 'KEEP_FILE': '.gitkeep', + 'PRICE_YEAR_CUTOFF': 1950, + 'DENOMINATION': 'USD', + 'NPV_DELTA_TOLERANCE': 0.0000001, + 'OPTIMIZATION_METHOD': "SLSQP" + } def get_trading_period(asset_type):