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 _get_histogram_chart(self): colors = Chart.get_axes_colors() chart = HistogramChart(self.returns_of_trades * 100) # expressed in % # Format the x-axis so that its labels are shown as a percentage. x_axis_formatter = FormatStrFormatter("%0.0f%%") axes_formatter_decorator = AxesFormatterDecorator(x_major=x_axis_formatter, key="axes_formatter") chart.add_decorator(axes_formatter_decorator) # Only show whole numbers on the y-axis. y_axis_locator = MaxNLocator(integer=True) axes_locator_decorator = AxesLocatorDecorator(y_major=y_axis_locator, key="axes_locator") chart.add_decorator(axes_locator_decorator) # Add an average line. avg_line = VerticalLineDecorator(self.returns_of_trades.values.mean(), color=colors[1], key="average_line_decorator", 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("Distribution of Trades", key="title_decorator") chart.add_decorator(title) chart.add_decorator(AxesLabelDecorator("Return", "Occurrences")) return chart
def _add_assets_number_in_portfolio_chart(self): chart = LineChart(rotate_x_axis=False) chart.add_decorator(AxesPositionDecorator(*self.full_image_axis_position)) legend = LegendDecorator(key="legend_decorator") positions_history = self.backtest_result.portfolio.positions_history() # Find all not NaN values (not NaN values indicate that the position was open for this contract at that time) # and count their number for each row (for each of the dates) number_of_contracts = positions_history.notna().sum(axis=1) number_of_contracts_decorator = DataElementDecorator(number_of_contracts) chart.add_decorator(number_of_contracts_decorator) legend.add_entry(number_of_contracts_decorator, "Contracts") # Group tickers by name and for each name and date check if there was at least one position open with any # of the corresponding tickers. Finally sum all the assets that had a position open on a certain date. number_of_assets = positions_history.groupby(by=lambda ticker: ticker.name, axis='columns') \ .apply(lambda x: x.notna().any(axis=1)).sum(axis=1) number_of_assets_decorator = DataElementDecorator(number_of_assets) chart.add_decorator(number_of_assets_decorator) legend.add_entry(number_of_assets_decorator, "Assets") chart.add_decorator(TitleDecorator("Number of assets in the portfolio")) chart.add_decorator(legend) chart.add_decorator(AxesLabelDecorator(y_label="Number of contracts / assets")) self.document.add_element(ChartElement(chart, figsize=self.full_image_size, dpi=self.dpi))
def create_gross_leverage_chart(gross_lev: QFSeries) -> LineChart: """ Creates a line chart showing gross leverage based on the specified gross leverage values. Parameters ---------- gross_lev: QFSeries Gross leverage as returned by the extract_rets_pos_txn_from_zipline function. Returns ------- LineChart Created line chart """ result = LineChart() result.add_decorator( DataElementDecorator(gross_lev, linewidth=0.5, color="g")) result.add_decorator( HorizontalLineDecorator(gross_lev.mean(), color="g", linestyle="--", linewidth=3)) result.add_decorator(TitleDecorator("Gross leverage")) result.add_decorator(AxesLabelDecorator(y_label="Gross leverage")) return result
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 cooks_distance_chart(self) -> LineChart: cooks_dist = self.model.cooks_distance_tms chart = LineChart() colors = cycle(Chart.get_axes_colors()) color = next(colors) marker_props = {'alpha': 0.7} stemline_props = {'linestyle': '-.', 'linewidth': 0.2} baseline_props = {'visible': False} marker_props['markeredgecolor'] = color marker_props['markerfacecolor'] = color stemline_props['color'] = color data_elem = StemDecorator(cooks_dist, marker_props=marker_props, stemline_props=stemline_props, baseline_props=baseline_props) chart.add_decorator(data_elem) chart.add_decorator(TitleDecorator("Cook's Distance")) chart.add_decorator( AxesLabelDecorator(y_label="max change of coefficients")) return chart
def _get_distribution_plot(self, data_series: SimpleReturnsSeries, title: str, bins: Union[int, str] = 50, crop: bool = False): colors = Chart.get_axes_colors() if crop: start_x = np.quantile(data_series, 0.01) end_x = np.quantile(data_series, 0.99) chart = HistogramChart(data_series, bins=bins, start_x=start_x, end_x=end_x) else: chart = HistogramChart(data_series, bins=bins) # Only show whole numbers on the y-axis. y_axis_locator = MaxNLocator(integer=True) axes_locator_decorator = AxesLocatorDecorator(y_major=y_axis_locator, key="axes_locator") chart.add_decorator(axes_locator_decorator) # Add an average line. avg_line = VerticalLineDecorator(data_series.mean(), color=colors[1], key="average_line_decorator", 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_decorator = TitleDecorator(title, key="title") chart.add_decorator(title_decorator) chart.add_decorator(AxesLabelDecorator(title, "Occurrences")) position_decorator = AxesPositionDecorator(*self.full_image_axis_position) chart.add_decorator(position_decorator) return chart
def create_qq_chart(strategy: QFSeries) -> Chart: colors = Chart.get_axes_colors() strategy = strategy.to_log_returns() # Normalize strategy = strategy - strategy.mean() strategy = strategy / strategy.std() # Sort strategy_values = sorted(strategy.values) # Create benchmark benchmark_values = list(range(1, len(strategy_values) + 1)) n = len(strategy_values) + 1 benchmark_values = map(lambda x: x / n, benchmark_values) benchmark_values = list(map(lambda x: norm.ppf(x), benchmark_values)) # Figure out the limits. maximum = max(max(benchmark_values), max(strategy_values)) result = LineChart(start_x=-maximum, end_x=maximum, upper_y=maximum, lower_y=-maximum) result.add_decorator(ScatterDecorator( benchmark_values, strategy_values, color=colors[0], alpha=0.6, edgecolors='black', linewidths=0.5)) result.add_decorator(VerticalLineDecorator(0, color='black', linewidth=1)) result.add_decorator(HorizontalLineDecorator(0, color='black', linewidth=1)) result.add_decorator(TitleDecorator("Normal Distribution Q-Q")) result.add_decorator(AxesLabelDecorator("Normal Distribution Quantile", "Observed Quantile")) # Add diagonal line. result.add_decorator(DiagonalLineDecorator(color=colors[1])) return result
def create_returns_distribution(returns: QFSeries, frequency: Frequency = Frequency.MONTHLY, title: str = None) -> \ HistogramChart: """ Creates a new returns distribution histogram with the specified frequency. Parameters ---------- returns: QFSeries The returns series to use in the histogram. frequency: Frequency frequency of the returns after aggregation title title of the chart Returns ------- HistogramChart A new ``HistogramChart`` instance. """ colors = Chart.get_axes_colors() aggregate_returns = get_aggregate_returns(returns, frequency, multi_index=True).multiply(100) chart = HistogramChart(aggregate_returns) # Format the x-axis so that its labels are shown as a percentage. x_axis_formatter = FormatStrFormatter("%.0f%%") axes_formatter_decorator = AxesFormatterDecorator(x_major=x_axis_formatter, key="axes_formatter") chart.add_decorator(axes_formatter_decorator) # Only show whole numbers on the y-axis. y_axis_locator = MaxNLocator(integer=True) axes_locator_decorator = AxesLocatorDecorator(y_major=y_axis_locator, key="axes_locator") chart.add_decorator(axes_locator_decorator) # Add an average line. avg_line = VerticalLineDecorator(aggregate_returns.values.mean(), color=colors[1], key="average_line_decorator", 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. if title is None: title = "Distribution of " + str(frequency).capitalize() + " Returns" title = TitleDecorator(title, key="title_decorator") chart.add_decorator(title) chart.add_decorator(AxesLabelDecorator("Returns", "Occurrences")) return chart
def _add_concentration_of_portfolio_chart(self, top_assets_numbers: tuple = (1, 5)): chart = LineChart(rotate_x_axis=False) position_decorator = AxesPositionDecorator(*self.full_image_axis_position) chart.add_decorator(position_decorator) # Define a legend legend = LegendDecorator(key="legend_decorator") # Add y label label_decorator = AxesLabelDecorator(y_label="Mean total exposure of top assets") chart.add_decorator(label_decorator) # Add top asset contribution positions_history = self.backtest_result.portfolio.positions_history() if positions_history.empty: raise ValueError("No positions found in positions history") positions_history = positions_history.applymap( lambda x: x.total_exposure if isinstance(x, BacktestPositionSummary) else 0) # Map all the single contracts onto tickers (including future tickers) and take the maximal total exposure for # each of the groups - in case if two contracts for a single asset will be included in the open positions in # the portfolio at any point of time, only one (with higher total exposure) will be considered while generating # the top assets plot def contract_to_ticker(c: Contract): return self.backtest_result.portfolio.contract_ticker_mapper. \ contract_to_ticker(c, strictly_to_specific_ticker=False) assets_history = positions_history.rename(columns=contract_to_ticker) assets_history = assets_history.groupby(level=0, axis=1).apply(func=( lambda x: x.abs().max(axis=1).astype(float) )) for assets_number in top_assets_numbers: # For each date (row), find the top_assets largest assets and compute the mean value of their market value top_assets_mean_values = assets_history.stack(dropna=False).groupby(level=0).apply( lambda group: group.nlargest(assets_number).mean() ).resample('D').last() # Divide the computed mean values by the portfolio value, for each of the dates top_assets_percentage_value = top_assets_mean_values / self.backtest_result.portfolio.portfolio_eod_series() concentration_top_asset = DataElementDecorator(top_assets_percentage_value) chart.add_decorator(concentration_top_asset) # Add to legend legend.add_entry(concentration_top_asset, "TOP {} assets".format(assets_number)) # Add title title_decorator = TitleDecorator("Concentration of assets") chart.add_decorator(title_decorator) # Add legend chart.add_decorator(legend) self.document.add_element(ChartElement(chart, figsize=self.full_image_size, dpi=self.dpi))
def _crete_single_line_chart(self, measure_name, parameters, values, tickers): result_series = QFSeries(data=values, index=parameters) line_chart = LineChart() data_element = DataElementDecorator(result_series) line_chart.add_decorator(data_element) line_chart.add_decorator(TitleDecorator(self._get_chart_title(tickers, measure_name))) param_names = self._get_param_names() line_chart.add_decorator(AxesLabelDecorator(x_label=param_names[0], y_label=measure_name)) self._resize_chart(line_chart) return line_chart
def _add_leverage_chart(self): lev_chart = self._get_leverage_chart(self.backtest_result.portfolio.leverage_series()) position_decorator = AxesPositionDecorator(*self.full_image_axis_position) lev_chart.add_decorator(position_decorator) # Add y label label_decorator = AxesLabelDecorator(y_label="Leverage") lev_chart.add_decorator(label_decorator) self.document.add_element(ChartElement(lev_chart, figsize=self.full_image_size, dpi=self.dpi))
def _add_assets_number_in_portfolio_chart(self): chart = LineChart(rotate_x_axis=False) legend = LegendDecorator(key="legend_decorator") position_decorator = AxesPositionDecorator( *self.full_image_axis_position) chart.add_decorator(position_decorator) positions_history = self.backtest_result.portfolio.positions_history() # Find all not NaN values (not NaN values indicate that the position was open for this contract at that time) # and count their number for each row (for each of the dates) number_of_contracts = positions_history.notnull().sum(axis=1) number_of_contracts_decorator = DataElementDecorator( number_of_contracts) chart.add_decorator(number_of_contracts_decorator) legend.add_entry(number_of_contracts_decorator, "Contracts") # Count number of assets in the portfolio (if on two contracts from same future family exist in the same time, # e.g. during rolling day, in the portfolio, they are counted as one asset) def contract_to_ticker(c: Contract): return self.backtest_result.portfolio.contract_ticker_mapper. \ contract_to_ticker(c, strictly_to_specific_ticker=False) assets_history = positions_history.rename(columns=contract_to_ticker) assets_history = assets_history.groupby(level=0, axis=1).apply(func=( # For each asset, group all of the corresponding columns (each of which corresponds to one contract), # and check if at any given timestamp the "value" of any of the contracts was different than None - this # indicates that at this point of time a position concerning the given asset was open in the portfolio # (which in the resulting series will be denoted as 1, otherwise - 0, so that it will be possible to # sum positions of all open assets at any given point of time) lambda x: x.notna().any(axis=1).astype(int))) number_of_assets = assets_history.sum(axis=1) number_of_assets_decorator = DataElementDecorator(number_of_assets) chart.add_decorator(number_of_assets_decorator) legend.add_entry(number_of_assets_decorator, "Assets") # Add title title_decorator = TitleDecorator("Number of assets in the portfolio") chart.add_decorator(title_decorator) # Add legend chart.add_decorator(legend) # Add y label label_decorator = AxesLabelDecorator( y_label="Number of contracts / assets") chart.add_decorator(label_decorator) self.document.add_element( ChartElement(chart, figsize=self.full_image_size, dpi=self.dpi))
def create_return_quantiles(returns: QFSeries, live_start_date: datetime = None, x_axis_labels_rotation: int = 20) \ -> BoxplotChart: """ Creates a new return quantiles boxplot chart based on the returns specified. A swarm plot is also rendered on the chart if the ``live_start_date`` is specified. Parameters ---------- returns The returns series to plot on the chart. live_start_date The live start date that will determine whether a swarm plot should be rendered. x_axis_labels_rotation Returns ------- A new ``BoxplotChart`` instance. """ simple_returns = returns.to_simple_returns() # case when we can plot IS together with OOS if live_start_date is not None: oos_returns = simple_returns.loc[simple_returns.index >= live_start_date] if len(oos_returns) > 0: in_sample_returns = simple_returns.loc[simple_returns.index < live_start_date] in_sample_weekly = get_aggregate_returns(in_sample_returns, Frequency.WEEKLY, multi_index=True) in_sample_monthly = get_aggregate_returns(in_sample_returns, Frequency.MONTHLY, multi_index=True) oos_weekly = get_aggregate_returns(oos_returns, Frequency.WEEKLY, multi_index=True) oos_monthly = get_aggregate_returns(oos_returns, Frequency.MONTHLY, multi_index=True) chart = BoxplotChart([in_sample_returns, oos_returns, in_sample_weekly, oos_weekly, in_sample_monthly, oos_monthly], linewidth=1) x_labels = ["daily IS", "daily OOS", "weekly IS", "weekly OOS", "monthly IS", "monthly OOS"] tick_decorator = AxisTickLabelsDecorator(labels=x_labels, axis=Axis.X, rotation=x_axis_labels_rotation) else: chart, tick_decorator = _get_simple_quantile_chart(simple_returns) else: # case where there is only one set of data chart, tick_decorator = _get_simple_quantile_chart(simple_returns) # fixed_format_decorator = AxesFormatterDecorator(x_major=fixed_formatter) chart.add_decorator(tick_decorator) # Set title. title = TitleDecorator("Return Quantiles") chart.add_decorator(title) chart.add_decorator(AxesLabelDecorator(y_label="Returns")) return chart
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 _create_single_heat_map(self, measure_name, result_df, tickers, min_v, max_v, third_param): chart = HeatMapChart(data=result_df, color_map=plt.get_cmap("coolwarm"), min_value=min_v, max_value=max_v) chart.add_decorator(AxisTickLabelsDecorator(labels=list(result_df.columns), axis=Axis.X)) chart.add_decorator(AxisTickLabelsDecorator(labels=list(reversed(result_df.index)), axis=Axis.Y)) chart.add_decorator(ValuesAnnotations()) param_names = self._get_param_names() chart.add_decorator(AxesLabelDecorator(x_label=param_names[1], y_label=param_names[0])) if third_param is None: title = self._get_chart_title(tickers, measure_name) else: title = "{}, {} = {:0.2f}".format(self._get_chart_title(tickers, measure_name), param_names[2], third_param) chart.add_decorator(TitleDecorator(title)) self._resize_chart(chart) return chart
def create_holdings_chart(positions: QFDataFrame) -> LineChart: """ Creates a line chart showing holdings per day based on the specified positions. Parameters ---------- positions: QFDataFrame Positions as returned by the extract_rets_pos_txn_from_zipline function. Returns ------- LineChart Created line chart """ # Based on: # https://github.com/quantopian/pyfolio/blob/5d63df4ca6e0ead83f4bebf9860732d37f532026/pyfolio/plotting.py#L323 result = LineChart() # Perform some calculations. positions = positions.copy().drop("cash", axis="columns") holdings = positions.apply(lambda x: np.sum(x != 0), axis="columns") holdings_by_month = holdings.resample("1M").mean() holdings_decorator = DataElementDecorator(holdings, color="steelblue", linewidth=1.5) result.add_decorator(holdings_decorator) holdings_by_month_decorator = DataElementDecorator(holdings_by_month, color="orangered", alpha=0.5, linewidth=2) result.add_decorator(holdings_by_month_decorator) hline_decorator = HorizontalLineDecorator(holdings.values.mean(), linestyle="--") result.add_decorator(hline_decorator) legend = LegendDecorator() legend.add_entry(holdings_decorator, "Daily Holdings") legend.add_entry(holdings_by_month_decorator, "Average Daily Holdings, by month") legend.add_entry(hline_decorator, "Average Daily Holdings, net") result.add_decorator(legend) result.add_decorator(TitleDecorator("Holdings per Day")) result.add_decorator( AxesLabelDecorator(y_label="Amount of holdings per Day")) return result
def beta_and_alpha_chart(self, benchmark_coefficients: Sequence[float] = None ) -> BarChart: colors_palette = Chart.get_axes_colors() coeff_names = [ self._get_security_name(ticker) for ticker in self.model.coefficients.index.values ] coeff_values = self.model.coefficients.values bars_colors = [colors_palette[0]] * len(self.model.coefficients) title = 'Coefficients of regressors' if self.model.input_data.is_fit_intercept: coeff_names = np.insert(coeff_names, 0, "intercept") coeff_values = np.insert(coeff_values, 0, self.model.intercept) bars_colors = ['gold'] + bars_colors if benchmark_coefficients is not None: raise ValueError( "Benchmark coefficients aren't used when model contains a bias value (constant)" ) elif benchmark_coefficients is not None: coeff_values -= benchmark_coefficients title = 'Relative coefficients of regressors' index_translator = self._get_index_translator(coeff_names) coefficients = QFSeries(index=pd.Index(coeff_names), data=coeff_values) bar_chart = BarChart(orientation=Orientation.Horizontal, index_translator=index_translator, thickness=self._bars_width, align='center') bar_chart.add_decorator( DataElementDecorator(coefficients, color=bars_colors)) bar_chart.add_decorator(TitleDecorator(title)) bar_chart.add_decorator(AxesLabelDecorator(x_label="sensitivity")) labels = ['{:.2f}'.format(value) for value in coeff_values] self._add_labels_for_bars(bar_chart, coefficients, labels) return bar_chart
def _create_single_heat_map(self, title, result_df, min_v, max_v): chart = HeatMapChart(data=result_df, color_map=plt.get_cmap("coolwarm"), min_value=min_v, max_value=max_v) chart.add_decorator( AxisTickLabelsDecorator(labels=list(result_df.columns), axis=Axis.X)) chart.add_decorator( AxisTickLabelsDecorator(labels=list(reversed(result_df.index)), axis=Axis.Y)) chart.add_decorator(ValuesAnnotations()) param_names = self._get_param_names() chart.add_decorator( AxesLabelDecorator(x_label=param_names[1], y_label=param_names[0])) chart.add_decorator(TitleDecorator(title)) position_decorator = AxesPositionDecorator(*self.image_axis_position) chart.add_decorator(position_decorator) return chart
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 main(): tms = data_provider.get_price(QuandlTicker('AAPL', 'WIKI'), PriceField.Close, start_date, end_date) tms2 = data_provider.get_price(QuandlTicker('MSFT', 'WIKI'), PriceField.Close, start_date, end_date) line_chart = LineChart() data_element = DataElementDecorator(tms) line_chart.add_decorator(data_element) data_element2 = DataElementDecorator(tms2, use_secondary_axes=True) line_chart.add_decorator(data_element2) axes_decorator = AxesLabelDecorator(x_label='dates', y_label='primary', secondary_y_label='secondary') line_chart.add_decorator(axes_decorator) legend = LegendDecorator(legend_placement=Location.BEST) legend.add_entry(data_element, 'AAPL') legend.add_entry(data_element2, 'MSFT') line_chart.add_decorator(legend) line_chart.plot() plt.show(block=True)
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 _add_concentration_of_portfolio_chart(self, top_assets_numbers: tuple = (1, 5)): chart = LineChart(rotate_x_axis=False) chart.add_decorator(AxesPositionDecorator(*self.full_image_axis_position)) chart.add_decorator(AxesLabelDecorator(y_label="Mean total exposure of top assets")) chart.add_decorator(TitleDecorator("Concentration of assets")) legend = LegendDecorator(key="legend_decorator") chart.add_decorator(legend) # Add top asset contribution positions_history = self.backtest_result.portfolio.positions_history() if positions_history.empty: raise ValueError("No positions found in positions history") positions_history = positions_history.applymap( lambda x: x.total_exposure if isinstance(x, BacktestPositionSummary) else 0) # Group all the tickers by their names and take the maximal total exposure for each of the groups - in case # if two contracts for a single asset will be included in the open positions in the portfolio at any point of # time, only one (with higher total exposure) will be considered while generating the top assets plot assets_history = positions_history.groupby(by=lambda ticker: ticker.name, axis='columns').apply( lambda x: x.abs().max(axis=1)) for assets_number in top_assets_numbers: # For each date (row), find the top_assets largest assets and compute the mean value of their market value top_assets_mean_values = assets_history.stack(dropna=False).groupby(level=0).apply( lambda group: group.nlargest(assets_number).mean() ).resample('D').last() # Divide the computed mean values by the portfolio value, for each of the dates top_assets_percentage_value = top_assets_mean_values / self.backtest_result.portfolio.portfolio_eod_series() concentration_top_asset = DataElementDecorator(top_assets_percentage_value) chart.add_decorator(concentration_top_asset) legend.add_entry(concentration_top_asset, "TOP {} assets".format(assets_number)) self.document.add_element(ChartElement(chart, figsize=self.full_image_size, dpi=self.dpi))
def create_dot_plot(series1: QFSeries, series2: QFSeries, x_label: str, y_label: str, start_x: float = None, end_x: float = None) -> LineChart: """Create a dot linechart. Parameters ----------- series1: QFSeries series2: QFSeries x_label: str y_label: str start_x: float end_x: float Returns -------- LineChart """ # Combine the series. combined = pd.concat([series1, series2], axis=1) combined_series = QFSeries(data=combined.iloc[:, 0].values, index=combined.iloc[:, 1].values) # Create a new line chart. line_chart = LineChart(start_x=start_x, end_x=end_x) line_chart.tick_fontweight = "bold" line_chart.tick_color = "black" # Add the data. data_element = DataElementDecorator(combined_series, marker="o") line_chart.add_decorator(data_element) # Add a title. title_decorator = TitleDecorator("US Beveridge Curve", key="title") line_chart.add_decorator(title_decorator) # Add axes labels. axes_label_decorator = AxesLabelDecorator(x_label=x_label, y_label=y_label) line_chart.add_decorator(axes_label_decorator) # Emphasise the last point. # This series has many NaNs, we want to retrieve the last non-NaN point. no_nans = combined_series.dropna() point_to_emphasise = (no_nans.index[len(no_nans) - 1], no_nans.values[len(no_nans) - 1]) point_emphasis_decorator = PointEmphasisDecorator(data_element, point_to_emphasise, color="#CC1414", decimal_points=1, label_format="") line_chart.add_decorator(point_emphasis_decorator) # Create a legend. legend_decorator = LegendDecorator() last_date = series1.dropna().index.max() legend_decorator.add_entry( point_emphasis_decorator, "Latest ({:g}, {:g}) [{}]".format(point_to_emphasise[0], point_to_emphasise[1], last_date.strftime("%b %y"))) line_chart.add_decorator(legend_decorator) return line_chart
def create_returns_similarity(strategy: QFSeries, benchmark: QFSeries, mean_normalization: bool = True, std_normalization: bool = True, frequency: Frequency = None) -> KDEChart: """ Creates a new returns similarity chart. The frequency is determined by the specified returns series. Parameters ---------- strategy: QFSeries The strategy series to plot. benchmark: QFSeries The benchmark series to plot. mean_normalization: bool Whether to perform mean normalization on the series data. std_normalization: bool Whether to perform variance normalization on the series data. frequency: Frequency Returns can be aggregated in to specific frequency before plotting the chart Returns ------- KDEChart A newly created KDEChart instance. """ chart = KDEChart() colors = Chart.get_axes_colors() if frequency is not None: aggregate_strategy = get_aggregate_returns( strategy.to_simple_returns(), frequency) aggregate_benchmark = get_aggregate_returns( benchmark.to_simple_returns(), frequency) else: aggregate_strategy = strategy.to_simple_returns() aggregate_benchmark = benchmark.to_simple_returns() scaled_strategy = preprocessing.scale(aggregate_strategy, with_mean=mean_normalization, with_std=std_normalization) strategy_data_element = DataElementDecorator(scaled_strategy, bw="scott", shade=True, label=strategy.name, color=colors[0]) chart.add_decorator(strategy_data_element) scaled_benchmark = preprocessing.scale(aggregate_benchmark, with_mean=mean_normalization, with_std=std_normalization) benchmark_data_element = DataElementDecorator(scaled_benchmark, bw="scott", shade=True, label=benchmark.name, color=colors[1]) chart.add_decorator(benchmark_data_element) # Add a title. title = _get_title(mean_normalization, std_normalization, frequency) title_decorator = TitleDecorator(title, key="title") chart.add_decorator(title_decorator) chart.add_decorator(AxesLabelDecorator("Returns", "Similarity")) return chart
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
def _get_underwater_chart(self, series: QFSeries): underwater_chart = UnderwaterChart(series, rotate_x_axis=True) underwater_chart.add_decorator(TopDrawdownDecorator(series, 5)) underwater_chart.add_decorator(AxesLabelDecorator(y_label="Drawdown")) underwater_chart.add_decorator(TitleDecorator("Drawdown")) return underwater_chart
def _add_line_plots(self, tickers: Sequence[Ticker]): parameters_list = sorted( self.backtest_evaluator.params_backtest_summary_elem_dict.keys()) title_to_plot = defaultdict(lambda: LineChart()) title_to_legend = defaultdict( lambda: LegendDecorator(key="legend_decorator")) for start_time, end_time in [ (self.backtest_summary.start_date, self.out_of_sample_start_date), (self.out_of_sample_start_date, self.backtest_summary.end_date) ]: results = [] for param_tuple in parameters_list: trades_eval_result = self.backtest_evaluator.evaluate_params_for_tickers( param_tuple, tickers, start_time, end_time) results.append(trades_eval_result) sqn_avg_nr_trades = DataElementDecorator( [x.sqn_per_avg_nr_trades for x in results]) avg_nr_of_trades = DataElementDecorator( [x.avg_nr_of_trades_1Y for x in results]) annualised_return = DataElementDecorator( [x.annualised_return for x in results]) adjusted_start_time = min([x.start_date for x in results]) adjusted_end_time = max([x.end_date for x in results]) if adjusted_start_time >= adjusted_end_time: adjusted_end_time = adjusted_start_time if adjusted_start_time <= self.backtest_summary.end_date \ else end_time adjusted_start_time = start_time title = "{} - {} ".format(adjusted_start_time.strftime("%Y-%m-%d"), adjusted_end_time.strftime("%Y-%m-%d")) title_to_plot["SQN (Arithmetic return) per year"].add_decorator( sqn_avg_nr_trades) title_to_legend["SQN (Arithmetic return) per year"].add_entry( sqn_avg_nr_trades, title) title_to_plot["Avg # trades 1Y"].add_decorator(avg_nr_of_trades) title_to_legend["Avg # trades 1Y"].add_entry( sqn_avg_nr_trades, title) if len(tickers) == 1: title_to_plot["Annualised return"].add_decorator( annualised_return) title_to_legend["Annualised return"].add_entry( annualised_return, title) tickers_used = "Many tickers" if len(tickers) > 1 else ( tickers[0].name) for description, line_chart in title_to_plot.items(): self.document.add_element( HeadingElement(3, "{} - {}".format(description, tickers_used))) line_chart.add_decorator( AxesLabelDecorator(x_label=self._get_param_names()[0], y_label=title)) position_decorator = AxesPositionDecorator( *self.image_axis_position) line_chart.add_decorator(position_decorator) legend = title_to_legend[description] line_chart.add_decorator(legend) self.document.add_element( ChartElement(line_chart, figsize=self.full_image_size))
def create_dd_probability_chart( prices_tms: QFSeries, bear_market_definition: float = 0.2) -> Tuple[Chart, Chart]: """Creates drawdowns probability chart. Parameters ---------- prices_tms: QFSeries timeseries of prices bear_market_definition: float definition of bear market threshold Returns ------- Tuple[Chart, Chart] Returns two charts - one showing the probability of drawdowns going beyond a certain level and one showing the marginal increase of probability of drawdowns going beyond the given level. """ def count_dd_above_threshold(drawdown_series: Sequence, threshold: float): return sum(1 for dd in drawdown_series if dd > threshold) drawdowns, duration_of_drawdowns = list_of_max_drawdowns(prices_tms) examined_dds = np.arange(0.01, bear_market_definition, 0.005) percentage_ending_in_bear_market = [] nr_of_bear_markets = count_dd_above_threshold(drawdowns, bear_market_definition) for examined_dd in examined_dds: number_of_dds_above = count_dd_above_threshold(drawdowns, examined_dd) percentage = nr_of_bear_markets / number_of_dds_above * 100 percentage_ending_in_bear_market.append(percentage) chart = LineChart() chart.add_decorator( ScatterDecorator(examined_dds * 100, percentage_ending_in_bear_market, edgecolors='black')) chart.add_decorator( TitleDecorator("Percentage of drawdowns going beyond {:2.0f}%".format( bear_market_definition * 100))) axis_dec = AxesLabelDecorator( "examined drawdown [%]", "chance that drawdown will go beyond {:2.0f}% in [%]".format( bear_market_definition * 100)) chart.add_decorator(axis_dec) x_axis_values = examined_dds * 100 prob_of_dd_chart = LineChart() prob_of_dd_chart.add_decorator( ScatterDecorator(x_axis_values, percentage_ending_in_bear_market, edgecolors='black')) prob_of_dd_chart.add_decorator( TitleDecorator("Percentage of drawdowns going beyond {:2.0f}%".format( bear_market_definition * 100))) axis_dec = AxesLabelDecorator( "examined drawdown [%]", "chance that drawdown will go beyond {:2.0f}% in [%]".format( bear_market_definition * 100)) prob_of_dd_chart.add_decorator(axis_dec) marginal_increase_in_prob_chart = LineChart() diff = np.diff([0] + percentage_ending_in_bear_market) marginal_increase_in_prob_chart.add_decorator( ScatterDecorator(x_axis_values, diff, edgecolors='black')) marginal_increase_in_prob_chart.add_decorator( TitleDecorator( "Marginal increase of probability of drawdowns going beyond {:2.0f}%" .format(bear_market_definition * 100))) axis_dec = AxesLabelDecorator( "examined drawdown [%]", "Marginal increase of chance that drawdown will go beyond {:2.0f}% in [%]" .format(bear_market_definition * 100)) marginal_increase_in_prob_chart.add_decorator(axis_dec) return prob_of_dd_chart, marginal_increase_in_prob_chart
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