Esempio n. 1
0
    def plot(self,
             tr_trace_kwargs={},
             atr_trace_kwargs={},
             fig=None,
             **layout_kwargs):  # pragma: no cover
        """Plot `ATR.tr` and `ATR.atr`.

        Args:
            tr_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `ATR.tr`.
            atr_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `ATR.atr`.
            fig (plotly.graph_objects.Figure): Figure to add traces to.
            **layout_kwargs: Keyword arguments for layout.
        Example:
            ```py
            atr[(10, False)].plot()
            ```

            ![](/vectorbt/docs/img/ATR.png)"""
        if self.wrapper.ndim > 1:
            raise TypeError("Select a column first. Use indexing.")

        tr_trace_kwargs = merge_kwargs(dict(name=f'TR ({self.short_name})'),
                                       tr_trace_kwargs)
        atr_trace_kwargs = merge_kwargs(dict(name=f'ATR ({self.short_name})'),
                                        atr_trace_kwargs)

        fig = self.tr.vbt.plot(trace_kwargs=tr_trace_kwargs,
                               fig=fig,
                               **layout_kwargs)
        fig = self.atr.vbt.plot(trace_kwargs=atr_trace_kwargs,
                                fig=fig,
                                **layout_kwargs)

        return fig
Esempio n. 2
0
    def plot(self,
             close_trace_kwargs={},
             ma_trace_kwargs={},
             fig=None,
             **layout_kwargs):  # pragma: no cover
        """Plot `MA.ma` against `MA.close`.

        Args:
            close_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `MA.close`.
            ma_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `MA.ma`.
            fig (plotly.graph_objects.Figure): Figure to add traces to.
            **layout_kwargs: Keyword arguments for layout.
        Example:
            ```py
            ma[(10, False)].plot()
            ```

            ![](/vectorbt/docs/img/MA.png)"""
        if self.wrapper.ndim > 1:
            raise TypeError("Select a column first. Use indexing.")

        close_trace_kwargs = merge_kwargs(
            dict(name=f'Close ({self.short_name})'), close_trace_kwargs)
        ma_trace_kwargs = merge_kwargs(dict(name=f'MA ({self.short_name})'),
                                       ma_trace_kwargs)

        fig = self.close.vbt.plot(trace_kwargs=close_trace_kwargs,
                                  fig=fig,
                                  **layout_kwargs)
        fig = self.ma.vbt.plot(trace_kwargs=ma_trace_kwargs,
                               fig=fig,
                               **layout_kwargs)

        return fig
Esempio n. 3
0
    def plot(self,
             macd_trace_kwargs=None,
             signal_trace_kwargs=None,
             hist_trace_kwargs=None,
             fig=None,
             **layout_kwargs):  # pragma: no cover
        """Plot `MACD.macd`, `MACD.signal` and `MACD.hist`.

        Args:
            macd_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `MACD.macd`.
            signal_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `MACD.signal`.
            hist_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Bar` for `MACD.hist`.
            fig (plotly.graph_objects.Figure): Figure to add traces to.
            **layout_kwargs: Keyword arguments for layout.
        Example:
            ```python-repl
            >>> vbt.MACD.run(price['Close']).plot()
            ```

            ![](/vectorbt/docs/img/MACD.png)"""
        if self.wrapper.ndim > 1:
            raise TypeError("Select a column first. Use indexing.")

        if macd_trace_kwargs is None:
            macd_trace_kwargs = {}
        if signal_trace_kwargs is None:
            signal_trace_kwargs = {}
        if hist_trace_kwargs is None:
            hist_trace_kwargs = {}
        macd_trace_kwargs = merge_kwargs(dict(name='MACD'), macd_trace_kwargs)
        signal_trace_kwargs = merge_kwargs(dict(name='Signal'),
                                           signal_trace_kwargs)
        hist_trace_kwargs = merge_kwargs(dict(name='Histogram'),
                                         hist_trace_kwargs)

        layout_kwargs = merge_kwargs(dict(bargap=0), layout_kwargs)
        fig = self.macd.vbt.plot(trace_kwargs=macd_trace_kwargs,
                                 fig=fig,
                                 **layout_kwargs)
        fig = self.signal.vbt.plot(trace_kwargs=signal_trace_kwargs,
                                   fig=fig,
                                   **layout_kwargs)

        # Plot hist
        hist = self.hist.values
        hist_diff = generic_nb.diff_1d_nb(hist)
        marker_colors = np.full(hist.shape, 'silver', dtype=np.object)
        marker_colors[(hist > 0) & (hist_diff > 0)] = 'green'
        marker_colors[(hist > 0) & (hist_diff <= 0)] = 'lightgreen'
        marker_colors[(hist < 0) & (hist_diff < 0)] = 'red'
        marker_colors[(hist < 0) & (hist_diff >= 0)] = 'lightcoral'

        hist_bar = go.Bar(x=self.hist.index,
                          y=self.hist.values,
                          marker_color=marker_colors,
                          marker_line_width=0)
        hist_bar.update(**hist_trace_kwargs)
        fig.add_trace(hist_bar)

        return fig
Esempio n. 4
0
    def plot(self,
             close_trace_kwargs={},
             middle_trace_kwargs={},
             upper_trace_kwargs={},
             lower_trace_kwargs={},
             fig=None,
             **layout_kwargs):  # pragma: no cover
        """Plot `BollingerBands.middle`, `BollingerBands.upper` and `BollingerBands.lower` against
        `BollingerBands.close`.

        Args:
            close_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `BollingerBands.close`.
            middle_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `BollingerBands.middle`.
            upper_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `BollingerBands.upper`.
            lower_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `BollingerBands.lower`.
            fig (plotly.graph_objects.Figure): Figure to add traces to.
            **layout_kwargs: Keyword arguments for layout.
        Example:
            ```py
            bb[(10, False, 2)].plot()
            ```

            ![](/vectorbt/docs/img/BollingerBands.png)"""
        if self.wrapper.ndim > 1:
            raise TypeError("Select a column first. Use indexing.")

        lower_trace_kwargs = merge_kwargs(
            dict(name=f'Lower Band ({self.short_name})',
                 line=dict(color='silver')), lower_trace_kwargs)
        upper_trace_kwargs = merge_kwargs(
            dict(name=f'Upper Band ({self.short_name})',
                 line=dict(color='silver'),
                 fill='tonexty',
                 fillcolor='rgba(128, 128, 128, 0.25)'),
            upper_trace_kwargs)  # default kwargs
        middle_trace_kwargs = merge_kwargs(
            dict(name=f'Middle Band ({self.short_name})',
                 line=dict(color=defaults.layout['colorway'][1])),
            middle_trace_kwargs)
        close_trace_kwargs = merge_kwargs(
            dict(name=f'Close ({self.short_name})',
                 line=dict(color=defaults.layout['colorway'][0])),
            close_trace_kwargs)

        fig = self.lower.vbt.plot(trace_kwargs=lower_trace_kwargs,
                                  fig=fig,
                                  **layout_kwargs)
        fig = self.upper.vbt.plot(trace_kwargs=upper_trace_kwargs,
                                  fig=fig,
                                  **layout_kwargs)
        fig = self.middle.vbt.plot(trace_kwargs=middle_trace_kwargs,
                                   fig=fig,
                                   **layout_kwargs)
        fig = self.close.vbt.plot(trace_kwargs=close_trace_kwargs,
                                  fig=fig,
                                  **layout_kwargs)

        return fig
Esempio n. 5
0
    def plot(self,
             levels=(30, 70),
             percent_k_trace_kwargs={},
             percent_d_trace_kwargs={},
             shape_kwargs={},
             fig=None,
             **layout_kwargs):  # pragma: no cover
        """Plot `Stochastic.percent_k` and `Stochastic.percent_d`.

        Args:
            percent_k_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `Stochastic.percent_k`.
            percent_d_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `Stochastic.percent_d`.
            shape_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Figure.add_shape` for zone between levels.
            fig (plotly.graph_objects.Figure): Figure to add traces to.
            **layout_kwargs: Keyword arguments for layout.
        Example:
            ```py
            stoch[(10, 2, False)].plot(levels=(20, 80))
            ```

            ![](/vectorbt/docs/img/Stochastic.png)"""
        if self.wrapper.ndim > 1:
            raise TypeError("Select a column first. Use indexing.")

        percent_k_trace_kwargs = merge_kwargs(
            dict(name=f'%K ({self.short_name})'), percent_k_trace_kwargs)
        percent_d_trace_kwargs = merge_kwargs(
            dict(name=f'%D ({self.short_name})'), percent_d_trace_kwargs)

        layout_kwargs = merge_kwargs(dict(yaxis=dict(range=[-5, 105])),
                                     layout_kwargs)
        fig = self.percent_k.vbt.plot(trace_kwargs=percent_k_trace_kwargs,
                                      fig=fig,
                                      **layout_kwargs)
        fig = self.percent_d.vbt.plot(trace_kwargs=percent_d_trace_kwargs,
                                      fig=fig,
                                      **layout_kwargs)

        # Plot levels
        # Fill void between levels
        shape_kwargs = merge_kwargs(
            dict(
                type="rect",
                xref="x",
                yref="y",
                x0=self.percent_k.index[0],
                y0=levels[0],
                x1=self.percent_k.index[-1],
                y1=levels[1],
                fillcolor="purple",
                opacity=0.15,
                layer="below",
                line_width=0,
            ), shape_kwargs)
        fig.add_shape(**shape_kwargs)

        return fig
