Skip to content

Commit bae9340

Browse files
committed
REF: Reorganize plotting of indicators
BUG: Fix incorrect plot when multiple indicators share the same name. E.g. both these names are 'λ' yet both plots contained values of the latter of the two. self.I(lambda: self.data.Open, overlay=False) self.I(lambda: self.data.High, overlay=False)
1 parent 7991596 commit bae9340

File tree

1 file changed

+41
-44
lines changed

1 file changed

+41
-44
lines changed

backtesting/_plotting.py

Lines changed: 41 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -422,64 +422,62 @@ class LegendStr(str):
422422
# legend items are listed separately even when they have the
423423
# same string contents. Otherwise, Bokeh would always consider
424424
# equal strings as one and the same legend item.
425-
# This also prevents legend items named the same as some
426-
# ColumnDataSource's column to be replaced with that column's
427-
# values.
428425
def __eq__(self, other):
429426
return self is other
430427

431428
ohlc_colors = colorgen()
432429

433-
for value in indicators:
430+
for i, value in enumerate(indicators):
434431
value = np.atleast_2d(value)
435432

436433
# Use .get()! A user might have assigned a Strategy.data-evolved
437434
# _Array without Strategy.I()
438435
if not value._opts.get('plot') or _too_many_dims(value):
439436
continue
440437

438+
is_overlay = value._opts['overlay']
439+
is_scatter = value._opts['scatter']
440+
if is_overlay:
441+
fig = fig_ohlc
442+
else:
443+
fig = new_indicator_figure()
444+
figs_below_ohlc.append(fig)
441445
tooltips = []
442-
443-
# Overlay indicators on the OHLC figure
444-
if value._opts['overlay']:
445-
color = value._opts['color']
446-
color = color and _as_list(color)[0] or next(ohlc_colors)
447-
legend = LegendStr(value.name)
448-
for i, arr in enumerate(value):
449-
source_name = '{}_{}'.format(value.name, i)
450-
source.add(arr, source_name)
451-
if value._opts.get('scatter'):
452-
fig_ohlc.scatter(
446+
colors = value._opts['color']
447+
colors = colors and cycle([_as_list(colors)[0]]) or (
448+
cycle([next(ohlc_colors)]) if is_overlay else colorgen())
449+
legend_label = LegendStr(value.name)
450+
for j, arr in enumerate(value, 1):
451+
color = next(colors)
452+
source_name = '{}_{}_{}'.format(legend_label, i, j)
453+
if arr.dtype == bool:
454+
arr = arr.astype(int)
455+
source.add(arr, source_name)
456+
tooltips.append('@{{{}}}{{0,0.0[0000]}}'.format(source_name))
457+
if is_overlay:
458+
ohlc_extreme_values[source_name] = arr
459+
if is_scatter:
460+
fig.scatter(
453461
'index', source_name, source=source,
454-
color=color, line_color='black', fill_alpha=.8,
455-
marker='circle', radius=bar_width / 2 * 1.5, legend_label=legend)
462+
legend_label=legend_label, color=color,
463+
line_color='black', fill_alpha=.8,
464+
marker='circle', radius=bar_width / 2 * 1.5)
456465
else:
457-
fig_ohlc.line(
466+
fig.line(
458467
'index', source_name, source=source,
459-
line_width=1.3, line_color=color, legend_label=legend)
460-
ohlc_extreme_values[source_name] = arr
461-
tooltips.append('@{{{}}}{{0,0.0[0000]}}'.format(source_name))
462-
ohlc_tooltips.append((value.name, NBSP.join(tooltips)))
463-
else:
464-
# Standalone indicator sections at the bottom
465-
color = value._opts['color']
466-
color = color and cycle(_as_list(color)) or colorgen()
467-
fig = new_indicator_figure()
468-
for i, arr in enumerate(value, 1):
469-
legend = '{}-{}'.format(value.name, i) if len(value) > 1 else value.name
470-
name = legend + '_' # Otherwise fig.line(legend=) is interpreted as col of source # noqa: E501
471-
tooltips.append('@{{{}}}'.format(name))
472-
source.add(arr.astype(int if arr.dtype == bool else float), name)
473-
if value._opts.get('scatter'):
468+
legend_label=legend_label, line_color=color,
469+
line_width=1.3)
470+
else:
471+
if is_scatter:
474472
r = fig.scatter(
475-
'index', name, source=source, color=next(color),
476-
marker='circle', radius=bar_width / 2 * .9,
477-
legend_label=LegendStr(legend))
473+
'index', source_name, source=source,
474+
legend_label=LegendStr(legend_label), color=color,
475+
marker='circle', radius=bar_width / 2 * .9)
478476
else:
479477
r = fig.line(
480-
'index', name, source=source, line_color=next(color),
481-
line_width=1.3, legend_label=LegendStr(legend))
482-
478+
'index', source_name, source=source,
479+
legend_label=LegendStr(legend_label), line_color=color,
480+
line_width=1.3)
483481
# Add dashed centerline just because
484482
mean = float(pd.Series(arr).mean())
485483
if not np.isnan(mean) and (abs(mean) < .1 or
@@ -488,16 +486,15 @@ def __eq__(self, other):
488486
fig.add_layout(Span(location=float(mean), dimension='width',
489487
line_color='#666666', line_dash='dashed',
490488
line_width=.5))
491-
492-
set_tooltips(fig, [(value.name, NBSP.join(tooltips))], vline=True, renderers=[r])
493-
489+
if is_overlay:
490+
ohlc_tooltips.append((legend_label, NBSP.join(tooltips)))
491+
else:
492+
set_tooltips(fig, [(legend_label, NBSP.join(tooltips))], vline=True, renderers=[r])
494493
# If the sole indicator line on this figure,
495494
# have the legend only contain text without the glyph
496495
if len(value) == 1:
497496
fig.legend.glyph_width = 0
498497

499-
figs_below_ohlc.append(fig)
500-
501498
# Construct figure ...
502499

503500
if plot_equity:

0 commit comments

Comments
 (0)