def _add_heat_maps(self, tickers: Sequence[Ticker]): parameters_list = sorted( self.backtest_evaluator.params_backtest_summary_elem_dict.keys()) # Group plots by type, so that they appear in the given logical order title_to_grid = defaultdict(lambda: GridElement( mode=PlottingMode.PDF, figsize=self.image_size)) 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 = QFDataFrame() for param_tuple in parameters_list: trades_eval_result = self.backtest_evaluator.evaluate_params_for_tickers( param_tuple, tickers, start_time, end_time) row, column = param_tuple results.loc[row, column] = trades_eval_result results.sort_index(axis=0, inplace=True, ascending=False) results.sort_index(axis=1, inplace=True) results.fillna(TradesEvaluationResult(), inplace=True) sqn_avg_nr_trades = results.applymap( lambda x: x.sqn_per_avg_nr_trades).fillna(0) avg_nr_of_trades = results.applymap( lambda x: x.avg_nr_of_trades_1Y).fillna(0) annualised_return = results.applymap( lambda x: x.annualised_return).fillna(0) adjusted_start_time = results.applymap( lambda x: x.start_date).min().min() adjusted_end_time = results.applymap( lambda x: x.end_date).max().max() 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_grid["SQN (Arithmetic return) per year"].add_chart( self._create_single_heat_map(title, sqn_avg_nr_trades, 0, 0.5)) title_to_grid["Avg # trades 1Y"].add_chart( self._create_single_heat_map(title, avg_nr_of_trades, 2, 15)) if len(tickers) == 1: title_to_grid["Annualised return"].add_chart( self._create_single_heat_map(title, annualised_return, 0.0, 0.3)) tickers_used = "Many tickers" if len(tickers) > 1 else ( tickers[0].name if isinstance( tickers[0], FutureTicker) else tickers[0].as_string()) for description, grid in title_to_grid.items(): self.document.add_element( HeadingElement(3, "{} - {}".format(description, tickers_used))) self.document.add_element(grid)
def _add_avg_time_in_the_market_per_ticker(self): """ Compute the total time in the market per ticker (separately for long and short positions) in minutes and divide it by the total duration of the backtest in minutes. """ self.document.add_element(NewPageElement()) self.document.add_element(HeadingElement(level=2, text="Average time in the market per asset")) start_time = self.backtest_result.start_date end_time = self.backtest_result.portfolio.timer.now() backtest_duration = pd.Timedelta(end_time - start_time) / pd.Timedelta(minutes=1) # backtest duration in min closed_positions_time = [ (self._ticker_name(position.contract()), position.start_time, position.end_time, position.direction()) for position in self.backtest_result.portfolio.closed_positions() ] open_positions_time = [ (self._ticker_name(c), position.start_time, end_time, position.direction()) for c, position in self.backtest_result.portfolio.open_positions_dict.items() ] positions = QFDataFrame(data=closed_positions_time + open_positions_time, columns=["Tickers name", "Start time", "End time", "Position direction"]) def compute_duration(grouped_rows): return pd.DatetimeIndex([]).union_many( [pd.date_range(row["Start time"], row["End time"], freq='T', closed='left') for _, row in grouped_rows.iterrows()]).size positions = positions.groupby(by=["Tickers name", "Position direction"]).apply(compute_duration) \ .rename("Duration (minutes)").reset_index() positions["Duration"] = positions["Duration (minutes)"] / backtest_duration positions = positions.pivot_table(index="Tickers name", columns="Position direction", values="Duration").reset_index() positions = positions.rename(columns={-1: "Short", 1: "Long"}) # Add default 0 column in case if only short / long positions occurred in the backtest for column in ["Short", "Long"]: if column not in positions.columns: positions[column] = 0.0 positions["Out"] = 1.0 - positions["Long"] - positions["Short"] positions[["Long", "Short", "Out"]] = positions[["Long", "Short", "Out"]].applymap(lambda x: '{:.2%}'.format(x)) positions = positions.fillna(0.0) table = DFTable(positions, css_classes=['table', 'left-align']) table.add_columns_classes(["Tickers name"], 'wide-column') self.document.add_element(table)