Beispiel #1
0
    def align_to(self, other):
        """Align to `other` by their indexes and columns.

        Example:
            ```python-repl
            >>> import pandas as pd
            >>> df1 = pd.DataFrame([[1, 2], [3, 4]], index=['x', 'y'], columns=['a', 'b'])
            >>> df2 = pd.DataFrame([[5, 6, 7, 8], [9, 10, 11, 12]], index=['x', 'y'], 
            ...     columns=pd.MultiIndex.from_arrays([[1, 1, 2, 2], ['a', 'b', 'a', 'b']]))

            >>> print(df1.vbt.align_to(df2))
                  1     2   
               a  b  a  b
            x  1  2  1  2
            y  3  4  3  4
            ```"""
        checks.assert_type(other, (pd.Series, pd.DataFrame))
        obj = reshape_fns.to_2d(self._obj)
        other = reshape_fns.to_2d(other)

        aligned_index = index_fns.align_index_to(obj.index, other.index)
        aligned_columns = index_fns.align_index_to(obj.columns, other.columns)
        obj = obj.iloc[aligned_index, aligned_columns]
        return self.wrap_array(obj.values,
                               index=other.index,
                               columns=other.columns)
Beispiel #2
0
 def apply_and_concat(self,
                      ntimes,
                      *args,
                      apply_func=None,
                      as_columns=None,
                      **kwargs):
     """Apply a function n times and concatenate results into a single dataframe."""
     checks.assert_not_none(apply_func)
     if checks.is_numba_func(apply_func):
         # NOTE: your apply_func must a numba-compiled function and arguments must be numba-compatible
         # Also NOTE: outputs of apply_func must always be 2-dimensional
         result = combine_fns.apply_and_concat_nb(np.asarray(self._obj),
                                                  ntimes, apply_func, *args,
                                                  **kwargs)
     else:
         result = combine_fns.apply_and_concat(np.asarray(self._obj),
                                               ntimes, apply_func, *args,
                                               **kwargs)
     # Build column hierarchy
     if as_columns is not None:
         new_columns = index_fns.combine(
             as_columns,
             reshape_fns.to_2d(self._obj).columns)
     else:
         new_columns = index_fns.tile(
             reshape_fns.to_2d(self._obj).columns, ntimes)
     return self.wrap_array(result, columns=new_columns)
Beispiel #3
0
def assert_same_meta(arg1, arg2):
    """Raise exception if `arg1` and `arg2` have different metadata."""
    assert_same_type(arg1, arg2)
    assert_same_shape(arg1, arg2)
    if is_pandas(arg1) or is_pandas(arg2):
        assert_same_index(arg1, arg2)
        assert_same_columns(reshape_fns.to_2d(arg1), reshape_fns.to_2d(arg2))
Beispiel #4
0
    def align_to(self, other):
        checks.assert_type(other, (pd.Series, pd.DataFrame))
        obj = reshape_fns.to_2d(self._obj)
        other = reshape_fns.to_2d(other)

        aligned_index = index_fns.align_to(obj.index, other.index)
        aligned_columns = index_fns.align_to(obj.columns, other.columns)
        obj = obj.iloc[aligned_index, aligned_columns]
        return self.wrap_array(obj.values,
                               index=other.index,
                               columns=other.columns)
Beispiel #5
0
 def repeat(self, n, as_columns=None):
     repeated = reshape_fns.repeat(self._obj, n, along_axis=1)
     if as_columns is not None:
         new_columns = index_fns.combine(
             reshape_fns.to_2d(self._obj).columns, as_columns)
         return self.wrap_array(repeated.values, columns=new_columns)
     return repeated
Beispiel #6
0
    def update_data(self, data):
        """Update the data of the plot efficiently.

        Args:
            data (array_like): Data in any format that can be converted to NumPy.

                Must be of shape (`x_labels`, `trace_names`).
        Example:
            ```py
            fig = pd.Series([1, 2], index=['x', 'y'], name='a').vbt.Bar()
            fig.update_data([2, 1])
            fig.show()
            ```
            ![](/vectorbt/docs/img/Bar_updated.png)
        """
        data = reshape_fns.to_2d(np.asarray(data))
        checks.assert_same_shape(data, self._x_labels, axis=(0, 0))
        checks.assert_same_shape(data, self._trace_names, axis=(1, 0))

        # Update traces
        with self.batch_update():
            for i, bar in enumerate(self.data):
                bar.y = data[:, i]
                if bar.marker.colorscale is not None:
                    bar.marker.color = data[:, i]
