Ejemplo n.º 1
0
    def build_document(self, tickers: Sequence[Ticker], start_date: datetime, end_date: datetime,
                       use_next_open_instead_of_close=False, title="Trend Strength"):
        """
        tickers
            tickers of all tested instruments 
        start_date
            start date of the study
        end_date
            end date of the study
        use_next_open_instead_of_close
            if True, the daily trend will be calculated from Open to Open of the next day,
            if False, the daily trend will be calculated from Open to Close of the same day
        title
            title of the document to be created
        """
        self.use_next_open_instead_of_close = use_next_open_instead_of_close

        suffix = "O-O" if use_next_open_instead_of_close else "O-C"
        self.title = "{} {}".format(title, suffix)

        self.tickers = tickers
        self.document = Document(title)
        self.start_date = start_date
        self.end_date = end_date
        self.ticker_to_trend_dict = {}

        for ticker in self.tickers:
            try:
                self._add_page(ticker)
                print("Finished evaluating trend strength of:  {}".format(ticker.as_string()))
            except Exception:
                print("Error while processing {}".format(ticker.as_string()))

        self._add_summary()
Ejemplo n.º 2
0
    def build_document(self,
                       backtest_summary: BacktestSummary,
                       out_of_sample_start_date: Optional[datetime] = None):
        self.backtest_summary = backtest_summary
        self.backtest_evaluator = BacktestSummaryEvaluator(backtest_summary)

        self.document = Document(backtest_summary.backtest_name)
        self.out_of_sample_start_date = out_of_sample_start_date if out_of_sample_start_date is not None else \
            (backtest_summary.start_date + (backtest_summary.end_date - backtest_summary.start_date) / 2)

        self._add_header()
        self._add_backtest_description()

        tickers_groups_for_stats_purposes = list(self.backtest_summary.tickers)
        # In case of > 1 ticker in the backtest summary, include also stats for all tickers if possible
        if len(self.backtest_summary.tickers) > 1:
            tickers_groups_for_stats_purposes = [
                self.backtest_summary.tickers
            ] + tickers_groups_for_stats_purposes

        if backtest_summary.num_of_model_params not in [1, 2]:
            raise ValueError(
                "Incorrect number of parameters. Supported: 1 and 2")

        for tickers in tickers_groups_for_stats_purposes:
            tickers, _ = convert_to_list(tickers, Ticker)
            self.document.add_element(NewPageElement())

            if backtest_summary.num_of_model_params == 1:
                self._add_line_plots(tickers)
            else:
                self._add_heat_maps(tickers)
Ejemplo n.º 3
0
    def __init__(self, settings: Settings, pdf_exporter: PDFExporter, trades_df: QFDataFrame, start_date: datetime,
                 end_date: datetime, nr_of_assets_traded: int = 1, title: str = "Trades"):
        """
        trades_df
            indexed by consecutive numbers starting at 0.
            columns are indexed using TradeField values
        nr_of_assets_traded
            the model can be used to trade on many instruments at the same time.
            All aggregated trades will be in trades_df
            nr_of_instruments_traded informs on how many instruments at the same time the model was traded.
        title
            title of the document, will be a part of the filename. Do not use special characters
        """
        self.trades_df = trades_df.sort_values([TradeField.EndDate, TradeField.StartDate]).reset_index(drop=True)
        self.start_date = start_date
        self.end_date = end_date
        self.nr_of_assets_traded = nr_of_assets_traded
        self.returns_of_trades = SimpleReturnsSeries(self.trades_df[TradeField.Return])
        self.returns_of_trades.name = "Returns of Trades"
        self.title = title

        self.document = Document(title)

        # position is linked to the position of axis in tearsheet.mplstyle
        self.half_image_size = (4, 2.2)
        self.dpi = 400

        self.settings = settings
        self.pdf_exporter = pdf_exporter
Ejemplo n.º 4
0
    def create_document(self):
        self.document = Document(self.backtest_summary.backtest_name)
        self._add_header()
        add_backtest_description(self.document, self.backtest_summary)

        selected_tickers, rejected_tickers = self._evaluate_tickers()

        self.document.add_element(HeadingElement(2, "Selected Tickers"))
        self.document.add_element(ParagraphElement("\n"))
        self._add_table(selected_tickers)

        self.document.add_element(HeadingElement(2, "Rejected Tickers"))
        self.document.add_element(ParagraphElement("\n"))
        self._add_table(rejected_tickers)
Ejemplo n.º 5
0
    def __init__(self,
                 settings: Settings,
                 pdf_exporter: PDFExporter,
                 title: str = "Document Title"):
        self.title = title
        self.document = Document(title)
        self.full_image_size = (8, 2.4)

        # position is linked to the position of axis in tearsheet.mplstyle
        self.full_image_axis_position = (0.07, 0.1, 0.915, 0.80
                                         )  # (left, bottom, width, height)
        self.half_image_size = (4, 2.1)
        self.dpi = 400

        self.settings = settings
        self.pdf_exporter = pdf_exporter
Ejemplo n.º 6
0
    def _merge_documents(documents: Sequence[Document], filename: str) -> Document:
        """
        Merges documents into a single document. All elements inside the documents are placed in the returned document.

        Parameters
        ----------
        documents
            a list of documents to merge
        filename
            The filename that should be applied to the resulting document.
        """
        result = Document(filename)
        for document in documents:
            for element in document.elements:
                result.add_element(element)

        return result