Esempio n. 6
0
    def plot(self,
             macd_trace_kwargs={},
             signal_trace_kwargs={},
             histogram_trace_kwargs={},
             fig=None,
             **layout_kwargs):  # pragma: no cover
        """Plot `MACD.macd`, `MACD.signal` and `MACD.histogram`.

        Args:
            macd_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `MACD.macd`.
            signal_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `MACD.signal`.
            histogram_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Bar` for `MACD.histogram`.
            fig (plotly.graph_objects.Figure): Figure to add traces to.
            **layout_kwargs: Keyword arguments for layout.
        Example:
            ```py
            macd[(10, 20, 30, False, True)].plot()
            ```

            ![](/vectorbt/docs/img/MACD.png)"""
        if self.wrapper.ndim > 1:
            raise TypeError("You must select a column first")

        macd_trace_kwargs = merge_kwargs(dict(
            name=f'MACD ({self.name})'
        ), macd_trace_kwargs)
        signal_trace_kwargs = merge_kwargs(dict(
            name=f'Signal ({self.name})'
        ), signal_trace_kwargs)
        histogram_trace_kwargs = merge_kwargs(dict(
            name=f'Histogram ({self.name})',
            showlegend=False
        ), histogram_trace_kwargs)

        layout_kwargs = merge_kwargs(dict(bargap=0), layout_kwargs)
        fig = self.macd.vbt.plot(trace_kwargs=macd_trace_kwargs, fig=fig, **layout_kwargs)
        fig = self.signal.vbt.plot(trace_kwargs=signal_trace_kwargs, fig=fig, **layout_kwargs)

        # Plot histogram
        hist = self.histogram.values
        hist_diff = generic_nb.diff_1d_nb(hist)
        marker_colors = np.full(hist.shape, 'silver', dtype=np.object)
        marker_colors[(hist > 0) & (hist_diff > 0)] = 'green'
        marker_colors[(hist > 0) & (hist_diff <= 0)] = 'lightgreen'
        marker_colors[(hist < 0) & (hist_diff < 0)] = 'red'
        marker_colors[(hist < 0) & (hist_diff >= 0)] = 'lightcoral'

        histogram_bar = go.Bar(
            x=self.histogram.index,
            y=self.histogram.values,
            marker_color=marker_colors,
            marker_line_width=0
        )
        histogram_bar.update(**histogram_trace_kwargs)
        fig.add_trace(histogram_bar)

        return fig
Esempio n. 7
0
    def plot(self,
             mstd_trace_kwargs={},
             fig=None,
             **layout_kwargs):  # pragma: no cover
        """Plot `MSTD.mstd`.

        Args:
            mstd_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `MSTD.mstd`.
            fig (plotly.graph_objects.Figure): Figure to add traces to.
            **layout_kwargs: Keyword arguments for layout.
        Example:
            ```py
            mstd[(10, False)].plot()
            ```

            ![](/vectorbt/docs/img/MSTD.png)"""
        if self.wrapper.ndim > 1:
            raise TypeError("You must select a column first")

        mstd_trace_kwargs = merge_kwargs(dict(
            name=f'MSTD ({self.name})'
        ), mstd_trace_kwargs)

        fig = self.mstd.vbt.plot(trace_kwargs=mstd_trace_kwargs, fig=fig, **layout_kwargs)

        return fig
Esempio n. 8
0
    def plot(self,
             obv_trace_kwargs={},
             fig=None,
             **layout_kwargs):  # pragma: no cover
        """Plot `OBV.obv`.

        Args:
            obv_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `OBV.obv`.
            fig (plotly.graph_objects.Figure): Figure to add traces to.
            **layout_kwargs: Keyword arguments for layout.
        Example:
            ```py
            obv.plot()
            ```

            ![](/vectorbt/docs/img/OBV.png)"""
        if self.wrapper.ndim > 1:
            raise TypeError("You must select a column first")

        obv_trace_kwargs = merge_kwargs(dict(
            name=f'OBV ({self.name})'
        ), obv_trace_kwargs)

        fig = self.obv.vbt.plot(trace_kwargs=obv_trace_kwargs, fig=fig, **layout_kwargs)

        return fig
Esempio n. 9
0
    def plot(self,
             obv_trace_kwargs=None,
             fig=None,
             **layout_kwargs):  # pragma: no cover
        """Plot `OBV.obv`.

        Args:
            obv_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `OBV.obv`.
            fig (plotly.graph_objects.Figure): Figure to add traces to.
            **layout_kwargs: Keyword arguments for layout.
        Example:
            ```py
            >>> vbt.OBV.run(price['Close'], price['Volume']).plot()
            ```

            ![](/vectorbt/docs/img/OBV.png)"""
        if self.wrapper.ndim > 1:
            raise TypeError("Select a column first. Use indexing.")

        if obv_trace_kwargs is None:
            obv_trace_kwargs = {}
        obv_trace_kwargs = merge_kwargs(dict(name='OBV'), obv_trace_kwargs)

        fig = self.obv.vbt.plot(trace_kwargs=obv_trace_kwargs,
                                fig=fig,
                                **layout_kwargs)

        return fig
Esempio n. 10
0
    def plot(self,
             mstd_trace_kwargs=None,
             fig=None,
             **layout_kwargs):  # pragma: no cover
        """Plot `MSTD.mstd`.

        Args:
            mstd_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `MSTD.mstd`.
            fig (plotly.graph_objects.Figure): Figure to add traces to.
            **layout_kwargs: Keyword arguments for layout.
        Example:
            ```python-repl
            >>> vbt.MSTD.run(price['Close'], 10).plot()
            ```

            ![](/vectorbt/docs/img/MSTD.png)"""
        if self.wrapper.ndim > 1:
            raise TypeError("Select a column first. Use indexing.")

        if mstd_trace_kwargs is None:
            mstd_trace_kwargs = {}
        mstd_trace_kwargs = merge_kwargs(dict(name='MSTD'), mstd_trace_kwargs)

        fig = self.mstd.vbt.plot(trace_kwargs=mstd_trace_kwargs,
                                 fig=fig,
                                 **layout_kwargs)

        return fig
Esempio n. 11
0
    def plot(self,
             levels=(30, 70),
             rsi_trace_kwargs=None,
             fig=None,
             **layout_kwargs):  # pragma: no cover
        """Plot `RSI.rsi`.

        Args:
            levels (tuple): Two extremes: bottom and top.
            rsi_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `RSI.rsi`.
            fig (plotly.graph_objects.Figure): Figure to add traces to.
            **layout_kwargs: Keyword arguments for layout.
        Example:
            ```python-repl
            >>> vbt.RSI.run(price['Close']).plot()
            ```

            ![](/vectorbt/docs/img/RSI.png)"""
        if self.wrapper.ndim > 1:
            raise TypeError("Select a column first. Use indexing.")

        if rsi_trace_kwargs is None:
            rsi_trace_kwargs = {}
        rsi_trace_kwargs = merge_kwargs(dict(name='RSI'), rsi_trace_kwargs)

        layout_kwargs = merge_kwargs(dict(yaxis=dict(range=[-5, 105])),
                                     layout_kwargs)
        fig = self.rsi.vbt.plot(trace_kwargs=rsi_trace_kwargs,
                                fig=fig,
                                **layout_kwargs)

        # Fill void between levels
        fig.add_shape(
            type="rect",
            xref="x",
            yref="y",
            x0=self.rsi.index[0],
            y0=levels[0],
            x1=self.rsi.index[-1],
            y1=levels[1],
            fillcolor="purple",
            opacity=0.15,
            layer="below",
            line_width=0,
        )

        return fig
