训练模型
在 上一篇文档 中,我们学会了如何在 PyBroker 中编写股票指标。指标是开发交易策略的良好起点。但是要创建一个成功的策略,可能需要使用预测建模的更复杂方法。
幸运的是,PyBroker 的主要功能之一就是能够训练和回测机器学习模型。这些模型可以利用指标作为特征来更准确地预测市场走势。一旦训练完成,可以使用一种称为向前分析(Walkforward Analysis)的流行技术对这些模型进行回测,该技术模拟了策略在实际交易中的表现。
稍后我们将在本笔记本中更深入地解释向前分析。但首先,让我们从一些必要的导入开始!
[1]:
import numpy as np
import pandas as pd
import pybroker
from numba import njit
from pybroker import Strategy, StrategyConfig, YFinance
与 DataSource 和 Indicator 数据一样,PyBroker 也可以将训练过的模型缓存到磁盘。你可以通过调用 pybroker.enable_caches 来启用这三者的缓存:
[2]:
pybroker.enable_caches('walkforward_strategy')
在 上一篇文档 中,我们使用 NumPy 和 Numba 实现了一个计算收盘价减去移动平均价(CMMA)的指标。以下是 CMMA 指标的代码:
[3]:
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 for close prices.
return vec_cmma(bar_data.close)
cmma_20 = pybroker.indicator('cmma_20', cmma, lookback=20)
训练和回测
接下来,我们想要构建一个模型,使用 20 天的 CMMA 预测第二天的回报。使用 简单线性回归 是开始实验的好方法。下面我们从 scikit-learn 导入一个 LinearRegression 模型:
[4]:
from sklearn.linear_model import LinearRegression
from sklearn.metrics import r2_score
我们创建一个 train_slr
函数来训练 LinearRegression
模型:
[5]:
def train_slr(symbol, train_data, test_data):
# Train
# Previous day close prices.
train_prev_close = train_data['close'].shift(1)
# Calculate daily returns.
train_daily_returns = (train_data['close'] - train_prev_close) / train_prev_close
# Predict next day's return.
train_data['pred'] = train_daily_returns.shift(-1)
train_data = train_data.dropna()
# Train the LinearRegession model to predict the next day's return
# given the 20-day CMMA.
X_train = train_data[['cmma_20']]
y_train = train_data[['pred']]
model = LinearRegression()
model.fit(X_train, y_train)
# Test
test_prev_close = test_data['close'].shift(1)
test_daily_returns = (test_data['close'] - test_prev_close) / test_prev_close
test_data['pred'] = test_daily_returns.shift(-1)
test_data = test_data.dropna()
X_test = test_data[['cmma_20']]
y_test = test_data[['pred']]
# Make predictions from test data.
y_pred = model.predict(X_test)
# Print goodness of fit.
r2 = r2_score(y_test, np.squeeze(y_pred))
print(symbol, f'R^2={r2}')
# Return the trained model and columns to use as input data.
return model, ['cmma_20']
train_slr
函数使用 20 天的 CMMA 作为 LinearRegression
模型的输入特征或预测因子。然后,该函数将 LinearRegression
模型拟合到该股票代码的训练数据。
拟合模型后,该函数使用测试数据评估模型的准确性,具体地说,是通过计算 R 平方 得分。R 平方得分提供了一个衡量 LinearRegression
模型拟合测试数据有多好的方法。
train_slr
函数的最终输出是针对该股票代码的训练过的 LinearRegression
模型,以及用作预测输入数据的 cmma_20
列。回测过程中,PyBroker 将使用此模型预测股票的第二天回报。对于每个股票代码,都会调用 train_slr
函数,训练过的模型将用于预测每个股票的第二天回报。
定义了训练模型的函数之后,需要将其注册到 PyBroker。这是通过使用 pybroker.model 函数创建一个新的 ModelSource 实例来完成的。此函数的参数是模型的名称(在本例中为 'slr'
)、将训练模型的函数(train_slr
)以及作为模型输入的指标列表(在本例中为 cmma_20
)。
[6]:
model_slr = pybroker.model('slr', train_slr, indicators=[cmma_20])
为了创建使用训练过的模型的交易策略,需要使用 YFinance 数据源创建一个新的 Strategy 对象,并指定回测周期的开始和结束日期。
[7]:
config = StrategyConfig(bootstrap_sample_size=100)
strategy = Strategy(YFinance(), '3/1/2017', '3/1/2022', config)
strategy.add_execution(None, ['NVDA', 'AMD'], models=model_slr)
然后在 Strategy 对象上调用 add_execution 方法来指定交易执行的详细信息。在这种情况下,将 None
值作为第一个参数传递,这意味着在回测期间不会使用交易功能。
最后一步是在 Strategy
对象上调用 backtest 方法进行回测,设置 train_size
为 0.5
以指定模型应该在回测数据的前半部分进行训练,并在后半部分进行测试。
[8]:
strategy.backtest(train_size=0.5)
Backtesting: 2017-03-01 00:00:00 to 2022-03-01 00:00:00
Loading bar data...
[*********************100%***********************] 2 of 2 completed
Loaded bar data: 0:00:00
Computing indicators...
100% (2 of 2) |##########################| Elapsed Time: 0:00:01 Time: 0:00:01
Train split: 2017-03-01 00:00:00 to 2019-08-28 00:00:00
AMD R^2=-0.006808549721842416
NVDA R^2=-0.004416132743176426
Finished training models: 0:00:00
Finished backtest: 0:00:01
向前分析
PyBroker 使用一种称为向前分析(Walkforward Analysis)的强大算法来进行回测。该算法将回测数据划分为固定数量的时间窗口,每个窗口包含数据的训练-测试划分。
然后,向前分析算法以与现实世界中执行交易策略相同的方式“向前行进”。首先在最早的窗口上训练模型,然后在该窗口的测试数据上进行评估。
当算法向前移动以评估时间中的下一个窗口时,将前一个窗口的测试数据添加到训练数据中。这个过程持续进行,直到评估完所有的时间窗口。
通过使用这种方法,向前分析算法能够模拟交易策略在现实世界中的表现,并产生更可靠、更准确的回测结果。
让我们考虑一个从我们之前训练的 LinearRegression 模型生成买卖信号的交易策略。该策略作为 hold_long
函数实现:
[9]:
def hold_long(ctx):
if not ctx.long_pos():
# Buy if the next bar is predicted to have a positive return:
if ctx.preds('slr')[-1] > 0:
ctx.buy_shares = 100
else:
# Sell if the next bar is predicted to have a negative return:
if ctx.preds('slr')[-1] < 0:
ctx.sell_shares = 100
strategy.clear_executions()
strategy.add_execution(hold_long, ['NVDA', 'AMD'], models=model_slr)
hold_long
函数在模型预测下一个柱状图的回报为正时开启一个多头仓位,然后在模型预测回报为负时平仓。
ctx.preds(‘slr’) 方法用于访问当前在函数中执行的股票代码(NVDA 或 AMD)的 'slr'
模型所做的预测。预测值存储在一个 NumPy 数组 中,使用 ctx.preds('slr')[-1]
可以访问当前股票代码的最新预测,这是模型对下一个柱状图回报的预测。
现在我们已经定义了一个交易策略并注册了 'slr'
模型,我们可以使用向前分析算法进行回测。
通过在 Strategy
对象上调用 walkforward 方法并指定所需的时间窗口数量和训练/测试划分比例来运行回测。在这种情况下,我们将使用 3 个时间窗口,每个窗口的训练-测试划分为 50/50。
此外,由于我们的 'slr'
模型对未来一个柱状图的回报进行了预测,我们需要将 lookahead
参数设置为 1。这是为了确保训练数据不会泄露到测试边界。lookahead
参数应始终设置为预测的未来柱状图数量。
[10]:
result = strategy.walkforward(
warmup=20,
windows=3,
train_size=0.5,
lookahead=1,
calc_bootstrap=True
)
Backtesting: 2017-03-01 00:00:00 to 2022-03-01 00:00:00
Loaded cached bar data.
Loaded cached indicator data.
Train split: 2017-03-06 00:00:00 to 2018-06-01 00:00:00
AMD R^2=-0.007950114729117885
NVDA R^2=-0.04203364470839133
Finished training models: 0:00:00
Test split: 2018-06-04 00:00:00 to 2019-08-30 00:00:00
100% (314 of 314) |######################| Elapsed Time: 0:00:00 Time: 0:00:00
Train split: 2018-06-04 00:00:00 to 2019-08-30 00:00:00
AMD R^2=0.0006422677593683757
NVDA R^2=-0.023591728578221893
Finished training models: 0:00:00
Test split: 2019-09-03 00:00:00 to 2020-11-27 00:00:00
100% (314 of 314) |######################| Elapsed Time: 0:00:00 Time: 0:00:00
Train split: 2019-09-03 00:00:00 to 2020-11-27 00:00:00
AMD R^2=-0.015508227883924253
NVDA R^2=-0.4567200095787838
Finished training models: 0:00:00
Test split: 2020-11-30 00:00:00 to 2022-02-28 00:00:00
100% (314 of 314) |######################| Elapsed Time: 0:00:00 Time: 0:00:00
Calculating bootstrap metrics: sample_size=100, samples=10000...
Calculated bootstrap metrics: 0:00:03
Finished backtest: 0:00:04
在使用向前分析算法进行回测过程中,'slr'
模型在给定窗口的训练数据上进行训练,然后 hold_long
函数在相同窗口的测试数据上运行。
模型在训练数据上进行训练以预测第二天的回报。然后,hold_long
函数使用这些预测为当前交易日的交易会话做出买卖决策。
通过评估每个窗口测试数据上交易策略的表现,我们可以了解策略在现实交易环境中可能的表现。这个过程在回测的每个时间窗口中重复进行,使用结果来评估交易策略的整体表现:
[11]:
result.metrics_df
[11]:
name | value | |
---|---|---|
0 | trade_count | 43.000000 |
1 | initial_market_value | 100000.000000 |
2 | end_market_value | 109831.000000 |
3 | total_pnl | 12645.000000 |
4 | unrealized_pnl | -2814.000000 |
5 | total_return_pct | 12.645000 |
6 | total_profit | 20566.000000 |
7 | total_loss | -7921.000000 |
8 | total_fees | 0.000000 |
9 | max_drawdown | -14177.000000 |
10 | max_drawdown_pct | -12.272121 |
11 | win_rate | 76.744186 |
12 | loss_rate | 23.255814 |
13 | winning_trades | 33.000000 |
14 | losing_trades | 10.000000 |
15 | avg_pnl | 294.069767 |
16 | avg_return_pct | 5.267674 |
17 | avg_trade_bars | 25.488372 |
18 | avg_profit | 623.212121 |
19 | avg_profit_pct | 9.237576 |
20 | avg_winning_trade_bars | 19.151515 |
21 | avg_loss | -792.100000 |
22 | avg_loss_pct | -7.833000 |
23 | avg_losing_trade_bars | 46.400000 |
24 | largest_win | 2715.000000 |
25 | largest_win_pct | 9.320000 |
26 | largest_win_bars | 2.000000 |
27 | largest_loss | -5054.000000 |
28 | largest_loss_pct | -16.140000 |
29 | largest_loss_bars | 43.000000 |
30 | max_wins | 13.000000 |
31 | max_losses | 2.000000 |
32 | sharpe | 0.023425 |
33 | profit_factor | 1.094471 |
34 | ulcer_index | 1.177116 |
35 | upi | 0.009193 |
36 | equity_r2 | 0.772082 |
37 | std_error | 4191.846954 |
[12]:
result.bootstrap.conf_intervals
[12]:
lower | upper | ||
---|---|---|---|
name | conf | ||
Profit Factor | 97.5% | 0.259819 | 1.296660 |
95% | 0.303435 | 1.151299 | |
90% | 0.373167 | 1.002514 | |
Sharpe Ratio | 97.5% | -0.359565 | 0.050383 |
95% | -0.332180 | 0.018154 | |
90% | -0.276757 | -0.018004 |
[13]:
result.bootstrap.drawdown_conf
[13]:
amount | percent | |
---|---|---|
conf | ||
99.9% | -13917.50 | -12.190522 |
99% | -11058.25 | -9.693729 |
95% | -8380.25 | -7.480589 |
90% | -7129.00 | -6.403027 |
总之,我们现在已经完成了使用 PyBroker 对线性回归模型进行训练和回测的过程,并借助向前分析。我们看到的指标是基于回测中所有时间窗口的测试数据。尽管我们的交易策略需要改进,但我们已经很好地理解了如何在 PyBroker 中训练和评估模型。
请注意,在进行回归分析之前,验证某些假设(如 同方差性、残差的正态性等)是很重要的。为了简洁起见,我在这里没有提供这些假设的细节,建议你自行进行这个练习。
我们在 PyBroker 中构建模型不仅限于线性回归模型。我们可以训练其他类型的模型,如梯度提升机、神经网络或任何我们选择的其他架构。这种灵活性使我们可以探索和尝试各种模型和方法,以找到最适合我们特定交易目标的最佳表现模型。
PyBroker 还提供了定制选项,例如为我们的模型指定一个 input_data_fn,以便我们可以自定义其输入数据的构建方式。当构建用于自回归模型(如 ARMA 或 RNN)的输入时,需要使用多个过去的值进行预测,此时就需要这样做。同样,我们可以指定自己的 predict_fn 来自定义预测的方式(默认情况下,会调用模型的 预测
函数)。
有了这些知识,你可以开始在 PyBroker 中构建和测试自己的模型和交易策略,并开始探索这个框架所提供的广泛可能性!