Ejemplo n.º 7
0
    def build_document(self, backtest_result: BacktestSummary):
        self.backtest_result = backtest_result
        self.backtest_evaluator = BacktestSummaryEvaluator(backtest_result)
        self.document = Document(backtest_result.backtest_name)

        self._add_header()
        param_names = self._get_param_names()
        add_backtest_description(self.document, self.backtest_result, param_names)

        if backtest_result.num_of_model_params == 1:
            chart_adding_function = self._add_line_chart_grid
        elif backtest_result.num_of_model_params == 2:
            parameters_list = self.backtest_evaluator.params_backtest_summary_elem_dict.keys()

            def fun(tickers):
                return self._add_heat_map_grid(tickers, parameters_list)

            chart_adding_function = fun
        elif backtest_result.num_of_model_params == 3:
            chart_adding_function = self._add_multiple_heat_maps
        else:
            raise ValueError("Incorrect number of parameters. Supported: 1, 2 and 3")

        self._create_charts(chart_adding_function)
Ejemplo n.º 8
0
 def setUp(self):
     self.document = Document("")
Ejemplo n.º 9
0
class AbstractDocument(metaclass=ABCMeta):
    """
    Base class for most PDF documents with charts and tables.

    Parameters
    -----------
    settings: Settings
        settings containing all necessary information
    pdf_exporter: PDFExporter
        used to create PDF document
    title: str
        title of the document
    """
    def __init__(self,
                 settings: Settings,
                 pdf_exporter: PDFExporter,
                 title: str = "Document Title"):
        self.title = title
        self.document = Document(title)
        self.full_image_size = (8, 2.4)

        # position is linked to the position of axis in tearsheet.mplstyle
        self.full_image_axis_position = (0.07, 0.1, 0.915, 0.80
                                         )  # (left, bottom, width, height)
        self.half_image_size = (4, 2.1)
        self.dpi = 400

        self.settings = settings
        self.pdf_exporter = pdf_exporter

    @abstractmethod
    def build_document(self):
        # main function that composes the document
        pass

    @abstractmethod
    def save(self, report_dir: str = ""):
        # function that saves the document on the disk
        pass

    def _get_new_grid(self) -> GridElement:
        return GridElement(mode=PlottingMode.PDF,
                           figsize=self.half_image_size,
                           dpi=self.dpi)

    def _add_header(self):
        logo_path = join(get_starting_dir_abs_path(), self.settings.logo_path)
        company_name = self.settings.company_name

        self.document.add_element(
            PageHeaderElement(logo_path, company_name, self.title))

    def _get_underwater_chart(self, series: QFSeries):
        underwater_chart = UnderwaterChart(series, rotate_x_axis=True)
        underwater_chart.add_decorator(TopDrawdownDecorator(series, 5))
        underwater_chart.add_decorator(AxesLabelDecorator(y_label="Drawdown"))
        underwater_chart.add_decorator(TitleDecorator("Drawdown"))
        return underwater_chart

    def _get_large_perf_chart(self, series_list):
        return self.__get_perf_chart(series_list, True)

    def _get_small_perf_chart(self, series_list):
        return self.__get_perf_chart(series_list, False)

    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_leverage_chart(self,
                            leverage: QFSeries,
                            rotate_x_axis: bool = False):
        return self._get_line_chart(leverage, "Leverage over time",
                                    rotate_x_axis)

    def _get_line_chart(self,
                        series: QFSeries,
                        title: str,
                        rotate_x_axis: bool = False):
        chart = LineChart(rotate_x_axis=rotate_x_axis)

        series_elem = DataElementDecorator(series)
        chart.add_decorator(series_elem)

        title_decorator = TitleDecorator(title, key="title")
        chart.add_decorator(title_decorator)
        return chart

    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)

        title_decorator = TitleDecorator(title_str, key="title")
        chart.add_decorator(title_decorator)
        return chart

    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_statistics_table(self, ta_list: List[TimeseriesAnalysis]):
        table = Table(css_class="table stats-table")

        for ta in ta_list:
            ta.populate_table(table)
        self.document.add_element(table)
