def tile(arg: tp.ArrayLike, n: int, axis: int = 1, raw: bool = False) -> tp.AnyArray: """Repeat the whole `arg` `n` times along the specified axis.""" arg = to_any_array(arg, raw=raw) if axis == 0: if arg.ndim == 2: if checks.is_pandas(arg): return array_wrapper.ArrayWrapper.from_obj(arg).wrap( np.tile(arg.values, (n, 1)), index=index_fns.tile_index(arg.index, n)) return np.tile(arg, (n, 1)) if checks.is_pandas(arg): return array_wrapper.ArrayWrapper.from_obj(arg).wrap( np.tile(arg.values, n), index=index_fns.tile_index(arg.index, n)) return np.tile(arg, n) elif axis == 1: arg = to_2d(arg) if checks.is_pandas(arg): return array_wrapper.ArrayWrapper.from_obj(arg).wrap( np.tile(arg.values, (1, n)), columns=index_fns.tile_index(arg.columns, n)) return np.tile(arg, (1, n)) else: raise ValueError("Only axis 0 and 1 are supported")
def tile(arg, n, axis=1): """Repeat the whole `arg` `n` times along the specified axis.""" if not checks.is_array(arg): arg = np.asarray(arg) if axis == 0: if arg.ndim == 2: if checks.is_pandas(arg): return array_wrapper.ArrayWrapper.from_obj(arg).wrap( np.tile(arg.values, (n, 1)), index=index_fns.tile_index(arg.index, n)) return np.tile(arg, (n, 1)) if checks.is_pandas(arg): return array_wrapper.ArrayWrapper.from_obj(arg).wrap( np.tile(arg.values, n), index=index_fns.tile_index(arg.index, n)) return np.tile(arg, n) elif axis == 1: arg = to_2d(arg) if checks.is_pandas(arg): return array_wrapper.ArrayWrapper.from_obj(arg).wrap( np.tile(arg.values, (1, n)), columns=index_fns.tile_index(arg.columns, n)) return np.tile(arg, (1, n)) else: raise ValueError("Only axis 0 and 1 are supported")
def broadcast_to(arg1, arg2, index_from=1, columns_from=1, writeable=False, copy_kwargs={}, raw=False, **kwargs): """Bring first argument to the shape of second argument. Closely resembles the other broadcast function.""" if not checks.is_array_like(arg1): arg1 = np.asarray(arg1) if not checks.is_array_like(arg2): arg2 = np.asarray(arg2) is_2d = arg1.ndim > 1 or arg2.ndim > 1 is_pd = checks.is_pandas(arg1) or checks.is_pandas(arg2) if is_pd: if is_2d: if checks.is_series(arg1): arg1 = arg1.to_frame() if checks.is_series(arg2): arg2 = arg2.to_frame() new_index = broadcast_index(arg1, arg2, index_from=index_from, axis=0, is_2d=is_2d, **kwargs) new_columns = broadcast_index(arg1, arg2, index_from=columns_from, axis=1, is_2d=is_2d, **kwargs) else: new_index, new_columns = None, None if is_broadcasting_needed(arg1, arg2): arg1_new = np.broadcast_to(arg1, arg2.shape, subok=True) arg1_new = np.array(arg1_new, copy=writeable, **copy_kwargs) else: arg1_new = np.array(arg1, copy=False, **copy_kwargs) return wrap_broadcasted(arg1, arg1_new, is_pd=is_pd, new_index=new_index, new_columns=new_columns)
def wrap_broadcasted(old_arg, new_arg, is_pd=False, new_index=None, new_columns=None): """Transform newly broadcasted array to match the type of the original object.""" if is_pd: if checks.is_pandas(old_arg): if new_index is None: # Take index from original pandas object if old_arg.shape[0] == new_arg.shape[0]: new_index = old_arg.index else: new_index = index_fns.repeat(old_arg.index, new_arg.shape[0]) if new_columns is None: # Take columns from original pandas object if new_arg.ndim == 2: if checks.is_series(old_arg): old_arg = old_arg.to_frame() if old_arg.shape[1] == new_arg.shape[1]: new_columns = old_arg.columns else: new_columns = index_fns.repeat(old_arg.columns, new_arg.shape[1]) else: if new_index is None and new_columns is None: # Return plain numpy array if not pandas and no rules set return new_arg return wrap_array(new_arg, index=new_index, columns=new_columns) return new_arg
def __init__(self, obj: tp.SeriesFrame, **kwargs) -> None: if not checks.is_pandas(obj): # parent accessor obj = obj._obj checks.assert_dtype(obj, np.bool_) GenericAccessor.__init__(self, obj, **kwargs)
def wrap_broadcasted(old_arg, new_arg, is_pd=False, new_index=None, new_columns=None): """If the newly brodcasted array was originally a pandas object, make it pandas object again and assign it the newly broadcasted index/columns.""" if is_pd: if checks.is_pandas(old_arg): if new_index is None: # Take index from original pandas object old_index = index_fns.get_index(old_arg, 0) if old_arg.shape[0] == new_arg.shape[0]: new_index = old_index else: new_index = index_fns.repeat_index(old_index, new_arg.shape[0]) if new_columns is None: # Take columns from original pandas object old_columns = index_fns.get_index(old_arg, 1) new_ncols = new_arg.shape[1] if new_arg.ndim == 2 else 1 if len(old_columns) == new_ncols: new_columns = old_columns else: new_columns = index_fns.repeat_index( old_columns, new_ncols) return array_wrapper.ArrayWrapper(index=new_index, columns=new_columns, ndim=new_arg.ndim).wrap(new_arg) return new_arg
def __init__(self, obj, freq=None): if not checks.is_pandas(obj): # parent accessor obj = obj._obj checks.assert_dtype(obj, np.bool) Generic_Accessor.__init__(self, obj, freq=freq)
def concat(self_or_cls, *others, as_columns=None, broadcast_kwargs={}): """Concatenate with `others` along columns. All arguments will be broadcasted using `vectorbt.utils.reshape_fns.broadcast` with `broadcast_kwargs`. Use `as_columns` as a top-level column level. 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.concat(df, as_columns=['c', 'd'])) c d a b a b x 1 1 3 4 y 2 2 5 6 ```""" others = tuple( map(lambda x: x._obj if isinstance(x, Base_Accessor) else x, others)) if isinstance(self_or_cls, type): objs = others else: objs = (self_or_cls._obj, ) + others broadcasted = reshape_fns.broadcast(*objs, **broadcast_kwargs) broadcasted = tuple(map(reshape_fns.to_2d, broadcasted)) if checks.is_pandas(broadcasted[0]): concated = pd.concat(broadcasted, axis=1) if as_columns is not None: concated.columns = index_fns.combine_indexes( as_columns, broadcasted[0].columns) else: concated = np.hstack(broadcasted) return concated
def __init__(self, obj, **kwargs): if not checks.is_pandas(obj): # parent accessor obj = obj._obj checks.assert_dtype(obj, np.bool) GenericAccessor.__init__(self, obj, **kwargs)
def wrap_broadcasted(old_arg, new_arg, is_pd=False, new_index=None, new_columns=None): """If the newly brodcasted array was originally a pandas object, make it pandas object again and assign it the newly broadcast index/columns.""" if is_pd: if checks.is_pandas(old_arg): if new_index is None: # Take index from original pandas object old_index = index_fns.get_index(old_arg, 0) if old_arg.shape[0] == new_arg.shape[0]: new_index = old_index else: new_index = index_fns.repeat_index(old_index, new_arg.shape[0]) if new_columns is None: # Take columns from original pandas object old_columns = index_fns.get_index(old_arg, 1) new_ncols = new_arg.shape[1] if new_arg.ndim == 2 else 1 if len(old_columns) == new_ncols: new_columns = old_columns else: new_columns = index_fns.repeat_index(old_columns, new_ncols) if new_arg.ndim == 2: return pd.DataFrame(new_arg, index=new_index, columns=new_columns) if new_columns is not None and len(new_columns) == 1: name = new_columns[0] if name == 0: name = None else: name = None return pd.Series(new_arg, index=new_index, name=name) return new_arg
def wrap_broadcasted(old_arg, new_arg, is_pd=False, new_index=None, new_columns=None): """If the newly brodcasted array was originally a pandas object, make it pandas object again and assign it the newly broadcasted index/columns.""" if is_pd: if checks.is_pandas(old_arg): if new_index is None: # Take index from original pandas object if old_arg.shape[0] == new_arg.shape[0]: new_index = old_arg.index else: new_index = index_fns.repeat_index(old_arg.index, new_arg.shape[0]) if new_columns is None: # Take columns from original pandas object if new_arg.ndim == 2: if checks.is_series(old_arg): old_arg = old_arg.to_frame() if old_arg.shape[1] == new_arg.shape[1]: new_columns = old_arg.columns else: new_columns = index_fns.repeat_index( old_arg.columns, new_arg.shape[1]) else: if new_index is None and new_columns is None: # Return plain numpy array if not pandas and no rules set return new_arg return ArrayWrapper(index=new_index, columns=new_columns).wrap(new_arg) return new_arg
def soft_broadcast_to_ndim(arg, ndim): """Try to softly bring `arg` to the specified number of dimensions `ndim` (max 2).""" if not checks.is_array(arg): arg = np.asarray(arg) if ndim == 1: if arg.ndim == 2: if arg.shape[1] == 1: if checks.is_pandas(arg): return arg.iloc[:, 0] return arg[:, 0] # downgrade if ndim == 2: if arg.ndim == 1: if checks.is_pandas(arg): return arg.to_frame() return arg[:, None] # upgrade return arg # do nothing
def repeat(arg, n, along_axis=1): """Repeat array n times along specified axis.""" if not checks.is_array_like(arg): arg = np.asarray(arg) if along_axis == 0: if checks.is_pandas(arg): return arg.vbt.wrap_array(np.repeat(arg.values, n, axis=0), index=index_fns.repeat(arg.index, n)) return np.repeat(arg, n, axis=0) elif along_axis == 1: arg = to_2d(arg) if checks.is_pandas(arg): return arg.vbt.wrap_array(np.repeat(arg.values, n, axis=1), columns=index_fns.repeat(arg.columns, n)) return np.repeat(arg, n, axis=1) else: raise ValueError("Only axis 0 and 1 are supported")
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)
def repeat(arg, n, axis=1): """Repeat each element in `arg` `n` times along the specified axis.""" if not checks.is_array(arg): arg = np.asarray(arg) if axis == 0: if checks.is_pandas(arg): return array_wrapper.ArrayWrapper.from_obj(arg).wrap( np.repeat(arg.values, n, axis=0), index=index_fns.repeat_index(arg.index, n)) return np.repeat(arg, n, axis=0) elif axis == 1: arg = to_2d(arg) if checks.is_pandas(arg): return array_wrapper.ArrayWrapper.from_obj(arg).wrap( np.repeat(arg.values, n, axis=1), columns=index_fns.repeat_index(arg.columns, n)) return np.repeat(arg, n, axis=1) else: raise ValueError("Only axis 0 and 1 are supported")
def __init__(self, obj, freq=None, year_freq=None): if not checks.is_pandas(obj): # parent accessor obj = obj._obj Generic_Accessor.__init__(self, obj, freq=freq) # Set year frequency self._year_freq = year_freq
def __init__(self, obj: tp.Frame, column_names: tp.Optional[tp.Dict[str, str]] = None, **kwargs) -> None: if not checks.is_pandas(obj): # parent accessor obj = obj._obj self._column_names = column_names GenericDFAccessor.__init__(self, obj, **kwargs)
def __init__(self, obj: tp.Frame, year_freq: tp.Optional[tp.FrequencyLike] = None, **kwargs) -> None: if not checks.is_pandas(obj): # parent accessor obj = obj._obj GenericDFAccessor.__init__(self, obj, **kwargs) ReturnsAccessor.__init__(self, obj, year_freq=year_freq, **kwargs)
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) # Set year frequency self._year_freq = year_freq
def to_pd_array(arg: tp.ArrayLike) -> tp.SeriesFrame: """Convert any array-like object to a pandas object.""" if checks.is_pandas(arg): return arg arg = np.asarray(arg) if arg.ndim == 1: return pd.Series(arg) if arg.ndim == 2: return pd.DataFrame(arg) raise ValueError( "Wrong number of dimensions: cannot convert to Series or DataFrame")
def repeat(arg: tp.ArrayLike, n: int, axis: int = 1, raw: bool = False) -> tp.AnyArray: """Repeat each element in `arg` `n` times along the specified axis.""" arg = to_any_array(arg, raw=raw) if axis == 0: if checks.is_pandas(arg): return array_wrapper.ArrayWrapper.from_obj(arg).wrap( np.repeat(arg.values, n, axis=0), index=index_fns.repeat_index(arg.index, n)) return np.repeat(arg, n, axis=0) elif axis == 1: arg = to_2d(arg) if checks.is_pandas(arg): return array_wrapper.ArrayWrapper.from_obj(arg).wrap( np.repeat(arg.values, n, axis=1), columns=index_fns.repeat_index(arg.columns, n)) return np.repeat(arg, n, axis=1) else: raise ValueError("Only axis 0 and 1 are supported")
def tile(arg, n, along_axis=1): """Tile array n times along specified axis.""" if not checks.is_array_like(arg): arg = np.asarray(arg) if along_axis == 0: if arg.ndim == 1: if checks.is_pandas(arg): return arg.vbt.wrap_array(np.tile(arg.values, n), index=index_fns.tile(arg.index, n)) return np.tile(arg, n) if arg.ndim == 2: if checks.is_pandas(arg): return arg.vbt.wrap_array(np.tile(arg.values, (n, 1)), index=index_fns.tile(arg.index, n)) return np.tile(arg, (n, 1)) elif along_axis == 1: arg = to_2d(arg) if checks.is_pandas(arg): return arg.vbt.wrap_array(np.tile(arg.values, (1, n)), columns=index_fns.tile(arg.columns, n)) return np.tile(arg, (1, n)) else: raise ValueError("Only axis 0 and 1 are supported")
def broadcast_to(arg1: tp.ArrayLike, arg2: tp.ArrayLike, to_pd: tp.Optional[bool] = None, index_from: tp.Optional[IndexFromLike] = None, columns_from: tp.Optional[IndexFromLike] = None, **kwargs) -> BCRT: """Broadcast `arg1` to `arg2`. Pass None to `index_from`/`columns_from` to use index/columns of the second argument. Keyword arguments `**kwargs` are passed to `broadcast`. ## Example ```python-repl >>> import numpy as np >>> import pandas as pd >>> from vectorbt.base.reshape_fns import broadcast_to >>> a = np.array([1, 2, 3]) >>> sr = pd.Series([4, 5, 6], index=pd.Index(['x', 'y', 'z']), name='a') >>> broadcast_to(a, sr) x 1 y 2 z 3 Name: a, dtype: int64 >>> broadcast_to(sr, a) array([4, 5, 6]) ``` """ arg1 = to_any_array(arg1) arg2 = to_any_array(arg2) if to_pd is None: to_pd = checks.is_pandas(arg2) if to_pd: # Take index and columns from arg2 if index_from is None: index_from = index_fns.get_index(arg2, 0) if columns_from is None: columns_from = index_fns.get_index(arg2, 1) return broadcast(arg1, to_shape=arg2.shape, to_pd=to_pd, index_from=index_from, columns_from=columns_from, **kwargs)
def broadcast_to(arg1, arg2, to_pd=None, index_from=None, columns_from=None, **kwargs): """Broadcast `arg1` to `arg2`. Keyword arguments `**kwargs` are passed to `broadcast`. ## Example ```python-repl >>> import numpy as np >>> import pandas as pd >>> from vectorbt.base.reshape_fns import broadcast_to >>> a = np.array([1, 2, 3]) >>> sr = pd.Series([4, 5, 6], index=pd.Index(['x', 'y', 'z']), name='a') >>> broadcast_to(a, sr) x 1 y 2 z 3 Name: a, dtype: int64 >>> broadcast_to(sr, a) array([4, 5, 6]) ``` """ if not checks.is_array(arg1): arg1 = np.asarray(arg1) if not checks.is_array(arg2): arg2 = np.asarray(arg2) if to_pd is None: to_pd = checks.is_pandas(arg2) if to_pd: # Take index and columns from arg2 if index_from is None: index_from = index_fns.get_index(arg2, 0) if columns_from is None: columns_from = index_fns.get_index(arg2, 1) return broadcast(arg1, to_shape=arg2.shape, to_pd=to_pd, index_from=index_from, columns_from=columns_from, **kwargs)
def concat(self_or_cls, *others, as_columns=None, broadcast_kwargs={}): others = tuple( map(lambda x: x._obj if isinstance(x, Base_Accessor) else x, others)) if isinstance(self_or_cls, type): objs = others else: objs = (self_or_cls._obj, ) + others broadcasted = reshape_fns.broadcast(*objs, **broadcast_kwargs) broadcasted = tuple(map(reshape_fns.to_2d, broadcasted)) if checks.is_pandas(broadcasted[0]): concated = pd.concat(broadcasted, axis=1) if as_columns is not None: concated.columns = index_fns.combine(as_columns, broadcasted[0].columns) else: concated = np.hstack(broadcasted) return concated
def __init__(self, obj, **kwargs): if not checks.is_pandas(obj): # parent accessor obj = obj._obj GenericSRAccessor.__init__(self, obj, **kwargs) SignalsAccessor.__init__(self, obj, **kwargs)
def __init__(self, obj, column_names=None, freq=None): if not checks.is_pandas(obj): # parent accessor obj = obj._obj self._column_names = column_names Generic_DFAccessor.__init__(self, obj, freq=freq)
def broadcast_index(args, to_shape, index_from=None, axis=0, ignore_sr_names=None, **kwargs): """Produce a broadcast index/columns. Args: *args (array_like): Array-like objects. to_shape (tuple): Target shape. index_from (None, int, str or array_like): Broadcasting rule for this index/these columns. Accepts the following values: * 'default' - take the value from `vectorbt.settings.broadcasting` * 'strict' - ensure that all pandas objects have the same index/columns * 'stack' - stack different indexes/columns using `vectorbt.base.index_fns.stack_indexes` * 'ignore' - ignore any index/columns * integer - use the index/columns of the i-nth object in `args` * None - use the original index/columns of the objects in `args` * everything else will be converted to `pd.Index` axis (int): Set to 0 for index and 1 for columns. ignore_sr_names (bool): Whether to ignore Series names if they are in conflict. Conflicting Series names are those that are different but not None. **kwargs: Keyword arguments passed to `vectorbt.base.index_fns.stack_indexes`. For defaults, see `vectorbt.settings.broadcasting`. !!! note Series names are treated as columns with a single element but without a name. If a column level without a name loses its meaning, better to convert Series to DataFrames with one column prior to broadcasting. If the name of a Series is not that important, better to drop it altogether by setting it to None. """ from vectorbt import settings if ignore_sr_names is None: ignore_sr_names = settings.broadcasting['ignore_sr_names'] index_str = 'columns' if axis == 1 else 'index' to_shape_2d = (to_shape[0], 1) if len(to_shape) == 1 else to_shape # maxlen stores the length of the longest index maxlen = to_shape_2d[1] if axis == 1 else to_shape_2d[0] new_index = None if index_from is not None: if isinstance(index_from, int): # Take index/columns of the object indexed by index_from if not checks.is_pandas(args[index_from]): raise TypeError( f"Argument under index {index_from} must be a pandas object" ) new_index = index_fns.get_index(args[index_from], axis) elif isinstance(index_from, str): if index_from == 'ignore': # Ignore index/columns new_index = pd.RangeIndex(start=0, stop=maxlen, step=1) elif index_from in ('stack', 'strict'): # Check whether all indexes/columns are equal last_index = None # of type pd.Index index_conflict = False for arg in args: if checks.is_pandas(arg): index = index_fns.get_index(arg, axis) if last_index is not None: if not pd.Index.equals(index, last_index): index_conflict = True last_index = index continue if not index_conflict: new_index = last_index else: # If pandas objects have different index/columns, stack them together for arg in args: if checks.is_pandas(arg): index = index_fns.get_index(arg, axis) if axis == 1 and checks.is_series( arg) and ignore_sr_names: # ignore Series name continue if checks.is_default_index(index): # ignore simple ranges without name continue if new_index is None: new_index = index else: if index_from == 'strict': # If pandas objects have different index/columns, raise an exception if not pd.Index.equals(index, new_index): raise ValueError( f"Broadcasting {index_str} is not allowed when {index_str}_from=strict" ) # Broadcasting index must follow the rules of a regular broadcasting operation # https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html#general-broadcasting-rules # 1. rule: if indexes are of the same length, they are simply stacked # 2. rule: if index has one element, it gets repeated and then stacked if pd.Index.equals(index, new_index): continue if len(index) != len(new_index): if len(index) > 1 and len(new_index) > 1: raise ValueError( "Indexes could not be broadcast together" ) if len(index) > len(new_index): new_index = index_fns.repeat_index( new_index, len(index)) elif len(index) < len(new_index): index = index_fns.repeat_index( index, len(new_index)) new_index = index_fns.stack_indexes( new_index, index, **kwargs) else: raise ValueError( f"Invalid value {index_from} for {'columns' if axis == 1 else 'index'}_from" ) else: new_index = index_from if new_index is not None: if maxlen > len(new_index): if index_from == 'strict': raise ValueError( f"Broadcasting {index_str} is not allowed when {index_str}_from=strict" ) # This happens only when some numpy object is longer than the new pandas index # In this case, new pandas index (one element) should be repeated to match this length. if maxlen > 1 and len(new_index) > 1: raise ValueError("Indexes could not be broadcast together") new_index = index_fns.repeat_index(new_index, maxlen) elif index_from is not None: # new_index=None can mean two things: 1) take original metadata or 2) reset index/columns # In case when index_from is not None, we choose 2) new_index = pd.RangeIndex(start=0, stop=maxlen, step=1) return new_index
def generate_ohlc_stop_exits(self, open, high=None, low=None, close=None, is_open_safe=True, out_dict=None, sl_stop=0., ts_stop=0., tp_stop=0., entry_wait=1, exit_wait=1, first=True, iteratively=False, broadcast_kwargs=None, wrap_kwargs=None): """Generate exits based on when the price hits (trailing) stop loss or take profit. If any of `high`, `low` or `close` is None, it will be set to `open`. Use `out_dict` as a dict to pass `hit_price` and `stop_type` arrays. You can also set `out_dict` to {} to produce these arrays automatically and still have access to them. For arguments, see `vectorbt.signals.nb.ohlc_stop_choice_nb`. If `iteratively` is True, see `vectorbt.signals.nb.generate_ohlc_stop_ex_iter_nb`. Otherwise, see `vectorbt.signals.nb.generate_ohlc_stop_ex_nb`. All array-like arguments including stops and `out_dict` will broadcast using `vectorbt.base.reshape_fns.broadcast` with `broadcast_kwargs`. For arguments, see `vectorbt.signals.nb.ohlc_stop_choice_nb`. !!! note `open` isn't necessarily open price, but can be any entry price (even previous close). Stop price is calculated based solely upon `open`. ## Example ```python-repl >>> from vectorbt.signals.enums import StopType >>> price = pd.DataFrame({ ... 'open': [10, 11, 12, 11, 10], ... 'high': [11, 12, 13, 12, 11], ... 'low': [9, 10, 11, 10, 9], ... 'close': [10, 11, 12, 11, 10] ... }) >>> out_dict = {} >>> exits = sig.vbt.signals.generate_ohlc_stop_exits( ... price['open'], price['high'], price['low'], price['close'], ... out_dict=out_dict, sl_stop=0.2, ts_stop=0.2, tp_stop=0.2) >>> out_dict['hit_price'][~exits] = np.nan >>> out_dict['stop_type'][~exits] = -1 >>> exits a b c 2020-01-01 False False False 2020-01-02 True True False 2020-01-03 False False False 2020-01-04 False False False 2020-01-05 False False True >>> out_dict['hit_price'] a b c 2020-01-01 NaN NaN NaN 2020-01-02 12.0 12.0 NaN 2020-01-03 NaN NaN NaN 2020-01-04 NaN NaN NaN 2020-01-05 NaN NaN 9.6 >>> out_dict['stop_type'].applymap( ... lambda x: StopType._fields[x] if x in StopType else '') a b c 2020-01-01 2020-01-02 TakeProfit TakeProfit 2020-01-03 2020-01-04 2020-01-05 StopLoss ``` """ if broadcast_kwargs is None: broadcast_kwargs = {} entries = self._obj if high is None: high = open if low is None: low = open if close is None: close = open if out_dict is None: out_dict = {} hit_price_out = out_dict.get('hit_price', None) stop_type_out = out_dict.get('stop_type', None) out_args = () if hit_price_out is not None: out_args += (hit_price_out,) if stop_type_out is not None: out_args += (stop_type_out,) keep_raw = (False, True, True, True, True, True, True, True) + (False,) * len(out_args) broadcast_kwargs = merge_dicts(dict(require_kwargs=dict(requirements='W')), broadcast_kwargs) entries, open, high, low, close, sl_stop, ts_stop, tp_stop, *out_args = reshape_fns.broadcast( entries, open, high, low, close, sl_stop, ts_stop, tp_stop, *out_args, **broadcast_kwargs, keep_raw=keep_raw) if hit_price_out is None: hit_price_out = np.empty_like(entries, dtype=np.float_) else: hit_price_out = out_args[0] if checks.is_pandas(hit_price_out): hit_price_out = hit_price_out.vbt.to_2d_array() out_args = out_args[1:] if stop_type_out is None: stop_type_out = np.empty_like(entries, dtype=np.int_) else: stop_type_out = out_args[0] if checks.is_pandas(stop_type_out): stop_type_out = stop_type_out.vbt.to_2d_array() # Perform generation if iteratively: new_entries, exits = nb.generate_ohlc_stop_ex_iter_nb( entries.vbt.to_2d_array(), open, high, low, close, hit_price_out, stop_type_out, sl_stop, ts_stop, tp_stop, is_open_safe, entry_wait, exit_wait, first, entries.ndim == 2) out_dict['hit_price'] = entries.vbt.wrapper.wrap(hit_price_out, **merge_dicts({}, wrap_kwargs)) out_dict['stop_type'] = entries.vbt.wrapper.wrap(stop_type_out, **merge_dicts({}, wrap_kwargs)) return entries.vbt.wrapper.wrap(new_entries, **merge_dicts({}, wrap_kwargs)), \ entries.vbt.wrapper.wrap(exits, **merge_dicts({}, wrap_kwargs)) else: exits = nb.generate_ohlc_stop_ex_nb( entries.vbt.to_2d_array(), open, high, low, close, hit_price_out, stop_type_out, sl_stop, ts_stop, tp_stop, is_open_safe, exit_wait, first, entries.ndim == 2) out_dict['hit_price'] = entries.vbt.wrapper.wrap(hit_price_out, **merge_dicts({}, wrap_kwargs)) out_dict['stop_type'] = entries.vbt.wrapper.wrap(stop_type_out, **merge_dicts({}, wrap_kwargs)) return entries.vbt.wrapper.wrap(exits, **merge_dicts({}, wrap_kwargs))
def broadcast(*args, to_shape=None, to_pd=None, to_frame=None, align_index=None, align_columns=None, index_from='default', columns_from='default', require_kwargs=None, keep_raw=False, return_meta=False, **kwargs): """Bring any array-like object in `args` to the same shape by using NumPy broadcasting. See [Broadcasting](https://docs.scipy.org/doc/numpy/user/basics.broadcasting.html). Can broadcast pandas objects by broadcasting their index/columns with `broadcast_index`. Args: *args (array_like): Array-like objects. to_shape (tuple): Target shape. If set, will broadcast every element in `args` to `to_shape`. to_pd (bool, tuple or list): Whether to convert all output arrays to pandas, otherwise returns raw NumPy arrays. If None, converts only if there is at least one pandas object among them. to_frame (bool): Whether to convert all Series to DataFrames. align_index (bool): Whether to align index of pandas objects using multi-index. align_columns (bool): Whether to align columns of pandas objects using multi-index. index_from (any): Broadcasting rule for index. columns_from (any): Broadcasting rule for columns. require_kwargs (dict or list of dict): Keyword arguments passed to `np.require`. keep_raw (bool, tuple or list): Whether to keep the unbroadcasted version of the array. Only makes sure that the array can be broadcast to the target shape. return_meta (bool): If True, will also return new shape, index and columns. **kwargs: Keyword arguments passed to `broadcast_index`. For defaults, see `vectorbt.settings.broadcasting`. ## Example Without broadcasting index and columns: ```python-repl >>> import numpy as np >>> import pandas as pd >>> from vectorbt.base.reshape_fns import broadcast >>> v = 0 >>> a = np.array([1, 2, 3]) >>> sr = pd.Series([1, 2, 3], index=pd.Index(['x', 'y', 'z']), name='a') >>> df = pd.DataFrame([[1, 2, 3], [4, 5, 6], [7, 8, 9]], ... index=pd.Index(['x2', 'y2', 'z2']), ... columns=pd.Index(['a2', 'b2', 'c2'])) >>> for i in broadcast( ... v, a, sr, df, ... index_from=None, ... columns_from=None, ... ): print(i) 0 1 2 0 0 0 0 1 0 0 0 2 0 0 0 0 1 2 0 1 2 3 1 1 2 3 2 1 2 3 a a a x 1 1 1 y 2 2 2 z 3 3 3 a2 b2 c2 x2 1 2 3 y2 4 5 6 z2 7 8 9 ``` Taking new index and columns from position: ```python-repl >>> for i in broadcast( ... v, a, sr, df, ... index_from=2, ... columns_from=3 ... ): print(i) a2 b2 c2 x 0 0 0 y 0 0 0 z 0 0 0 a2 b2 c2 x 1 2 3 y 1 2 3 z 1 2 3 a2 b2 c2 x 1 1 1 y 2 2 2 z 3 3 3 a2 b2 c2 x 1 2 3 y 4 5 6 z 7 8 9 ``` Broadcasting index and columns through stacking: ```python-repl >>> for i in broadcast( ... v, a, sr, df, ... index_from='stack', ... columns_from='stack' ... ): print(i) a2 b2 c2 x x2 0 0 0 y y2 0 0 0 z z2 0 0 0 a2 b2 c2 x x2 1 2 3 y y2 1 2 3 z z2 1 2 3 a2 b2 c2 x x2 1 1 1 y y2 2 2 2 z z2 3 3 3 a2 b2 c2 x x2 1 2 3 y y2 4 5 6 z z2 7 8 9 ``` Setting index and columns manually: ```python-repl >>> for i in broadcast( ... v, a, sr, df, ... index_from=['a', 'b', 'c'], ... columns_from=['d', 'e', 'f'] ... ): print(i) d e f a 0 0 0 b 0 0 0 c 0 0 0 d e f a 1 2 3 b 1 2 3 c 1 2 3 d e f a 1 1 1 b 2 2 2 c 3 3 3 d e f a 1 2 3 b 4 5 6 c 7 8 9 ``` """ from vectorbt import settings is_pd = False is_2d = False args = list(args) if require_kwargs is None: require_kwargs = {} if align_index is None: align_index = settings.broadcasting['align_index'] if align_columns is None: align_columns = settings.broadcasting['align_columns'] if isinstance(index_from, str) and index_from == 'default': index_from = settings.broadcasting['index_from'] if isinstance(columns_from, str) and columns_from == 'default': columns_from = settings.broadcasting['columns_from'] # Convert to np.ndarray object if not numpy or pandas # Also check whether we broadcast to pandas and whether work on 2-dim data for i in range(len(args)): if not checks.is_array(args[i]): args[i] = np.asarray(args[i]) if args[i].ndim > 1: is_2d = True if checks.is_pandas(args[i]): is_pd = True # If target shape specified, check again if we work on 2-dim data if to_shape is not None: if isinstance(to_shape, int): to_shape = (to_shape, ) checks.assert_type(to_shape, tuple) if len(to_shape) > 1: is_2d = True if to_frame is not None: # force either keeping Series or converting them to DataFrames is_2d = to_frame if to_pd is not None: # force either raw or pandas if isinstance(to_pd, (tuple, list)): is_pd = any(to_pd) else: is_pd = to_pd # Align pandas objects if align_index: index_to_align = [] for i in range(len(args)): if checks.is_pandas(args[i]) and len(args[i].index) > 1: index_to_align.append(i) if len(index_to_align) > 1: indexes = [args[i].index for i in index_to_align] if len(set(map(len, indexes))) > 1: index_indices = index_fns.align_indexes(*indexes) for i in range(len(args)): if i in index_to_align: args[i] = args[i].iloc[index_indices[ index_to_align.index(i)]] if align_columns: cols_to_align = [] for i in range(len(args)): if checks.is_frame(args[i]) and len(args[i].columns) > 1: cols_to_align.append(i) if len(cols_to_align) > 1: indexes = [args[i].columns for i in cols_to_align] if len(set(map(len, indexes))) > 1: col_indices = index_fns.align_indexes(*indexes) for i in range(len(args)): if i in cols_to_align: args[i] = args[i].iloc[:, col_indices[cols_to_align. index(i)]] # Convert all pd.Series objects to pd.DataFrame if we work on 2-dim data args_2d = [ arg.to_frame() if is_2d and checks.is_series(arg) else arg for arg in args ] # Get final shape if to_shape is None: to_shape = np.lib.stride_tricks._broadcast_shape(*args_2d) # Perform broadcasting new_args = [] for i, arg in enumerate(args_2d): if isinstance(keep_raw, (tuple, list)): _keep_raw = keep_raw[i] else: _keep_raw = keep_raw bc_arg = np.broadcast_to(arg, to_shape) if _keep_raw: new_args.append(arg) continue new_args.append(bc_arg) # Force to match requirements for i in range(len(new_args)): if isinstance(require_kwargs, (tuple, list)): _require_kwargs = require_kwargs[i] else: _require_kwargs = require_kwargs new_args[i] = np.require(new_args[i], **_require_kwargs) if is_pd: # Decide on index and columns # NOTE: Important to pass args, not args_2d, to preserve original shape info new_index = broadcast_index(args, to_shape, index_from=index_from, axis=0, **kwargs) new_columns = broadcast_index(args, to_shape, index_from=columns_from, axis=1, **kwargs) else: new_index, new_columns = None, None # Bring arrays to their old types (e.g. array -> pandas) for i in range(len(new_args)): if isinstance(keep_raw, (tuple, list)): _keep_raw = keep_raw[i] else: _keep_raw = keep_raw if _keep_raw: continue if isinstance(to_pd, (tuple, list)): _is_pd = to_pd[i] else: _is_pd = is_pd new_args[i] = wrap_broadcasted(args[i], new_args[i], is_pd=_is_pd, new_index=new_index, new_columns=new_columns) if len(new_args) > 1: if return_meta: return tuple(new_args), to_shape, new_index, new_columns return tuple(new_args) if return_meta: return new_args[0], to_shape, new_index, new_columns return new_args[0]