Ejemplo n.º 1
0
from scipy.stats import skew, kurtosis

from vectorbt import _typing as tp
from vectorbt.root_accessors import register_dataframe_accessor, register_series_accessor
from vectorbt.utils import checks
from vectorbt.utils.config import merge_dicts
from vectorbt.utils.figure import make_figure
from vectorbt.utils.decorators import cached_property, cached_method
from vectorbt.base.reshape_fns import to_1d, to_2d, broadcast_to
from vectorbt.generic.drawdowns import Drawdowns
from vectorbt.generic.accessors import (GenericAccessor, GenericSRAccessor,
                                        GenericDFAccessor)
from vectorbt.utils.datetime import freq_to_timedelta, DatetimeIndexes
from vectorbt.returns import nb, metrics

ReturnsAccessorT = tp.TypeVar("ReturnsAccessorT", bound="ReturnsAccessor")


class ReturnsAccessor(GenericAccessor):
    """Accessor on top of return series. For both, Series and DataFrames.

    Accessible through `pd.Series.vbt.returns` and `pd.DataFrame.vbt.returns`."""
    def __init__(self,
                 obj: tp.SeriesFrame,
                 year_freq: tp.Optional[tp.FrequencyLike] = None,
                 **kwargs) -> None:
        if not checks.is_pandas(obj):  # parent accessor
            obj = obj._obj

        GenericAccessor.__init__(self, obj, **kwargs)
Ejemplo n.º 2
0
"""

import numpy as np
import pandas as pd

from vectorbt import _typing as tp
from vectorbt.utils import checks
from vectorbt.utils.decorators import cached_method
from vectorbt.utils.config import merge_dicts
from vectorbt.base.reshape_fns import to_1d
from vectorbt.base.array_wrapper import ArrayWrapper, Wrapping
from vectorbt.records import nb
from vectorbt.records.mapped_array import MappedArray
from vectorbt.records.col_mapper import ColumnMapper

RecordsT = tp.TypeVar("RecordsT", bound="Records")
IndexingMetaT = tp.Tuple[ArrayWrapper, tp.RecordArray, tp.MaybeArray,
                         tp.Array1d]


class Records(Wrapping):
    """Wraps the actual records array (such as trades) and exposes methods for mapping
    it to some array of values (such as P&L of each trade).

    Args:
        wrapper (ArrayWrapper): Array wrapper.

            See `vectorbt.base.array_wrapper.ArrayWrapper`.
        records_arr (array_like): A structured NumPy array of records.

            Must have the fields `id` (record index) and `col` (column index).
Ejemplo n.º 3
0
    Accessors do not utilize caching.

    Grouping is only supported by the methods that accept the `group_by` argument."""

import numpy as np
import pandas as pd

from vectorbt import _typing as tp
from vectorbt.base import combine_fns, index_fns, reshape_fns
from vectorbt.base.array_wrapper import ArrayWrapper, Wrapping
from vectorbt.base.column_grouper import ColumnGrouper
from vectorbt.utils import checks
from vectorbt.utils.config import merge_dicts, get_func_arg_names
from vectorbt.utils.decorators import class_or_instancemethod, attach_binary_magic_methods, attach_unary_magic_methods

BaseAccessorT = tp.TypeVar("BaseAccessorT", bound="BaseAccessor")


@attach_binary_magic_methods(lambda self, other, np_func: self.combine(
    other, allow_multiple=False, combine_func=np_func))
@attach_unary_magic_methods(
    lambda self, np_func: self.apply(apply_func=np_func))
class BaseAccessor(Wrapping):
    """Accessor on top of Series and DataFrames.

    Accessible through `pd.Series.vbt` and `pd.DataFrame.vbt`, and all child accessors.

    Series is just a DataFrame with one column, hence to avoid defining methods exclusively for 1-dim data,
    we will convert any Series to a DataFrame and perform matrix computation on it. Afterwards,
    by using `BaseAccessor.wrapper`, we will convert the 2-dim output back to a Series.