Esempio n. 12
0
    def plot_trades(self,
                    buy_trace_kwargs={},
                    sell_trace_kwargs={},
                    fig=None,
                    **layout_kwargs):
        """Plot trades as markers.

        Args:
            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
            vbt.Portfolio.from_orders(price, price.diff(), init_capital=100).plot_trades()
            ```

            ![](/vectorbt/docs/img/portfolio_plot_trades.png)"""
        checks.assert_type(self.price, pd.Series)
        checks.assert_type(self.trades, pd.Series)
        sell_mask = self.trades < 0
        buy_mask = self.trades > 0

        # Plot time series
        fig = self.price.vbt.timeseries.plot(fig=fig, **layout_kwargs)
        # Plot markers
        buy_trace_kwargs = merge_kwargs(
            dict(customdata=self.trades[buy_mask],
                 hovertemplate='(%{x}, %{y})<br>%{customdata:.6g}',
                 marker=dict(symbol='triangle-up', color='limegreen')),
            buy_trace_kwargs)
        buy_mask.vbt.signals.plot_markers(self.price,
                                          name='Buy',
                                          trace_kwargs=buy_trace_kwargs,
                                          fig=fig,
                                          **layout_kwargs)
        sell_trace_kwargs = merge_kwargs(
            dict(customdata=self.trades[sell_mask],
                 hovertemplate='(%{x}, %{y})<br>%{customdata:.6g}',
                 marker=dict(symbol='triangle-down', color='orangered')),
            sell_trace_kwargs)
        sell_mask.vbt.signals.plot_markers(self.price,
                                           name='Sell',
                                           trace_kwargs=sell_trace_kwargs,
                                           fig=fig,
                                           **layout_kwargs)
        return fig
Esempio n. 13
0
    def plot(self,
             levels=(30, 70),
             rsi_trace_kwargs={},
             fig=None,
             **layout_kwargs):  # pragma: no cover
        """Plot `RSI.rsi`.

        Args:
            trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `RSI.rsi`.
            fig (plotly.graph_objects.Figure): Figure to add traces to.
            **layout_kwargs: Keyword arguments for layout.
        Example:
            ```py
            rsi[(10, False)].plot()
            ```

            ![](/vectorbt/docs/img/RSI.png)"""
        if self.wrapper.ndim > 1:
            raise TypeError("You must select a column first")

        rsi_trace_kwargs = merge_kwargs(dict(name=f'RSI ({self.short_name})'),
                                        rsi_trace_kwargs)

        layout_kwargs = merge_kwargs(dict(yaxis=dict(range=[-5, 105])),
                                     layout_kwargs)
        fig = self.rsi.vbt.plot(trace_kwargs=rsi_trace_kwargs,
                                fig=fig,
                                **layout_kwargs)

        # Fill void between levels
        fig.add_shape(
            type="rect",
            xref="x",
            yref="y",
            x0=self.rsi.index[0],
            y0=levels[0],
            x1=self.rsi.index[-1],
            y1=levels[1],
            fillcolor="purple",
            opacity=0.15,
            layer="below",
            line_width=0,
        )

        return fig
Esempio n. 14
0
        def plot(_self,
                 entry_y=None,
                 exit_y=None,
                 entry_types=None,
                 exit_types=None,
                 entry_trace_kwargs=None,
                 exit_trace_kwargs=None,
                 fig=None,
                 **layout_kwargs):  # pragma: no cover
            if _self.wrapper.ndim > 1:
                raise TypeError("Select a column first. Use indexing.")

            if entry_trace_kwargs is None:
                entry_trace_kwargs = {}
            if exit_trace_kwargs is None:
                exit_trace_kwargs = {}
            if entry_types is not None:
                entry_trace_kwargs = merge_kwargs(
                    dict(customdata=entry_types,
                         hovertemplate="(%{x}, %{y})<br>Type: %{customdata}"),
                    entry_trace_kwargs)
            if exit_types is not None:
                exit_trace_kwargs = merge_kwargs(
                    dict(customdata=exit_types,
                         hovertemplate="(%{x}, %{y})<br>Type: %{customdata}"),
                    exit_trace_kwargs)
            if exit_only and iteratively:
                entries = _self.new_entries
            else:
                entries = _self.entries
            exits = _self.exits
            fig = entries.vbt.signals.plot_as_entry_markers(
                y=entry_y,
                trace_kwargs=entry_trace_kwargs,
                fig=fig,
                **layout_kwargs)
            fig = exits.vbt.signals.plot_as_exit_markers(
                y=exit_y,
                trace_kwargs=exit_trace_kwargs,
                fig=fig,
                **layout_kwargs)

            return fig
Esempio n. 15
0
 def plot_exit_markers(self, *args, name='Exit', trace_kwargs={}, **kwargs):
     """Plot signals as exit markers.
     
     See `Signals_SRAccessor.plot_markers`."""
     trace_kwargs = merge_kwargs(
         dict(marker=dict(symbol='triangle-down', color='orangered')),
         trace_kwargs)
     return self.plot_markers(*args,
                              name=name,
                              trace_kwargs=trace_kwargs,
                              **kwargs)
Esempio n. 16
0
    def generate_take_profit_exits(self, ts, stops, first=True, iteratively=False, keys=None, broadcast_kwargs={}):
        """Generate take profit exits.

        See `vectorbt.signals.nb.generate_tp_ex_iter_nb` if `iteratively` is `True`, otherwise see
        `vectorbt.signals.nb.generate_tp_ex_nb`.

        Arguments will be broadcasted using `vectorbt.base.reshape_fns.broadcast`
        with `broadcast_kwargs`. Argument `stops` can be either a single number, an array of
        numbers, or a 3D array, where each matrix corresponds to a single configuration.
        Use `keys` as the outermost level.

        Example:
            For each entry in `sig`, set take profit for 10% and 20% above the entry price:
            ```python-repl
            >>> ts = pd.Series([1, 2, 3, 4, 5])
            >>> sig.vbt.signals.generate_take_profit_exits(ts, [0.1, 0.5])
            take_profit                  0.1                  0.5
                             a      b      c      a      b      c
            2020-01-01   False  False  False  False  False  False
            2020-01-02    True   True  False   True   True  False
            2020-01-03   False  False  False  False  False  False
            2020-01-04   False   True   True  False  False  False
            2020-01-05   False  False  False  False  False   True
            ```"""
        entries = self._obj
        checks.assert_type(ts, (pd.Series, pd.DataFrame))

        broadcast_kwargs = merge_kwargs(dict(require_kwargs=dict(requirements='W')), broadcast_kwargs)
        entries, ts = reshape_fns.broadcast(entries, ts, **broadcast_kwargs)
        stops = reshape_fns.broadcast_to_array_of(stops, entries.vbt.to_2d_array())

        # Build column hierarchy
        if keys is not None:
            param_columns = keys
        else:
            param_columns = index_fns.index_from_values(stops, name='take_profit')
        columns = index_fns.combine_indexes(param_columns, entries.vbt.columns)

        # Perform generation
        if iteratively:
            new_entries, exits = nb.generate_tp_ex_iter_nb(
                entries.vbt.to_2d_array(),
                ts.vbt.to_2d_array(),
                stops)
            return entries.vbt.wrap(new_entries, columns=columns), entries.vbt.wrap(exits, columns=columns)
        else:
            exits = nb.generate_tp_ex_nb(
                entries.vbt.to_2d_array(),
                ts.vbt.to_2d_array(),
                stops,
                first=first)
            return entries.vbt.wrap(exits, columns=columns)
Esempio n. 17
0
 def plot_entry_markers(self,
                        *args,
                        name='Entry',
                        trace_kwargs={},
                        **kwargs):
     """Plot signals as entry markers.
     
     See `Signals_SRAccessor.plot_markers`."""
     trace_kwargs = merge_kwargs(
         dict(marker=dict(symbol='triangle-up', color='limegreen')),
         trace_kwargs)
     return self.plot_markers(*args,
                              name=name,
                              trace_kwargs=trace_kwargs,
                              **kwargs)
Esempio n. 18
0
    def plot(self,
             tr_trace_kwargs=None,
             atr_trace_kwargs=None,
             fig=None,
             **layout_kwargs):  # pragma: no cover
        """Plot `ATR.tr` and `ATR.atr`.

        Args:
            tr_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `ATR.tr`.
            atr_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `ATR.atr`.
            fig (plotly.graph_objects.Figure): Figure to add traces to.
            **layout_kwargs: Keyword arguments for layout.
        Example:
            ```python-repl
            >>> vbt.ATR.run(price['High'], price['Low'], price['Close'], 10).plot()
            ```

            ![](/vectorbt/docs/img/ATR.png)"""
        if self.wrapper.ndim > 1:
            raise TypeError("Select a column first. Use indexing.")

        if tr_trace_kwargs is None:
            tr_trace_kwargs = {}
        if atr_trace_kwargs is None:
            atr_trace_kwargs = {}
        tr_trace_kwargs = merge_kwargs(dict(name='TR'), tr_trace_kwargs)
        atr_trace_kwargs = merge_kwargs(dict(name='ATR'), atr_trace_kwargs)

        fig = self.tr.vbt.plot(trace_kwargs=tr_trace_kwargs,
                               fig=fig,
                               **layout_kwargs)
        fig = self.atr.vbt.plot(trace_kwargs=atr_trace_kwargs,
                                fig=fig,
                                **layout_kwargs)

        return fig
