编写指标

本篇文章解释了如何在 PyBroker 中创建和整合自定义股票指标。PyBroker 中的指标是用 NumPy 编写的,NumPy 是一个功能强大的数值计算库。为了优化性能,我们还将使用 Numba,这是一个即时编译器,可以将 Python 代码转换为高效的机器代码。Numba 对于加速涉及循环和 NumPy 数组的代码特别有用。以下是我们如何导入这些库:

[1]:
import numpy as np
from numba import njit

以下代码显示了一个指标函数,计算收盘价减去移动平均值(CMMA),可用于 均值回归 策略:

[2]:
def cmma(bar_data, lookback):

    @njit  # Enable Numba JIT.
    def vec_cmma(values):
        # Initialize the result array.
        n = len(values)
        out = np.array([np.nan for _ in range(n)])

        # For all bars starting at lookback:
        for i in range(lookback, n):
            # Calculate the moving average for the lookback.
            ma = 0
            for j in range(i - lookback, i):
                ma += values[j]
            ma /= lookback
            # Subtract the moving average from value.
            out[i] = values[i] - ma
        return out

    # Calculate with close prices.
    return vec_cmma(bar_data.close)

cmma 函数接受两个参数:bar_data,它是一个包含 OHLCV 数据和自定义字段的 BarData 类实例;以及 lookback,它是一个用户定义的参数,用于移动平均值的回溯期。

vec_cmma 函数由 Numba JIT 编译并嵌套在 cmma 中。这是必要的,因为 Numba 编译过的函数支持 NumPy 数组作为参数,但不支持像 BarData 这样的 Python 类实例。请注意,指标值的计算是由 Numba 向量化 的,这意味着它是一次性对所有历史数据进行的。这种方法显著加快了回测过程。

下一步是使用以下代码将指标函数注册到 PyBroker

[3]:
import pybroker

cmma_20 = pybroker.indicator("cmma_20", cmma, lookback=20)

这里,我们将指标函数命名为 cmma_20,并将 回溯 参数指定为 20 条。指标函数中在 bar_data 之后的任何参数都将作为用户定义的参数传递给 pybroker.indicator。一旦指标函数在 PyBroker 中注册,它将返回一个新的 Indicator 实例,该实例引用我们定义的指标函数。

以下是如何在 PyBroker 中使用已注册的指标的示例,其中数据来自于从 Yahoo Finance 下载的数据:

[4]:
from pybroker import YFinance

pybroker.enable_data_source_cache("yfinance")

yfinance = YFinance()
df = yfinance.query("PG", "4/1/2020", "4/1/2022")
Loading bar data...
[*********************100%***********************]  1 of 1 completed
Loaded bar data: 0:00:00

[5]:
cmma_20(df)
[5]:
2020-04-01         NaN
2020-04-02         NaN
2020-04-03         NaN
2020-04-06         NaN
2020-04-07         NaN
                ...
2022-03-25    1.967502
2022-03-28    3.288005
2022-03-29    4.968507
2022-03-30    3.790999
2022-03-31    2.171002
Length: 505, dtype: float64

正如您所看到的,Indicator 实例是 Callable 的。调用后,计算出的指标值将作为 Pandas Series 返回。

Indicator 类还提供了用于衡量其信息含量的函数。例如,您可以计算 四分位距(IQR)

[6]:
cmma_20.iqr(df)
[6]:
4.655495452880842

或者计算相对熵:

[7]:
cmma_20.relative_entropy(df)
[7]:
0.7495800114455111

在策略中使用指标

在实现我们的指标之后,下一步是将其整合到交易策略中。以下示例展示了一个简单的策略,当 20 日 CMMA 小于 0 时进行多头建仓,即当最近收盘价跌破 20 日移动平均线时:

[8]:
def buy_cmma_cross(ctx):
    if ctx.long_pos():
        return
    # Place a buy order if the most recent value of the 20 day CMMA is < 0:
    if ctx.indicator("cmma_20")[-1] < 0:
        ctx.buy_shares = ctx.calc_target_shares(1)
        ctx.hold_bars = 3

通过在 ExecContext 上调用 ctx.indicator 并传入已注册的 cmma_20 指标名称,可以获取指标值。

[9]:
from pybroker import Strategy

strategy = Strategy(yfinance, "4/1/2020", "4/1/2022")
strategy.add_execution(buy_cmma_cross, "PG", indicators=cmma_20)

在这里,将 buy_cmma_cross 函数与 cmma_20 指标一起添加到 Strategy 中。我们可以通过以下方式启用将计算出的指标值缓存到磁盘:

[10]:
pybroker.enable_indicator_cache("my_indicators")
[10]:
<pybroker.cache._L1Cache at 0x7f6dc4c82060>

最后,我们可以使用以下代码运行回测。warmup 参数指定在运行回测执行之前需要经过 20 个 Bar:

