Ejemplo n.º 1
0
    def from_signals(cls,
                     ts,
                     entries,
                     exits,
                     volume=np.inf,
                     accumulate=False,
                     investment=None,
                     slippage=None,
                     commission=None,
                     broadcast_kwargs={}):
        """Build portfolio based on entry and exit signals and the corresponding volume.

        Set volume to the number of shares to buy/sell.
        Set volume to np.inf to buy/sell everything.
        Set accumulate to False to avoid producing new orders if already in the market."""
        if investment is None:
            investment = defaults.portfolio['investment']
        if slippage is None:
            slippage = defaults.portfolio['slippage']
        if commission is None:
            commission = defaults.portfolio['commission']

        checks.assert_type(ts, (pd.Series, pd.DataFrame))
        checks.assert_type(entries, (pd.Series, pd.DataFrame))
        checks.assert_type(exits, (pd.Series, pd.DataFrame))

        ts.vbt.timeseries.validate()
        entries.vbt.signals.validate()
        exits.vbt.signals.validate()

        ts, entries, exits = reshape_fns.broadcast(ts,
                                                   entries,
                                                   exits,
                                                   **broadcast_kwargs,
                                                   writeable=True)

        volume = reshape_fns.broadcast_to(volume,
                                          ts,
                                          writeable=True,
                                          copy_kwargs={'dtype': np.float64})

        investment = float(investment)
        slippage = float(slippage)
        commission = float(commission)

        cash, shares = nb.portfolio_from_signals_np(ts.vbt.to_2d_array(),
                                                    investment, slippage,
                                                    commission,
                                                    entries.vbt.to_2d_array(),
                                                    exits.vbt.to_2d_array(),
                                                    volume.vbt.to_2d_array(),
                                                    accumulate)

        cash = ts.vbt.wrap_array(cash)
        shares = ts.vbt.wrap_array(shares)

        return cls(ts, cash, shares, investment, slippage, commission)
Ejemplo n.º 2
0
def mapper_indexing_func(mapper, ref_obj, pd_indexing_func):
    """Broadcast `mapper` Series to `ref_obj` and perform pandas indexing using `pd_indexing_func`."""
    checks.assert_type(mapper, pd.Series)
    checks.assert_type(ref_obj, (pd.Series, pd.DataFrame))

    df_range_mapper = reshape_fns.broadcast_to(np.arange(len(mapper.index)), ref_obj)
    loced_range_mapper = pd_indexing_func(df_range_mapper)
    new_mapper = mapper.iloc[loced_range_mapper.values[0]]
    if checks.is_frame(loced_range_mapper):
        return pd.Series(new_mapper.values, index=loced_range_mapper.columns, name=mapper.name)
    elif checks.is_series(loced_range_mapper):
        return pd.Series([new_mapper], index=[loced_range_mapper.name], name=mapper.name)
Ejemplo n.º 3
0
    def __init__(self,
                 price,
                 init_capital,
                 order_records,
                 cash,
                 shares,
                 data_freq=None,
                 year_freq=None,
                 risk_free=None,
                 required_return=None,
                 cutoff=None,
                 factor_returns=None):
        # Perform checks
        checks.assert_type(price, (pd.Series, pd.DataFrame))
        checks.assert_type(order_records, np.ndarray)
        checks.assert_same_shape(order_records, OrderRecord, axis=(1, 0))
        checks.assert_same_meta(price, cash)
        checks.assert_same_meta(price, shares)

        # Main parameters
        self._price = price
        self._init_capital = init_capital
        self._order_records = order_records
        self._cash = cash
        self._shares = shares

        # Other parameters
        if data_freq is None:
            data_freq = price.vbt.timeseries.timedelta
        else:
            data_freq = pd.to_timedelta(data_freq)
        self._data_freq = data_freq
        year_freq = defaults.portfolio[
            'year_freq'] if year_freq is None else year_freq
        year_freq = pd.to_timedelta(year_freq)
        self._year_freq = year_freq
        self._ann_factor = year_freq / data_freq
        self._risk_free = defaults.portfolio[
            'risk_free'] if risk_free is None else risk_free
        self._required_return = defaults.portfolio[
            'required_return'] if required_return is None else required_return
        self._cutoff = defaults.portfolio[
            'cutoff'] if cutoff is None else cutoff
        if factor_returns is not None:
            factor_returns = reshape_fns.broadcast_to(factor_returns, price)
        self._factor_returns = factor_returns

        # Supercharge
        self.wrapper = TSRArrayWrapper.from_obj(price)