Ejemplo n.º 10
0
class ModelParamsEvaluationDocument:
    def __init__(self, settings: Settings, pdf_exporter: PDFExporter):
        self.backtest_summary = None
        self.backtest_evaluator = None  # type: BacktestSummaryEvaluator
        self.document = None
        self.out_of_sample_start_date = None  # type: Optional[datetime]

        # position is linked to the position of axis in tearsheet.mplstyle
        self.image_size = (7, 6)
        self.full_image_size = (8, 2.4)
        self.image_axis_position = (0.08, 0.08, 0.92, 0.85)
        self.dpi = 400
        self.settings = settings
        self.pdf_exporter = pdf_exporter

    def build_document(self,
                       backtest_summary: BacktestSummary,
                       out_of_sample_start_date: Optional[datetime] = None):
        self.backtest_summary = backtest_summary
        self.backtest_evaluator = BacktestSummaryEvaluator(backtest_summary)

        self.document = Document(backtest_summary.backtest_name)
        self.out_of_sample_start_date = out_of_sample_start_date if out_of_sample_start_date is not None else \
            (backtest_summary.start_date + (backtest_summary.end_date - backtest_summary.start_date) / 2)

        self._add_header()
        self._add_backtest_description()

        tickers_groups_for_stats_purposes = list(self.backtest_summary.tickers)
        # In case of > 1 ticker in the backtest summary, include also stats for all tickers if possible
        if len(self.backtest_summary.tickers) > 1:
            tickers_groups_for_stats_purposes = [
                self.backtest_summary.tickers
            ] + tickers_groups_for_stats_purposes

        if backtest_summary.num_of_model_params not in [1, 2]:
            raise ValueError(
                "Incorrect number of parameters. Supported: 1 and 2")

        for tickers in tickers_groups_for_stats_purposes:
            tickers, _ = convert_to_list(tickers, Ticker)
            self.document.add_element(NewPageElement())

            if backtest_summary.num_of_model_params == 1:
                self._add_line_plots(tickers)
            else:
                self._add_heat_maps(tickers)

    def _add_header(self):
        logo_path = join(get_starting_dir_abs_path(), self.settings.logo_path)
        company_name = self.settings.company_name
        self.document.add_element(
            PageHeaderElement(logo_path, company_name,
                              self.backtest_summary.backtest_name))

    def _add_backtest_description(self):
        """
        Adds verbal description of the backtest to the document. The description will be placed on a single page.
        """
        param_names = self._get_param_names()

        self.document.add_element(
            HeadingElement(
                1, "Model: {}".format(self.backtest_summary.backtest_name)))
        self.document.add_element(ParagraphElement("\n"))

        self.document.add_element(
            HeadingElement(2, "Tickers tested in this study: "))
        ticker_str = "\n".join(
            [ticker.name for ticker in self.backtest_summary.tickers])
        self.document.add_element(ParagraphElement(ticker_str))
        self.document.add_element(ParagraphElement("\n"))

        self.document.add_element(HeadingElement(2, "Dates of the backtest"))
        self.document.add_element(
            ParagraphElement("Backtest start date: {}".format(
                date_to_str(self.backtest_summary.start_date))))
        self.document.add_element(
            ParagraphElement("Backtest end date: {}".format(
                date_to_str(self.backtest_summary.end_date))))
        self.document.add_element(ParagraphElement("\n"))

        self.document.add_element(HeadingElement(2, "Parameters Tested"))
        for param_index, param_list in enumerate(
                self.backtest_summary.parameters_tested):
            param_list_str = ", ".join(map(str, param_list))
            self.document.add_element(
                ParagraphElement("{} = [{}]".format(param_names[param_index],
                                                    param_list_str)))

        self.document.add_element(NewPageElement())
        self.document.add_element(
            HeadingElement(2, "Alpha model implementation"))
        self.document.add_element(ParagraphElement("\n"))

        model_type = self.backtest_summary.alpha_model_type
        with open(inspect.getfile(model_type)) as f:
            class_implementation = f.read()
        # Remove the imports section
        class_implementation = "<pre>class {}".format(model_type.__name__) + \
                               class_implementation.split("class {}".format(model_type.__name__))[1] + "</pre>"
        self.document.add_element(CustomElement(class_implementation))

    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))

    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)

        for description, grid in title_to_grid.items():
            self.document.add_element(
                HeadingElement(3, "{} - {}".format(description, tickers_used)))
            self.document.add_element(grid)

    def _create_single_heat_map(self, title, result_df, min_v, max_v):
        chart = HeatMapChart(data=result_df,
                             color_map=plt.get_cmap("coolwarm"),
                             min_value=min_v,
                             max_value=max_v)
        chart.add_decorator(
            AxisTickLabelsDecorator(labels=list(result_df.columns),
                                    axis=Axis.X))
        chart.add_decorator(
            AxisTickLabelsDecorator(labels=list(reversed(result_df.index)),
                                    axis=Axis.Y))
        chart.add_decorator(ValuesAnnotations())
        param_names = self._get_param_names()
        chart.add_decorator(
            AxesLabelDecorator(x_label=param_names[1], y_label=param_names[0]))
        chart.add_decorator(TitleDecorator(title))
        position_decorator = AxesPositionDecorator(*self.image_axis_position)
        chart.add_decorator(position_decorator)
        return chart

    def _get_param_names(self):
        model_parameters_names = [
            tuple(el.model_parameters_names)
            for el in self.backtest_summary.elements_list
        ]
        assert len(
            set(model_parameters_names)
        ) == 1, "All parameters should have exactly the same parameters tuples"
        return model_parameters_names[0]

    def save(self, title: Optional[str] = None):
        if self.document is not None:
            output_sub_dir = "param_estimation"

            # Set the style for the report
            plt.style.use(['tearsheet'])
            if title is None:
                title = self.backtest_summary.backtest_name
            filename = "%Y_%m_%d-%H%M {}.pdf".format(title)
            filename = datetime.now().strftime(filename)
            self.pdf_exporter.generate([self.document], output_sub_dir,
                                       filename)
        else:
            raise AssertionError(
                "The documnent is not initialized. Build the document first")
