예제 #1
0
    def from_order_func(cls,
                        main_price,
                        order_func_nb,
                        *args,
                        init_capital=None,
                        row_wise=False,
                        row_prep_func_nb=None,
                        broadcast_kwargs={},
                        freq=None,
                        **kwargs):
        """Build portfolio from a custom order function.

        Starting with initial capital `init_capital`, iterates over shape `main_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).

        if `row_wise` is `True`, see `vectorbt.portfolio.nb.simulate_row_wise_nb`.
        Otherwise, see `vectorbt.portfolio.nb.simulate_nb`.

        Args:
            main_price (pandas_like): Main price of the asset, such as close. Will broadcast.
            order_func_nb (function): Function that returns an order.

                See `vectorbt.portfolio.enums.Order`.
            *args: Arguments passed to `order_func_nb`.
            init_capital (float or array_like): The initial capital. Will broadcast.

                Allowed is either a single value or value per column.
            row_wise (bool): If `True`, iterates over rows, otherwise over columns.

                Set to `True` if columns depend upon each other.
            row_prep_func_nb (function): Function to call before iterating over the next row.

                Can be used to do preprocessing, such as to calculate past returns.
            broadcast_kwargs: Keyword arguments passed to `vectorbt.base.reshape_fns.broadcast`.
            freq (any): Index frequency in case `main_price.index` is not datetime-like.
            **kwargs: Keyword arguments passed to the `__init__` method.

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

        All time series will be broadcasted together using `vectorbt.base.reshape_fns.broadcast`.
        At the end, they will have the same metadata.

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

        Example:
            Placing a buy order each day:
            ```python-repl
            >>> from vectorbt.portfolio import Order, SizeType

            >>> @njit
            ... def order_func_nb(oc, price):
            ...     return Order(10, SizeType.Shares, price[oc.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)

            >>> portfolio.orders.records
               col  idx  size  price   fees  side
            0    0    0  10.0   1.01  1.101     0
            1    0    1  10.0   2.02  1.202     0
            2    0    2  10.0   3.03  1.303     0
            3    0    3  10.0   2.02  1.202     0
            4    0    4  10.0   1.01  1.101     0
            >>> portfolio.equity
            2020-01-01     98.799
            2020-01-02    107.397
            2020-01-03    125.794
            2020-01-04     94.392
            2020-01-05     53.191
            Name: a, dtype: float64
            ```
        """
        # Get defaults
        if init_capital is None:
            init_capital = defaults.portfolio['init_capital']

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

        # Broadcast inputs
        # Only main_price is broadcasted, others can remain unchanged thanks to flexible indexing
        keep_raw = (False, True)
        main_price, init_capital = reshape_fns.broadcast(main_price,
                                                         init_capital,
                                                         **broadcast_kwargs,
                                                         writeable=True,
                                                         keep_raw=keep_raw)
        target_shape = (main_price.shape[0],
                        main_price.shape[1] if main_price.ndim > 1 else 1)

        # Perform calculation
        if row_wise:
            if row_prep_func_nb is None:
                row_prep_func_nb = nb.none_row_prep_func_nb
            order_records, cash, shares = nb.simulate_row_wise_nb(
                target_shape, init_capital, row_prep_func_nb, order_func_nb,
                *args)
        else:
            if row_prep_func_nb is not None:
                raise ValueError(
                    "Function row_prep_func_nb can be only called when row_wise=True"
                )
            order_records, cash, shares = nb.simulate_nb(
                target_shape, init_capital, order_func_nb, *args)

        # Bring to the same meta
        cash = main_price.vbt.wrap(cash)
        shares = main_price.vbt.wrap(shares)
        orders = Orders(order_records, main_price, freq=freq)
        if checks.is_series(main_price):
            init_capital = init_capital.item(0)
        else:
            init_capital = np.broadcast_to(init_capital, (target_shape[1], ))
            init_capital = main_price.vbt.wrap_reduced(init_capital)

        return cls(main_price,
                   init_capital,
                   orders,
                   cash,
                   shares,
                   freq=freq,
                   **kwargs)
