|
23 | 23 | CrosshairTool,
|
24 | 24 | CustomJS,
|
25 | 25 | ColumnDataSource,
|
| 26 | + CustomJSTransform, |
26 | 27 | Label, NumeralTickFormatter,
|
27 | 28 | Span,
|
28 | 29 | HoverTool,
|
|
39 | 40 | from bokeh.io.state import curstate
|
40 | 41 | from bokeh.layouts import gridplot
|
41 | 42 | from bokeh.palettes import Category10
|
42 |
| -from bokeh.transform import factor_cmap |
| 43 | +from bokeh.transform import factor_cmap, transform |
43 | 44 |
|
44 | 45 | from backtesting._util import _data_period, _as_list, _Indicator, try_
|
45 | 46 |
|
@@ -258,7 +259,6 @@ def plot(*, results: pd.Series,
|
258 | 259 | trade_source = ColumnDataSource(dict(
|
259 | 260 | index=trades['ExitBar'],
|
260 | 261 | datetime=trades['ExitTime'],
|
261 |
| - exit_price=trades['ExitPrice'], |
262 | 262 | size=trades['Size'],
|
263 | 263 | returns_positive=(trades['ReturnPct'] > 0).astype(int).astype(str),
|
264 | 264 | ))
|
@@ -426,31 +426,24 @@ def _plot_pl_section():
|
426 | 426 | """Profit/Loss markers section"""
|
427 | 427 | fig = new_indicator_figure(y_axis_label="Profit / Loss")
|
428 | 428 | fig.add_layout(Span(location=0, dimension='width', line_color='#666666',
|
429 |
| - line_dash='dashed', line_width=1)) |
430 |
| - returns_long = np.where(trades['Size'] > 0, trades['ReturnPct'], np.nan) |
431 |
| - returns_short = np.where(trades['Size'] < 0, trades['ReturnPct'], np.nan) |
| 429 | + line_dash='dashed', level='underlay', line_width=1)) |
| 430 | + trade_source.add(trades['ReturnPct'], 'returns') |
432 | 431 | size = trades['Size'].abs()
|
433 | 432 | size = np.interp(size, (size.min(), size.max()), (8, 20))
|
434 |
| - trade_source.add(returns_long, 'returns_long') |
435 |
| - trade_source.add(returns_short, 'returns_short') |
436 | 433 | trade_source.add(size, 'marker_size')
|
437 | 434 | if 'count' in trades:
|
438 | 435 | trade_source.add(trades['count'], 'count')
|
439 | 436 | trade_source.add(trades[['EntryBar', 'ExitBar']].values.tolist(), 'lines')
|
440 |
| - trade_source.add([[0, r] for r in trades['ReturnPct'].values], 'returns_both') |
441 |
| - fig.multi_line(xs='lines', ys='returns_both', |
442 |
| - source=trade_source, color='#bbb', line_width=1) |
443 |
| - r1 = fig.scatter('index', 'returns_long', source=trade_source, fill_color=cmap, |
444 |
| - marker='triangle', line_color='black', size='marker_size') |
445 |
| - r2 = fig.scatter('index', 'returns_short', source=trade_source, fill_color=cmap, |
446 |
| - marker='inverted_triangle', line_color='black', size='marker_size') |
| 437 | + fig.multi_line(xs='lines', |
| 438 | + ys=transform('returns', CustomJSTransform(v_func='return [...xs].map(i => [0, i]);')), |
| 439 | + source=trade_source, color='#999', line_width=1) |
| 440 | + r1 = fig.scatter('index', 'returns', source=trade_source, fill_color=cmap, |
| 441 | + marker='circle', line_color='black', size='marker_size') |
447 | 442 | tooltips = [("Size", "@size{0,0}")]
|
448 | 443 | if 'count' in trades:
|
449 | 444 | tooltips.append(("Count", "@count{0,0}"))
|
450 |
| - set_tooltips(fig, tooltips + [("P/L", "@returns_long{+0.[000]%}")], |
| 445 | + set_tooltips(fig, tooltips + [("P/L", "@returns{+0.[000]%}")], |
451 | 446 | vline=False, renderers=[r1])
|
452 |
| - set_tooltips(fig, tooltips + [("P/L", "@returns_short{+0.[000]%}")], |
453 |
| - vline=False, renderers=[r2]) |
454 | 447 | fig.yaxis.formatter = NumeralTickFormatter(format="0.[00]%")
|
455 | 448 | return fig
|
456 | 449 |
|
@@ -619,7 +612,7 @@ def __eq__(self, other):
|
619 | 612 | round(abs(mean), -1) in (50, 100, 200)):
|
620 | 613 | fig.add_layout(Span(location=float(mean), dimension='width',
|
621 | 614 | line_color='#666666', line_dash='dashed',
|
622 |
| - line_width=.5)) |
| 615 | + level='underlay', line_width=.5)) |
623 | 616 | if is_overlay:
|
624 | 617 | ohlc_tooltips.append((tooltip_label, NBSP.join(tooltips)))
|
625 | 618 | else:
|
|
0 commit comments