def _plot_indicators(): """Strategy indicators""" def _too_many_dims(value): assert value.ndim >= 2 if value.ndim > 2: warnings.warn( f"Can't plot indicators with >2D ('{value.name}')", stacklevel=5) return True return False class LegendStr(str): # The legend string is such a string that only matches # itself if it's the exact same object. This ensures # legend items are listed separately even when they have the # same string contents. Otherwise, Bokeh would always consider # equal strings as one and the same legend item. def __eq__(self, other): return self is other ohlc_colors = colorgen() indicator_figs = [] for i, value in enumerate(indicators): value = np.atleast_2d(value) # Use .get()! A user might have assigned a Strategy.data-evolved # _Array without Strategy.I() if not value._opts.get('plot') or _too_many_dims(value): continue is_overlay = value._opts['overlay'] is_scatter = value._opts['scatter'] if is_overlay: fig = fig_ohlc else: fig = new_indicator_figure() indicator_figs.append(fig) 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) for j, arr in enumerate(value, 1): color = next(colors) source_name = f'{legend_label}_{i}_{j}' if arr.dtype == bool: arr = arr.astype(int) source.add(arr, source_name) tooltips.append(f'@{{{source_name}}}{{0,0.0[0000]}}') if is_overlay: ohlc_extreme_values[source_name] = arr if is_scatter: fig.scatter('index', source_name, source=source, legend_label=legend_label, color=color, line_color='black', fill_alpha=.8, marker='circle', radius=BAR_WIDTH / 2 * 1.5) else: fig.line('index', source_name, source=source, legend_label=legend_label, line_color=color, line_width=1.3) else: if is_scatter: r = fig.scatter('index', source_name, source=source, legend_label=LegendStr(legend_label), color=color, marker='circle', radius=BAR_WIDTH / 2 * .9) else: r = fig.line('index', source_name, source=source, legend_label=LegendStr(legend_label), line_color=color, line_width=1.3) # Add dashed centerline just because mean = float(pd.Series(arr).mean()) if not np.isnan(mean) and ( abs(mean) < .1 or round(abs(mean), 1) == .5 or round(abs(mean), -1) in (50, 100, 200)): fig.add_layout( Span(location=float(mean), dimension='width', line_color='#666666', line_dash='dashed', line_width=.5)) if is_overlay: ohlc_tooltips.append((legend_label, NBSP.join(tooltips))) else: set_tooltips(fig, [(legend_label, NBSP.join(tooltips))], vline=True, renderers=[r]) # If the sole indicator line on this figure, # have the legend only contain text without the glyph if len(value) == 1: fig.legend.glyph_width = 0 return indicator_figs
def _plot_indicators(): """Strategy indicators""" def _too_many_dims(value): assert value.ndim >= 2 if value.ndim > 2: warnings.warn("Can't plot indicators with >2D ('{}')".format( value.name), stacklevel=5) return True return False class LegendStr(str): # The legend string is such a string that only matches # itself if it's the exact same object. This ensures # legend items are listed separately even when they have the # same string contents. Otherwise, Bokeh would always consider # equal strings as one and the same legend item. # This also prevents legend items named the same as some # ColumnDataSource's column to be replaced with that column's # values. def __eq__(self, other): return self is other ohlc_colors = colorgen() for value in indicators: value = np.atleast_2d(value) # Use .get()! A user might have assigned a Strategy.data-evolved # _Array without Strategy.I() if not value._opts.get('plot') or _too_many_dims(value): continue tooltips = [] # Overlay indicators on the OHLC figure if value._opts['overlay']: color = value._opts['color'] color = color and _as_list(color)[0] or next(ohlc_colors) legend = LegendStr(value.name) for i, arr in enumerate(value): source_name = '{}_{}'.format(value.name, i) source.add(arr, source_name) if value._opts.get('scatter'): fig_ohlc.scatter('index', source_name, source=source, color=color, line_color='black', fill_alpha=.8, marker='circle', radius=bar_width / 2 * 1.5, legend=legend) else: fig_ohlc.line('index', source_name, source=source, line_width=1.3, line_color=color, legend=legend) ohlc_extreme_values[source_name] = arr tooltips.append( '@{{{}}}{{0,0.0[0000]}}'.format(source_name)) ohlc_tooltips.append((value.name, NBSP.join(tooltips))) else: # Standalone indicator sections at the bottom color = value._opts['color'] color = color and cycle(_as_list(color)) or colorgen() fig = new_indicator_figure() for i, arr in enumerate(value, 1): legend = '{}-{}'.format( value.name, i) if len(value) > 1 else value.name name = legend + '_' # Otherwise fig.line(legend=) is interpreted as col of source # noqa: E501 tooltips.append('@{{{}}}'.format(name)) source.add(arr.astype(int if arr.dtype == bool else float), name) if value._opts.get('scatter'): r = fig.scatter('index', name, source=source, color=next(color), marker='circle', radius=bar_width / 2 * .9, legend=LegendStr(legend)) else: r = fig.line('index', name, source=source, line_color=next(color), line_width=1.3, legend=LegendStr(legend)) # Add dashed centerline just because mean = float(pd.Series(arr).mean()) if not np.isnan(mean) and (abs(mean) < .1 or round( abs(mean), -1) in (50, 100, 200)): fig.add_layout( Span(location=float(mean), dimension='width', line_color='#666666', line_dash='dashed', line_width=.5)) set_tooltips(fig, [(value.name, NBSP.join(tooltips))], vline=True, renderers=[r]) # If the sole indicator line on this figure, # have the legend only contain text without the glyph if len(value) == 1: fig.legend.glyph_width = 0 figs_below_ohlc.append(fig)