예제 #1
0
    def _add_performance_statistics(self):
        """
        For each ticker computes its overall performance (PnL of short positions, PnL of long positions, total PnL).
        It generates a table containing final PnL values for each of the ticker nad optionally plots the performance
        throughout the backtest.
        """
        closed_positions = self.backtest_result.portfolio.closed_positions()
        closed_positions_pnl = QFDataFrame.from_records(
            data=[(self._ticker_name(p.contract()), p.end_time, p.direction(), p.total_pnl) for p in closed_positions],
            columns=["Tickers name", "Time", "Direction", "Realised PnL"]
        )
        closed_positions_pnl = closed_positions_pnl.sort_values(by="Time")

        # Get all open positions history
        open_positions_history = self.backtest_result.portfolio.positions_history()
        open_positions_history = open_positions_history.reset_index().melt(
            id_vars='index', value_vars=open_positions_history.columns, var_name='Contract',
            value_name='Position summary')
        open_positions_pnl = QFDataFrame(data={
            "Tickers name": open_positions_history["Contract"].apply(lambda contract: self._ticker_name(contract)),
            "Time": open_positions_history["index"],
            "Direction": open_positions_history["Position summary"].apply(
                lambda p: p.direction if isinstance(p, BacktestPositionSummary) else 0),
            "Total PnL of open position": open_positions_history["Position summary"].apply(
                lambda p: p.total_pnl if isinstance(p, BacktestPositionSummary) else 0)
        })

        all_positions_pnl = pd.concat([closed_positions_pnl, open_positions_pnl], sort=False)

        performance_dicts_series = all_positions_pnl.groupby(by=["Tickers name"]).apply(
            self._performance_series_for_ticker)
        performance_df = QFDataFrame(performance_dicts_series.tolist(), index=performance_dicts_series.index)

        self.document.add_element(NewPageElement())
        self.document.add_element(HeadingElement(level=2, text="Performance of each asset"))
        final_performance = performance_df. \
            applymap(lambda pnl_series: pnl_series.iloc[-1] if not pnl_series.empty else 0.0). \
            sort_values(by="Overall performance", ascending=False). \
            applymap(lambda p: '{:,.2f}'.format(p)). \
            reset_index()
        table = DFTable(final_performance, css_classes=['table', 'left-align'])
        table.add_columns_classes(["Tickers name"], 'wide-column')
        self.document.add_element(table)

        # Add performance plots
        if self.generate_pnl_chart_per_ticker:
            self.document.add_element(NewPageElement())
            self.document.add_element(
                HeadingElement(level=2, text="Performance of each asset during the whole backtest"))
            for ticker_name, performance in performance_df.iterrows():
                self._plot_ticker_performance(ticker_name, performance)
