def get_market_window(self, window, day): # Compose historical market window, including opening prices available = util.get_avail_stocks(self.data.get_op()[day - window + 1, :]) if (day <= window - 1): available = util.get_avail_stocks(self.data_train.get_op()[-1, :]) available_inds = np.asarray( [i for i in range(497) if available[i] > 0]) if (day >= window - 1): op = self.data.get_op()[day - window + 1:day + 1, available_inds] hi = self.data.get_hi()[day - window + 1:day, available_inds] lo = self.data.get_lo()[day - window + 1:day, available_inds] cl = self.data.get_cl()[day - window + 1:day, available_inds] elif (self.data_train is not None): op = self.data_train.get_op()[day - window + 1:, available_inds] op = np.concatenate((op, self.data.get_op()[:day + 1, available_inds])) hi = self.data_train.get_hi()[day - window + 1:, available_inds] hi = np.concatenate((hi, self.data.get_hi()[:day, available_inds])) lo = self.data_train.get_lo()[day - window + 1:, available_inds] lo = np.concatenate((lo, self.data.get_lo()[:day, available_inds])) cl = self.data_train.get_cl()[day - window + 1:, available_inds] cl = np.concatenate((cl, self.data.get_cl()[:day, available_inds])) else: raise 'NPM called get_market_window with day<window' history = np.concatenate((op, hi, lo, cl)).T return history
def get_market_window(self, window, day): # Compose historical market window, including opening prices available = util.get_avail_stocks(self.data.get_op()[day - window + 1, :]) if(day <= window-1): available = util.get_avail_stocks(self.data_train.get_op()[-1, :]) available_inds = np.asarray([i for i in range(497) if available[i] > 0]) if(day >= window-1): op = self.data.get_op()[day-window+1:day+1,available_inds] hi = self.data.get_hi()[day-window+1:day,available_inds] lo = self.data.get_lo()[day-window+1:day,available_inds] cl = self.data.get_cl()[day-window+1:day,available_inds] elif(self.data_train is not None): op = self.data_train.get_op()[day-window+1:, available_inds] op = np.concatenate((op,self.data.get_op()[:day+1,available_inds])) hi = self.data_train.get_hi()[day-window+1:, available_inds] hi = np.concatenate((hi,self.data.get_hi()[:day,available_inds])) lo = self.data_train.get_lo()[day-window+1:, available_inds] lo = np.concatenate((lo,self.data.get_lo()[:day,available_inds])) cl = self.data_train.get_cl()[day-window+1:, available_inds] cl = np.concatenate((cl,self.data.get_cl()[:day,available_inds])) else: raise 'NPM called get_market_window with day<window' history = np.concatenate((op, hi, lo, cl)).T return history
def get_new_allocation(self, cur_day): """ :param cur_day: :return: A (1 x num_stocks) array of fractions. Each fraction represents the amount of the money should be invested in that stock at the end of the day. If we haven't reached the start date (the day when we have confidence in our mean/covariance info), return a uniform portfolio. Otherwise, perform nonparametric markowitz. """ if cur_day == 0: cur_day_op = self.data.get_op(relative=False)[cur_day, :] # opening prices on |cur_day| return util.get_uniform_allocation(self.num_stocks, cur_day_op) elif(cur_day < self.start_date): available = util.get_avail_stocks(self.data.get_op()[cur_day,:]) num_available = np.sum(available) new_allocation = 1.0/num_available * np.asarray(available) else: k = self.k available = util.get_avail_stocks(self.data.get_op()[cur_day-self.window_len+1,:]) available_inds = util.get_available_inds(available) history = self.get_market_window(self.window_len, cur_day) num_available = history.shape[0] neighbors = np.zeros((num_available, k)) history_norms = np.diag(np.dot(history, history.T)) # Compute k nearest neighbors for each stock for i in range(num_available): stock = history[i,:] neighbors[i,:] = util.k_nearest_neighbors(stock, history, k, history_norms) # Solve optimization problem l = self.risk_aversion neighbors = neighbors.astype(int) b = Variable(num_available) c = Variable(num_available) d = 0 e = 0 for i in range(num_available): inds = available_inds[neighbors[i,:]] m_i = self.mu[inds] #self.mu[inds] S_i = self.sigma[inds,:] S_i = S_i[:,inds] d += (b[i]*m_i).T*np.ones(k) e += quad_form(b[i]*np.ones(k), S_i) constraints = [c>= b, c >= -b, sum_entries(c)==1] #, b <= self.cap, b >= -self.cap] #[b >= 0, np.ones(num_available).T*b == 1] objective = Maximize(d-l*e) prob = Problem(objective, constraints) prob.solve() new_allocation = np.zeros(len(available)) new_allocation[available_inds] = b.value return new_allocation
def get_market_window(self, window, day): # Compose historical market window, including opening prices available = util.get_avail_stocks(self.data.get_op()[day-window+1,:]) available_inds = np.asarray([i for i in range(497) if available[i] > 0]) op = self.data.get_op()[day-window+1:day+1,available_inds] hi = self.data.get_hi()[day-window+1:day,available_inds] lo = self.data.get_lo()[day-window+1:day,available_inds] cl = self.data.get_cl()[day-window+1:day,available_inds] history = np.concatenate((op, hi, lo, cl)).T return history
def get_market_window(self, window, day): # Compose historical market window, including opening prices available = util.get_avail_stocks(self.data.get_op()[day - window + 1, :]) available_inds = np.asarray( [i for i in range(497) if available[i] > 0]) op = self.data.get_op()[day - window + 1:day + 1, available_inds] hi = self.data.get_hi()[day - window + 1:day, available_inds] lo = self.data.get_lo()[day - window + 1:day, available_inds] cl = self.data.get_cl()[day - window + 1:day, available_inds] history = np.concatenate((op, hi, lo, cl)).T return history
def get_new_allocation(self, cur_day): """ :param cur_day: :return: A (1 x num_stocks) array of fractions. Each fraction represents the amount of the money should be invested in that stock at the end of the day. """ stocks_avail = util.get_avail_stocks(self.data.op[cur_day, :]) num_stocks_avail = len(stocks_avail.keys()) new_allocation = np.zeros(self.num_stocks) for stock in stocks_avail.keys(): new_allocation[stock] = 1.0 / num_stocks_avail return new_allocation
def update_portfolio(self, cur_day, new_allocation): """ A naive approach. Rebalances the stock holdings so that an equal amount of money is invested into each stock. :param cur_day: :param new_allocation: A (1 x num_stocks) array of fractions. Each fraction represents the amount of the money should be invested in that stock at the end of the day. :return: List of the new share holdings """ #print 'Reinvesting into a uniform portfolio for day: ', cur_day close_prices = self.data.cl[cur_day, :] total_dollars = util.dollars_in_stocks(self.shares_holding, close_prices) self.dollars_hist.append(total_dollars) if cur_day % self.interval == 0: # Apply the CRP approach num_stocks_to_invest_in = np.count_nonzero(new_allocation) dist_from_uniform = util.dollars_away_from_uniform( self.shares_holding, close_prices, 1.0 * total_dollars / num_stocks_to_invest_in) total_trans_costs = dist_from_uniform * cost_per_trans_per_dollar rebalanced_dollars = 1.0 * ( total_dollars - total_trans_costs) / num_stocks_to_invest_in stocks_avail = util.get_avail_stocks(self.data.op[cur_day, :]) new_share_holdings = np.zeros(self.num_stocks) for idx in stocks_avail.keys(): new_share_holdings[ idx] = 1.0 * rebalanced_dollars / close_prices[idx] return new_share_holdings else: # Don't apply the CRP approach on this day prev_close_prices = np.nan_to_num(self.data.cl[cur_day - 1, :]) rel_change = np.nan_to_num( np.divide(close_prices, prev_close_prices)) new_share_holdings = np.multiply(self.shares_holding, rel_change) return new_share_holdings
def get_new_allocation(self, day, init=False): """ Determine the new desired allocation for the end of |day| using the OLMAR algorithm. :param day: :param init: If True, this portfolio is being initialized today. :return: """ "" if init and self.data_train is None: # Use uniform allocation cur_day_op = self.data.get_op(relative=False)[day, :] # opening prices on |cur_day| return util.get_uniform_allocation(self.num_stocks, cur_day_op) predicted_price_rel = self.predict_price_relatives(day) # Compute mean price relative of available stocks (x bar at t+1) today_op = self.data.get_op(relative=False)[day, :] avail_stocks = util.get_avail_stocks(today_op) avail_idxs = util.get_available_inds(avail_stocks) ppr_avail = predicted_price_rel[avail_idxs] # predicted price relatives of available stocks mean_price_rel = np.mean(ppr_avail) lam = self.compute_lambda(ppr_avail, mean_price_rel, avail_idxs) # lambda at t+1 # limit lambda to avoid numerical problems from acting too aggressively. # (referenced from marigold's implementation: https://github.com/Marigold/universal-portfolios) lam = min(100000, lam) # Note: we don't perform simplex project b/c negative values (shorting) is allowed. new_b = np.zeros(self.num_stocks) for i, _ in enumerate(new_b): ppr = predicted_price_rel[i] if ppr > 0: new_b[i] = self.b[i] + lam * (ppr - mean_price_rel) # Normalize b so that it sums to 1 sum_b = np.linalg.norm(new_b, ord=1) return (1.0 / sum_b) * new_b
def get_new_allocation(self, cur_day): """ :param cur_day: :return: A (1 x num_stocks) array of fractions. Each fraction represents the amount of the money should be invested in that stock at the end of the day. If we haven't reached the start date (the day when we have confidence in our mean/covariance info), return a uniform portfolio. Otherwise, perform nonparametric markowitz. """ if cur_day == 0: cur_day_op = self.data.get_op( relative=False)[cur_day, :] # opening prices on |cur_day| return util.get_uniform_allocation(self.num_stocks, cur_day_op) elif (cur_day < self.start_date): available = util.get_avail_stocks(self.data.get_op()[cur_day, :]) num_available = np.sum(available) new_allocation = 1.0 / num_available * np.asarray(available) else: k = self.k available = util.get_avail_stocks( self.data.get_op()[cur_day - self.window_len + 1, :]) available_inds = util.get_available_inds(available) history = self.get_market_window(self.window_len, cur_day) num_available = history.shape[0] neighbors = np.zeros((num_available, k)) history_norms = np.diag(np.dot(history, history.T)) # Compute k nearest neighbors for each stock for i in range(num_available): stock = history[i, :] neighbors[i, :] = util.k_nearest_neighbors( stock, history, k, history_norms) # Solve optimization problem l = self.risk_aversion neighbors = neighbors.astype(int) b = Variable(num_available) c = Variable(num_available) d = 0 e = 0 for i in range(num_available): inds = available_inds[neighbors[i, :]] m_i = self.mu[inds] #self.mu[inds] S_i = self.sigma[inds, :] S_i = S_i[:, inds] d += (b[i] * m_i).T * np.ones(k) e += quad_form(b[i] * np.ones(k), S_i) constraints = [ c >= b, c >= -b, sum_entries(c) == 1 ] #, b <= self.cap, b >= -self.cap] #[b >= 0, np.ones(num_available).T*b == 1] objective = Maximize(d - l * e) prob = Problem(objective, constraints) prob.solve() new_allocation = np.zeros(len(available)) new_allocation[available_inds] = b.value return new_allocation
def predict_price_relatives(self, day): """ This function predicts the price relative vector at the end of |day| based on the L1 median in the window |day|-w to |day|-1: :param day: The day to predict the closing price relatives for. (This plays the role of t+1 in the above equation.) :return: The predicted price relatives vector. """ window, window_cl, today_op = self.get_window_prices(day, self.window) avail_today = util.get_avail_stocks(today_op) num_avail = sum(avail_today) today_op = np.reshape(today_op, newshape=(1, self.num_stocks)) window_prices = np.append(window_cl, today_op, axis=0) avail_full_window = np.ones(self.num_stocks) for i in range(self.num_stocks): cur_window = window_prices[:, i] # Recent prices for stock i if np.isnan(cur_window).any(): avail_full_window[ i] = 0 # Stock is not available for at least 1 day in the window num_avail_full_window = int(sum(avail_full_window)) window_pr_avail_full_window = np.zeros(shape=(window + 1, num_avail_full_window)) window_pr_avail_today = np.zeros(shape=(window + 1, num_avail)) op_avail_today = np.zeros(shape=(1, num_avail)) col_today = 0 col_full = 0 for i, is_avail in enumerate(avail_today): if is_avail: cur_window = window_prices[:, i] # Note: Some elements here may be nan window_pr_avail_today[:, col_today] = cur_window op_avail_today[0, col_today] = today_op[0, i] col_today += 1 if avail_full_window[i]: window_pr_avail_full_window[:, col_full] = cur_window col_full += 1 # Get median of each stock in the window (avoid nans) mu = np.zeros(sum(avail_today)) mu_avail_full_window = np.zeros(int(sum(avail_full_window))) j = 0 for i, _ in enumerate(mu): # Get window of prices for the current stock (only include non-nan prices) cur_window = window_pr_avail_today[:, i] cur_window = cur_window[np.isfinite(cur_window)] mu[i] = np.median(cur_window) if np.isnan(mu[i]): print 'median is nan!' if avail_full_window[i]: mu_avail_full_window[j] = mu[i] j += 1 for i in range(1, self.max_iter): prev_mu = mu_avail_full_window mu_avail_full_window = self.T_func(mu_avail_full_window, window_pr_avail_full_window) L1_dist = np.linalg.norm((prev_mu - mu_avail_full_window), ord=1) thresh = self.tau * np.linalg.norm(mu_avail_full_window, ord=1) if L1_dist <= thresh: break mu_final = np.zeros(sum(avail_today)) j = 0 for i, med_avail_today in enumerate(mu): if avail_full_window[i]: # Use the T function's price mu_final[i] = mu_avail_full_window[j] j += 1 else: # Just use the median of available prices mu_final[i] = med_avail_today # Use mu as the predicted raw closing prices. price_rel_avail = util.silent_divide(mu_final, op_avail_today) price_rel = np.zeros(shape=(1, self.num_stocks)) col = 0 for i, is_avail in enumerate(avail_today): if is_avail: price_rel[0, i] = price_rel_avail[:, col] col += 1 return price_rel[0]