From 77ea4c4009fa8f10c4bfa62c23d57515df442ecd Mon Sep 17 00:00:00 2001
From: Zeel Patel <zlpatel@hotmail.com>
Date: Tue, 15 Jun 2021 21:17:51 -0700
Subject: [PATCH]  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 bec6757a..c4881eef 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,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,
@@ -544,7 +548,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 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):