예제 #2
0
파일: df_table.py 프로젝트: espiney/qf-lib
class ModelController(object):
    def __init__(self,
                 data=None,
                 index=None,
                 columns=None,
                 dtype=None,
                 copy=False):

        self.logger = qf_logger.getChild(self.__class__.__name__)

        # Data Frame containing the table data
        self.data = QFDataFrame(data, index, columns, dtype, copy)

        # Dictionary containing a mapping from column names onto ColumnStyles
        self._columns_styles = {
            column_name: self.ColumnStyle(column_name)
            for column_name in self.data.columns.tolist()
        }

        # Series containing the styles for each of the rows
        self._rows_styles = QFSeries(
            data=[self.RowStyle(loc) for loc in self.data.index],
            index=self.data.index)

        # Data Frame containing styles for all cells in the table, based upon columns_styles and rows_styles
        self._styles = QFDataFrame(data={
            column_name: [
                self.CellStyle(row_style, column_style)
                for row_style in self.rows_styles
            ]
            for column_name, column_style in self.columns_styles.items()
        },
                                   index=self.data.index,
                                   columns=self.data.columns)

        self.table_styles = self.Style()

    def add_columns_styles(self, columns: Union[str, Sequence[str]],
                           styles_dict: Dict[str, str]):
        if not isinstance(columns, list):
            columns = [columns]

        for column_name in columns:
            self.columns_styles[column_name].add_styles(styles_dict)

    def add_columns_classes(self, columns: Union[str, Sequence[str]],
                            css_classes: Sequence[str]):
        if not isinstance(columns, list):
            columns = [columns]

        for column_name in columns:
            self.columns_styles[column_name].add_css_class(css_classes)

    def remove_columns_classes(self, columns: Union[str, Sequence[str]],
                               css_classes: Sequence[str]):
        if not isinstance(columns, list):
            columns = [columns]

        for column_name in columns:
            self.columns_styles[column_name].remove_css_class(css_classes)

    def remove_columns_styles(self, columns: Union[str, Sequence[str]],
                              styles: Union[Dict[str, str], Sequence[str]]):
        if not isinstance(columns, list):
            columns = [columns]

        for column_name in columns:
            self.columns_styles[column_name].remove_styles(styles)

    def add_rows_styles(self, loc_indexer: Union[Any, Sequence[Any]],
                        styles_dict: Dict[str, str]):
        for row in self.rows_styles.loc[loc_indexer]:
            row.add_styles(styles_dict)

    def add_rows_classes(self, loc_indexer: Union[Any, Sequence[Any]],
                         css_classes: Sequence[str]):
        for row in self.rows_styles.loc[loc_indexer]:
            row.add_css_class(css_classes)

    def remove_rows_styles(self, loc_indexer: Union[Any, Sequence[Any]],
                           styles: Union[Dict[str, str], Sequence[str]]):
        for row in self.rows_styles.loc[loc_indexer]:
            row.remove_styles(styles)

    def remove_rows_classes(self, loc_indexer: Union[Any, Sequence[Any]],
                            css_classes: Sequence[str]):
        for row in self.rows_styles.loc[loc_indexer]:
            row.remove_css_class(css_classes)

    def add_cells_styles(self, columns: Union[str, Sequence[str]],
                         rows: Union[Any, Sequence[Any]],
                         css_styles: Dict[str, str]):
        if not isinstance(columns, list):
            columns = [columns]

        if not isinstance(rows, list):
            rows = [rows]

        for column_name in columns:
            for row in rows:
                self.styles.loc[row, column_name].add_styles(css_styles)

    def add_cells_classes(self, columns: Union[str, Sequence[str]],
                          rows: Union[Any, Sequence[Any]],
                          css_classes: Sequence[str]):
        if not isinstance(columns, list):
            columns = [columns]

        if not isinstance(rows, list):
            rows = [rows]

        for column_name in columns:
            for row in rows:
                self.styles.loc[row, column_name].add_css_class(css_classes)

    def remove_cells_styles(self, columns: Union[str, Sequence[str]],
                            rows: Union[Any, Sequence[Any]],
                            styles: Union[Dict[str, str], Sequence[str]]):
        if not isinstance(columns, list):
            columns = [columns]

        if not isinstance(rows, list):
            rows = [rows]

        for column_name in columns:
            for row in rows:
                self.styles.loc[row, column_name].remove_styles(styles)

    def remove_cells_classes(self, columns: Union[str, Sequence[str]],
                             rows: Union[Any, Sequence[Any]],
                             css_classes: Sequence[str]):
        if not isinstance(columns, list):
            columns = [columns]

        if not isinstance(rows, list):
            rows = [rows]

        for column_name in columns:
            for row in rows:
                self.styles.loc[row, column_name].remove_css_class(css_classes)

    def iterrows(self):
        return zip(self.data.iterrows(), self.styles.iterrows())

    @property
    def styles(self):
        def get_cell(row_style, column_style, column_name):
            try:
                row_indexer = row_style.label
                column_indexer = column_name
                return self._styles.loc[row_indexer, column_indexer]
            except KeyError:
                return self.CellStyle(row_style, column_style)

        self._styles = QFDataFrame(data={
            column_name: [
                get_cell(row_style, column_style, column_name)
                for row_style in self.rows_styles
            ]
            for column_name, column_style in self.columns_styles.items()
        },
                                   index=self.data.index,
                                   columns=self.data.columns)
        return self._styles

    @property
    def columns_styles(self):
        if set(self._columns_styles.keys()) != set(self.data.columns):
            # Delete the unnecessary columns styles
            self._columns_styles = {
                column_name: column_style
                for column_name, column_style in self._columns_styles.items()
                if column_name in self.data.columns
            }
            # Add columns styles, which do not exist yet
            existing_columns = [
                column_name for column_name in self._columns_styles.keys()
            ]
            self._columns_styles.update({
                column_name: self.ColumnStyle(column_name)
                for column_name in self.data.columns.tolist()
                if column_name not in existing_columns
            })

        return self._columns_styles

    @property
    def rows_styles(self):
        if not self._rows_styles.index.equals(self.data.index):
            # Delete the unnecessary rows styles
            indices_to_delete = [
                index for index in self._rows_styles.index
                if index not in self.data.index
            ]
            self._rows_styles.drop(indices_to_delete, inplace=True)

            # Add rows styles, which do not exist yet
            new_indices = [
                index for index in self.data.index
                if index not in self._rows_styles.index
            ]
            new_row_styles = QFSeries(
                [self.RowStyle(loc) for loc in new_indices], index=new_indices)

            self._rows_styles = self._rows_styles.append(new_row_styles,
                                                         ignore_index=False)

        return self._rows_styles

    class Style(object):
        def __init__(self,
                     style: Dict[str, str] = None,
                     css_class: str = None):
            self.style = style if style is not None else dict()
            self.css_class = css_class.split() if css_class is not None else []
            self.logger = qf_logger.getChild(self.__class__.__name__)

        def add_css_class(self, css_classes: Sequence[str]):
            self.css_class.extend(css_classes)

        def remove_css_class(self, css_classes: Sequence[str]):
            for class_name in css_classes:
                try:
                    self.css_class.remove(class_name)
                except ValueError:
                    self.logger.warning(
                        "The css class {} can not be removed, as it does not exist"
                        .format(class_name))

        def add_styles(self, styles_dict: Dict[str, str]):
            self.style.update(styles_dict)

        def remove_styles(self, styles: Union[Dict[str, str], Sequence[str]]):
            properties = styles.keys() if type(styles) is dict else styles
            for property_name in properties:
                try:
                    del self.style[property_name]
                except KeyError:
                    self.logger.warning(
                        "The css style for proptyety {} can not be removed, as it does not exist"
                        .format(property_name))

        def styles(self):
            # The function merges into string all styles. This string may be further used as the css styles attributes
            # value. No spaces between attributes and their values are allowed.
            def merge_styles(styles_dict: Dict[str, str]) -> str:
                return "".join([
                    '%s:%s;' % (key, value)
                    for key, value in styles_dict.items()
                ])

            styles = merge_styles(self.style)
            styles = '""' if len(styles) == 0 else styles

            return styles

        def classes(self):
            def merge_classes(css_classes_list: Sequence[str]) -> str:
                return " ".join(css_classes_list)

            css_classes = merge_classes(self.css_class)
            return css_classes

    class ColumnStyle(Style):
        def __init__(self,
                     column_name: str,
                     style: Dict[str, str] = None,
                     css_class: str = None):
            super().__init__(style, css_class)
            self.column_name = column_name

    class RowStyle(Style):
        def __init__(self,
                     label: Any,
                     style: Dict[str, str] = None,
                     css_class: str = None):
            super().__init__(style, css_class)
            self.label = label

    class CellStyle(Style):
        def __init__(self,
                     row_style,
                     column_style,
                     style: Dict[str, str] = None,
                     css_class: str = None):
            super().__init__(style, css_class)
            self.row_style = row_style
            self.column_style = column_style

        def styles(self):
            # The function merges into string all styles, corresponding to the row, column and cell.
            styles = super().styles()

            styles = self.row_style.styles() + self.column_style.styles(
            ) + styles
            styles = '""' if len(styles) == 0 else styles

            return styles

        def classes(self):
            # The function merges into string all styles, corresponding to the row, column and cell. This string may be
            # further used as the css class attribute value
            classes = super().classes()

            css_classes = self.row_style.classes() + self.column_style.classes(
            ) + classes
            css_classes = '""' if len(css_classes) == 0 else css_classes

            return css_classes