Example #1
0
    def from_ts(cls, ts, idx_field='end_idx', **kwargs):
        """Build `Drawdowns` from time series `ts`.

        `**kwargs` such as `freq` will be passed to `Drawdowns.__init__`."""
        records_arr = nb.find_drawdowns_nb(ts.vbt.to_2d_array())
        wrapper = ArrayWrapper.from_obj(ts, **kwargs)
        return cls(wrapper, records_arr, ts, idx_field=idx_field)
Example #2
0
    def from_ts(cls: tp.Type[RangesT],
                ts: tp.ArrayLike,
                gap_value: tp.Optional[tp.Scalar] = None,
                attach_ts: bool = True,
                wrapper_kwargs: tp.KwargsLike = None,
                **kwargs) -> RangesT:
        """Build `Ranges` from time series `ts`.

        Searches for sequences of

        * True values in boolean data (False acts as a gap),
        * positive values in integer data (-1 acts as a gap), and
        * non-NaN values in any other data (NaN acts as a gap).

        `**kwargs` will be passed to `Ranges.__init__`."""
        if wrapper_kwargs is None:
            wrapper_kwargs = {}

        ts_pd = to_pd_array(ts)
        ts_arr = to_2d_array(ts_pd)
        if gap_value is None:
            if np.issubdtype(ts_arr.dtype, np.bool_):
                gap_value = False
            elif np.issubdtype(ts_arr.dtype, np.integer):
                gap_value = -1
            else:
                gap_value = np.nan
        records_arr = nb.find_ranges_nb(ts_arr, gap_value)
        wrapper = ArrayWrapper.from_obj(ts_pd, **wrapper_kwargs)
        return cls(wrapper,
                   records_arr,
                   ts=ts_pd if attach_ts else None,
                   **kwargs)
Example #3
0
def _mapped_indexing_func(obj, pd_indexing_func):
    """Perform indexing on `MappedArray`."""
    if obj.wrapper.ndim == 1:
        raise TypeError("Indexing on Series is not supported")

    n_rows = len(obj.wrapper.index)
    n_cols = len(obj.wrapper.columns)
    col_mapper = obj.wrapper.wrap(
        np.broadcast_to(np.arange(n_cols), (n_rows, n_cols)))
    col_mapper = pd_indexing_func(col_mapper)
    if not pd.Index.equals(col_mapper.index, obj.wrapper.index):
        raise NotImplementedError(
            "Changing index (time axis) is not supported")

    new_cols = reshape_fns.to_1d(col_mapper.values[0])  # array required
    new_indices, new_col_arr = nb.select_mapped_cols_nb(
        obj.col_arr, obj.col_index, new_cols)
    new_mapped_arr = obj.mapped_arr[new_indices]
    if obj.idx_arr is not None:
        new_idx_arr = obj.idx_arr[new_indices]
    else:
        new_idx_arr = None
    new_wrapper = ArrayWrapper.from_obj(col_mapper, freq=obj.wrapper.freq)
    return obj.__class__(new_mapped_arr,
                         new_col_arr,
                         new_wrapper,
                         idx_arr=new_idx_arr)
Example #4
0
    def repeat(self, n: int, keys: tp.Optional[tp.IndexLike] = None, axis: int = 1,
               wrap_kwargs: tp.KwargsLike = None) -> tp.SeriesFrame:
        """See `vectorbt.base.reshape_fns.repeat`.

        Set `axis` to 1 for columns and 0 for index.
        Use `keys` as the outermost level."""
        repeated = reshape_fns.repeat(self.obj, n, axis=axis)
        if keys is not None:
            if axis == 1:
                new_columns = index_fns.combine_indexes([self.wrapper.columns, keys])
                return ArrayWrapper.from_obj(repeated).wrap(
                    repeated.values, **merge_dicts(dict(columns=new_columns), wrap_kwargs))
            else:
                new_index = index_fns.combine_indexes([self.wrapper.index, keys])
                return ArrayWrapper.from_obj(repeated).wrap(
                    repeated.values, **merge_dicts(dict(index=new_index), wrap_kwargs))
        return repeated
