def list_longest_drawdowns(prices_tms: QFSeries, count: int) -> List[Tuple[datetime, datetime]]: """ Analyses the specified series and finds the top ``count`` longest drawdowns. Returns ------- A list of 2-item tuples. The first tuple item contains the start date and the second the end date of the drawdown period. """ result = [] drawdown_timeseries = drawdown_tms(prices_tms) start_date = None for date, value in drawdown_timeseries.iteritems(): if value == 0: if start_date is not None: result.append((start_date, date)) start_date = None else: if start_date is None: start_date = date if start_date is not None: result.append((start_date, drawdown_timeseries.index[-1])) # Sort according to drawdown length. result.sort(key=lambda val: val[0] - val[1]) return result[:count]
def test_drawdown_tms(self): test_prices = [100, 90, 80, 85, 70, 100, 90, 95, 65] prices_tms = PricesSeries(data=test_prices, index=date_range('2015-01-01', periods=9)) expected_drawdown_values = [0, 0.1, 0.2, 0.15, 0.3, 0, 0.1, 0.05, 0.35] expected_drawdowns_tms = QFSeries(expected_drawdown_values, date_range('2015-01-01', periods=9)) actual_drawdowns_tms = drawdown_tms(prices_tms) assert_series_equal(expected_drawdowns_tms, actual_drawdowns_tms)
def decorate(self, chart: "Chart") -> None: drawdown_series = drawdown_tms(self.series) drawdown_series *= -1 ax = chart.axes ax.yaxis.set_major_formatter(PercentageFormatter()) ax.fill_between(drawdown_series.index, 0, drawdown_series.values, alpha=self._colors_alpha) ax.set_ylim(top=0)
def avg_drawdown(prices_tms: QFSeries) -> float: """ Finds the average drawdown for the given timeseries of prices. Parameters ---------- prices_tms: QFSeries timeseries of prices Returns ------- float average drawdown for the given timeseries of prices expressed as the percentage value (e.g. 0.5 corresponds to the 50% drawdown) """ return drawdown_tms(prices_tms).mean()
def apply_data_element_decorators( self, data_element_decorators: List["DataElementDecorator"]): colors = cycle(Chart.get_axes_colors()) for data_element in data_element_decorators: plot_settings = data_element.plot_settings.copy() plot_settings.setdefault("color", next(colors)) series = data_element.data trimmed_series = self._trim_data(series) drawdown_series = drawdown_tms(trimmed_series) drawdown_series *= -1 axes = self._ax axes.yaxis.set_major_formatter(PercentageFormatter()) axes.fill_between(drawdown_series.index, 0, drawdown_series.values) axes.set_ylim(top=0)
def list_of_max_drawdowns(prices_tms: QFSeries) -> (List[float], List[float]): """ Finds the values of individual maximum drawdowns and the duration of each drawdown. Parameters ---------- prices_tms timeseries of prices Returns ------- max_drawdowns list of all maximum values in individual drawdowns duration_of_drawdowns list of all durations of drawdowns expressed in days """ drawdown_timeseries = drawdown_tms(prices_tms) max_drawdowns = [] duration_of_drawdowns = [] series_sample = QFSeries() for date, value in drawdown_timeseries.iteritems(): if value == 0: if not series_sample.empty: # empty sequence returns false max_drawdowns.append(series_sample.max()) time_span = series_sample.index[-1] - series_sample.index[ 0] + Timedelta('1 days') duration_of_drawdowns.append(time_span) series_sample = QFSeries() # reset the sample series else: series_sample[date] = value if not series_sample.empty: # the last element was not added if the drawdown did not recovered max_drawdowns.append(series_sample.max()) time_span = series_sample.index[-1] - series_sample.index[ 0] + Timedelta('1 days') duration_of_drawdowns.append(time_span) # convert the duration of drawdown to float value expressed in days duration_of_drawdowns = list(map(to_days, duration_of_drawdowns)) return max_drawdowns, duration_of_drawdowns
def max_drawdown(input_data: Union[QFSeries, QFDataFrame], frequency: Frequency = None) -> Union[float, QFSeries]: """ Finds maximal drawdown for the given timeseries of prices. Parameters ---------- input_data: QFSeries, QFDataFrame timeseries of prices/returns frequency: Frequency optional parameter that improves teh performance of the function it is not need to infer the frequency Returns ------- maximal drawdown for the given timeseries of prices expressed as the percentage value (e.g. 0.5 corresponds to the 50% drawdown) """ return drawdown_tms(input_data, frequency=frequency).max()
def _get_underwater_chart(self, series: QFSeries, title="Drawdown", benchmark_series: QFSeries = None, rotate_x_axis: bool = False): underwater_chart = LineChart(start_x=series.index[0], end_x=series.index[-1], log_scale=False, rotate_x_axis=rotate_x_axis) underwater_chart.add_decorator(UnderwaterDecorator(series)) underwater_chart.add_decorator(TitleDecorator(title)) if benchmark_series is not None: legend = LegendDecorator() benchmark_dd = PricesSeries(drawdown_tms(benchmark_series)) benchmark_dd *= -1 benchmark_dd_elem = DataElementDecorator(benchmark_dd, color="black", linewidth=0.5) legend.add_entry(benchmark_dd_elem, "Benchmark DD") underwater_chart.add_decorator(benchmark_dd_elem) underwater_chart.add_decorator(legend) return underwater_chart
def test_avg_drawdown(self): avg_drawdown_value = avg_drawdown(self.test_dd_prices_tms) dd_tms = drawdown_tms(self.test_dd_prices_tms) self.assertEqual(avg_drawdown_value, dd_tms.mean())