def get_return_vol(data, scale=1, ret=False, plotit=False): """ Calculate return and volatility of given data. input: data a DataFrame or Series with ETF prices scale factor if convert to annual return. Default 1 12 for monthly, 52 for weekly, and 252 for daily data ret indicate whether the input data is return or price (default). default False, and simple return is calculated plotit indicate whether to make plots return a DataFrame with tickers as index and columns: "Return", "Volatility" """ from basic.mathe import covariance logger = logging.getLogger(__name__) if data.ndim == 1: data = data.to_frame() if ret: rts = data else: rts = get_returns(data, 'simple') ret = rts.mean().values * scale vol = rts.apply(lambda x: np.sqrt(covariance(x) * scale)).values if plotit: from invest.plot import return_vol return_vol(ret, vol, data.columns) return pd.DataFrame({"Return": ret, "Volatility": vol}, index=data.columns)
def mean_variance_optimization(mean, variance, returns=None, riskfree=None, alloc_lim=None, short_sell=False, strict=True, verbose=True, plotit=False): """ Calculate the portfolio with the lowest risk given returns. input: mean a numpy array (n,) of mean values variance a numpy array (n,n) of the covariance matrix returns a list of return values to be calculated, default mean(mean) riskfree the risk-free rate, default None. If not None, then a risk free stock is added for consideration alloc_lim a list of two numbers indicating the percentage range of one stock. Default None, i.e. no limit constraint. strict indicate whether the return value should be strict, default True verbose indicate whether to print progress bar, default True. plotit indicate whether to make Return-Volatility plots, default False. Returns vol, alloc. A numpy array (m,) of minimum volatility and a numpy array (n,m) of allocations. Here m is the number of given returns. """ logger = logging.getLogger(__name__) if returns is None: returns = [np.mean(mean)] returns = np.array(returns) if alloc_lim is None: var_inv = np.linalg.inv(variance) ones = np.ones(mean.shape) a = mean.dot(var_inv).dot(mean) b = mean.dot(var_inv).dot(ones) c = ones.dot(var_inv).dot(ones) lambda1 = (c * returns - b) / (a * c - b * b) lambda2 = (-b * returns + a) / (a * c - b * b) alloc = np.kron(lambda1, var_inv.dot(mean)) + np.kron( lambda2, var_inv.dot(ones)) vol = np.sqrt(c * returns**2 - 2 * b * returns + a) if plotit: import matplotlib.pyplot as plt from invest.plot import return_vol return_vol(mean, np.sqrt(np.diag(variance)), [''] * len(returns)) plt.plot(aloc.Volatility * 100, aloc.Return * 100, '.-') xs = np.linspace(np.min(mean), np.max(mean), 200) ys = np.sqrt(c * xs**2 - 2 * b * xs + a) plt.plot(aloc.Volatility[arg] * 100, aloc.Return[arg] * 100, 'rX', markersize=12) print("Max Sharpe ratio is {:.2f}".format(sharpe[arg])) if riskfree is not None: var_inv = np.linalg.inv(variance) vol = np.abs(returns - riskfree) / np.sqrt( (mean - riskfree).dot(var_inv).dot(mean - riskfree)) alloc = np.kron(vol * vol / (returns - riskfree), var_inv.dot(mean - riskfree))
def initial_plot(self, column='Adj Close', style='week', start='2015-1-1'): self._logger_.debug("Initialize plots for portfolio window") tickers = self.select.get_right() self._data_ = get_returns(resample(read_portfolio(tickers, column=column, start=start), column=None, style=style, method='close'), style='simple', fillna=False) self._data_['benchmark'] = 0 data = self._data_.iloc[-52:, :-1].dropna(axis=1, how='any') rv = get_return_vol(pd.concat([data * 3, -data], axis=1), scale=52, ret=True, plotit=False) fig = return_vol(rv.Return, rv.Volatility, rv.index) fig.axes[0].plot([0], [0], 'r*') return fig, pie_plot([10, 6], labels=['a', 'b'])
def minimize_risk(data, returns=None, strict=True, riskfree=None, max_alloc=1, short_sell=False, scale=1, ret=False, verbose=True, plotit=False): """ Calculate the portfolio with the lowest risk given returns. input: data a DataFrame with ETF prices or returns returns a list of return values to use strict indicate whether the return value should be strict, default True riskfree the risk-free return rate, default None. If not None, then a risk free stock is added for consideration max_alloc a number from 0 to 1. The maximum percentage of one stock short_sell indicate whether to allow short sell. Default False. scale factor if convert to annual return. Default 1. 12 for monthly, 52 for weekly, 252 for daily ret indicate whether the input data is return or price (default). default False, and simple return is calculated verbose indicate whether to print progress bar, default True. plotit indicate whether to make Return-Volatility plots, default False. Returns a DataFrame with columns as [tickers,Volatility,Return] and index as targeted returns """ logger = logging.getLogger(__name__) if ret: weekly = data else: weekly = get_returns(data, 'simple') ret = weekly.mean().values * scale cov = weekly.cov().values * scale if short_sell: return pd.DataFrame() n = data.shape[1] if riskfree is None: aloc = pd.DataFrame( columns=np.append(data.columns, ['Volatility', 'Return'])) bounds = [(0, max_alloc)] * n else: ret = np.append(ret, riskfree) cov = np.hstack( [np.vstack([cov, np.zeros([1, n])]), np.zeros([n + 1, 1])]) aloc = pd.DataFrame(columns=np.append( data.columns, ['risk-free', 'Volatility', 'Return'])) bounds = [(0, max_alloc)] * n + [(0, 1)] n += 1 if returns is None: returns = np.linspace(min(ret), max(ret), 25, endpoint=True) from scipy.optimize import minimize from basic.useful import progress_bar def func(alpha): def loss(x): return x.dot(cov).dot(x) def jac(x): return cov.dot(x) * 2 cons1 = { 'type': 'eq', 'fun': lambda x: np.ones(n).dot(x) - 1, 'jac': lambda x: np.ones(n) } types = 'eq' if not strict: types = 'ineq' cons2 = { 'type': types, 'fun': lambda x: ret.dot(x) - alpha, 'jac': lambda x: ret } x = minimize(loss, np.ones(n) / n, jac=jac, constraints=[cons1, cons2], bounds=bounds, method='SLSQP') aloc.loc[alpha, :] = np.append(np.round( x['x'], 4), [np.sqrt(x['fun']), ret.dot(x['x'])]) return "" progress_bar(returns, func, disable=not verbose) if plotit: import matplotlib.pyplot as plt from invest.plot import return_vol vol = np.sqrt(np.diag(cov)) return_vol(ret, vol, data.columns) plt.plot(aloc.Volatility * 100, aloc.Return * 100, '.-') sharpe = aloc.Return / aloc.Volatility arg = sharpe.argmax() plt.plot(aloc.Volatility[arg] * 100, aloc.Return[arg] * 100, 'rX', markersize=12) print("Max Sharpe ratio is {:.2f}".format(sharpe[arg])) return aloc.astype(float)