def objective_function(w): # annual risk ts = Rebalance.rebalanced_portfolio_return_ts(w, self.ror, period=self.reb_period) risk_monthly = ts.std() mean_return = ts.mean() result = - Float.annualize_risk(risk_monthly, mean_return) return result
def gmv_annualized(self) -> Tuple[float, float]: """ Returns the annualized risk and return of the Global Minimum Volatility portfolio """ return ( Float.annualize_risk(self.gmv_monthly[0], self.gmv_monthly[1]), Float.annualize_return(self.gmv_monthly[1]), )
def gmv_annual_values(self) -> Tuple[float, float]: """ Returns the annual risk (std) and CAGR of the Global Minimum Volatility portfolio. """ returns = Rebalance.rebalanced_portfolio_return_ts(self.gmv_annual_weights, self.ror, period=self.reb_period) return ( Float.annualize_risk(returns.std(), returns.mean()), (returns + 1.0).prod() ** (_MONTHS_PER_YEAR / returns.shape[0]) - 1.0, )
def target_risk_range(self) -> np.ndarray: """ Range of annual risk values (from min risk to max risk). """ min_std = self.gmv_annual_values[0] ticker_with_largest_risk = self.ror.std().nlargest(1, keep='first').index.values[0] max_std_monthly = self.ror.std().max() mean_return = self.ror.loc[:, ticker_with_largest_risk].mean() max_std = Float.annualize_risk(max_std_monthly, mean_return) return np.linspace(min_std, max_std, self.n_points)
def get_monte_carlo(self, n: int = 100) -> pd.DataFrame: """ Generate N random risk / cagr point for rebalanced portfolios. Risk and cagr are calculated for a set of random weights. """ weights_df = Float.get_random_weights(n, self.ror.shape[1]) # Portfolio risk and cagr for each set of weights portfolios_ror = weights_df.aggregate(Rebalance.rebalanced_portfolio_return_ts, ror=self.ror, period=self.reb_period) random_portfolios = pd.DataFrame() for _, data in portfolios_ror.iterrows(): risk_monthly = data.std() mean_return = data.mean() risk = Float.annualize_risk(risk_monthly, mean_return) cagr = Frame.get_cagr(data) row = { 'Risk': risk, 'CAGR': cagr } random_portfolios = random_portfolios.append(row, ignore_index=True) return random_portfolios
def get_monte_carlo(self, n: int = 100, kind: str = "mean") -> pd.DataFrame: """ Generate N random risk / cagr point for portfolios. Risk and cagr are calculated for a set of random weights. """ weights_series = Float.get_random_weights(n, self.ror.shape[1]) # Portfolio risk and return for each set of weights random_portfolios = pd.DataFrame(dtype=float) for weights in weights_series: risk_monthly = Frame.get_portfolio_risk(weights, self.ror) mean_return_monthly = Frame.get_portfolio_mean_return(weights, self.ror) risk = Float.annualize_risk(risk_monthly, mean_return_monthly) mean_return = Float.annualize_return(mean_return_monthly) if kind.lower() == "cagr": cagr = Float.approx_return_risk_adjusted(mean_return, risk) row = dict(Risk=risk, CAGR=cagr) elif kind.lower() == "mean": row = dict(Risk=risk, Return=mean_return) else: raise ValueError('kind should be "mean" or "cagr"') random_portfolios = random_portfolios.append(row, ignore_index=True) return random_portfolios
def global_max_return_portfolio(self) -> dict: """ Returns the weights and risk / CAGR of the maximum return portfolio point. """ ror = self.ror period = self.reb_period n = self.ror.shape[1] # Number of assets init_guess = np.repeat(1 / n, n) bounds = ((0.0, 1.0),) * n # Set the objective function def objective_function(w): # Accumulated return for rebalanced portfolio time series objective_function.returns = Rebalance.rebalanced_portfolio_return_ts(w, ror, period=period) accumulated_return = (objective_function.returns + 1.).prod() - 1. return - accumulated_return # construct the constraints weights_sum_to_1 = {'type': 'eq', 'fun': lambda weights: np.sum(weights) - 1 } weights = minimize(objective_function, init_guess, method='SLSQP', options={'disp': False}, constraints=(weights_sum_to_1,), bounds=bounds) portfolio_ts = objective_function.returns mean_return = portfolio_ts.mean() portfolio_risk = portfolio_ts.std() point = { 'Weights': weights.x, 'CAGR': (1 - weights.fun) ** (_MONTHS_PER_YEAR / self.ror.shape[0]) - 1, 'Risk': Float.annualize_risk(portfolio_risk, mean_return), 'Risk_monthly': portfolio_risk } return point
def objective_function(w): ts = Rebalance.rebalanced_portfolio_return_ts(w, ror, period=period) mean_return = ts.mean() risk = ts.std() return Float.annualize_risk(risk=risk, mean_return=mean_return)
def minimize_risk( self, target_return: float, monthly_return: bool = False, tolerance: float = 1e-08, ) -> Dict[str, float]: """ Finds minimal risk given the target return. Returns a "point" with monthly values: - weights - mean return - CAGR - risk (std) Target return is a monthly or annual value: monthly_return = False / True tolerance - sets the accuracy for the solver """ if not monthly_return: target_return = Float.get_monthly_return_from_annual(target_return) ror = self.ror n = ror.shape[1] # number of assets init_guess = np.repeat(1 / n, n) # initial weights def objective_function(w): return Frame.get_portfolio_risk(w, ror) # construct the constraints weights_sum_to_1 = {"type": "eq", "fun": lambda weights: np.sum(weights) - 1} return_is_target = { "type": "eq", "fun": lambda weights: target_return - Frame.get_portfolio_mean_return(weights, ror), } weights = minimize( objective_function, init_guess, method="SLSQP", constraints=(weights_sum_to_1, return_is_target), bounds=self.bounds, options={"disp": False, "ftol": tolerance}, ) if weights.success: # Calculate point of EF given optimal weights risk = weights.fun # Annualize risk and return a_r = Float.annualize_return(target_return) a_risk = Float.annualize_risk(risk=risk, mean_return=target_return) # # Risk adjusted return approximation # r_gmean = Float.approx_return_risk_adjusted(mean_return=a_r, std=a_risk) # CAGR calculation portfolio_return_ts = Frame.get_portfolio_return_ts(weights.x, ror) cagr = Frame.get_cagr(portfolio_return_ts) if not self.labels_are_tickers: asset_labels = list(self.names.values()) else: asset_labels = self.symbols point = {x: y for x, y in zip(asset_labels, weights.x)} point["Mean return"] = a_r point["CAGR"] = cagr # point['CAGR (approx)'] = r_gmean point["Risk"] = a_risk else: raise Exception("No solutions were found") return point