Ejemplo n.º 4
0
"""

ranges_attach_field_config = Config(dict(status=dict(attach_filters=True)),
                                    readonly=True,
                                    as_attrs=False)
"""_"""

__pdoc__[
    'ranges_attach_field_config'] = f"""Config of fields to be attached to `Ranges`.

```json
{ranges_attach_field_config.to_doc()}
```
"""

RangesT = tp.TypeVar("RangesT", bound="Ranges")


@attach_fields(ranges_attach_field_config)
@override_field_config(ranges_field_config)
class Ranges(Records):
    """Extends `Records` for working with range records.

    Requires `records_arr` to have all fields defined in `vectorbt.generic.enums.range_dt`."""
    @property
    def field_config(self) -> Config:
        return self._field_config

    def __init__(self,
                 wrapper: ArrayWrapper,
                 records_arr: tp.RecordArray,
Ejemplo n.º 5
0
to each structured object and constructing the new user-defined class using them. This way,
one can manipulate complex classes with dozens of pandas objects using a single command."""

import numpy as np
import pandas as pd

from vectorbt import _typing as tp
from vectorbt.utils import checks
from vectorbt.base import index_fns, reshape_fns


class IndexingError(Exception):
    """Exception raised when an indexing error has occurred."""


IndexingBaseT = tp.TypeVar("IndexingBaseT", bound="IndexingBase")


class IndexingBase:
    """Class that supports indexing through `IndexingBase.indexing_func`."""
    def indexing_func(self: IndexingBaseT, pd_indexing_func: tp.Callable,
                      **kwargs) -> IndexingBaseT:
        """Apply `pd_indexing_func` on all pandas objects in question and return a new instance of the class.

        Should be overridden."""
        raise NotImplementedError


class LocBase:
    """Class that implements location-based indexing."""
    def __init__(self, indexing_func: tp.Callable, **kwargs) -> None:
Ejemplo n.º 6
0
                        if p.kind in (Parameter.POSITIONAL_ONLY,
                                      Parameter.POSITIONAL_OR_KEYWORD) else p
                        for p in list(source_sig.parameters.values())[1:]
                    ]
                    source_sig = source_sig.replace(parameters=(self_arg, ) +
                                                    tuple(other_args))
                    new_method.__signature__ = source_sig

                new_method.__doc__ = f"See `quantstats.{module_name}.{qs_func_name}`."
                new_method.__qualname__ = f"{cls.__name__}.{new_method_name}"
                new_method.__name__ = new_method_name
                setattr(cls, new_method_name, new_method)
    return cls


QSAdapterT = tp.TypeVar("QSAdapterT", bound="QSAdapter")


@attach_qs_methods
class QSAdapter(Configured):
    """Adapter class for quantstats."""
    def __init__(self,
                 returns_accessor: ReturnsAccessor,
                 defaults: tp.KwargsLike = None,
                 **kwargs) -> None:
        checks.assert_instance_of(returns_accessor, ReturnsAccessor)

        Configured.__init__(self,
                            returns_accessor=returns_accessor,
                            defaults=defaults,
                            **kwargs)
Ejemplo n.º 7
0
from vectorbt.utils.datetime_ import is_tz_aware, to_timezone
from vectorbt.utils.decorators import cached_method

__pdoc__ = {}


class symbol_dict(dict):
    """Dict that contains symbols as keys."""
    pass


class MetaData(type(StatsBuilderMixin), type(PlotsBuilderMixin)):
    pass


DataT = tp.TypeVar("DataT", bound="Data")