Ejemplo n.º 11
0
class ModelParamsEvaluator(object):

    def __init__(self, settings: Settings, pdf_exporter: PDFExporter):
        self.backtest_result = None
        self.backtest_evaluator = None  # type: BacktestSummaryEvaluator
        self.document = None

        # position is linked to the position of axis in tearsheet.mplstyle
        self.image_size = (7, 6)
        self.image_axis_position = (0.08, 0.08, 0.92, 0.85)
        self.dpi = 400
        self.settings = settings
        self.pdf_exporter = pdf_exporter

    def build_document(self, backtest_result: BacktestSummary):
        self.backtest_result = backtest_result
        self.backtest_evaluator = BacktestSummaryEvaluator(backtest_result)
        self.document = Document(backtest_result.backtest_name)

        self._add_header()
        param_names = self._get_param_names()
        add_backtest_description(self.document, self.backtest_result, param_names)

        if backtest_result.num_of_model_params == 1:
            chart_adding_function = self._add_line_chart_grid
        elif backtest_result.num_of_model_params == 2:
            parameters_list = self.backtest_evaluator.params_backtest_summary_elem_dict.keys()

            def fun(tickers):
                return self._add_heat_map_grid(tickers, parameters_list)

            chart_adding_function = fun
        elif backtest_result.num_of_model_params == 3:
            chart_adding_function = self._add_multiple_heat_maps
        else:
            raise ValueError("Incorrect number of parameters. Supported: 1, 2 and 3")

        self._create_charts(chart_adding_function)

    def _create_charts(self, chart_adding_function: Callable[[Any], None]):
        # create a chart for all trades of all tickers traded
        chart_adding_function(tickers=self.backtest_result.tickers)

        for ticker in self.backtest_result.tickers:
            # create a chart for single ticker
            chart_adding_function(tickers=[ticker])

    def _add_header(self):
        logo_path = join(get_starting_dir_abs_path(), self.settings.logo_path)
        company_name = self.settings.company_name
        self.document.add_element(PageHeaderElement(logo_path, company_name, self.backtest_result.backtest_name))

    def _add_line_chart_grid(self, tickers: Sequence[Ticker]):
        grid = GridElement(mode=PlottingMode.PDF, figsize=self.image_size)
        params = sorted(self.backtest_evaluator.params_backtest_summary_elem_dict.keys())  # this will sort the tuples
        params_as_values = [param_tuple[0] for param_tuple in params]

        results = []
        for param_tuple in params:
            trades_eval_result = self.backtest_evaluator.evaluate_params_for_tickers(param_tuple, tickers)
            results.append(trades_eval_result)

        sqn_avg_trades = [elem.sqn_per_avg_nr_trades for elem in results]
        sqn_per100trades = [elem.sqn_per100trades for elem in results]
        avg_nr_of_trades = [elem.avg_nr_of_trades_1Y for elem in results]
        annualised_return = [elem.annualised_return for elem in results]
        drawdown = [elem.drawdown for elem in results]

        grid.add_chart(self._crete_single_line_chart("SQN / AVG #trades", params_as_values, sqn_avg_trades, tickers))
        grid.add_chart(self._crete_single_line_chart("SQN / 100 trades", params_as_values, sqn_per100trades, tickers))
        grid.add_chart(self._crete_single_line_chart("Avg # trades 1Y", params_as_values, avg_nr_of_trades, tickers))
        grid.add_chart(self._crete_single_line_chart("An Return", params_as_values, annualised_return, tickers))
        grid.add_chart(self._crete_single_line_chart("Drawdown", params_as_values, drawdown, tickers))

        self.document.add_element(grid)

    def _crete_single_line_chart(self, measure_name, parameters, values, tickers):
        result_series = QFSeries(data=values, index=parameters)
        line_chart = LineChart()
        data_element = DataElementDecorator(result_series)
        line_chart.add_decorator(data_element)
        line_chart.add_decorator(TitleDecorator(self._get_chart_title(tickers, measure_name)))
        param_names = self._get_param_names()
        line_chart.add_decorator(AxesLabelDecorator(x_label=param_names[0], y_label=measure_name))
        self._resize_chart(line_chart)
        return line_chart

    def _add_heat_map_grid(self, tickers: Sequence[Ticker], parameters_list: Sequence[tuple], third_param=None):
        grid = GridElement(mode=PlottingMode.PDF, figsize=self.image_size)
        result_df = QFDataFrame()

        for param_tuple in parameters_list:
            row = param_tuple[0]
            column = param_tuple[1]
            trades_eval_result = self.backtest_evaluator.evaluate_params_for_tickers(param_tuple, tickers)
            result_df.loc[row, column] = trades_eval_result

        result_df.sort_index(axis=0, inplace=True, ascending=False)
        result_df.sort_index(axis=1, inplace=True)

        sqn_avg_nr_trades = result_df.applymap(lambda x: x.sqn_per_avg_nr_trades)
        sqn_per100trades = result_df.applymap(lambda x: x.sqn_per100trades)
        avg_nr_of_trades = result_df.applymap(lambda x: x.avg_nr_of_trades_1Y)
        annualised_return = result_df.applymap(lambda x: x.annualised_return)
        drawdown = result_df.applymap(lambda x: x.drawdown)

        grid.add_chart(self._create_single_heat_map("SQN / AVG #trades", sqn_avg_nr_trades, tickers, 0, 1, third_param))
        grid.add_chart(self._create_single_heat_map("SQN / 100 trades", sqn_per100trades, tickers, 0, 2, third_param))
        grid.add_chart(self._create_single_heat_map("Avg # trades 1Y", avg_nr_of_trades, tickers, 3, 30, third_param))
        grid.add_chart(self._create_single_heat_map("An Return", annualised_return, tickers, -0.1, 0.2, third_param))
        grid.add_chart(self._create_single_heat_map("Drawdown", drawdown, tickers, -0.5, -0.1, third_param))

        self.document.add_element(grid)

    def _create_single_heat_map(self, measure_name, result_df, tickers, min_v, max_v, third_param):
        chart = HeatMapChart(data=result_df, color_map=plt.get_cmap("coolwarm"), min_value=min_v, max_value=max_v)
        chart.add_decorator(AxisTickLabelsDecorator(labels=list(result_df.columns), axis=Axis.X))
        chart.add_decorator(AxisTickLabelsDecorator(labels=list(reversed(result_df.index)), axis=Axis.Y))
        chart.add_decorator(ValuesAnnotations())
        param_names = self._get_param_names()
        chart.add_decorator(AxesLabelDecorator(x_label=param_names[1], y_label=param_names[0]))
        if third_param is None:
            title = self._get_chart_title(tickers, measure_name)
        else:
            title = "{}, {} = {:0.2f}".format(self._get_chart_title(tickers, measure_name), param_names[2], third_param)
        chart.add_decorator(TitleDecorator(title))
        self._resize_chart(chart)
        return chart

    def _resize_chart(self, chart):
        left, bottom, width, height = self.image_axis_position
        position_decorator = AxesPositionDecorator(left, bottom, width, height)
        chart.add_decorator(position_decorator)

    def _add_multiple_heat_maps(self, tickers: Sequence[Ticker]):
        parameters_list = self.backtest_evaluator.params_backtest_summary_elem_dict.keys()
        # sort by third parameter, must have for groupby.
        sorted_list = sorted(parameters_list, key=lambda x: x[2])

        for third_param, group in groupby(sorted_list, lambda x: x[2]):
            self._add_heat_map_grid(tickers, group, third_param=third_param)

    def _get_chart_title(self, tickers, measure_name):
        if len(tickers) > 1:
            title = "{} - Many Tickers".format(measure_name)
        elif len(tickers) == 1:
            title = "{} - {}".format(measure_name, tickers[0].as_string())
        else:
            raise ValueError("No tickers provided")
        return title

    def _get_param_names(self):
        names = []
        num_of_params = self.backtest_result.num_of_model_params
        model_type = self.backtest_result.alpha_model_type

        args = getfullargspec(model_type.__init__).args
        if len(args) <= 3:  # alpha model type does not include any parameter fields to evaluate
            for param in range(1, num_of_params + 1):
                names.append("Parameter #{}".format(param))
        else:
            for param in range(1, num_of_params + 1):
                names.append(args[param])

        assert len(names) == self.backtest_result.num_of_model_params
        return names

    def save(self):
        if self.document is not None:
            output_sub_dir = "param_estimation"

            # Set the style for the report
            plt.style.use(['tearsheet'])

            filename = "%Y_%m_%d-%H%M {}.pdf".format(self.backtest_result.backtest_name)
            filename = datetime.now().strftime(filename)
            self.pdf_exporter.generate([self.document], output_sub_dir, filename)
        else:
            raise AssertionError("The documnent is not initialized. Build the document first")
