From 0581c550e0f654fd683f6fce8fa4db2b7db859cf Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sat, 22 May 2021 19:30:20 -0700 Subject: [PATCH 01/28] Auto-scale Y-axis for indicators when zooming #356 --- backtesting/_plotting.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index bec6757a..1b01d385 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -523,6 +523,12 @@ def __eq__(self, other): colors = colors and cycle(_as_list(colors)) or ( cycle([next(ohlc_colors)]) if is_overlay else colorgen()) legend_label = LegendStr(value.name) + + indicator_max=value.df.max(axis='columns') + indicator_min=value.df.min(axis='columns') + source.add(indicator_max, f'indicator_{j}_range_max') + source.add(indicator_min,f'indicator_{j}_range_min') + for j, arr in enumerate(value, 1): color = next(colors) source_name = f'{legend_label}_{i}_{j}' @@ -609,6 +615,12 @@ def __eq__(self, other): source=source) if plot_volume: custom_js_args.update(volume_range=fig_volume.y_range) + + indicator_ranges = {} + for idx,indicator in enumerate(indicator_figs): + indicator_range_key = f'indicator_{idx}_range' + indicator_ranges.update({indicator_range_key:indicator.y_range}) + custom_js_args.update({'indicator_ranges':indicator_ranges}) fig_ohlc.x_range.js_on_change('end', CustomJS(args=custom_js_args, code=_AUTOSCALE_JS_CALLBACK)) From 779258895bc8f6836d9b5483b03244c9d156222f Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sat, 22 May 2021 19:31:43 -0700 Subject: [PATCH 02/28] Auto-scale Y-axis for indicators when zooming #356 --- backtesting/autoscale_cb.js | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/backtesting/autoscale_cb.js b/backtesting/autoscale_cb.js index da888ecf..63135615 100644 --- a/backtesting/autoscale_cb.js +++ b/backtesting/autoscale_cb.js @@ -31,5 +31,18 @@ window._bt_autoscale_timeout = setTimeout(function () { max = Math.max.apply(null, source.data['Volume'].slice(i, j)); _bt_scale_range(volume_range, 0, max * 1.03, false); } + + if(indicator_ranges){ + let keys = Object.keys(indicator_ranges); + for(var count=0;count Date: Sat, 22 May 2021 19:36:10 -0700 Subject: [PATCH 03/28] Auto-scale Y-axis for indicators when zooming #356 --- backtesting/_plotting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index 1b01d385..85c3a251 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -526,8 +526,8 @@ def __eq__(self, other): indicator_max=value.df.max(axis='columns') indicator_min=value.df.min(axis='columns') - source.add(indicator_max, f'indicator_{j}_range_max') - source.add(indicator_min,f'indicator_{j}_range_min') + source.add(indicator_max, f'indicator_{i}_range_max') + source.add(indicator_min,f'indicator_{i}_range_min') for j, arr in enumerate(value, 1): color = next(colors) From c0dcbbedced95f08b61d9789b09a0b316cbbd99a Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sat, 22 May 2021 20:19:37 -0700 Subject: [PATCH 04/28] Auto-scale Y-axis for indicators when zooming #356 --- backtesting/_plotting.py | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index 85c3a251..86c1697a 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -523,12 +523,10 @@ def __eq__(self, other): colors = colors and cycle(_as_list(colors)) or ( cycle([next(ohlc_colors)]) if is_overlay else colorgen()) legend_label = LegendStr(value.name) - indicator_max=value.df.max(axis='columns') indicator_min=value.df.min(axis='columns') source.add(indicator_max, f'indicator_{i}_range_max') - source.add(indicator_min,f'indicator_{i}_range_min') - + source.add(indicator_min, f'indicator_{i}_range_min') for j, arr in enumerate(value, 1): color = next(colors) source_name = f'{legend_label}_{i}_{j}' @@ -619,9 +617,8 @@ def __eq__(self, other): indicator_ranges = {} for idx,indicator in enumerate(indicator_figs): indicator_range_key = f'indicator_{idx}_range' - indicator_ranges.update({indicator_range_key:indicator.y_range}) - custom_js_args.update({'indicator_ranges':indicator_ranges}) - + indicator_ranges.update({indicator_range_key: indicator.y_range}) + custom_js_args.update({'indicator_ranges': indicator_ranges}) fig_ohlc.x_range.js_on_change('end', CustomJS(args=custom_js_args, code=_AUTOSCALE_JS_CALLBACK)) From 6b1c436a459d46d9a5e25f07ab232377004f69cf Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sat, 22 May 2021 20:23:02 -0700 Subject: [PATCH 05/28] Auto-scale Y-axis for indicators when zooming #356 --- backtesting/_plotting.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index 86c1697a..e22358b0 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -523,8 +523,8 @@ def __eq__(self, other): colors = colors and cycle(_as_list(colors)) or ( cycle([next(ohlc_colors)]) if is_overlay else colorgen()) legend_label = LegendStr(value.name) - indicator_max=value.df.max(axis='columns') - indicator_min=value.df.min(axis='columns') + indicator_max = value.df.max(axis = 'columns') + indicator_min = value.df.min(axis = 'columns') source.add(indicator_max, f'indicator_{i}_range_max') source.add(indicator_min, f'indicator_{i}_range_min') for j, arr in enumerate(value, 1): @@ -612,10 +612,9 @@ def __eq__(self, other): custom_js_args = dict(ohlc_range=fig_ohlc.y_range, source=source) if plot_volume: - custom_js_args.update(volume_range=fig_volume.y_range) - + custom_js_args.update(volume_range=fig_volume.y_range) indicator_ranges = {} - for idx,indicator in enumerate(indicator_figs): + for idx, indicator in enumerate(indicator_figs): indicator_range_key = f'indicator_{idx}_range' indicator_ranges.update({indicator_range_key: indicator.y_range}) custom_js_args.update({'indicator_ranges': indicator_ranges}) From bf03b63503d28a568b80a440c018e4dc1432f91a Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sat, 22 May 2021 20:34:29 -0700 Subject: [PATCH 06/28] Auto-scale Y-axis for indicators when zooming #356 --- backtesting/_plotting.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index e22358b0..424dd18a 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -523,8 +523,8 @@ def __eq__(self, other): colors = colors and cycle(_as_list(colors)) or ( cycle([next(ohlc_colors)]) if is_overlay else colorgen()) legend_label = LegendStr(value.name) - indicator_max = value.df.max(axis = 'columns') - indicator_min = value.df.min(axis = 'columns') + indicator_max = value.df.max(axis='columns') + indicator_min = value.df.min(axis='columns') source.add(indicator_max, f'indicator_{i}_range_max') source.add(indicator_min, f'indicator_{i}_range_min') for j, arr in enumerate(value, 1): @@ -612,7 +612,7 @@ def __eq__(self, other): custom_js_args = dict(ohlc_range=fig_ohlc.y_range, source=source) if plot_volume: - custom_js_args.update(volume_range=fig_volume.y_range) + custom_js_args.update(volume_range=fig_volume.y_range) indicator_ranges = {} for idx, indicator in enumerate(indicator_figs): indicator_range_key = f'indicator_{idx}_range' From 46cffe3ce75863f6eb934b8f020e4184ce5249c0 Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sat, 12 Jun 2021 15:11:58 -0700 Subject: [PATCH 07/28] Trailing pct instead of ATR #223 --- backtesting/lib.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/backtesting/lib.py b/backtesting/lib.py index f7f61e74..73d5a935 100644 --- a/backtesting/lib.py +++ b/backtesting/lib.py @@ -451,6 +451,24 @@ def next(self): trade.sl = min(trade.sl or np.inf, self.data.Close[index] + self.__atr[index] * self.__n_atr) +class PercentageTrailingStrategy(Strategy): + _sl_percent = 5 + def init(self): + super().init() + + def set_trailing_sl(self, percentage: float = 5): + self._sl_percent = percentage + + def next(self): + super().next() + index = len(self.data)-1 + for trade in self.trades: + if trade.is_long: + trade.sl = max(trade.sl or -np.inf, + self.data.Close[index]*(1-(self._sl_percent/100))) + else: + trade.sl = min(trade.sl or np.inf, + self.data.Close[index]*(1+(self._sl_percent/100))) # Prevent pdoc3 documenting __init__ signature of Strategy subclasses for cls in list(globals().values()): From 8eab87c15b226bc4c9360fe4826204ed46fb3934 Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sat, 12 Jun 2021 20:56:32 -0700 Subject: [PATCH 08/28] Trailing pct instead of ATR kernc#223 Add code description and use float type for default percentage value. --- backtesting/lib.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/backtesting/lib.py b/backtesting/lib.py index 73d5a935..e1c5b3a4 100644 --- a/backtesting/lib.py +++ b/backtesting/lib.py @@ -452,11 +452,27 @@ def next(self): self.data.Close[index] + self.__atr[index] * self.__n_atr) class PercentageTrailingStrategy(Strategy): - _sl_percent = 5 + """ + A strategy with automatic trailing stop-loss, trailing the current + price at distance of some percentage. Call + `PercentageTrailingStrategy.set_trailing_sl()` to set said percentage + (`5` by default). See [tutorials] for usage examples. + + [tutorials]: index.html#tutorials + + Remember to call `super().init()` and `super().next()` in your + overridden methods. + """ + _sl_percent = 5. def init(self): super().init() def set_trailing_sl(self, percentage: float = 5): + assert percentage > 0, "percentage must be greater than 0" + """ + Sets the future trailing stop-loss as some (`percentage`) + percentage away from the current price. + """ self._sl_percent = percentage def next(self): From d6fa85127f5cdfa34922697e7119ab5e04ee2e4e Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sat, 12 Jun 2021 22:56:35 -0700 Subject: [PATCH 09/28] Test case for PercentageTrailingStrategy kernc#223 --- backtesting/test/_test.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/backtesting/test/_test.py b/backtesting/test/_test.py index 85ecea6a..3cbcc372 100644 --- a/backtesting/test/_test.py +++ b/backtesting/test/_test.py @@ -24,6 +24,7 @@ quantile, SignalStrategy, TrailingStrategy, + PercentageTrailingStrategy, resample_apply, plot_heatmaps, random_ohlc_data, @@ -862,6 +863,20 @@ def next(self): stats = Backtest(GOOG, S).run() self.assertEqual(stats['# Trades'], 57) + def test_PercentageTrailingStrategy(self): + class S(PercentageTrailingStrategy): + def init(self): + super().init() + self.set_trailing_sl(5) + self.sma = self.I(lambda: self.data.Close.s.rolling(10).mean()) + + def next(self): + super().next() + if not self.position and self.data.Close > self.sma: + self.buy() + + stats = Backtest(GOOG, S).run() + self.assertEqual(stats['# Trades'], 91) class TestUtil(TestCase): def test_as_str(self): From b045c7cf0901475c7ec2b3c604db6a20db7c8aa1 Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sat, 12 Jun 2021 23:05:12 -0700 Subject: [PATCH 10/28] Added blank line to fix lint issue kernc#223 --- backtesting/test/_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backtesting/test/_test.py b/backtesting/test/_test.py index 3cbcc372..e4cc8d05 100644 --- a/backtesting/test/_test.py +++ b/backtesting/test/_test.py @@ -878,6 +878,7 @@ def next(self): stats = Backtest(GOOG, S).run() self.assertEqual(stats['# Trades'], 91) + class TestUtil(TestCase): def test_as_str(self): def func(): From 3ef557a655fe72d829852c6969abd5ce2f7fc33b Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sat, 12 Jun 2021 23:08:27 -0700 Subject: [PATCH 11/28] added blank space to fix lint issue kernc#223 --- backtesting/lib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backtesting/lib.py b/backtesting/lib.py index e1c5b3a4..9df03ca8 100644 --- a/backtesting/lib.py +++ b/backtesting/lib.py @@ -486,6 +486,7 @@ def next(self): trade.sl = min(trade.sl or np.inf, self.data.Close[index]*(1+(self._sl_percent/100))) + # Prevent pdoc3 documenting __init__ signature of Strategy subclasses for cls in list(globals().values()): if isinstance(cls, type) and issubclass(cls, Strategy): From 898969c69756123636af9aec0e43692f6f9f9406 Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sat, 12 Jun 2021 23:21:25 -0700 Subject: [PATCH 12/28] added blank line to fix lint issue kernc#223 --- backtesting/lib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backtesting/lib.py b/backtesting/lib.py index 9df03ca8..fe009f51 100644 --- a/backtesting/lib.py +++ b/backtesting/lib.py @@ -451,6 +451,7 @@ def next(self): trade.sl = min(trade.sl or np.inf, self.data.Close[index] + self.__atr[index] * self.__n_atr) + class PercentageTrailingStrategy(Strategy): """ A strategy with automatic trailing stop-loss, trailing the current From a7732f27f37755334304b2794139a1c459f7b124 Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sat, 12 Jun 2021 23:31:15 -0700 Subject: [PATCH 13/28] Added blank line to fix lin issue kernc#223 --- backtesting/lib.py | 1 + 1 file changed, 1 insertion(+) diff --git a/backtesting/lib.py b/backtesting/lib.py index fe009f51..f7715e20 100644 --- a/backtesting/lib.py +++ b/backtesting/lib.py @@ -465,6 +465,7 @@ class PercentageTrailingStrategy(Strategy): overridden methods. """ _sl_percent = 5. + def init(self): super().init() From 5ca9a59ccb37f90fc8230b2ede1af4bbdf2dcd48 Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sun, 13 Jun 2021 00:06:36 -0700 Subject: [PATCH 14/28] Auto-scale Y-axis for Profit/Loss chart when zooming kernc#356 --- backtesting/_plotting.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index 424dd18a..0ecd0d45 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -588,7 +588,8 @@ def __eq__(self, other): figs_above_ohlc.append(_plot_drawdown_section()) if plot_pl: - figs_above_ohlc.append(_plot_pl_section()) + fig_pl = _plot_pl_section() + figs_above_ohlc.append(fig_pl) if plot_volume: fig_volume = _plot_volume_section() @@ -611,6 +612,8 @@ def __eq__(self, other): custom_js_args = dict(ohlc_range=fig_ohlc.y_range, source=source) + if plot_pl: + custom_js_args.update(pl_range=fig_pl.y_range) if plot_volume: custom_js_args.update(volume_range=fig_volume.y_range) indicator_ranges = {} From ab17cbf7ef674e6b9be542a516e17f2b7a071246 Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sun, 13 Jun 2021 12:00:52 -0700 Subject: [PATCH 15/28] Auto-scale Y-axis for PL chart when zooming kernc#356 --- backtesting/_plotting.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index 0ecd0d45..d2724858 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -391,6 +391,9 @@ def _plot_pl_section(): returns_short = np.where(trades['Size'] < 0, trades['ReturnPct'], np.nan) size = trades['Size'].abs() size = np.interp(size, (size.min(), size.max()), (8, 20)) + ohlcv_index_trade_close_arr = np.empty(source.data['index'][-1]+1,dtype=float) + ohlcv_index_trade_close_arr[trade_source.data['index']] = trades['ReturnPct'] + source.add(ohlcv_index_trade_close_arr,'return_pct') trade_source.add(returns_long, 'returns_long') trade_source.add(returns_short, 'returns_short') trade_source.add(size, 'marker_size') From 44a0200e02aa44e437b42b5a74e3cf8a0d0b0ea4 Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sun, 13 Jun 2021 12:03:00 -0700 Subject: [PATCH 16/28] Add auto scaling of y-axis for PL chart kernc#356 --- backtesting/autoscale_cb.js | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/backtesting/autoscale_cb.js b/backtesting/autoscale_cb.js index 63135615..9ed3784c 100644 --- a/backtesting/autoscale_cb.js +++ b/backtesting/autoscale_cb.js @@ -27,6 +27,14 @@ window._bt_autoscale_timeout = setTimeout(function () { min = Math.min.apply(null, source.data['ohlc_low'].slice(i, j)); _bt_scale_range(ohlc_range, min, max, true); + if (pl_range) { + max = Math.max.apply(null, source.data['return_pct'].slice(i, j)); + min = Math.min.apply(null, source.data['return_pct'].slice(i, j)); + if(min && max){ + _bt_scale_range(pl_range, min, max, true); + } + } + if (volume_range) { max = Math.max.apply(null, source.data['Volume'].slice(i, j)); _bt_scale_range(volume_range, 0, max * 1.03, false); From 307e4ffb654698a5274588f71c8fc518ceb60dc8 Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sun, 13 Jun 2021 12:05:26 -0700 Subject: [PATCH 17/28] Add whitespace after , to fix lint issue kernc#356 --- backtesting/_plotting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index d2724858..c6195777 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -391,9 +391,9 @@ def _plot_pl_section(): returns_short = np.where(trades['Size'] < 0, trades['ReturnPct'], np.nan) size = trades['Size'].abs() size = np.interp(size, (size.min(), size.max()), (8, 20)) - ohlcv_index_trade_close_arr = np.empty(source.data['index'][-1]+1,dtype=float) + ohlcv_index_trade_close_arr = np.empty(source.data['index'][-1]+1, dtype=float) ohlcv_index_trade_close_arr[trade_source.data['index']] = trades['ReturnPct'] - source.add(ohlcv_index_trade_close_arr,'return_pct') + source.add(ohlcv_index_trade_close_arr, 'return_pct') trade_source.add(returns_long, 'returns_long') trade_source.add(returns_short, 'returns_short') trade_source.add(size, 'marker_size') From 97712c7b91ab5fe9520010a669b5e1aabe9e62fc Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sun, 13 Jun 2021 12:20:08 -0700 Subject: [PATCH 18/28] Fixing build error by adding [:] kernc#356 --- backtesting/_plotting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index c6195777..e6c52058 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -392,7 +392,7 @@ def _plot_pl_section(): size = trades['Size'].abs() size = np.interp(size, (size.min(), size.max()), (8, 20)) ohlcv_index_trade_close_arr = np.empty(source.data['index'][-1]+1, dtype=float) - ohlcv_index_trade_close_arr[trade_source.data['index']] = trades['ReturnPct'] + ohlcv_index_trade_close_arr[trade_source.data['index'][:]] = trades['ReturnPct'] source.add(ohlcv_index_trade_close_arr, 'return_pct') trade_source.add(returns_long, 'returns_long') trade_source.add(returns_short, 'returns_short') From 14943964415e9fb1007403ba56802cbe7548c513 Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sun, 13 Jun 2021 13:36:17 -0700 Subject: [PATCH 19/28] Remove y-axis scaling logic for PnL graph kernc#356 --- backtesting/_plotting.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index e6c52058..0ecd0d45 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -391,9 +391,6 @@ def _plot_pl_section(): returns_short = np.where(trades['Size'] < 0, trades['ReturnPct'], np.nan) size = trades['Size'].abs() size = np.interp(size, (size.min(), size.max()), (8, 20)) - ohlcv_index_trade_close_arr = np.empty(source.data['index'][-1]+1, dtype=float) - ohlcv_index_trade_close_arr[trade_source.data['index'][:]] = trades['ReturnPct'] - source.add(ohlcv_index_trade_close_arr, 'return_pct') trade_source.add(returns_long, 'returns_long') trade_source.add(returns_short, 'returns_short') trade_source.add(size, 'marker_size') From 95e03f8b61ca1491008f4befb655d97521eefe6f Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Sun, 13 Jun 2021 13:36:50 -0700 Subject: [PATCH 20/28] Remove y-axis scaling logic for PnL graph kernc#356 --- backtesting/autoscale_cb.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/backtesting/autoscale_cb.js b/backtesting/autoscale_cb.js index 9ed3784c..63135615 100644 --- a/backtesting/autoscale_cb.js +++ b/backtesting/autoscale_cb.js @@ -27,14 +27,6 @@ window._bt_autoscale_timeout = setTimeout(function () { min = Math.min.apply(null, source.data['ohlc_low'].slice(i, j)); _bt_scale_range(ohlc_range, min, max, true); - if (pl_range) { - max = Math.max.apply(null, source.data['return_pct'].slice(i, j)); - min = Math.min.apply(null, source.data['return_pct'].slice(i, j)); - if(min && max){ - _bt_scale_range(pl_range, min, max, true); - } - } - if (volume_range) { max = Math.max.apply(null, source.data['Volume'].slice(i, j)); _bt_scale_range(volume_range, 0, max * 1.03, false); From bf2585c929a76c2e79756e3b7af395da38c55fe0 Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Mon, 14 Jun 2021 23:15:03 -0700 Subject: [PATCH 21/28] Add Histogram indicator plot style kernc#195 --- backtesting/_plotting.py | 13 +++++++++++-- backtesting/backtesting.py | 8 ++++++-- backtesting/test/_test.py | 18 ++++++++++++++++++ 3 files changed, 35 insertions(+), 4 deletions(-) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index bec6757a..7ccdafc9 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -513,6 +513,7 @@ def __eq__(self, other): is_overlay = value._opts['overlay'] is_scatter = value._opts['scatter'] + is_histogram = value._opts['histogram'] if is_overlay: fig = fig_ohlc else: @@ -532,7 +533,11 @@ def __eq__(self, other): tooltips.append(f'@{{{source_name}}}{{0,0.0[0000]}}') if is_overlay: ohlc_extreme_values[source_name] = arr - if is_scatter: + if is_histogram: + fig.vbar(x=source.data['index'], legend_label=legend_label, + bottom=[0 for _ in source.data['index']], + top=arr, width=BAR_WIDTH, color=color) + elif is_scatter: fig.scatter( 'index', source_name, source=source, legend_label=legend_label, color=color, @@ -544,7 +549,11 @@ def __eq__(self, other): legend_label=legend_label, line_color=color, line_width=1.3) else: - if is_scatter: + if is_histogram: + r = fig.vbar(x=source.data['index'], legend_label=LegendStr(legend_label), + bottom=[0 for _ in source.data['index']], + top=arr, width=BAR_WIDTH, color=color) + elif is_scatter: r = fig.scatter( 'index', source_name, source=source, legend_label=LegendStr(legend_label), color=color, diff --git a/backtesting/backtesting.py b/backtesting/backtesting.py index edb7be01..f5b47b35 100644 --- a/backtesting/backtesting.py +++ b/backtesting/backtesting.py @@ -75,7 +75,7 @@ def _check_params(self, params): def I(self, # noqa: E741, E743 func: Callable, *args, - name=None, plot=True, overlay=None, color=None, scatter=False, + name=None, plot=True, overlay=None, color=None, scatter=False, histogram=False, **kwargs) -> np.ndarray: """ Declare indicator. An indicator is just an array of values, @@ -105,6 +105,10 @@ def I(self, # noqa: E741, E743 If `scatter` is `True`, the plotted indicator marker will be a circle instead of a connected line segment (default). + If `histogram` is `True`, the indicator values will be plotted + as a histogram instead of line or circle. When `histogram` is + `True`, 'scatter' value will be ignored even if it's set. + Additional `*args` and `**kwargs` are passed to `func` and can be used for parameters. @@ -151,7 +155,7 @@ def init(): overlay = ((x < 1.4) & (x > .6)).mean() > .6 value = _Indicator(value, name=name, plot=plot, overlay=overlay, - color=color, scatter=scatter, + color=color, scatter=scatter, histogram=histogram, # _Indicator.s Series accessor uses this: index=self.data.index) self._indicators.append(value) diff --git a/backtesting/test/_test.py b/backtesting/test/_test.py index 85ecea6a..faab4f15 100644 --- a/backtesting/test/_test.py +++ b/backtesting/test/_test.py @@ -771,6 +771,24 @@ def next(self): plot_drawdown=False, plot_equity=False, plot_pl=False, plot_volume=False, open_browser=False) + def test_indicator_histogram(self): + class S(Strategy): + def init(self): + self.I(SMA, self.data.Close, 5, overlay=True, scatter=False, histogram=True) + self.I(SMA, self.data.Close, 10, overlay=False, scatter=False, histogram=True) + + def next(self): + pass + + bt = Backtest(GOOG, S) + bt.run() + with _tempfile() as f: + bt.plot(filename=f, + plot_drawdown=False, plot_equity=False, plot_pl=False, plot_volume=False, + open_browser=True) + # Give browser time to open before tempfile is removed + time.sleep(1) + class TestLib(TestCase): def test_barssince(self): From dea0663444dba3606790e061a7cd1b2368c5523a Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Mon, 14 Jun 2021 23:58:50 -0700 Subject: [PATCH 22/28] Fix legends values on histogram kernc#195 --- backtesting/_plotting.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index 7ccdafc9..0f8ae18e 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -534,9 +534,7 @@ def __eq__(self, other): if is_overlay: ohlc_extreme_values[source_name] = arr if is_histogram: - fig.vbar(x=source.data['index'], legend_label=legend_label, - bottom=[0 for _ in source.data['index']], - top=arr, width=BAR_WIDTH, color=color) + fig.vbar('index', BAR_WIDTH, source_name, source=source, legend_label=legend_label, color=color) elif is_scatter: fig.scatter( 'index', source_name, source=source, @@ -550,9 +548,7 @@ def __eq__(self, other): line_width=1.3) else: if is_histogram: - r = fig.vbar(x=source.data['index'], legend_label=LegendStr(legend_label), - bottom=[0 for _ in source.data['index']], - top=arr, width=BAR_WIDTH, color=color) + r = fig.vbar('index', BAR_WIDTH, source_name, source=source, legend_label=LegendStr(legend_label), color=color) elif is_scatter: r = fig.scatter( 'index', source_name, source=source, From 45be27547a5759f391c453bf1bf867b522ccc180 Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Tue, 15 Jun 2021 00:01:24 -0700 Subject: [PATCH 23/28] Fix lint issues kernc#195 --- backtesting/_plotting.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index 0f8ae18e..3e2305b4 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -534,7 +534,8 @@ def __eq__(self, other): if is_overlay: ohlc_extreme_values[source_name] = arr if is_histogram: - fig.vbar('index', BAR_WIDTH, source_name, source=source, legend_label=legend_label, color=color) + fig.vbar('index', BAR_WIDTH, source_name, source=source, + legend_label=legend_label, color=color) elif is_scatter: fig.scatter( 'index', source_name, source=source, @@ -548,7 +549,8 @@ def __eq__(self, other): line_width=1.3) else: if is_histogram: - r = fig.vbar('index', BAR_WIDTH, source_name, source=source, legend_label=LegendStr(legend_label), color=color) + r = fig.vbar('index', BAR_WIDTH, source_name, source=source, + legend_label=LegendStr(legend_label), color=color) elif is_scatter: r = fig.scatter( 'index', source_name, source=source, From 8eb9a6864398dcffabb98ab07a8634dd037389a4 Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Tue, 15 Jun 2021 00:03:19 -0700 Subject: [PATCH 24/28] Fix lint issues kernc#195 --- backtesting/_plotting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index 3e2305b4..2535b8ad 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -534,7 +534,7 @@ def __eq__(self, other): if is_overlay: ohlc_extreme_values[source_name] = arr if is_histogram: - fig.vbar('index', BAR_WIDTH, source_name, source=source, + fig.vbar('index', BAR_WIDTH, source_name, source=source, legend_label=legend_label, color=color) elif is_scatter: fig.scatter( @@ -549,7 +549,7 @@ def __eq__(self, other): line_width=1.3) else: if is_histogram: - r = fig.vbar('index', BAR_WIDTH, source_name, source=source, + r = fig.vbar('index', BAR_WIDTH, source_name, source=source, legend_label=LegendStr(legend_label), color=color) elif is_scatter: r = fig.scatter( From b4bc12367bf37269dc8ea7d13c6cafde33ca5617 Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Tue, 15 Jun 2021 00:06:51 -0700 Subject: [PATCH 25/28] Fix lint issues kernc#195 --- backtesting/_plotting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index 2535b8ad..7197c0c2 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -535,7 +535,7 @@ def __eq__(self, other): ohlc_extreme_values[source_name] = arr if is_histogram: fig.vbar('index', BAR_WIDTH, source_name, source=source, - legend_label=legend_label, color=color) + legend_label=legend_label, color=color) elif is_scatter: fig.scatter( 'index', source_name, source=source, @@ -550,7 +550,7 @@ def __eq__(self, other): else: if is_histogram: r = fig.vbar('index', BAR_WIDTH, source_name, source=source, - legend_label=LegendStr(legend_label), color=color) + legend_label=LegendStr(legend_label), color=color) elif is_scatter: r = fig.scatter( 'index', source_name, source=source, From 67e2fff9fbc8a766022ffb5a047dad820da3bd19 Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Tue, 15 Jun 2021 00:10:29 -0700 Subject: [PATCH 26/28] Fix lint issues kernc#195 --- backtesting/_plotting.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index 7197c0c2..c4881eef 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -535,7 +535,7 @@ def __eq__(self, other): ohlc_extreme_values[source_name] = arr if is_histogram: fig.vbar('index', BAR_WIDTH, source_name, source=source, - legend_label=legend_label, color=color) + legend_label=legend_label, color=color) elif is_scatter: fig.scatter( 'index', source_name, source=source, @@ -550,7 +550,7 @@ def __eq__(self, other): else: if is_histogram: r = fig.vbar('index', BAR_WIDTH, source_name, source=source, - legend_label=LegendStr(legend_label), color=color) + legend_label=LegendStr(legend_label), color=color) elif is_scatter: r = fig.scatter( 'index', source_name, source=source, From 8a687570b6694c36d9968cbb9b312304b446fe01 Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Tue, 15 Jun 2021 00:13:16 -0700 Subject: [PATCH 27/28] Fix lint issues kernc#195 --- backtesting/backtesting.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/backtesting/backtesting.py b/backtesting/backtesting.py index f5b47b35..41b7a7d5 100644 --- a/backtesting/backtesting.py +++ b/backtesting/backtesting.py @@ -106,7 +106,7 @@ def I(self, # noqa: E741, E743 circle instead of a connected line segment (default). If `histogram` is `True`, the indicator values will be plotted - as a histogram instead of line or circle. When `histogram` is + as a histogram instead of line or circle. When `histogram` is `True`, 'scatter' value will be ignored even if it's set. Additional `*args` and `**kwargs` are passed to `func` and can From dc3ffd9ef69135996b957b2f1d6fea39d83ab4bc Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Mon, 14 Jun 2021 23:15:03 -0700 Subject: [PATCH 28/28] Add Histogram indicator plot style kernc#195 --- backtesting/_plotting.py | 11 +++++++++-- backtesting/backtesting.py | 8 ++++++-- backtesting/test/_test.py | 18 ++++++++++++++++++ 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index 0ecd0d45..879e88b1 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -513,6 +513,7 @@ def __eq__(self, other): is_overlay = value._opts['overlay'] is_scatter = value._opts['scatter'] + is_histogram = value._opts['histogram'] if is_overlay: fig = fig_ohlc else: @@ -536,7 +537,10 @@ def __eq__(self, other): tooltips.append(f'@{{{source_name}}}{{0,0.0[0000]}}') if is_overlay: ohlc_extreme_values[source_name] = arr - if is_scatter: + if is_histogram: + fig.vbar('index', BAR_WIDTH, source_name, source=source, + legend_label=legend_label, color=color) + elif is_scatter: fig.scatter( 'index', source_name, source=source, legend_label=legend_label, color=color, @@ -548,7 +552,10 @@ def __eq__(self, other): legend_label=legend_label, line_color=color, line_width=1.3) else: - if is_scatter: + if is_histogram: + r = fig.vbar('index', BAR_WIDTH, source_name, source=source, + legend_label=LegendStr(legend_label), color=color) + elif is_scatter: r = fig.scatter( 'index', source_name, source=source, legend_label=LegendStr(legend_label), color=color, diff --git a/backtesting/backtesting.py b/backtesting/backtesting.py index edb7be01..41b7a7d5 100644 --- a/backtesting/backtesting.py +++ b/backtesting/backtesting.py @@ -75,7 +75,7 @@ def _check_params(self, params): def I(self, # noqa: E741, E743 func: Callable, *args, - name=None, plot=True, overlay=None, color=None, scatter=False, + name=None, plot=True, overlay=None, color=None, scatter=False, histogram=False, **kwargs) -> np.ndarray: """ Declare indicator. An indicator is just an array of values, @@ -105,6 +105,10 @@ def I(self, # noqa: E741, E743 If `scatter` is `True`, the plotted indicator marker will be a circle instead of a connected line segment (default). + If `histogram` is `True`, the indicator values will be plotted + as a histogram instead of line or circle. When `histogram` is + `True`, 'scatter' value will be ignored even if it's set. + Additional `*args` and `**kwargs` are passed to `func` and can be used for parameters. @@ -151,7 +155,7 @@ def init(): overlay = ((x < 1.4) & (x > .6)).mean() > .6 value = _Indicator(value, name=name, plot=plot, overlay=overlay, - color=color, scatter=scatter, + color=color, scatter=scatter, histogram=histogram, # _Indicator.s Series accessor uses this: index=self.data.index) self._indicators.append(value) diff --git a/backtesting/test/_test.py b/backtesting/test/_test.py index e4cc8d05..0b0c1a51 100644 --- a/backtesting/test/_test.py +++ b/backtesting/test/_test.py @@ -772,6 +772,24 @@ def next(self): plot_drawdown=False, plot_equity=False, plot_pl=False, plot_volume=False, open_browser=False) + def test_indicator_histogram(self): + class S(Strategy): + def init(self): + self.I(SMA, self.data.Close, 5, overlay=True, scatter=False, histogram=True) + self.I(SMA, self.data.Close, 10, overlay=False, scatter=False, histogram=True) + + def next(self): + pass + + bt = Backtest(GOOG, S) + bt.run() + with _tempfile() as f: + bt.plot(filename=f, + plot_drawdown=False, plot_equity=False, plot_pl=False, plot_volume=False, + open_browser=True) + # Give browser time to open before tempfile is removed + time.sleep(1) + class TestLib(TestCase): def test_barssince(self):