def get_fund_predictions(self): """ This method generates 12 month predictions for fund expected returns and covariance. :return: (ers, covars) ers is a pandas series of 12 month expected return indexed on fund id covars is a pandas dataframe of 12 month expected covariance indexed on both axis by fund id """ today = self.data_provider.get_current_date() # The earliest data we want to build our predictions begin_date = today - timedelta(days=365 * MAX_HISTORY) # Get the funds funds = self.data_provider.get_tickers() # Get all the return data relating to the funds fund_returns, benchmark_returns = get_return_history( funds, begin_date, today) # For now we're assigning the benchmark returns for the return history. returns = get_benchmark_returns(funds, benchmark_returns) # Filter any funds that don't have enough data. # Our latest start date is the first day of the last complete investment cycle from the current date. latest_start = self.get_last_cycle_start() # try to be more tolerant - although not sure what this will do returns = filter_returns(returns, OLDEST_ACCEPTABLE_DATA, latest_start=latest_start) #returns = filter_returns(returns, OLDEST_ACCEPTABLE_DATA) this gives weird symmetric error in portfolio optim if returns.empty: raise OptimizationException('No returns data available') oldest_dt = today - timedelta(days=OLDEST_ACCEPTABLE_DATA) cycles = self.get_cycle_obs(begin_date) if cycles.index[-1] < oldest_dt: raise OptimizationException('Last observed cycle was too long ago') probs = np.array(self.get_normalized_probabilities(oldest_dt).tail(1)) return_cycles = self._merge_cycle_returns(returns, cycles) mu = (1 + self._expected_returns_prob_v1(return_cycles, probs))**WEEKDAYS_PER_YEAR - 1 sigma = self._covariance_matrix_prob_v1(return_cycles, probs) * WEEKDAYS_PER_YEAR return mu, sigma
def get_investment_cycles(self): # Populate the cache as we'll be hitting it a few times. Boolean evaluation causes full cache population obs = InvestmentCycleObservation.objects.all().filter( as_of__lt=self.get_current_date()).order_by('as_of') if not obs: raise OptimizationException( "There are no historic observations available") return obs
def _merge_cycle_returns(self, returns, cycles): """ Merge a Daily and a Monthly Dataframe by certain label column :param returns: The Dataframe of the daily return observations :param cycles: The Dataframe of the monthly investment cycle observations :return: Returns the merged dataframe """ returns[CYCLE_LABEL] = cycles.reindex(returns.index, method='pad') if len(returns[CYCLE_LABEL].unique()) != 5: emsg = "A full investment cycle was not present in the available history ({} - {})" logger.error(emsg.format(returns.index[0], returns.index[-1])) raise OptimizationException( "Not enough data for portfolio optimisation.") return returns
def get_normalized_probabilities(self, begin_date): """ Normalize the probability to sum 1 (This will configure a prob space) :param begin_date: The earliest date you want the probabilities from :return: Dataframe of normalized probabilities. The fields are in positions corresponding to the class ids on InvestmentCycleObservation. """ probs_df = self.data_provider.get_probs_df(begin_date) if probs_df.empty: raise OptimizationException( "There are no investment clock predictions available") sum_row = probs_df.sum(axis=1) norm_probs = probs_df.div(sum_row, axis=0) return norm_probs
def get_investment_cycles(self): obs = self.investment_cycles if not obs: raise OptimizationException("There are no historic observations available") return self.investment_cycles