예제 #2
0
    def from_signals(cls,
                     main_price,
                     entries,
                     exits,
                     size=np.inf,
                     size_type=SizeType.Shares,
                     entry_price=None,
                     exit_price=None,
                     init_capital=None,
                     fees=None,
                     fixed_fees=None,
                     slippage=None,
                     accumulate=False,
                     broadcast_kwargs={},
                     freq=None,
                     **kwargs):
        """Build portfolio from entry and exit signals.

        For each signal in `entries`, buys `size` of shares for `entry_price` to enter
        a position. For each signal in `exits`, sells everything for `exit_price`
        to exit the position. Accumulation of orders is disabled by default.

        For more details, see `vectorbt.portfolio.nb.simulate_from_signals_nb`.

        Args:
            main_price (pandas_like): Main price of the asset, such as close. Will broadcast.
            entries (array_like): Boolean array of entry signals. Will broadcast.
            exits (array_like): Boolean array of exit signals. Will broadcast.
            size (float or array_like): The amount of shares to order. Will broadcast.

                To buy/sell everything, set the size to `np.inf`.
            size_type (int or array_like): See `vectorbt.portfolio.enums.SizeType`.

                Only `SizeType.Shares` and `SizeType.Cash` are supported.
            entry_price (array_like): Entry price. Defaults to `main_price`. Will broadcast.
            exit_price (array_like): Exit price. Defaults to `main_price`. Will broadcast.
            init_capital (float or array_like): The initial capital. Will broadcast.

                Allowed is either a single value or value per column.
            fees (float or array_like): Fees in percentage of the order value. Will broadcast.
            fixed_fees (float or array_like): Fixed amount of fees to pay per order. Will broadcast.
            slippage (float or array_like): Slippage in percentage of price. Will broadcast.
            accumulate (bool): If `accumulate` is `True`, entering the market when already
                in the market will be allowed to increase a position.
            broadcast_kwargs: Keyword arguments passed to `vectorbt.base.reshape_fns.broadcast`.
            freq (any): Index frequency in case `main_price.index` is not datetime-like.
            **kwargs: Keyword arguments passed to the `__init__` method.

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

        All time series will be broadcasted together using `vectorbt.base.reshape_fns.broadcast`.
        At the end, they will have the same metadata.

        Example:
            Portfolio from various signal sequences:
            ```python-repl
            >>> entries = pd.DataFrame({
            ...     'a': [True, False, False, False, False],
            ...     'b': [True, False, True, False, True],
            ...     'c': [True, True, True, True, True]
            ... }, index=index)
            >>> exits = pd.DataFrame({
            ...     'a': [False, False, False, False, False],
            ...     'b': [False, True, False, True, False],
            ...     'c': [True, True, True, True, True]
            ... }, index=index)
            >>> portfolio = vbt.Portfolio.from_signals(
            ...     price, entries, exits, size=10,
            ...     init_capital=100, fees=0.0025, fixed_fees=1., slippage=0.001)

            >>> portfolio.orders.records
               col  idx  size  price      fees  side
            0    0    0  10.0  1.001  1.025025     0
            1    1    0  10.0  1.001  1.025025     0
            2    1    1  10.0  1.998  1.049950     1
            3    1    2  10.0  3.003  1.075075     0
            4    1    3  10.0  1.998  1.049950     1
            5    1    4  10.0  1.001  1.025025     0
            6    2    0  10.0  1.001  1.025025     0
            >>> portfolio.equity
                                 a           b           c
            2020-01-01   98.964975   98.964975   98.964975
            2020-01-02  108.964975  107.895025  108.964975
            2020-01-03  118.964975  106.789950  118.964975
            2020-01-04  108.964975   95.720000  108.964975
            2020-01-05   98.964975   94.684975   98.964975
            ```
        """
        # Get defaults
        if entry_price is None:
            entry_price = main_price
        if exit_price is None:
            exit_price = main_price
        if init_capital is None:
            init_capital = defaults.portfolio['init_capital']
        if size is None:
            size = defaults.portfolio['size']
        if size_type is None:
            size_type = defaults.portfolio['size_type']
        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(main_price, (pd.Series, pd.DataFrame))
        checks.assert_dtype(entries, np.bool_)
        checks.assert_dtype(exits, np.bool_)

        # Broadcast inputs
        # Only main_price is broadcasted, others can remain unchanged thanks to flexible indexing
        keep_raw = (False, True, True, True, True, True, True, True, True,
                    True, True)
        main_price, entries, exits, size, size_type, entry_price, \
            exit_price, fees, fixed_fees, slippage, init_capital = \
            reshape_fns.broadcast(
                main_price, entries, exits, size, size_type, entry_price, exit_price, fees,
                fixed_fees, slippage, init_capital, **broadcast_kwargs,
                writeable=True, keep_raw=keep_raw)
        target_shape = (main_price.shape[0],
                        main_price.shape[1] if main_price.ndim > 1 else 1)

        # Perform calculation
        order_records, cash, shares = nb.simulate_from_signals_nb(
            target_shape,
            init_capital,
            entries,
            exits,
            size,
            size_type,
            entry_price,
            exit_price,
            fees,
            fixed_fees,
            slippage,
            accumulate,
            is_2d=main_price.ndim == 2)

        # Bring to the same meta
        cash = main_price.vbt.wrap(cash)
        shares = main_price.vbt.wrap(shares)
        orders = Orders(order_records, main_price, freq=freq)
        if checks.is_series(main_price):
            init_capital = init_capital.item(0)
        else:
            init_capital = np.broadcast_to(init_capital, (target_shape[1], ))
            init_capital = main_price.vbt.wrap_reduced(init_capital)

        return cls(main_price,
                   init_capital,
                   orders,
                   cash,
                   shares,
                   freq=freq,
                   **kwargs)
