def _plot_end_markers(mask, name, color, kwargs): if np.any(mask): customdata = np.stack( (_id[mask], duration[mask], size[mask], exit_fees[mask], pnl[mask], ret[mask], direction[mask], *((self_col.values['position_id'][mask], ) if self.trade_type == TradeType.Trade else ())), axis=1) scatter = go.Scatter( x=self_col.wrapper.index[exit_idx[mask]], y=exit_price[mask], mode='markers', marker=dict(symbol='square', color=color, size=7, line=dict(width=1, color=adjust_lightness(color))), name=name, customdata=customdata, hovertemplate=_id_str + ": %{customdata[0]}" "<br>Date: %{x}" "<br>Duration: %{customdata[1]}" "<br>Avg. Price: %{y}" "<br>Size: %{customdata[2]:.6f}" "<br>Fees: %{customdata[3]:.6f}" "<br>PnL: %{customdata[4]:.6f}" "<br>Return: %{customdata[5]:.2%}" "<br>Direction: %{customdata[6]}" + ("<br>Position Id: %{customdata[7]}" if self.trade_type == TradeType.Trade else '')) scatter.update(**kwargs) fig.add_trace(scatter, row=row, col=col)
def _plot_scatter(mask: tp.Array1d, name: tp.TraceName, color: tp.Any, kwargs: tp.Kwargs) -> None: if np.any(mask): scatter = go.Scatter( x=self_col.wrapper.index[exit_idx[mask]], y=returns[mask] if as_pct else pnl[mask], mode='markers', marker=dict( symbol='circle', color=color, size=marker_size[mask], opacity=opacity[mask], line=dict(width=1, color=adjust_lightness(color)), ), name=name, customdata=np.stack( (_id[mask], pnl[mask] if as_pct else returns[mask]), axis=1), hovertemplate=_id_str + ": %{customdata[0]}" "<br>Date: %{x}" f"<br>PnL: {_pnl_str}" f"<br>Return: {_return_str}") scatter.update(**kwargs) fig.add_trace(scatter, **add_trace_kwargs)
def plot_as_markers(self, y=None, **kwargs): # pragma: no cover """Plot Series as markers. Args: y (array_like): Y-axis values to plot markers on. **kwargs: Keyword arguments passed to `vectorbt.generic.accessors.GenericAccessor.scatterplot`. ## Example ```python-repl >>> ts = pd.Series([1, 2, 3, 2, 1], index=sig.index) >>> fig = ts.vbt.lineplot() >>> sig['b'].vbt.signals.plot_as_entry_markers(y=ts, fig=fig) >>> (~sig['b']).vbt.signals.plot_as_exit_markers(y=ts, fig=fig) ``` ![](/vectorbt/docs/img/signals_plot_as_markers.png) """ from vectorbt.settings import contrast_color_schema if y is None: y = pd.Series.vbt.empty_like(self._obj, 1) return y[self._obj].vbt.scatterplot(**merge_dicts( dict(trace_kwargs=dict(marker=dict( symbol='circle', color=contrast_color_schema['blue'], size=7, line=dict(width=1, color=adjust_lightness( contrast_color_schema['blue']))))), kwargs))
def plot_as_markers(self, ts, name=None, trace_kwargs={}, fig=None, **layout_kwargs): # pragma: no cover """Plot Series as markers. Args: ts (pandas.Series): Time series to plot markers on. !!! note Doesn't plot `ts`. name (str): Name of the signals. trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter`. fig (plotly.graph_objects.Figure): Figure to add traces to. **layout_kwargs: Keyword arguments for layout. Example: ```py ts = pd.Series([1, 2, 3, 2, 1], index=sig.index) fig = ts.vbt.plot() sig['b'].vbt.signals.plot_as_entry_markers(ts, fig=fig) (~sig['b']).vbt.signals.plot_as_exit_markers(ts, fig=fig) ``` ![](/vectorbt/docs/img/signals_plot_as_markers.png)""" checks.assert_type(ts, pd.Series) checks.assert_same_index(self._obj, ts) if fig is None: fig = CustomFigureWidget() fig.update_layout(**layout_kwargs) if name is None: name = self._obj.name # Plot markers scatter = go.Scatter( x=ts.index[self._obj], y=ts[self._obj], mode='markers', marker=dict(symbol='circle', color=contrast_color_schema['blue'], size=7, line=dict(width=1, color=adjust_lightness( contrast_color_schema['blue']))), name=str(name), showlegend=name is not None) scatter.update(**trace_kwargs) fig.add_trace(scatter) return fig
def plot_as_exit_markers(self, *args, name='Exit', trace_kwargs={}, **kwargs): # pragma: no cover """Plot signals as exit markers. See `Signals_SRAccessor.plot_as_markers`.""" trace_kwargs = merge_kwargs(dict( marker=dict( symbol='circle', color=contrast_color_schema['orange'], size=7, line=dict( width=1, color=adjust_lightness(contrast_color_schema['orange']) ) ) ), trace_kwargs) return self.plot_as_markers(*args, name=name, trace_kwargs=trace_kwargs, **kwargs)
def plot_as_exit_markers(self, y=None, **kwargs): # pragma: no cover """Plot signals as exit markers. See `SignalsSRAccessor.plot_as_markers`.""" from vectorbt.settings import contrast_color_schema return self.plot_as_markers( y=y, **merge_dicts( dict(trace_kwargs=dict(marker=dict( symbol='triangle-down', color=contrast_color_schema['red'], size=8, line=dict(width=1, color=adjust_lightness( contrast_color_schema['red']))), name='Exit')), kwargs))
def plot_as_entry_markers(self, *args, name='Entry', trace_kwargs=None, **kwargs): # pragma: no cover """Plot signals as entry markers. See `Signals_SRAccessor.plot_as_markers`.""" from vectorbt.settings import contrast_color_schema if trace_kwargs is None: trace_kwargs = {} trace_kwargs = merge_dicts(dict( marker=dict( symbol='triangle-up', color=contrast_color_schema['green'], size=8, line=dict( width=1, color=adjust_lightness(contrast_color_schema['green']) ) ) ), trace_kwargs) return self.plot_as_markers(*args, name=name, trace_kwargs=trace_kwargs, **kwargs)
def plot_as_entry_markers( self, y: tp.Optional[tp.ArrayLike] = None, **kwargs ) -> tp.Union[tp.BaseFigure, plotting.Scatter]: # pragma: no cover """Plot signals as entry markers. See `SignalsSRAccessor.plot_as_markers`.""" from vectorbt.settings import contrast_color_schema return self.plot_as_markers( y=y, **merge_dicts( dict(trace_kwargs=dict(marker=dict( symbol='triangle-up', color=contrast_color_schema['green'], size=8, line=dict(width=1, color=adjust_lightness( contrast_color_schema['green']))), name='Entry')), kwargs))
def plot_as_markers( self, y: tp.Optional[tp.ArrayLike] = None, **kwargs ) -> tp.Union[tp.BaseFigure, plotting.Scatter]: # pragma: no cover """Plot Series as markers. Args: y (array_like): Y-axis values to plot markers on. **kwargs: Keyword arguments passed to `vectorbt.generic.accessors.GenericAccessor.scatterplot`. ## Example ```python-repl >>> ts = pd.Series([1, 2, 3, 2, 1], index=sig.index) >>> fig = ts.vbt.lineplot() >>> sig['b'].vbt.signals.plot_as_entry_markers(y=ts, fig=fig) >>> (~sig['b']).vbt.signals.plot_as_exit_markers(y=ts, fig=fig) ``` ![](/vectorbt/docs/img/signals_plot_as_markers.svg) """ from vectorbt._settings import settings plotting_cfg = settings['plotting'] if y is None: y = pd.Series.vbt.empty_like(self._obj, 1) else: y = reshape_fns.to_pd_array(y) return y[self._obj].vbt.scatterplot(**merge_dicts( dict(trace_kwargs=dict(marker=dict( symbol='circle', color=plotting_cfg['contrast_color_schema']['blue'], size=7, line=dict(width=1, color=adjust_lightness( plotting_cfg['contrast_color_schema'] ['blue']))))), kwargs))
def _plot_scatter(mask: tp.Array1d, name: tp.TraceName, color: tp.Any, kwargs: tp.Kwargs) -> None: if np.any(mask): if self_col.trade_type == TradeType.Trade: customdata = np.stack( (self_col.values['id'][mask], self_col.values['position_id'][mask], pnl[mask] if as_pct else returns[mask]), axis=1) hovertemplate = "Trade Id: %{customdata[0]}" \ "<br>Position Id: %{customdata[1]}" \ "<br>Date: %{x}" \ f"<br>PnL: {_pnl_str}" \ f"<br>Return: {_return_str}" else: customdata = np.stack( (self_col.values['id'][mask], pnl[mask] if as_pct else returns[mask]), axis=1) hovertemplate = "Position Id: %{customdata[0]}" \ "<br>Date: %{x}" \ f"<br>PnL: {_pnl_str}" \ f"<br>Return: {_return_str}" scatter = go.Scatter( x=self_col.wrapper.index[exit_idx[mask]], y=returns[mask] if as_pct else pnl[mask], mode='markers', marker=dict( symbol='circle', color=color, size=marker_size[mask], opacity=opacity[mask], line=dict(width=1, color=adjust_lightness(color)), ), name=name, customdata=customdata, hovertemplate=hovertemplate) scatter.update(**kwargs) fig.add_trace(scatter, **add_trace_kwargs)
def plot_end_markers(mask, name, color, kwargs): customdata = np.stack((size[mask], exit_fees[mask], pnl[mask], ret[mask], duration[mask]), axis=1) scatter = go.Scatter(x=self.wrapper.index[exit_idx[mask]], y=exit_price[mask], mode='markers', marker=dict( symbol='circle', color=color, size=7, line=dict(width=1, color=adjust_lightness(color))), name=name, customdata=customdata, hovertemplate="%{x}<br>Price: %{y}" + "<br>Size: %{customdata[0]:.4f}" + "<br>Fees: %{customdata[1]:.2f}" + "<br>PnL: %{customdata[2]:.2f}" + "<br>Return: %{customdata[3]:.2%}" + "<br>Duration: %{customdata[4]}") scatter.update(**kwargs) fig.add_trace(scatter)
def plot_as_exit_markers( self, y: tp.Optional[tp.ArrayLike] = None, **kwargs ) -> tp.Union[tp.BaseFigure, plotting.Scatter]: # pragma: no cover """Plot signals as exit markers. See `SignalsSRAccessor.plot_as_markers`.""" from vectorbt._settings import settings plotting_cfg = settings['plotting'] return self.plot_as_markers( y=y, **merge_dicts( dict(trace_kwargs=dict(marker=dict( symbol='triangle-down', color=plotting_cfg['contrast_color_schema']['red'], size=8, line=dict( width=1, color=adjust_lightness( plotting_cfg['contrast_color_schema']['red']))), name='Exit')), kwargs))
def plot(self, column=None, plot_close=True, close_trace_kwargs=None, buy_trace_kwargs=None, sell_trace_kwargs=None, add_trace_kwargs=None, fig=None, **layout_kwargs): # pragma: no cover """Plot orders. Args: column (str): Name of the column to plot. plot_close (bool): Whether to plot `Orders.close`. close_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `Orders.close`. buy_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for "Buy" markers. sell_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for "Sell" markers. add_trace_kwargs (dict): Keyword arguments passed to `add_trace`. fig (plotly.graph_objects.Figure): Figure to add traces to. **layout_kwargs: Keyword arguments for layout. ## Example ```python-repl >>> orders.plot() ``` ![](/vectorbt/docs/img/orders_plot.png)""" from vectorbt.settings import color_schema, contrast_color_schema self_col = self.select_series(column=column, group_by=False) if close_trace_kwargs is None: close_trace_kwargs = {} close_trace_kwargs = merge_dicts( dict(line_color=color_schema['blue'], name='Close' if self_col.wrapper.name is None else self_col.wrapper.name), close_trace_kwargs) if buy_trace_kwargs is None: buy_trace_kwargs = {} if sell_trace_kwargs is None: sell_trace_kwargs = {} if add_trace_kwargs is None: add_trace_kwargs = {} if fig is None: fig = FigureWidget() fig.update_layout(**layout_kwargs) # Plot close if plot_close: fig = self_col.close.vbt.plot(trace_kwargs=close_trace_kwargs, add_trace_kwargs=add_trace_kwargs, fig=fig) if len(self_col.values) > 0: # Extract information _id = self_col.values['id'] idx = self_col.values['idx'] size = self_col.values['size'] price = self_col.values['price'] fees = self_col.values['fees'] side = self_col.values['side'] # Plot Buy markers buy_mask = side == OrderSide.Buy buy_customdata = np.stack( (_id[buy_mask], size[buy_mask], fees[buy_mask]), axis=1) buy_scatter = go.Scatter( x=self_col.wrapper.index[idx[buy_mask]], y=price[buy_mask], mode='markers', marker=dict(symbol='triangle-up', color=contrast_color_schema['green'], size=8, line=dict(width=1, color=adjust_lightness( contrast_color_schema['green']))), name='Buy', customdata=buy_customdata, hovertemplate="Order Id: %{customdata[0]}" "<br>Date: %{x}" "<br>Price: %{y}" "<br>Size: %{customdata[1]:.6f}" "<br>Fees: %{customdata[2]:.6f}") buy_scatter.update(**buy_trace_kwargs) fig.add_trace(buy_scatter, **add_trace_kwargs) # Plot Sell markers sell_mask = side == OrderSide.Sell sell_customdata = np.stack( (_id[sell_mask], size[sell_mask], fees[sell_mask]), axis=1) sell_scatter = go.Scatter( x=self_col.wrapper.index[idx[sell_mask]], y=price[sell_mask], mode='markers', marker=dict(symbol='triangle-down', color=contrast_color_schema['red'], size=8, line=dict(width=1, color=adjust_lightness( contrast_color_schema['red']))), name='Sell', customdata=sell_customdata, hovertemplate="Order Id: %{customdata[0]}" "<br>Date: %{x}" "<br>Price: %{y}" "<br>Size: %{customdata[1]:.6f}" "<br>Fees: %{customdata[2]:.6f}") sell_scatter.update(**sell_trace_kwargs) fig.add_trace(sell_scatter, **add_trace_kwargs) return fig
def plot(self, column: tp.Optional[tp.Label] = None, top_n: int = 5, plot_zones: bool = True, ts_trace_kwargs: tp.KwargsLike = None, peak_trace_kwargs: tp.KwargsLike = None, valley_trace_kwargs: tp.KwargsLike = None, recovery_trace_kwargs: tp.KwargsLike = None, active_trace_kwargs: tp.KwargsLike = None, decline_shape_kwargs: tp.KwargsLike = None, recovery_shape_kwargs: tp.KwargsLike = None, active_shape_kwargs: tp.KwargsLike = None, add_trace_kwargs: tp.KwargsLike = None, xref: str = 'x', yref: str = 'y', fig: tp.Optional[tp.BaseFigure] = None, **layout_kwargs) -> tp.BaseFigure: # pragma: no cover """Plot drawdowns. Args: column (str): Name of the column to plot. top_n (int): Filter top N drawdown records by maximum drawdown. plot_zones (bool): Whether to plot zones. ts_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `Drawdowns.ts`. peak_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for peak values. valley_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for valley values. recovery_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for recovery values. active_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for active recovery values. decline_shape_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Figure.add_shape` for decline zones. recovery_shape_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Figure.add_shape` for recovery zones. active_shape_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Figure.add_shape` for active recovery zones. add_trace_kwargs (dict): Keyword arguments passed to `add_trace`. xref (str): X coordinate axis. yref (str): Y coordinate axis. fig (Figure or FigureWidget): Figure to add traces to. **layout_kwargs: Keyword arguments for layout. ## Example ```python-repl >>> import vectorbt as vbt >>> from datetime import datetime, timedelta >>> import pandas as pd >>> price = pd.Series([1, 2, 1, 2, 3, 2, 1, 2], name='Price') >>> price.index = [datetime(2020, 1, 1) + timedelta(days=i) for i in range(len(price))] >>> vbt.Drawdowns.from_ts(price, wrapper_kwargs=dict(freq='1 day')).plot() ``` ![](/docs/img/drawdowns_plot.svg) """ from vectorbt._settings import settings plotting_cfg = settings['plotting'] self_col = self.select_one(column=column, group_by=False) if top_n is not None: # Drawdowns is negative, thus top_n becomes bottom_n self_col = self_col.apply_mask( self_col.drawdown.bottom_n_mask(top_n)) if ts_trace_kwargs is None: ts_trace_kwargs = {} ts_trace_kwargs = merge_dicts( dict(line=dict(color=plotting_cfg['color_schema']['blue'])), ts_trace_kwargs) if peak_trace_kwargs is None: peak_trace_kwargs = {} if valley_trace_kwargs is None: valley_trace_kwargs = {} if recovery_trace_kwargs is None: recovery_trace_kwargs = {} if active_trace_kwargs is None: active_trace_kwargs = {} if decline_shape_kwargs is None: decline_shape_kwargs = {} if recovery_shape_kwargs is None: recovery_shape_kwargs = {} if active_shape_kwargs is None: active_shape_kwargs = {} if add_trace_kwargs is None: add_trace_kwargs = {} if fig is None: fig = make_figure() fig.update_layout(**layout_kwargs) y_domain = get_domain(yref, fig) if self_col.ts is not None: fig = self_col.ts.vbt.plot(trace_kwargs=ts_trace_kwargs, add_trace_kwargs=add_trace_kwargs, fig=fig) if self_col.count() > 0: # Extract information id_ = self_col.get_field_arr('id') id_title = self_col.get_field_title('id') peak_idx = self_col.get_map_field_to_index('peak_idx') peak_idx_title = self_col.get_field_title('peak_idx') if self_col.ts is not None: peak_val = self_col.ts.loc[peak_idx] else: peak_val = self_col.get_field_arr('peak_val') peak_val_title = self_col.get_field_title('peak_val') valley_idx = self_col.get_map_field_to_index('valley_idx') valley_idx_title = self_col.get_field_title('valley_idx') if self_col.ts is not None: valley_val = self_col.ts.loc[valley_idx] else: valley_val = self_col.get_field_arr('valley_val') valley_val_title = self_col.get_field_title('valley_val') end_idx = self_col.get_map_field_to_index('end_idx') end_idx_title = self_col.get_field_title('end_idx') if self_col.ts is not None: end_val = self_col.ts.loc[end_idx] else: end_val = self_col.get_field_arr('end_val') end_val_title = self_col.get_field_title('end_val') drawdown = self_col.drawdown.values recovery_return = self_col.recovery_return.values decline_duration = np.vectorize(str)(self_col.wrapper.to_timedelta( self_col.decline_duration.values, to_pd=True, silence_warnings=True)) recovery_duration = np.vectorize(str)( self_col.wrapper.to_timedelta( self_col.recovery_duration.values, to_pd=True, silence_warnings=True)) duration = np.vectorize(str)(self_col.wrapper.to_timedelta( self_col.duration.values, to_pd=True, silence_warnings=True)) status = self_col.get_field_arr('status') peak_mask = peak_idx != np.roll( end_idx, 1) # peak and recovery at same time -> recovery wins if peak_mask.any(): # Plot peak markers peak_customdata = id_[peak_mask][:, None] peak_scatter = go.Scatter( x=peak_idx[peak_mask], y=peak_val[peak_mask], mode='markers', marker=dict( symbol='diamond', color=plotting_cfg['contrast_color_schema']['blue'], size=7, line=dict(width=1, color=adjust_lightness( plotting_cfg['contrast_color_schema'] ['blue']))), name='Peak', customdata=peak_customdata, hovertemplate=f"{id_title}: %{{customdata[0]}}" f"<br>{peak_idx_title}: %{{x}}" f"<br>{peak_val_title}: %{{y}}") peak_scatter.update(**peak_trace_kwargs) fig.add_trace(peak_scatter, **add_trace_kwargs) recovered_mask = status == DrawdownStatus.Recovered if recovered_mask.any(): # Plot valley markers valley_customdata = np.stack( (id_[recovered_mask], drawdown[recovered_mask], decline_duration[recovered_mask]), axis=1) valley_scatter = go.Scatter( x=valley_idx[recovered_mask], y=valley_val[recovered_mask], mode='markers', marker=dict( symbol='diamond', color=plotting_cfg['contrast_color_schema']['red'], size=7, line=dict(width=1, color=adjust_lightness( plotting_cfg['contrast_color_schema'] ['red']))), name='Valley', customdata=valley_customdata, hovertemplate=f"{id_title}: %{{customdata[0]}}" f"<br>{valley_idx_title}: %{{x}}" f"<br>{valley_val_title}: %{{y}}" f"<br>Drawdown: %{{customdata[1]:.2%}}" f"<br>Duration: %{{customdata[2]}}") valley_scatter.update(**valley_trace_kwargs) fig.add_trace(valley_scatter, **add_trace_kwargs) if plot_zones: # Plot drawdown zones for i in range(len(id_[recovered_mask])): fig.add_shape(**merge_dicts( dict( type="rect", xref=xref, yref="paper", x0=peak_idx[recovered_mask][i], y0=y_domain[0], x1=valley_idx[recovered_mask][i], y1=y_domain[1], fillcolor='red', opacity=0.2, layer="below", line_width=0, ), decline_shape_kwargs)) # Plot recovery markers recovery_customdata = np.stack( (id_[recovered_mask], recovery_return[recovered_mask], recovery_duration[recovered_mask]), axis=1) recovery_scatter = go.Scatter( x=end_idx[recovered_mask], y=end_val[recovered_mask], mode='markers', marker=dict( symbol='diamond', color=plotting_cfg['contrast_color_schema']['green'], size=7, line=dict(width=1, color=adjust_lightness( plotting_cfg['contrast_color_schema'] ['green']))), name='Recovery/Peak', customdata=recovery_customdata, hovertemplate=f"{id_title}: %{{customdata[0]}}" f"<br>{end_idx_title}: %{{x}}" f"<br>{end_val_title}: %{{y}}" f"<br>Return: %{{customdata[1]:.2%}}" f"<br>Duration: %{{customdata[2]}}") recovery_scatter.update(**recovery_trace_kwargs) fig.add_trace(recovery_scatter, **add_trace_kwargs) if plot_zones: # Plot recovery zones for i in range(len(id_[recovered_mask])): fig.add_shape(**merge_dicts( dict( type="rect", xref=xref, yref="paper", x0=valley_idx[recovered_mask][i], y0=y_domain[0], x1=end_idx[recovered_mask][i], y1=y_domain[1], fillcolor='green', opacity=0.2, layer="below", line_width=0, ), recovery_shape_kwargs)) # Plot active markers active_mask = status == DrawdownStatus.Active if active_mask.any(): active_customdata = np.stack( (id_[active_mask], drawdown[active_mask], duration[active_mask]), axis=1) active_scatter = go.Scatter( x=end_idx[active_mask], y=end_val[active_mask], mode='markers', marker=dict( symbol='diamond', color=plotting_cfg['contrast_color_schema']['orange'], size=7, line=dict(width=1, color=adjust_lightness( plotting_cfg['contrast_color_schema'] ['orange']))), name='Active', customdata=active_customdata, hovertemplate=f"{id_title}: %{{customdata[0]}}" f"<br>{end_idx_title}: %{{x}}" f"<br>{end_val_title}: %{{y}}" f"<br>Return: %{{customdata[1]:.2%}}" f"<br>Duration: %{{customdata[2]}}") active_scatter.update(**active_trace_kwargs) fig.add_trace(active_scatter, **add_trace_kwargs) if plot_zones: # Plot active drawdown zones for i in range(len(id_[active_mask])): fig.add_shape(**merge_dicts( dict( type="rect", xref=xref, yref="paper", x0=peak_idx[active_mask][i], y0=y_domain[0], x1=end_idx[active_mask][i], y1=y_domain[1], fillcolor='orange', opacity=0.2, layer="below", line_width=0, ), active_shape_kwargs)) return fig
def plot(self, main_price_trace_kwargs={}, entry_trace_kwargs={}, exit_trace_kwargs={}, exit_profit_trace_kwargs={}, exit_loss_trace_kwargs={}, active_trace_kwargs={}, profit_shape_kwargs={}, loss_shape_kwargs={}, fig=None, **layout_kwargs): # pragma: no cover """Plot orders. Args: main_price_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for main price. entry_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for "Entry" markers. exit_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for "Exit" markers. exit_profit_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for "Exit - Profit" markers. exit_loss_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for "Exit - Loss" markers. active_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for "Active" markers. profit_shape_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Figure.add_shape` for profit zones. loss_shape_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Figure.add_shape` for loss zones. fig (plotly.graph_objects.Figure): Figure to add traces to. **layout_kwargs: Keyword arguments for layout. Example: ```py import vectorbt as vbt import pandas as pd price = pd.Series([1, 2, 3, 2, 1]) orders = pd.Series([1, -1, 1, -1, 0]) portfolio = vbt.Portfolio.from_orders(price, orders, freq='1D') portfolio.trades.plot() ``` ![](/vectorbt/docs/img/events.png)""" if self.wrapper.ndim > 1: raise TypeError("You must select a column first") # Plot main price fig = self.main_price.vbt.plot(trace_kwargs=main_price_trace_kwargs, fig=fig, **layout_kwargs) # Extract information size = self.records_arr['size'] entry_idx = self.records_arr['entry_idx'] entry_price = self.records_arr['entry_price'] entry_fees = self.records_arr['entry_fees'] exit_idx = self.records_arr['exit_idx'] exit_price = self.records_arr['exit_price'] exit_fees = self.records_arr['exit_fees'] pnl = self.records_arr['pnl'] ret = self.records_arr['return'] status = self.records_arr['status'] def get_duration_str(from_idx, to_idx): if isinstance(self.wrapper.index, DatetimeTypes): duration = self.wrapper.index[to_idx] - self.wrapper.index[ from_idx] elif self.wrapper.freq is not None: duration = self.wrapper.to_time_units(to_idx - from_idx) else: duration = to_idx - from_idx return np.vectorize(str)(duration) duration = get_duration_str(entry_idx, exit_idx) # Plot Entry markers entry_customdata = np.stack((size, entry_fees), axis=1) entry_scatter = go.Scatter( x=self.wrapper.index[entry_idx], y=entry_price, mode='markers', marker=dict(symbol='circle', color=contrast_color_schema['blue'], size=7, line=dict(width=1, color=adjust_lightness( contrast_color_schema['blue']))), name='Entry', customdata=entry_customdata, hovertemplate= "%{x}<br>Price: %{y}<br>Size: %{customdata[0]:.4f}<br>Fees: %{customdata[1]:.2f}" ) entry_scatter.update(**entry_trace_kwargs) fig.add_trace(entry_scatter) # Plot end markers def plot_end_markers(mask, name, color, kwargs): customdata = np.stack((size[mask], exit_fees[mask], pnl[mask], ret[mask], duration[mask]), axis=1) scatter = go.Scatter(x=self.wrapper.index[exit_idx[mask]], y=exit_price[mask], mode='markers', marker=dict( symbol='circle', color=color, size=7, line=dict(width=1, color=adjust_lightness(color))), name=name, customdata=customdata, hovertemplate="%{x}<br>Price: %{y}" + "<br>Size: %{customdata[0]:.4f}" + "<br>Fees: %{customdata[1]:.2f}" + "<br>PnL: %{customdata[2]:.2f}" + "<br>Return: %{customdata[3]:.2%}" + "<br>Duration: %{customdata[4]}") scatter.update(**kwargs) fig.add_trace(scatter) # Plot Exit markers plot_end_markers((status == EventStatus.Closed) & (pnl == 0.), 'Exit', contrast_color_schema['gray'], exit_trace_kwargs) # Plot Exit - Profit markers plot_end_markers((status == EventStatus.Closed) & (pnl > 0.), 'Exit - Profit', contrast_color_schema['green'], exit_profit_trace_kwargs) # Plot Exit - Loss markers plot_end_markers((status == EventStatus.Closed) & (pnl < 0.), 'Exit - Loss', contrast_color_schema['red'], exit_loss_trace_kwargs) # Plot Active markers plot_end_markers(status == EventStatus.Open, 'Active', contrast_color_schema['orange'], active_trace_kwargs) # Plot profit zones profit_mask = pnl > 0. for i in np.flatnonzero(profit_mask): fig.add_shape(**merge_kwargs( dict( type="rect", xref="x", yref="y", x0=self.wrapper.index[entry_idx[i]], y0=entry_price[i], x1=self.wrapper.index[exit_idx[i]], y1=exit_price[i], fillcolor='green', opacity=0.15, layer="below", line_width=0, ), profit_shape_kwargs)) # Plot loss zones loss_mask = pnl < 0. for i in np.flatnonzero(loss_mask): fig.add_shape(**merge_kwargs( dict( type="rect", xref="x", yref="y", x0=self.wrapper.index[entry_idx[i]], y0=entry_price[i], x1=self.wrapper.index[exit_idx[i]], y1=exit_price[i], fillcolor='red', opacity=0.15, layer="below", line_width=0, ), loss_shape_kwargs)) return fig
def plot_as_markers(self, y=None, name=None, trace_kwargs=None, row=None, col=None, fig=None, **layout_kwargs): # pragma: no cover """Plot Series as markers. Args: y (array_like): Y-axis values to plot markers on. !!! note Doesn't plot `y`. name (str): Name of the signals. trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter`. row (int): Row position. col (int): Column position. fig (plotly.graph_objects.Figure): Figure to add traces to. **layout_kwargs: Keyword arguments for layout. ## Example ```python-repl >>> ts = pd.Series([1, 2, 3, 2, 1], index=sig.index) >>> fig = ts.vbt.plot() >>> sig['b'].vbt.signals.plot_as_entry_markers(y=ts, fig=fig) >>> (~sig['b']).vbt.signals.plot_as_exit_markers(y=ts, fig=fig) ``` ![](/vectorbt/docs/img/signals_plot_as_markers.png) """ from vectorbt.settings import contrast_color_schema if trace_kwargs is None: trace_kwargs = {} if fig is None: fig = CustomFigureWidget() fig.update_layout(**layout_kwargs) if name is None: if 'name' in trace_kwargs: name = trace_kwargs.pop('name') else: name = self._obj.name if name is not None: name = str(name) # Plot markers _y = 1 if y is None else y scatter = go.Scatter( x=self.wrapper.index, y=np.where(self._obj, _y, np.nan), mode='markers', marker=dict( symbol='circle', color=contrast_color_schema['blue'], size=7, line=dict( width=1, color=adjust_lightness(contrast_color_schema['blue']) ) ), name=name, showlegend=name is not None ) scatter.update(**trace_kwargs) fig.add_trace(scatter, row=row, col=col) return fig
def plot(self, column: tp.Optional[tp.Label] = None, top_n: int = 5, plot_zones: bool = True, ts_trace_kwargs: tp.KwargsLike = None, start_trace_kwargs: tp.KwargsLike = None, end_trace_kwargs: tp.KwargsLike = None, open_shape_kwargs: tp.KwargsLike = None, closed_shape_kwargs: tp.KwargsLike = None, add_trace_kwargs: tp.KwargsLike = None, xref: str = 'x', yref: str = 'y', fig: tp.Optional[tp.BaseFigure] = None, **layout_kwargs) -> tp.BaseFigure: # pragma: no cover """Plot ranges. Args: column (str): Name of the column to plot. top_n (int): Filter top N range records by maximum duration. plot_zones (bool): Whether to plot zones. ts_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `Ranges.ts`. start_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for start values. end_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for end values. open_shape_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Figure.add_shape` for open zones. closed_shape_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Figure.add_shape` for closed zones. add_trace_kwargs (dict): Keyword arguments passed to `add_trace`. xref (str): X coordinate axis. yref (str): Y coordinate axis. fig (Figure or FigureWidget): Figure to add traces to. **layout_kwargs: Keyword arguments for layout. ## Example ```python-repl >>> import vectorbt as vbt >>> from datetime import datetime, timedelta >>> import pandas as pd >>> price = pd.Series([1, 2, 1, 2, 3, 2, 1, 2], name='Price') >>> price.index = [datetime(2020, 1, 1) + timedelta(days=i) for i in range(len(price))] >>> vbt.Ranges.from_ts(price >= 2, wrapper_kwargs=dict(freq='1 day')).plot() ``` ![](/docs/img/ranges_plot.svg) """ from vectorbt._settings import settings plotting_cfg = settings['plotting'] self_col = self.select_one(column=column, group_by=False) if top_n is not None: self_col = self_col.apply_mask(self_col.duration.top_n_mask(top_n)) if ts_trace_kwargs is None: ts_trace_kwargs = {} ts_trace_kwargs = merge_dicts( dict(line=dict(color=plotting_cfg['color_schema']['blue'])), ts_trace_kwargs) if start_trace_kwargs is None: start_trace_kwargs = {} if end_trace_kwargs is None: end_trace_kwargs = {} if open_shape_kwargs is None: open_shape_kwargs = {} if closed_shape_kwargs is None: closed_shape_kwargs = {} if add_trace_kwargs is None: add_trace_kwargs = {} if fig is None: fig = make_figure() fig.update_layout(**layout_kwargs) y_domain = get_domain(yref, fig) if self_col.ts is not None: fig = self_col.ts.vbt.plot(trace_kwargs=ts_trace_kwargs, add_trace_kwargs=add_trace_kwargs, fig=fig) if self_col.count() > 0: # Extract information id_ = self_col.get_field_arr('id') id_title = self_col.get_field_title('id') start_idx = self_col.get_map_field_to_index('start_idx') start_idx_title = self_col.get_field_title('start_idx') if self_col.ts is not None: start_val = self_col.ts.loc[start_idx] else: start_val = np.full(len(start_idx), 0) end_idx = self_col.get_map_field_to_index('end_idx') end_idx_title = self_col.get_field_title('end_idx') if self_col.ts is not None: end_val = self_col.ts.loc[end_idx] else: end_val = np.full(len(end_idx), 0) duration = np.vectorize(str)(self_col.wrapper.to_timedelta( self_col.duration.values, to_pd=True, silence_warnings=True)) status = self_col.get_field_arr('status') # Plot start markers start_customdata = id_[:, None] start_scatter = go.Scatter( x=start_idx, y=start_val, mode='markers', marker=dict( symbol='diamond', color=plotting_cfg['contrast_color_schema']['blue'], size=7, line=dict( width=1, color=adjust_lightness( plotting_cfg['contrast_color_schema']['blue']))), name='Start', customdata=start_customdata, hovertemplate=f"{id_title}: %{{customdata[0]}}" f"<br>{start_idx_title}: %{{x}}") start_scatter.update(**start_trace_kwargs) fig.add_trace(start_scatter, **add_trace_kwargs) closed_mask = status == RangeStatus.Closed if closed_mask.any(): # Plot end markers closed_end_customdata = np.stack( (id_[closed_mask], duration[closed_mask]), axis=1) closed_end_scatter = go.Scatter( x=end_idx[closed_mask], y=end_val[closed_mask], mode='markers', marker=dict( symbol='diamond', color=plotting_cfg['contrast_color_schema']['green'], size=7, line=dict(width=1, color=adjust_lightness( plotting_cfg['contrast_color_schema'] ['green']))), name='Closed', customdata=closed_end_customdata, hovertemplate=f"{id_title}: %{{customdata[0]}}" f"<br>{end_idx_title}: %{{x}}" f"<br>Duration: %{{customdata[1]}}") closed_end_scatter.update(**end_trace_kwargs) fig.add_trace(closed_end_scatter, **add_trace_kwargs) if plot_zones: # Plot closed range zones for i in range(len(id_[closed_mask])): fig.add_shape(**merge_dicts( dict( type="rect", xref=xref, yref="paper", x0=start_idx[closed_mask][i], y0=y_domain[0], x1=end_idx[closed_mask][i], y1=y_domain[1], fillcolor='teal', opacity=0.2, layer="below", line_width=0, ), closed_shape_kwargs)) open_mask = status == RangeStatus.Open if open_mask.any(): # Plot end markers open_end_customdata = np.stack( (id_[open_mask], duration[open_mask]), axis=1) open_end_scatter = go.Scatter( x=end_idx[open_mask], y=end_val[open_mask], mode='markers', marker=dict( symbol='diamond', color=plotting_cfg['contrast_color_schema']['orange'], size=7, line=dict(width=1, color=adjust_lightness( plotting_cfg['contrast_color_schema'] ['orange']))), name='Open', customdata=open_end_customdata, hovertemplate=f"{id_title}: %{{customdata[0]}}" f"<br>{end_idx_title}: %{{x}}" f"<br>Duration: %{{customdata[1]}}") open_end_scatter.update(**end_trace_kwargs) fig.add_trace(open_end_scatter, **add_trace_kwargs) if plot_zones: # Plot open range zones for i in range(len(id_[open_mask])): fig.add_shape(**merge_dicts( dict( type="rect", xref=xref, yref="paper", x0=start_idx[open_mask][i], y0=y_domain[0], x1=end_idx[open_mask][i], y1=y_domain[1], fillcolor='orange', opacity=0.2, layer="below", line_width=0, ), open_shape_kwargs)) return fig
def plot(self, ts_trace_kwargs={}, peak_trace_kwargs={}, valley_trace_kwargs={}, recovery_trace_kwargs={}, active_trace_kwargs={}, ptv_shape_kwargs={}, vtr_shape_kwargs={}, active_shape_kwargs={}, fig=None, **layout_kwargs): # pragma: no cover """Plot drawdowns over `Drawdowns.ts`. Args: ts_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for time series. peak_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for peak values. valley_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for valley values. recovery_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for recovery values. active_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for active recovery values. ptv_shape_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Figure.add_shape` for PtV zones. vtr_shape_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Figure.add_shape` for VtR zones. active_shape_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Figure.add_shape` for active VtR zones. fig (plotly.graph_objects.Figure): Figure to add traces to. **layout_kwargs: Keyword arguments for layout. Example: ```py import vectorbt as vbt import pandas as pd ts = pd.Series([1, 2, 1, 2, 3, 2, 1, 2]) vbt.records.Drawdowns.from_ts(ts, freq='1 days').plot() ``` ![](/vectorbt/docs/img/drawdowns.png)""" if self.wrapper.ndim > 1: raise TypeError("You must select a column first") fig = self.ts.vbt.plot(trace_kwargs=ts_trace_kwargs, fig=fig, **layout_kwargs) if self.records_arr.shape[0] == 0: return fig # Extract information start_idx = self.records_arr['start_idx'] valley_idx = self.records_arr['valley_idx'] end_idx = self.records_arr['end_idx'] status = self.records_arr['status'] start_val = self.ts.values[start_idx] valley_val = self.ts.values[valley_idx] end_val = self.ts.values[end_idx] def get_duration_str(from_idx, to_idx): if isinstance(self.wrapper.index, DatetimeTypes): duration = self.wrapper.index[to_idx] - self.wrapper.index[from_idx] elif self.wrapper.freq is not None: duration = self.wrapper.to_time_units(to_idx - from_idx) else: duration = to_idx - from_idx return np.vectorize(str)(duration) # Plot peak markers and zones peak_mask = start_idx != np.roll(end_idx, 1) # peak and recovery at same time -> recovery wins peak_scatter = go.Scatter( x=self.ts.index[start_idx[peak_mask]], y=start_val[peak_mask], mode='markers', marker=dict( symbol='circle', color=contrast_color_schema['blue'], size=7, line=dict( width=1, color=adjust_lightness(contrast_color_schema['blue']) ) ), name='Peak' ) peak_scatter.update(**peak_trace_kwargs) fig.add_trace(peak_scatter) recovery_mask = status == DrawdownStatus.Recovered if np.any(recovery_mask): # Plot valley markers and zones valley_drawdown = (valley_val[recovery_mask] - start_val[recovery_mask]) / start_val[recovery_mask] valley_duration = get_duration_str(start_idx[recovery_mask], valley_idx[recovery_mask]) valley_customdata = np.stack((valley_drawdown, valley_duration), axis=1) valley_scatter = go.Scatter( x=self.ts.index[valley_idx[recovery_mask]], y=valley_val[recovery_mask], mode='markers', marker=dict( symbol='circle', color=contrast_color_schema['red'], size=7, line=dict( width=1, color=adjust_lightness(contrast_color_schema['red']) ) ), name='Valley', customdata=valley_customdata, hovertemplate="(%{x}, %{y})<br>Drawdown: %{customdata[0]:.2%}<br>Duration: %{customdata[1]}" ) valley_scatter.update(**valley_trace_kwargs) fig.add_trace(valley_scatter) for i in np.flatnonzero(recovery_mask): fig.add_shape(**merge_kwargs(dict( type="rect", xref="x", yref="paper", x0=self.ts.index[start_idx[i]], y0=0, x1=self.ts.index[valley_idx[i]], y1=1, fillcolor='red', opacity=0.15, layer="below", line_width=0, ), ptv_shape_kwargs)) # Plot recovery markers and zones recovery_return = (end_val[recovery_mask] - valley_val[recovery_mask]) / valley_val[recovery_mask] recovery_duration = get_duration_str(valley_idx[recovery_mask], end_idx[recovery_mask]) recovery_customdata = np.stack((recovery_return, recovery_duration), axis=1) recovery_scatter = go.Scatter( x=self.ts.index[end_idx[recovery_mask]], y=end_val[recovery_mask], mode='markers', marker=dict( symbol='circle', color=contrast_color_schema['green'], size=7, line=dict( width=1, color=adjust_lightness(contrast_color_schema['green']) ) ), name='Recovery/Peak', customdata=recovery_customdata, hovertemplate="(%{x}, %{y})<br>Return: %{customdata[0]:.2%}<br>Duration: %{customdata[1]}" ) recovery_scatter.update(**recovery_trace_kwargs) fig.add_trace(recovery_scatter) for i in np.flatnonzero(recovery_mask): fig.add_shape(**merge_kwargs(dict( type="rect", xref="x", yref="paper", x0=self.ts.index[valley_idx[i]], y0=0, x1=self.ts.index[end_idx[i]], y1=1, fillcolor='green', opacity=0.15, layer="below", line_width=0, ), vtr_shape_kwargs)) # Plot active markers and zones active_mask = ~recovery_mask if np.any(active_mask): active_drawdown = (valley_val[active_mask] - start_val[active_mask]) / start_val[active_mask] active_duration = get_duration_str(valley_idx[active_mask], end_idx[active_mask]) active_customdata = np.stack((active_drawdown, active_duration), axis=1) active_scatter = go.Scatter( x=self.ts.index[end_idx[active_mask]], y=end_val[active_mask], mode='markers', marker=dict( symbol='circle', color=contrast_color_schema['orange'], size=7, line=dict( width=1, color=adjust_lightness(contrast_color_schema['orange']) ) ), name='Active', customdata=active_customdata, hovertemplate="(%{x}, %{y})<br>Drawdown: %{customdata[0]:.2%}<br>Duration: %{customdata[1]}" ) active_scatter.update(**active_trace_kwargs) fig.add_trace(active_scatter) for i in np.flatnonzero(active_mask): fig.add_shape(**merge_kwargs(dict( type="rect", xref="x", yref="paper", x0=self.ts.index[start_idx[i]], y0=0, x1=self.ts.index[end_idx[i]], y1=1, fillcolor='orange', opacity=0.15, layer="below", line_width=0, ), active_shape_kwargs)) return fig
def plot(self, column=None, plot_close=True, plot_zones=True, close_trace_kwargs=None, entry_trace_kwargs=None, exit_trace_kwargs=None, exit_profit_trace_kwargs=None, exit_loss_trace_kwargs=None, active_trace_kwargs=None, profit_shape_kwargs=None, loss_shape_kwargs=None, row=None, col=None, xref='x', yref='y', fig=None, **layout_kwargs): # pragma: no cover """Plot orders. Args: column (str): Name of the column to plot. plot_close (bool): Whether to plot `Trades.close`. plot_zones (bool): Whether to plot zones. Set to False if there are many trades within one position. close_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `Trades.close`. entry_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for "Entry" markers. exit_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for "Exit" markers. exit_profit_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for "Exit - Profit" markers. exit_loss_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for "Exit - Loss" markers. active_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for "Active" markers. profit_shape_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Figure.add_shape` for profit zones. loss_shape_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Figure.add_shape` for loss zones. row (int): Row position. col (int): Column position. xref (str): X coordinate axis. yref (str): Y coordinate axis. fig (plotly.graph_objects.Figure): Figure to add traces to. **layout_kwargs: Keyword arguments for layout. ## Example ```python-repl >>> trades.plot() ``` ![](/vectorbt/docs/img/trades_plot.png)""" from vectorbt.settings import color_schema, contrast_color_schema self_col = self.select_series(column=column, group_by=False) if close_trace_kwargs is None: close_trace_kwargs = {} close_trace_kwargs = merge_dicts(dict( line_color=color_schema['blue'], name='Close' if self_col.wrapper.name is None else self_col.wrapper.name ), close_trace_kwargs) if entry_trace_kwargs is None: entry_trace_kwargs = {} if exit_trace_kwargs is None: exit_trace_kwargs = {} if exit_profit_trace_kwargs is None: exit_profit_trace_kwargs = {} if exit_loss_trace_kwargs is None: exit_loss_trace_kwargs = {} if active_trace_kwargs is None: active_trace_kwargs = {} if profit_shape_kwargs is None: profit_shape_kwargs = {} if loss_shape_kwargs is None: loss_shape_kwargs = {} if fig is None: fig = CustomFigureWidget() fig.update_layout(**layout_kwargs) # Plot close if plot_close: fig = self_col.close.vbt.plot(trace_kwargs=close_trace_kwargs, row=row, col=col, fig=fig) if len(self_col.values) > 0: # Extract information _id = self_col.values['id'] _id_str = 'Trade Id' if self.trade_type == TradeType.Trade else 'Position Id' size = self_col.values['size'] entry_idx = self_col.values['entry_idx'] entry_price = self_col.values['entry_price'] entry_fees = self_col.values['entry_fees'] exit_idx = self_col.values['exit_idx'] exit_price = self_col.values['exit_price'] exit_fees = self_col.values['exit_fees'] pnl = self_col.values['pnl'] ret = self_col.values['return'] direction_value_map = to_value_map(TradeDirection) direction = self_col.values['direction'] direction = np.vectorize(lambda x: str(direction_value_map[x]))(direction) status = self_col.values['status'] def get_duration_str(from_idx, to_idx): if isinstance(self_col.wrapper.index, DatetimeTypes): duration = self_col.wrapper.index[to_idx] - self_col.wrapper.index[from_idx] elif self_col.wrapper.freq is not None: duration = self_col.wrapper.to_time_units(to_idx - from_idx) else: duration = to_idx - from_idx return np.vectorize(str)(duration) duration = get_duration_str(entry_idx, exit_idx) if len(entry_idx) > 0: # Plot Entry markers entry_customdata = np.stack(( _id, size, entry_fees, direction, *((self_col.values['position_id'],) if self.trade_type == TradeType.Trade else ()) ), axis=1) entry_scatter = go.Scatter( x=self_col.wrapper.index[entry_idx], y=entry_price, mode='markers', marker=dict( symbol='square', color=contrast_color_schema['blue'], size=7, line=dict( width=1, color=adjust_lightness(contrast_color_schema['blue']) ) ), name='Entry', customdata=entry_customdata, hovertemplate=_id_str + ": %{customdata[0]}" "<br>Date: %{x}" "<br>Avg. Price: %{y}" "<br>Size: %{customdata[1]:.6f}" "<br>Fees: %{customdata[2]:.6f}" "<br>Direction: %{customdata[3]}" + ("<br>Position Id: %{customdata[4]}" if self.trade_type == TradeType.Trade else '') ) entry_scatter.update(**entry_trace_kwargs) fig.add_trace(entry_scatter, row=row, col=col) # Plot end markers def _plot_end_markers(mask, name, color, kwargs): if np.any(mask): customdata = np.stack(( _id[mask], duration[mask], size[mask], exit_fees[mask], pnl[mask], ret[mask], direction[mask], *((self_col.values['position_id'][mask],) if self.trade_type == TradeType.Trade else ()) ), axis=1) scatter = go.Scatter( x=self_col.wrapper.index[exit_idx[mask]], y=exit_price[mask], mode='markers', marker=dict( symbol='square', color=color, size=7, line=dict( width=1, color=adjust_lightness(color) ) ), name=name, customdata=customdata, hovertemplate=_id_str + ": %{customdata[0]}" "<br>Date: %{x}" "<br>Duration: %{customdata[1]}" "<br>Avg. Price: %{y}" "<br>Size: %{customdata[2]:.6f}" "<br>Fees: %{customdata[3]:.6f}" "<br>PnL: %{customdata[4]:.6f}" "<br>Return: %{customdata[5]:.2%}" "<br>Direction: %{customdata[6]}" + ("<br>Position Id: %{customdata[7]}" if self.trade_type == TradeType.Trade else '') ) scatter.update(**kwargs) fig.add_trace(scatter, row=row, col=col) # Plot Exit markers _plot_end_markers( (status == TradeStatus.Closed) & (pnl == 0.), 'Exit', contrast_color_schema['gray'], exit_trace_kwargs ) # Plot Exit - Profit markers _plot_end_markers( (status == TradeStatus.Closed) & (pnl > 0.), 'Exit - Profit', contrast_color_schema['green'], exit_profit_trace_kwargs ) # Plot Exit - Loss markers _plot_end_markers( (status == TradeStatus.Closed) & (pnl < 0.), 'Exit - Loss', contrast_color_schema['red'], exit_loss_trace_kwargs ) # Plot Active markers _plot_end_markers( status == TradeStatus.Open, 'Active', contrast_color_schema['orange'], active_trace_kwargs ) if plot_zones: profit_mask = pnl > 0. if np.any(profit_mask): # Plot profit zones for i in np.flatnonzero(profit_mask): fig.add_shape(**merge_dicts(dict( type="rect", xref=xref, yref=yref, x0=self_col.wrapper.index[entry_idx[i]], y0=entry_price[i], x1=self_col.wrapper.index[exit_idx[i]], y1=exit_price[i], fillcolor='green', opacity=0.2, layer="below", line_width=0, ), profit_shape_kwargs)) loss_mask = pnl < 0. if np.any(loss_mask): # Plot loss zones for i in np.flatnonzero(loss_mask): fig.add_shape(**merge_dicts(dict( type="rect", xref=xref, yref=yref, x0=self_col.wrapper.index[entry_idx[i]], y0=entry_price[i], x1=self_col.wrapper.index[exit_idx[i]], y1=exit_price[i], fillcolor='red', opacity=0.2, layer="below", line_width=0, ), loss_shape_kwargs)) return fig
def plot_pnl(self, column=None, marker_size_range=[7, 14], opacity_range=[0.75, 0.9], closed_profit_trace_kwargs=None, closed_loss_trace_kwargs=None, open_trace_kwargs=None, hline_shape_kwargs=None, row=None, col=None, xref='x', yref='y', fig=None, **layout_kwargs): # pragma: no cover """Plot trade PnL. Args: column (str): Name of the column to plot. closed_profit_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for "Closed - Profit" markers. closed_loss_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for "Closed - Loss" markers. open_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for "Open" markers. hline_shape_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Figure.add_shape` for zeroline. row (int): Row position. col (int): Column position. xref (str): X coordinate axis. yref (str): Y coordinate axis. fig (plotly.graph_objects.Figure): Figure to add traces to. **layout_kwargs: Keyword arguments for layout. ## Example ```python-repl >>> trades.plot_pnl() ``` ![](/vectorbt/docs/img/trades_plot_pnl.png) """ from vectorbt.settings import contrast_color_schema self_col = self.select_series(column=column, group_by=False) if closed_profit_trace_kwargs is None: closed_profit_trace_kwargs = {} if closed_loss_trace_kwargs is None: closed_loss_trace_kwargs = {} if open_trace_kwargs is None: open_trace_kwargs = {} if hline_shape_kwargs is None: hline_shape_kwargs = {} marker_size_range = tuple(marker_size_range) if fig is None: fig = CustomFigureWidget() fig.update_layout(**layout_kwargs) x_domain = [0, 1] xaxis = 'xaxis' + xref[1:] if xaxis in fig.layout: if 'domain' in fig.layout[xaxis]: if fig.layout[xaxis]['domain'] is not None: x_domain = fig.layout[xaxis]['domain'] if len(self_col.values) > 0: # Extract information _id = self.values['id'] _id_str = 'Trade Id' if self.trade_type == TradeType.Trade else 'Position Id' exit_idx = self.values['exit_idx'] pnl = self.values['pnl'] returns = self.values['return'] status = self.values['status'] neutral_mask = pnl == 0 profit_mask = pnl > 0 loss_mask = pnl < 0 marker_size = min_rel_rescale(np.abs(returns), marker_size_range) opacity = max_rel_rescale(np.abs(returns), opacity_range) open_mask = status == TradeStatus.Open closed_profit_mask = (~open_mask) & profit_mask closed_loss_mask = (~open_mask) & loss_mask open_mask &= ~neutral_mask if np.any(closed_profit_mask): # Plot Profit markers profit_scatter = go.Scatter( x=self_col.wrapper.index[exit_idx[closed_profit_mask]], y=pnl[closed_profit_mask], mode='markers', marker=dict( symbol='circle', color=contrast_color_schema['green'], size=marker_size[closed_profit_mask], opacity=opacity[closed_profit_mask], line=dict( width=1, color=adjust_lightness(contrast_color_schema['green']) ), ), name='Closed - Profit', customdata=np.stack((_id[closed_profit_mask], returns[closed_profit_mask]), axis=1), hovertemplate=_id_str + ": %{customdata[0]}" "<br>Date: %{x}" "<br>PnL: %{y}" "<br>Return: %{customdata[1]:.2%}" ) profit_scatter.update(**closed_profit_trace_kwargs) fig.add_trace(profit_scatter, row=row, col=col) if np.any(closed_loss_mask): # Plot Loss markers loss_scatter = go.Scatter( x=self_col.wrapper.index[exit_idx[closed_loss_mask]], y=pnl[closed_loss_mask], mode='markers', marker=dict( symbol='circle', color=contrast_color_schema['red'], size=marker_size[closed_loss_mask], opacity=opacity[closed_loss_mask], line=dict( width=1, color=adjust_lightness(contrast_color_schema['red']) ) ), name='Closed - Loss', customdata=np.stack((_id[closed_loss_mask], returns[closed_loss_mask]), axis=1), hovertemplate=_id_str + ": %{customdata[0]}" "<br>Date: %{x}" "<br>PnL: %{y}" "<br>Return: %{customdata[1]:.2%}" ) loss_scatter.update(**closed_loss_trace_kwargs) fig.add_trace(loss_scatter, row=row, col=col) if np.any(open_mask): # Plot Active markers active_scatter = go.Scatter( x=self_col.wrapper.index[exit_idx[open_mask]], y=pnl[open_mask], mode='markers', marker=dict( symbol='circle', color=contrast_color_schema['orange'], size=marker_size[open_mask], opacity=opacity[open_mask], line=dict( width=1, color=adjust_lightness(contrast_color_schema['orange']) ) ), name='Open', customdata=np.stack((_id[open_mask], returns[open_mask]), axis=1), hovertemplate=_id_str + ": %{customdata[0]}" "<br>Date: %{x}" "<br>PnL: %{y}" "<br>Return: %{customdata[1]:.2%}" ) active_scatter.update(**open_trace_kwargs) fig.add_trace(active_scatter, row=row, col=col) # Plot zeroline fig.add_shape(**merge_dicts(dict( type='line', xref="paper", yref=yref, x0=x_domain[0], y0=0, x1=x_domain[1], y1=0, line=dict( color="gray", dash="dash", ) ), hline_shape_kwargs)) return fig
def plot(self, column=None, ref_price_trace_kwargs=None, buy_trace_kwargs=None, sell_trace_kwargs=None, fig=None, **layout_kwargs): # pragma: no cover """Plot orders. Args: column (str): Name of the column to plot. ref_price_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for main price. buy_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for "Buy" markers. sell_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for "Sell" markers. fig (plotly.graph_objects.Figure): Figure to add traces to. **layout_kwargs: Keyword arguments for layout. Example: ```py portfolio.orders.plot() ``` ![](/vectorbt/docs/img/orders.png)""" if column is not None: if self.wrapper.grouper.group_by is None: self_col = self[column] else: self_col = self.copy(wrapper=self.wrapper.copy(group_by=None))[column] else: self_col = self if self_col.wrapper.ndim > 1: raise TypeError("Select a column first. Use indexing or column argument.") if ref_price_trace_kwargs is None: ref_price_trace_kwargs = {} if buy_trace_kwargs is None: buy_trace_kwargs = {} if sell_trace_kwargs is None: sell_trace_kwargs = {} # Plot main price fig = self_col.close.vbt.plot(trace_kwargs=ref_price_trace_kwargs, fig=fig, **layout_kwargs) # Extract information idx = self_col.records_arr['idx'] size = self_col.records_arr['size'] price = self_col.records_arr['price'] fees = self_col.records_arr['fees'] side = self_col.records_arr['side'] # Plot Buy markers buy_mask = side == OrderSide.Buy buy_customdata = np.stack((size[buy_mask], fees[buy_mask]), axis=1) buy_scatter = go.Scatter( x=self_col.wrapper.index[idx[buy_mask]], y=price[buy_mask], mode='markers', marker=dict( symbol='circle', color=contrast_color_schema['green'], size=7, line=dict( width=1, color=adjust_lightness(contrast_color_schema['green']) ) ), name='Buy', customdata=buy_customdata, hovertemplate="%{x}<br>Price: %{y}<br>Size: %{customdata[0]:.4f}<br>Fees: %{customdata[1]:.4f}" ) buy_scatter.update(**buy_trace_kwargs) fig.add_trace(buy_scatter) # Plot Sell markers sell_mask = side == OrderSide.Sell sell_customdata = np.stack((size[sell_mask], fees[sell_mask]), axis=1) sell_scatter = go.Scatter( x=self_col.wrapper.index[idx[sell_mask]], y=price[sell_mask], mode='markers', marker=dict( symbol='circle', color=contrast_color_schema['orange'], size=7, line=dict( width=1, color=adjust_lightness(contrast_color_schema['orange']) ) ), name='Sell', customdata=sell_customdata, hovertemplate="%{x}<br>Price: %{y}<br>Size: %{customdata[0]:.4f}<br>Fees: %{customdata[1]:.4f}" ) sell_scatter.update(**sell_trace_kwargs) fig.add_trace(sell_scatter) return fig
def plot(self, column=None, top_n=5, plot_ts=True, plot_zones=True, ts_trace_kwargs=None, peak_trace_kwargs=None, valley_trace_kwargs=None, recovery_trace_kwargs=None, active_trace_kwargs=None, ptv_shape_kwargs=None, vtr_shape_kwargs=None, active_shape_kwargs=None, row=None, col=None, xref='x', yref='y', fig=None, **layout_kwargs): # pragma: no cover """Plot drawdowns over `Drawdowns.ts`. Args: column (str): Name of the column to plot. top_n (int): Filter top N drawdown records by maximum drawdown. plot_ts (bool): Whether to plot time series. plot_zones (bool): Whether to plot zones. ts_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for time series. peak_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for peak values. valley_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for valley values. recovery_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for recovery values. active_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for active recovery values. ptv_shape_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Figure.add_shape` for PtV zones. vtr_shape_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Figure.add_shape` for VtR zones. active_shape_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Figure.add_shape` for active VtR zones. row (int): Row position. col (int): Column position. xref (str): X coordinate axis. yref (str): Y coordinate axis. fig (plotly.graph_objects.Figure): Figure to add traces to. **layout_kwargs: Keyword arguments for layout. ## Example ```python-repl >>> import vectorbt as vbt >>> import pandas as pd >>> ts = pd.Series([1, 2, 1, 2, 3, 2, 1, 2]) >>> vbt.Drawdowns.from_ts(ts, freq='1 days').plot() ``` ![](/vectorbt/docs/img/drawdowns_plot.png) """ from vectorbt.settings import color_schema, contrast_color_schema self_col = self.select_series(column=column, group_by=False) if top_n is not None: # Drawdowns is negative, thus top_n becomes bottom_n self_col = self_col.filter_by_mask( self_col.drawdown.bottom_n_mask(top_n)) if ts_trace_kwargs is None: ts_trace_kwargs = {} ts_trace_kwargs = merge_dicts(dict(line_color=color_schema['blue']), ts_trace_kwargs) if peak_trace_kwargs is None: peak_trace_kwargs = {} if valley_trace_kwargs is None: valley_trace_kwargs = {} if recovery_trace_kwargs is None: recovery_trace_kwargs = {} if active_trace_kwargs is None: active_trace_kwargs = {} if ptv_shape_kwargs is None: ptv_shape_kwargs = {} if vtr_shape_kwargs is None: vtr_shape_kwargs = {} if active_shape_kwargs is None: active_shape_kwargs = {} if fig is None: fig = CustomFigureWidget() fig.update_layout(**layout_kwargs) y_domain = [0, 1] yaxis = 'yaxis' + yref[1:] if yaxis in fig.layout: if 'domain' in fig.layout[yaxis]: if fig.layout[yaxis]['domain'] is not None: y_domain = fig.layout[yaxis]['domain'] if plot_ts: fig = self_col.ts.vbt.plot(trace_kwargs=ts_trace_kwargs, row=row, col=col, fig=fig) if len(self_col.values) > 0: # Extract information _id = self_col.values['id'] start_idx = self_col.values['start_idx'] valley_idx = self_col.values['valley_idx'] end_idx = self_col.values['end_idx'] status = self_col.values['status'] start_val = self_col.ts.values[start_idx] valley_val = self_col.ts.values[valley_idx] end_val = self_col.ts.values[end_idx] def get_duration_str(from_idx, to_idx): if isinstance(self_col.wrapper.index, DatetimeTypes): duration = self_col.wrapper.index[ to_idx] - self_col.wrapper.index[from_idx] elif self_col.wrapper.freq is not None: duration = self_col.wrapper.to_time_units(to_idx - from_idx) else: duration = to_idx - from_idx return np.vectorize(str)(duration) # Plot peak markers peak_mask = start_idx != np.roll( end_idx, 1) # peak and recovery at same time -> recovery wins if np.any(peak_mask): peak_customdata = _id[peak_mask][:, None] peak_scatter = go.Scatter( x=self_col.ts.index[start_idx[peak_mask]], y=start_val[peak_mask], mode='markers', marker=dict(symbol='diamond', color=contrast_color_schema['blue'], size=7, line=dict(width=1, color=adjust_lightness( contrast_color_schema['blue']))), name='Peak', customdata=peak_customdata, hovertemplate="Drawdown Id: %{customdata[0]}" "<br>Date: %{x}" "<br>Price: %{y}") peak_scatter.update(**peak_trace_kwargs) fig.add_trace(peak_scatter, row=row, col=col) recovery_mask = status == DrawdownStatus.Recovered if np.any(recovery_mask): # Plot valley markers valley_drawdown = ( valley_val[recovery_mask] - start_val[recovery_mask]) / start_val[recovery_mask] valley_duration = get_duration_str(start_idx[recovery_mask], valley_idx[recovery_mask]) valley_customdata = np.stack( (_id[recovery_mask], valley_drawdown, valley_duration), axis=1) valley_scatter = go.Scatter( x=self_col.ts.index[valley_idx[recovery_mask]], y=valley_val[recovery_mask], mode='markers', marker=dict(symbol='diamond', color=contrast_color_schema['red'], size=7, line=dict(width=1, color=adjust_lightness( contrast_color_schema['red']))), name='Valley', customdata=valley_customdata, hovertemplate="Drawdown Id: %{customdata[0]}" "<br>Date: %{x}" "<br>Price: %{y}" "<br>Drawdown: %{customdata[1]:.2%}" "<br>Duration: %{customdata[2]}") valley_scatter.update(**valley_trace_kwargs) fig.add_trace(valley_scatter, row=row, col=col) if plot_zones: # Plot drawdown zones for i in np.flatnonzero(recovery_mask): fig.add_shape(**merge_dicts( dict( type="rect", xref=xref, yref="paper", x0=self_col.ts.index[start_idx[i]], y0=y_domain[0], x1=self_col.ts.index[valley_idx[i]], y1=y_domain[1], fillcolor='red', opacity=0.2, layer="below", line_width=0, ), ptv_shape_kwargs)) # Plot recovery markers recovery_return = ( end_val[recovery_mask] - valley_val[recovery_mask]) / valley_val[recovery_mask] recovery_duration = get_duration_str(valley_idx[recovery_mask], end_idx[recovery_mask]) recovery_customdata = np.stack( (_id[recovery_mask], recovery_return, recovery_duration), axis=1) recovery_scatter = go.Scatter( x=self_col.ts.index[end_idx[recovery_mask]], y=end_val[recovery_mask], mode='markers', marker=dict(symbol='diamond', color=contrast_color_schema['green'], size=7, line=dict( width=1, color=adjust_lightness( contrast_color_schema['green']))), name='Recovery/Peak', customdata=recovery_customdata, hovertemplate="Drawdown Id: %{customdata[0]}" "<br>Date: %{x}" "<br>Price: %{y}" "<br>Return: %{customdata[1]:.2%}" "<br>Duration: %{customdata[2]}") recovery_scatter.update(**recovery_trace_kwargs) fig.add_trace(recovery_scatter, row=row, col=col) if plot_zones: # Plot recovery zones for i in np.flatnonzero(recovery_mask): fig.add_shape(**merge_dicts( dict( type="rect", xref=xref, yref="paper", x0=self_col.ts.index[valley_idx[i]], y0=y_domain[0], x1=self_col.ts.index[end_idx[i]], y1=y_domain[1], fillcolor='green', opacity=0.2, layer="below", line_width=0, ), vtr_shape_kwargs)) # Plot active markers active_mask = ~recovery_mask if np.any(active_mask): active_drawdown = ( valley_val[active_mask] - start_val[active_mask]) / start_val[active_mask] active_duration = get_duration_str(valley_idx[active_mask], end_idx[active_mask]) active_customdata = np.stack( (_id[active_mask], active_drawdown, active_duration), axis=1) active_scatter = go.Scatter( x=self_col.ts.index[end_idx[active_mask]], y=end_val[active_mask], mode='markers', marker=dict(symbol='diamond', color=contrast_color_schema['orange'], size=7, line=dict( width=1, color=adjust_lightness( contrast_color_schema['orange']))), name='Active', customdata=active_customdata, hovertemplate="Drawdown Id: %{customdata[0]}" "<br>Date: %{x}" "<br>Price: %{y}" "<br>Drawdown: %{customdata[1]:.2%}" "<br>Duration: %{customdata[2]}") active_scatter.update(**active_trace_kwargs) fig.add_trace(active_scatter, row=row, col=col) if plot_zones: # Plot active drawdown zones for i in np.flatnonzero(active_mask): fig.add_shape(**merge_dicts( dict( type="rect", xref=xref, yref="paper", x0=self_col.ts.index[start_idx[i]], y0=y_domain[0], x1=self_col.ts.index[end_idx[i]], y1=y_domain[1], fillcolor='orange', opacity=0.2, layer="below", line_width=0, ), active_shape_kwargs)) return fig
def plot(self, column: tp.Optional[tp.Label] = None, close_trace_kwargs: tp.KwargsLike = None, buy_trace_kwargs: tp.KwargsLike = None, sell_trace_kwargs: tp.KwargsLike = None, add_trace_kwargs: tp.KwargsLike = None, fig: tp.Optional[tp.BaseFigure] = None, **layout_kwargs) -> tp.BaseFigure: # pragma: no cover """Plot orders. Args: column (str): Name of the column to plot. close_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `Orders.close`. buy_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for "Buy" markers. sell_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for "Sell" markers. add_trace_kwargs (dict): Keyword arguments passed to `add_trace`. fig (Figure or FigureWidget): Figure to add traces to. **layout_kwargs: Keyword arguments for layout. ## Example ```python-repl >>> import pandas as pd >>> from datetime import datetime, timedelta >>> import vectorbt as vbt >>> price = pd.Series([1., 2., 3., 2., 1.], name='Price') >>> price.index = [datetime(2020, 1, 1) + timedelta(days=i) for i in range(len(price))] >>> size = pd.Series([1., 1., 1., 1., -1.]) >>> orders = vbt.Portfolio.from_orders(price, size).orders >>> orders.plot() ``` ![](/docs/img/orders_plot.svg)""" from vectorbt._settings import settings plotting_cfg = settings['plotting'] self_col = self.select_one(column=column, group_by=False) if close_trace_kwargs is None: close_trace_kwargs = {} close_trace_kwargs = merge_dicts(dict( line=dict( color=plotting_cfg['color_schema']['blue'] ), name='Close' ), close_trace_kwargs) if buy_trace_kwargs is None: buy_trace_kwargs = {} if sell_trace_kwargs is None: sell_trace_kwargs = {} if add_trace_kwargs is None: add_trace_kwargs = {} if fig is None: fig = make_figure() fig.update_layout(**layout_kwargs) # Plot price if self_col.close is not None: fig = self_col.close.vbt.plot(trace_kwargs=close_trace_kwargs, add_trace_kwargs=add_trace_kwargs, fig=fig) if self_col.count() > 0: # Extract information id_ = self_col.get_field_arr('id') id_title = self_col.get_field_title('id') idx = self_col.get_map_field_to_index('idx') idx_title = self_col.get_field_title('idx') size = self_col.get_field_arr('size') size_title = self_col.get_field_title('size') fees = self_col.get_field_arr('fees') fees_title = self_col.get_field_title('fees') price = self_col.get_field_arr('price') price_title = self_col.get_field_title('price') side = self_col.get_field_arr('side') buy_mask = side == OrderSide.Buy if buy_mask.any(): # Plot buy markers buy_customdata = np.stack(( id_[buy_mask], size[buy_mask], fees[buy_mask] ), axis=1) buy_scatter = go.Scatter( x=idx[buy_mask], y=price[buy_mask], mode='markers', marker=dict( symbol='triangle-up', color=plotting_cfg['contrast_color_schema']['green'], size=8, line=dict( width=1, color=adjust_lightness(plotting_cfg['contrast_color_schema']['green']) ) ), name='Buy', customdata=buy_customdata, hovertemplate=f"{id_title}: %{{customdata[0]}}" f"<br>{idx_title}: %{{x}}" f"<br>{price_title}: %{{y}}" f"<br>{size_title}: %{{customdata[1]:.6f}}" f"<br>{fees_title}: %{{customdata[2]:.6f}}" ) buy_scatter.update(**buy_trace_kwargs) fig.add_trace(buy_scatter, **add_trace_kwargs) sell_mask = side == OrderSide.Sell if sell_mask.any(): # Plot sell markers sell_customdata = np.stack(( id_[sell_mask], size[sell_mask], fees[sell_mask] ), axis=1) sell_scatter = go.Scatter( x=idx[sell_mask], y=price[sell_mask], mode='markers', marker=dict( symbol='triangle-down', color=plotting_cfg['contrast_color_schema']['red'], size=8, line=dict( width=1, color=adjust_lightness(plotting_cfg['contrast_color_schema']['red']) ) ), name='Sell', customdata=sell_customdata, hovertemplate=f"{id_title}: %{{customdata[0]}}" f"<br>{idx_title}: %{{x}}" f"<br>{price_title}: %{{y}}" f"<br>{size_title}: %{{customdata[1]:.6f}}" f"<br>{fees_title}: %{{customdata[2]:.6f}}" ) sell_scatter.update(**sell_trace_kwargs) fig.add_trace(sell_scatter, **add_trace_kwargs) return fig