问答

如何 …

… 获取 PyBroker 的版本?

[1]:
import pybroker

pybroker.__version__
[1]:
'1.1.27'

… 从另外一个标的获取数据?

[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:01 Time:  0:00:01

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:02

你还可以检索其他符号的模型、预测和其他数据。有关更多信息,请参阅 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 shares limit_price fill_price fees
id
1 buy SPY 2022-03-04 100 431.35 430.62 0.0
2 sell SPY 2022-03-18 100 NaN 441.04 0.0
3 buy SPY 2022-04-06 100 446.52 446.20 0.0
4 sell SPY 2022-04-21 100 NaN 443.56 0.0
5 buy SPY 2022-04-22 100 433.68 431.76 0.0
6 sell SPY 2022-05-06 100 NaN 410.26 0.0
7 buy SPY 2022-05-09 100 407.23 401.46 0.0
8 sell SPY 2022-05-23 100 NaN 394.06 0.0
9 buy SPY 2022-05-24 100 392.95 391.05 0.0
10 sell SPY 2022-06-08 100 NaN 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(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
[4]:
type symbol date shares limit_price fill_price fees
id
1 buy SPY 2022-03-04 100 431.35 430.62 0.0
2 sell SPY 2022-03-18 100 NaN 441.04 0.0
3 buy SPY 2022-04-06 100 446.52 446.20 0.0
4 sell SPY 2022-04-21 100 NaN 443.56 0.0
5 buy SPY 2022-04-22 100 433.68 431.76 0.0
6 sell SPY 2022-05-06 100 NaN 410.26 0.0
7 buy SPY 2022-05-09 100 407.23 401.46 0.0
8 sell SPY 2022-05-23 100 NaN 394.06 0.0
9 buy SPY 2022-05-24 100 392.95 391.05 0.0
10 sell SPY 2022-06-08 100 NaN 413.10 0.0

… 获取当前仓位

[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())
    # 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

有关更多信息,请参阅 Position 类

… 使用自定义列数据

使用 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 shares limit_price fill_price fees
id
1 buy SPY 2022-03-02 100 NaN 435.65 0.0
2 sell SPY 2022-03-03 100 NaN 437.45 0.0
3 buy SPY 2022-03-04 100 NaN 430.62 0.0
4 sell SPY 2022-03-07 100 NaN 425.83 0.0
5 buy SPY 2022-03-08 100 NaN 421.16 0.0
6 sell SPY 2022-03-09 100 NaN 426.17 0.0
7 buy SPY 2022-03-10 100 NaN 423.43 0.0
8 sell SPY 2022-03-11 100 NaN 424.15 0.0
9 buy SPY 2022-03-14 100 NaN 420.17 0.0
10 sell SPY 2022-03-15 100 NaN 422.63 0.0

… 在超过一个K线之后下单

使用 buy_delaysell_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 shares limit_price fill_price fees
id
1 buy SPY 2022-03-08 100 NaN 421.16 0.0
2 sell SPY 2022-03-09 100 NaN 426.17 0.0
3 buy SPY 2022-03-16 100 NaN 430.24 0.0
4 sell SPY 2022-03-17 100 NaN 437.13 0.0
5 buy SPY 2022-03-24 100 NaN 447.63 0.0
6 sell SPY 2022-03-25 100 NaN 450.71 0.0
7 buy SPY 2022-04-01 100 NaN 451.30 0.0
8 sell SPY 2022-04-04 100 NaN 454.59 0.0
9 buy SPY 2022-04-11 100 NaN 442.20 0.0
10 sell SPY 2022-04-12 100 NaN 441.20 0.0

… 取消未完成的订单

请参阅 cancel_pending_ordercancel_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 shares limit_price fill_price fees
id
1 buy SPY 2022-03-23 100 NaN 446.10 0.0
2 sell SPY 2022-03-24 100 NaN 447.63 0.0
3 buy SPY 2022-03-31 100 NaN 454.96 0.0
4 sell SPY 2022-04-01 100 NaN 451.30 0.0
5 buy SPY 2022-04-08 100 NaN 448.29 0.0
6 sell SPY 2022-04-11 100 NaN 442.20 0.0
7 buy SPY 2022-04-19 100 NaN 441.74 0.0
8 sell SPY 2022-04-20 100 NaN 445.53 0.0

… 如何在多个K线之间保持数据?

使用 ExecContext#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
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
2 long SPY 2022-02-22 2022-04-07 430.68 447.11 100 1643.0 3.81 -2611.0 32 51.34 None
3 long SPY 2022-04-08 2022-05-25 448.29 395.67 100 -5262.0 -11.74 -7873.0 32 -164.44 None
4 long SPY 2022-05-26 2022-07-14 402.75 375.04 100 -2771.0 -6.88 -10644.0 32 -86.59 None
5 long SPY 2022-07-15 2022-08-30 382.90 400.05 100 1715.0 4.48 -8929.0 32 53.59 None
6 long SPY 2022-08-31 2022-10-17 398.14 362.63 100 -3551.0 -8.92 -12480.0 32 -110.97 None
7 long SPY 2022-10-18 2022-12-02 371.49 405.00 100 3351.0 9.02 -9129.0 32 104.72 None

… 如何同时处理多个标的?

使用 set_before_execset_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
id
1 long NVDA 2022-01-05 2022-01-10 284.74 265.57 100 -1917.0 -6.73 -1917.0 3 -639.00 bar
2 short AMD 2022-01-05 2022-01-10 139.52 128.72 100 1080.0 8.39 -837.0 3 360.00 bar
3 long NVDA 2022-01-14 2022-01-20 267.04 248.28 100 -1876.0 -7.03 -2713.0 3 -625.33 bar
4 short AMD 2022-01-14 2022-01-20 134.21 124.96 100 925.0 7.40 -1788.0 3 308.33 bar
5 long NVDA 2022-01-21 2022-01-26 240.43 231.79 100 -864.0 -3.59 -2652.0 3 -288.00 bar
... ... ... ... ... ... ... ... ... ... ... ... ... ...
76 short AMD 2022-12-07 2022-12-12 70.33 69.10 100 123.0 1.78 1863.0 3 41.00 bar
77 long NVDA 2022-12-15 2022-12-20 170.10 160.81 100 -929.0 -5.46 934.0 3 -309.67 bar
78 short AMD 2022-12-15 2022-12-20 67.17 64.79 100 238.0 3.67 1172.0 3 79.33 bar
79 long NVDA 2022-12-21 2022-12-27 163.65 145.78 100 -1787.0 -10.92 -615.0 3 -595.67 bar
80 short AMD 2022-12-21 2022-12-27 66.53 63.62 100 291.0 4.57 -324.0 3 97.00 bar

80 rows × 13 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.8930247224724036

… 用于卖空的限制保证金

默认情况下,PyBroker 并不限制可以用于卖空的保证金数量。然而,你可以手动限制可用保证金的数量:

[13]:
import pybroker
from pybroker import ExecContext
from decimal import Decimal

def short_fn(ctx: ExecContext):
    margin_requirement = Decimal('0.25')
    max_margin = ctx.total_equity / margin_requirement - ctx.total_equity
    if not ctx.short_pos():
        available_margin = max_margin - ctx.total_margin
        ctx.sell_shares = ctx.calc_target_shares(0.5, cash=available_margin)
        ctx.hold_bars = 1

strategy = Strategy(YFinance(), start_date='1/1/2022', end_date='1/1/2023')
strategy.add_execution(short_fn, ['NVDA', 'AMD'])
result = strategy.backtest()
result.portfolio.head(10)
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
[13]:
cash equity margin market_value pnl unrealized_pnl fees
date
2022-01-03 100000.00 100000.00 0.00 100000.00 0.00 0.00 0.0
2022-01-04 100000.00 100000.00 289702.46 102722.18 0.00 2722.18 0.0
2022-01-05 111667.90 111667.90 0.00 111667.90 11667.90 0.00 0.0
2022-01-06 111667.90 111667.90 338321.57 107432.09 11667.90 -4235.81 0.0
2022-01-07 112472.56 112472.56 0.00 112472.56 12472.56 0.00 0.0
2022-01-10 112472.56 112472.56 338302.00 103062.55 12472.56 -9410.01 0.0
2022-01-11 98536.05 98536.05 0.00 98536.05 -1463.95 0.00 0.0
2022-01-12 98536.05 98536.05 296592.41 99830.87 -1463.95 1294.82 0.0
2022-01-13 103550.41 103550.41 0.00 103550.41 3550.41 0.00 0.0
2022-01-14 103550.41 103550.41 317490.89 99036.58 3550.41 -4513.83 0.0

… 如何获取和设置全局参数?

[14]:
import pybroker

# Set parameter.
pybroker.param('lookback', 100)

# Get parameter.
pybroker.param('lookback')
[14]:
100

… 如何应用随机滑点?

设置一个 RandomSlippageModel

[15]:
from pybroker import ExecContext, RandomSlippageModel, Strategy, YFinance

def buy_fn(ctx: ExecContext):
    if not ctx.long_pos():
        ctx.buy_shares = 100
        ctx.hold_bars = 1

slippage = RandomSlippageModel(min_pct=1.0, max_pct=5.0) # Slippage of 1-5%
strategy = Strategy(YFinance(), start_date='3/1/2022', end_date='1/1/2023')
strategy.set_slippage_model(slippage)
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
[15]:
type symbol date shares limit_price fill_price fees
id
1 buy SPY 2022-03-02 98 NaN 435.65 0.0
2 sell SPY 2022-03-03 98 NaN 437.45 0.0
3 buy SPY 2022-03-04 98 NaN 430.62 0.0
4 sell SPY 2022-03-07 98 NaN 425.83 0.0
5 buy SPY 2022-03-08 98 NaN 421.16 0.0
6 sell SPY 2022-03-09 98 NaN 426.17 0.0
7 buy SPY 2022-03-10 97 NaN 423.43 0.0
8 sell SPY 2022-03-11 97 NaN 424.15 0.0
9 buy SPY 2022-03-14 97 NaN 420.17 0.0
10 sell SPY 2022-03-15 97 NaN 422.63 0.0