class Data(Wrapping, StatsBuilderMixin, PlotsBuilderMixin, metaclass=MetaData):
    """Class that downloads, updates, and manages data coming from a data source."""

    def __init__(self,
                 wrapper: ArrayWrapper,
                 data: tp.Data,
                 tz_localize: tp.Optional[tp.TimezoneLike],
                 tz_convert: tp.Optional[tp.TimezoneLike],
                 missing_index: str,
                 missing_columns: str,
                 download_kwargs: dict,
                 **kwargs) -> None:
        Wrapping.__init__(
Ejemplo n.º 8
0
import pandas as pd

from vectorbt import _typing as tp
from vectorbt.base.array_wrapper import ArrayWrapper, Wrapping
from vectorbt.base.reshape_fns import to_1d_array, to_dict
from vectorbt.generic import nb as generic_nb
from vectorbt.generic.plots_builder import PlotsBuilderMixin
from vectorbt.generic.stats_builder import StatsBuilderMixin
from vectorbt.records import nb
from vectorbt.records.col_mapper import ColumnMapper
from vectorbt.utils import checks
from vectorbt.utils.config import merge_dicts, Config, Configured
from vectorbt.utils.decorators import cached_method, attach_binary_magic_methods, attach_unary_magic_methods
from vectorbt.utils.mapping import to_mapping, apply_mapping

MappedArrayT = tp.TypeVar("MappedArrayT", bound="MappedArray")
IndexingMetaT = tp.Tuple[ArrayWrapper, tp.Array1d, tp.Array1d, tp.Array1d,
                         tp.Optional[tp.Array1d], tp.MaybeArray, tp.Array1d]


def combine_mapped_with_other(
    self: MappedArrayT, other: tp.Union["MappedArray", tp.ArrayLike],
    np_func: tp.Callable[[tp.ArrayLike, tp.ArrayLike], tp.Array1d]
) -> MappedArrayT:
    """Combine `MappedArray` with other compatible object.

    If other object is also `MappedArray`, their `id_arr` and `col_arr` must match."""
    if isinstance(other, MappedArray):
        checks.assert_array_equal(self.id_arr, other.id_arr)
        checks.assert_array_equal(self.col_arr, other.col_arr)
        other = other.values
Ejemplo n.º 9
0
            attach_filters=True
        )
    ),
    readonly=True,
    as_attrs=False
)
"""_"""

__pdoc__['orders_attach_field_config'] = f"""Config of fields to be attached to `Orders`.

```json
{orders_attach_field_config.to_doc()}
```
"""

OrdersT = tp.TypeVar("OrdersT", bound="Orders")


@attach_fields(orders_attach_field_config)
@override_field_config(orders_field_config)
class Orders(Records):
    """Extends `Records` for working with order records."""

    @property
    def field_config(self) -> Config:
        return self._field_config

    def __init__(self,
                 wrapper: ArrayWrapper,
                 records_arr: tp.RecordArray,
                 close: tp.Optional[tp.ArrayLike] = None,
Ejemplo n.º 10
0
    res_side=dict(attach_filters=True),
    res_status=dict(attach_filters=True),
    res_status_info=dict(attach_filters=True)),
                                  readonly=True,
                                  as_attrs=False)
"""_"""

__pdoc__[
    'logs_attach_field_config'] = f"""Config of fields to be attached to `Logs`.

```json
{logs_attach_field_config.to_doc()}
```
"""

LogsT = tp.TypeVar("LogsT", bound="Logs")