Esempio n. 19
0
 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)
Esempio n. 20
0
    def combine_with(self, other, *args, combine_func=None, pass_2d=False, broadcast_kwargs={}, **kwargs):
        """Combine both using `combine_func` into a Series/DataFrame of the same shape.

        All arguments will be broadcasted using `vectorbt.base.reshape_fns.broadcast`
        with `broadcast_kwargs`.

        Arguments `*args` and `**kwargs` will be directly passed to `combine_func`.
        If `pass_2d` is `True`, 2-dimensional NumPy arrays will be passed, otherwise as is.

        !!! note
            The resulted array must have the same shape as broadcasted input arrays.

        Example:
            ```python-repl
            >>> import vectorbt as vbt
            >>> import pandas as pd
            >>> sr = pd.Series([1, 2], index=['x', 'y'])
            >>> df = pd.DataFrame([[3, 4], [5, 6]], index=['x', 'y'], columns=['a', 'b'])

            >>> sr.vbt.combine_with(df, combine_func=lambda x, y: x + y)
               a  b
            x  4  5
            y  7  8
            ```"""
        if isinstance(other, Base_Accessor):
            other = other._obj
        checks.assert_not_none(combine_func)
        if checks.is_numba_func(combine_func):
            # Numba requires writable arrays
            broadcast_kwargs = merge_kwargs(dict(require_kwargs=dict(requirements='W')), broadcast_kwargs)
        new_obj, new_other = reshape_fns.broadcast(self._obj, other, **broadcast_kwargs)
        # Optionally cast to 2d array
        if pass_2d:
            new_obj_arr = reshape_fns.to_2d(new_obj, raw=True)
            new_other_arr = reshape_fns.to_2d(new_other, raw=True)
        else:
            new_obj_arr = np.asarray(new_obj)
            new_other_arr = np.asarray(new_other)
        result = combine_func(new_obj_arr, new_other_arr, *args, **kwargs)
        return new_obj.vbt.wrap(result)
Esempio n. 21
0
    def volume(self,
               x_level=None,
               y_level=None,
               z_level=None,
               x_labels=None,
               y_labels=None,
               z_labels=None,
               slider_level=None,
               slider_labels=None,
               **kwargs):  # pragma: no cover
        """Create a 3D volume figure based on object's multi-index and values.

        If multi-index contains more than three levels or you want them in specific order, pass
        `x_level`, `y_level`, and `z_level`, each (`int` if index or `str` if name) corresponding
        to an axis of the volume. Optionally, pass `slider_level` to use a level as a slider.

        See `vectorbt.generic.plotting.create_volume` for other keyword arguments."""
        (x_level, y_level, z_level), (slider_level, ) = index_fns.pick_levels(
            self.index,
            required_levels=(x_level, y_level, z_level),
            optional_levels=(slider_level, ))

        x_level_vals = self.index.get_level_values(x_level)
        y_level_vals = self.index.get_level_values(y_level)
        z_level_vals = self.index.get_level_values(z_level)
        # Labels are just unique level values
        if x_labels is None:
            x_labels = np.unique(x_level_vals)
        if y_labels is None:
            y_labels = np.unique(y_level_vals)
        if z_labels is None:
            z_labels = np.unique(z_level_vals)

        x_name = x_level_vals.name if x_level_vals.name is not None else 'x'
        y_name = y_level_vals.name if y_level_vals.name is not None else 'y'
        z_name = z_level_vals.name if z_level_vals.name is not None else 'z'
        kwargs = merge_kwargs(
            dict(trace_kwargs=dict(hovertemplate=f"{x_name}: %{{x}}<br>" +
                                   f"{y_name}: %{{y}}<br>" +
                                   f"{z_name}: %{{z}}<br>" +
                                   "value: %{value}<extra></extra>"),
                 scene=dict(xaxis_title=x_level_vals.name,
                            yaxis_title=y_level_vals.name,
                            zaxis_title=z_level_vals.name)), kwargs)

        contains_nans = False
        if slider_level is None:
            # No grouping
            v = self.unstack_to_array(levels=(x_level, y_level, z_level))
            if np.isnan(v).any():
                contains_nans = True
            fig = plotting.create_volume(data=v,
                                         x_labels=x_labels,
                                         y_labels=y_labels,
                                         z_labels=z_labels,
                                         **kwargs)
        else:
            # Requires grouping
            # See https://plotly.com/python/sliders/
            fig = None
            _slider_labels = []
            for i, (name,
                    group) in enumerate(self._obj.groupby(level=slider_level)):
                if slider_labels is not None:
                    name = slider_labels[i]
                _slider_labels.append(name)
                v = group.vbt.unstack_to_array(levels=(x_level, y_level,
                                                       z_level))
                if np.isnan(v).any():
                    contains_nans = True
                _kwargs = merge_kwargs(
                    dict(
                        trace_kwargs=dict(
                            name=str(name) if name is not None else None,
                            visible=False),
                        width=700,
                        height=520,
                    ), kwargs)
                fig = plotting.create_volume(data=v,
                                             x_labels=x_labels,
                                             y_labels=y_labels,
                                             z_labels=z_labels,
                                             fig=fig,
                                             **_kwargs)
            fig.data[0].visible = True
            steps = []
            for i in range(len(fig.data)):
                step = dict(method="update",
                            args=[{
                                "visible": [False] * len(fig.data)
                            }, {}],
                            label=str(_slider_labels[i])
                            if _slider_labels[i] is not None else None)
                step["args"][0]["visible"][i] = True
                steps.append(step)
            prefix = f'{self.index.names[slider_level]}: ' if self.index.names[
                slider_level] is not None else None
            sliders = [
                dict(active=0,
                     currentvalue={"prefix": prefix},
                     pad={"t": 50},
                     steps=steps)
            ]
            fig.update_layout(sliders=sliders)

        if contains_nans:
            warnings.warn(
                "Data contains NaNs. In case of visualization issues, use .show() method on the widget."
            )
        return fig
Esempio n. 22
0
    def heatmap(self,
                x_level=None,
                y_level=None,
                symmetric=False,
                x_labels=None,
                y_labels=None,
                slider_level=None,
                slider_labels=None,
                **kwargs):  # pragma: no cover
        """Create a heatmap figure based on object's multi-index and values.

        If multi-index contains more than two levels or you want them in specific order,
        pass `x_level` and `y_level`, each (`int` if index or `str` if name) corresponding
        to an axis of the heatmap. Optionally, pass `slider_level` to use a level as a slider.

        See `vectorbt.generic.plotting.create_heatmap` for other keyword arguments."""
        (x_level, y_level), (slider_level, ) = index_fns.pick_levels(
            self.index,
            required_levels=(x_level, y_level),
            optional_levels=(slider_level, ))

        x_level_vals = self.index.get_level_values(x_level)
        y_level_vals = self.index.get_level_values(y_level)
        x_name = x_level_vals.name if x_level_vals.name is not None else 'x'
        y_name = y_level_vals.name if y_level_vals.name is not None else 'y'
        kwargs = merge_kwargs(
            dict(trace_kwargs=dict(hovertemplate=f"{x_name}: %{{x}}<br>" +
                                   f"{y_name}: %{{y}}<br>" +
                                   "value: %{z}<extra></extra>"),
                 xaxis_title=x_level_vals.name,
                 yaxis_title=y_level_vals.name), kwargs)

        if slider_level is None:
            # No grouping
            df = self.unstack_to_df(index_levels=x_level,
                                    column_levels=y_level,
                                    symmetric=symmetric)
            fig = df.vbt.heatmap(x_labels=x_labels,
                                 y_labels=y_labels,
                                 **kwargs)
        else:
            # Requires grouping
            # See https://plotly.com/python/sliders/
            fig = None
            _slider_labels = []
            for i, (name,
                    group) in enumerate(self._obj.groupby(level=slider_level)):
                if slider_labels is not None:
                    name = slider_labels[i]
                _slider_labels.append(name)
                df = group.vbt.unstack_to_df(index_levels=x_level,
                                             column_levels=y_level,
                                             symmetric=symmetric)
                if x_labels is None:
                    x_labels = df.columns
                if y_labels is None:
                    y_labels = df.index
                _kwargs = merge_kwargs(
                    dict(
                        trace_kwargs=dict(
                            name=str(name) if name is not None else None,
                            visible=False),
                        width=600,
                        height=520,
                    ), kwargs)
                fig = plotting.create_heatmap(data=df.vbt.to_2d_array(),
                                              x_labels=x_labels,
                                              y_labels=y_labels,
                                              fig=fig,
                                              **_kwargs)
            fig.data[0].visible = True
            steps = []
            for i in range(len(fig.data)):
                step = dict(method="update",
                            args=[{
                                "visible": [False] * len(fig.data)
                            }, {}],
                            label=str(_slider_labels[i])
                            if _slider_labels[i] is not None else None)
                step["args"][0]["visible"][i] = True
                steps.append(step)
            prefix = f'{self.index.names[slider_level]}: ' if self.index.names[
                slider_level] is not None else None
            sliders = [
                dict(active=0,
                     currentvalue={"prefix": prefix},
                     pad={"t": 50},
                     steps=steps)
            ]
            fig.update_layout(sliders=sliders)

        return fig
