编写指标
本篇文章解释了如何在 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.