diff options
author | montezdesousa <79287829+montezdesousa@users.noreply.github.com> | 2022-12-02 23:56:41 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2022-12-02 23:56:41 +0000 |
commit | faca7ab67d1ce5d0ae0e5c862332bcfc37f72ea9 (patch) | |
tree | 4e149ff740f7d78a81aa929b976f310d1159716e | |
parent | f47d15088fbe76533f2d29857b58f93fcd34c9d4 (diff) |
Portfolio optimization bug fixes (#3675)
* remove hcp
* add prams dict to statics
* little controller bug
* fix view bug
* avoid crash if one stock
* check params on setup
* create parameter class
* check and convert parameters using class
* change params name to avoid confusion
* create a parameter statics dict
* change some funcs names
* remove unused imports
* remove default dict
* optional type
* add multi choices
* cast only int and float
* fix completer
* fix bugs with parameter validation
* fix bugs with parameter validation
* add excel formatting
* sdk needs mapping as well, controller takes care of this in terminal
* small formating
* add some safe guard try except
* controller bugs
* oops
* change export path of parameters to portfolio folder
* add more commands to scripts
* catch optimization exception
* log errors
* black and exceptions
* add flag to test
* black
* flake8
* forgot this
* pylint
* change defaults
* fix ef default
* fix bl
* sync sdk defaults
* sync last defaults
* fix plot heat and add more choices to controller autocomplete
* patch weights
* fix wrong bool parsing
Co-authored-by: James Maslek <jmaslek11@gmail.com>
16 files changed, 1373 insertions, 699 deletions
diff --git a/openbb_terminal/miscellaneous/portfolio_examples/optimization/OpenBB_Parameters_Template_v1.0.0.xlsx b/openbb_terminal/miscellaneous/portfolio_examples/optimization/OpenBB_Parameters_Template_v1.0.0.xlsx Binary files differindex 3b8a5afb170..56ffb4d5e05 100644 --- a/openbb_terminal/miscellaneous/portfolio_examples/optimization/OpenBB_Parameters_Template_v1.0.0.xlsx +++ b/openbb_terminal/miscellaneous/portfolio_examples/optimization/OpenBB_Parameters_Template_v1.0.0.xlsx diff --git a/openbb_terminal/miscellaneous/scripts/portfolio/test_po_show_plot_rpf.openbb b/openbb_terminal/miscellaneous/scripts/portfolio/test_po_show_plot_rpf.openbb index 7146022d153..4277466898d 100644 --- a/openbb_terminal/miscellaneous/scripts/portfolio/test_po_show_plot_rpf.openbb +++ b/openbb_terminal/miscellaneous/scripts/portfolio/test_po_show_plot_rpf.openbb @@ -52,3 +52,4 @@ plot -pf name_0 -ct sector -he maxsharpe rpf -pf a,b,c rpf -pf name_0,maxsharpe_1 +exit diff --git a/openbb_terminal/miscellaneous/scripts/portfolio/test_portfolio_boolean_args.openbb b/openbb_terminal/miscellaneous/scripts/portfolio/test_portfolio_boolean_args.openbb index dec916dbccc..97abd141325 100644 --- a/openbb_terminal/miscellaneous/scripts/portfolio/test_portfolio_boolean_args.openbb +++ b/openbb_terminal/miscellaneous/scripts/portfolio/test_portfolio_boolean_args.openbb @@ -2,8 +2,9 @@ portfolio load --file Public_Equity_Orderbook.xlsx po params -file --file OpenBB_Parameters_Template_v1.0.0.xlsx +load OpenBB_Parameters_Template_v1.0.0.xlsx set ef arg tangency False arg log_returns True -exit
\ No newline at end of file +clear +exit diff --git a/openbb_terminal/miscellaneous/scripts/portfolio/test_portfolio_po.openbb b/openbb_terminal/miscellaneous/scripts/portfolio/test_portfolio_po.openbb index 979549dab13..57a3e5d812d 100644 --- a/openbb_terminal/miscellaneous/scripts/portfolio/test_portfolio_po.openbb +++ b/openbb_terminal/miscellaneous/scripts/portfolio/test_portfolio_po.openbb @@ -1,13 +1,14 @@ portfolio po load 60_40_Portfolio.xlsx +file --file example.ini maxsharpe -s 2022-10-20 minrisk -r 0.05 -maxutil -a 0.1 +maxutil -a 0.1 -ra 2 -tk 0.4 -tr 8 maxret -e 2022-11-20 maxdiv -mn 0.1 maxdecorr -lr -ef -t +ef -t -se 69 -n 10 equal -th 0.1 riskparity -p 1y relriskparity -de 0.9 @@ -21,4 +22,21 @@ plot -pf MAXSHARPE_0 -dd plot -pf MAXSHARPE_0 -rc plot -pf MAXSHARPE_0 -he rpf NAME_6 +.. +load --file market.csv +po +equal +maxsharpe -s 1995-05-07 +minrisk -r 0.08 +maxutil -a 0.1 -ra 2 -tk 0.4 -tr 8 +maxret -e 2022-11-20 +maxdiv -mn 0.1 +maxdecorr -lr -p max +ef -t -se 420 -n 0 +equal -th 0.1 +riskparity -p 1y +relriskparity -de 0.9 +hrp -cd pearson +herc -at 0.1 +nco -rm EDaR -lr exit diff --git a/openbb_terminal/portfolio/portfolio_optimization/optimizer_helper.py b/openbb_terminal/portfolio/portfolio_optimization/optimizer_helper.py index c97832ce3fb..9284da4bf35 100644 --- a/openbb_terminal/portfolio/portfolio_optimization/optimizer_helper.py +++ b/openbb_terminal/portfolio/portfolio_optimization/optimizer_helper.py @@ -2,9 +2,14 @@ __docformat__ = "numpy" import argparse +from typing import Any import pandas as pd -from openbb_terminal.portfolio.portfolio_optimization import statics +from openbb_terminal.portfolio.portfolio_optimization.statics import ( + RISK_CHOICES, + OPTIMIZATION_PARAMETERS, + TERMINAL_TEMPLATE_MAP, +) from openbb_terminal.rich_config import console # These are all the possible yfinance properties @@ -146,8 +151,45 @@ def validate_risk_measure(risk_measure: str, warning: bool = True) -> str: str Validated risk measure """ - if risk_measure.lower() in statics.RISK_CHOICES: - return statics.RISK_CHOICES[risk_measure.lower()] + if risk_measure.lower() in RISK_CHOICES: + return RISK_CHOICES[risk_measure.lower()] if warning: console.print("[yellow]Risk measure not found. Using 'MV'.[/yellow]") return "MV" + + +def get_kwarg(key: str, kwargs: dict, default: Any = None) -> Any: + """Get a key from kwargs + + If key is in kwargs, returns it. + Otherwise, if default provided, returns it. + Otherwise, if key is in OPTIMIZATION_PARAMETERS, returns it. + + Parameters + ---------- + key : str + The key to be searched + kwargs : dict + The kwargs to be searched + default : Any + The default value to be returned if the key is not found + + Returns + ------- + Any + The value of the key if it exists, else None + """ + + if key in kwargs: + return kwargs[key] + + if default: + return default + + # TODO: Remove this line when mapping between template and terminal is not needed + template_key = TERMINAL_TEMPLATE_MAP.get(key, key) + + PARAMETER = OPTIMIZATION_PARAMETERS.get(template_key) + if PARAMETER is None: + return default + return PARAMETER.default diff --git a/openbb_terminal/portfolio/portfolio_optimization/optimizer_model.py b/openbb_terminal/portfolio/portfolio_optimization/optimizer_model.py index f4a1ec0675d..2a61e251633 100644 --- a/openbb_terminal/portfolio/portfolio_optimization/optimizer_model.py +++ b/openbb_terminal/portfolio/portfolio_optimization/optimizer_model.py @@ -21,7 +21,11 @@ from scipy.interpolate import interp1d from openbb_terminal.decorators import log_start_end from openbb_terminal.portfolio.portfolio_optimization import ( yahoo_finance_model, - optimizer_helper, +) +from openbb_terminal.portfolio.portfolio_optimization.optimizer_helper import ( + get_kwarg, + validate_risk_measure, + valid_property_infos, ) from openbb_terminal.rich_config import console @@ -199,15 +203,15 @@ def get_equal_weights( Dictionary of weights where keys are the tickers, dataframe of stock returns """ - interval = kwargs.get("interval", "3y") - start_date = kwargs.get("start_date", "") - end_date = kwargs.get("end_date", "") - log_returns = kwargs.get("log_returns", False) - freq = kwargs.get("freq", "D") - maxnan = kwargs.get("maxnan", 0.05) - threshold = kwargs.get("threshold", 0.0) - method = kwargs.get("method", "time") - value = kwargs.get("value", 1.0) + interval = get_kwarg("interval", kwargs) + start_date = get_kwarg("start_date", kwargs) + end_date = get_kwarg("end_date", kwargs) + log_returns = get_kwarg("log_returns", kwargs) + freq = get_kwarg("freq", kwargs) + maxnan = get_kwarg("maxnan", kwargs) + threshold = get_kwarg("threshold", kwargs) + method = get_kwarg("method", kwargs) + value = get_kwarg("value", kwargs) stock_prices = yahoo_finance_model.process_stocks( symbols, interval, start_date, end_date @@ -271,17 +275,17 @@ def get_property_weights( Dictionary of portfolio weights or allocations """ - interval = kwargs.get("interval", "3y") - start_date = kwargs.get("start_date", "") - end_date = kwargs.get("end_date", "") - log_returns = kwargs.get("log_returns", False) - freq = kwargs.get("freq", "D") - maxnan = kwargs.get("maxnan", 0.05) - threshold = kwargs.get("threshold", 0.0) - method = kwargs.get("method", "time") - value = kwargs.get("value", 1.0) + interval = get_kwarg("interval", kwargs) + start_date = get_kwarg("start_date", kwargs) + end_date = get_kwarg("end_date", kwargs) + log_returns = get_kwarg("log_returns", kwargs) + freq = get_kwarg("freq", kwargs) + maxnan = get_kwarg("maxnan", kwargs) + threshold = get_kwarg("threshold", kwargs) + method = get_kwarg("method", kwargs) + value = get_kwarg("value", kwargs) - s_property = kwargs.get("s_property", "marketCap") + s_property = get_kwarg("s_property", kwargs, default="marketCap") stock_prices = yahoo_finance_model.process_stocks( symbols, interval, start_date, end_date @@ -427,29 +431,29 @@ def get_mean_risk_portfolio( DataFrame of stock returns. """ - interval = kwargs.get("interval", "3y") - start_date = kwargs.get("start_date", "") - end_date = kwargs.get("end_date", "") - log_returns = kwargs.get("log_returns", False) - freq = kwargs.get("freq", "D") - maxnan = kwargs.get("maxnan", 0.05) - threshold = kwargs.get("threshold", 0.0) - method = kwargs.get("method", "time") - value = kwargs.get("value", 1.0) - value_short = kwargs.get("value_short", 0.0) - - risk_measure = kwargs.get("risk_measure", "MV") - objective = kwargs.get("objective", "Sharpe") - risk_free_rate = kwargs.get("risk_free_rate", 0.0) - risk_aversion = kwargs.get("risk_aversion", 1.0) - alpha = kwargs.get("alpha", 0.05) - target_return = kwargs.get("target_return", -1.0) - target_risk = kwargs.get("target_risk", -1.0) - mean = kwargs.get("mean", "hist") - covariance = kwargs.get("covariance", "hist") - d_ewma = kwargs.get("d_ewma", 0.94) - - risk_measure = optimizer_helper.validate_risk_measure(risk_measure) + interval = get_kwarg("interval", kwargs) + start_date = get_kwarg("start_date", kwargs) + end_date = get_kwarg("end_date", kwargs) + log_returns = get_kwarg("log_returns", kwargs) + freq = get_kwarg("freq", kwargs) + maxnan = get_kwarg("maxnan", kwargs) + threshold = get_kwarg("threshold", kwargs) + method = get_kwarg("method", kwargs) + value = get_kwarg("value", kwargs) + value_short = get_kwarg("value_short", kwargs) + + risk_measure = get_kwarg("risk_measure", kwargs) + objective = get_kwarg("objective", kwargs) + risk_free_rate = get_kwarg("risk_free_rate", kwargs) + risk_aversion = get_kwarg("risk_aversion", kwargs) + alpha = get_kwarg("alpha", kwargs) + target_return = get_kwarg("target_return", kwargs) + target_risk = get_kwarg("target_risk", kwargs) + mean = get_kwarg("mean", kwargs) + covariance = get_kwarg("covariance", kwargs) + d_ewma = get_kwarg("d_ewma", kwargs) + + risk_measure = validate_risk_measure(risk_measure) stock_prices = yahoo_finance_model.process_stocks( symbols, interval, start_date, end_date @@ -468,48 +472,64 @@ def get_mean_risk_portfolio( ) return {}, pd.DataFrame() - risk_free_rate = risk_free_rate / time_factor[freq.upper()] - - # Building the portfolio object - port = rp.Portfolio(returns=stock_returns, alpha=alpha) + if stock_returns.shape[1] < 2: + console.print( + f"[red]Given the parameters could only get data for '{stock_returns.columns[0]}'.[/red]\n" + "[red]Optimization needs at least two assets.[/red]\n", + ) + return {}, pd.DataFrame() - # Estimate input parameters: - port.assets_stats(method_mu=mean, method_cov=covariance, d=d_ewma) + first_day = stock_returns.index[0].strftime("%Y-%m-%d") + console.print( + f"[yellow]First day of data respecting parameters: {first_day}[/yellow]\n" + ) - # Budget constraints - port.upperlng = value - if value_short > 0: - port.sht = True - port.uppersht = value_short - port.budget = value - value_short - else: - port.budget = value + risk_free_rate = risk_free_rate / time_factor[freq.upper()] - # Estimate optimal portfolio: - model = "Classic" - hist = True + try: + # Building the portfolio object + port = rp.Portfolio(returns=stock_returns, alpha=alpha) - if target_return > -1: - port.lowerret = float(target_return) / time_factor[freq.upper()] + # Estimate input parameters: + port.assets_stats(method_mu=mean, method_cov=covariance, d=d_ewma) - if target_risk > -1: - if risk_measure not in ["ADD", "MDD", "CDaR", "EDaR", "UCI"]: - setattr( - port, - upper_risk[risk_measure], - float(target_risk) / time_factor[freq.upper()] ** 0.5, - ) + # Budget constraints + port.upperlng = value + if value_short > 0: + port.sht = True + port.uppersht = value_short + port.budget = value - value_short else: - setattr(port, upper_risk[risk_measure], float(target_risk)) + port.budget = value - weights = port.optimization( - model=model, - rm=risk_measure, - obj=objective, - rf=risk_free_rate, - l=risk_aversion, - hist=hist, - ) + # Estimate optimal portfolio: + model = "Classic" + hist = True + + if target_return > -1: + port.lowerret = float(target_return) / time_factor[freq.upper()] + + if target_risk > -1: + if risk_measure not in ["ADD", "MDD", "CDaR", "EDaR", "UCI"]: + setattr( + port, + upper_risk[risk_measure], + float(target_risk) / time_factor[freq.upper()] ** 0.5, + ) + else: + setattr(port, upper_risk[risk_measure], float(target_risk)) + + weights = port.optimization( + model=model, + rm=risk_measure, + obj=objective, + rf=risk_free_rate, + l=risk_aversion, + hist=hist, + ) + + except Exception as _: + weights = None if weights is not None: weights = weights.round(5) @@ -1032,19 +1052,19 @@ def get_max_diversification_portfolio( DataFrame of stock returns. """ - interval = kwargs.get("interval", "3y") - start_date = kwargs.get("start_date", "") - end_date = kwargs.get("end_date", "") - log_returns = kwargs.get("log_returns", False) - freq = kwargs.get("freq", "D") - maxnan = kwargs.get("maxnan", 0.05) - threshold = kwargs.get("threshold", 0.0) - method = kwargs.get("method", "time") - value = kwargs.get("value", 1.0) - value_short = kwargs.get("value_short", 0.0) + interval = get_kwarg("interval", kwargs) + start_date = get_kwarg("start_date", kwargs) + end_date = get_kwarg("end_date", kwargs) + log_returns = get_kwarg("log_returns", kwargs) + freq = get_kwarg("freq", kwargs) + maxnan = get_kwarg("maxnan", kwargs) + threshold = get_kwarg("threshold", kwargs) + method = get_kwarg("method", kwargs) + value = get_kwarg("value", kwargs) + value_short = get_kwarg("value_short", kwargs) - covariance = kwargs.get("covariance", "hist") - d_ewma = kwargs.get("d_ewma", 0.94) + covariance = get_kwarg("covariance", kwargs) + d_ewma = get_kwarg("d_ewma", kwargs) stock_prices = yahoo_finance_model.process_stocks( symbols, interval, start_date, end_date @@ -1058,24 +1078,29 @@ def get_max_diversification_portfolio( method=method, ) - # Building the portfolio object - port = rp.Portfolio(returns=stock_returns) + try: + # Building the portfolio object + port = rp.Portfolio(returns=stock_returns) - # Estimate input parameters: - port.assets_stats(method_mu="hist", method_cov=covariance, d=d_ewma) - port.mu = stock_returns.std().to_frame().T + # Estimate input parameters: + port.assets_stats(method_mu="hist", method_cov=covariance, d=d_ewma) + port.mu = stock_returns.std().to_frame().T - # Budget constraints - port.upperlng = value - if value_short > 0: - port.sht = True - port.uppersht = value_short - port.budget = value - value_short - else: - port.budget = value + # Budget constraints + port.upperlng = value + if value_short > 0: + port.sht = True + port.uppersht = value_short + port.budget = value - value_short + else: + port.budget = value - # Estimate optimal portfolio: - weights = port.optimization(model="Classic", rm="MV", obj="Sharpe", rf=0, hist=True) + # Estimate optimal portfolio: + weights = port.optimization( + model="Classic", rm="MV", obj="Sharpe", rf=0, hist=True + ) + except Exception as _: + weights = None if weights is not None: weights = weights.round(5) @@ -1151,19 +1176,19 @@ def get_max_decorrelation_portfolio( DataFrame of stock returns. """ - interval = kwargs.get("interval", "3y") - start_date = kwargs.get("start_date", "") - end_date = kwargs.get("end_date", "") - log_returns = kwargs.get("log_returns", False) - freq = kwargs.get("freq", "D") - maxnan = kwargs.get("maxnan", 0.05) - threshold = kwargs.get("threshold", 0.0) - method = kwargs.get("method", "time") - value = kwargs.get("value", 1.0) - value_short = kwargs.get("value_short", 0.0) + interval = get_kwarg("interval", kwargs) + start_date = get_kwarg("start_date", kwargs) + end_date = get_kwarg("end_date", kwargs) + log_returns = get_kwarg("log_returns", kwargs) + freq = get_kwarg("freq", kwargs) + maxnan = get_kwarg("maxnan", kwargs) + threshold = get_kwarg("threshold", kwargs) + method = get_kwarg("method", kwargs) + value = get_kwarg("value", kwargs) + value_short = get_kwarg("value_short", kwargs) - covariance = kwargs.get("covariance", "hist") - d_ewma = kwargs.get("d_ewma", 0.94) + covariance = get_kwarg("covariance", kwargs) + d_ewma = get_kwarg("d_ewma", kwargs) stock_prices = yahoo_finance_model.process_stocks( symbols, interval, start_date, end_date @@ -1177,26 +1202,30 @@ def get_max_decorrelation_portfolio( method=method, ) - # Building the portfolio object - port = rp.Portfolio(returns=stock_returns) + try: - # Estimate input parameters: - port.assets_stats(method_mu="hist", method_cov=covariance, d=d_ewma) - port.cov = rp.cov2corr(port.cov) + # Building the portfolio object + port = rp.Portfolio(returns=stock_returns) - # Budget constraints - port.upperlng = value - if value_short > 0: - port.sht = True - port.uppersht = value_short - port.budget = value - value_short - else: - port.budget = value + # Estimate input parameters: + port.assets_stats(method_mu="hist", method_cov=covariance, d=d_ewma) + port.cov = rp.cov2corr(port.cov) - # Estimate optimal portfolio: - weights = port.optimization( - model="Classic", rm="MV", obj="MinRisk", rf=0, hist=True - ) + # Budget constraints + port.upperlng = value + if value_short > 0: + port.sht = True + port.uppersht = value_short + port.budget = value - value_short + else: + port.budget = value + + # Estimate optimal portfolio: + weights = port.optimization( + model="Classic", rm="MV", obj="MinRisk", rf=0, hist=True + ) + except Exception as _: + weights = None if weights is not None: weights = weights.round(5) @@ -1284,26 +1313,26 @@ def get_black_litterman_portfolio( DataFrame of stock returns. """ - interval = kwargs.get("interval", "3y") - start_date = kwargs.get("start_date", "") - end_date = kwargs.get("end_date", "") - log_returns = kwargs.get("log_returns", False) - freq = kwargs.get("freq", "D") - maxnan = kwargs.get("maxnan", 0.05) - threshold = kwargs.get("threshold", 0.0) - method = kwargs.get("method", "time") - value = kwargs.get("value", 1.0) - value_short = kwargs.get("value_short", 0.0) - - benchmark = kwargs.get("benchmark", None) - p_views = kwargs.get("p_views", None) - q_views = kwargs.get("q_views", None) - objective = kwargs.get("objective", "Sharpe") - risk_free_rate = kwargs.get("risk_free_rate", 0) - risk_aversion = kwargs.get("risk_aversion", 1) - delta = kwargs.get("delta", None) - equilibrium = kwargs.get("equilibrium", True) - optimize = kwargs.get("optimize", True) + interval = get_kwarg("interval", kwargs) + start_date = get_kwarg("start_date", kwargs) + end_date = get_kwarg("end_date", kwargs) + log_returns = get_kwarg("log_returns", kwargs) + freq = get_kwarg("freq", kwargs) + maxnan = get_kwarg("maxnan", kwargs) + threshold = get_kwarg("threshold", kwargs) + method = get_kwarg("method", kwargs) + value = get_kwarg("value", kwargs) + value_short = get_kwarg("value_short", kwargs) + + benchmark = get_kwarg("benchmark", kwargs) + p_views = get_kwarg("p_views", kwargs) + q_views = get_kwarg("q_views", kwargs) + objective = get_kwarg("objective", kwargs) + risk_free_rate = get_kwarg("risk_free_rate", kwargs) + risk_aversion = get_kwarg("risk_aversion", kwargs) + delta = get_kwarg("delta", kwargs) + equilibrium = get_kwarg("equilibrium", kwargs) + optimize = get_kwarg("optimize", kwargs) stock_prices = yahoo_finance_model.process_stocks( symbols, interval, start_date, end_date @@ -1349,32 +1378,35 @@ def get_black_litterman_portfolio( weights = pd.DataFrame(weights) if optimize: - # Building the portfolio object - port = rp.Portfolio(returns=stock_returns) - - # Estimate input parameters: - port.assets_stats(method_mu="hist", method_cov="hist") - port.mu_bl = pd.DataFrame(mu).T - port.cov_bl = pd.DataFrame(cov) - - # Budget constraints - port.upperlng = value - if value_short > 0: - port.sht = True - port.uppersht = value_short - port.budget = value - value_short - else: - port.budget = value - - # Estimate optimal portfolio: - weights = port.optimization( - model="BL", - rm="MV", - obj=objective, - rf=risk_free_rate, - l=risk_aversion, - hist=True, - ) + try: + # Building the portfolio object + port = rp.Portfolio(returns=stock_returns) + + # Estimate input parameters: + port.assets_stats(method_mu="hist", method_cov="hist") + port.mu_bl = pd.DataFrame(mu).T + port.cov_bl = pd.DataFrame(cov) + + # Budget constraints + port.upperlng = value + if value_short > 0: + port.sht = True + port.uppersht = value_short + port.budget = value - value_short + else: + port.budget = value + + # Estimate optimal portfolio: + weights = port.optimization( + model="BL", + rm="MV", + obj=objective, + rf=risk_free_rate, + l=risk_aversion, + hist=True, + ) + except Exception as _: + weights = None if weights is not None: weights = weights.round(5) @@ -1478,25 +1510,25 @@ def get_ef( frontier, mu, cov, stock_returns, weights, X1, Y1, port """ - interval = kwargs.get("interval", "3y") - start_date = kwargs.get("start_date", "") - end_date = kwargs.get("end_date", "") - log_returns = kwargs.get("log_returns", False) - freq = kwargs.get("freq", "D") - maxnan = kwargs.get("maxnan", 0.05) - threshold = kwargs.get("threshold", 0.05) - method = kwargs.get("method", "time") - value = kwargs.get("value", 1.0) - value_short = kwargs.get("value_short", 0.0) - - risk_measure = kwargs.get("risk_measure", "MV") - risk_free_rate = kwargs.get("risk_free_rate", 0.0) - alpha = kwargs.get("alpha", 0.05) - n_portfolios = kwargs.get("n_portfolios", 100) - seed = kwargs.get("seed", 123) + interval = get_kwarg("interval", kwargs) + start_date = get_kwarg("start_date", kwargs) + end_date = get_kwarg("end_date", k |