def get_factor_return_attribution(cls, fund_tms: QFSeries, fit_tms: QFSeries, regressors_df: QFDataFrame, coefficients: QFSeries, alpha: float) -> Tuple[QFSeries, float]: """ Returns performance attribution for each factor in given regressors and also calculates the unexplained return. """ fund_returns = fund_tms.to_simple_returns() regressors_returns = regressors_df.to_simple_returns() annualised_fund_return = cagr(fund_returns) annualised_fit_return = cagr(fit_tms) total_nav = fit_tms.to_prices(initial_price=1.0) def calc_factors_profit(series) -> float: factor_ret = regressors_returns.loc[:, series.name].values return coefficients.loc[series.name] * (total_nav[:-1].values * factor_ret).sum() factors_profits = regressors_returns.apply(calc_factors_profit) alpha_profit = total_nav[:-1].sum() * alpha total_profit = factors_profits.sum() + alpha_profit regressors_return_attribution = factors_profits * annualised_fit_return / total_profit regressors_return_attribution = cast_series( regressors_return_attribution, QFSeries) unexplained_return = annualised_fund_return - regressors_return_attribution.sum( ) return regressors_return_attribution, unexplained_return
def cagr(qf_series: QFSeries, frequency=None): """ Returns the Compound Annual Growth Rate (CAGR) calculated for the given series. Parameters ---------- qf_series: QFSeries series of returns of an asset frequency: Frequency Frequency of the timeseries of returns; if it is None (by default) it is inferred from the timeseries of returns Returns ------- float annual compound return for the given series of prices """ prices_tms = qf_series.to_prices(frequency=frequency, initial_price=1.0) last_date = prices_tms.index[-1] first_date = prices_tms.index[0] period_length = last_date - first_date period_length_in_years = to_days(period_length) / DAYS_PER_YEAR_AVG total_return = prices_tms[-1] / prices_tms[0] - 1 return annualise_total_return( total_return=total_return, period_length_in_years=period_length_in_years, returns_type=SimpleReturnsSeries)
def calculate_analysis(cls, strategy_tms: QFSeries, benchmark_tms: QFSeries): """ Calculates the rolling table for provided timeseries """ rows = list() windows = [(6 * 21, "6 Months"), (252, "1 Year"), (252 * 2, "2 Years"), (252 * 5, "5 Years")] # Ensure that this data is daily. df = PricesDataFrame() strategy_name = strategy_tms.name benchmark_name = benchmark_tms.name df[strategy_name] = strategy_tms.to_prices() df[benchmark_name] = benchmark_tms.to_prices() df.fillna(method='ffill', inplace=True) for window_info in windows: window = window_info[0] # if window is too big for the strategy then skip it if window >= int(df.shape[0] / 2): continue step = int(window * 0.2) strategy_rolling = df[strategy_name].rolling_window( window, lambda x: x.total_cumulative_return(), step) benchmark_rolling = df[benchmark_name].rolling_window( window, lambda x: x.total_cumulative_return(), step) outperforming = strategy_rolling > benchmark_rolling percentage_outperforming = len( strategy_rolling[outperforming]) / len(strategy_rolling) dto = RollingAnalysisDTO( period=window_info[1], strategy_average=strategy_rolling.mean(), strategy_worst=strategy_rolling.min(), strategy_best=strategy_rolling.max(), benchmark_average=benchmark_rolling.mean(), benchmark_worst=benchmark_rolling.min(), benchmark_best=benchmark_rolling.max(), percentage_difference=percentage_outperforming) rows.append(dto) return rows
def create_skewness_chart(series: QFSeries, title: str = None) -> LineChart: """ Creates a new line chart showing the skewness of the distribution. It plots original series together with another series which contains sorted absolute value of the returns Parameters ---------- series ``QFSeries`` to plot on the chart. title title of the graph, specify ``None`` if you don't want the chart to show a title. Returns ------- The constructed ``LineChart``. """ original_price_series = series.to_prices(1) # Construct a series with returns sorted by their amplitude returns_series = series.to_simple_returns() abs_returns_series = returns_series.abs() returns_df = pd.concat([returns_series, abs_returns_series], axis=1, keys=['simple', 'abs']) sorted_returns_df = returns_df.sort_values(by='abs') skewness_series = SimpleReturnsSeries( index=returns_series.index, data=sorted_returns_df['simple'].values) skewed_price_series = skewness_series.to_prices(1) # Create a new Line Chart. line_chart = LineChart(start_x=series.index[0], rotate_x_axis=True) # Add original series to the chart original_series_element = DataElementDecorator(original_price_series) line_chart.add_decorator(original_series_element) skewed_series_element = DataElementDecorator(skewed_price_series) line_chart.add_decorator(skewed_series_element) # Add a point at the end point = (skewed_price_series.index[-1], skewed_price_series[-1]) point_emphasis = PointEmphasisDecorator(skewed_series_element, point, font_size=9) line_chart.add_decorator(point_emphasis) # Create a title. if title is not None: title_decorator = TitleDecorator(title, "title") line_chart.add_decorator(title_decorator) # Add a legend. legend_decorator = LegendDecorator(key='legend') legend_decorator.add_entry(original_series_element, 'Chronological returns') legend_decorator.add_entry(skewed_series_element, 'Returns sorted by magnitude') line_chart.add_decorator(legend_decorator) line_chart.add_decorator(AxesLabelDecorator(y_label="Profit/Loss")) return line_chart