vectorbt icon indicating copy to clipboard operation
vectorbt copied to clipboard

apply function defaults and overwriting them in run()

Open forLinDre opened this issue 4 years ago • 1 comments

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

image

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

image

forLinDre avatar Nov 22 '21 20:11 forLinDre

@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.

polakowo avatar Nov 22 '21 22:11 polakowo