编写指标
本篇文章解释了如何在 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 模块还包括其他向量化辅助函数,如 lowv、sumv、returnv 和 cross,其中最后一个用于计算交叉点。
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_20
和 hhv_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.