Ejemplo n.º 12
0
class TickerScreener(object):
    """
    TODO: class not implemented in full

    Class enables generation of the PDF document containing evaluation of trades on individual tickers
    """

    def __init__(self, backtest_summary: BacktestSummary, settings: Settings, pdf_exporter: PDFExporter):
        self.backtest_summary = backtest_summary
        self.settings = settings
        self.pdf_exporter = pdf_exporter

        self.document = None

        self.all_tickers_tested = backtest_summary.tickers
        self.num_of_model_params = backtest_summary.num_of_model_params

    def create_document(self):
        self.document = Document(self.backtest_summary.backtest_name)
        self._add_header()
        add_backtest_description(self.document, self.backtest_summary)

        selected_tickers, rejected_tickers = self._evaluate_tickers()

        self.document.add_element(HeadingElement(2, "Selected Tickers"))
        self.document.add_element(ParagraphElement("\n"))
        self._add_table(selected_tickers)

        self.document.add_element(HeadingElement(2, "Rejected Tickers"))
        self.document.add_element(ParagraphElement("\n"))
        self._add_table(rejected_tickers)

    def _add_header(self):
        logo_path = join(get_starting_dir_abs_path(), self.settings.logo_path)
        company_name = self.settings.company_name
        self.document.add_element(PageHeaderElement(logo_path, company_name, self.backtest_summary.backtest_name))

    def _evaluate_tickers(self):
        for ticker in self.all_tickers_tested:
            for backtest_elem in self.backtest_summary.elements_list:
                raise NotImplementedError()
                # ticker_eval = TradesEvaluationResult()
                # ticker_eval.SQN = sqn(ticker_trades_df)
                # ticker_eval.avg_nr_of_trades_1Y = avg_nr_of_trades_per1y()

    def _add_table(self, tickers_eval_list):
        table = Table(column_names=["Ticker", "Max SQN per 100 trades", "Avg #trades per 1Y for Max SQN"],
                      css_class="table stats-table")

        sorted_tickers_eval_list = sorted(tickers_eval_list, key=lambda x: x.SQN)

        for ticker_eval in sorted_tickers_eval_list:
            table.add_row([ticker_eval.ticker.as_string(), ticker_eval.SQN, ticker_eval.avg_nr_of_trades_1Y])

        self.document.add_element(table)

    def _select_trades_of_ticker(self, trades: QFDataFrame, ticker: Ticker):
        """
        Select only the trades generated by the ticker provided
        If ticker is not provided (None) return all the trades
        """
        if ticker is not None:
            trades = trades.loc[trades[TradeField.Ticker] == ticker]
        return trades

    def _objective_function(self, trades: QFDataFrame):
        """
        Calculates the simple SQN * sqrt(average number of trades per year)
        """

        number_of_instruments_traded = len(self.all_tickers_tested)
        returns = trades[TradeField.Return]

        period_length = self.backtest_summary.end_date - self.backtest_summary.start_date
        period_length_in_years = to_days(period_length) / DAYS_PER_YEAR_AVG
        avg_number_of_trades_1y = returns.count() / period_length_in_years / number_of_instruments_traded

        sqn = returns.mean() / returns.std()
        sqn = sqn * np.sqrt(avg_number_of_trades_1y)
        return sqn

    def save(self):
        if self.document is not None:
            output_sub_dir = "param_estimation"

            # Set the style for the report
            plt.style.use(['tearsheet'])

            filename = "%Y_%m_%d-%H%M Screening {}.pdf".format(self.backtest_summary.backtest_name)
            filename = datetime.now().strftime(filename)
            self.pdf_exporter.generate([self.document], output_sub_dir, filename)
        else:
            raise AssertionError("The documnent is not initialized. Build the document first")
