Skip to content

Commit c6eb56f

Browse files
committed
Auto-scale Y-axis for indicators when zooming kernc#356
1 parent 0a76e96 commit c6eb56f

File tree

2 files changed

+32
-4
lines changed

2 files changed

+32
-4
lines changed

backtesting/_plotting.py

+19-4
Original file line numberDiff line numberDiff line change
@@ -502,6 +502,7 @@ def __eq__(self, other):
502502

503503
ohlc_colors = colorgen()
504504
indicator_figs = []
505+
non_overlay_indicator_idxs = []
505506

506507
for i, value in enumerate(indicators):
507508
value = np.atleast_2d(value)
@@ -518,11 +519,16 @@ def __eq__(self, other):
518519
else:
519520
fig = new_indicator_figure()
520521
indicator_figs.append(fig)
522+
non_overlay_indicator_idxs.append(i)
521523
tooltips = []
522524
colors = value._opts['color']
523525
colors = colors and cycle(_as_list(colors)) or (
524526
cycle([next(ohlc_colors)]) if is_overlay else colorgen())
525527
legend_label = LegendStr(value.name)
528+
indicator_max = value.df.max(axis='columns')
529+
indicator_min = value.df.min(axis='columns')
530+
source.add(indicator_max, f'indicator_{i}_range_max')
531+
source.add(indicator_min, f'indicator_{i}_range_min')
526532
for j, arr in enumerate(value, 1):
527533
color = next(colors)
528534
source_name = f'{legend_label}_{i}_{j}'
@@ -570,7 +576,7 @@ def __eq__(self, other):
570576
# have the legend only contain text without the glyph
571577
if len(value) == 1:
572578
fig.legend.glyph_width = 0
573-
return indicator_figs
579+
return (indicator_figs, non_overlay_indicator_idxs)
574580

575581
# Construct figure ...
576582

@@ -584,7 +590,8 @@ def __eq__(self, other):
584590
figs_above_ohlc.append(_plot_drawdown_section())
585591

586592
if plot_pl:
587-
figs_above_ohlc.append(_plot_pl_section())
593+
fig_pl = _plot_pl_section()
594+
figs_above_ohlc.append(fig_pl)
588595

589596
if plot_volume:
590597
fig_volume = _plot_volume_section()
@@ -595,9 +602,10 @@ def __eq__(self, other):
595602

596603
ohlc_bars = _plot_ohlc()
597604
_plot_ohlc_trades()
598-
indicator_figs = _plot_indicators()
605+
indicator_figs, non_overlay_indicator_idxs = _plot_indicators()
599606
if reverse_indicators:
600607
indicator_figs = indicator_figs[::-1]
608+
non_overlay_indicator_idxs = non_overlay_indicator_idxs[::-1]
601609
figs_below_ohlc.extend(indicator_figs)
602610

603611
set_tooltips(fig_ohlc, ohlc_tooltips, vline=True, renderers=[ohlc_bars])
@@ -607,9 +615,16 @@ def __eq__(self, other):
607615

608616
custom_js_args = dict(ohlc_range=fig_ohlc.y_range,
609617
source=source)
618+
if plot_pl:
619+
custom_js_args.update(pl_range=fig_pl.y_range)
610620
if plot_volume:
611621
custom_js_args.update(volume_range=fig_volume.y_range)
612-
622+
indicator_ranges = {}
623+
for idx, (indicator,
624+
indicator_idx) in enumerate(zip(indicator_figs, non_overlay_indicator_idxs)):
625+
indicator_range_key = f'indicator_{indicator_idx}_range'
626+
indicator_ranges.update({indicator_range_key: indicator.y_range})
627+
custom_js_args.update({'indicator_ranges': indicator_ranges})
613628
fig_ohlc.x_range.js_on_change('end', CustomJS(args=custom_js_args,
614629
code=_AUTOSCALE_JS_CALLBACK))
615630

backtesting/autoscale_cb.js

+13
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,18 @@ window._bt_autoscale_timeout = setTimeout(function () {
3131
max = Math.max.apply(null, source.data['Volume'].slice(i, j));
3232
_bt_scale_range(volume_range, 0, max * 1.03, false);
3333
}
34+
35+
if(indicator_ranges){
36+
let keys = Object.keys(indicator_ranges);
37+
for(var count=0;count<keys.length;count++){
38+
if(keys[count]){
39+
max = Math.max.apply(null, source.data[keys[count]+'_max'].slice(i, j));
40+
min = Math.min.apply(null, source.data[keys[count]+'_min'].slice(i, j));
41+
if(min && max){
42+
_bt_scale_range(indicator_ranges[keys[count]], min, max, true);
43+
}
44+
}
45+
}
46+
}
3447

3548
}, 50);

0 commit comments

Comments
 (0)