Example #5
0
    def from_ts(cls: tp.Type[DrawdownsT], ts: tp.ArrayLike, idx_field: str = 'end_idx', **kwargs) -> DrawdownsT:
        """Build `Drawdowns` from time series `ts`.

        `**kwargs` such as `freq` will be passed to `Drawdowns.__init__`."""
        pd_ts = to_pd_array(ts)
        records_arr = nb.find_drawdowns_nb(pd_ts.vbt.to_2d_array())
        wrapper = ArrayWrapper.from_obj(pd_ts, **kwargs)
        return cls(wrapper, records_arr, pd_ts, idx_field=idx_field)
Example #6
0
    def __init__(self, obj, freq=None):
        if not checks.is_pandas(obj):  # parent accessor
            obj = obj._obj
        self._obj = obj

        # Initialize array wrapper
        wrapper = ArrayWrapper.from_obj(obj)
        ArrayWrapper.__init__(self, index=wrapper.index, columns=wrapper.columns, ndim=wrapper.ndim, freq=freq)
Example #7
0
    def __init__(self,
                 main_price,
                 init_capital,
                 orders,
                 cash,
                 shares,
                 freq=None,
                 year_freq=None,
                 levy_alpha=None,
                 risk_free=None,
                 required_return=None,
                 cutoff=None,
                 factor_returns=None,
                 incl_unrealized_stats=False):
        # Perform checks
        checks.assert_type(main_price, (pd.Series, pd.DataFrame))
        if checks.is_frame(main_price):
            checks.assert_type(init_capital, pd.Series)
            checks.assert_same(main_price.columns, init_capital.index)
        else:
            checks.assert_ndim(init_capital, 0)
        checks.assert_same_meta(main_price, cash)
        checks.assert_same_meta(main_price, shares)

        # Store passed arguments
        self._main_price = main_price
        self._init_capital = init_capital
        self._orders = orders
        self._cash = cash
        self._shares = shares
        self._incl_unrealized_stats = incl_unrealized_stats

        freq = main_price.vbt(freq=freq).freq
        if freq is None:
            raise ValueError(
                "Couldn't parse the frequency of index. You must set `freq`.")
        self._freq = freq

        year_freq = main_price.vbt.returns(year_freq=year_freq).year_freq
        if freq is None:
            raise ValueError("You must set `year_freq`.")
        self._year_freq = year_freq

        # Parameters
        self._levy_alpha = defaults.portfolio[
            'levy_alpha'] if levy_alpha is None else levy_alpha
        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
        self._factor_returns = defaults.portfolio[
            'factor_returns'] if factor_returns is None else factor_returns

        # Supercharge
        PandasIndexer.__init__(self, _indexing_func)
        self.wrapper = ArrayWrapper.from_obj(main_price, freq=freq)
Example #8
0
    def __init__(self, records_arr, ts, freq=None, idx_field='end_idx'):
        Records.__init__(self,
                         records_arr,
                         ArrayWrapper.from_obj(ts, freq=freq),
                         idx_field=idx_field)
        PandasIndexer.__init__(self, _indexing_func)

        if not all(field in records_arr.dtype.names
                   for field in drawdown_dt.names):
            raise ValueError(
                "Records array must have all fields defined in drawdown_dt")

        self.ts = ts
Example #9
0
    def __init__(self, records_arr, main_price, freq=None, idx_field='idx'):
        Records.__init__(self,
                         records_arr,
                         ArrayWrapper.from_obj(main_price, freq=freq),
                         idx_field=idx_field)
        PandasIndexer.__init__(self, _indexing_func)

        if not all(field in records_arr.dtype.names
                   for field in order_dt.names):
            raise ValueError(
                "Records array must have all fields defined in order_dt")

        self.main_price = main_price