예제 #3
0
    def from_orders(cls,
                    main_price,
                    order_size,
                    size_type=SizeType.Shares,
                    order_price=None,
                    init_capital=None,
                    fees=None,
                    fixed_fees=None,
                    slippage=None,
                    broadcast_kwargs={},
                    freq=None,
                    **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`.

        For more details, see `vectorbt.portfolio.nb.simulate_from_orders_nb`.

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

                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 `np.inf`.
            size_type (int or array_like): See `vectorbt.portfolio.enums.SizeType`.
            order_price (array_like): Order price. Defaults to `main_price`. Will broadcast.
            init_capital (float or array_like): The initial capital. Will broadcast.

                Allowed is either a single value or value per column.
            fees (float or array_like): Fees in percentage of the order value. Will broadcast.
            fixed_fees (float or array_like): Fixed amount of fees to pay per order. Will broadcast.
            slippage (float or array_like): Slippage in percentage of price. Will broadcast.
            broadcast_kwargs: Keyword arguments passed to `vectorbt.base.reshape_fns.broadcast`.
            freq (any): Index frequency in case `main_price.index` is not datetime-like.
            **kwargs: Keyword arguments passed to the `__init__` method.

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

        All time series will be broadcasted together using `vectorbt.base.reshape_fns.broadcast`.
        At the end, they will have the same metadata.

        Example:
            Portfolio from various order sequences:
            ```python-repl
            >>> portfolio = vbt.Portfolio.from_orders(price, orders,
            ...     init_capital=100, fees=0.0025, fixed_fees=1., slippage=0.001)

            >>> portfolio.orders.records
                col  idx        size  price      fees  side
            0     0    0   98.654463  1.001  1.246883     0
            1     1    0    1.000000  1.001  1.002502     0
            2     1    1    1.000000  2.002  1.005005     0
            3     1    2    1.000000  3.003  1.007507     0
            4     1    3    1.000000  2.002  1.005005     0
            5     1    4    4.000000  0.999  1.009990     1
            6     2    0   98.654463  1.001  1.246883     0
            7     2    1   98.654463  1.998  1.492779     1
            8     2    2   64.646521  3.003  1.485334     0
            9     2    3   64.646521  1.998  1.322909     1
            10    2    4  126.398131  1.001  1.316311     0
            >>> portfolio.equity
                                 a          b           c
            2020-01-01   98.654463  98.996498   98.654463
            2020-01-02  197.308925  98.989493  195.618838
            2020-01-03  295.963388  99.978985  193.939564
            2020-01-04  197.308925  95.971980  127.840840
            2020-01-05   98.654463  90.957990  126.398131
            ```
        """
        # Get defaults
        if order_price is None:
            order_price = main_price
        if init_capital is None:
            init_capital = defaults.portfolio['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(main_price, (pd.Series, pd.DataFrame))

        # Broadcast inputs
        # Only main_price is broadcasted, others can remain unchanged thanks to flexible indexing
        keep_raw = (False, True, True, True, True, True, True, True)
        main_price, order_size, size_type, order_price, fees, fixed_fees, slippage, init_capital = \
            reshape_fns.broadcast(
                main_price, order_size, size_type, order_price, fees, fixed_fees, slippage, init_capital,
                **broadcast_kwargs, writeable=True, keep_raw=keep_raw)
        target_shape = (main_price.shape[0],
                        main_price.shape[1] if main_price.ndim > 1 else 1)

        # Perform calculation
        order_records, cash, shares = nb.simulate_from_orders_nb(
            target_shape,
            init_capital,
            order_size,
            size_type,
            order_price,
            fees,
            fixed_fees,
            slippage,
            is_2d=main_price.ndim == 2)

        # Bring to the same meta
        cash = main_price.vbt.wrap(cash)
        shares = main_price.vbt.wrap(shares)
        orders = Orders(order_records, main_price, freq=freq)
        if checks.is_series(main_price):
            init_capital = init_capital.item(0)
        else:
            init_capital = np.broadcast_to(init_capital, (target_shape[1], ))
            init_capital = main_price.vbt.wrap_reduced(init_capital)

        return cls(main_price,
                   init_capital,
                   orders,
                   cash,
                   shares,
                   freq=freq,
                   **kwargs)
예제 #4
0
파일: base.py 프로젝트: khprash/vectorbt
    def from_order_func(cls, main_price, order_func_nb, *args, init_capital=None, freq=None, **kwargs):
        """Build portfolio from a custom order function.

        Starting with initial capital `init_capital`, iterates over shape `main_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 `main_price`, you should tile/repeat `main_price` to the desired shape.

        Args:
            main_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, float or array_like): The initial capital.

                Single value or value per column.
            freq (any): Index frequency in case `main_price.index` is not datetime-like.
            **kwargs: Keyword arguments passed to the `__init__` method.

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

        All time series will be broadcasted together using `vectorbt.base.reshape_fns.broadcast`.
        At the end, they will have the same metadata.

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

        Example:
            Portfolio from buying daily:
            ```python-repl
            >>> from vectorbt.portfolio 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.orders.records)
               col  idx  size  price   fees  side
            0    0    0  10.0   1.01  1.101     0
            1    0    1  10.0   2.02  1.202     0
            2    0    2  10.0   3.03  1.303     0
            3    0    3  10.0   2.02  1.202     0
            4    0    4  10.0   1.01  1.101     0
            >>> print(portfolio.equity)
            2020-01-01     98.799
            2020-01-02    107.397
            2020-01-03    125.794
            2020-01-04     94.392
            2020-01-05     53.191
            Name: a, dtype: float64
            ```
        """
        # Get defaults
        if init_capital is None:
            init_capital = defaults.portfolio['init_capital']

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

        # Broadcast inputs
        target_shape = (main_price.shape[0], main_price.shape[1] if main_price.ndim > 1 else 1)
        init_capital = np.broadcast_to(init_capital, (target_shape[1],))

        # Perform calculation
        order_records, cash, shares = nb.simulate_nb(
            target_shape,
            init_capital,
            order_func_nb,
            *args)

        # Bring to the same meta
        wrapper = ArrayWrapper.from_obj(main_price, freq=freq)
        cash = wrapper.wrap(cash)
        shares = wrapper.wrap(shares)
        orders = Orders(order_records, main_price, freq=freq)
        if checks.is_series(main_price):
            init_capital = init_capital[0]
        else:
            init_capital = wrapper.wrap_reduced(init_capital)

        return cls(main_price, init_capital, orders, cash, shares, freq=freq, **kwargs)