文档

指标

构建可参数化、可分析的指标流水线

会员内容预览(英文)

The vectorbtpro.indicators.factory.IndicatorFactory class is one of the most powerful components in the VBT ecosystem. It can wrap any indicator function, making it both parameterizable and analyzable.

An indicator is a pipeline that performs the following steps:

  • Accepts input arrays (for example, opening and closing prices).
  • Accepts parameters either as scalars or arrays (for example, window size).
  • Accepts other relevant arguments and keyword arguments.
  • Broadcasts input arrays against each other or to a specific shape.
  • Broadcasts parameters against each other to create a fixed set of parameter combinations.
  • Calculates output arrays for each parameter combination using the input arrays, producing the same shape (for example, a rolling average).
  • Concatenates the output arrays of all parameter combinations along the columns.
  • Converts the results back to the Pandas format.

Let's manually create an indicator that takes two time series, computes their normalized moving averages, and returns the difference between the two. We will test different shapes as well as parameter combinations to see how broadcasting can be leveraged:

from vectorbtpro import *

def mov_avg_crossover(ts1, ts2, w1, w2):
    ts1, ts2 = vbt.broadcast(ts1, ts2)  

    w1, w2 = vbt.broadcast(  
        vbt.to_1d_array(w1),
        vbt.to_1d_array(w2))

    ts1_mas = []
    for w in w1:
        ts1_mas.append(ts1.vbt.rolling_mean(w) / ts1)  
    ts2_mas = []
    for w in w2:
        ts2_mas.append(ts2.vbt.rolling_mean(w) / ts2)

    ts1_ma = pd.concat(ts1_mas, axis=1)  
    ts2_ma = pd.concat(ts2_mas, axis=1)

    ts1_ma.columns = vbt.combine_indexes((  
        pd.Index(w1, name="ts1_window"),
        ts1.columns))
    ts2_ma.columns = vbt.combine_indexes((
        pd.Index(w2, name="ts2_window"),
        ts2.columns))

    return ts1_ma.vbt - ts2_ma  

def generate_index(n):  
    return vbt.date_range("2020-01-01", periods=n)

ts1 = pd.Series([1, 2, 3, 4, 5, 6, 7], index=generate_index(7))
ts2 = pd.DataFrame({
    'a': [5, 4, 3, 2, 3, 4, 5],
    'b': [2, 3, 4, 5, 4, 3, 2]
}, index=generate_index(7))
w1 = 2
w2 = [3, 4]

mov_avg_crossover(ts1, ts2, w1, w2)
ts1_window                                       2
ts2_window                   3                   4
                   a         b         a         b
2020-01-01       NaN       NaN       NaN       NaN
2020-01-02       NaN       NaN       NaN       NaN
2020-01-03 -0.500000  0.083333       NaN       NaN
2020-01-04 -0.625000  0.075000 -0.875000  0.175000
2020-01-05  0.011111 -0.183333 -0.100000 -0.100000
2020-01-06  0.166667 -0.416667  0.166667 -0.416667
2020-01-07  0.128571 -0.571429  0.228571 -0.821429

Pretty neat! We just built a flexible pipeline that can handle arbitrary input and parameter combinations. The resulting DataFrame shows each column as a specific window combination applied to each column in both ts1 and ts2. But is this pipeline user-friendly? 🤔 Dealing with broadcasting, output concatenation, and column hierarchies makes this process very similar to working with regular Pandas code.

The pipeline above can be easily standardized using vectorbtpro.indicators.factory.IndicatorBase.run_pipeline. This method conveniently prepares inputs, parameters, and columns. However, you still need to perform the calculation and output concatenation yourself by providing a custom_func. Let's update the example:

def custom_func(ts1, ts2, w1, w2):
    ts1_mas = []
    for w in w1:
        ts1_mas.append(vbt.nb.rolling_mean_nb(ts1, w) / ts1)  
    ts2_mas = []
    for w in w2:
        ts2_mas.append(vbt.nb.rolling_mean_nb(ts2, w) / ts2)

    ts1_ma = np.column_stack(ts1_mas)  
    ts2_ma = np.column_stack(ts2_mas)

    return ts1_ma - ts2_ma  

outputs = vbt.IndicatorBase.run_pipeline(
    num_ret_outputs=1,
    custom_func=custom_func,
    inputs=dict(ts1=ts1, ts2=ts2),
    params=dict(w1=w1, w2=w2)
)
(type(outputs[0]), *outputs[1:])
(vectorbtpro.base.wrapping.ArrayWrapper,
 [array([[1, 1],
         [2, 2],
         [3, 3],
         [4, 4],
         [5, 5],
         [6, 6],
         [7, 7]]),
  array([[5, 2],
         [4, 3],
         [3, 4],
         [2, 5],
         [3, 4],
         [4, 3],
         [5, 2]])],
 array([0, 1, 0, 1]),
 [],
 [array([[        nan,         nan,         nan,         nan],
         [        nan,         nan,         nan,         nan],
         [-0.5       ,  0.08333333,         nan,         nan],
         [-0.625     ,  0.075     , -0.875     ,  0.175     ],
         [ 0.01111111, -0.18333333, -0.1       , -0.1       ],
         [ 0.16666667, -0.41666667,  0.16666667, -0.41666667],
         [ 0.12857143, -0.57142857,  0.22857143, -0.82142857]])],
 [[2, 2], [3, 4]],
 [Index([2, 2, 2, 2], dtype='int64'), Index([3, 3, 4, 4], dtype='int64')],
 [])

With much less code, we performed the entire calculation using only NumPy and Numba—a big win! But what is this complex output?

指标系统将计算函数转化为可复用的流水线:接受输入和参数,生成带标签的输出,并支持同时分析多种配置。

会员主题(英文内容)

版权所有 © 2021–2026 Oleg Polakow。保留所有权利。

本站内容与文档供使用和评估 VectorBT PRO 以及教育用途。任何其他用途,包括开发或支持竞争产品或服务,均须事先取得书面同意。