Source code for kaxanuk.attribution_analysis.interfaces.brinston_fachler_method
import pandas as pd
import matplotlib.pyplot as plt
from kaxanuk.attribution_analysis.interfaces.brinston_fachler_interface import BrinstonFachlerInterface
[docs]
class BrinstonFachlerAttribution(BrinstonFachlerInterface):
def __init__(
self,
returns_investable_assets: pd.DataFrame,
complete_portfolio_weights: pd.DataFrame,
complete_benchmark_weights: pd.DataFrame
):
self.returns_investable_assets = returns_investable_assets
self.complete_portfolio_weights = complete_portfolio_weights
self.complete_benchmark_weights = complete_benchmark_weights
[docs]
def brinson_fachler_model(self, date : str):
"""
Calculate the Brinson-Fachler attribution model for a given date.
:param date: The date for which to calculate the attribution.
:return: A tuple containing the detailed attribution DataFrame and a totals DataFrame.
"""
r = self.returns_investable_assets.loc[date]
w_p = self.complete_portfolio_weights.loc[date]
w_b = self.complete_benchmark_weights.loc[date]
# Returns
r_p = w_p * r
r_b = w_b * r
# r_b_daysum
alpha_index = r_p - r_b
allocation = (w_p - w_b) * r_b
selection = alpha_index * w_p
interaction = alpha_index - allocation - selection
# Output
df_output_brinson_fachler = pd.concat([w_p, w_b, r, r_p, r_b, alpha_index, allocation, selection, interaction],
axis = 1)
df_output_brinson_fachler.columns = ["portfolio_weights",
"benchmark_weights",
"returns_data",
"portfolio_returns",
"benchmark_returns",
"alpha",
"allocation",
"selection",
"interaction"]
totals = df_output_brinson_fachler[["portfolio_returns",
"benchmark_returns",
"alpha",
"allocation",
"selection",
"interaction"]].sum()
totals = totals.to_frame().T
totals["date"] = date
self.df_output_brinson_fachler = df_output_brinson_fachler
self.totals = totals
return df_output_brinson_fachler, totals
[docs]
def time_series_calculation(self):
output_dict = {}
df_totals_list = []
dates_portfolio = pd.to_datetime(self.complete_portfolio_weights.index)
dates_benchmark = pd.to_datetime(self.complete_benchmark_weights.index)
dates_returns = pd.to_datetime(self.returns_investable_assets.index)
dates = list( set(dates_benchmark).intersection(set(dates_portfolio)) )
dates.sort()
for date in dates:
if date in dates_returns:
df_output_brinson_fachler, df_totals = self.brinson_fachler_model(date)
output_dict[date] = df_output_brinson_fachler
df_totals_list.append(df_totals)
# a = 0
df = pd.concat(df_totals_list).set_index("date")
self.output_dict = output_dict
self.df = df
[docs]
def attribution_plots(self):
self.brinston_fach_indexes = self.df.cumsum()
# self.brinston_fach_indexes = (1 + self.df).cumprod()
_fig, axes = plt.subplots(3, 1, figsize=(10, 14))
# fig.suptitle(f"Portfolio Attribution", fontsize=16)
self.brinston_fach_indexes[['portfolio_returns', 'benchmark_returns']].plot(ax=axes[0])
axes[0].set_title("Strategy vs Benchmark Returns")
axes[0].grid(visible=True)
self.brinston_fach_indexes[['alpha', ]].plot(ax=axes[1])
axes[1].set_title("Cummulative Alpha")
axes[1].grid(visible=True)
self.brinston_fach_indexes[['allocation', 'selection', 'interaction']].plot(ax=axes[2])
axes[2].set_title("Allocation, Selection and Interaction Effects")
axes[2].grid(visible=True)
# brinston_fach_indexes.plot(title = "Brinston Fachler Indexes", figsize=(10, 6))
plt.tight_layout(rect=[0, 0, 1, 0.95])
plt.show()