Example #10
0
    def __init__(self, obj: tp.SeriesFrame, wrapper: tp.Optional[ArrayWrapper] = None, **kwargs) -> None:
        checks.assert_instance_of(obj, (pd.Series, pd.DataFrame))

        self._obj = obj

        wrapper_arg_names = get_func_arg_names(ArrayWrapper.__init__)
        grouper_arg_names = get_func_arg_names(ColumnGrouper.__init__)
        wrapping_kwargs = dict()
        for k in list(kwargs.keys()):
            if k in wrapper_arg_names or k in grouper_arg_names:
                wrapping_kwargs[k] = kwargs.pop(k)
        if wrapper is None:
            wrapper = ArrayWrapper.from_obj(obj, **wrapping_kwargs)
        else:
            wrapper = wrapper.replace(**wrapping_kwargs)
        Wrapping.__init__(self, wrapper, obj=obj, **kwargs)
Example #11
0
    def from_ts(cls: tp.Type[DrawdownsT],
                ts: tp.ArrayLike,
                attach_ts: bool = True,
                wrapper_kwargs: tp.KwargsLike = None,
                **kwargs) -> DrawdownsT:
        """Build `Drawdowns` from time series `ts`.

        `**kwargs` will be passed to `Drawdowns.__init__`."""
        ts_pd = to_pd_array(ts)
        records_arr = nb.get_drawdowns_nb(to_2d_array(ts_pd))
        wrapper = ArrayWrapper.from_obj(ts_pd, **merge_dicts({},
                                                             wrapper_kwargs))
        return cls(wrapper,
                   records_arr,
                   ts=ts_pd if attach_ts else None,
                   **kwargs)
Example #12
0
def indexing_on_records(obj, pd_indexing_func):
    """Perform indexing on `Records`."""
    if obj.wrapper.ndim == 1:
        raise TypeError("Indexing on Series is not supported")

    n_rows = len(obj.wrapper.index)
    n_cols = len(obj.wrapper.columns)
    col_mapper = obj.wrapper.wrap(
        np.broadcast_to(np.arange(n_cols), (n_rows, n_cols)))
    col_mapper = pd_indexing_func(col_mapper)
    if not pd.Index.equals(col_mapper.index, obj.wrapper.index):
        raise NotImplementedError(
            "Changing index (time axis) is not supported")

    new_cols = reshape_fns.to_1d(col_mapper.values[0])  # array required
    records = nb.select_record_cols_nb(obj.records_arr, obj.col_index,
                                       new_cols)
    wrapper = ArrayWrapper.from_obj(col_mapper, freq=obj.wrapper.freq)
    return records, wrapper
Example #13
0
        def __init__(self, ts_list, output_list, param_list, mapper_list,
                     name):
            perform_init_checks(ts_list, output_list, param_list, mapper_list,
                                name)

            for i, ts_name in enumerate(ts_names):
                setattr(self, f'_{ts_name}', ts_list[i])
            self.wrapper = ArrayWrapper.from_obj(ts_list[0])
            for i, output_name in enumerate(output_names):
                setattr(self, f'_{output_name}', output_list[i])
            for i, param_name in enumerate(param_names):
                setattr(self, f'_{param_name}_array', param_list[i])
                setattr(self, f'_{param_name}_mapper', mapper_list[i])
            if len(param_names) > 1:
                setattr(self, '_tuple_mapper', mapper_list[-1])
            setattr(self, '_name', name)

            # Initialize indexers
            PandasIndexer.__init__(self, indexing_func)
            ParamIndexer.__init__(self, mapper_list, indexing_func)
