def gmv_monthly(self) -> Tuple[float, float]: """ Returns the monthly risk and return of the Global Minimum Volatility portfolio """ return ( Frame.get_portfolio_risk(self.gmv_weights, self.ror), Frame.get_portfolio_mean_return(self.gmv_weights, self.ror), )
def max_cagr_asset(self) -> dict: """ Find an asset with max CAGR. """ max_asset_cagr = Frame.get_cagr(self.ror).max() ticker_with_largest_cagr = Frame.get_cagr(self.ror).nlargest(1, keep='first').index.values[0] return {'max_asset_cagr': max_asset_cagr, 'ticker_with_largest_cagr': ticker_with_largest_cagr, 'list_position': self.symbols.index(ticker_with_largest_cagr) }
def get_ef_points(self): """ Get all the points for the Efficient Frontier running optimizer. If verbose=True calculates elapsed time for each point and the total elapsed time. """ main_start_time = time.time() df = pd.DataFrame() # left part of the EF for i, target_cagr in enumerate(self.target_cagr_range_left): start_time = time.time() row = self.minimize_risk(target_cagr) df = df.append(row, ignore_index=True) end_time = time.time() if self.verbose: print(f"left EF point #{i + 1}/{self.n_points} is done in {end_time - start_time:.2f} sec.") # right part of the EF range_right = self.target_cagr_range_right if range_right is not None: # range_right can be a DataFrame. Must put an explicit "is not None" n = len(range_right) for i, target_cagr in enumerate(range_right): start_time = time.time() row = self.maximize_risk(target_cagr) df = df.append(row, ignore_index=True) end_time = time.time() if self.verbose: print(f"right EF point #{i + 1}/{n} is done in {end_time - start_time:.2f} sec.") df = Frame.change_columns_order(df, ['Risk', 'CAGR']) main_end_time = time.time() if self.verbose: print(f"Total time taken is {(main_end_time - main_start_time) / 60:.2f} min.") self._ef_points = df
def target_cagr_range_left(self) -> np.ndarray: """ Full range of cagr values (from min to max). """ max_cagr = self.global_max_return_portfolio['CAGR'] min_cagr = Frame.get_cagr(self.ror).min() return np.linspace(min_cagr, max_cagr, self.n_points)
def optimize_return(self, option: str = "max") -> dict: """ Finds global max or min for the rate of return. Returns monthly values for the risk, mean return and the weights. 'max' - search for global maximum 'min' - search for global minimum """ n = self.ror.shape[1] # Number of assets init_guess = np.repeat(1 / n, n) # Set the objective function if option == "max": def objective_function(w, ror): month_return_value = Frame.get_portfolio_mean_return(w, ror) return -month_return_value elif option == "min": def objective_function(w, ror): month_return_value = Frame.get_portfolio_mean_return(w, ror) return month_return_value else: raise ValueError('option should be "max" or "min"') # construct the constraints weights_sum_to_1 = {"type": "eq", "fun": lambda weights: np.sum(weights) - 1} weights = minimize( objective_function, init_guess, args=(self.ror,), method="SLSQP", constraints=(weights_sum_to_1,), bounds=self.bounds, options={ "disp": False, "ftol": 1e-08, }, # 1e-06 is not enough to optimize monthly returns ) if weights.success: portfolio_risk = Frame.get_portfolio_risk(weights.x, self.ror) if option.lower() == "max": optimized_return = -weights.fun else: optimized_return = weights.fun point = { "Weights": weights.x, "Mean_return_monthly": optimized_return, "Risk_monthly": portfolio_risk, } return point else: raise Exception("No solutions where found")
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 ef_points(self) -> pd.DataFrame: """ DataFrame of weights and risk/return values for the Efficient Frontier. The columns of the DataFrame: - weights - mean return - CAGR - risk (std) All the values are annualized. """ target_rs = self.mean_return_range df = pd.DataFrame(dtype="float") for x in target_rs: row = self.minimize_risk(x, monthly_return=True) df = df.append(row, ignore_index=True) df = Frame.change_columns_order(df, ["Risk", "Mean return", "CAGR"]) return df
def max_cagr_asset_right_to_max_cagr(self) -> Optional[dict]: """ The asset with max CAGR lieing to the right of the global max CAGR point (risk should be more than self.max_return['Risk']). Global max return point should not be an asset. """ tolerance = 0.01 # assets CAGR should be less than max CAGR with certain tolerance global_max_cagr_is_not_asset = (self.get_cagr() < self.global_max_return_portfolio['CAGR'] * (1 - tolerance)).all() if global_max_cagr_is_not_asset: condition = self.risk_annual.values > self.global_max_return_portfolio['Risk'] ror_selected = self.ror.loc[:, condition] if not ror_selected.empty: cagr_selected = Frame.get_cagr(ror_selected) max_asset_cagr = cagr_selected.max() ticker_with_largest_cagr = cagr_selected.nlargest(1, keep='first').index.values[0] return {'max_asset_cagr': max_asset_cagr, 'ticker_with_largest_cagr': ticker_with_largest_cagr, 'list_position': self.symbols.index(ticker_with_largest_cagr) }
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 objective_function(w): return Frame.get_portfolio_risk(w, ror)
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
def objective_function(w, ror): month_return_value = Frame.get_portfolio_mean_return(w, ror) return month_return_value