@attach_fields(logs_attach_field_config)
@override_field_config(logs_field_config)
class Logs(Records):
    """Extends `Records` for working with log records."""
    @property
    def field_config(self) -> Config:
        return self._field_config

    # ############# Stats ############# #

    @property
    def stats_defaults(self) -> tp.Kwargs:
        """Defaults for `Logs.stats`.
Ejemplo n.º 11
0
    if dct is None:
        return {}
    dct_copy = type(dct)()
    for k, v in dct.items():
        if isinstance(v, dict):
            dct_copy[k] = copy_dict(v)
        else:
            dct_copy[k] = copy(v)
    return dct_copy


_RaiseKeyError = object()

DumpTuple = namedtuple('DumpTuple', ('cls', 'dumps'))

PickleableT = tp.TypeVar("PickleableT", bound="Pickleable")


class Pickleable:
    """Superclass that defines abstract properties and methods for pickle-able classes."""
    def dumps(self, **kwargs) -> bytes:
        """Pickle to bytes."""
        raise NotImplementedError

    @classmethod
    def loads(cls: tp.Type[PickleableT], dumps: bytes,
              **kwargs) -> PickleableT:
        """Unpickle from bytes."""
        raise NotImplementedError

    def save(self, fname: tp.Union[str, Path], **kwargs) -> None:
Ejemplo n.º 12
0
        if end is not None:
            end = to_tzaware_datetime(end, tz=get_local_tz())

        return yf.Ticker(symbol).history(period=period, start=start, end=end, **kwargs)

    def update_symbol(self, symbol: tp.Label, **kwargs) -> tp.Frame:
        """Update the symbol.

        `**kwargs` will override keyword arguments passed to `YFData.download_symbol`."""
        download_kwargs = self.select_symbol_kwargs(symbol, self.download_kwargs)
        download_kwargs['start'] = self.data[symbol].index[-1]
        kwargs = merge_dicts(download_kwargs, kwargs)
        return self.download_symbol(symbol, **kwargs)


BinanceDataT = tp.TypeVar("BinanceDataT", bound="BinanceData")


class BinanceData(Data):
    """`Data` for data coming from `python-binance`.

    Usage:
        * Fetch the 1-minute data of the last 2 hours, wait 1 minute, and update:

        ```pycon
        >>> import vectorbt as vbt

        >>> binance_data = vbt.BinanceData.download(
        ...     "BTCUSDT",
        ...     start='2 hours ago UTC',
        ...     end='now UTC',
Ejemplo n.º 13
0
from vectorbt.utils.config import merge_dicts
from vectorbt.utils.datetime import DatetimeIndexes
from vectorbt.utils.enum import enum_to_value_map
from vectorbt.utils.figure import make_figure, get_domain
from vectorbt.utils.array import min_rel_rescale, max_rel_rescale
from vectorbt.base.reshape_fns import to_1d, to_2d, broadcast_to
from vectorbt.base.array_wrapper import ArrayWrapper
from vectorbt.records.base import Records
from vectorbt.records.mapped_array import MappedArray
from vectorbt.portfolio.enums import TradeDirection, TradeStatus, trade_dt, position_dt, TradeType
from vectorbt.portfolio import nb
from vectorbt.portfolio.orders import Orders

# ############# Trades ############# #

TradesT = tp.TypeVar("TradesT", bound="Trades")


class Trades(Records):
    """Extends `Records` for working with trade records.

    In vectorbt, a trade is a partial closing operation; it's is a more fine-grained representation
    of a position. One position can incorporate multiple trades. Performance for this operation is
    calculated based on the size-weighted average of previous opening operations within the same
    position. The PnL of all trades combined always equals to the PnL of the entire position.

    For example, if you have a single large buy operation and 100 small sell operations, you will see
    100 trades, each opening with a fraction of the buy operation's size and fees. On the other hand,
    having 100 buy operations and just a single sell operation will generate a single trade with buy
    price being a size-weighted average over all purchase prices, and opening size and fees being
    the sum over all sizes and fees.
Ejemplo n.º 14
0
                result,
                attr,
                getattr_func=getattr_func,
                call_last_attr=True
            )
        else:
            result = deep_getattr(
                result,
                attr,
                getattr_func=getattr_func,
                call_last_attr=call_last_attr
            )
    return result


AttrResolverT = tp.TypeVar("AttrResolverT", bound="AttrResolver")


class AttrResolver:
    """Class that implements resolution of self and its attributes.

    Resolution is `getattr` that works for self, properties, and methods. It also utilizes built-in caching."""

    @property
    def self_aliases(self) -> tp.Set[str]:
        """Names to associate with this object."""
        return {'self'}

    def resolve_self(self: AttrResolverT,
                     cond_kwargs: tp.KwargsLike = None,
                     custom_arg_names: tp.Optional[tp.Set[str]] = None,
Ejemplo n.º 15
0
    def __init__(self, func: tp.Callable) -> None:
        self.func = func
        self.__doc__ = getattr(func, '__doc__')

    def __get__(self,
                instance: object,
                owner: tp.Optional[tp.Type] = None) -> tp.Any:
        if instance is None:
            return self.func(owner)
        return self.func(instance)

    def __set__(self, instance: object, value: tp.Any) -> None:
        raise AttributeError("can't set attribute")


custom_propertyT = tp.TypeVar("custom_propertyT", bound="custom_property")


class custom_property:
    """Custom property that stores function and flags as attributes.

    Can be called both as
    ```pycon
    >>> @custom_property
    ... def user_function(self): pass
    ```
    and
    ```plaintext
    >>> @custom_property(a=0, b=0)  # flags
    ... def user_function(self): pass
    ```
Ejemplo n.º 16
0
import numpy as np
import pandas as pd

from vectorbt import _typing as tp
from vectorbt.base import index_fns, reshape_fns
from vectorbt.base.column_grouper import ColumnGrouper
from vectorbt.base.indexing import IndexingError, PandasIndexer
from vectorbt.base.reshape_fns import to_pd_array
from vectorbt.utils import checks
from vectorbt.utils.array_ import get_ranges_arr
from vectorbt.utils.attr_ import AttrResolver, AttrResolverT
from vectorbt.utils.config import Configured, merge_dicts
from vectorbt.utils.datetime_ import freq_to_timedelta, DatetimeIndexes
from vectorbt.utils.decorators import cached_method

ArrayWrapperT = tp.TypeVar("ArrayWrapperT", bound="ArrayWrapper")
IndexingMetaT = tp.Tuple[ArrayWrapperT, tp.MaybeArray, tp.MaybeArray,
                         tp.Array1d]


class ArrayWrapper(Configured, PandasIndexer):
    """Class that stores index, columns and shape metadata for wrapping NumPy arrays.
    Tightly integrated with `vectorbt.base.column_grouper.ColumnGrouper`.

    If the underlying object is a Series, pass `[sr.name]` as `columns`.

    `**kwargs` are passed to `vectorbt.base.column_grouper.ColumnGrouper`.

    !!! note
        This class is meant to be immutable. To change any attribute, use `ArrayWrapper.replace`.
Ejemplo n.º 17
0
"""

dd_attach_field_config = Config(dict(status=dict(attach_filters=True)),
                                readonly=True,
                                as_attrs=False)
"""_"""

__pdoc__[
    'dd_attach_field_config'] = f"""Config of fields to be attached to `Drawdowns`.

```json
{dd_attach_field_config.to_doc()}
```
"""

DrawdownsT = tp.TypeVar("DrawdownsT", bound="Drawdowns")


@attach_fields(dd_attach_field_config)
@override_field_config(dd_field_config)
class Drawdowns(Ranges):
    """Extends `vectorbt.generic.ranges.Ranges` for working with drawdown records.

    Requires `records_arr` to have all fields defined in `vectorbt.generic.enums.drawdown_dt`."""
    @property
    def field_config(self) -> Config:
        return self._field_config

    def __init__(self,
                 wrapper: ArrayWrapper,
                 records_arr: tp.RecordArray,
Ejemplo n.º 18
0
So, for example, the method `pd.Series.vbt.to_2d_array` is also available as
`pd.Series.vbt.returns.to_2d_array`.

!!! note
    Accessors in vectorbt are not cached, so querying `df.vbt` twice will also call `Vbt_DFAccessor` twice."""

import warnings

import pandas as pd
from pandas.core.accessor import DirNamesMixin

from vectorbt import _typing as tp
from vectorbt.generic.accessors import GenericSRAccessor, GenericDFAccessor
from vectorbt.utils.config import Configured

ParentAccessorT = tp.TypeVar("ParentAccessorT", bound=object)
AccessorT = tp.TypeVar("AccessorT", bound=object)


class Accessor:
    """Custom property-like object.

    !!! note
        In contrast to other pandas accessors, this accessor is not cached!

        This prevents from using old data if the object has been changed in-place."""

    def __init__(self, name: str, accessor: tp.Type[AccessorT]) -> None:
        self._name = name
        self._accessor = accessor