FAQs

How to…

… get your version of PyBroker?

[1]:
import pybroker

pybroker.__version__
[1]:
'1.1.27'

… get data for another symbol?

[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

You can also retrieve models, predictions, and other data for other symbols. For more information, refer to the ExecContext reference documentation.

… set a limit price?

Set buy_limit_price or 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

… set the fill price?

Set buy_fill_price and sell_fill_price. See PriceType for options.

[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

… get current 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())
    # 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

See the Position class for more information.

… use custom column data?

Register your custom columns with 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

… place an order more than one bar ahead?

Use the buy_delay and sell_delay configuration options:

[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 orders?

See the cancel_pending_order and cancel_all_pending_orders methods.

[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

… persist data across bars?

Use the ExecContext#session dictionary:

[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

… exit a position?

Use sell_all_shares() or cover_all_shares() to liquidate a position:

[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

… process multiple symbols at once?

Use set_before_exec or 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
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

… annualize the Sharpe Ratio?

Set the bars_per_year configuration option. For example, setting a value of 252 would be used to annualize daily returns.

[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

… limit margin used for short selling?

By default, PyBroker does not limit the amount of margin that can be used for short selling. However, you can manually limit the amount of margin that can be used:

[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

… get and set a global parameter?

[14]:
import pybroker

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

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

… apply random slippage?

Set a 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