From 0581c550e0f654fd683f6fce8fa4db2b7db859cf Mon Sep 17 00:00:00 2001
From: Zeel Patel <zlpatel@users.noreply.github.com>
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 <zlpatel@users.noreply.github.com>
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<keys.length;count++){
+            if(keys[count]){
+                max = Math.max.apply(null, source.data[keys[count]+'_max'].slice(i, j));
+                min = Math.min.apply(null, source.data[keys[count]+'_min'].slice(i, j));
+                if(min && max){
+                    _bt_scale_range(indicator_ranges[keys[count]], min, max, true);
+                }    
+            }
+        }
+    }
 
 }, 50);

From 4ff1fe80d0bf5b565e1e1380874ec14305203f80 Mon Sep 17 00:00:00 2001
From: Zeel Patel <zlpatel@users.noreply.github.com>
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 <zlpatel@users.noreply.github.com>
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 <zlpatel@users.noreply.github.com>
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 <zlpatel@users.noreply.github.com>
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 <zlpatel@users.noreply.github.com>
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 <zlpatel@users.noreply.github.com>
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 <zlpatel@users.noreply.github.com>
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 <zlpatel@users.noreply.github.com>
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 <zlpatel@users.noreply.github.com>
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 <zlpatel@users.noreply.github.com>
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 <zlpatel@users.noreply.github.com>
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 <zlpatel@users.noreply.github.com>
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 <zlpatel@users.noreply.github.com>
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 <zlpatel@users.noreply.github.com>
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 <zlpatel@users.noreply.github.com>
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 <zlpatel@users.noreply.github.com>
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 <zlpatel@users.noreply.github.com>
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 <zlpatel@users.noreply.github.com>
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 <zlpatel@hotmail.com>
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 <zlpatel@hotmail.com>
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 <zlpatel@hotmail.com>
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 <zlpatel@hotmail.com>
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 <zlpatel@hotmail.com>
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 <zlpatel@hotmail.com>
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 <zlpatel@hotmail.com>
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 <zlpatel@hotmail.com>
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):