From c6eb56fd86b018b8083057a4160ca69ff1126182 Mon Sep 17 00:00:00 2001 From: Zeel Patel Date: Thu, 17 Jun 2021 16:09:38 -0700 Subject: [PATCH] Auto-scale Y-axis for indicators when zooming kernc#356 --- backtesting/_plotting.py | 23 +++++++++++++++++++---- backtesting/autoscale_cb.js | 13 +++++++++++++ 2 files changed, 32 insertions(+), 4 deletions(-) diff --git a/backtesting/_plotting.py b/backtesting/_plotting.py index bec6757a..aa6768cc 100644 --- a/backtesting/_plotting.py +++ b/backtesting/_plotting.py @@ -502,6 +502,7 @@ def __eq__(self, other): ohlc_colors = colorgen() indicator_figs = [] + non_overlay_indicator_idxs = [] for i, value in enumerate(indicators): value = np.atleast_2d(value) @@ -518,11 +519,16 @@ def __eq__(self, other): else: fig = new_indicator_figure() indicator_figs.append(fig) + non_overlay_indicator_idxs.append(i) tooltips = [] colors = value._opts['color'] 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') for j, arr in enumerate(value, 1): color = next(colors) source_name = f'{legend_label}_{i}_{j}' @@ -570,7 +576,7 @@ def __eq__(self, other): # have the legend only contain text without the glyph if len(value) == 1: fig.legend.glyph_width = 0 - return indicator_figs + return (indicator_figs, non_overlay_indicator_idxs) # Construct figure ... @@ -584,7 +590,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() @@ -595,9 +602,10 @@ def __eq__(self, other): ohlc_bars = _plot_ohlc() _plot_ohlc_trades() - indicator_figs = _plot_indicators() + indicator_figs, non_overlay_indicator_idxs = _plot_indicators() if reverse_indicators: indicator_figs = indicator_figs[::-1] + non_overlay_indicator_idxs = non_overlay_indicator_idxs[::-1] figs_below_ohlc.extend(indicator_figs) set_tooltips(fig_ohlc, ohlc_tooltips, vline=True, renderers=[ohlc_bars]) @@ -607,9 +615,16 @@ 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 = {} + for idx, (indicator, + indicator_idx) in enumerate(zip(indicator_figs, non_overlay_indicator_idxs)): + indicator_range_key = f'indicator_{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)) 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