Beispiel #7
0
def build_mapper(params, ts, new_columns, level_name):
    """Build a mapper that maps parameter values in `params` to columns in `new_columns`."""
    params_mapper = np.repeat(params, len(reshape_fns.to_2d(ts).columns))
    params_mapper = pd.Series(params_mapper,
                              index=new_columns,
                              name=level_name)
    return params_mapper
Beispiel #8
0
 def __call__(self, trace_names=None, **kwargs):
     if trace_names is None:
         if checks.is_frame(self._obj) or (checks.is_series(self._obj)
                                           and self._obj.name is not None):
             trace_names = reshape_fns.to_2d(self._obj).columns
     return widgets.Histogram(trace_names=trace_names,
                              data=self._obj.values,
                              **kwargs)
Beispiel #9
0
 def tile(self, n, as_columns=None):
     tiled = reshape_fns.tile(self._obj, n, along_axis=1)
     if as_columns is not None:
         new_columns = index_fns.combine(
             as_columns,
             reshape_fns.to_2d(self._obj).columns)
         return self.wrap_array(tiled.values, columns=new_columns)
     return tiled
Beispiel #10
0
def apply_and_concat_one(n, apply_func, *args, **kwargs):
    """For each value `i` from 0 to `n`, apply `apply_func` with arguments `*args` and `**kwargs`, 
    and concat the results along axis 1. 
    
    The result of `apply_func` must be a single 1-dim or 2-dim array.
    
    `apply_func` must accept arguments `i`, `*args` and `**kwargs`."""
    return np.hstack(
        [reshape_fns.to_2d(apply_func(i, *args, **kwargs)) for i in range(n)])
Beispiel #11
0
 def __call__(self, x_labels=None, y_labels=None, **kwargs):
     if x_labels is None:
         x_labels = reshape_fns.to_2d(self._obj).columns
     if y_labels is None:
         y_labels = self._obj.index
     return widgets.Heatmap(x_labels,
                            y_labels,
                            data=self._obj.values,
                            **kwargs)