Ejemplo n.º 4
0
    def __init__(self,
                 price,
                 cash,
                 shares,
                 init_capital,
                 fees_paid,
                 slippage_paid,
                 data_freq=None,
                 year_freq=None,
                 risk_free=None,
                 required_return=None,
                 cutoff=None,
                 factor_returns=None):
        checks.assert_type(price, (pd.Series, pd.DataFrame))
        checks.assert_same_meta(price, cash)
        checks.assert_same_meta(price, shares)
        checks.assert_same_meta(price, fees_paid)
        checks.assert_same_meta(price, slippage_paid)

        # Time series
        self._price = price
        self._cash = cash
        self._shares = shares
        self._fees_paid = fees_paid
        self._slippage_paid = slippage_paid

        # User-defined parameters
        self._init_capital = init_capital
        if data_freq is None:
            data_freq = price.vbt.timeseries.timedelta
        else:
            data_freq = pd.to_timedelta(data_freq)
        self._data_freq = data_freq
        year_freq = defaults.portfolio[
            'year_freq'] if year_freq is None else year_freq
        year_freq = pd.to_timedelta(year_freq)
        self._year_freq = year_freq
        self._ann_factor = year_freq / data_freq
        self._risk_free = defaults.portfolio[
            'risk_free'] if risk_free is None else risk_free
        self._required_return = defaults.portfolio[
            'required_return'] if required_return is None else required_return
        self._cutoff = defaults.portfolio[
            'cutoff'] if cutoff is None else cutoff
        if factor_returns is not None:
            factor_returns = reshape_fns.broadcast_to(factor_returns, price)
        self._factor_returns = factor_returns

        ArrayWrapper.__init__(self, self.price)
Ejemplo n.º 5
0
    def from_order_func(cls,
                        price,
                        order_func_nb,
                        *args,
                        init_capital=None,
                        fees=None,
                        slippage=None):
        """Build portfolio from a custom order function.

        Starting with initial capital `init_capital`, at each time step, orders the number 
        of shares returned by `order_func_nb`. 

        Args:
            price (pandas_like): Price of the asset.
            order_func_nb (function): Function that returns the amount of shares to order.

                See `vectorbt.portfolio.nb.portfolio_nb`.
            *args: Arguments passed to `order_func_nb`.
            init_capital (int or float): The initial capital.
            fees (float or array_like): Trading fees in percentage of the value involved.
            slippage (float or array_like): Slippage in percentage of `price`.

        All array-like arguments will be broadcasted together using `vectorbt.utils.reshape_fns.broadcast`
        with `broadcast_kwargs`. At the end, each time series object will have the same metadata.

        !!! note
            `order_func_nb` must be Numba-compiled.

        Example:
            Portfolio value of a simple buy-and-hold strategy:
            ```python-repl
            >>> @njit
            ... def order_func_nb(col, i, run_cash, run_shares):
            ...     return 10 if i == 0 else 0

            >>> portfolio = vbt.Portfolio.from_order_func(price, 
            ...     order_func_nb, init_capital=100, fees=0.0025)

            >>> print(portfolio.cash)
            2018-01-01    89.975
            2018-01-02    89.975
            2018-01-03    89.975
            2018-01-04    89.975
            2018-01-05    89.975
            dtype: float64
            >>> print(portfolio.shares)
            2018-01-01    10.0
            2018-01-02    10.0
            2018-01-03    10.0
            2018-01-04    10.0
            2018-01-05    10.0
            dtype: float64
            >>> print(portfolio.equity)
            2018-01-01     99.975
            2018-01-02    109.975
            2018-01-03    119.975
            2018-01-04    109.975
            2018-01-05     99.975
            dtype: float64
            >>> print(portfolio.total_costs)
            0.02499999999999858
            ```
        """
        # Get defaults
        if init_capital is None:
            init_capital = defaults.portfolio['init_capital']
        init_capital = float(init_capital)
        if fees is None:
            fees = defaults.portfolio['fees']
        if slippage is None:
            slippage = defaults.portfolio['slippage']

        # Perform checks
        checks.assert_type(price, (pd.Series, pd.DataFrame))
        checks.assert_numba_func(order_func_nb)

        # Broadcast inputs
        fees = reshape_fns.broadcast_to(fees,
                                        price,
                                        to_pd=False,
                                        writeable=True)
        slippage = reshape_fns.broadcast_to(slippage,
                                            price,
                                            to_pd=False,
                                            writeable=True)

        # Perform calculation
        cash, shares, paid_fees, paid_slippage = nb.portfolio_nb(
            reshape_fns.to_2d(price, raw=True), init_capital,
            reshape_fns.to_2d(fees, raw=True),
            reshape_fns.to_2d(slippage, raw=True), order_func_nb, *args)

        # Bring to the same meta
        cash = price.vbt.wrap_array(cash)
        shares = price.vbt.wrap_array(shares)
        paid_fees = price.vbt.wrap_array(paid_fees)
        paid_slippage = price.vbt.wrap_array(paid_slippage)

        return cls(price, cash, shares, init_capital, paid_fees, paid_slippage)
