def __get_perf_chart(self, series_list, is_large_chart): strategy = series_list[0].to_prices( 1) # the main strategy should be the first series log_scale = True if strategy[ -1] > 10 else False # use log scale for returns above 1 000 % if is_large_chart: chart = LineChart(start_x=strategy.index[0], end_x=strategy.index[-1], log_scale=log_scale) position_decorator = AxesPositionDecorator( *self.full_image_axis_position) chart.add_decorator(position_decorator) else: chart = LineChart(log_scale=log_scale, rotate_x_axis=True) line_decorator = HorizontalLineDecorator(1, key="h_line", linewidth=1) chart.add_decorator(line_decorator) legend = LegendDecorator() for series in series_list: strategy_tms = series.to_prices(1) series_elem = DataElementDecorator(strategy_tms) chart.add_decorator(series_elem) legend.add_entry(series_elem, strategy_tms.name) chart.add_decorator(legend) title_decorator = TitleDecorator("Strategy Performance", key="title") chart.add_decorator(title_decorator) 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 _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 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 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 _add_price_chart(self, prices_df: QFDataFrame): close_tms = prices_df[PriceField.Close] price_tms = close_tms.to_prices(1) chart = LineChart(start_x=price_tms.index[0], end_x=price_tms.index[-1]) price_elem = DataElementDecorator(price_tms) chart.add_decorator(price_elem) line_decorator = HorizontalLineDecorator(1, key="h_line", linewidth=1) chart.add_decorator(line_decorator) legend = LegendDecorator() legend.add_entry(price_elem, "Close Price") chart.add_decorator(legend) title_decorator = TitleDecorator("Price of the instrument", key="title") chart.add_decorator(title_decorator) self._add_axes_position_decorator(chart) self.document.add_element(ChartElement(chart, figsize=self.image_size, dpi=self.dpi))
def _plot_ticker_performance(self, ticker_name: str, performance): self.document.add_element(HeadingElement(level=2, text="PnL of {}".format(ticker_name))) chart = LineChart() legend = LegendDecorator(key="legend_decorator") line_colors = iter(("#add8e6", "#000000", "#fa8072")) for title, pnl_series in performance.iteritems(): # Plot series only in case if it consist anything else then 0 if (pnl_series != 0).any(): data_series = DataElementDecorator(pnl_series, **{"color": next(line_colors)}) legend.add_entry(data_series, title) chart.add_decorator(data_series) chart.add_decorator(legend) self.document.add_element(ChartElement(chart, figsize=self.full_image_size, dpi=self.dpi))
def _add_trend_strength_chart(self, prices_df: QFDataFrame): def _fun(df): return trend_strength(df, self.use_next_open_instead_of_close) trend_strength_tms = prices_df.rolling_time_window(window_length=self.window_len, step=1, func=_fun) chart = LineChart() trend_elem = DataElementDecorator(trend_strength_tms, color='black') chart.add_decorator(trend_elem) legend = LegendDecorator(legend_placement=Location.BEST, key='legend') legend.add_entry(trend_elem, 'Trend strength') chart.add_decorator(legend) title_decorator = TitleDecorator("Strength of the trend - rolling {} days".format(self.window_len), key="title") chart.add_decorator(title_decorator) self._add_axes_position_decorator(chart) self.document.add_element(ChartElement(chart, figsize=self.image_size, dpi=self.dpi))
def _create_line_chart(self, rolling_values, title): line_chart = LineChart() legend = LegendDecorator() for column_name, values_tms in rolling_values.iteritems(): coeff_tms_data_elem = DataElementDecorator(values_tms) line_chart.add_decorator(coeff_tms_data_elem) legend.add_entry(coeff_tms_data_elem, column_name) full_title_str = "".join([ title, ' {:d} samples rolling'.format(self.rolling_model.window_size_) ]) line_chart.add_decorator(TitleDecorator(full_title_str)) line_chart.add_decorator( AxesFormatterDecorator(x_major=DateFormatter( fmt=str(DateFormat.YEAR_DOT_MONTH)))) line_chart.add_decorator(legend) return line_chart
def main(): data_provider = container.resolve(GeneralPriceProvider) prices_tms = data_provider.get_price(QuandlTicker('AAPL', 'WIKI'), PriceField.Close, start_date, end_date) line_chart = LineChart() data_element = DataElementDecorator(prices_tms) line_chart.add_decorator(data_element) legend = LegendDecorator(legend_placement=Location.BEST, key='legend') legend.add_entry(data_element, 'SPY') line_chart.add_decorator(legend) cone_decorator = ConeDecorator(live_start_date=live_start_date, series=prices_tms, key='cone') line_chart.add_decorator(cone_decorator) line_chart.plot() plt.show(block=True)
def get_cone_chart(paths_data_frame, series_list, names_list, title=None, log_sacle=True): """ Helper function to plot simulated paths together with significant lines (like expectation) """ line_chart = LineChart(log_scale=log_sacle) # plot all paths for series_name in paths_data_frame: series_element = DataElementDecorator(paths_data_frame[series_name], linewidth=1) line_chart.add_decorator(series_element) legend_decorator = LegendDecorator(key='legend') colors = ['black', 'red', 'green', 'purple', 'lime'] for i in range(len(series_list)): series = series_list[i] name = names_list[i] series_element = DataElementDecorator(series, color=colors[i % len(colors)], linewidth=3) line_chart.add_decorator(series_element) legend_decorator.add_entry(series_element, name) point = (series.index[-1], series[series.index[-1]]) point_emphasis = PointEmphasisDecorator(series_element, point, move_point=False) line_chart.add_decorator(point_emphasis) line_chart.add_decorator(legend_decorator) # Create a title. if title is not None: title_decorator = TitleDecorator(title, "title") line_chart.add_decorator(title_decorator) return line_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 _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 main(): data_provider = container.resolve(GeneralPriceProvider) series = data_provider.get_price(QuandlTicker('AAPL', 'WIKI'), PriceField.Close, start_date=start_date, end_date=end_date) series = series.to_prices(1) vol_manager = VolatilityManager(series) managed_series, weights_series = vol_manager.get_managed_series( vol_level=0.2, window_size=20, lag=1, min_leverage=min_lev, max_leverage=max_lev) managed_series = managed_series.to_prices(1) line_chart = LineChart() series_element = DataElementDecorator(series) managed_element = DataElementDecorator(managed_series) line_chart.add_decorator(series_element) line_chart.add_decorator(managed_element) legend = LegendDecorator(legend_placement=Location.BEST, key='legend') legend.add_entry(series_element, 'Original') legend.add_entry(managed_element, 'Vol_managed') line_chart.add_decorator(legend) line_chart.plot() plt.show(block=True)
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 _create_legend(bar_chart, data_elements, line_decorators, names_list, quarterly: bool) -> LegendDecorator: legend_decorator = LegendDecorator(key='legend') i = 0 for data_element in data_elements: bar_label = names_list[i] if bar_label is not None: date = data_element.data.index[-1] formatted_date = date.strftime("%b %y") if quarterly: formatted_date = "Q{} {}".format(str(date.quarter), date.year) last_date_label = " [{}]".format(formatted_date) legend_decorator.add_entry(data_element, bar_label + last_date_label) i += 1 for line_decorator in line_decorators: series_label = names_list[i] if series_label is not None: legend_decorator.add_entry(line_decorator, series_label) i += 1 bar_chart.add_decorator(legend_decorator) return legend_decorator
def _add_up_and_down_trend_strength(self, prices_df: QFDataFrame): def _down_trend_fun(df): return down_trend_strength(df, self.use_next_open_instead_of_close) def _up_trend_fun(df): return up_trend_strength(df, self.use_next_open_instead_of_close) up_trend_strength_tms = prices_df.rolling_time_window(window_length=self.window_len, step=1, func=_down_trend_fun) down_trend_strength_tms = prices_df.rolling_time_window(window_length=self.window_len, step=1, func=_up_trend_fun) chart = LineChart() up_trend_elem = DataElementDecorator(up_trend_strength_tms) down_trend_elem = DataElementDecorator(down_trend_strength_tms) chart.add_decorator(up_trend_elem) chart.add_decorator(down_trend_elem) legend = LegendDecorator(legend_placement=Location.BEST, key='legend') legend.add_entry(up_trend_elem, 'Up trend strength') legend.add_entry(down_trend_elem, 'Down trend strength') chart.add_decorator(legend) title = "Strength of the up and down trend - rolling {} days".format(self.window_len) title_decorator = TitleDecorator(title, key="title") chart.add_decorator(title_decorator) self._add_axes_position_decorator(chart) self.document.add_element(ChartElement(chart, figsize=self.image_size, dpi=self.dpi))
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 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 _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 _get_underwater_chart(self, series: QFSeries, title="Drawdown", benchmark_series: QFSeries = None, rotate_x_axis: bool = False): underwater_chart = LineChart(start_x=series.index[0], end_x=series.index[-1], log_scale=False, rotate_x_axis=rotate_x_axis) underwater_chart.add_decorator(UnderwaterDecorator(series)) underwater_chart.add_decorator(TitleDecorator(title)) if benchmark_series is not None: legend = LegendDecorator() benchmark_dd = PricesSeries(drawdown_tms(benchmark_series)) benchmark_dd *= -1 benchmark_dd_elem = DataElementDecorator(benchmark_dd, color="black", linewidth=0.5) legend.add_entry(benchmark_dd_elem, "Benchmark DD") underwater_chart.add_decorator(benchmark_dd_elem) underwater_chart.add_decorator(legend) return underwater_chart
def main(): # GENERATE DATA regressors_and_fund_df = QFDataFrame(data=[[1, 3, 5], [2, 3, 1], [3, 1, 2], [4, 2, 3], [5, 3, 4]], index=pd.bdate_range( start='2015-01-01', periods=5), columns=['a', 'b', 'c']) # add data to the chart and the legend marker_props_template = {'alpha': 0.5} stemline_props_template = {'linestyle': '-.', 'linewidth': 0.2} baseline_props = {'visible': True} colors = cycle(Chart.get_axes_colors()) chart = LineChart(start_x=str_to_date('2014-12-31'), end_x=str_to_date('2015-01-08')) legend = LegendDecorator() for name, 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, name) chart.add_decorator(legend) chart.plot() plt.show(block=True)
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_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 _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 _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 _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 main(): tms = QFSeries(data=[200, 20, 300, 40], index=[1, 2, 3, 4]) tms2 = QFSeries(data=[80, 20, 100, 40], index=[1, 2, 3, 4]) tms3 = QFSeries(data=[80, 20, 100, 40], index=[1, 2, 3, 4]) bar_chart = BarChart(Orientation.Vertical) data_element = DataElementDecorator(tms) bar_chart.add_decorator(data_element) data_element2 = DataElementDecorator(tms2) bar_chart.add_decorator(data_element2) bar_chart.add_decorator(SeriesLineDecorator(tms3, use_secondary_axes=True)) legend = LegendDecorator(legend_placement=Location.BEST) legend.add_entry(data_element, 'Series 1') legend.add_entry(data_element2, 'Series 2') bar_chart.add_decorator(legend) bar_chart.plot() plt.show(block=True)