Beispiel #12
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.utils.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.

        Example:
            ```python-repl
            >>> 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'])

            >>> print(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 = {**dict(writeable=True), **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(np.asarray(new_obj))
            new_other_arr = reshape_fns.to_2d(np.asarray(new_other))
        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_array(result)
Beispiel #13
0
 def __call__(self, x_labels=None, trace_names=None, **kwargs):
     if x_labels is None:
         x_labels = self._obj.index
     if trace_names is None:
         if checks.is_frame(self._obj) or (checks.is_series(self._obj)
                                           and self._obj.name is not None):
             trace_names = reshape_fns.to_2d(self._obj).columns
     return widgets.Scatter(x_labels,
                            trace_names=trace_names,
                            data=self._obj.values,
                            **kwargs)
Beispiel #14
0
 def rolling_window(self, window, n=None):
     cube = nb.rolling_window_nb(self.to_2d_array(), window)
     if n is not None:
         idxs = np.round(np.linspace(0, cube.shape[2]-1, n)).astype(int)
         cube = cube[:, :, idxs]
     else:
         idxs = np.arange(cube.shape[2])
     matrix = np.hstack(cube)
     range_columns = pd.Index(self._obj.index[idxs], name='start_date')
     new_columns = index_fns.combine(range_columns, reshape_fns.to_2d(self._obj).columns)
     return pd.DataFrame(matrix, columns=new_columns)
Beispiel #15
0
def perform_init_checks(ts_list, output_list, param_list, mapper_list, name):
    """Perform checks on objects created by running or slicing an indicator."""
    checks.assert_type(ts_list[0], (pd.Series, pd.DataFrame))
    for ts in ts_list + output_list:
        checks.assert_same_meta(ts_list[0], ts)
    for params in param_list:
        checks.assert_same_shape(param_list[0], params)
    for mapper in mapper_list:
        checks.assert_type(mapper, pd.Series)
        checks.assert_same_index(
            reshape_fns.to_2d(ts_list[0]).iloc[0, :], mapper)
    checks.assert_type(name, str)
Beispiel #16
0
def perform_init_checks(ts_list, output_list, param_list, mapper_list, name):
    """Perform checks on objects created by running or slicing an indicator."""
    for ts in ts_list:
        checks.assert_type(ts, (pd.Series, pd.DataFrame))
        ts.vbt.timeseries.validate()
    for i in range(1, len(ts_list) + len(output_list)):
        checks.assert_same_meta((ts_list + output_list)[i - 1],
                                (ts_list + output_list)[i])
    for i in range(1, len(param_list)):
        checks.assert_same_shape(param_list[i - 1], param_list[i])
    for mapper in mapper_list:
        checks.assert_type(mapper, pd.Series)
        checks.assert_same_index(
            reshape_fns.to_2d(ts_list[0]).iloc[0, :], mapper)
    checks.assert_type(name, str)
Beispiel #17
0
    def update_data(self, data):
        """Update the data of the plot efficiently.

        Args:
            data (array_like): Data in any format that can be converted to NumPy.

                Must be of shape (`x_labels`, `trace_names`).
        """
        data = reshape_fns.to_2d(np.asarray(data))
        checks.assert_same_shape(data, self._x_labels, axis=(0, 0))
        checks.assert_same_shape(data, self._trace_names, axis=(1, 0))

        # Update traces
        with self.batch_update():
            for i, scatter in enumerate(self.data):
                scatter.y = data[:, i]
Beispiel #18
0
    def apply_and_concat(self,
                         ntimes,
                         *args,
                         apply_func=None,
                         pass_2d=False,
                         as_columns=None,
                         **kwargs):
        """Apply `apply_func` `ntimes` times and concatenate the results along columns.
        See `vectorbt.utils.combine_fns.apply_and_concat`.

        Arguments `*args` and `**kwargs` will be directly passed to `apply_func`.
        If `pass_2d` is `True`, 2-dimensional NumPy arrays will be passed, otherwise as is.
        Use `as_columns` as a top-level column level.

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

            >>> print(df.vbt.apply_and_concat(3, [1, 2, 3], 
            ...     apply_func=lambda i, a, b: a * b[i], as_columns=['c', 'd', 'e']))
                  c       d       e    
               a  b   a   b   a   b
            x  3  4   6   8   9  12
            y  5  6  10  12  15  18
            ```"""
        checks.assert_not_none(apply_func)
        # Optionally cast to 2d array
        if pass_2d:
            obj_arr = reshape_fns.to_2d(np.asarray(self._obj))
        else:
            obj_arr = np.asarray(self._obj)
        if checks.is_numba_func(apply_func):
            result = combine_fns.apply_and_concat_nb(obj_arr, ntimes,
                                                     apply_func, *args,
                                                     **kwargs)
        else:
            result = combine_fns.apply_and_concat(obj_arr, ntimes, apply_func,
                                                  *args, **kwargs)
        # Build column hierarchy
        if as_columns is not None:
            new_columns = index_fns.combine_indexes(as_columns, self.columns)
        else:
            new_columns = index_fns.tile_index(self.columns, ntimes)
        return self.wrap_array(result, columns=new_columns)
Beispiel #19
0
    def update_data(self, data):
        """Update the data of the plot efficiently.

        Args:
            data (array_like): Data in any format that can be converted to NumPy.

                Must be of shape (`y_labels`, `x_labels`).
        """
        data = reshape_fns.to_2d(np.asarray(data))
        checks.assert_same_shape(data, self._x_labels, axis=(1, 0))
        checks.assert_same_shape(data, self._y_labels, axis=(0, 0))

        # Update traces
        with self.batch_update():
            heatmap = self.data[0]
            if self._horizontal:
                heatmap.z = data.transpose()
            else:
                heatmap.z = data
Beispiel #20
0
    def update_data(self, data):
        """Update the data of the plot efficiently.

        Args:
            data (array_like): Data in any format that can be converted to NumPy.

                Must be of shape (any, `trace_names`).
        """
        data = reshape_fns.to_2d(np.asarray(data))
        checks.assert_same_shape(data, self._trace_names, axis=(1, 0))

        # Update traces
        with self.batch_update():
            for i, histogram in enumerate(self.data):
                if self._horizontal:
                    histogram.x = None
                    histogram.y = data[:, i]
                else:
                    histogram.x = data[:, i]
                    histogram.y = None
Beispiel #21
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)
Beispiel #22
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)
Beispiel #23
0
    def from_signals(cls,
                     price,
                     entries,
                     exits,
                     amount=np.inf,
                     init_capital=None,
                     fees=None,
                     slippage=None,
                     broadcast_kwargs={}):
        """Build portfolio from entry and exit signals.

        Starting with initial capital `init_capital`, for each `True` in `entries`/`exits`, 
        orders the number of shares specified in `amount`. 

        Args:
            price (pandas_like): Price of the asset.
            entries (pandas_like): Boolean array of entry signals.
            exits (pandas_like): Boolean array of exit signals.
            amount (int, float or array_like): The amount of shares to order. 

                To buy/sell everything, set the amount to `numpy.inf`.
            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
            There is no mechanism implemented to prevent order accumulation, meaning multiple entry/exit 
            signals one after another may increase/decrease your position in the market. That's why we will
            later calculate P/L of positions instead of trades.

            To select at most one exit signal, use `vectorbt.signals.accessors.Signals_Accessor.first`. 

        Example:
            Portfolio value of various signal sequences:
            ```python-repl
            >>> entries = pd.DataFrame({
            ...     'a': [True, False, False, False, False],
            ...     'b': [True, True, True, True, True],
            ...     'c': [True, False, True, False, True]
            ... }, index=index)
            >>> exits = pd.DataFrame({
            ...     'a': [False, False, False, False, False],
            ...     'b': [False, False, False, False, False],
            ...     'c': [False, True, False, True, False]
            ... }, index=index)

            >>> portfolio = vbt.Portfolio.from_signals(price, entries, 
            ...     exits, amount=10, init_capital=100, fees=0.0025)

            >>> print(portfolio.cash)
                             a       b        c
            2018-01-01  89.975  89.975   89.975
            2018-01-02  89.975  69.925  109.925
            2018-01-03  89.975  39.850   79.850
            2018-01-04  89.975  19.800   99.800
            2018-01-05  89.975   9.775   89.775
            >>> print(portfolio.shares)
                           a     b     c
            2018-01-01  10.0  10.0  10.0
            2018-01-02  10.0  20.0   0.0
            2018-01-03  10.0  30.0  10.0
            2018-01-04  10.0  40.0   0.0
            2018-01-05  10.0  50.0  10.0
            >>> print(portfolio.equity)
                              a        b        c
            2018-01-01   99.975   99.975   99.975
            2018-01-02  109.975  109.925  109.925
            2018-01-03  119.975  129.850  109.850
            2018-01-04  109.975   99.800   99.800
            2018-01-05   99.975   59.775   99.775
            >>> print(portfolio.total_costs)
            a    0.025
            b    0.225
            c    0.225
            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(entries, (pd.Series, pd.DataFrame))
        checks.assert_type(exits, (pd.Series, pd.DataFrame))
        entries.vbt.signals.validate()
        exits.vbt.signals.validate()

        # Broadcast inputs
        price, entries, exits, amount, fees, slippage = reshape_fns.broadcast(
            price,
            entries,
            exits,
            amount,
            fees,
            slippage,
            **broadcast_kwargs,
            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.signals_order_func_nb,
            reshape_fns.to_2d(entries, raw=True),
            reshape_fns.to_2d(exits, raw=True),
            reshape_fns.to_2d(amount, raw=True))

        # 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)
Beispiel #24
0
def from_params_pipeline(ts_list,
                         param_list,
                         level_names,
                         num_outputs,
                         custom_func,
                         *args,
                         pass_lists=False,
                         param_product=False,
                         broadcast_kwargs={},
                         return_raw=False,
                         **kwargs):
    """A pipeline for calculating an indicator, used by `IndicatorFactory`.

    Does the following:

    * Takes one or multiple time series objects in `ts_list` and broadcasts them. For example:

    ```python-repl
    >>> sr = pd.Series([1, 2], index=['x', 'y'])
    >>> df = pd.DataFrame([[3, 4], [5, 6]], index=['x', 'y'], columns=['a', 'b'])
    >>> ts_list = [sr, df]

    >>> ts_list = vbt.utils.reshape_fns.broadcast(*ts_list)
    >>> print(ts_list[0])
       a  b
    x  1  1
    y  2  2
    >>> print(ts_list[1])
       a  b
    x  3  4
    y  5  6
    ```

    * Takes one or multiple parameters in `param_list`, converts them to NumPy arrays and 
        broadcasts them. For example:

    ```python-repl
    >>> p1, p2, p3 = 1, [2, 3, 4], [False]
    >>> param_list = [p1, p2, p3]

    >>> param_list = vbt.utils.reshape_fns.broadcast(*param_list)
    >>> print(param_list[0])
    array([1, 1, 1])
    >>> print(param_list[1])
    array([2, 3, 4])
    >>> print(param_list[2])
    array([False, False, False])
    ```

    * Performs calculation using `custom_func` to build output arrays (`output_list`) and 
        other objects (`other_list`, optional). For example:

    ```python-repl
    >>> def custom_func(ts1, ts2, p1, p2, p3, *args, **kwargs):
    ...     return pd.DataFrame.vbt.concat(
    ...         (ts1.values + ts2.values) + p1[0] * p2[0],
    ...         (ts1.values + ts2.values) + p1[1] * p2[1],
    ...         (ts1.values + ts2.values) + p1[2] * p2[2]
    ...     )

    >>> output = custom_func(*ts_list, *param_list)
    >>> print(output)
    array([[ 6,  7,  7,  8,  8,  9],
           [ 9, 10, 10, 11, 11, 12]])
    ```

    * Creates new column hierarchy based on parameters and level names. For example:

    ```python-repl
    >>> p1_columns = pd.Index(param_list[0], name='p1')
    >>> p2_columns = pd.Index(param_list[1], name='p2')
    >>> p3_columns = pd.Index(param_list[2], name='p3')
    >>> p_columns = vbt.utils.index_fns.stack(p1_columns, p2_columns, p3_columns)
    >>> new_columns = vbt.utils.index_fns.combine(p_columns, ts_list[0].columns)

    >>> output_df = pd.DataFrame(output, columns=new_columns)
    >>> print(output_df)
    p1      1      1      1      1      1      1                        
    p2      2      2      3      3      4      4    
    p3  False  False  False  False  False  False    
            a      b      a      b      a      b
    0       6      7      7      8      8      9
    1       9     10     10     11     11     12
    ```

    * Broadcasts objects in `ts_list` to match the shape of objects in `output_list` through tiling.
        This is done to be able to compare them and generate signals, since you cannot compare NumPy 
        arrays that have totally different shapes, such as (2, 2) and (2, 6). For example:

    ```python-repl
    >>> new_ts_list = [
    ...     ts_list[0].vbt.tile(len(param_list[0]), as_columns=p_columns),
    ...     ts_list[1].vbt.tile(len(param_list[0]), as_columns=p_columns)
    ... ]
    >>> print(new_ts_list[0])
    p1      1      1      1      1      1      1                        
    p2      2      2      3      3      4      4    
    p3  False  False  False  False  False  False     
            a      b      a      b      a      b
    0       1      1      1      1      1      1
    1       2      2      2      2      2      2
    ```

    * Builds parameter mappers that will link parameters from `param_list` to columns in 
        `ts_list` and `output_list`. This is done to enable column indexing using parameter values.

    Args:
        ts_list (list of array_like): A list of time series objects. At least one must be a pandas object.
        param_list (list of array_like): A list of parameters. Each element is either an array-like object
            or a single value of any type.
        level_names (list of str): A list of column level names corresponding to each parameter.
        num_outputs (int): The number of output arrays.
        custom_func (function): A custom calculation function. See `IndicatorFactory.from_custom_func`.
        *args: Arguments passed to the `custom_func`.
        pass_lists (bool): If True, arguments are passed to the `custom_func` as lists. Defaults to False.
        param_product (bool): If True, builds a Cartesian product out of all parameters. Defaults to False.
        broadcast_kwargs (dict, optional): Keyword arguments passed to the `vectorbt.utils.reshape_fns.broadcast` 
            on time series objects.
        return_raw (bool): If True, returns the raw output without post-processing. Defaults to False.
        **kwargs: Keyword arguments passed to the `custom_func`.

            Some common arguments include `return_cache` to return cache and `cache` to pass cache. 
            Those are only applicable to `custom_func` that supports it (`custom_func` created using
            `IndicatorFactory.from_apply_func` are supported by default).
    Returns:
        A list of transformed inputs (`pandas_like`), a list of generated outputs (`pandas_like`), 
        a list of parameter arrays (`numpy.ndarray`), a list of parameter mappers (`pandas.Series`),
        a list of other generated outputs that are outside of  `num_outputs`.
    """
    # Check time series objects
    checks.assert_type(ts_list[0], (pd.Series, pd.DataFrame))
    for i in range(1, len(ts_list)):
        ts_list[i].vbt.timeseries.validate()
    if len(ts_list) > 1:
        # Broadcast time series
        ts_list = reshape_fns.broadcast(*ts_list,
                                        **broadcast_kwargs,
                                        writeable=True)
    # Check level names
    checks.assert_type(level_names, (list, tuple))
    checks.assert_same_len(param_list, level_names)
    for ts in ts_list:
        # Every time series object should be free of the specified level names in its columns
        for level_name in level_names:
            checks.assert_level_not_exists(ts, level_name)
    # Convert params to 1-dim arrays
    param_list = list(map(reshape_fns.to_1d, param_list))
    if len(param_list) > 1:
        if param_product:
            # Make Cartesian product out of all params
            param_list = list(map(reshape_fns.to_1d, param_list))
            param_list = list(zip(*list(itertools.product(*param_list))))
            param_list = list(map(np.asarray, param_list))
        else:
            # Broadcast such that each array has the same length
            param_list = reshape_fns.broadcast(*param_list, writeable=True)
    # Perform main calculation
    if pass_lists:
        output_list = custom_func(ts_list, param_list, *args, **kwargs)
    else:
        output_list = custom_func(*ts_list, *param_list, *args, **kwargs)
    if return_raw or kwargs.get('return_cache', False):
        return output_list  # return raw cache outputs
    if not isinstance(output_list, (tuple, list, List)):
        output_list = [output_list]
    else:
        output_list = list(output_list)
    # Other outputs should be returned without post-processing (for example cache_dict)
    if len(output_list) > num_outputs:
        other_list = output_list[num_outputs:]
    else:
        other_list = []
    # Process only the num_outputs outputs
    output_list = output_list[:num_outputs]
    if len(param_list) > 0:
        # Build new column levels on top of time series levels
        new_columns = build_column_hierarchy(
            param_list, level_names,
            reshape_fns.to_2d(ts_list[0]).columns)
        # Wrap into new pandas objects both time series and output objects
        new_ts_list = list(
            map(lambda x: broadcast_ts(x, param_list[0].shape[0], new_columns),
                ts_list))
        # Build mappers to easily map between parameters and columns
        mapper_list = [
            build_mapper(x, ts_list[0], new_columns, level_names[i])
            for i, x in enumerate(param_list)
        ]
    else:
        # Some indicators don't have any params
        new_columns = reshape_fns.to_2d(ts_list[0]).columns
        new_ts_list = list(ts_list)
        mapper_list = []
    output_list = list(
        map(lambda x: wrap_output(x, ts_list[0], new_columns), output_list))
    if len(mapper_list) > 1:
        # Tuple object is a mapper that accepts tuples of parameters
        tuple_mapper = build_tuple_mapper(mapper_list, new_columns,
                                          tuple(level_names))
        mapper_list.append(tuple_mapper)
    return new_ts_list, output_list, param_list, mapper_list, other_list
Beispiel #25
0
    def to_2d_array(self):
        """Convert to 2-dim NumPy array.

        See `vectorbt.utils.reshape_fns.to_2d`."""
        return reshape_fns.to_2d(self._obj, raw=True)
Beispiel #26
0
    def combine_with_multiple(self,
                              others,
                              *args,
                              combine_func=None,
                              pass_2d=False,
                              concat=False,
                              broadcast_kwargs={},
                              as_columns=None,
                              **kwargs):
        """Combine with `others` using `combine_func`.

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

        If `concat` is `True`, concatenate the results along columns, 
        see `vectorbt.utils.combine_fns.combine_and_concat`.
        Otherwise, pairwise combine into a Series/DataFrame of the same shape, 
        see `vectorbt.utils.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 `as_columns` as a top-level column level.

        !!! note
            If `combine_func` is Numba-compiled, will broadcast using `writeable=True` and
            copy using `order='C'` 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 pandas as pd
            >>> sr = pd.Series([1, 2], index=['x', 'y'])
            >>> df = pd.DataFrame([[3, 4], [5, 6]], index=['x', 'y'], columns=['a', 'b'])

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

            >>> print(sr.vbt.combine_with_multiple([df, df*2], 
            ...     combine_func=lambda x, y: x + y, concat=True, as_columns=['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 writable arrays
            broadcast_kwargs = {**dict(writeable=True), **broadcast_kwargs}
            # Plus all of our arrays must be in the same order
            broadcast_kwargs['copy_kwargs'] = {
                **dict(order='C'),
                **broadcast_kwargs.get('copy_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(np.asarray(x)),
                    (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_same_meta(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 as_columns is not None:
                new_columns = index_fns.combine_indexes(as_columns, columns)
            else:
                new_columns = index_fns.tile_index(columns, len(others))
            return new_obj.vbt.wrap_array(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_same_dtype(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_array(result)
Beispiel #27
0
    def from_orders(cls,
                    price,
                    order_size,
                    order_price=None,
                    init_capital=None,
                    fees=None,
                    fixed_fees=None,
                    slippage=None,
                    is_target=False,
                    broadcast_kwargs={},
                    **kwargs):
        """Build portfolio from orders.

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

        Args:
            price (pandas_like): Main price of the asset, such as close.
            order_size (int, float or array_like): The amount of shares to order. 

                If the size is positive, this is the number of shares to buy. 
                If the size is negative, this is the number of shares to sell.
                To buy/sell everything, set the size to `numpy.inf`.
            order_price (array_like): Order price. Defaults to `price`.
            init_capital (int or float): The initial capital.
            fees (float or array_like): Fees in percentage of the order value.
            fixed_fees (float or array_like): Fixed amount of fees to pay per order.
            slippage (float or array_like): Slippage in percentage of `order_price`.
            is_target (bool): If `True`, will order the difference between current and target size.
            **kwargs: Keyword arguments passed to the `__init__` method.

        For defaults, see `vectorbt.defaults.portfolio`.

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

        Example:
            Portfolio from 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, fixed_fees=1., slippage=0.001)

            >>> print(portfolio.order_records)
                Column  Index        Size  Price      Fees  Side
            0      0.0    0.0   98.654463  1.001  1.246883   0.0
            1      1.0    0.0    1.000000  1.001  1.002502   0.0
            2      1.0    1.0    1.000000  2.002  1.005005   0.0
            3      1.0    2.0    1.000000  3.003  1.007507   0.0
            4      1.0    3.0    1.000000  2.002  1.005005   0.0
            5      1.0    4.0    4.000000  0.999  1.009990   1.0
            6      2.0    0.0   98.654463  1.001  1.246883   0.0
            7      2.0    1.0   98.654463  1.998  1.492779   1.0
            8      2.0    2.0   64.646521  3.003  1.485334   0.0
            9      2.0    3.0   64.646521  1.998  1.322909   1.0
            10     2.0    4.0  126.398131  1.001  1.316311   0.0
            >>> print(portfolio.shares)
                                a    b           c
            2018-01-01  98.654463  1.0   98.654463
            2018-01-02  98.654463  2.0    0.000000
            2018-01-03  98.654463  3.0   64.646521
            2018-01-04  98.654463  4.0    0.000000
            2018-01-05  98.654463  0.0  126.398131
            >>> print(portfolio.cash)
                          a          b             c
            2018-01-01  0.0  97.996498  0.000000e+00
            2018-01-02  0.0  94.989493  1.956188e+02
            2018-01-03  0.0  90.978985  2.842171e-14
            2018-01-04  0.0  87.971980  1.278408e+02
            2018-01-05  0.0  90.957990  0.000000e+00
            ```
        """
        # Get defaults
        if order_price is None:
            order_price = price
        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 fixed_fees is None:
            fixed_fees = defaults.portfolio['fixed_fees']
        if slippage is None:
            slippage = defaults.portfolio['slippage']

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

        # Broadcast inputs
        price, order_size, order_price, fees, fixed_fees, slippage = \
            reshape_fns.broadcast(price, order_size, order_price, fees, fixed_fees,
                                  slippage, **broadcast_kwargs, writeable=True)

        # Perform calculation
        order_records, cash, shares = nb.simulate_from_orders_nb(
            reshape_fns.to_2d(price, raw=True).shape, init_capital,
            reshape_fns.to_2d(order_size, raw=True),
            reshape_fns.to_2d(order_price, raw=True),
            reshape_fns.to_2d(fees, raw=True),
            reshape_fns.to_2d(fixed_fees, raw=True),
            reshape_fns.to_2d(slippage, raw=True), is_target)

        # Bring to the same meta
        cash = price.vbt.wrap(cash)
        shares = price.vbt.wrap(shares)

        return cls(price, init_capital, order_records, cash, shares, **kwargs)
Beispiel #28
0
    def from_order_func(cls,
                        price,
                        order_func_nb,
                        *args,
                        init_capital=None,
                        **kwargs):
        """Build portfolio from a custom order function.

        Starting with initial capital `init_capital`, iterates over shape `price.shape`, and for 
        each data point, generates an order using `order_func_nb`. This way, you can specify order 
        size, price and transaction costs dynamically (for example, based on the current balance).

        To iterate over a bigger shape than `price`, you should tile/repeat `price` to the desired shape.

        Args:
            price (pandas_like): Main price of the asset, such as close.

                Must be a pandas object.
            order_func_nb (function): Function that returns an order. 

                See `vectorbt.portfolio.enums.Order`.
            *args: Arguments passed to `order_func_nb`.
            init_capital (int or float): The initial capital.
            **kwargs: Keyword arguments passed to the `__init__` method.

        For defaults, see `vectorbt.defaults.portfolio`.

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

        Example:
            Portfolio from buying daily:
            ```python-repl
            >>> from vectorbt.portfolio.nb import Order

            >>> @njit
            ... def order_func_nb(col, i, run_cash, run_shares, price):
            ...     return Order(10, price[i], fees=0.01, fixed_fees=1., slippage=0.01)

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

            >>> print(portfolio.order_records)
               Column  Index  Size  Price   Fees  Side
            0     0.0    0.0  10.0   1.01  1.101   0.0
            1     0.0    1.0  10.0   2.02  1.202   0.0
            2     0.0    2.0  10.0   3.03  1.303   0.0
            3     0.0    3.0  10.0   2.02  1.202   0.0
            4     0.0    4.0  10.0   1.01  1.101   0.0
            >>> print(portfolio.shares)
            2018-01-01    10.0
            2018-01-02    20.0
            2018-01-03    30.0
            2018-01-04    40.0
            2018-01-05    50.0
            Name: a, dtype: float64
            >>> print(portfolio.cash)
            2018-01-01    88.799
            2018-01-02    67.397
            2018-01-03    35.794
            2018-01-04    14.392
            2018-01-05     3.191
            Name: a, dtype: float64
            ```
        """
        # Get defaults
        if init_capital is None:
            init_capital = defaults.portfolio['init_capital']
        init_capital = float(init_capital)

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

        # Perform calculation
        order_records, cash, shares = nb.simulate_nb(
            reshape_fns.to_2d(price, raw=True).shape, init_capital,
            order_func_nb, *args)

        # Bring to the same meta
        cash = price.vbt.wrap(cash)
        shares = price.vbt.wrap(shares)

        return cls(price, init_capital, order_records, cash, shares, **kwargs)
Beispiel #29
0
    def combine_with_multiple(self,
                              others,
                              *args,
                              combine_func=None,
                              concat=False,
                              broadcast_kwargs={},
                              as_columns=None,
                              **kwargs):
        """Broadcast with other objects to the same shape and combine them all pairwise.

        The returned shape is the same as broadcasted shape if concat is False.
        The returned shape is concatenation of broadcasted shapes if concat is True."""
        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 writable arrays
            broadcast_kwargs = {**dict(writeable=True), **broadcast_kwargs}
            # Plus all of our arrays must be in the same order
            broadcast_kwargs['copy_kwargs'] = {
                **dict(order='C'),
                **broadcast_kwargs.get('copy_kwargs', {})
            }
        new_obj, *new_others = reshape_fns.broadcast(self._obj, *others,
                                                     **broadcast_kwargs)
        broadcasted = tuple(map(np.asarray, (new_obj, *new_others)))
        if concat:
            # Concat the results horizontally
            if checks.is_numba_func(combine_func):
                for i in range(1, len(broadcasted)):
                    # NOTE: all inputs must have the same dtype
                    checks.assert_same_meta(broadcasted[i - 1], broadcasted[i])
                result = combine_fns.combine_and_concat_nb(
                    broadcasted[0], broadcasted[1:], combine_func, *args,
                    **kwargs)
            else:
                result = combine_fns.combine_and_concat(
                    broadcasted[0], broadcasted[1:], combine_func, *args,
                    **kwargs)
            if as_columns is not None:
                new_columns = index_fns.combine(
                    as_columns,
                    reshape_fns.to_2d(new_obj).columns)
            else:
                new_columns = index_fns.tile(
                    reshape_fns.to_2d(new_obj).columns, len(others))
            return new_obj.vbt.wrap_array(result, columns=new_columns)
        else:
            # Combine arguments pairwise into one object
            if checks.is_numba_func(combine_func):
                for i in range(1, len(broadcasted)):
                    # NOTE: all inputs must have the same dtype
                    checks.assert_same_dtype(broadcasted[i - 1],
                                             broadcasted[i])
                result = combine_fns.combine_multiple_nb(
                    broadcasted, combine_func, *args, **kwargs)
            else:
                result = combine_fns.combine_multiple(broadcasted,
                                                      combine_func, *args,
                                                      **kwargs)
            return new_obj.vbt.wrap_array(result)
Beispiel #30
0
 def to_2d_array(self):
     return reshape_fns.to_2d(self._obj, raw=True)