Ejemplo n.º 13
0
class TradeAnalysisSheet(object):
    """
    Creates a PDF containing main statistics of the trades
    """

    def __init__(self, settings: Settings, pdf_exporter: PDFExporter, trades_df: QFDataFrame, start_date: datetime,
                 end_date: datetime, nr_of_assets_traded: int = 1, title: str = "Trades"):
        """
        trades_df
            indexed by consecutive numbers starting at 0.
            columns are indexed using TradeField values
        nr_of_assets_traded
            the model can be used to trade on many instruments at the same time.
            All aggregated trades will be in trades_df
            nr_of_instruments_traded informs on how many instruments at the same time the model was traded.
        title
            title of the document, will be a part of the filename. Do not use special characters
        """
        self.trades_df = trades_df.sort_values([TradeField.EndDate, TradeField.StartDate]).reset_index(drop=True)
        self.start_date = start_date
        self.end_date = end_date
        self.nr_of_assets_traded = nr_of_assets_traded
        self.returns_of_trades = SimpleReturnsSeries(self.trades_df[TradeField.Return])
        self.returns_of_trades.name = "Returns of Trades"
        self.title = title

        self.document = Document(title)

        # position is linked to the position of axis in tearsheet.mplstyle
        self.half_image_size = (4, 2.2)
        self.dpi = 400

        self.settings = settings
        self.pdf_exporter = pdf_exporter

    def build_document(self):
        self._add_header()

        self.document.add_element(ParagraphElement("\n"))

        self._add_histogram_and_cumulative()
        self._add_statistics_table()

    def _add_header(self):
        logo_path = join(get_starting_dir_abs_path(), self.settings.logo_path)
        company_name = self.settings.company_name

        self.document.add_element(PageHeaderElement(logo_path, company_name, self.title))

    def _add_histogram_and_cumulative(self):
        grid = GridElement(mode=PlottingMode.PDF, figsize=self.half_image_size, dpi=self.dpi)

        perf_chart = self._get_perf_chart()
        grid.add_chart(perf_chart)

        histogram_chart = self._get_histogram_chart()
        grid.add_chart(histogram_chart)

        self.document.add_element(grid)

    def _get_perf_chart(self):
        strategy_tms = self.returns_of_trades.to_prices(1)
        chart = LineChart()
        line_decorator = HorizontalLineDecorator(1, key="h_line", linewidth=1)
        chart.add_decorator(line_decorator)

        series_elem = DataElementDecorator(strategy_tms)
        chart.add_decorator(series_elem)

        title_decorator = TitleDecorator("Alpha Model Performance", key="title")
        chart.add_decorator(title_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 _add_statistics_table(self):
        table = Table(column_names=["Measure", "Value"], css_class="table stats-table")

        number_of_trades = self.returns_of_trades.count()
        table.add_row(["Number of trades", number_of_trades])

        period_length = self.end_date - self.start_date
        period_length_in_years = to_days(period_length) / DAYS_PER_YEAR_AVG
        avg_number_of_trades = number_of_trades / period_length_in_years / self.nr_of_assets_traded
        table.add_row(["Avg number of trades per year per asset", avg_number_of_trades])

        positive_trades = self.returns_of_trades[self.returns_of_trades > 0]
        negative_trades = self.returns_of_trades[self.returns_of_trades < 0]

        percentage_of_positive = positive_trades.count() / number_of_trades
        percentage_of_negative = negative_trades.count() / number_of_trades
        table.add_row(["% of positive trades", percentage_of_positive * 100])
        table.add_row(["% of negative trades", percentage_of_negative * 100])

        avg_positive = positive_trades.mean()
        avg_negative = negative_trades.mean()
        table.add_row(["Avg positive trade [%]", avg_positive * 100])
        table.add_row(["Avg negative trade [%]", avg_negative * 100])

        best_return = max(self.returns_of_trades)
        worst_return = min(self.returns_of_trades)
        table.add_row(["Best trade [%]", best_return * 100])
        table.add_row(["Worst trade [%]", worst_return * 100])

        max_dd = max_drawdown(self.returns_of_trades)
        table.add_row(["Max drawdown [%]", max_dd * 100])

        prices_tms = self.returns_of_trades.to_prices()
        total_return = prices_tms.iloc[-1] / prices_tms.iloc[0] - 1
        table.add_row(["Total return [%]", total_return * 100])

        annualised_ret = annualise_total_return(total_return, period_length_in_years, SimpleReturnsSeries)
        table.add_row(["Annualised return [%]", annualised_ret * 100])

        avg_return = self.returns_of_trades.mean()
        table.add_row(["Avg return of trade [%]", avg_return * 100])

        std_of_returns = self.returns_of_trades.std()
        table.add_row(["Std of return of trades [%]", std_of_returns * 100])

        # System Quality Number
        sqn = avg_return / std_of_returns
        table.add_row(["SQN", sqn])
        table.add_row(["SQN for 100 trades", sqn * 10])  # SQN * sqrt(100)
        table.add_row(["SQN * Sqrt(avg number of trades per year)", sqn * sqrt(avg_number_of_trades)])

        self.document.add_element(table)

    def save(self):
        output_sub_dir = "trades_analysis"

        # Set the style for the report
        plt.style.use(['tearsheet'])

        filename = "%Y_%m_%d-%H%M {}.pdf".format(self.title)
        filename = datetime.now().strftime(filename)
        self.pdf_exporter.generate([self.document], output_sub_dir, filename)
Ejemplo n.º 14
0
class AbstractDocument(metaclass=ABCMeta):
    """
    Base class for Most PDF document with charts and tables.
    """
    def __init__(self,
                 settings: Settings,
                 pdf_exporter: PDFExporter,
                 title: str = "Document Title"):
        self.title = title
        self.document = Document(title)
        self.full_image_size = (8, 2.4)

        # position is linked to the position of axis in tearsheet.mplstyle
        self.full_image_axis_position = (0.07, 0.1, 0.915, 0.80
                                         )  # (left, bottom, width, height)
        self.half_image_size = (4, 2.1)
        self.dpi = 400

        self.settings = settings
        self.pdf_exporter = pdf_exporter

    @abstractmethod
    def build_document(self):
        # main function that composes the document
        pass

    @abstractmethod
    def save(self, report_dir: str = ""):
        # function that saves the document on the disk
        pass

    def _get_new_grid(self) -> GridElement:
        return GridElement(mode=PlottingMode.PDF,
                           figsize=self.half_image_size,
                           dpi=self.dpi)

    def _add_header(self):
        logo_path = join(get_starting_dir_abs_path(), self.settings.logo_path)
        company_name = self.settings.company_name

        self.document.add_element(
            PageHeaderElement(logo_path, company_name, self.title))

    def _get_underwater_chart(self, series: QFSeries):
        underwater_chart = UnderwaterChart(series, rotate_x_axis=True)
        underwater_chart.add_decorator(TopDrawdownDecorator(series, 5))
        underwater_chart.add_decorator(AxesLabelDecorator(y_label="Drawdown"))
        underwater_chart.add_decorator(TitleDecorator("Drawdown"))
        return underwater_chart

    def _get_large_perf_chart(self, series_list):
        return self.__get_perf_chart(series_list, True)

    def _get_small_perf_chart(self, series_list):
        return self.__get_perf_chart(series_list, False)

    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] > 5 else False  # use log scale for returns above 500 %

        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_leverage_chart(self,
                            leverage: QFSeries,
                            rotate_x_axis: bool = False):
        chart = LineChart(rotate_x_axis=rotate_x_axis)

        series_elem = DataElementDecorator(leverage)
        chart.add_decorator(series_elem)

        title_decorator = TitleDecorator("Leverage over time", key="title")
        chart.add_decorator(title_decorator)
        return chart

    def _add_statistics_table(self, ta_list: List[TimeseriesAnalysis]):
        table = Table(css_class="table stats-table")

        for ta in ta_list:
            ta.populate_table(table)
        self.document.add_element(table)
