问答
如何 …
查看你的 PyBroker 版本
[1]:
import pybroker
pybroker.__version__
[1]:
'2.0.0'
获取其他标的的数据
使用 ctx.foreign,并将标的符号传入 ctx.indicator:
[2]:
from pybroker import ExecContext, Strategy, YFinance, highest
def exec_fn(ctx: ExecContext):
if ctx.symbol == "NVDA":
other_bar_data = ctx.foreign("AMD")
other_highest = ctx.indicator("high_10d", "AMD")
strategy = Strategy(YFinance(), start_date="1/1/2022", end_date="1/1/2023")
strategy.add_execution(
exec_fn,
["NVDA", "AMD"],
indicators=highest("high_10d", "close", period=10),
)
result = strategy.backtest()
Backtesting: 2022-01-01 00:00:00 to 2023-01-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:00 Time: 0:00:00
Test split: 2022-01-03 00:00:00 to 2022-12-30 00:00:00
100% (251 of 251) |######################| Elapsed Time: 0:00:00 Time: 0:00:00
Finished backtest: 0:00:01
你还可以检索其他符号的模型、预测和其他数据。有关更多信息,请参阅 ExecContext 参考文档。
设置限价
设置 买入限价(buy_limit_price) 或 卖出限价(sell_limit_price):
[3]:
from pybroker import ExecContext, Strategy, YFinance
def buy_fn(ctx: ExecContext):
if not ctx.long_pos():
ctx.buy_shares = 100
ctx.buy_limit_price = ctx.close[-1] * 0.99
ctx.hold_bars = 10
strategy = Strategy(YFinance(), start_date="3/1/2022", end_date="1/1/2023")
strategy.add_execution(buy_fn, "SPY")
result = strategy.backtest()
result.orders.head(10)
Backtesting: 2022-03-01 00:00:00 to 2023-01-01 00:00:00
Loading bar data...
[*********************100%***********************] 1 of 1 completed
Loaded bar data: 0:00:00
Test split: 2022-03-01 00:00:00 to 2022-12-30 00:00:00
100% (212 of 212) |######################| Elapsed Time: 0:00:00 Time: 0:00:00
Finished backtest: 0:00:00
[3]:
| type | symbol | date | created | order_type | intent | shares | limit_price | market_price | fill_price | fees | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||
| 1 | buy | SPY | 2022-03-04 | 2022-03-03 | limit | buy_to_open | 100 | 431.35 | 430.63 | 430.63 | 0.0 |
| 2 | sell | SPY | 2022-03-18 | NaT | stop_bar | sell_to_close | 100 | NaN | 441.04 | 441.04 | 0.0 |
| 3 | buy | SPY | 2022-04-06 | 2022-04-05 | limit | buy_to_open | 100 | 446.52 | 446.20 | 446.20 | 0.0 |
| 4 | sell | SPY | 2022-04-21 | NaT | stop_bar | sell_to_close | 100 | NaN | 443.56 | 443.56 | 0.0 |
| 5 | buy | SPY | 2022-04-22 | 2022-04-21 | limit | buy_to_open | 100 | 433.68 | 431.76 | 431.76 | 0.0 |
| 6 | sell | SPY | 2022-05-06 | NaT | stop_bar | sell_to_close | 100 | NaN | 410.26 | 410.26 | 0.0 |
| 7 | buy | SPY | 2022-05-09 | 2022-05-06 | limit | buy_to_open | 100 | 407.23 | 401.46 | 401.46 | 0.0 |
| 8 | sell | SPY | 2022-05-23 | NaT | stop_bar | sell_to_close | 100 | NaN | 394.06 | 394.06 | 0.0 |
| 9 | buy | SPY | 2022-05-24 | 2022-05-23 | limit | buy_to_open | 100 | 392.95 | 391.05 | 391.05 | 0.0 |
| 10 | sell | SPY | 2022-06-08 | NaT | stop_bar | sell_to_close | 100 | NaN | 413.10 | 413.10 | 0.0 |
设置成交价格
设置 买入成交价格(buy_fill_price) 和 卖出成交价格(sell_fill_price)。请参阅 PriceType 以查看选项。
[4]:
from pybroker import ExecContext, PriceType, Strategy, YFinance
def exec_fn(ctx: ExecContext):
if ctx.long_pos():
ctx.buy_shares = 100
ctx.buy_fill_price = PriceType.AVERAGE
else:
ctx.sell_shares = 100
ctx.sell_fill_price = PriceType.CLOSE
strategy = Strategy(YFinance(), start_date="3/1/2022", end_date="1/1/2023")
strategy.add_execution(exec_fn, "SPY")
result = strategy.backtest()
result.orders.head(10)
Backtesting: 2022-03-01 00:00:00 to 2023-01-01 00:00:00
Loading bar data...
[*********************100%***********************] 1 of 1 completed
Loaded bar data: 0:00:00
Test split: 2022-03-01 00:00:00 to 2022-12-30 00:00:00
100% (212 of 212) |######################| Elapsed Time: 0:00:00 Time: 0:00:00
Finished backtest: 0:00:00
[4]:
| type | symbol | date | created | order_type | intent | shares | limit_price | market_price | fill_price | fees | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||
| 1 | sell | SPY | 2022-03-02 | 2022-03-01 | market | sell_to_open | 100 | NaN | 437.89 | 437.89 | 0.0 |
| 2 | sell | SPY | 2022-03-03 | 2022-03-02 | market | sell_to_open | 100 | NaN | 435.71 | 435.71 | 0.0 |
| 3 | sell | SPY | 2022-03-04 | 2022-03-03 | market | sell_to_open | 29 | NaN | 432.17 | 432.17 | 0.0 |
获取当前仓位
使用 positions、long_positions、short_positions、long_pos 和 short_pos。如果只是想检查是否存在多头或空头仓位,而不需要实际获取这些仓位对象,可以使用 has_long_positions 和 has_short_positions:
[5]:
from pybroker import ExecContext, Strategy, YFinance
def exec_fn(ctx: ExecContext):
# Get all positions.
all_positions = tuple(ctx.positions())
# Get all long positions.
long_positions = tuple(ctx.long_positions())
# Get all short positions.
short_positions = tuple(ctx.short_positions())
# Whether any long/short positions are open.
any_long = ctx.has_long_positions()
any_short = ctx.has_short_positions()
# Get long position for current ctx.symbol.
long_position = ctx.long_pos()
# Get short position for a symbol.
short_position = ctx.short_pos("QQQ")
strategy = Strategy(YFinance(), start_date="3/1/2022", end_date="1/1/2023")
strategy.add_execution(exec_fn, ["SPY", "QQQ"])
result = strategy.backtest()
Backtesting: 2022-03-01 00:00:00 to 2023-01-01 00:00:00
Loading bar data...
[*********************100%***********************] 2 of 2 completed
Loaded bar data: 0:00:00
Test split: 2022-03-01 00:00:00 to 2022-12-30 00:00:00
100% (212 of 212) |######################| Elapsed Time: 0:00:00 Time: 0:00:00
Finished backtest: 0:00:00
使用自定义列数据
使用 pybroker.register_columns 注册您的自定义列:
[6]:
import pybroker
from pybroker import ExecContext, Strategy, YFinance
yf = YFinance()
df = yf.query("SPY", start_date="1/1/2022", end_date="1/1/2023")
df["buy_signal"] = 1
def buy_fn(ctx: ExecContext):
if not ctx.long_pos() and ctx.buy_signal[-1] == 1:
ctx.buy_shares = 100
ctx.hold_bars = 1
pybroker.register_columns("buy_signal")
strategy = Strategy(df, start_date="3/1/2022", end_date="1/1/2023")
strategy.add_execution(buy_fn, "SPY")
result = strategy.backtest()
result.orders.head(10)
Loading bar data...
[*********************100%***********************] 1 of 1 completed
Loaded bar data: 0:00:00
Backtesting: 2022-03-01 00:00:00 to 2023-01-01 00:00:00
Test split: 2022-03-01 00:00:00 to 2022-12-30 00:00:00
100% (212 of 212) |######################| 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 | SPY | 2022-03-02 | 2022-03-01 | market | buy_to_open | 100 | NaN | 435.65 | 435.65 | 0.0 |
| 2 | sell | SPY | 2022-03-03 | NaT | stop_bar | sell_to_close | 100 | NaN | 437.45 | 437.45 | 0.0 |
| 3 | buy | SPY | 2022-03-04 | 2022-03-03 | market | buy_to_open | 100 | NaN | 430.63 | 430.63 | 0.0 |
| 4 | sell | SPY | 2022-03-07 | NaT | stop_bar | sell_to_close | 100 | NaN | 425.83 | 425.83 | 0.0 |
| 5 | buy | SPY | 2022-03-08 | 2022-03-07 | market | buy_to_open | 100 | NaN | 421.16 | 421.16 | 0.0 |
| 6 | sell | SPY | 2022-03-09 | NaT | stop_bar | sell_to_close | 100 | NaN | 426.17 | 426.17 | 0.0 |
| 7 | buy | SPY | 2022-03-10 | 2022-03-09 | market | buy_to_open | 100 | NaN | 423.43 | 423.43 | 0.0 |
| 8 | sell | SPY | 2022-03-11 | NaT | stop_bar | sell_to_close | 100 | NaN | 424.15 | 424.15 | 0.0 |
| 9 | buy | SPY | 2022-03-14 | 2022-03-11 | market | buy_to_open | 100 | NaN | 420.17 | 420.17 | 0.0 |
| 10 | sell | SPY | 2022-03-15 | NaT | stop_bar | sell_to_close | 100 | NaN | 422.63 | 422.63 | 0.0 |
在超过一个K线之后下单
使用 buy_delay 和 sell_delay 配置选项:
[7]:
from pybroker import ExecContext, Strategy, StrategyConfig, YFinance
def buy_fn(ctx: ExecContext):
if not tuple(ctx.pending_orders()) and not ctx.long_pos():
ctx.buy_shares = 100
ctx.hold_bars = 1
config = StrategyConfig(buy_delay=5)
strategy = Strategy(
YFinance(), start_date="3/1/2022", end_date="1/1/2023", config=config
)
strategy.add_execution(buy_fn, "SPY")
result = strategy.backtest()
result.orders.head(10)
Backtesting: 2022-03-01 00:00:00 to 2023-01-01 00:00:00
Loading bar data...
[*********************100%***********************] 1 of 1 completed
Loaded bar data: 0:00:00
Test split: 2022-03-01 00:00:00 to 2022-12-30 00:00:00
100% (212 of 212) |######################| Elapsed Time: 0:00:00 Time: 0:00:00
Finished backtest: 0:00:00
[7]:
| type | symbol | date | created | order_type | intent | shares | limit_price | market_price | fill_price | fees | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||
| 1 | buy | SPY | 2022-03-08 | 2022-03-01 | market | buy_to_open | 100 | NaN | 421.16 | 421.16 | 0.0 |
| 2 | sell | SPY | 2022-03-09 | NaT | stop_bar | sell_to_close | 100 | NaN | 426.17 | 426.17 | 0.0 |
| 3 | buy | SPY | 2022-03-16 | 2022-03-09 | market | buy_to_open | 100 | NaN | 430.24 | 430.24 | 0.0 |
| 4 | sell | SPY | 2022-03-17 | NaT | stop_bar | sell_to_close | 100 | NaN | 437.13 | 437.13 | 0.0 |
| 5 | buy | SPY | 2022-03-24 | 2022-03-17 | market | buy_to_open | 100 | NaN | 447.63 | 447.63 | 0.0 |
| 6 | sell | SPY | 2022-03-25 | NaT | stop_bar | sell_to_close | 100 | NaN | 450.71 | 450.71 | 0.0 |
| 7 | buy | SPY | 2022-04-01 | 2022-03-25 | market | buy_to_open | 100 | NaN | 451.30 | 451.30 | 0.0 |
| 8 | sell | SPY | 2022-04-04 | NaT | stop_bar | sell_to_close | 100 | NaN | 454.59 | 454.59 | 0.0 |
| 9 | buy | SPY | 2022-04-11 | 2022-04-04 | market | buy_to_open | 100 | NaN | 442.20 | 442.20 | 0.0 |
| 10 | sell | SPY | 2022-04-12 | NaT | stop_bar | sell_to_close | 100 | NaN | 441.20 | 441.20 | 0.0 |
取消未完成的订单
请参阅 cancel_pending_order 和 cancel_all_pending_orders 方法。
[8]:
from pybroker import ExecContext, Strategy, StrategyConfig, YFinance
def buy_fn(ctx: ExecContext):
pending = tuple(ctx.pending_orders())
if not pending and not ctx.long_pos():
ctx.buy_shares = 100
ctx.hold_bars = 1
if pending and ctx.close[-1] < 430:
ctx.cancel_all_pending_orders(ctx.symbol)
config = StrategyConfig(buy_delay=5)
strategy = Strategy(
YFinance(), start_date="3/1/2022", end_date="1/1/2023", config=config
)
strategy.add_execution(buy_fn, "SPY")
result = strategy.backtest()
result.orders.head(10)
Backtesting: 2022-03-01 00:00:00 to 2023-01-01 00:00:00
Loading bar data...
[*********************100%***********************] 1 of 1 completed
Loaded bar data: 0:00:00
Test split: 2022-03-01 00:00:00 to 2022-12-30 00:00:00
100% (212 of 212) |######################| Elapsed Time: 0:00:00 Time: 0:00:00
Finished backtest: 0:00:00
[8]:
| type | symbol | date | created | order_type | intent | shares | limit_price | market_price | fill_price | fees | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||
| 1 | buy | SPY | 2022-03-23 | 2022-03-16 | market | buy_to_open | 100 | NaN | 446.10 | 446.10 | 0.0 |
| 2 | sell | SPY | 2022-03-24 | NaT | stop_bar | sell_to_close | 100 | NaN | 447.63 | 447.63 | 0.0 |
| 3 | buy | SPY | 2022-03-31 | 2022-03-24 | market | buy_to_open | 100 | NaN | 454.96 | 454.96 | 0.0 |
| 4 | sell | SPY | 2022-04-01 | NaT | stop_bar | sell_to_close | 100 | NaN | 451.30 | 451.30 | 0.0 |
| 5 | buy | SPY | 2022-04-08 | 2022-04-01 | market | buy_to_open | 100 | NaN | 448.29 | 448.29 | 0.0 |
| 6 | sell | SPY | 2022-04-11 | NaT | stop_bar | sell_to_close | 100 | NaN | 442.20 | 442.20 | 0.0 |
| 7 | buy | SPY | 2022-04-19 | 2022-04-11 | market | buy_to_open | 100 | NaN | 441.74 | 441.74 | 0.0 |
| 8 | sell | SPY | 2022-04-20 | NaT | stop_bar | sell_to_close | 100 | NaN | 445.53 | 445.53 | 0.0 |
在多个K线之间保持数据
使用 ctx.session 字典:
[9]:
from pybroker import ExecContext, Strategy, YFinance
def buy_fn(ctx: ExecContext):
if not ctx.long_pos():
ctx.buy_shares = 100
ctx.hold_bars = 1
count = ctx.session.get("entry_count", 0)
ctx.session["entry_count"] = count + 1
strategy = Strategy(YFinance(), start_date="1/1/2022", end_date="1/1/2023")
strategy.add_execution(buy_fn, "SPY")
result = strategy.backtest()
Backtesting: 2022-01-01 00:00:00 to 2023-01-01 00:00:00
Loading bar data...
[*********************100%***********************] 1 of 1 completed
Loaded bar data: 0:00:00
Test split: 2022-01-03 00:00:00 to 2022-12-30 00:00:00
100% (251 of 251) |######################| Elapsed Time: 0:00:00 Time: 0:00:00
Finished backtest: 0:00:00
平仓
使用 sell_all_shares 或 cover_all_shares 来清空仓位:
[10]:
from pybroker import ExecContext, Strategy, YFinance
def buy_fn(ctx: ExecContext):
pos = ctx.long_pos()
if not pos:
ctx.buy_shares = 100
elif pos.bars > 30:
ctx.sell_all_shares()
strategy = Strategy(YFinance(), start_date="1/1/2022", end_date="1/1/2023")
strategy.add_execution(buy_fn, "SPY")
result = strategy.backtest()
result.trades
Backtesting: 2022-01-01 00:00:00 to 2023-01-01 00:00:00
Loading bar data...
[*********************100%***********************] 1 of 1 completed
Loaded bar data: 0:00:00
Test split: 2022-01-03 00:00:00 to 2022-12-30 00:00:00
100% (251 of 251) |######################| Elapsed Time: 0:00:00 Time: 0:00:00
Finished backtest: 0:00:00
[10]:
| type | symbol | entry_date | exit_date | entry | exit | shares | pnl | return_pct | agg_pnl | bars | pnl_per_bar | stop | mae | mfe | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||
| 1 | long | SPY | 2022-01-04 | 2022-02-18 | 477.78 | 435.24 | 100 | -4254.0 | -8.90 | -4254.0 | 32 | -132.94 | None | -57.02 | 2.20 |
| 2 | long | SPY | 2022-02-22 | 2022-04-07 | 430.68 | 447.11 | 100 | 1643.0 | 3.81 | -2611.0 | 32 | 51.34 | None | -20.04 | 31.39 |
| 3 | long | SPY | 2022-04-08 | 2022-05-25 | 448.29 | 395.67 | 100 | -5262.0 | -11.74 | -7873.0 | 32 | -164.44 | None | -67.75 | 2.34 |
| 4 | long | SPY | 2022-05-26 | 2022-07-14 | 402.75 | 375.04 | 100 | -2771.0 | -6.88 | -10644.0 | 32 | -86.59 | None | -40.58 | 14.69 |
| 5 | long | SPY | 2022-07-15 | 2022-08-30 | 382.90 | 400.05 | 100 | 1715.0 | 4.48 | -8929.0 | 32 | 53.59 | None | -2.36 | 48.83 |
| 6 | long | SPY | 2022-08-31 | 2022-10-17 | 398.14 | 362.63 | 100 | -3551.0 | -8.92 | -12480.0 | 32 | -110.97 | None | -50.03 | 13.59 |
| 7 | long | SPY | 2022-10-18 | 2022-12-02 | 371.49 | 405.00 | 100 | 3351.0 | 9.02 | -9129.0 | 32 | 104.72 | None | -7.95 | 38.51 |
同时处理多个标的
使用 set_before_exec 或 set_after_exec:
[11]:
from pybroker import ExecContext, Strategy, YFinance
def long_short_fn(ctxs: dict[str, ExecContext]):
nvda_ctx = ctxs["NVDA"]
amd_ctx = ctxs["AMD"]
if nvda_ctx.long_pos() or amd_ctx.short_pos():
return
if nvda_ctx.bars >= 2 and nvda_ctx.close[-1] < nvda_ctx.low[-2]:
nvda_ctx.buy_shares = 100
nvda_ctx.hold_bars = 3
amd_ctx.sell_shares = 100
amd_ctx.hold_bars = 3
strategy = Strategy(YFinance(), start_date="1/1/2022", end_date="1/1/2023")
strategy.add_execution(None, ["NVDA", "AMD"])
strategy.set_after_exec(long_short_fn)
result = strategy.backtest()
result.trades
Backtesting: 2022-01-01 00:00:00 to 2023-01-01 00:00:00
Loading bar data...
[*********************100%***********************] 2 of 2 completed
Loaded bar data: 0:00:00
Test split: 2022-01-03 00:00:00 to 2022-12-30 00:00:00
100% (251 of 251) |######################| Elapsed Time: 0:00:00 Time: 0:00:00
Finished backtest: 0:00:00
[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 | NVDA | 2022-01-05 | 2022-01-10 | 28.47 | 26.56 | 100 | -191.0 | -6.71 | -191.0 | 3 | -63.67 | bar | -1.91 | 0.95 |
| 2 | short | AMD | 2022-01-05 | 2022-01-10 | 139.52 | 128.72 | 100 | 1080.0 | 8.39 | 889.0 | 3 | 360.00 | bar | -4.24 | 10.80 |
| 3 | long | NVDA | 2022-01-14 | 2022-01-20 | 26.70 | 24.83 | 100 | -187.0 | -7.00 | 702.0 | 3 | -62.33 | bar | -1.87 | 0.50 |
| 4 | short | AMD | 2022-01-14 | 2022-01-20 | 134.21 | 124.96 | 100 | 925.0 | 7.40 | 1627.0 | 3 | 308.33 | bar | -2.79 | 9.25 |
| 5 | long | NVDA | 2022-01-21 | 2022-01-26 | 24.04 | 23.18 | 100 | -86.0 | -3.58 | 1541.0 | 3 | -28.67 | bar | -3.15 | 0.78 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 76 | short | AMD | 2022-12-07 | 2022-12-12 | 70.33 | 69.10 | 100 | 123.0 | 1.78 | 589.0 | 3 | 41.00 | bar | -1.04 | 1.81 |
| 77 | long | NVDA | 2022-12-15 | 2022-12-20 | 17.01 | 16.08 | 100 | -93.0 | -5.47 | 496.0 | 3 | -31.00 | bar | -0.93 | 0.31 |
| 78 | short | AMD | 2022-12-15 | 2022-12-20 | 67.17 | 64.79 | 100 | 238.0 | 3.67 | 734.0 | 3 | 79.33 | bar | -1.04 | 3.46 |
| 79 | long | NVDA | 2022-12-21 | 2022-12-27 | 16.36 | 14.58 | 100 | -178.0 | -10.88 | 556.0 | 3 | -59.33 | bar | -1.78 | 0.27 |
| 80 | short | AMD | 2022-12-21 | 2022-12-27 | 66.53 | 63.63 | 100 | 290.0 | 4.56 | 846.0 | 3 | 96.67 | bar | -1.32 | 4.27 |
80 rows × 15 columns
对夏普比率进行年化
设置 bars_per_year 配置选项。例如,设置为 252 的值将用于对每日回报进行年化。
[12]:
from pybroker import ExecContext, Strategy, StrategyConfig, YFinance
def buy_fn(ctx: ExecContext):
if ctx.long_pos() or ctx.bars < 2:
return
if ctx.close[-1] < ctx.high[-2]:
ctx.buy_shares = 100
ctx.hold_bars = 1
config = StrategyConfig(bars_per_year=252)
strategy = Strategy(
YFinance(), start_date="1/1/2022", end_date="1/1/2023", config=config
)
strategy.add_execution(buy_fn, "SPY")
result = strategy.backtest()
result.metrics.sharpe
Backtesting: 2022-01-01 00:00:00 to 2023-01-01 00:00:00
Loading bar data...
[*********************100%***********************] 1 of 1 completed
Loaded bar data: 0:00:00
Test split: 2022-01-03 00:00:00 to 2022-12-30 00:00:00
100% (251 of 251) |######################| Elapsed Time: 0:00:00 Time: 0:00:00
Finished backtest: 0:00:00
[12]:
-0.8490616135692908
获取和设置全局参数
使用 param 来设置在整个会话中共享的全局值。对于配合 optimize 使用的可调策略参数,请改用 hyperparam:
[13]:
import pybroker
# Set parameter.
pybroker.param("lookback", 100)
# Get parameter.
pybroker.param("lookback")
[13]:
100
设置目标仓位
set_target_shares 会买卖足够的股数,以逼近目标仓位:
[14]:
from pybroker import ExecContext, Strategy, YFinance
def exec_fn(ctx: ExecContext):
if ctx.bars < 2:
return
if ctx.close[-1] > ctx.close[-2]:
if ctx.short_pos() is not None:
ctx.set_target_shares(0, dir="short")
else:
ctx.set_target_shares(0.5, dir="long")
elif ctx.close[-1] < ctx.close[-2]:
if ctx.long_pos() is not None:
ctx.set_target_shares(0, dir="long")
else:
ctx.set_target_shares(0.5, dir="short")
strategy = Strategy(YFinance(), start_date="1/1/2022", end_date="1/1/2023")
strategy.add_execution(exec_fn, ["NVDA", "AMD"])
result = strategy.backtest()
result.trades
Backtesting: 2022-01-01 00:00:00 to 2023-01-01 00:00:00
Loading bar data...
[*********************100%***********************] 2 of 2 completed
Loaded bar data: 0:00:00
Test split: 2022-01-03 00:00:00 to 2022-12-30 00:00:00
100% (251 of 251) |######################| Elapsed Time: 0:00:00 Time: 0:00:00
Finished backtest: 0:00:00
[14]:
| type | symbol | entry_date | exit_date | entry | exit | shares | pnl | return_pct | agg_pnl | bars | pnl_per_bar | stop | mae | mfe | |
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| id | |||||||||||||||
| 1 | short | AMD | 2022-01-05 | 2022-01-07 | 139.52 | 134.29 | 346 | 1809.58 | 3.89 | 1809.58 | 2 | 904.79 | None | -4.24 | 7.75 |
| 2 | short | AMD | 2022-01-06 | 2022-01-07 | 134.89 | 134.29 | 21 | 12.60 | 0.45 | 1822.18 | 1 | 12.60 | None | -3.11 | 3.12 |
| 3 | short | NVDA | 2022-01-05 | 2022-01-07 | 28.47 | 27.74 | 1707 | 1246.11 | 2.63 | 3068.29 | 2 | 623.06 | None | -0.95 | 1.40 |
| 4 | short | NVDA | 2022-01-06 | 2022-01-07 | 27.75 | 27.74 | 10 | 0.10 | 0.04 | 3068.39 | 1 | 0.10 | None | -0.69 | 0.68 |
| 5 | short | NVDA | 2022-01-10 | 2022-01-11 | 26.56 | 27.45 | 1891 | -1682.99 | -3.24 | 1385.40 | 1 | -1682.99 | None | -0.91 | 0.92 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... | ... |
| 283 | long | AMD | 2022-12-27 | 2022-12-28 | 63.63 | 62.75 | 469 | -412.72 | -1.38 | -39814.08 | 1 | -412.72 | None | -0.88 | 0.65 |
| 284 | short | AMD | 2022-12-29 | 2022-12-30 | 64.12 | 63.98 | 437 | 61.18 | 0.22 | -39752.90 | 1 | 61.18 | None | -1.06 | 1.06 |
| 285 | short | NVDA | 2022-12-23 | 2022-12-30 | 15.11 | 14.43 | 1977 | 1344.36 | 4.71 | -38408.54 | 4 | 336.09 | None | -0.23 | 1.23 |
| 286 | short | NVDA | 2022-12-27 | 2022-12-30 | 14.58 | 14.43 | 15 | 2.25 | 1.04 | -38406.29 | 3 | 0.75 | None | -0.52 | 0.70 |
| 287 | short | NVDA | 2022-12-28 | 2022-12-30 | 14.07 | 14.43 | 147 | -52.92 | -2.49 | -38459.21 | 2 | -26.46 | None | -0.61 | 0.19 |
287 rows × 15 columns
记录仓位K线数据
设置 record_position_bars,即可在 result.positions 中捕获每根K线的仓位快照:
[15]:
from pybroker import ExecContext, Strategy, StrategyConfig, YFinance
def buy_fn(ctx: ExecContext):
if not ctx.long_pos():
ctx.buy_shares = 100
ctx.hold_bars = 3
config = StrategyConfig(record_position_bars=True)
strategy = Strategy(
YFinance(), start_date="1/1/2022", end_date="1/1/2023", config=config
)
strategy.add_execution(buy_fn, ["NVDA", "AMD"])
result = strategy.backtest()
result.positions
Backtesting: 2022-01-01 00:00:00 to 2023-01-01 00:00:00
Loading bar data...
[*********************100%***********************] 2 of 2 completed
Loaded bar data: 0:00:00
Test split: 2022-01-03 00:00:00 to 2022-12-30 00:00:00
100% (251 of 251) |######################| Elapsed Time: 0:00:00 Time: 0:00:00
Finished backtest: 0:00:00
[15]:
| long_shares | short_shares | close | equity | market_value | margin | unrealized_pnl | ||
|---|---|---|---|---|---|---|---|---|
| symbol | date | |||||||
| AMD | 2022-01-04 | 100 | 0 | 144.42 | 14442.0 | 14442.0 | 0.0 | -214.0 |
| NVDA | 2022-01-04 | 100 | 0 | 29.29 | 2929.0 | 2929.0 | 0.0 | -12.0 |
| AMD | 2022-01-05 | 100 | 0 | 136.15 | 13615.0 | 13615.0 | 0.0 | -1041.0 |
| NVDA | 2022-01-05 | 100 | 0 | 27.60 | 2760.4 | 2760.4 | 0.0 | -180.6 |
| AMD | 2022-01-06 | 100 | 0 | 136.23 | 13623.0 | 13623.0 | 0.0 | -1033.0 |
| ... | ... | ... | ... | ... | ... | ... | ... | ... |
| NVDA | 2022-12-27 | 100 | 0 | 14.12 | 1412.1 | 1412.1 | 0.0 | -138.9 |
| AMD | 2022-12-29 | 100 | 0 | 64.82 | 6482.0 | 6482.0 | 0.0 | 70.0 |
| NVDA | 2022-12-29 | 100 | 0 | 14.60 | 1460.3 | 1460.3 | 0.0 | 15.3 |
| AMD | 2022-12-30 | 100 | 0 | 64.77 | 6477.0 | 6477.0 | 0.0 | 65.0 |
| NVDA | 2022-12-30 | 100 | 0 | 14.61 | 1461.4 | 1461.4 | 0.0 | 16.4 |
376 rows × 7 columns