Esempio n. 23
0
    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
Esempio n. 24
0
    def plot(self,
             levels=(30, 70),
             percent_k_trace_kwargs=None,
             percent_d_trace_kwargs=None,
             shape_kwargs=None,
             fig=None,
             **layout_kwargs):  # pragma: no cover
        """Plot `STOCH.percent_k` and `STOCH.percent_d`.

        Args:
            levels (tuple): Two extremes: bottom and top.
            percent_k_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `STOCH.percent_k`.
            percent_d_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `STOCH.percent_d`.
            shape_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Figure.add_shape` for zone between levels.
            fig (plotly.graph_objects.Figure): Figure to add traces to.
            **layout_kwargs: Keyword arguments for layout.
        Example:
            ```python-repl
            >>> vbt.STOCH.run(price['High'], price['Low'], price['Close']).plot()
            ```

            ![](/vectorbt/docs/img/STOCH.png)"""
        if self.wrapper.ndim > 1:
            raise TypeError("Select a column first. Use indexing.")

        if percent_k_trace_kwargs is None:
            percent_k_trace_kwargs = {}
        if percent_d_trace_kwargs is None:
            percent_d_trace_kwargs = {}
        if shape_kwargs is None:
            shape_kwargs = {}
        percent_k_trace_kwargs = merge_kwargs(dict(name='%K'),
                                              percent_k_trace_kwargs)
        percent_d_trace_kwargs = merge_kwargs(dict(name='%D'),
                                              percent_d_trace_kwargs)

        layout_kwargs = merge_kwargs(dict(yaxis=dict(range=[-5, 105])),
                                     layout_kwargs)
        fig = self.percent_k.vbt.plot(trace_kwargs=percent_k_trace_kwargs,
                                      fig=fig,
                                      **layout_kwargs)
        fig = self.percent_d.vbt.plot(trace_kwargs=percent_d_trace_kwargs,
                                      fig=fig,
                                      **layout_kwargs)

        # Plot levels
        # Fill void between levels
        shape_kwargs = merge_kwargs(
            dict(
                type="rect",
                xref="x",
                yref="y",
                x0=self.percent_k.index[0],
                y0=levels[0],
                x1=self.percent_k.index[-1],
                y1=levels[1],
                fillcolor="purple",
                opacity=0.15,
                layer="below",
                line_width=0,
            ), shape_kwargs)
        fig.add_shape(**shape_kwargs)

        return fig
Esempio n. 25
0
    def plot(self,
             close_trace_kwargs=None,
             middle_trace_kwargs=None,
             upper_trace_kwargs=None,
             lower_trace_kwargs=None,
             fig=None,
             **layout_kwargs):  # pragma: no cover
        """Plot `BBANDS.middle`, `BBANDS.upper` and `BBANDS.lower` against
        `BBANDS.close`.

        Args:
            close_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `BBANDS.close`.
            middle_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `BBANDS.middle`.
            upper_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `BBANDS.upper`.
            lower_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `BBANDS.lower`.
            fig (plotly.graph_objects.Figure): Figure to add traces to.
            **layout_kwargs: Keyword arguments for layout.
        Example:
            ```python-repl
            >>> vbt.BBANDS.run(price['Close']).plot()
            ```

            ![](/vectorbt/docs/img/BBANDS.png)"""
        if self.wrapper.ndim > 1:
            raise TypeError("Select a column first. Use indexing.")

        if close_trace_kwargs is None:
            close_trace_kwargs = {}
        if middle_trace_kwargs is None:
            middle_trace_kwargs = {}
        if upper_trace_kwargs is None:
            upper_trace_kwargs = {}
        if lower_trace_kwargs is None:
            lower_trace_kwargs = {}
        lower_trace_kwargs = merge_kwargs(
            dict(name='Lower Band', line=dict(color='silver')),
            lower_trace_kwargs)
        upper_trace_kwargs = merge_kwargs(
            dict(name='Upper Band',
                 line=dict(color='silver'),
                 fill='tonexty',
                 fillcolor='rgba(128, 128, 128, 0.25)'),
            upper_trace_kwargs)  # default kwargs
        middle_trace_kwargs = merge_kwargs(
            dict(name='Middle Band',
                 line=dict(color=defaults.layout['colorway'][1])),
            middle_trace_kwargs)
        close_trace_kwargs = merge_kwargs(
            dict(name='Close',
                 line=dict(color=defaults.layout['colorway'][0])),
            close_trace_kwargs)

        fig = self.lower.vbt.plot(trace_kwargs=lower_trace_kwargs,
                                  fig=fig,
                                  **layout_kwargs)
        fig = self.upper.vbt.plot(trace_kwargs=upper_trace_kwargs,
                                  fig=fig,
                                  **layout_kwargs)
        fig = self.middle.vbt.plot(trace_kwargs=middle_trace_kwargs,
                                   fig=fig,
                                   **layout_kwargs)
        fig = self.close.vbt.plot(trace_kwargs=close_trace_kwargs,
                                  fig=fig,
                                  **layout_kwargs)

        return fig