Ejemplo n.º 15
0
class TrendStrengthSheet(object):
    """
    Creates a PDF containing main statistics of strength of a day trend
    """

    def __init__(self, settings: Settings, pdf_exporter: PDFExporter, price_provider: DataProvider, window_len=128):
        self.settings = settings
        self.pdf_exporter = pdf_exporter
        self.price_provider = price_provider
        self.window_len = window_len

        self.start_date = None
        self.end_date = None
        self.title = None
        self.document = None
        self.tickers = None
        self.ticker_to_trend_dict = None

        # if True, the daily trend will be calculated from Open to Open of the next day,
        # if False, the daily trend will be calculated from Open to Close of the same day
        self.use_next_open_instead_of_close = None

        # position is linked to the position of axis in tearsheet.mplstyle
        self.image_size = (8, 2.4)
        self.full_image_axis_position = (0.06, 0.1, 0.94, 0.80)
        self.dpi = 400

    def build_document(self, tickers: Sequence[Ticker], start_date: datetime, end_date: datetime,
                       use_next_open_instead_of_close=False, title="Trend Strength"):
        """
        tickers
            tickers of all tested instruments 
        start_date
            start date of the study
        end_date
            end date of the study
        use_next_open_instead_of_close
            if True, the daily trend will be calculated from Open to Open of the next day,
            if False, the daily trend will be calculated from Open to Close of the same day
        title
            title of the document to be created
        """
        self.use_next_open_instead_of_close = use_next_open_instead_of_close

        suffix = "O-O" if use_next_open_instead_of_close else "O-C"
        self.title = "{} {}".format(title, suffix)

        self.tickers = tickers
        self.document = Document(title)
        self.start_date = start_date
        self.end_date = end_date
        self.ticker_to_trend_dict = {}

        for ticker in self.tickers:
            try:
                self._add_page(ticker)
                print("Finished evaluating trend strength of:  {}".format(ticker.as_string()))
            except Exception:
                print("Error while processing {}".format(ticker.as_string()))

        self._add_summary()

    def _add_page(self, ticker: Ticker):
        self._add_header()

        self.document.add_element(ParagraphElement("\n"))
        self.document.add_element(HeadingElement(2, ticker.as_string()))
        self.document.add_element(ParagraphElement("\n"))

        price_df = self.price_provider.get_price(ticker, PriceField.ohlcv(), self.start_date, self.end_date)

        self._insert_table_with_overall_measures(price_df, ticker)
        self.document.add_element(ParagraphElement("\n"))

        self._add_price_chart(price_df)
        self.document.add_element(ParagraphElement("\n"))

        self._add_trend_strength_chart(price_df)
        self.document.add_element(ParagraphElement("\n"))

        self._add_up_and_down_trend_strength(price_df)
        self.document.add_element(NewPageElement())  # add page break

    def _add_header(self):
        logo_path = join(get_starting_dir_abs_path(), self.settings.logo_path)
        company_name = self.settings.company_name
        self.document.add_element(PageHeaderElement(logo_path, company_name, self.title))

    def _insert_table_with_overall_measures(self, prices_df: PricesDataFrame, ticker: Ticker):
        table = Table(column_names=["Measure", "Value"], css_class="table stats-table")

        table.add_row(["Instrument", ticker.as_string()])
        series = prices_df[PriceField.Close]
        table.add_row(["Start date", date_to_str(series.index[0])])
        table.add_row(["End date", date_to_str(series.index[-1])])

        trend_strength_overall = trend_strength(prices_df, self.use_next_open_instead_of_close)
        table.add_row(["Overall strength of the day trends", trend_strength_overall])

        trend_strength_1y = trend_strength(prices_df.tail(252), self.use_next_open_instead_of_close)
        table.add_row(["Strength of the day trends in last 1Y", trend_strength_1y])
        self.ticker_to_trend_dict[ticker] = (trend_strength_1y, trend_strength_overall)

        table.add_row(["Up trends strength", up_trend_strength(prices_df, self.use_next_open_instead_of_close)])
        table.add_row(["Down trends strength", down_trend_strength(prices_df, self.use_next_open_instead_of_close)])
        self.document.add_element(table)

    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 _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_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_summary(self):
        self.document.add_element(ParagraphElement("\n"))
        self.document.add_element(HeadingElement(2, "Summary"))
        self.document.add_element(ParagraphElement("\n"))

        self.document.add_element(ParagraphElement("1Y strength  -  Overall strength - Ticker\n"))

        pairs_sorted_by_value = sorted(self.ticker_to_trend_dict.items(), key=lambda pair: pair[1], reverse=True)

        for ticker, trend_strength_values in pairs_sorted_by_value:
            paragraph_str = "{:12.3f} - {:12.3f} - {}".format(
                trend_strength_values[0], trend_strength_values[1], ticker.as_string())
            self.document.add_element(ParagraphElement(paragraph_str))

    def _add_axes_position_decorator(self, chart: Chart):
        left, bottom, width, height = self.full_image_axis_position
        position_decorator = AxesPositionDecorator(left, bottom, width, height)
        chart.add_decorator(position_decorator)

    def save(self):
        output_sub_dir = "trend_strength"

        # Set the style for the report
        plt.style.use(['tearsheet'])

        filename = "%Y_%m_%d-%H%M {}.pdf".format(self.title)
        filename = datetime.now().strftime(filename)
        self.pdf_exporter.generate([self.document], output_sub_dir, filename)
