Пример #1
0
    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)
Пример #2
0
    def _create_performance_contribution_tables(
            self, performance_df: QFDataFrame) -> List[DFTable]:
        """
        Create a list of DFTables with assets names in the index and different years / months in columns, which contains
        details on the performance contribution for each asset.
        """
        # Create a QFSeries which contains the initial amount of cash in the portfolio for each year / month
        numeric_columns = [
            col for col in performance_df.columns
            if is_numeric_dtype(performance_df[col])
        ]
        portfolio_values = performance_df[numeric_columns].sum().shift(
            fill_value=self._initial_cash).cumsum()
        performance_df[numeric_columns] = performance_df[
            numeric_columns] / portfolio_values[numeric_columns]

        # Add category column and aggregate data accordingly
        ticker_name_to_category = {
            t.name: category
            for t, category in self._ticker_to_category.items()
        }
        performance_df["Category"] = performance_df["Asset"].apply(
            lambda t: ticker_name_to_category[t])
        all_categories = list(set(ticker_name_to_category.values()))
        performance_df = performance_df.sort_values(by=["Category", "Asset"])
        performance_df = performance_df.groupby("Category").apply(
            lambda d: pd.concat([
                PricesDataFrame({
                    **{
                        "Asset": [d.name],
                        "Category": [d.name]
                    },
                    **{c: [d[c].sum()]
                       for c in numeric_columns}
                }), d
            ],
                                ignore_index=True)).drop(columns=["Category"])

        # Add the Total Performance row (divide by 2 as the df contains already aggregated data for each group)
        total_sum_row = performance_df[numeric_columns].sum() / 2
        total_sum_row["Asset"] = "Total Performance"
        performance_df = performance_df.append(total_sum_row,
                                               ignore_index=True)

        # Format the rows using the percentage formatter
        performance_df[numeric_columns] = performance_df[
            numeric_columns].applymap(lambda x: '{:.2%}'.format(x))

        # Divide the performance dataframe into a number of dataframes, so that each of them contains up to
        # self._max_columns_per_page columns
        split_dfs = np.array_split(performance_df.set_index("Asset"),
                                   np.ceil(
                                       (performance_df.num_of_columns - 1) /
                                       self._max_columns_per_page),
                                   axis=1)
        df_tables = [
            DFTable(df.reset_index(),
                    css_classes=[
                        'table', 'shrink-font', 'right-align',
                        'wide-first-column'
                    ]) for df in split_dfs
        ]

        # Get the indices of rows, which contain category info
        category_indices = performance_df[performance_df["Asset"].isin(
            all_categories)].index

        for df_table in df_tables:
            # Add table formatting, highlight rows showing the total contribution of the given category
            df_table.add_rows_styles(
                category_indices, {
                    "font-weight": "bold",
                    "font-size": "0.95em",
                    "background-color": "#cbd0d2"
                })
            df_table.add_rows_styles(
                [performance_df.index[-1]], {
                    "font-weight": "bold",
                    "font-size": "0.95em",
                    "background-color": "#b9bcbd"
                })
        return df_tables