[11]:
result = strategy.backtest(warmup=20)
result.metrics_df.round(4).head(10)
Backtesting: 2020-04-01 00:00:00 to 2022-04-01 00:00:00

Loaded cached bar data.

Computing indicators...
100% (1 of 1) |##########################| Elapsed Time: 0:00:00 Time:  0:00:00

Test split: 2020-04-01 00:00:00 to 2022-03-31 00:00:00
100% (505 of 505) |######################| Elapsed Time: 0:00:00 Time:  0:00:00

Finished backtest: 0:00:00
[11]:
name value
0 trade_count 60
1 initial_market_value 100000.0
2 end_market_value 100759.36
3 total_pnl 759.36
4 unrealized_pnl 0.0
5 total_return_pct 0.75936
6 total_profit 41596.75
7 total_loss -40837.39
8 total_fees 0.0
9 max_drawdown -13446.93

PyBroker 会在回测运行之前计算指标值。默认情况下,这些计算是串行执行的。有关如何并行计算指标的详细信息,请参阅 配置并行化

向量化辅助函数

PyBroker 库提供了向量化辅助函数,以简化计算指标的过程。其中一个辅助函数是 highv,它用于计算每个 n 条 Bar 周期的最高值。

在示例代码中,定义了一个名为 hhv 的指标函数,该函数使用 highv 计算每个 5 条 Bar 周期的*最高*价格:

[12]:
from pybroker import highv


def hhv(bar_data, period):
    return highv(bar_data.high, period)


hhv_5 = pybroker.indicator("hhv_5", hhv, period=5)
hhv_5(df)
[12]:
2020-04-01           NaN
2020-04-02           NaN
2020-04-03           NaN
2020-04-06           NaN
2020-04-07    120.059998
                 ...
2022-03-25    153.919998
2022-03-28    153.919998
2022-03-29    156.470001
2022-03-30    156.470001
2022-03-31    156.470001
Length: 505, dtype: float64

pybroker.vect 模块还包括其他向量化辅助函数,如 lowvsumvreturnvcross,其中最后一个用于计算交叉点。

此外,PyBroker 还包括方便的 highestlowest 指标包装器。我们的 hhv 指标可以改写为:

[13]:
from pybroker import highest

hhv_5 = highest("hhv_5", "high", period=5)
hhv_5(df)
[13]:
2020-04-01           NaN
2020-04-02           NaN
2020-04-03           NaN
2020-04-06           NaN
2020-04-07    120.059998
                 ...
2022-03-25    153.919998
2022-03-28    153.919998
2022-03-29    156.470001
2022-03-30    156.470001
2022-03-31    156.470001
Length: 505, dtype: float64

计算多个指标

可以使用 IndicatorSet 来计算多个指标。通过将 cmma_20hhv_5 指标添加到 IndicatorSet 中,可以一起计算它们。最终输出将是一个包含两者的 Pandas DataFrame

[14]:
from pybroker import IndicatorSet

indicator_set = IndicatorSet()
indicator_set.add(cmma_20, hhv_5)
indicator_set(df)
Computing indicators...
100% (2 of 2) |##########################| Elapsed Time: 0:00:00 Time:  0:00:00

[14]:
symbol date cmma_20 hhv_5
0 PG 2020-04-01 NaN NaN
1 PG 2020-04-02 NaN NaN
2 PG 2020-04-03 NaN NaN
3 PG 2020-04-06 NaN NaN
4 PG 2020-04-07 NaN 120.059998
... ... ... ... ...
500 PG 2022-03-25 1.967502 153.919998
501 PG 2022-03-28 3.288005 153.919998
502 PG 2022-03-29 4.968507 156.470001
503 PG 2022-03-30 3.790999 156.470001
504 PG 2022-03-31 2.171002 156.470001

505 rows × 4 columns

使用 TA-Lib

TA-Lib 是一个广泛使用的技术分析库,实现了许多金融指标。你可以使用 pip install ta-lib 安装它。

将 TA-Lib 与 PyBroker 集成非常简单。以下是一个示例:

[15]:
import talib

rsi_20 = pybroker.indicator(
    "rsi_20", lambda data: talib.RSI(data.close, timeperiod=20)
)
rsi_20(df)
[15]:
2020-04-01          NaN
2020-04-02          NaN
2020-04-03          NaN
2020-04-06          NaN
2020-04-07          NaN
                ...
2022-03-25    49.373093
2022-03-28    51.014810
2022-03-29    53.407971
2022-03-30    51.610544
2022-03-31    49.029540
Length: 505, dtype: float64

内置指标

PyBroker 还包括内置指标,可在 indicator 模块 中找到。PyBroker 对这些标准指标进行了重新实现,加入了波动率归一化和稳健的非线性重缩放,使其值在不同股票代码和不同市场状态下都具有可比性。

在下一个教程中,你将学习如何使用 PyBroker 中的自定义指标来训练模型