def risk_contribution_chart(self) -> BarChart: colors_palette = Chart.get_axes_colors() tickers = self.model.input_data.regressors_df.columns.values names = [self._get_security_name(ticker) for ticker in tickers] risk_contributions = QFSeries(data=self.model.risk_contribution.values, index=pd.Index(names)) index_translator = self._get_index_translator(labels=names) bar_chart = BarChart(orientation=Orientation.Horizontal, index_translator=index_translator, thickness=self._bars_width, align='center') bar_chart.add_decorator( DataElementDecorator(risk_contributions, color=colors_palette[1])) bar_chart.add_decorator(TitleDecorator("Risk contribution")) bar_chart.add_decorator( AxesLabelDecorator(x_label="risk contribution [%]")) bar_chart.add_decorator( AxesFormatterDecorator(x_major=PercentageFormatter())) labels = ('{:.2f}'.format(value * 100) for value in risk_contributions) self._add_labels_for_bars(bar_chart, risk_contributions, labels, margin=0.001) return bar_chart
def historical_performance_chart(self) -> LineChart: frequency = self.model.input_data.frequency analysed_tms = self.model.input_data.analysed_tms fitted_tms = self.model.fitted_tms cumulative_fund_rets = analysed_tms.to_prices(initial_price=1.0, frequency=frequency) - 1 cumulative_fit_rets = fitted_tms.to_prices(initial_price=1.0, frequency=frequency) - 1 hist_performance_chart = LineChart() fund_cummulative_rets_data_elem = DataElementDecorator( cumulative_fund_rets) fit_cummulative_rets_data_elem = DataElementDecorator( cumulative_fit_rets) legend_decorator = LegendDecorator( legend_placement=Location.LOWER_RIGHT) legend_decorator.add_entry(fund_cummulative_rets_data_elem, self._get_security_name(analysed_tms.name)) legend_decorator.add_entry(fit_cummulative_rets_data_elem, 'Fit') hist_performance_chart.add_decorator(fund_cummulative_rets_data_elem) hist_performance_chart.add_decorator(fit_cummulative_rets_data_elem) hist_performance_chart.add_decorator( TitleDecorator("Historical Performance")) hist_performance_chart.add_decorator( AxesLabelDecorator(y_label="Cumulative return")) hist_performance_chart.add_decorator(legend_decorator) hist_performance_chart.add_decorator( AxesFormatterDecorator(y_major=PercentageFormatter())) return hist_performance_chart
def decorate(self, chart: "Chart") -> None: drawdown_series = drawdown_tms(self.series) drawdown_series *= -1 ax = chart.axes ax.yaxis.set_major_formatter(PercentageFormatter()) ax.fill_between(drawdown_series.index, 0, drawdown_series.values, alpha=self._colors_alpha) ax.set_ylim(top=0)
def _add_rolling_alpha_and_beta(self, timeseries_list): freq = timeseries_list[0].get_frequency() timeseries_list = [ tms.dropna().to_simple_returns() for tms in timeseries_list ] df = pd.concat(timeseries_list, axis=1).fillna(0) rolling_window_len = int(freq.value / 2) # 6M rolling step = round(freq.value / 6) # 2M shift legend = LegendDecorator() chart = LineChart(start_x=df.index[0], end_x=df.index[-1]) line_decorator = HorizontalLineDecorator(0, key="h_line", linewidth=1) chart.add_decorator(line_decorator) def alpha_function(df_in_window): strategy_returns = df_in_window.iloc[:, 0] benchmark_returns = df_in_window.iloc[:, 1] beta, alpha, _, _, _ = stats.linregress(benchmark_returns, strategy_returns) return beta, alpha rolling = df.rolling_time_window(rolling_window_len, step, alpha_function) rolling = pd.DataFrame([[b, a] for b, a in rolling.values], columns=["beta", "alpha"], index=rolling.index) rolling_element = DataElementDecorator(rolling["beta"]) chart.add_decorator(rolling_element) legend.add_entry(rolling_element, "beta") rolling_element = DataElementDecorator(rolling["alpha"], use_secondary_axes=True) chart.add_decorator(rolling_element) legend.add_entry(rolling_element, "alpha") chart.add_decorator(legend) chart.add_decorator( AxesFormatterDecorator(use_secondary_axes=True, y_major=PercentageFormatter(".1f"))) # modify axes position to make secondary scale visible axes_position = list(self.full_image_axis_position) axes_position[2] = axes_position[2] - 0.07 position_decorator = AxesPositionDecorator(*axes_position) chart.add_decorator(position_decorator) title_str = "Rolling alpha and beta [{} {} samples]".format( rolling_window_len, freq) title_decorator = TitleDecorator(title_str, key="title") chart.add_decorator(title_decorator) self.document.add_element( ChartElement(chart, figsize=self.full_image_size, dpi=self.dpi))
def create_returns_bar_chart( returns: QFSeries, frequency: Frequency = Frequency.YEARLY) -> BarChart: """ Constructs a new returns bar chart based on the returns specified. By default a new annual returns bar chart will be created. """ colors = Chart.get_axes_colors() # Calculate data. aggregate_returns = get_aggregate_returns(returns, frequency, multi_index=True) data_series = QFSeries(aggregate_returns.sort_index(ascending=True)) chart = BarChart(Orientation.Horizontal, align="center") chart.add_decorator(DataElementDecorator(data_series)) chart.add_decorator(BarValuesDecorator(data_series)) # Format the x-axis so that its labels are shown as a percentage. chart.add_decorator(AxesFormatterDecorator(x_major=PercentageFormatter())) # Format Y axis to make sure we have a tick for each year or 2 years if len(data_series) > 10: y_labels = data_series[data_series.index % 2 == 1].index else: y_labels = data_series.index chart.add_decorator( AxisTickLabelsDecorator(labels=y_labels, axis=Axis.Y, tick_values=y_labels)) # Add an average line. avg_line = VerticalLineDecorator(aggregate_returns.values.mean(), color=colors[1], key="avg_line", linestyle="--", alpha=0.8) chart.add_decorator(avg_line) # Add a legend. legend = LegendDecorator(key="legend_decorator") legend.add_entry(avg_line, "Mean") chart.add_decorator(legend) # Add a title. title = TitleDecorator(str(frequency).capitalize() + " Returns", key="title_decorator") chart.add_decorator(title) chart.add_decorator(AxesLabelDecorator("Returns", "Year")) return chart
def _plot_data(self, datapoints_tms, regression_line, beta, alpha, r_squared, max_ret): colors = Chart.get_axes_colors() self.axes.scatter(x=datapoints_tms.iloc[:, 0], y=datapoints_tms.iloc[:, 1], c=colors[0], alpha=0.6, edgecolors='black', linewidths=0.5) self.axes.axhline(0, color='black', axes=self.axes, linewidth=1) self.axes.axvline(0, color='black', axes=self.axes, linewidth=1) self.axes.plot(regression_line.index.values, regression_line.values, axes=self.axes, color=colors[1]) self.axes.set_xlim([-max_ret, max_ret]) self.axes.set_ylim([-max_ret, max_ret]) props = dict(boxstyle='square', facecolor='white', alpha=0.5) textstr = '$\\beta={0:.2f}$\n$\\alpha={1:.2%}$$\%$\n$R^2={2:.2}$'.format(beta, alpha, r_squared) font_size = mpl.rcParams['legend.fontsize'] self.axes.text( 0.05, 0.95, textstr, transform=self.axes.transAxes, bbox=props, verticalalignment='top', fontsize=font_size) self.axes.xaxis.set_major_formatter(PercentageFormatter()) self.axes.yaxis.set_major_formatter(PercentageFormatter())
def _add_returns_distribution(self): if self.initial_risk is not None: returns = SimpleReturnsSeries(data=[t.percentage_pnl / self.initial_risk for t in self.trades]) title = "Distribution of R multiples, Initial risk = {:.2%}".format(self.initial_risk) returns_histogram = self._get_distribution_plot(returns, title) else: returns = SimpleReturnsSeries(data=[t.percentage_pnl for t in self.trades]) title = "Distribution of returns [%]" returns_histogram = self._get_distribution_plot(returns, title) # Format the x-axis so that its labels are shown as a percentage in case of percentage returns axes_formatter_decorator = AxesFormatterDecorator(x_major=PercentageFormatter(), key="axes_formatter") returns_histogram.add_decorator(axes_formatter_decorator) self.document.add_element(ChartElement(returns_histogram, figsize=self.full_image_size, dpi=self.dpi))
def apply_data_element_decorators( self, data_element_decorators: List["DataElementDecorator"]): colors = cycle(Chart.get_axes_colors()) for data_element in data_element_decorators: plot_settings = data_element.plot_settings.copy() plot_settings.setdefault("color", next(colors)) series = data_element.data trimmed_series = self._trim_data(series) drawdown_series = drawdown_tms(trimmed_series) drawdown_series *= -1 axes = self._ax axes.yaxis.set_major_formatter(PercentageFormatter()) axes.fill_between(drawdown_series.index, 0, drawdown_series.values) axes.set_ylim(top=0)
def performance_attribution_chart(self) -> BarChart: colors_palette = Chart.get_axes_colors() unexplained_ret = self.model.unexplained_performance_attribution_ret factors_ret = self.model.factors_performance_attribution_ret fund_ret = self.model.fund_tms_analysis.cagr unexplained_name = "Unexplained" factors_names = [ self._get_security_name(ticker) for ticker in self.model.coefficients.index.values ] fund_name = self._get_security_name( self.model.input_data.analysed_tms.name) all_values = [unexplained_ret] + list(factors_ret) + [fund_ret] all_names = [unexplained_name] + list(factors_names) + [fund_name] all_returns = SimpleReturnsSeries(data=all_values, index=pd.Index(all_names)) colors = [ colors_palette[0] ] + [colors_palette[1]] * len(factors_names) + [colors_palette[2]] index_translator = self._get_index_translator(labels=all_names) bar_chart = BarChart(orientation=Orientation.Horizontal, index_translator=index_translator, thickness=self._bars_width, align='center') bar_chart.add_decorator(DataElementDecorator(all_returns, color=colors)) bar_chart.add_decorator( TitleDecorator("Attribution of Fund Annualised Return")) bar_chart.add_decorator( AxesLabelDecorator(x_label="annualised return [%]")) bar_chart.add_decorator( AxesFormatterDecorator(x_major=PercentageFormatter())) labels = ('{:.2f}'.format(value * 100) for value in all_returns) self._add_labels_for_bars(bar_chart, all_returns, labels) return bar_chart
def _add_rolling_chart(self): days_rolling = int(252 / 2) # 6M rolling step = round(days_rolling / 5) strategy = self.strategy_series.to_prices(1) chart = LineChart(start_x=strategy.index[0], end_x=strategy.index[-1]) line_decorator = HorizontalLineDecorator(0, key="h_line", linewidth=1) chart.add_decorator(line_decorator) legend = LegendDecorator() def tot_return(window): return PricesSeries(window).total_cumulative_return() def volatility(window): return get_volatility(PricesSeries(window), Frequency.DAILY) functions = [tot_return, volatility] names = ['Rolling Return', 'Rolling Volatility'] for func, name in zip(functions, names): rolling = strategy.rolling_window(days_rolling, func, step=step) rolling_element = DataElementDecorator(rolling) chart.add_decorator(rolling_element) legend.add_entry(rolling_element, name) chart.add_decorator(legend) chart.add_decorator( AxesFormatterDecorator(y_major=PercentageFormatter(".0f"))) left, bottom, width, height = self.full_image_axis_position position_decorator = AxesPositionDecorator(left, bottom, width, height) chart.add_decorator(position_decorator) title_decorator = TitleDecorator( "Rolling Statistics [6 Months]".format(days_rolling), key="title") chart.add_decorator(title_decorator) self.document.add_element( ChartElement(chart, figsize=self.full_image_size, dpi=self.dpi))
def _get_rolling_ret_and_vol_chart(self, timeseries): freq = timeseries.get_frequency() rolling_window_len = int(freq.value / 2) # 6M rolling step = round(freq.value / 6) # 2M shift tms = timeseries.to_prices(1) chart = LineChart(start_x=tms.index[0], end_x=tms.index[-1]) line_decorator = HorizontalLineDecorator(0, key="h_line", linewidth=1) chart.add_decorator(line_decorator) legend = LegendDecorator() def tot_return(window): return PricesSeries(window).total_cumulative_return() def volatility(window): return get_volatility(PricesSeries(window), freq) functions = [tot_return, volatility] names = ['Rolling Return', 'Rolling Volatility'] for func, name in zip(functions, names): rolling = tms.rolling_window(rolling_window_len, func, step=step) rolling_element = DataElementDecorator(rolling) chart.add_decorator(rolling_element) legend.add_entry(rolling_element, name) chart.add_decorator(legend) chart.add_decorator( AxesFormatterDecorator(y_major=PercentageFormatter(".0f"))) position_decorator = AxesPositionDecorator( *self.full_image_axis_position) chart.add_decorator(position_decorator) title_str = "Rolling Stats [{} {} samples]".format( rolling_window_len, freq) title_decorator = TitleDecorator(title_str, key="title") chart.add_decorator(title_decorator) return chart
def _add_simulation_results(self): """ Generate a data frame consisting of a certain number of "scenarios" (each scenario denotes one single equity curve). """ self.document.add_element(NewPageElement()) self.document.add_element(HeadingElement(level=1, text="Monte Carlo simulations\n")) self.document.add_element(HeadingElement(level=2, text="Average number of trades per year: {}\n".format( int(self._average_number_of_trades_per_year())))) if self.initial_risk is not None: self.document.add_element(HeadingElement(level=2, text="Initial risk: {:.2%}".format(self.initial_risk))) scenarios_df, total_returns = self._get_scenarios() # Plot all the possible paths on a chart all_paths_chart = self._get_simulation_plot(scenarios_df) self.document.add_element(ChartElement(all_paths_chart, figsize=self.full_image_size, dpi=self.dpi)) # Plot the distribution plot distribution_plot = self._get_distribution_plot( total_returns, title="Monte Carlo Simulations Distribution (one year % return)", bins=200, crop=True) # Format the x-axis so that its labels are shown as a percentage in case of percentage returns axes_formatter_decorator = AxesFormatterDecorator(x_major=PercentageFormatter(), key="axes_formatter") distribution_plot.add_decorator(axes_formatter_decorator) self.document.add_element(ChartElement(distribution_plot, figsize=self.full_image_size, dpi=self.dpi)) simulations_summary_table = self._get_monte_carlos_simulator_outputs(scenarios_df, total_returns) self.document.add_element(simulations_summary_table) # Extract the results of each of the scenarios and summarize the data in the tables dist_summary_tables = self._get_distribution_summary_table(total_returns) self.document.add_element(dist_summary_tables) # Add the "Chances of dropping below" and "Simulations summary" tables ruin_chances_table = self._get_chances_of_dropping_below_table(scenarios_df) self.document.add_element(ruin_chances_table)
def _get_rolling_chart(self, timeseries_list, rolling_function, function_name): freq = timeseries_list[0].get_frequency() timeseries_list = [ tms.dropna().to_prices(1) for tms in timeseries_list ] df = pd.concat(timeseries_list, axis=1).fillna(method='ffill') rolling_window_len = int(freq.value / 2) # 6M rolling step = round(freq.value / 6) # 2M shift legend = LegendDecorator() chart = LineChart(start_x=df.index[0], end_x=df.index[-1]) line_decorator = HorizontalLineDecorator(0, key="h_line", linewidth=1) chart.add_decorator(line_decorator) for _, tms in df.iteritems(): rolling = tms.rolling_window(rolling_window_len, rolling_function, step=step) rolling_element = DataElementDecorator(rolling) chart.add_decorator(rolling_element) legend.add_entry(rolling_element, tms.name) chart.add_decorator(legend) chart.add_decorator( AxesFormatterDecorator(y_major=PercentageFormatter(".0f"))) position_decorator = AxesPositionDecorator( *self.full_image_axis_position) chart.add_decorator(position_decorator) title_str = "{} - Rolling Stats [{} {} samples]".format( function_name, rolling_window_len, freq) title_decorator = TitleDecorator(title_str, key="title") chart.add_decorator(title_decorator) return chart
def regressors_and_explained_variable_chart(self) -> LineChart: regressors_df = self.model.input_data.regressors_df fund_tms = self.model.input_data.analysed_tms chart = LineChart() legend = LegendDecorator() # add data to the chart and the legend marker_props_template = {'alpha': 0.5} stemline_props_template = {'linestyle': '-.', 'linewidth': 0.2} baseline_props = {'visible': False} regressors_and_fund_df = pd.concat([regressors_df, fund_tms], axis=1) colors = cycle(Chart.get_axes_colors()) for ticker, series in regressors_and_fund_df.iteritems(): marker_props = marker_props_template.copy() stemline_props = stemline_props_template.copy() color = next(colors) marker_props['markeredgecolor'] = color marker_props['markerfacecolor'] = color stemline_props['color'] = color data_elem = StemDecorator(series, marker_props=marker_props, stemline_props=stemline_props, baseline_props=baseline_props) chart.add_decorator(data_elem) legend.add_entry(data_elem, self._get_security_name(ticker)) # add decorators to the chart chart.add_decorator(TitleDecorator("Returns")) chart.add_decorator(AxesLabelDecorator(y_label="return [%]")) chart.add_decorator(legend) chart.add_decorator( AxesFormatterDecorator(y_major=PercentageFormatter())) return chart
def _plot_data(self, annual_returns_tms): self.axes.yaxis.set_major_formatter(PercentageFormatter()) self.axes.yaxis.set_major_locator(MaxNLocator(steps=[1, 2, 5, 10], nbins=20, symmetric=True)) # plot the line for mean mean_annual_return = annual_returns_tms.mean() self.axes.axhline(mean_annual_return, color='grey', linestyle='--', alpha=0.7) # plot the line for 0Y self.axes.axhline(0, color='black', linestyle='-') # plot yearly returns as bars self.axes.xaxis.set_major_formatter(DateFormatter('%Y-%m')) annual_returns_tms.plot(ax=self.axes, kind='bar', alpha=0.7, width=0.8) self.axes.set_xlabel('Year') self.axes.set_ylabel('Returns') self.axes.set_title("Annual Returns") mean_label = 'mean={0:.2%}'.format(mean_annual_return) self.axes.legend([mean_label], loc='best') self._adjust_style()
def _get_rolling_chart(self, timeseries_list, rolling_function, function_name): days_rolling = int(BUSINESS_DAYS_PER_YEAR / 2) # 6M rolling step = round(days_rolling / 5) legend = LegendDecorator() chart = None for i, tms in enumerate(timeseries_list): if i == 0: chart = LineChart(start_x=tms.index[0], end_x=tms.index[-1]) line_decorator = HorizontalLineDecorator(0, key="h_line", linewidth=1) chart.add_decorator(line_decorator) tms = tms.to_prices(1) rolling = tms.rolling_window(days_rolling, rolling_function, step=step) rolling_element = DataElementDecorator(rolling) chart.add_decorator(rolling_element) legend.add_entry(rolling_element, tms.name) chart.add_decorator(legend) chart.add_decorator( AxesFormatterDecorator(y_major=PercentageFormatter(".0f"))) position_decorator = AxesPositionDecorator( *self.full_image_axis_position) chart.add_decorator(position_decorator) title_str = "{} - Rolling Stats [{} days]".format( function_name, days_rolling) title_decorator = TitleDecorator(title_str, key="title") chart.add_decorator(title_decorator) return chart
def _add_relative_performance_chart( self, strategy_tms: QFSeries, benchmark_tms: QFSeries, chart_title: str = "Relative Performance", legend_subtitle: str = "Strategy - Benchmark"): diff = strategy_tms.to_simple_returns().subtract( benchmark_tms.to_simple_returns(), fill_value=0) diff = diff.to_prices(1) - 1 chart = LineChart(start_x=diff.index[0], end_x=diff.index[-1], log_scale=False) position_decorator = AxesPositionDecorator( *self.full_image_axis_position) chart.add_decorator(position_decorator) line_decorator = HorizontalLineDecorator(0, key="h_line", linewidth=1) chart.add_decorator(line_decorator) legend = LegendDecorator() series_elem = DataElementDecorator(diff) chart.add_decorator(series_elem) legend.add_entry(series_elem, legend_subtitle) chart.add_decorator(legend) title_decorator = TitleDecorator(chart_title, key="title") chart.add_decorator(title_decorator) chart.add_decorator( AxesFormatterDecorator(y_major=PercentageFormatter(".0f"))) fill_decorator = FillBetweenDecorator(diff) chart.add_decorator(fill_decorator) self.document.add_element( ChartElement(chart, figsize=self.full_image_size, dpi=self.dpi))
def historical_out_of_sample_performance_chart(self) -> LineChart: analysed_tms = self.model.input_data.analysed_tms frequency = self.model.input_data.frequency fund_cumulative_rets = analysed_tms.to_prices( initial_price=1.0, frequency=frequency) - 1 # type: PricesSeries fit_cumulative_rets = self.model.fitted_tms.to_prices( initial_price=1.0, frequency=frequency) - 1 # type: PricesSeries live_start_date = self.model.oos_start_date in_sample_fund_tms = fund_cumulative_rets.loc[:live_start_date] in_sample_fit_tms = fit_cumulative_rets.loc[:live_start_date] out_of_sample_fund_tms = fund_cumulative_rets.loc[live_start_date:] out_of_sample_fit_tms = fit_cumulative_rets.loc[live_start_date:] colors = Chart.get_axes_colors() in_sample_fund_data_elem = DataElementDecorator(in_sample_fund_tms, color=colors[0]) out_of_sample_fund_data_elem = DataElementDecorator( out_of_sample_fund_tms, color=colors[0]) in_sample_fit_data_elem = DataElementDecorator(in_sample_fit_tms, color=colors[1]) out_of_sample_fit_data_elem = DataElementDecorator( out_of_sample_fit_tms, color=colors[1]) legend_decorator = LegendDecorator( legend_placement=Location.LOWER_RIGHT) legend_decorator.add_entry(in_sample_fund_data_elem, self._get_security_name(analysed_tms.name)) legend_decorator.add_entry(in_sample_fit_data_elem, 'Fit') is_vs_oos_performance_chart = LineChart() is_vs_oos_performance_chart.add_decorator(in_sample_fund_data_elem) is_vs_oos_performance_chart.add_decorator(out_of_sample_fund_data_elem) is_vs_oos_performance_chart.add_decorator(in_sample_fit_data_elem) is_vs_oos_performance_chart.add_decorator(out_of_sample_fit_data_elem) is_vs_oos_performance_chart.add_decorator( AxesFormatterDecorator(y_major=PercentageFormatter())) is_vs_oos_performance_chart.add_decorator( AxesLabelDecorator(y_label="Cumulative return [%]")) is_vs_oos_performance_chart.add_decorator(legend_decorator) is_vs_oos_performance_chart.add_decorator( TextDecorator("In Sample ", x=DataCoordinate(live_start_date), y=AxesCoordinate(0.99), verticalalignment='top', horizontalalignment='right')) is_vs_oos_performance_chart.add_decorator( TextDecorator(" Out Of Sample", x=DataCoordinate(live_start_date), y=AxesCoordinate(0.99), verticalalignment='top', horizontalalignment='left')) last_date = fund_cumulative_rets.index[-1] is_vs_oos_performance_chart.add_decorator( VerticalSpanDecorator(x_min=live_start_date, x_max=last_date)) return is_vs_oos_performance_chart