Ejemplo n.º 16
0
def add_backtest_description(document: Document,
                             backtest_result: BacktestSummary,
                             param_names: List[str]):
    """
    Adds verbal description of the backtest to the document. The description will be placed on a single page.
    """

    document.add_element(ParagraphElement("\n"))
    document.add_element(
        HeadingElement(1, "Model: {}".format(backtest_result.backtest_name)))
    document.add_element(ParagraphElement("\n"))

    document.add_element(HeadingElement(2, "Tickers tested in this study: "))
    ticker_str = "\n".join(
        [ticker.as_string() for ticker in backtest_result.tickers])
    document.add_element(ParagraphElement(ticker_str))
    document.add_element(ParagraphElement("\n"))

    document.add_element(HeadingElement(2, "Dates of the backtest"))
    document.add_element(
        ParagraphElement("Backtest start date: {}".format(
            date_to_str(backtest_result.start_date))))
    document.add_element(
        ParagraphElement("Backtest end date: {}".format(
            date_to_str(backtest_result.end_date))))

    document.add_element(ParagraphElement("\n"))

    document.add_element(HeadingElement(2, "Parameters Tested"))
    for param_index, param_list in enumerate(
            backtest_result.parameters_tested):
        param_list_str = ", ".join(map(str, param_list))
        document.add_element(
            ParagraphElement("{} = [{}]".format(param_names[param_index],
                                                param_list_str)))

    document.add_element(NewPageElement())