Beispiel #1
0
    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]