def create_tickers_analysis(self, ticker: Ticker): """ For the given ticker add candlestick charts representing OHLC prices with highlighted signals generated by each of the alpha models. In case if the model generates a BUY signal the background for the given day is highlighted with green color, in case of a SELL signal - red. """ prices_df = self.get_prices(ticker) alpha_model_signals: Dict[AlphaModel, QFSeries] = {} self.document.add_element(HeadingElement(level=2, text=ticker.name)) for alpha_model in self.alpha_models: exposures = [] dates = [] prev_exposure = Exposure.OUT for date in date_range(self.start_date, self.end_date, freq="B"): try: self.timer.set_current_time(date) new_exposure = alpha_model.get_signal( ticker, prev_exposure).suggested_exposure exposures.append(new_exposure.value) dates.append(date) if not self.only_entry_signals: prev_exposure = new_exposure except NoValidTickerException as e: print(e) exposures_series = QFSeries(data=exposures, index=dates) alpha_model_signals[alpha_model] = exposures_series candlestick_chart = CandlestickChart(prices_df, title=str(alpha_model)) candlestick_chart.add_highlight(exposures_series) self.document.add_element( ChartElement(candlestick_chart, figsize=self.full_image_size, dpi=self.dpi)) candlestick_chart = CandlestickChart(prices_df, title="All models summary") for model, exposures_series in alpha_model_signals.items(): candlestick_chart.add_highlight(exposures_series) self.document.add_element( ChartElement(candlestick_chart, figsize=self.full_image_size, dpi=self.dpi))
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 _add_rolling_return_chart(self, timeseries_list): def tot_return(window): return PricesSeries(window).total_cumulative_return() chart = self._get_rolling_chart(timeseries_list, tot_return, "Return") self.document.add_element( ChartElement(chart, figsize=self.full_image_size, dpi=self.dpi))
def _add_gini_coefficient_chart(self): chart = LineChart(rotate_x_axis=False) position_decorator = AxesPositionDecorator(*self.full_image_axis_position) chart.add_decorator(position_decorator) # Get the assets history assets_history = self.backtest_result.portfolio.positions_history() assets_history = assets_history.applymap( lambda x: x.total_exposure if isinstance(x, BacktestPositionSummary) else 0) def gini(group): # Function computing the Gini coefficients for each row group = group.abs() ranks = group.rank(ascending=True) mean_value = group.mean() samples = group.size group = group * (2 * ranks - samples - 1) return group.sum() / (samples * samples * mean_value) assets_history = assets_history.stack(dropna=False).groupby(level=0).apply(gini).to_frame(name="Gini " "coefficient") assets_history_decorator = DataElementDecorator(assets_history) chart.add_decorator(assets_history_decorator) # Add title title_decorator = TitleDecorator("Concentration of assets - Gini coefficient") chart.add_decorator(title_decorator) self.document.add_element(ChartElement(chart, figsize=self.full_image_size, dpi=self.dpi))
def _add_line_chart_element(self, series, title): chart = self._get_line_chart(series, title) position_decorator = AxesPositionDecorator(*self.full_image_axis_position) chart.add_decorator(position_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) 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 _add_leverage_chart(self): lev_chart = self._get_leverage_chart(self.leverage) position_decorator = AxesPositionDecorator( *self.full_image_axis_position) lev_chart.add_decorator(position_decorator) self.document.add_element( ChartElement(lev_chart, figsize=self.full_image_size, dpi=self.dpi))
def _add_chart(self, df: QFDataFrame, title: str): chart = self._create_chart(df, title) self.document.add_element( ChartElement( chart=chart, figsize=self.full_image_size, dpi=self.dpi, ))
def _add_perf_chart(self, series_list: List[QFSeries]): """ series_list List of compared series. The strategy should always be the first element in the list """ self.document.add_element( ChartElement(self._get_large_perf_chart(series_list), figsize=self.full_image_size, dpi=self.dpi))
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 _add_cone_chart(self): cone_chart = ConeChartOOS(self.strategy_series, is_mean_return=self.is_mean_return, is_sigma=self.is_sigma) position_decorator = AxesPositionDecorator(*self.full_image_axis_position) cone_chart.add_decorator(position_decorator) chart_element = ChartElement(cone_chart, self.full_image_size, self.dpi, False) self.document.add_element(chart_element)
def _add_rolling_vol_chart(self, timeseries_list): def volatility(window): return get_volatility(PricesSeries(window), Frequency.DAILY, annualise=True) chart = self._get_rolling_chart(timeseries_list, volatility, "Volatility") self.document.add_element( ChartElement(chart, figsize=self.full_image_size, dpi=self.dpi))
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_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 _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 _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 _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 _add_perf_chart_for_factor(self, series_list: List[QFSeries], title: str = "Factor Index Performance"): """ Add performance chart for factor Parameters ---------- series_list: List[QFSeries] list of compared series title: str chart title """ self.document.add_element( ChartElement(self._get_perf_chart(series_list, is_large_chart=True, title=title), figsize=self.full_image_size, dpi=self.dpi))
def add_chart(self, chart: Chart, grid_proportion=GridProportion.Eight) -> ChartElement: """ Add a chart to this grid. Plot settings such as ``figsize``, ``dpi`` and ``optimise`` will be based on the ones specified to this GridElement's constructor. Parameters ---------- chart grid_proportion The proportion of the grid that this chart should use up (out of 16). As an example, to get 4 charts side by side set this to ``4``. This is currently only used in the ``Web`` plotting mode. Returns ------- The ChartElement instance that was constructed for this chart. """ result = ChartElement(chart, self.figsize, self.dpi, self.optimise, grid_proportion) self._elements.append(result) return result
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 _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_ret_and_vol_chart(self, timeseries): chart = self._get_rolling_ret_and_vol_chart(timeseries) self.document.add_element( ChartElement(chart, figsize=self.full_image_size, dpi=self.dpi))
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))