轮动交易

轮动交易涉及购买表现最好的资产,同时出售表现不佳的资产。PyBroker 可用于回测此类策略。

[1]:
import pybroker
from pybroker import ExecContext, Strategy, YFinance

我们的策略将涉及对 价格涨幅(ROC) 最高的股票进行排名和购买。首先,我们将使用 TA-Lib 定义一个 20 天的 ROC 指标:

[2]:
import talib as ta

roc_20 = pybroker.indicator(
    "roc_20", lambda data: ta.ROC(data.adj_close, timeperiod=20)
)

接下来,让我们定义策略的规则:

  • 购买 20 天涨幅(ROC)最高的两只股票。

  • 将我们的资本的 50% 分配给每只股票。

  • 如果其中一只股票不再位于前五名的 20 天涨幅(ROC)中,则我们将清盘该股票。

  • 每天交易这些规则。

为了实现该策略,我们编写一个 rotate 函数,将每只股票的 long_score 设置为其 20 天涨幅(ROC)。然后 PyBroker 会根据 long_score 对这些股票进行降序排名。

[3]:
def rotate(ctx: ExecContext):
    ctx.long_score = ctx.indicator("roc_20")[-1]

既然我们已经有了一种根据 ROC 对股票评分的方法,就可以使用 enable_rotation 方法来启用轮动交易。

我们通过 set_max_long_positions 将多头仓位数量上限设置为 2。将 worst_rank_held 设置为 5 会清盘任何跌出前五名 20 天涨幅(ROC)排名之外的当前持仓股票。否则,PyBroker 会买入排名前两位的股票,将 50% 的资金分配给每只股票。这次回测将使用由 10 只股票组成的股票池:

[4]:
strategy = Strategy(YFinance(), start_date="1/1/2018", end_date="1/1/2023")
strategy.set_max_long_positions(2)
strategy.enable_rotation(worst_rank_held=5)
strategy.add_execution(
    rotate,
    [
        "TSLA",
        "NFLX",
        "AAPL",
        "NVDA",
        "AMZN",
        "MSFT",
        "GOOG",
        "AMD",
        "INTC",
        "META",
    ],
    indicators=roc_20,
)
result = strategy.backtest(warmup=20)
Backtesting: 2018-01-01 00:00:00 to 2023-01-01 00:00:00

Loading bar data...
[*********************100%***********************]  10 of 10 completed
Loaded bar data: 0:00:01

Computing indicators...
100% (10 of 10) |########################| Elapsed Time: 0:00:00 Time:  0:00:00

Test split: 2018-01-02 00:00:00 to 2022-12-30 00:00:00
100% (1259 of 1259) |####################| Elapsed Time: 0:00:00 Time:  0:00:00

Finished backtest: 0:00:01
[5]:
result.orders
[5]:
type symbol date created order_type intent shares limit_price market_price fill_price fees
id
1 buy NFLX 2018-02-01 2018-01-31 market buy_to_open 1849 NaN 26.77 26.77 0.0
2 buy AMD 2018-02-01 2018-01-31 market buy_to_open 3639 NaN 13.53 13.53 0.0
3 sell AMD 2018-02-05 2018-02-02 market sell_to_close 3639 NaN 11.56 11.56 0.0
4 buy AMZN 2018-02-05 2018-02-02 market buy_to_open 623 NaN 69.49 69.49 0.0
5 sell AMZN 2018-04-03 2018-04-02 market sell_to_close 623 NaN 69.23 69.23 0.0
... ... ... ... ... ... ... ... ... ... ... ...
256 buy AMD 2022-11-21 2022-11-18 market buy_to_open 3591 NaN 72.28 72.28 0.0
257 sell AMD 2022-12-14 2022-12-13 market sell_to_close 3591 NaN 70.16 70.16 0.0
258 buy NFLX 2022-12-14 2022-12-13 market buy_to_open 8822 NaN 31.96 31.96 0.0
259 sell NVDA 2022-12-28 2022-12-27 market sell_to_close 14918 NaN 14.07 14.07 0.0
260 buy META 2022-12-28 2022-12-27 market buy_to_open 1868 NaN 116.83 116.83 0.0

260 rows × 11 columns

自定义仓位配置

默认情况下,PyBroker 会将我们的资金平均分配到各个仓位。若要自定义此行为,我们可以向 enable_rotation 传入一个 sizer 函数。在轮动决定要买入哪些股票之后,会使用 RotationContext 调用 sizer,使我们能够覆盖每笔入场的仓位规模。long_ranks 属性包含每只股票的排名,其中 1 表示排名最高。

让我们重用相同的策略,但这次将 70% 的资金分配给排名第一的股票,30% 分配给排名第二的股票:

[6]:
from pybroker import RotationContext


def size_by_rank(rotation: RotationContext):
    for symbol, ctx in rotation.ctxs.items():
        if ctx.buy_shares is not None:
            rank = rotation.long_ranks[symbol]
            match rank:
                case 1:
                    ctx.buy_shares = ctx.calc_target_shares(0.7)
                case 2:
                    ctx.buy_shares = ctx.calc_target_shares(0.3)


strategy.enable_rotation(worst_rank_held=5, sizer=size_by_rank)
result = strategy.backtest(warmup=20)
result.orders
Backtesting: 2018-01-01 00:00:00 to 2023-01-01 00:00:00

Loading bar data...
[*********************100%***********************]  10 of 10 completed
Loaded bar data: 0:00:00

Computing indicators...
100% (10 of 10) |########################| Elapsed Time: 0:00:00 Time:  0:00:00

Test split: 2018-01-02 00:00:00 to 2022-12-30 00:00:00
100% (1259 of 1259) |####################| Elapsed Time: 0:00:00 Time:  0:00:00

Finished backtest: 0:00:00
[6]:
type symbol date created order_type intent shares limit_price market_price fill_price fees
id
1 buy NFLX 2018-02-01 2018-01-31 market buy_to_open 2589 NaN 26.77 26.77 0.0
2 buy AMD 2018-02-01 2018-01-31 market buy_to_open 2183 NaN 13.53 13.53 0.0
3 sell AMD 2018-02-05 2018-02-02 market sell_to_close 2183 NaN 11.56 11.56 0.0
4 buy AMZN 2018-02-05 2018-02-02 market buy_to_open 379 NaN 69.49 69.49 0.0
5 sell AMZN 2018-04-03 2018-04-02 market sell_to_close 379 NaN 69.23 69.23 0.0
... ... ... ... ... ... ... ... ... ... ... ...
256 buy AMD 2022-11-21 2022-11-18 market buy_to_open 4701 NaN 72.28 72.28 0.0
257 sell AMD 2022-12-14 2022-12-13 market sell_to_close 4701 NaN 70.16 70.16 0.0
258 buy NFLX 2022-12-14 2022-12-13 market buy_to_open 4790 NaN 31.96 31.96 0.0
259 sell NVDA 2022-12-28 2022-12-27 market sell_to_close 7975 NaN 14.07 14.07 0.0
260 buy META 2022-12-28 2022-12-27 market buy_to_open 2731 NaN 116.83 116.83 0.0

260 rows × 11 columns