Esempio n. 26
0
    def from_choice_func(self,
                         entry_choice_func=None,
                         exit_choice_func=None,
                         cache_func=None,
                         entry_settings=None,
                         exit_settings=None,
                         cache_settings=None,
                         **kwargs):
        """Build signal generator class around entry and exit choice functions.

        A choice function is simply a function that returns indices of signals.
        There are two types of it: entry choice function and exit choice function.
        Each choice function takes broadcast time series, broadcast in-place output time series,
        broadcast parameter arrays, and other arguments, and returns an array of indices
        corresponding to chosen signals. See `vectorbt.signals.nb.generate_nb`.

        If `exit_only` is True, calls `vectorbt.signals.nb.generate_ex_nb`.
        If `exit_only` is False or `iteratively` is True, calls `vectorbt.signals.nb.generate_enex_nb`.

        Args:
            entry_choice_func (callable): `choice_func_nb` that returns indices of entries.

                If `exit_only` is True, automatically set to `vectorbt.signals.nb.first_choice_nb`.
            exit_choice_func (callable): `choice_func_nb` that returns indices of exits.
            cache_func (callable): A caching function to preprocess data beforehand.

                All returned objects will be passed as last arguments to choice functions.
            entry_settings (dict): Settings dict for `entry_choice_func`.
            exit_settings (dict): Settings dict for `exit_choice_func`.
            cache_settings (dict): Settings dict for `cache_func`.
            **kwargs: Keyword arguments passed to `IndicatorFactory.from_custom_func`.

        !!! note
            Choice functions should be Numba-compiled.

            Which inputs, parameters and arguments to pass to each function should be
            explicitly indicated in the function's settings dict. By default, nothing is passed.

        Settings dict of each function can have the following keys:

        Attributes:
            pass_inputs (list of str): Input names to pass to the choice function.

                Defaults to []. Order matters. Each name must be in `input_names`.
            pass_in_outputs (list of str): In-place output names to pass to the choice function.

                Defaults to []. Order matters. Each name must be in `in_output_names`.
            pass_params (list of str): Parameter names to pass to the choice function.

                Defaults to []. Order matters. Each name must be in `param_names`.
            pass_kwargs (list of str or list of tuple): Keyword arguments from `kwargs` dict to
                pass as positional arguments to the choice function.

                Defaults to []. Order matters.

                If any element is a tuple, should contain the name and the default value.
                If any element is a string, the default value is None.

                Built-in keys include:

                * `input_shape`: Input shape if no input time series passed.
                    Default is provided by the pipeline if `forward_input_shape` is True.
                * `wait`: Number of ticks to wait before placing signals.
                    Default is 1.
                * `first`: Whether to stop as soon as the first exit signal is found.
                    Default is True.
                * `temp_idx_arr`: Empty integer array used to temporarily store indices.
                    Default is an automatically generated array of shape `input_shape[0]`.

                    You can also pass `temp_idx_arr1`, `temp_idx_arr2`, etc. to generate multiple.
                * `flex_2d`: See `vectorbt.base.reshape_fns.flex_choose_i_and_col_nb`.
                    Default is provided by the pipeline if `forward_flex_2d` is True.
            pass_cache (bool): Whether to pass cache from `cache_func` to the choice function.

                Defaults to False. Cache is passed unpacked.

        The following arguments can be passed to `run` and `run_combs` methods:

        Args:
            *args: Should be used instead of `exit_args` when `exit_only` is True.
            entry_args (tuple): Arguments passed to the entry choice function.
            exit_args (tuple): Arguments passed to the exit choice function.
            cache_args (tuple): Arguments passed to the cache function.
            entry_kwargs (tuple): Settings for the entry choice function. Also contains arguments
                passed as positional if in `pass_kwargs`.
            exit_kwargs (tuple): Settings for the exit choice function. Also contains arguments
                passed as positional if in `pass_kwargs`.
            cache_kwargs (tuple): Settings for the cache function. Also contains arguments
                passed as positional if in `pass_kwargs`.
            return_cache (bool): Whether to return only cache.
            use_cache (any): Cache to use.
            **kwargs: Should be used instead of `exit_kwargs` when `exit_only` is True.

        For more arguments, see `vectorbt.indicators.factory.run_pipeline`.

        Example:
            Take the first entry and place an exit after waiting `n` ticks. Find the next entry and repeat.
            Test three different `n` values.

            ```python-repl
            >>> from numba import njit
            >>> from vectorbt.signals.factory import SignalFactory

            >>> @njit
            ... def wait_choice_nb(col, from_i, to_i, n, temp_idx_arr):
            ...     temp_idx_arr[0] = from_i + n  # index of next exit
            ...     if temp_idx_arr[0] < to_i:
            ...         return temp_idx_arr[:1]
            ...     return temp_idx_arr[:0]  # must return array anyway

            >>> # Build signal generator
            >>> MySignals = SignalFactory(
            ...     param_names=['n'],
            ...     iteratively=True
            ... ).from_choice_func(
            ...     exit_choice_func=wait_choice_nb,
            ...     exit_settings=dict(
            ...         pass_params=['n'],
            ...         pass_kwargs=['temp_idx_arr']  # built-in kwarg
            ...     )
            ... )

            >>> # Run signal generator
            >>> entries = [True, True, True, True, True]
            >>> my_sig = MySignals.run(entries, [0, 1, 2])

            >>> my_sig.entries  # input entries
            custom_n     0     1     2
            0         True  True  True
            1         True  True  True
            2         True  True  True
            3         True  True  True
            4         True  True  True
            >>> my_sig.new_entries  # output entries
            custom_n      0      1      2
            0          True   True   True
            1         False  False  False
            2          True  False  False
            3         False   True  False
            4          True  False   True
            >>> my_sig.exits  # output exits
            custom_n      0      1      2
            0         False  False  False
            1          True  False  False
            2         False   True  False
            3          True  False   True
            4         False  False  False
            ```

            To combine multiple iterative signals, you would need to create a choice function
            that does that. Here is an example of combining two random generators using "OR" rule:

            ```python-repl
            >>> from numba import njit
            >>> from collections import namedtuple
            >>> from vectorbt.signals.factory import SignalFactory
            >>> from vectorbt.signals.nb import rand_by_prob_choice_nb
            >>> from vectorbt.signals.basic import flex_elem_param_config

            >>> # Enum to distinguish random generators
            >>> RandType = namedtuple('RandType', ['R1', 'R2'])(0, 1)

            >>> # Define exit choice function
            >>> @njit
            ... def rand_exit_choice_nb(col, from_i, to_i, rand_type_out, prob1,
            ...                         prob2, temp_idx_arr1, temp_idx_arr2, flex_2d):
            ...     idxs1 = rand_by_prob_choice_nb(
            ...         col, from_i, to_i, prob1, True, temp_idx_arr1, flex_2d)
            ...     if len(idxs1) > 0:
            ...         to_i = idxs1[0]  # no need to go beyond first signal
            ...     idxs2 = rand_by_prob_choice_nb(
            ...         col, from_i, to_i, prob2, True, temp_idx_arr2, flex_2d)
            ...     if len(idxs2) > 0:
            ...         rand_type_out[idxs2[0], col] = RandType.R2
            ...         return idxs2
            ...     if len(idxs1) > 0:
            ...         rand_type_out[idxs1[0], col] = RandType.R1
            ...         return idxs1
            ...     return temp_idx_arr1[:0]

            >>> # Build signal generator
            >>> MySignals = SignalFactory(
            ...     in_output_names=['rand_type'],
            ...     in_output_settings=dict(
            ...         rand_type=dict(
            ...             dtype=int,
            ...             default=-1
            ...         )
            ...     ),
            ...     param_names=['prob1', 'prob2'],
            ...     param_settings=dict(
            ...         prob1=flex_elem_param_config,  # param per frame/row/col/element
            ...         prob2=flex_elem_param_config
            ...     ),
            ...     attr_settings=dict(
            ...         rand_type=dict(dtype=RandType)  # creates rand_type_readable
            ...     ),
            ...     iteratively=True
            ... ).from_choice_func(
            ...     exit_choice_func=rand_exit_choice_nb,
            ...     exit_settings=dict(
            ...         pass_in_outputs=['rand_type'],
            ...         pass_params=['prob1', 'prob2'],
            ...         pass_kwargs=['temp_idx_arr1', 'temp_idx_arr2', 'flex_2d']
            ...     ),
            ...     forward_flex_2d=True
            ... )

            >>> # Run signal generator
            >>> entries = [True, True, True, True, True]
            >>> my_sig = MySignals.run(entries, [0., 1.], [0., 1.], param_product=True)

            >>> my_sig.new_entries
            custom_prob1           0.0           1.0
            custom_prob2    0.0    1.0    0.0    1.0
            0              True   True   True   True
            1             False  False  False  False
            2             False   True   True   True
            3             False  False  False  False
            4             False   True   True   True
            >>> my_sig.exits
            custom_prob1           0.0           1.0
            custom_prob2    0.0    1.0    0.0    1.0
            0             False  False  False  False
            1             False   True   True   True
            2             False  False  False  False
            3             False   True   True   True
            4             False  False  False  False
            >>> my_sig.rand_type_readable
            custom_prob1     0.0     1.0
            custom_prob2 0.0 1.0 0.0 1.0
            0
            1                 R2  R1  R1
            2
            3                 R2  R1  R1
            4
            ```
        """

        exit_only = self.exit_only
        iteratively = self.iteratively
        input_names = self.input_names
        param_names = self.param_names
        in_output_names = self.in_output_names

        checks.assert_not_none(exit_choice_func)
        checks.assert_numba_func(exit_choice_func)
        if exit_only:
            if iteratively:
                if entry_choice_func is None:
                    entry_choice_func = first_choice_nb
                if entry_settings is None:
                    entry_settings = {}
                entry_settings = merge_kwargs(dict(pass_inputs=['entries']),
                                              entry_settings)
        else:
            checks.assert_not_none(entry_choice_func)
            checks.assert_numba_func(entry_choice_func)

        if entry_settings is None:
            entry_settings = {}
        if exit_settings is None:
            exit_settings = {}
        if cache_settings is None:
            cache_settings = {}

        def _check_settings(func_settings):
            for k in func_settings:
                if k not in ('pass_inputs', 'pass_in_outputs', 'pass_params',
                             'pass_kwargs', 'pass_cache'):
                    raise ValueError(
                        f"Can't find key {k} in function settings")

        _check_settings(entry_settings)
        _check_settings(exit_settings)
        _check_settings(cache_settings)

        # Get input names for each function
        def _get_func_names(func_settings, setting, all_names):
            func_input_names = func_settings.get(setting, None)
            if func_input_names is None:
                return []
            else:
                for name in func_input_names:
                    checks.assert_in(name, all_names)
            return func_input_names

        entry_input_names = _get_func_names(entry_settings, 'pass_inputs',
                                            input_names)
        exit_input_names = _get_func_names(exit_settings, 'pass_inputs',
                                           input_names)
        cache_input_names = _get_func_names(cache_settings, 'pass_inputs',
                                            input_names)

        entry_in_output_names = _get_func_names(entry_settings,
                                                'pass_in_outputs',
                                                in_output_names)
        exit_in_output_names = _get_func_names(exit_settings,
                                               'pass_in_outputs',
                                               in_output_names)
        cache_in_output_names = _get_func_names(cache_settings,
                                                'pass_in_outputs',
                                                in_output_names)

        entry_param_names = _get_func_names(entry_settings, 'pass_params',
                                            param_names)
        exit_param_names = _get_func_names(exit_settings, 'pass_params',
                                           param_names)
        cache_param_names = _get_func_names(cache_settings, 'pass_params',
                                            param_names)

        if exit_only and not iteratively:

            @njit
            def apply_nb(i, entries, exit_wait, exit_input_list,
                         exit_in_output_tuples, exit_param_tuples, exit_args):
                return generate_ex_nb(entries, exit_wait, exit_choice_func,
                                      *exit_input_list,
                                      *exit_in_output_tuples[i],
                                      *exit_param_tuples[i], *exit_args)
        else:

            @njit
            def apply_nb(i, shape, entry_wait, exit_wait, entry_input_list,
                         exit_input_list, entry_in_output_tuples,
                         exit_in_output_tuples, entry_param_tuples,
                         exit_param_tuples, entry_args, exit_args):
                return generate_enex_nb(
                    shape, entry_wait, exit_wait, entry_choice_func,
                    (*entry_input_list, *entry_in_output_tuples[i],
                     *entry_param_tuples[i], *entry_args), exit_choice_func,
                    (*exit_input_list, *exit_in_output_tuples[i],
                     *exit_param_tuples[i], *exit_args))

        def custom_func(input_list,
                        in_output_list,
                        param_list,
                        *args,
                        input_shape=None,
                        flex_2d=None,
                        entry_args=None,
                        exit_args=None,
                        cache_args=None,
                        entry_kwargs=None,
                        exit_kwargs=None,
                        cache_kwargs=None,
                        return_cache=False,
                        use_cache=None,
                        **_kwargs):
            # Get arguments
            if len(input_list) == 0:
                if input_shape is None:
                    raise ValueError(
                        "Pass input_shape if no input time series passed")
            else:
                input_shape = input_list[0].shape

            if entry_args is None:
                entry_args = ()
            if exit_args is None:
                exit_args = ()
            if cache_args is None:
                cache_args = ()
            if exit_only:
                if len(exit_args) > 0:
                    raise ValueError(
                        "Use *args instead of exit_args when exit_only=True")
                exit_args = args
            else:
                if len(args) > 0:
                    raise ValueError(
                        "*args can be only used when exit_only=True")

            if entry_kwargs is None:
                entry_kwargs = {}
            if exit_kwargs is None:
                exit_kwargs = {}
            if cache_kwargs is None:
                cache_kwargs = {}
            if exit_only:
                if len(exit_kwargs) > 0:
                    raise ValueError(
                        "Use **kwargs instead of exit_kwargs when exit_only=True"
                    )
                exit_kwargs = _kwargs
            else:
                if len(_kwargs) > 0:
                    raise ValueError(
                        "**kwargs can be only used when exit_only=True")

            kwargs_defaults = dict(
                input_shape=input_shape,
                wait=1,
                first=True,
                flex_2d=flex_2d,
            )
            entry_kwargs = merge_kwargs(kwargs_defaults, entry_kwargs)
            exit_kwargs = merge_kwargs(kwargs_defaults, exit_kwargs)
            cache_kwargs = merge_kwargs(kwargs_defaults, cache_kwargs)
            entry_wait = entry_kwargs['wait']
            exit_wait = exit_kwargs['wait']

            # Distribute arguments across functions
            entry_input_list = ()
            exit_input_list = ()
            cache_input_list = ()
            for input_name in entry_input_names:
                entry_input_list += (
                    input_list[input_names.index(input_name)], )
            for input_name in exit_input_names:
                exit_input_list += (
                    input_list[input_names.index(input_name)], )
            for input_name in cache_input_names:
                cache_input_list += (
                    input_list[input_names.index(input_name)], )

            entry_in_output_list = ()
            exit_in_output_list = ()
            cache_in_output_list = ()
            for in_output_name in entry_in_output_names:
                entry_in_output_list += (
                    in_output_list[in_output_names.index(in_output_name)], )
            for in_output_name in exit_in_output_names:
                exit_in_output_list += (
                    in_output_list[in_output_names.index(in_output_name)], )
            for in_output_name in cache_in_output_names:
                cache_in_output_list += (
                    in_output_list[in_output_names.index(in_output_name)], )

            entry_param_list = ()
            exit_param_list = ()
            cache_param_list = ()
            for param_name in entry_param_names:
                entry_param_list += (
                    param_list[param_names.index(param_name)], )
            for param_name in exit_param_names:
                exit_param_list += (
                    param_list[param_names.index(param_name)], )
            for param_name in cache_param_names:
                cache_param_list += (
                    param_list[param_names.index(param_name)], )

            n_params = len(param_list[0]) if len(param_list) > 0 else 1
            entry_in_output_tuples = tuple(zip(*entry_in_output_list))
            if len(entry_in_output_tuples) == 0:
                entry_in_output_tuples = ((), ) * n_params
            exit_in_output_tuples = tuple(zip(*exit_in_output_list))
            if len(exit_in_output_tuples) == 0:
                exit_in_output_tuples = ((), ) * n_params
            entry_param_tuples = tuple(zip(*entry_param_list))
            if len(entry_param_tuples) == 0:
                entry_param_tuples = ((), ) * n_params
            exit_param_tuples = tuple(zip(*exit_param_list))
            if len(exit_param_tuples) == 0:
                exit_param_tuples = ((), ) * n_params

            def _build_more_args(func_settings, func_kwargs):
                pass_kwargs = func_settings.get('pass_kwargs', [])
                more_args = ()
                for key in pass_kwargs:
                    value = None
                    if isinstance(key, tuple):
                        key, value = key
                    else:
                        if key.startswith('temp_idx_arr'):
                            value = np.empty((input_shape[0], ), dtype=np.int_)
                    value = func_kwargs.get(key, value)
                    more_args += (value, )
                return more_args

            entry_more_args = _build_more_args(entry_settings, entry_kwargs)
            exit_more_args = _build_more_args(exit_settings, exit_kwargs)
            cache_more_args = _build_more_args(cache_settings, cache_kwargs)

            # Caching
            cache = use_cache
            if cache is None and cache_func is not None:
                cache = cache_func(*cache_input_list, *cache_in_output_list,
                                   *cache_param_list, *cache_args,
                                   *cache_more_args)
            if return_cache:
                return cache
            if cache is None:
                cache = ()
            if not isinstance(cache, (tuple, list, List)):
                cache = (cache, )

            entry_cache = ()
            exit_cache = ()
            if entry_settings.get('pass_cache', False):
                entry_cache = cache
            if exit_settings.get('pass_cache', False):
                exit_cache = cache

            # Apply and concatenate
            if exit_only and not iteratively:
                return combine_fns.apply_and_concat_one_nb(
                    n_params, apply_nb, input_list[0], exit_wait,
                    exit_input_list, exit_in_output_tuples, exit_param_tuples,
                    exit_args + exit_more_args + exit_cache)
            else:
                return combine_fns.apply_and_concat_multiple_nb(
                    n_params, apply_nb, input_shape, entry_wait, exit_wait,
                    entry_input_list, exit_input_list, entry_in_output_tuples,
                    exit_in_output_tuples, entry_param_tuples,
                    exit_param_tuples,
                    entry_args + entry_more_args + entry_cache,
                    exit_args + exit_more_args + exit_cache)

        return self.from_custom_func(custom_func, pass_lists=True, **kwargs)