Ejemplo n.º 6
0
    def from_orders(cls,
                    price,
                    orders,
                    is_target=False,
                    init_capital=None,
                    fees=None,
                    slippage=None,
                    broadcast_kwargs={}):
        """Build portfolio from orders.

        Starting with initial capital `init_capital`, at each time step, orders the number 
        of shares specified in `orders`. 

        Args:
            price (pandas_like): Price of the asset.
            orders (int, float or array_like): The amount of shares to order. 

                If the amount is positive, this is the number of shares to buy. 
                If the amount is negative, this is the number of shares to sell.
                To buy/sell everything, set the amount to `numpy.inf`.
            is_target (bool): If `True`, will order the difference between current and target amount.
            init_capital (int or float): The initial capital.
            fees (float or array_like): Trading fees in percentage of the value involved.
            slippage (float or array_like): Slippage in percentage of `price`.

        All array-like arguments will be broadcasted together using `vectorbt.utils.reshape_fns.broadcast`
        with `broadcast_kwargs`. At the end, each time series object will have the same metadata.

        Example:
            Portfolio value of various order sequences:
            ```python-repl
            >>> orders = pd.DataFrame({
            ...     'a': [np.inf, 0, 0, 0, 0],
            ...     'b': [1, 1, 1, 1, -np.inf],
            ...     'c': [np.inf, -np.inf, np.inf, -np.inf, np.inf]
            ... }, index=index)

            >>> portfolio = vbt.Portfolio.from_orders(price, orders, 
            ...     init_capital=100, fees=0.0025)

            >>> print(portfolio.cash)
                          a        b           c
            2018-01-01  0.0  98.9975    0.000000
            2018-01-02  0.0  96.9925  199.002494
            2018-01-03  0.0  93.9850    0.000000
            2018-01-04  0.0  91.9800  132.006642
            2018-01-05  0.0  95.9700    0.000000
            >>> print(portfolio.shares)
                                a    b           c
            2018-01-01  99.750623  1.0   99.750623
            2018-01-02  99.750623  2.0    0.000000
            2018-01-03  99.750623  3.0   66.168743
            2018-01-04  99.750623  4.0    0.000000
            2018-01-05  99.750623  0.0  131.677448
            >>> print(portfolio.equity)
                                 a         b           c
            2018-01-01   99.750623   99.9975   99.750623
            2018-01-02  199.501247  100.9925  199.002494
            2018-01-03  299.251870  102.9850  198.506228
            2018-01-04  199.501247   99.9800  132.006642
            2018-01-05   99.750623   95.9700  131.677448
            >>> print(portfolio.total_costs)
            a    0.249377
            b    0.030000
            c    1.904433
            dtype: float64
            ```
        """
        # Get defaults
        if init_capital is None:
            init_capital = defaults.portfolio['init_capital']
        init_capital = float(init_capital)
        if fees is None:
            fees = defaults.portfolio['fees']
        if slippage is None:
            slippage = defaults.portfolio['slippage']

        # Perform checks
        checks.assert_type(price, (pd.Series, pd.DataFrame))
        checks.assert_type(orders, (pd.Series, pd.DataFrame))

        # Broadcast inputs
        price, orders = reshape_fns.broadcast(price,
                                              orders,
                                              **broadcast_kwargs,
                                              writeable=True)
        fees = reshape_fns.broadcast_to(fees,
                                        price,
                                        to_pd=False,
                                        writeable=True)
        slippage = reshape_fns.broadcast_to(slippage,
                                            price,
                                            to_pd=False,
                                            writeable=True)

        # Perform calculation
        cash, shares, paid_fees, paid_slippage = nb.portfolio_nb(
            reshape_fns.to_2d(price, raw=True), init_capital,
            reshape_fns.to_2d(fees, raw=True),
            reshape_fns.to_2d(slippage, raw=True), nb.amount_order_func_nb,
            reshape_fns.to_2d(orders, raw=True), is_target)

        # Bring to the same meta
        cash = price.vbt.wrap_array(cash)
        shares = price.vbt.wrap_array(shares)
        paid_fees = price.vbt.wrap_array(paid_fees)
        paid_slippage = price.vbt.wrap_array(paid_slippage)

        return cls(price, cash, shares, init_capital, paid_fees, paid_slippage)
