编写指标

本篇文章解释了如何在 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:01

[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 指标名称,可以获取指标值。

(注意,您还可以通过将股票代码传递给 ExecContext#indicator() 来获取其他股票的指标数据。)

[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]:
<diskcache.core.Cache at 0x7f45b0a73bb0>

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

[11]:
result = strategy.backtest(warmup=20)
result.metrics_df.round(4)
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:01
[11]:
name value
0 trade_count 60.0000
1 initial_market_value 100000.0000
2 end_market_value 100759.3600
3 total_pnl 759.3600
4 unrealized_pnl 0.0000
5 total_return_pct 0.7594
6 total_profit 41596.7500
7 total_loss -40837.3900
8 total_fees 0.0000
9 max_drawdown -13446.9300
10 max_drawdown_pct -11.9774
11 win_rate 53.3333
12 loss_rate 46.6667
13 winning_trades 32.0000
14 losing_trades 28.0000
15 avg_pnl 12.6560
16 avg_return_pct 0.0293
17 avg_trade_bars 3.0000
18 avg_profit 1299.8984
19 avg_profit_pct 1.2609
20 avg_winning_trade_bars 3.0000
21 avg_loss -1458.4782
22 avg_loss_pct -1.3782
23 avg_losing_trade_bars 3.0000
24 largest_win 4263.4500
25 largest_win_pct 4.1000
26 largest_win_bars 3.0000
27 largest_loss -4675.6700
28 largest_loss_pct -4.1700
29 largest_loss_bars 3.0000
30 max_wins 7.0000
31 max_losses 4.0000
32 sharpe 0.0023
33 profit_factor 1.0092
34 ulcer_index 1.8823
35 upi 0.0019
36 equity_r2 0.0015
37 std_error 3385.1968

当回测运行时,PyBroker 计算指标值。如果在 Strategy 中添加了多个指标,那么 PyBroker 将在多个 CPU 核心上并行计算它们。

向量化辅助函数

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,其中最后一个用于计算交叉点。

Additionally, PyBroker includes convenient wrappers for highest and lowest indicators. Our hhv indicator can be rewritten as:

[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:01 Time:  0:00:01

[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 是一个广泛使用的技术分析库,实现了许多金融指标。将 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

Built-In Indicators

PyBroker also includes built-in indicators that are available in the indicator module.

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