Example #14
0
    def combine(self,
                other: tp.MaybeTupleList[tp.Union[tp.ArrayLike,
                                                  "BaseAccessor"]],
                *args,
                allow_multiple: bool = True,
                combine_func: tp.Optional[tp.Callable] = None,
                keep_pd: bool = False,
                to_2d: bool = False,
                concat: bool = False,
                numba_loop: bool = False,
                use_ray: bool = False,
                broadcast: bool = True,
                broadcast_kwargs: tp.KwargsLike = None,
                keys: tp.Optional[tp.IndexLike] = None,
                wrap_kwargs: tp.KwargsLike = None,
                **kwargs) -> tp.SeriesFrame:
        """Combine with `other` using `combine_func`.

        Args:
            other (array_like): Object to combine this array with.
            *args: Variable arguments passed to `combine_func`.
            allow_multiple (bool): Whether a tuple/list will be considered as multiple objects in `other`.
            combine_func (callable): Function to combine two arrays.

                Can be Numba-compiled.
            keep_pd (bool): Whether to keep inputs as pandas objects, otherwise convert to NumPy arrays.
            to_2d (bool): Whether to reshape inputs to 2-dim arrays, otherwise keep as-is.
            concat (bool): Whether to concatenate the results along the column axis.
                Otherwise, pairwise combine into a Series/DataFrame of the same shape.

                If True, see `vectorbt.base.combine_fns.combine_and_concat`.
                If False, see `vectorbt.base.combine_fns.combine_multiple`.
            numba_loop (bool): Whether to loop using Numba.

                Set to True when iterating large number of times over small input,
                but note that Numba doesn't support variable keyword arguments.
            use_ray (bool): Whether to use Ray to execute `combine_func` in parallel.

                Only works with `numba_loop` set to False and `concat` is set to True.
                See `vectorbt.base.combine_fns.ray_apply` for related keyword arguments.
            broadcast (bool): Whether to broadcast all inputs.
            broadcast_kwargs (dict): Keyword arguments passed to `vectorbt.base.reshape_fns.broadcast`.
            keys (index_like): Outermost column level.
            wrap_kwargs (dict): Keyword arguments passed to `vectorbt.base.array_wrapper.ArrayWrapper.wrap`.
            **kwargs: Keyword arguments passed to `combine_func`.

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

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

        ## Example

        ```python-repl
        >>> import vectorbt as vbt
        >>> import pandas as pd

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

        >>> sr.vbt.combine(df, combine_func=lambda x, y: x + y)
           a  b
        x  4  5
        y  7  8

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

        >>> sr.vbt.combine([df, df*2], combine_func=lambda x, y: x + y, concat=True, keys=['c', 'd'])
              c       d
           a  b   a   b
        x  4  5   7   9
        y  7  8  12  14
        ```

        Use Ray for small inputs and large processing times:

        ```python-repl
        >>> def combine_func(a, b):
        ...     time.sleep(1)
        ...     return a + b

        >>> sr = pd.Series([1, 2, 3])

        >>> %timeit sr.vbt.combine([1, 1, 1], combine_func=combine_func)
        3.01 s ± 2.98 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)

        >>> %timeit sr.vbt.combine([1, 1, 1], combine_func=combine_func, concat=True, use_ray=True)
        1.02 s ± 2.32 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
        ```
        """
        if not allow_multiple or not isinstance(other, (tuple, list)):
            others = (other, )
        else:
            others = other
        others = tuple(
            map(lambda x: x.obj if isinstance(x, BaseAccessor) else x, others))
        checks.assert_not_none(combine_func)
        # Broadcast arguments
        if broadcast:
            if broadcast_kwargs is None:
                broadcast_kwargs = {}
            if checks.is_numba_func(combine_func):
                # Numba requires writeable arrays
                # Plus all of our arrays must be in the same order
                broadcast_kwargs = merge_dicts(
                    dict(require_kwargs=dict(requirements=['W', 'C'])),
                    broadcast_kwargs)
            new_obj, *new_others = reshape_fns.broadcast(
                self.obj, *others, **broadcast_kwargs)
        else:
            new_obj, new_others = self.obj, others
        if not checks.is_pandas(new_obj):
            new_obj = ArrayWrapper.from_shape(new_obj.shape).wrap(new_obj)
        # Optionally cast to 2d array
        if to_2d:
            inputs = tuple(
                map(lambda x: reshape_fns.to_2d(x, raw=not keep_pd),
                    (new_obj, *new_others)))
        else:
            if not keep_pd:
                inputs = tuple(
                    map(lambda x: np.asarray(x), (new_obj, *new_others)))
            else:
                inputs = new_obj, *new_others
        if len(inputs) == 2:
            result = combine_func(inputs[0], inputs[1], *args, **kwargs)
            return ArrayWrapper.from_obj(new_obj).wrap(
                result, **merge_dicts({}, wrap_kwargs))
        if concat:
            # Concat the results horizontally
            if checks.is_numba_func(combine_func) and numba_loop:
                if use_ray:
                    raise ValueError("Ray cannot be used within Numba")
                for i in range(1, len(inputs)):
                    checks.assert_meta_equal(inputs[i - 1], inputs[i])
                result = combine_fns.combine_and_concat_nb(
                    inputs[0], inputs[1:], combine_func, *args, **kwargs)
            else:
                if use_ray:
                    result = combine_fns.combine_and_concat_ray(
                        inputs[0], inputs[1:], combine_func, *args, **kwargs)
                else:
                    result = combine_fns.combine_and_concat(
                        inputs[0], inputs[1:], combine_func, *args, **kwargs)
            columns = ArrayWrapper.from_obj(new_obj).columns
            if keys is not None:
                new_columns = index_fns.combine_indexes([keys, columns])
            else:
                top_columns = pd.Index(np.arange(len(new_others)),
                                       name='combine_idx')
                new_columns = index_fns.combine_indexes([top_columns, columns])
            return ArrayWrapper.from_obj(new_obj).wrap(
                result, **merge_dicts(dict(columns=new_columns), wrap_kwargs))
        else:
            # Combine arguments pairwise into one object
            if use_ray:
                raise ValueError("Ray cannot be used with concat=False")
            if checks.is_numba_func(combine_func) and numba_loop:
                for i in range(1, len(inputs)):
                    checks.assert_dtype_equal(inputs[i - 1], inputs[i])
                result = combine_fns.combine_multiple_nb(
                    inputs, combine_func, *args, **kwargs)
            else:
                result = combine_fns.combine_multiple(inputs, combine_func,
                                                      *args, **kwargs)
            return ArrayWrapper.from_obj(new_obj).wrap(
                result, **merge_dicts({}, wrap_kwargs))
