vectorbt
vectorbt copied to clipboard
apply function defaults and overwriting them in run()
Hello,
I am not sure if this is actually a bug or issue or not but it seems default parameter/inputs are not behaving as described in the indicator factory documentation. When adding default lists into from_apply_func() and then running the indicator without any inputs or parameters defined, there seems to be an issue with the assert_equal check inside utils. Take the example below.
Parameter lists/arrays are defined:
# Define EMA parameter windows
EMA3_span = [i for i in range(interface.EMA3_window_start, interface.EMA3_window_end + interface.EMA3_window_step_size, interface.EMA3_window_step_size)]
EMA4_span = [i for i in range(interface.EMA4_window_start, interface.EMA4_window_end + interface.EMA4_window_step_size, interface.EMA4_window_step_size)]
EMA3_exp = interface.EMA3_exp
EMA4_exp = interface.EMA4_exp
# Define percent change window parameters
pct_change_span = [i for i in range(interface.pct_change_window_start, interface.pct_change_window_end + interface.pct_change_window_step_size, interface.pct_change_window_step_size)]
pct_change_thres_span = np.linspace(interface.pct_change_thres_window_start, interface.pct_change_thres_window_end, interface.pct_change_thres_window_steps + 1).tolist()
input prices are defined:
Open_prefetch = stock_data_prefetch['Open']
High_prefetch = stock_data_prefetch['High']
Low_prefetch = stock_data_prefetch['Low']
Close_prefetch = stock_data_prefetch['Close']
apply function is defined:
@njit
def RMA_strat_apply_func_nb(open, high, low, max_pos, max_value, value_change, EWM_window1, EWM_window2, ewm1, ewm2, change_window, change_thres):
RMA1 = vbt.indicators.nb.ma_nb(a=open, window=EWM_window1, ewm=ewm1)
RMA2 = vbt.indicators.nb.ma_nb(a=open, window=EWM_window2, ewm=ewm2)
pct_change = np.full_like(open, np.nan, dtype=np.float_)
change_thres = np.full_like(open, change_thres, np.float_)
for col in range(open.shape[1]):
buf = max(EWM_window1, EWM_window2, change_window)
for i in range(buf, open.shape[0]):
max_pos[i, col] = vbt.nb.argmax_reduce_nb(0, high[i - change_window:i, col])
max_value[i, col] = high[i - change_window + max_pos[i, col], col]
value_change[i, col] = low[i - 1, col] - max_value[i, col]
pct_change[i, col] = (value_change[i, col]/max_value[i, col])*100
return RMA1, RMA2, pct_change, change_thres
Indicator Factory is applied with default inputs and parameters defined:
RMA_Strat_Indicator = vbt.IndicatorFactory(
input_names=['open', 'high', 'low'],
param_names=['EWM_window1', 'EWM_window2', 'ewm1', 'ewm2', 'change_window', 'change_thres'],
output_names=['RMA1', 'RMA2', 'pct_change', 'change_thres'],
in_output_names=['max_pos', 'max_value', 'value_change'],
attr_settings=dict(
open=dict(dtype=np.float_),
high=dict(dtype=np.float_),
low=dict(dtype=np.float_),
)
).from_apply_func(
RMA_strat_apply_func_nb,
open=Open_prefetch, high=High_prefetch, low=Low_prefetch,
max_pos=1,
max_value=1.00,
value_change=1.00,
EWM_window1=EMA3_span,
EWM_window2=EMA4_span,
ewm1=EMA3_exp,
ewm2=EMA4_exp,
change_window=pct_change_span,
change_thres=pct_change_thres_span,
in_output_settings=dict(
max_pos=dict(dtype=np.int_),
max_value=dict(dtype=np.float_),
value_change=dict(dtype=np.float_)
),
param_product=True,
run_unique=True,
)
Then the run method is used without re-defining any inputs or parameters:
RMA_strat_indicator = RMA_Strat_Indicator.run(
#EWM_window1=EMA3_span,
#EWM_window2=EMA4_span,
#ewm1=EMA3_exp,
#ewm2=EMA4_exp,
#change_window=pct_change_span,
#change_thres=pct_change_thres_span,
)
The following error occurs:
---------------------------------------------------------------------------
AssertionError Traceback (most recent call last)
C:\Users\ANDRE~1.DES\AppData\Local\Temp/ipykernel_4884/1468944326.py in <module>
1 # Run RMA strategy entries with desired parameter sweeps
----> 2 RMA_strat_indicator = RMA_Strat_Indicator.run(
3 #EWM_window1=EMA3_span,
4 #EWM_window2=EMA4_span,
5 #ewm1=EMA3_exp,
~\anaconda3\envs\strategy_lib_vectorbt\lib\site-packages\vectorbt\indicators\factory.py in run(cls, open, high, low, EWM_window1, EWM_window2, ewm1, ewm2, change_window, change_thres, max_pos, max_value, value_change, short_name, hide_params, hide_default, **kwargs)
12 Each indicator is basically a pipeline that:
13
---> 14 * Accepts a list of input arrays (for example, OHLCV data)
15 * Accepts a list of parameter arrays (for example, window size)
16 * Accepts other relevant arguments and keyword arguments
~\anaconda3\envs\strategy_lib_vectorbt\lib\site-packages\vectorbt\indicators\factory.py in _run(cls, *args, **kwargs)
2893
2894 # Create a new instance
-> 2895 obj = cls(
2896 wrapper,
2897 new_input_list,
~\anaconda3\envs\strategy_lib_vectorbt\lib\site-packages\vectorbt\indicators\factory.py in __init__(self, wrapper, input_list, input_mapper, in_output_list, output_list, param_list, mapper_list, short_name, level_names)
2418 short_name: str,
2419 level_names: tp.Tuple[str, ...]) -> None:
-> 2420 IndicatorBase.__init__(
2421 self,
2422 wrapper,
~\anaconda3\envs\strategy_lib_vectorbt\lib\site-packages\vectorbt\indicators\factory.py in __init__(self, wrapper, input_list, input_mapper, in_output_list, output_list, param_list, mapper_list, short_name, level_names)
2116
2117 if input_mapper is not None:
-> 2118 checks.assert_equal(input_mapper.shape[0], wrapper.shape_2d[1])
2119 for ts in input_list:
2120 checks.assert_equal(ts.shape[0], wrapper.shape_2d[0])
~\anaconda3\envs\strategy_lib_vectorbt\lib\site-packages\vectorbt\utils\checks.py in assert_equal(arg1, arg2, deep)
457 else:
458 if not is_equal(arg1, arg2):
--> 459 raise AssertionError(f"{arg1} and {arg2} do not match")
460
461
AssertionError: 1980 and 1 do not match
Now if the run method is used with a single parameter being redefined as follows, the script runs fine but all other default parameters initially defined in the indicator factory no longer apply:
RMA_strat_indicator = RMA_Strat_Indicator.run(
EWM_window1=65,
#EWM_window2=EMA4_span,
#ewm1=EMA3_exp,
#ewm2=EMA4_exp,
#change_window=pct_change_span,
#change_thres=pct_change_thres_span,
)
RMA_strat_indicator.max_pos

You can see from the image above that only the 65 EMA window length parameter is applicable even though other parameter lists were defined in the indicator factory. Is this how this should behave?
If all parameters are redefined in the run method, the script runs fine and all parameters are ran.
RMA_strat_indicator = RMA_Strat_Indicator.run(
EWM_window1=EMA3_span,
EWM_window2=EMA4_span,
ewm1=EMA3_exp,
ewm2=EMA4_exp,
change_window=pct_change_span,
change_thres=pct_change_thres_span,
)
RMA_strat_indicator.max_pos

@forLinDre thanks for reporting, I can reproduce the issue.
I haven't really tested the possibility of providing multiple combinations as default values. Defaults, in my opinion, should always be scalars (such as the default window size of 10 rather than [10, 11, 12, ...]) such that they can exist irrespective of any other input and parameter. Any complex data such as input arrays and multiple parameters should be provided in run() method rather than be part of the indicator itself, otherwise you tie your data to your indicator class and make it opinionated, which is a bad practice. But I will still look into fixing this.
Edit: setting hide_default=False seems to mitigate the issue.