Esempio n. 27
0
 def test_merge_kwargs(self):
     assert config.merge_kwargs({'a': 1}, {'b': 2}) == {'a': 1, 'b': 2}
     assert config.merge_kwargs({'a': 1}, {'a': 2}) == {'a': 2}
     assert config.merge_kwargs({'a': {'b': 2}}, {'a': {'c': 3}}) == {'a': {'b': 2, 'c': 3}}
     assert config.merge_kwargs({'a': {'b': 2}}, {'a': {'b': 3}}) == {'a': {'b': 3}}
Esempio n. 28
0
    def combine_with_multiple(self, others, *args, combine_func=None, pass_2d=False,
                              concat=False, broadcast_kwargs={}, keys=None, **kwargs):
        """Combine with `others` using `combine_func`.

        All arguments will be broadcasted using `vectorbt.base.reshape_fns.broadcast`
        with `broadcast_kwargs`.

        If `concat` is `True`, concatenate the results along columns, 
        see `vectorbt.base.combine_fns.combine_and_concat`.
        Otherwise, pairwise combine into a Series/DataFrame of the same shape, 
        see `vectorbt.base.combine_fns.combine_multiple`.

        Arguments `*args` and `**kwargs` will be directly passed to `combine_func`. 
        If `pass_2d` is `True`, 2-dimensional NumPy arrays will be passed, otherwise as is.
        Use `keys` as the outermost level.

        !!! note
            If `combine_func` is Numba-compiled, will broadcast using `WRITEABLE` and `C_CONTIGUOUS`
            flags, which can lead to an expensive computation overhead if passed objects are large and
            have different shape/memory order. You also must ensure that all objects have the same data type.

            Also remember to bring each in `*args` to a Numba-compatible format.

        Example:
            ```python-repl
            >>> import vectorbt as vbt
            >>> import pandas as pd
            >>> sr = pd.Series([1, 2], index=['x', 'y'])
            >>> df = pd.DataFrame([[3, 4], [5, 6]], index=['x', 'y'], columns=['a', 'b'])

            >>> sr.vbt.combine_with_multiple([df, df*2],
            ...     combine_func=lambda x, y: x + y)
                a   b
            x  10  13
            y  17  20

            >>> sr.vbt.combine_with_multiple([df, df*2],
            ...     combine_func=lambda x, y: x + y, concat=True, keys=['c', 'd'])
                  c       d    
               a  b   a   b
            x  4  5   7   9
            y  7  8  12  14
            ```"""
        others = tuple(map(lambda x: x._obj if isinstance(x, Base_Accessor) else x, others))
        checks.assert_not_none(combine_func)
        checks.assert_type(others, Iterable)
        # Broadcast arguments
        if checks.is_numba_func(combine_func):
            # Numba requires writeable arrays
            # Plus all of our arrays must be in the same order
            broadcast_kwargs = merge_kwargs(dict(require_kwargs=dict(requirements=['W', 'C'])), broadcast_kwargs)
        new_obj, *new_others = reshape_fns.broadcast(self._obj, *others, **broadcast_kwargs)
        # Optionally cast to 2d array
        if pass_2d:
            bc_arrays = tuple(map(lambda x: reshape_fns.to_2d(x, raw=True), (new_obj, *new_others)))
        else:
            bc_arrays = tuple(map(lambda x: np.asarray(x), (new_obj, *new_others)))
        if concat:
            # Concat the results horizontally
            if checks.is_numba_func(combine_func):
                for i in range(1, len(bc_arrays)):
                    checks.assert_meta_equal(bc_arrays[i - 1], bc_arrays[i])
                result = combine_fns.combine_and_concat_nb(bc_arrays[0], bc_arrays[1:], combine_func, *args, **kwargs)
            else:
                result = combine_fns.combine_and_concat(bc_arrays[0], bc_arrays[1:], combine_func, *args, **kwargs)
            columns = new_obj.vbt.columns
            if keys is not None:
                new_columns = index_fns.combine_indexes(keys, columns)
            else:
                top_columns = pd.Index(np.arange(len(new_others)), name='combine_idx')
                new_columns = index_fns.combine_indexes(top_columns, columns)
            return new_obj.vbt.wrap(result, columns=new_columns)
        else:
            # Combine arguments pairwise into one object
            if checks.is_numba_func(combine_func):
                for i in range(1, len(bc_arrays)):
                    checks.assert_dtype_equal(bc_arrays[i - 1], bc_arrays[i])
                result = combine_fns.combine_multiple_nb(bc_arrays, combine_func, *args, **kwargs)
            else:
                result = combine_fns.combine_multiple(bc_arrays, combine_func, *args, **kwargs)
            return new_obj.vbt.wrap(result)
