def test_as_str(self): def func(): pass class Class: def __call__(self): pass self.assertEqual(_as_str('4'), '4') self.assertEqual(_as_str(4), '4') self.assertEqual(_as_str(_Indicator([1, 2], name='x')), 'x') self.assertEqual(_as_str(func), 'func') self.assertEqual(_as_str(Class), 'Class') self.assertEqual(_as_str(Class()), 'Class') self.assertEqual(_as_str(pd.Series([1, 2], name='x')), 'x') self.assertEqual(_as_str(pd.DataFrame()), 'df') self.assertEqual(_as_str(lambda x: x), 'λ') for s in ('Open', 'High', 'Low', 'Close', 'Volume'): self.assertEqual(_as_str(_Array([1], name=s)), s[0])
def test_as_str(self): def func(): pass class Class: pass self.assertEqual(_as_str('4'), '4') self.assertEqual(_as_str(4), '4') self.assertEqual(_as_str(_Indicator([1, 2], name='x')), 'x') self.assertEqual(_as_str(func), 'func') self.assertEqual(_as_str(Class), 'Class') self.assertEqual(_as_str(lambda x: x), '') for s in ('Open', 'High', 'Low', 'Close'): self.assertEqual(_as_str(_Array([1], name=s)), s[0])
def resample_apply( rule: str, func: Optional[Callable[..., Sequence]], series: Union[pd.Series, pd.DataFrame, _Array], *args, agg: Union[str, dict] = None, **kwargs, ): if func is None: def func(x, *_, **__): return x if not isinstance(series, (pd.Series, pd.DataFrame)): assert isinstance( series, _Array ), 'resample_apply() takes either a `pd.Series`, `pd.DataFrame`, ' 'or a `Strategy.data.*` array' series = series.s if agg is None: agg = OHLCV_AGG.get(getattr(series, 'name', None), 'last') if isinstance(series, pd.DataFrame): agg = { column: OHLCV_AGG.get(column, 'last') for column in series.columns } resampled = series.resample(rule, label='right').agg(agg).dropna() resampled.name = _as_str(series) + '[' + rule + ']' # Check first few stack frames if we are being called from # inside Strategy.init, and if so, extract Strategy.I wrapper. frame, level = currentframe(), 0 while frame and level <= 3: frame = frame.f_back level += 1 if isinstance(frame.f_locals.get('self'), Strategy): # type: ignore strategy_I = frame.f_locals['self'].I # type: ignore break else: def strategy_I(func, *args, **kwargs): return func(*args, **kwargs) def wrap_func(resampled, *args, **kwargs): result = func(resampled, *args, **kwargs) if not isinstance(result, pd.DataFrame) and not isinstance( result, pd.Series): result = np.asarray(result) if result.ndim == 1: result = pd.Series(result, name=resampled.name) elif result.ndim == 2: result = pd.DataFrame(result.T) # Resample back to data index if not isinstance(result.index, pd.DatetimeIndex): result.index = resampled.index result = result.reindex(index=series.index.union(resampled.index), method='ffill').reindex(series.index) return result wrap_func.__name__ = func.__name__ # type: ignore array = strategy_I(wrap_func, resampled, *args, **kwargs) return array
def I( self, func: Callable, *args, name=None, plot=True, overlay=None, color=None, scatter=False, **kwargs, ) -> np.ndarray: if name is None: params = ','.join(filter(None, map(_as_str, chain(args, kwargs.values())))) func_name = _as_str(func) name = f'{func_name}({params})' if params else f'{func_name}' else: name = name.format( *map(_as_str, args), **dict(zip(kwargs.keys(), map(_as_str, kwargs.values()))), ) try: value = func(*args, **kwargs) except Exception as e: raise RuntimeError(f'I "{name}" errored with exception: {e}') if isinstance(value, pd.DataFrame): value = value.values.T if value is not None: value = try_(lambda: np.asarray(value, order='C'), None) is_arraylike = value is not None # Optionally flip the array if the user returned e.g. `df.values` if is_arraylike and np.argmax(value.shape) == 0: value = value.T if not is_arraylike or not 1 <= value.ndim <= 2 or value.shape[-1] != len(self._data.Close): raise ValueError( 'Indicators must return (optionally a tuple of) numpy.arrays of same ' f'length as `data` (data shape: {self._data.Close.shape}; indicator "{name}"' f'shape: {getattr(value, "shape" , "")}, returned value: {value})' ) if plot and overlay is None and np.issubdtype(value.dtype, np.number): x = value / self._data.Close # By default, overlay if strong majority of indicator values # is within 30% of Close with np.errstate(invalid='ignore'): overlay = ((x < 1.4) & (x > 0.6)).mean() > 0.6 value = _Indicator( value, name=name, plot=plot, overlay=overlay, color=color, scatter=scatter, index=self.data.index, ) self._indicators.append(value) return value