Ejemplo n.º 7
0
    def plot_against(self,
                     other,
                     name=None,
                     other_name=None,
                     above_trace_kwargs={},
                     below_trace_kwargs={},
                     other_trace_kwargs={},
                     equal_trace_kwargs={},
                     fig=None,
                     **layout_kwargs):
        """Plot Series against `other` as markers.

        Args:
            other (float, int, or array_like): The other time series/value.
            name (str): Name of the time series.
            other_name (str): Name of the other time series/value.
            other_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for `other`.
            above_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for values above `other`.
            below_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for values below `other`.
            equal_trace_kwargs (dict): Keyword arguments passed to `plotly.graph_objects.Scatter` for values equal `other`.
            fig (plotly.graph_objects.Figure): Figure to add traces to.
            **layout_kwargs: Keyword arguments for layout.
        Example:
            Can plot against single values such as benchmarks.
            ```py
            df['a'].vbt.timeseries.plot_against(3)
            ```

            ![](/vectorbt/docs/img/timeseries_plot_against_line.png)

            But also against other time series.

            ```py
            df['a'].vbt.timeseries.plot_against(df['b'])
            ```

            ![](/vectorbt/docs/img/timeseries_plot_against_series.png)"""
        if name is None:
            name = self._obj.name
        if other_name is None:
            other_name = getattr(other, 'name', None)

        # Prepare data
        other = reshape_fns.to_1d(other)
        other = reshape_fns.broadcast_to(other, self._obj)
        above_obj = self._obj[self._obj > other]
        below_obj = self._obj[self._obj < other]
        equal_obj = self._obj[self._obj == other]

        # Set up figure
        if fig is None:
            fig = DefaultFigureWidget()
            fig.update_layout(**layout_kwargs)

        # Plot other
        other_scatter = go.Scatter(x=other.index,
                                   y=other,
                                   line=dict(
                                       color="grey",
                                       width=2,
                                       dash="dot",
                                   ),
                                   name=other_name,
                                   showlegend=other_name is not None)
        other_scatter.update(**other_trace_kwargs)
        fig.add_trace(other_scatter)

        # Plot markets
        above_scatter = go.Scatter(x=above_obj.index,
                                   y=above_obj,
                                   mode='markers',
                                   marker=dict(symbol='circle',
                                               color='green',
                                               size=10),
                                   name=f'{name} (above)',
                                   showlegend=name is not None)
        above_scatter.update(**above_trace_kwargs)
        fig.add_trace(above_scatter)

        below_scatter = go.Scatter(x=below_obj.index,
                                   y=below_obj,
                                   mode='markers',
                                   marker=dict(symbol='circle',
                                               color='red',
                                               size=10),
                                   name=f'{name} (below)',
                                   showlegend=name is not None)
        below_scatter.update(**below_trace_kwargs)
        fig.add_trace(below_scatter)

        equal_scatter = go.Scatter(x=equal_obj.index,
                                   y=equal_obj,
                                   mode='markers',
                                   marker=dict(symbol='circle',
                                               color='grey',
                                               size=10),
                                   name=f'{name} (equal)',
                                   showlegend=name is not None)
        equal_scatter.update(**equal_trace_kwargs)
        fig.add_trace(equal_scatter)

        # If other is a straight line, make y-axis symmetric
        if np.all(other.values == other.values.item(0)):
            maxval = np.nanmax(np.abs(self.to_array()))
            space = 0.1 * 2 * maxval
            y = other.values.item(0)
            fig.update_layout(
                yaxis=dict(range=[y - (maxval + space), y + maxval + space]),
                shapes=[
                    dict(type="line",
                         xref="paper",
                         yref='y',
                         x0=0,
                         x1=1,
                         y0=y,
                         y1=y,
                         line=dict(
                             color="grey",
                             width=2,
                             dash="dot",
                         ))
                ])

        return fig
Ejemplo n.º 8
0
 def broadcast_to(self, other, **kwargs):
     """See `vectorbt.utils.reshape_fns.broadcast_to`."""
     if isinstance(other, Base_Accessor):
         other = other._obj
     return reshape_fns.broadcast_to(self._obj, other, **kwargs)
Ejemplo n.º 9
0
 def broadcast_to(self, other, **kwargs):
     if isinstance(other, Base_Accessor):
         other = other._obj
     return reshape_fns.broadcast_to(self._obj, other, **kwargs)