Esempio n. 29
0
    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
Esempio n. 30
0
        def custom_func(input_list,
                        in_output_list,
                        param_list,
                        *args,
                        input_shape=None,
                        flex_2d=None,
                        entry_args=None,
                        exit_args=None,
                        cache_args=None,
                        entry_kwargs=None,
                        exit_kwargs=None,
                        cache_kwargs=None,
                        return_cache=False,
                        use_cache=None,
                        **_kwargs):
            # Get arguments
            if len(input_list) == 0:
                if input_shape is None:
                    raise ValueError(
                        "Pass input_shape if no input time series passed")
            else:
                input_shape = input_list[0].shape

            if entry_args is None:
                entry_args = ()
            if exit_args is None:
                exit_args = ()
            if cache_args is None:
                cache_args = ()
            if exit_only:
                if len(exit_args) > 0:
                    raise ValueError(
                        "Use *args instead of exit_args when exit_only=True")
                exit_args = args
            else:
                if len(args) > 0:
                    raise ValueError(
                        "*args can be only used when exit_only=True")

            if entry_kwargs is None:
                entry_kwargs = {}
            if exit_kwargs is None:
                exit_kwargs = {}
            if cache_kwargs is None:
                cache_kwargs = {}
            if exit_only:
                if len(exit_kwargs) > 0:
                    raise ValueError(
                        "Use **kwargs instead of exit_kwargs when exit_only=True"
                    )
                exit_kwargs = _kwargs
            else:
                if len(_kwargs) > 0:
                    raise ValueError(
                        "**kwargs can be only used when exit_only=True")

            kwargs_defaults = dict(
                input_shape=input_shape,
                wait=1,
                first=True,
                flex_2d=flex_2d,
            )
            entry_kwargs = merge_kwargs(kwargs_defaults, entry_kwargs)
            exit_kwargs = merge_kwargs(kwargs_defaults, exit_kwargs)
            cache_kwargs = merge_kwargs(kwargs_defaults, cache_kwargs)
            entry_wait = entry_kwargs['wait']
            exit_wait = exit_kwargs['wait']

            # Distribute arguments across functions
            entry_input_list = ()
            exit_input_list = ()
            cache_input_list = ()
            for input_name in entry_input_names:
                entry_input_list += (
                    input_list[input_names.index(input_name)], )
            for input_name in exit_input_names:
                exit_input_list += (
                    input_list[input_names.index(input_name)], )
            for input_name in cache_input_names:
                cache_input_list += (
                    input_list[input_names.index(input_name)], )

            entry_in_output_list = ()
            exit_in_output_list = ()
            cache_in_output_list = ()
            for in_output_name in entry_in_output_names:
                entry_in_output_list += (
                    in_output_list[in_output_names.index(in_output_name)], )
            for in_output_name in exit_in_output_names:
                exit_in_output_list += (
                    in_output_list[in_output_names.index(in_output_name)], )
            for in_output_name in cache_in_output_names:
                cache_in_output_list += (
                    in_output_list[in_output_names.index(in_output_name)], )

            entry_param_list = ()
            exit_param_list = ()
            cache_param_list = ()
            for param_name in entry_param_names:
                entry_param_list += (
                    param_list[param_names.index(param_name)], )
            for param_name in exit_param_names:
                exit_param_list += (
                    param_list[param_names.index(param_name)], )
            for param_name in cache_param_names:
                cache_param_list += (
                    param_list[param_names.index(param_name)], )

            n_params = len(param_list[0]) if len(param_list) > 0 else 1
            entry_in_output_tuples = tuple(zip(*entry_in_output_list))
            if len(entry_in_output_tuples) == 0:
                entry_in_output_tuples = ((), ) * n_params
            exit_in_output_tuples = tuple(zip(*exit_in_output_list))
            if len(exit_in_output_tuples) == 0:
                exit_in_output_tuples = ((), ) * n_params
            entry_param_tuples = tuple(zip(*entry_param_list))
            if len(entry_param_tuples) == 0:
                entry_param_tuples = ((), ) * n_params
            exit_param_tuples = tuple(zip(*exit_param_list))
            if len(exit_param_tuples) == 0:
                exit_param_tuples = ((), ) * n_params

            def _build_more_args(func_settings, func_kwargs):
                pass_kwargs = func_settings.get('pass_kwargs', [])
                more_args = ()
                for key in pass_kwargs:
                    value = None
                    if isinstance(key, tuple):
                        key, value = key
                    else:
                        if key.startswith('temp_idx_arr'):
                            value = np.empty((input_shape[0], ), dtype=np.int_)
                    value = func_kwargs.get(key, value)
                    more_args += (value, )
                return more_args

            entry_more_args = _build_more_args(entry_settings, entry_kwargs)
            exit_more_args = _build_more_args(exit_settings, exit_kwargs)
            cache_more_args = _build_more_args(cache_settings, cache_kwargs)

            # Caching
            cache = use_cache
            if cache is None and cache_func is not None:
                cache = cache_func(*cache_input_list, *cache_in_output_list,
                                   *cache_param_list, *cache_args,
                                   *cache_more_args)
            if return_cache:
                return cache
            if cache is None:
                cache = ()
            if not isinstance(cache, (tuple, list, List)):
                cache = (cache, )

            entry_cache = ()
            exit_cache = ()
            if entry_settings.get('pass_cache', False):
                entry_cache = cache
            if exit_settings.get('pass_cache', False):
                exit_cache = cache

            # Apply and concatenate
            if exit_only and not iteratively:
                return combine_fns.apply_and_concat_one_nb(
                    n_params, apply_nb, input_list[0], exit_wait,
                    exit_input_list, exit_in_output_tuples, exit_param_tuples,
                    exit_args + exit_more_args + exit_cache)
            else:
                return combine_fns.apply_and_concat_multiple_nb(
                    n_params, apply_nb, input_shape, entry_wait, exit_wait,
                    entry_input_list, exit_input_list, entry_in_output_tuples,
                    exit_in_output_tuples, entry_param_tuples,
                    exit_param_tuples,
                    entry_args + entry_more_args + entry_cache,
                    exit_args + exit_more_args + exit_cache)