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):