回测一个策略
我们已经准备好使用 PyBroker 测试一个基本的交易策略了!首先,我们将导入以下所列的必要类:
[1]:
import pybroker
from pybroker import Strategy, StrategyConfig, YFinance
pybroker.enable_data_source_cache('my_strategy')
[1]:
<diskcache.core.Cache at 0x7f453c0a85b0>
对于我们的回测,我们将使用 Yahoo Finance 作为我们的 数据源。我们还将使用数据源缓存来确保在运行回测时只下载一次所需的数据。
下一步是设置一个新的 Strategy 类实例,它将用于对我们的交易策略进行回测。以下是操作方法:
首先,你可以创建一个 StrategyConfig 对象来配置策略。在这个例子中,我们将初始资金设为 500,000:
[2]:
config = StrategyConfig(initial_cash=500_000)
接下来,你可以通过传入以下参数来创建一个 Strategy
类的新实例:
数据源:在这种情况下,我们使用 Yahoo Finance 作为数据源。
开始日期:这是回测的开始日期。
结束日期:这是回测的结束日期。
之前创建的配置对象。
[3]:
strategy = Strategy(YFinance(), '3/1/2017', '3/1/2022', config)
现在,Strategy
实例已准备好在使用指定的配置选项运行回测之前,从 Yahoo Finance 下载 2017 年 3 月 1 日至 2022 年 3 月 1 日之间的数据。如果你需要修改其他配置选项,可以参考 StrategyConfig 参考文档。
定义策略规则
在本节中,你将学习如何在 PyBroker
中实现一个基本的交易策略,包括以下规则:
如果最后收盘价低于前一根K线的最低价,并且该股票没有未平仓的多头头寸,则购买该股票的股份。
将买单的限价设置为比最后收盘价低 0.01 的价格。
持有头寸 3 天后以市价平仓。
在 AAPL 和 MSFT 上执行这些规则,为每只股票分配最多 25% 的投资组合。
为实现这一目标,你将定义一个 buy_low
函数,PyBroker 将在每条数据的每个K线上分别为 AAPL 和 MSFT 调用该函数。每个 K 线对应一天的数据:
[4]:
def buy_low(ctx):
# If shares were already purchased and are currently being held, then return.
if ctx.long_pos():
return
# If the latest close price is less than the previous day's low price,
# then place a buy order.
if ctx.bars >= 2 and ctx.close[-1] < ctx.low[-2]:
# Buy a number of shares that is equal to 25% the portfolio.
ctx.buy_shares = ctx.calc_target_shares(0.25)
# Set the limit price of the order.
ctx.buy_limit_price = ctx.close[-1] - 0.01
# Hold the position for 3 bars before liquidating (in this case, 3 days).
ctx.hold_bars = 3
这里有很多内容需要解释!buy_low
函数将接收一个包含当前股票代码(AAPL 或 MSFT)数据的 ExecContext (ctx
)。ExecContext 将包含当前股票代码最近 K 线之前的所有收盘价。通过 ctx.close[-1]
获取最新的收盘价。
buy_low
函数将使用 ExecContext
来下达买单。购买的股份数量通过 ctx.buy_shares,设置,它是通过 ctx.calc_target_shares 计算的。在这种情况下,要购买的股份数量将等于投资组合的 25%。
订单的限价通过 buy_limit_price 设置。如果满足条件,买单将在下一根 K 线成交。订单成交的时间可以通过 StrategyConfig.buy_delay 进行配置,成交价可以通过 ExecContext.buy_fill_price 设置。默认情况下,买单在下一根 K 线(buy_delay=1
)成交,成交价等于该 K 线的最低价和最高价之间的中点。
最后,ctx.hold_bars 指定在平仓之前持有头寸的 K 线数。平仓时,股票以市价出售,市价等于 ExecContext.sell_fill_price,这是可配置的,默认值为K线最低价和最高价之间的中点。
要将 buy_low
规则添加到 AAPL 和 MSFT 的 策略
中,你需要使用 add_execution:
[5]:
strategy.add_execution(buy_low, ['AAPL', 'MSFT'])
添加第二个执行逻辑
在同一个 Strategy
实例中,你可以为不同的股票代码使用不同的交易规则。换句话说,你并不局限于对一组股票代码只使用一套交易规则。
为了说明这一点,提供了一个名为 short_high
的函数,其中包含一个做空策略的新规则集,这与之前的规则集类似:
[6]:
def short_high(ctx):
# If shares were already shorted then return.
if ctx.short_pos():
return
# If the latest close price is more than the previous day's high price,
# then place a sell order.
if ctx.bars >= 2 and ctx.close[-1] > ctx.high[-2]:
# Short 100 shares.
ctx.sell_shares = 100
# Cover the shares after 2 bars (in this case, 2 days).
ctx.hold_bars = 2
short_high
中的规则将应用于 TSLA
:
[7]:
strategy.add_execution(short_high, ['TSLA'])
(注意,你还可以通过调用 ExecContext#foreign 获取另一个股票代码的 K 线数据)
运行回测
要运行回测,请在 Strategy 实例上调用 backtest 方法。以下是一个例子:
[8]:
result = strategy.backtest()
Backtesting: 2017-03-01 00:00:00 to 2022-03-01 00:00:00
Loading bar data...
[*********************100%***********************] 3 of 3 completed
Loaded bar data: 0:00:01
Test split: 2017-03-01 00:00:00 to 2022-02-28 00:00:00
100% (1259 of 1259) |####################| Elapsed Time: 0:00:00 Time: 0:00:000:00
Finished backtest: 0:00:03
这很快!backtest
方法将返回一个 TestResult 实例。你可以通过此实例访问有关回测的各种信息和指标。例如,要查看投资组合的每日余额,你可以使用 Matplotlib 绘制市值:
[9]:
import matplotlib.pyplot as plt
chart = plt.subplot2grid((3, 2), (0, 0), rowspan=3, colspan=2)
chart.plot(result.portfolio.index, result.portfolio['market_value'])
[9]:
[<matplotlib.lines.Line2D at 0x7f44544ecfd0>]

你还可以访问每个持有头寸的每日余额、每次进出场的交易以及下达的所有订单:
[10]:
result.positions
[10]:
long_shares | short_shares | close | equity | market_value | margin | unrealized_pnl | ||
---|---|---|---|---|---|---|---|---|
symbol | date | |||||||
MSFT | 2017-03-03 | 1952 | 0 | 64.25 | 125416.00 | 125416.00 | 0.00 | 585.60 |
2017-03-06 | 1952 | 0 | 64.27 | 125455.03 | 125455.03 | 0.00 | 624.63 | |
2017-03-07 | 1952 | 0 | 64.40 | 125708.80 | 125708.80 | 0.00 | 878.40 | |
2017-03-14 | 1937 | 0 | 64.41 | 124762.18 | 124762.18 | 0.00 | 116.23 | |
TSLA | 2017-03-15 | 0 | 100 | 17.05 | 0.00 | 1718.00 | 1704.87 | 13.13 |
... | ... | ... | ... | ... | ... | ... | ... | ... |
MSFT | 2022-02-22 | 583 | 0 | 287.72 | 167740.76 | 167740.76 | 0.00 | -1375.88 |
AAPL | 2022-02-22 | 1005 | 0 | 164.32 | 165141.61 | 165141.61 | 0.00 | -4060.19 |
MSFT | 2022-02-23 | 583 | 0 | 280.27 | 163397.40 | 163397.40 | 0.00 | -5719.24 |
AAPL | 2022-02-23 | 1005 | 0 | 160.07 | 160870.36 | 160870.36 | 0.00 | -8331.44 |
TSLA | 2022-02-28 | 0 | 100 | 290.14 | 0.00 | 28193.00 | 29014.33 | -821.33 |
938 rows × 7 columns
[11]:
result.trades
[11]:
type | symbol | entry_date | exit_date | entry | exit | shares | pnl | return_pct | agg_pnl | bars | pnl_per_bar | stop | mae | mfe | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
id | |||||||||||||||
1 | long | MSFT | 2017-03-03 | 2017-03-08 | 63.95 | 64.67 | 1952 | 1405.44 | 1.13 | 1405.44 | 3 | 468.48 | bar | -0.33 | 0.83 |
2 | long | MSFT | 2017-03-14 | 2017-03-17 | 64.35 | 64.96 | 1937 | 1181.57 | 0.95 | 2587.01 | 3 | 393.86 | bar | -0.20 | 0.61 |
3 | short | TSLA | 2017-03-15 | 2017-03-17 | 17.18 | 17.55 | 100 | -37.00 | -2.11 | 2550.01 | 2 | -18.50 | bar | -0.54 | 0.23 |
4 | short | TSLA | 2017-03-27 | 2017-03-29 | 17.68 | 18.50 | 100 | -82.00 | -4.43 | 2468.01 | 2 | -41.00 | bar | -1.03 | 0.36 |
5 | short | TSLA | 2017-04-04 | 2017-04-06 | 19.98 | 19.87 | 100 | 11.00 | 0.55 | 2479.01 | 2 | 5.50 | bar | -0.35 | 0.37 |
... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
384 | long | AAPL | 2022-02-11 | 2022-02-16 | 170.56 | 171.69 | 984 | 1111.92 | 0.66 | 180139.06 | 3 | 370.64 | bar | -4.00 | 2.52 |
385 | long | MSFT | 2022-02-11 | 2022-02-16 | 299.26 | 297.27 | 560 | -1114.40 | -0.66 | 179024.66 | 3 | -371.47 | bar | -7.91 | 5.03 |
386 | short | TSLA | 2022-02-16 | 2022-02-18 | 304.61 | 287.41 | 100 | 1720.00 | 5.98 | 180744.66 | 2 | 860.00 | bar | -4.20 | 17.20 |
387 | long | AAPL | 2022-02-18 | 2022-02-24 | 168.36 | 157.43 | 1005 | -10984.65 | -6.49 | 169760.01 | 3 | -3661.55 | bar | -10.93 | 2.18 |
388 | long | MSFT | 2022-02-18 | 2022-02-24 | 290.08 | 283.34 | 583 | -3929.42 | -2.32 | 165830.59 | 3 | -1309.81 | bar | -9.98 | 3.78 |
388 rows × 15 columns
[12]:
result.orders
[12]:
type | symbol | date | shares | limit_price | fill_price | fees | |
---|---|---|---|---|---|---|---|
id | |||||||
1 | buy | MSFT | 2017-03-03 | 1952 | 64.00 | 63.95 | 0.0 |
2 | sell | MSFT | 2017-03-08 | 1952 | NaN | 64.67 | 0.0 |
3 | buy | MSFT | 2017-03-14 | 1937 | 64.70 | 64.35 | 0.0 |
4 | sell | TSLA | 2017-03-15 | 100 | NaN | 17.18 | 0.0 |
5 | sell | MSFT | 2017-03-17 | 1937 | NaN | 64.96 | 0.0 |
... | ... | ... | ... | ... | ... | ... | ... |
773 | buy | AAPL | 2022-02-18 | 1005 | 168.87 | 168.36 | 0.0 |
774 | buy | MSFT | 2022-02-18 | 583 | 290.72 | 290.08 | 0.0 |
775 | sell | AAPL | 2022-02-24 | 1005 | NaN | 157.43 | 0.0 |
776 | sell | MSFT | 2022-02-24 | 583 | NaN | 283.34 | 0.0 |
777 | sell | TSLA | 2022-02-28 | 100 | NaN | 281.93 | 0.0 |
777 rows × 7 columns
此外,result.metrics_df
包含一个使用回测收益率计算得出的指标 DataFrame。你可以在参考文档中了解这些指标的含义。
[13]:
result.metrics_df
[13]:
name | value | |
---|---|---|
0 | trade_count | 388.000000 |
1 | initial_market_value | 500000.000000 |
2 | end_market_value | 665009.260000 |
3 | total_pnl | 165830.590000 |
4 | unrealized_pnl | -821.330000 |
5 | total_return_pct | 33.166118 |
6 | total_profit | 402053.210000 |
7 | total_loss | -236222.620000 |
8 | total_fees | 0.000000 |
9 | max_drawdown | -31619.460000 |
10 | max_drawdown_pct | -4.722785 |
11 | win_rate | 52.577320 |
12 | loss_rate | 47.422680 |
13 | winning_trades | 204.000000 |
14 | losing_trades | 184.000000 |
15 | avg_pnl | 427.398428 |
16 | avg_return_pct | 0.279639 |
17 | avg_trade_bars | 2.414948 |
18 | avg_profit | 1970.849069 |
19 | avg_profit_pct | 3.168775 |
20 | avg_winning_trade_bars | 2.465686 |
21 | avg_loss | -1283.818587 |
22 | avg_loss_pct | -2.923533 |
23 | avg_losing_trade_bars | 2.358696 |
24 | largest_win | 20973.750000 |
25 | largest_win_pct | 14.490000 |
26 | largest_win_bars | 3.000000 |
27 | largest_loss | -10984.650000 |
28 | largest_loss_pct | -6.490000 |
29 | largest_loss_bars | 3.000000 |
30 | max_wins | 7.000000 |
31 | max_losses | 7.000000 |
32 | sharpe | 0.055208 |
33 | sortino | 0.061908 |
34 | profit_factor | 1.317514 |
35 | ulcer_index | 0.659022 |
36 | upi | 0.035627 |
37 | equity_r2 | 0.902314 |
38 | std_error | 65830.655991 |
筛选回测数据
你可以筛选用于回测的数据,仅包括特定的 K 线。例如,你可以通过筛选仅包含周一的数据来限制策略仅在周一交易:
[14]:
result = strategy.backtest(days='mon')
result.orders
Backtesting: 2017-03-01 00:00:00 to 2022-03-01 00:00:00
Loaded cached bar data.
Test split: 2017-03-06 00:00:00 to 2022-02-28 00:00:00
100% (238 of 238) |######################| Elapsed Time: 0:00:00 Time: 0:00:000:00
Finished backtest: 0:00:00
[14]:
type | symbol | date | shares | limit_price | fill_price | fees | |
---|---|---|---|---|---|---|---|
id | |||||||
1 | sell | TSLA | 2017-03-27 | 100 | NaN | 17.68 | 0.0 |
2 | buy | TSLA | 2017-04-10 | 100 | NaN | 20.75 | 0.0 |
3 | sell | TSLA | 2017-04-17 | 100 | NaN | 20.09 | 0.0 |
4 | buy | TSLA | 2017-05-01 | 100 | NaN | 21.40 | 0.0 |
5 | sell | TSLA | 2017-05-08 | 100 | NaN | 20.65 | 0.0 |
... | ... | ... | ... | ... | ... | ... | ... |
178 | sell | TSLA | 2022-02-07 | 100 | NaN | 308.41 | 0.0 |
179 | sell | AAPL | 2022-02-14 | 777 | NaN | 168.07 | 0.0 |
180 | buy | MSFT | 2022-02-14 | 457 | 300.94 | 294.06 | 0.0 |
181 | buy | TSLA | 2022-02-28 | 100 | NaN | 281.93 | 0.0 |
182 | buy | AAPL | 2022-02-28 | 811 | 168.87 | 163.92 | 0.0 |
182 rows × 7 columns
由于启用了缓存,因此无需再次从 Yahoo Finance 下载数据,只需对缓存的数据进行筛选即可。
你还可以使用 between_time 参数按时间范围筛选数据,例如 9:30-10:30 AM。
尽管之前的指标表明我们拥有一个盈利的策略,但我们可能被随机性误导。在下一篇文章中,我们将讨论如何使用自助法进一步评估我们的交易策略。