def _create_mse_debug_chart(self, alphas, chosen_solution, mean_square_errors, min_se_solution): mse_chart = LineChart() mean_square_errors_paths = QFDataFrame(data=mean_square_errors, index=pd.Index(alphas)) mse_chart.add_decorator(TitleDecorator("Cross-validated avg. MSE of Elastic Net fit")) for _, path in mean_square_errors_paths.iteritems(): mse_chart.add_decorator(DataElementDecorator(path)) mse_chart.add_decorator(VerticalLineDecorator(x=min_se_solution, linestyle='-.')) mse_chart.add_decorator(VerticalLineDecorator(x=chosen_solution, linestyle='-')) mse_chart.plot() mse_chart.axes.invert_xaxis() return mse_chart
def _create_coeffs_debug_chart(self, alphas, chosen_solution, coeffs_path, min_se_solution): coeffs_chart = LineChart() coefficients_paths = QFDataFrame(data=coeffs_path, index=pd.Index(alphas)) for _, path in coefficients_paths.iteritems(): coeffs_chart.add_decorator(DataElementDecorator(path)) coeffs_chart.add_decorator(TitleDecorator("Elastic Net (using Cross Validation)")) coeffs_chart.add_decorator(VerticalLineDecorator(x=min_se_solution, linestyle='-.')) coeffs_chart.add_decorator(VerticalLineDecorator(x=chosen_solution, linestyle='-')) coeffs_chart.plot() coeffs_chart.axes.invert_xaxis() return coeffs_chart
def _get_distribution_plot(self, data_series: SimpleReturnsSeries, title: str, bins: Union[int, str] = 50, crop: bool = False): colors = Chart.get_axes_colors() if crop: start_x = np.quantile(data_series, 0.01) end_x = np.quantile(data_series, 0.99) chart = HistogramChart(data_series, bins=bins, start_x=start_x, end_x=end_x) else: chart = HistogramChart(data_series, bins=bins) # 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(data_series.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_decorator = TitleDecorator(title, key="title") chart.add_decorator(title_decorator) chart.add_decorator(AxesLabelDecorator(title, "Occurrences")) position_decorator = AxesPositionDecorator(*self.full_image_axis_position) chart.add_decorator(position_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 create_qq_chart(strategy: QFSeries) -> Chart: colors = Chart.get_axes_colors() strategy = strategy.to_log_returns() # Normalize strategy = strategy - strategy.mean() strategy = strategy / strategy.std() # Sort strategy_values = sorted(strategy.values) # Create benchmark benchmark_values = list(range(1, len(strategy_values) + 1)) n = len(strategy_values) + 1 benchmark_values = map(lambda x: x / n, benchmark_values) benchmark_values = list(map(lambda x: norm.ppf(x), benchmark_values)) # Figure out the limits. maximum = max(max(benchmark_values), max(strategy_values)) result = LineChart(start_x=-maximum, end_x=maximum, upper_y=maximum, lower_y=-maximum) result.add_decorator(ScatterDecorator( benchmark_values, strategy_values, color=colors[0], alpha=0.6, edgecolors='black', linewidths=0.5)) result.add_decorator(VerticalLineDecorator(0, color='black', linewidth=1)) result.add_decorator(HorizontalLineDecorator(0, color='black', linewidth=1)) result.add_decorator(TitleDecorator("Normal Distribution Q-Q")) result.add_decorator(AxesLabelDecorator("Normal Distribution Quantile", "Observed Quantile")) # Add diagonal line. result.add_decorator(DiagonalLineDecorator(color=colors[1])) return result
def create_returns_distribution(returns: QFSeries, frequency: Frequency = Frequency.MONTHLY, title: str = None) -> \ HistogramChart: """ Creates a new returns distribution histogram with the specified frequency. Parameters ---------- returns: QFSeries The returns series to use in the histogram. frequency: Frequency frequency of the returns after aggregation title title of the chart Returns ------- HistogramChart A new ``HistogramChart`` instance. """ colors = Chart.get_axes_colors() aggregate_returns = get_aggregate_returns(returns, frequency, multi_index=True).multiply(100) chart = HistogramChart(aggregate_returns) # Format the x-axis so that its labels are shown as a percentage. x_axis_formatter = FormatStrFormatter("%.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(aggregate_returns.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. if title is None: title = "Distribution of " + str(frequency).capitalize() + " Returns" title = TitleDecorator(title, key="title_decorator") chart.add_decorator(title) chart.add_decorator(AxesLabelDecorator("Returns", "Occurrences")) return chart
def create_returns_bar_chart( returns: QFSeries, frequency: Frequency = Frequency.YEARLY) -> BarChart: """ Constructs a new returns bar chart based on the returns specified. By default a new annual returns bar chart will be created. """ colors = Chart.get_axes_colors() # Calculate data. aggregate_returns = get_aggregate_returns(returns, frequency, multi_index=True) data_series = QFSeries(aggregate_returns.sort_index(ascending=True)) chart = BarChart(Orientation.Horizontal, align="center") chart.add_decorator(DataElementDecorator(data_series)) chart.add_decorator(BarValuesDecorator(data_series)) # Format the x-axis so that its labels are shown as a percentage. chart.add_decorator(AxesFormatterDecorator(x_major=PercentageFormatter())) # Format Y axis to make sure we have a tick for each year or 2 years if len(data_series) > 10: y_labels = data_series[data_series.index % 2 == 1].index else: y_labels = data_series.index chart.add_decorator( AxisTickLabelsDecorator(labels=y_labels, axis=Axis.Y, tick_values=y_labels)) # Add an average line. avg_line = VerticalLineDecorator(aggregate_returns.values.mean(), color=colors[1], key="avg_line", 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(str(frequency).capitalize() + " Returns", key="title_decorator") chart.add_decorator(title) chart.add_decorator(AxesLabelDecorator("Returns", "Year")) return chart
def create_line_chart(data_list: List[Union[QFSeries, DataElementDecorator]], names_list, title: str = None, recession_series: QFSeries = None, horizontal_lines_list: List[float] = None, vertical_lines_list: List[float] = None, disable_dot: bool = False, start_x: datetime = None, end_x: datetime = None, upper_y: float = None, lower_y: float = None, dot_decimal_points: int = 2, recession_name: str = None) -> LineChart: """ Creates a new line chart based on the settings specified. This function makes certain assumptions about the line chart, it can be customised via the parameters which should cover >90% of use cases. For more customisation use the ``LineChart`` class directly. Parameters ---------- data_list A list of ``QFSeries`` or ``DataElementDecorator``s to plot on the chart. names_list A list of strings specifying the labels for the series, horizontal and vertical lines respectively. ``None`` can be specified for labels to not display it for a specific series, or line. title The title of the graph, specify ``None`` if you don't want the chart to show a title. recession_series A ``QFSeries`` specifying where recessions occurred on the chart, will be highlighted using grey rectangles on the graph. horizontal_lines_list An optional list of values where a horizontal line should be drawn. vertical_lines_list An optional list of values where a vertical line should be drawn. disable_dot Whether a marker on the last point should be disabled. start_x The date where plotting should begin. end_x The date where plotting should end. upper_y The upper bound y-axis value at which plotting should begin. lower_y The lower bound y-axis value at which plotting should begin. dot_decimal_points How many decimal places to show after the decimal points when drawing text for "dot". recession_name A string specifying the recession label. If "None" or missing, will not be included. Returns ------- The constructed ``LineChart``. """ # If `end_x` was not specified, use a heuristic to determine it. if end_x is None and start_x is not None: end_x = LineChart.determine_end_x(start_x, data_list) # Create a new Line Chart. line_chart = LineChart(start_x=start_x, end_x=end_x, upper_y=upper_y, lower_y=lower_y) line_chart.tick_fontweight = "bold" line_chart.tick_color = "black" names_index = 0 # Current legend label. legend_decorator = LegendDecorator(key='legend') # Retrieve necessary data. for data in data_list: assert isinstance(data, (pandas.Series, DataElementDecorator)) # Add the current series with a label taken from ``names_list``. data_element = data if isinstance(data_element, pandas.Series): data_element = DataElementDecorator(data) line_id = data_element.key line_chart.add_decorator(data_element) # Retrieve the last data point. point_to_emphasise = \ (_get_last_valid_value(data_element.data.index), _get_last_valid_value(data_element.data.values)) series_label = _get_name(names_list, names_index) if series_label is not None: legend_decorator.add_entry( data_element, series_label + " [{}]".format(point_to_emphasise[0].strftime("%b %y"))) names_index += 1 if not disable_dot: # Emphasise the last data point. point_emphasis = PointEmphasisDecorator( data_element, point_to_emphasise, decimal_points=dot_decimal_points, key="point_emphasis_{}".format(line_id), use_secondary_axes=data_element.use_secondary_axes) line_chart.add_decorator(point_emphasis) # Create a title. if title is not None: title_decorator = TitleDecorator(title, "title") line_chart.add_decorator(title_decorator) # Create spans (rectangles) to highlight the recession periods. if recession_series is not None: span_decorator = SpanDecorator.from_int_list(recession_series, "span") line_chart.add_decorator(span_decorator) if recession_name is not None: legend_decorator.add_entry(span_decorator, recession_name) # Create horizontal lines. if horizontal_lines_list is not None: for hline in horizontal_lines_list: line_decorator = HorizontalLineDecorator(hline, key="hline" + str(hline)) line_chart.add_decorator(line_decorator) series_label = _get_name(names_list, names_index) if series_label is not None: legend_decorator.add_entry(line_decorator, series_label) names_index += 1 # Create vertical lines. if vertical_lines_list is not None: for vline in vertical_lines_list: line_decorator = VerticalLineDecorator(vline, key="vline" + str(vline)) line_chart.add_decorator(line_decorator) series_label = _get_name(names_list, names_index) if series_label is not None: legend_decorator.add_entry(line_decorator, series_label) names_index += 1 # Add a legend. line_chart.add_decorator(legend_decorator) return line_chart
def create_event_comparison_chart( series: QFSeries, event_dates_list: Iterable[datetime], title: str, samples_before: int = 100, samples_after: int = 200, rebase_method: RebaseMethod = RebaseMethod.divide) -> LineChart: """ Creates a new chart based on a line chart. The chart puts all events at date = 0 and than compares the evolution of the series after the event date. Parameters ---------- series: QFSeries Series usually with values of an index or level of interest rates event_dates_list: Iterable[datetime] A list specifying the dates of the events that we would like to compare. Each date will create a new series in the chart title: str The title of the graph, specify ``None`` if you don't want the chart to show a title. samples_before: int Number of samples shown on the chart that are before the event date samples_after: int Number of samples after the event date that are plotted on the chart rebase_method: RebaseMethod Specifies the way in which the data is normalised at the date of the event. - 'divide' - will divide all the values by the value at the date of the event - 'subtract' - will subtract the value at the event from the whole sample - 'none - will show the value as is (no rebasing). Returns ------- LineChart The constructed ``LineChart``. """ # Create a chart line_chart = LineChart() legend_decorator = LegendDecorator(key='legend') # For each date create a rebased series with event date index = 0 for event_date in event_dates_list: # calculate the integer index of the beginning and end of the sample event_closest_date = series.index.asof(event_date) if isinstance(event_closest_date, datetime): event_date_int_index = series.index.get_loc(event_closest_date) start_index = event_date_int_index - samples_before end_index = event_date_int_index + samples_after end_index = min(end_index, series.count()) # to simplify the algorithm accept only samples that are within the series if start_index >= 0 and end_index <= series.count(): series_sample = series.iloc[start_index:end_index] value_at_event = series.asof(event_date) if rebase_method == RebaseMethod.divide: series_sample = series_sample.div(value_at_event) elif rebase_method == RebaseMethod.subtract: series_sample = series_sample.subtract(value_at_event) elif rebase_method == RebaseMethod.norebase: series_sample = series_sample else: raise ValueError("Incorrect rebase_method. Use 'divide', 'subtract' or 'norebase' ") new_index = np.array(range(0, series_sample.count())) - samples_before reindexed_series = QFSeries(data=series_sample.values, index=new_index) data_element = DataElementDecorator(reindexed_series) line_chart.add_decorator(data_element) legend_decorator.add_entry(data_element, event_date.strftime('%Y-%m-%d')) # Put vertical line at x = 0 line_decorator = VerticalLineDecorator(x=0, key='vline', linestyle="dashed") line_chart.add_decorator(line_decorator) # Create a title title_decorator = TitleDecorator(title, key="title") line_chart.add_decorator(title_decorator) # Add a legend. line_chart.add_decorator(legend_decorator) return line_chart
def create_rolling_chart_using_benchmark(series: Union[QFSeries, List[QFSeries]], benchmark_series: QFSeries, func: RollingWindowFunction, func_name: str, window_size: int = 126, step: int = 20, oos_date: str = None) -> LineChart: """ Creates a new line chart and adds the rolling window for each of the specified series to it. The `func` function is fed data for each window and whatever it returns is added to the resulting rolled series. For example: .. code-block::python create_rolling_chart_using_benchmark([strategy1_tms, strategy2_tms], benchmark_tms lambda a, b: beta_and_alpha(PricesSeries(a), PricesSeries(b))[0], "Sharpe Ratio", oos_date=oos_date) The example above will return beta of every rolling window Parameters ---------- series One or more series to apply the rolling window transformation on add to the resulting chart. benchmark_series benchmark for every series passed as a first argument func Called for each window. Takes two arguments (series_window, benchmark_window). Returns a float. func_name Used in the title to specify the function that was called. window_size step determines by how many steps we shift the rolling window oos_date only the OOS date of the first series in the list will be taken into account Returns ------- LineChart """ chart = LineChart() # Add a legend. legend = LegendDecorator() chart.add_decorator(legend) series_list = series if isinstance(series_list, QFSeries): series_list = [series_list] assert isinstance(series_list, list) for tms in series_list: rolling = tms.rolling_window_with_benchmark(benchmark_series, window_size, func, step=step) rolling_element = DataElementDecorator(rolling) chart.add_decorator(rolling_element) legend.add_entry(rolling_element, tms.name) # Add title chart.add_decorator( TitleDecorator("Rolling {} (window: {}, step: {}, BM: {}) ".format( func_name, window_size, step, benchmark_series.name))) # Add OOS line. new_oos_date = series_list[0].index.asof(pandas.to_datetime(oos_date)) line = VerticalLineDecorator(new_oos_date, color='orange', linestyle="dashed") chart.add_decorator(line) return chart
def create_rolling_chart(series: Union[QFSeries, List[QFSeries]], func: Callable[[Union[QFSeries, numpy.ndarray]], float], func_name: str, window_size: int = 126, step: int = 20, oos_date: str = None) -> LineChart: """ Creates a new line chart and adds the rolling window for each of the specified series to it. The `func` function is fed data for each window and whatever it returns is added to the resulting rolled series. For example: .. code-block::python create_rolling_chart([strategy_tms, benchmark_tms], lambda window: sharpe_ratio(PricesSeries(window), Frequency.DAILY), "Sharpe Ratio", oos_date=oos_date) Parameters ---------- series One or more series to apply the rolling window transformation on add to the resulting chart. func Called for each window. Takes one argument which is part of a series corresponding to a window. Returns a float. func_name Used in the title to specify the function that was called. window_size step oos_date only the OOS date of the first series in the list will be taken into account Returns ------- LineChart """ chart = LineChart() # Add a legend. legend = LegendDecorator() chart.add_decorator(legend) series_list = series if isinstance(series_list, QFSeries): series_list = [series_list] assert isinstance(series_list, list) for tms in series_list: rolling = tms.rolling_window(window_size, func, step=step) rolling_element = DataElementDecorator(rolling) chart.add_decorator(rolling_element) legend.add_entry(rolling_element, tms.name) # Add title chart.add_decorator( TitleDecorator("Rolling {} (window: {}, step: {}) ".format( func_name, window_size, step))) # Add OOS line. new_oos_date = series_list[0].index.asof(pandas.to_datetime(oos_date)) line = VerticalLineDecorator(new_oos_date, color='orange', linestyle="dashed") chart.add_decorator(line) return chart