Example #15
0
    def from_signals(cls, main_price, entries, exits, size=np.inf, 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.

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

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

                To buy/sell everything, set the size to `np.inf`.
            entry_price (array_like): Entry price. Defaults to `main_price`.
            exit_price (array_like): Exit price. Defaults to `main_price`.
            init_capital (int, float or array_like): The initial capital.

                Single value or value per column.
            fees (float or array_like): Fees in percentage of the order value.

                Single value, value per column, or value per element.
            fixed_fees (float or array_like): Fixed amount of fees to pay per order.

                Single value, value per column, or value per element.
            slippage (float or array_like): Slippage in percentage of price.

                Single value, value per column, or value per element.
            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)

            >>> print(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
            >>> print(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 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
        main_price, entries, exits, size, entry_price, exit_price, fees, fixed_fees, slippage = \
            reshape_fns.broadcast(
                main_price, entries, exits, size, entry_price, exit_price, fees,
                fixed_fees, slippage, **broadcast_kwargs, writeable=True)
        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_from_signals_nb(
            target_shape,
            init_capital,
            reshape_fns.to_2d(entries, raw=True),
            reshape_fns.to_2d(exits, raw=True),
            reshape_fns.to_2d(size, raw=True),
            reshape_fns.to_2d(entry_price, raw=True),
            reshape_fns.to_2d(exit_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),
            accumulate)

        # 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)
Example #16
0
 def __init__(self, obj, **kwargs):
     if not checks.is_pandas(obj):  # parent accessor
         obj = obj._obj
     self._obj = obj
     self._wrapper = ArrayWrapper.from_obj(obj, **kwargs)
Example #17
0
    def from_data(cls: tp.Type[DataT],
                  data: tp.Data,
                  tz_localize: tp.Optional[tp.TimezoneLike] = None,
                  tz_convert: tp.Optional[tp.TimezoneLike] = None,
                  missing_index: tp.Optional[str] = None,
                  missing_columns: tp.Optional[str] = None,
                  wrapper_kwargs: tp.KwargsLike = None,
                  **kwargs) -> DataT:
        """Create a new `Data` instance from (aligned) data.

        Args:
            data (dict): Dictionary of array-like objects keyed by symbol.
            tz_localize (timezone_like): If the index is tz-naive, convert to a timezone.

                See `vectorbt.utils.datetime_.to_timezone`.
            tz_convert (timezone_like): Convert the index from one timezone to another.

                See `vectorbt.utils.datetime_.to_timezone`.
            missing_index (str): See `Data.align_index`.
            missing_columns (str): See `Data.align_columns`.
            wrapper_kwargs (dict): Keyword arguments passed to `vectorbt.base.array_wrapper.ArrayWrapper`.
            **kwargs: Keyword arguments passed to the `__init__` method.

        For defaults, see `data` in `vectorbt._settings.settings`."""
        from vectorbt._settings import settings
        data_cfg = settings['data']

        # Get global defaults
        if tz_localize is None:
            tz_localize = data_cfg['tz_localize']
        if tz_convert is None:
            tz_convert = data_cfg['tz_convert']
        if missing_index is None:
            missing_index = data_cfg['missing_index']
        if missing_columns is None:
            missing_columns = data_cfg['missing_columns']
        if wrapper_kwargs is None:
            wrapper_kwargs = {}

        data = data.copy()
        for k, v in data.items():
            # Convert array to pandas
            if not isinstance(v, (pd.Series, pd.DataFrame)):
                v = np.asarray(v)
                if v.ndim == 1:
                    v = pd.Series(v)
                else:
                    v = pd.DataFrame(v)

            # Perform operations with datetime-like index
            if isinstance(v.index, pd.DatetimeIndex):
                if tz_localize is not None:
                    if not is_tz_aware(v.index):
                        v = v.tz_localize(to_timezone(tz_localize))
                if tz_convert is not None:
                    v = v.tz_convert(to_timezone(tz_convert))
                v.index.freq = v.index.inferred_freq
            data[k] = v

        # Align index and columns
        data = cls.align_index(data, missing=missing_index)
        data = cls.align_columns(data, missing=missing_columns)

        # Create new instance
        symbols = list(data.keys())
        wrapper = ArrayWrapper.from_obj(data[symbols[0]], **wrapper_kwargs)
        return cls(
            wrapper,
            data,
            tz_localize=tz_localize,
            tz_convert=tz_convert,
            missing_index=missing_index,
            missing_columns=missing_columns,
            **kwargs
        )
Example #18
0
    def from_orders(cls, main_price, order_size, order_price=None, init_capital=None, fees=None, fixed_fees=None,
                    slippage=None, is_target=False, 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`.

        Args:
            main_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 `np.inf`.
            order_price (array_like): Order price. Defaults to `main_price`.
            init_capital (int, float or array_like): The initial capital.

                Single value or value per column.
            fees (float or array_like): Fees in percentage of the order value.

                Single value, value per column, or value per element.
            fixed_fees (float or array_like): Fixed amount of fees to pay per order.

                Single value, value per column, or value per element.
            slippage (float or array_like): Slippage in percentage of price.

                Single value, value per column, or value per element.
            is_target (bool): If `True`, will order the difference between current and target size.
            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)

            >>> print(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
            >>> print(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
        main_price, order_size, order_price, fees, fixed_fees, slippage = \
            reshape_fns.broadcast(main_price, order_size, order_price, fees, fixed_fees,
                                  slippage, **broadcast_kwargs, writeable=True)
        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_from_orders_nb(
            target_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
        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)
Example #19
0
    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)