Skip to content

Commit 655f802

Browse files
committed
REF: Better error handling in Strategy.I
1 parent 41b1ddf commit 655f802

File tree

2 files changed

+23
-12
lines changed

2 files changed

+23
-12
lines changed

backtesting/backtesting.py

+20-12
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ def _tqdm(seq, **_):
2626
return seq
2727

2828
from ._plotting import plot
29-
from ._util import _as_str, _Indicator, _Data, _data_period
29+
from ._util import _as_str, _Indicator, _Data, _data_period, try_
3030

3131

3232
__pdoc__ = {
@@ -121,18 +121,26 @@ def init():
121121
name = name.format(*map(_as_str, args),
122122
**dict(zip(kwargs.keys(), map(_as_str, kwargs.values()))))
123123

124-
value = func(*args, **kwargs)
125-
126124
try:
127-
if isinstance(value, pd.DataFrame):
128-
value = value.values.T
129-
value = np.asarray(value)
130-
except Exception:
131-
raise ValueError('Indicators must return array-like sequences of values')
132-
if value.shape[-1] != len(self._data.Close):
133-
raise ValueError('Indicators must be (a tuple of) arrays of same length as `data`'
134-
'(data: {}, indicator "{}": {})'.format(len(self._data.Close),
135-
name, value.shape))
125+
value = func(*args, **kwargs)
126+
except Exception as e:
127+
raise RuntimeError('Indicator "{}" errored with exception: {}'.format(name, e))
128+
129+
if isinstance(value, pd.DataFrame):
130+
value = value.values.T
131+
132+
value = try_(lambda: np.asarray(value, order='C'), None)
133+
is_arraylike = value is not None
134+
135+
# Optionally flip the array if the user returned e.g. `df.values`
136+
if is_arraylike and np.argmax(value.shape) == 0:
137+
value = value.T
138+
139+
if not is_arraylike or not 1 <= value.ndim <= 2 or value.shape[-1] != len(self._data.Close):
140+
raise ValueError(
141+
'Indicators must return (optionally a tuple of) numpy.arrays of same '
142+
'length as `data`(data shape: {}; indicator "{}" shape: {}, value: {})'
143+
.format(self._data.Close.shape, name, getattr(value, 'shape', ''), value))
136144

137145
if plot and overlay is None and np.issubdtype(value.dtype, np.number):
138146
x = value / self._data.Close

backtesting/test/_test.py

+3
Original file line numberDiff line numberDiff line change
@@ -120,6 +120,9 @@ def init(self):
120120
self.sma = self.I(SMA, self.data.Close, 10)
121121
self.remains_indicator = np.r_[2] * np.cumsum(self.sma * 5 + 1) * np.r_[2]
122122

123+
self.transpose_invalid = self.I(lambda: np.column_stack((self.data.Open,
124+
self.data.Close)))
125+
123126
resampled = resample_apply('W', SMA, self.data.Close, 3)
124127
resampled_ind = resample_apply('W', SMA, self.sma, 3)
125128
assert np.unique(resampled[-5:]).size == 1

0 commit comments

Comments
 (0)