Features
Signals and stops
Simulate signal-driven entries, exits, callbacks, and advanced stop logic
✅ Stop laddering is a technique for incrementally moving out of a position. Instead of providing a single stop value to close a position, you can provide an array of stop values, with each one removing a certain amount of the position when triggered. You can control this amount by choosing a different ladder mode. Thanks to a new broadcasting feature that allows arrays to broadcast along just one axis, the stop values do not need to have the same shape as the data. You can even provide stop arrays of different shapes as parameters!
data = vbt.YFData.pull("BTC-USD", end="2017-01")
pf = vbt.PF.from_holding(
data,
stop_ladder="uniform",
tp_stop=vbt.Param([
[0.1, 0.2, 0.3, 0.4, 0.5],
[0.4, 0.5, 0.6],
], keys=["tp_ladder_1", "tp_ladder_2"])
)
pf.trades.plot(column="tp_ladder_1").show()✅ Joining other stop orders, time stop orders can close a position either after a certain period of time or on a specific date.
data = vbt.YFData.pull("BTC-USD", start="2022-01", end="2022-04")
entries = vbt.pd_acc.signals.generate_random(data.symbol_wrapper, n=10, seed=42)
pf = vbt.PF.from_signals(data, entries, dt_stop="M")
pf.orders.readable[["Fill Index", "Side", "Stop Type"]] Fill Index Side Stop Type
0 2022-01-19 00:00:00+00:00 Buy None
1 2022-01-31 00:00:00+00:00 Sell DT
2 2022-02-25 00:00:00+00:00 Buy None
3 2022-02-28 00:00:00+00:00 Sell DT
4 2022-03-11 00:00:00+00:00 Buy None
5 2022-03-31 00:00:00+00:00 Sell DT✅ Target size can be converted to signals using a special signal function, giving access to stop and limit order functionality. This is especially useful, for example, in portfolio optimization.
data = vbt.YFData.pull(
["SPY", "TLT", "XLF", "XLE", "XLU", "XLK", "XLB", "XLP", "XLY", "XLI", "XLV"],
start="2022",
end="2023",
missing_index="drop"
)
pfo = vbt.PFO.from_riskfolio(data.returns, every="M")
pf = pfo.simulate(
data,
pf_method="from_signals",
sl_stop=0.05,
tp_stop=0.1,
stop_exit_price="close"
)
pf.plot_allocations().show()✅ Want to customize your simulation based on signals, or even generate signals dynamically according to the current backtesting environment? Two new callbacks now bring simulator flexibility to the next level: one lets you generate or override signals for each asset at every bar, and another allows you to compute user-defined metrics for the entire group at the end of each bar. Both accept a "context" that contains information about the current simulation state, enabling trading decisions to be made in a way similar to event-driven backtesters.
InOutputs = namedtuple("InOutputs", ["fast_sma", "slow_sma"])
def initialize_in_outputs(target_shape):
return InOutputs(
fast_sma=np.full(target_shape, np.nan),
slow_sma=np.full(target_shape, np.nan)
)
@njit
def signal_func_nb(ctx, fast_window, slow_window):
fast_sma = ctx.in_outputs.fast_sma
slow_sma = ctx.in_outputs.slow_sma
fast_start_i = ctx.i - fast_window + 1
slow_start_i = ctx.i - slow_window + 1
if fast_start_i >= 0 and slow_start_i >= 0:
fast_sma[ctx.i, ctx.col] = np.nanmean(ctx.close[fast_start_i : ctx.i + 1])
slow_sma[ctx.i, ctx.col] = np.nanmean(ctx.close[slow_start_i : ctx.i + 1])
is_entry = vbt.pf_nb.iter_crossed_above_nb(ctx, fast_sma, slow_sma)
is_exit = vbt.pf_nb.iter_crossed_below_nb(ctx, fast_sma, slow_sma)
return is_entry, is_exit, False, False
return False, False, False, False
pf = vbt.PF.from_signals(
vbt.YFData.pull("BTC-USD"),
signal_func_nb=signal_func_nb,
signal_args=(50, 200),
in_outputs=vbt.RepFunc(initialize_in_outputs),
)
fig = pf.get_in_output("fast_sma").vbt.plot()
pf.get_in_output("slow_sma").vbt.plot(fig=fig)
pf.orders.plot(plot_ohlc=False, plot_close=False, fig=fig)
fig.show()✅ Signal generation functions have been redesigned to operate on contexts. This allows you to design more complex signal strategies with less namespace pollution.
@njit
def entry_place_func_nb(ctx, index):
for i in range(ctx.from_i, ctx.to_i):
if i == 0:
return i - ctx.from_i
else:
index_before = index[i - 1]
index_now = index[i]
index_next_week = vbt.dt_nb.future_weekday_nb(index_before, 0)
if index_now >= index_next_week:
return i - ctx.from_i
return -1
@njit
def exit_place_func_nb(ctx, index):
for i in range(ctx.from_i, ctx.to_i):
if i == len(index) - 1:
return i - ctx.from_i
else:
index_now = index[i]
index_after = index[i + 1]
index_next_week = vbt.dt_nb.future_weekday_nb(index_now, 0)
if index_after >= index_next_week:
return i - ctx.from_i
return -1
data = vbt.YFData.pull("BTC-USD", start="2020-01-01", end="2020-01-14")
entries, exits = vbt.pd_acc.signals.generate_both(
data.symbol_wrapper.shape,
entry_place_func_nb=entry_place_func_nb,
entry_place_args=(data.index.vbt.to_ns(),),
exit_place_func_nb=exit_place_func_nb,
exit_place_args=(data.index.vbt.to_ns(),),
wrapper=data.symbol_wrapper
)
pd.concat((
entries.rename("Entries"),
exits.rename("Exits")
), axis=1).to_period("W") Entries Exits
Date
2020-01-06/2020-01-12 True False
2020-01-06/2020-01-12 False False
2020-01-06/2020-01-12 False False
2020-01-06/2020-01-12 False False
2020-01-06/2020-01-12 False False
2020-01-06/2020-01-12 False False
2020-01-06/2020-01-12 False True
2020-01-13/2020-01-19 True FalseTutorial
Learn more in the Signal development tutorial.
Copyright © 2021–2026 Oleg Polakow. All rights reserved.
Site content and documentation are provided for using and evaluating VectorBT PRO and for educational purposes. Any other use, including building or supporting competing products or services, requires prior written consent.