Features
Live simulation
Continue backtests on new bars, chain runs, and feed an external trading system
Can a backtest keep running as new bars arrive, without recomputing its history? VBT continues a portfolio from its last state: cash, positions, pending stops, and limit orders carry over, so each new bar advances the simulation from where it stopped. You can replay historical data, then keep the same simulated account running on new data for incremental backtests and paper trading in Python.
data = vbt.YFData.pull("BTC-USD", start="2023-01-01", end="2024-01-01")
def get_signals(data):
sma = data.close.rolling(20).mean()
return data.close.vbt.crossed_above(sma), data.close.vbt.crossed_below(sma)
history = data.iloc[:330]
entries, exits = get_signals(history)
pf = vbt.PF.from_signals(
history,
entries,
exits,
tsl_stop=0.05,
attach_preparer=True,
)
emitted = []
for i in range(330, len(data.index)):
available = data.iloc[:i + 1]
entries, exits = get_signals(available)
pf = pf.update(
available.iloc[-1:],
entries=entries.iloc[-1:],
exits=exits.iloc[-1:],
)
orders = pf.orders.readable
emitted.append(orders[orders["Fill Index"] == available.index[-1]])
pd.concat(emitted)[["Fill Index", "Side", "Price", "Stop Type"]] Fill Index Side Price Stop Type
45 2023-12-11 00:00:00+00:00 Sell 42470.239844 TSL
46 2023-12-18 00:00:00+00:00 Buy 42623.539062 None
47 2023-12-26 00:00:00+00:00 Sell 42149.559180 TSL
48 2023-12-27 00:00:00+00:00 Buy 43442.855469 None
49 2023-12-28 00:00:00+00:00 Sell 42627.855469 Noneentries, exits = get_signals(data)
full_pf = vbt.PF.from_signals(data, entries, exits, tsl_stop=0.05)
pf.orders.records.equals(full_pf.orders.records), pf.value.equals(full_pf.value)(True, True)The trailing stop that fired on December 11 belonged to a position opened before the replay began. Its peak price was carried through every update. After 35 single-bar updates, the orders and the equity curve match the one-shot backtest exactly.
From research updates to live data
Keep your research moving as new data arrives. Continuation connects historical backtests, incremental analysis, and simulated trading:
| What you want to do | How VBT handles it |
|---|---|
| Update a strategy after another day of prices | Extend the portfolio with pf.update |
| Replay a historical feed or monitor a simulated account | Process one new bar or a batch of bars at a time |
| Retrain a model or change parameters between periods | Chain simulations while carrying the account forward |
| Keep a native simulator running between incoming bars | Advance a Rust stepper and save checkpoints |
The sections below cover both Python portfolio updates and native Rust stepping. Your data source and execution system can remain separate from the simulation.
Continuing from the last state
pf.update takes new data and new inputs and continues from the portfolio's last state. That state
includes cash, positions, debt, the valuation price, entry prices, pending limit orders, and every
stop, including trailing peaks and time-stop counters. By default the result contains the full
history. With stack=False it contains only the new segment, which is cheaper when you only need
the latest orders.
Continuation also works with custom trading rules. A cooldown after a loss, for example, can span several updates. Keep your callback's memory alongside the portfolio state and pass it into the next update. The Event-driven backtesting page introduces these rules, and the live tutorial shows how to carry their memory forward.
Resume after a restart
The state can also be stored. save_state=True keeps the cash and position series computed during
the simulation, and pf.last_state holds the final state that a later run starts from. A portfolio
built with attach_preparer=True can be written to disk with pf.save(path) and restored with
vbt.PF.load(path) in a later session, ready for the next pf.update. For a complete live
pipeline, also retain the indicator state and any custom strategy memory your application owns. The
Portfolio continuation highlight below verifies a trailing stop and a
time stop across updates.
Chaining and hybrid runs
Some workflows need to stop the simulation on purpose: retrain a model every month, reoptimize
weights, or change parameters between periods. Run the backtest in segments, start each segment from
the previous one, and stack the segments into one history with vbt.PF.row_stack. The
Chaining simulations highlight below builds such a chain, and the same
pattern lets a portfolio optimizer re-estimate weights at each rebalance with the account carried
forward.
Bar-by-bar stepping
The native Rust engine adds steppers: a simulator object that stays in memory and advances one bar at a time, with strategy and execution state kept between steps. A stepper can be cloned or serialized as a checkpoint and restored later, for example after a restart, and its outputs can be saved as NumPy files that Python loads back into a portfolio.
Tutorial
The members-only From Python to Rust tutorial continues a stateful strategy in Python, in Numba, and with a native Rust stepper, and checks that all three agree.
Live pipelines
A live loop has three stages, and each one keeps its own state: the data, the indicators, and the
portfolio. data.update() fetches new bars and, with return_meta=True, reports which rows were
appended and which were revised. Indicators either recompute over the window they need or, for
streaming indicators, update from their last state. The portfolio continues with pf.update.
vbt.wait(timeframe, floor=True) sleeps until the next bar boundary, so the loop runs right after
each bar closes:
while True:
vbt.wait("1h", floor=True)
update = data.update(return_meta=True)
data = update["data"]
for row in update["appended"]:
... # compute signals for the closed bar, then call pf.updateConnecting to a broker
VBT does not send orders to a broker. It decides and simulates, and your execution layer routes the orders, handles fills, and reports them back. A few rules keep the two consistent:
- Decide on closed bars. Use only completed bars for signals, so a decision cannot change after it was acted on. Many sources also return the bar that is still forming, so drop it before computing signals.
- Plan before the fill. With next-open execution, the order to place comes from the signals of the last closed bar. The simulator records it only once the next bar arrives, and a stop or a conflicting signal can still change it.
- Reconcile. Compare the broker's fills and positions with the simulated ones after each update, and avoid sending an order twice when a bar is revised. Real fills can be analyzed as a portfolio of their own, as shown on the Orders and execution page.
Order records hold only filled orders. Pending stops and limit orders live in the final state, so a broker-side stop can mirror the simulated one:
data = vbt.YFData.pull("ETH-USD", start="2024-09-01", end="2024-11-13")
sma = data.close.rolling(20).mean()
pf = vbt.PF.from_signals(
data,
data.close.vbt.crossed_above(sma),
data.close.vbt.crossed_below(sma),
tsl_stop=0.08,
)
state = pf.last_state
position = float(state.last_position[0])
tsl_info = state.last_tsl_info[0]
position, float(tsl_info["peak_price"])(0.0333110557990236, 3444.154052734375)vbt.pf_nb.get_tsl_info_target_price_nb(tsl_info, position) 3168.621728515625The position opened on November 6 and is still open after the November 12 close. If the next bar trades through 3168.62, the backtest sells at that level, or at the open if the bar opens below it. Your execution layer can use the same stop level at the broker and reconcile the actual fill with the simulated one.
✅ Pick up right where your simulation left off as new data arrives. VBT carries positions, order IDs, and stop state into each update, including entry timestamps and trailing highs. Your trailing stops remember their peaks, your time stops keep counting, and each update returns a new portfolio with the combined history.
close = pd.Series(
[100.0, 110.0, 108.0, 98.0, 103.0, 115.0],
index=pd.date_range("2026-09-01", periods=6, freq="D")
)
entries = pd.Series([True, False, False, False, True, False], index=close.index)
pf_kwargs = dict(size=1, init_cash=1000, tsl_stop=0.05, freq="1D")
pf = vbt.PF.from_signals(
close.iloc[:2],
entries=entries.iloc[:2],
attach_preparer=True,
**pf_kwargs,
)
for start in [2, 4]:
pf = pf.update(
close.iloc[start:start + 2],
entries=entries.iloc[start:start + 2],
)
print(pf.orders.readable[["Order Id", "Fill Index", "Side", "Price", "Stop Type"]]) Order Id Fill Index Side Price Stop Type
0 0 2026-09-01 Buy 100.0 None
1 1 2026-09-04 Sell 98.0 TSL
2 2 2026-09-05 Buy 103.0 Nonefull_pf = vbt.PF.from_signals(close, entries=entries, **pf_kwargs)
pf.orders.records.equals(full_pf.orders.records) and pf.value.equals(full_pf.value)TrueTutorial
Learn more in the Live simulation tutorial.
✅ Every built-in simulation function now returns the final simulation state after processing all data, including the last cash, position, pending order(s), and other relevant information. This state can be passed to the next simulation run, allowing you to continue from where the previous run ended. This enables seamless chaining of simulations across different time periods or datasets.
data = vbt.BinanceData.pull(
"BTCUSDT",
start="one year ago",
timeframe="5 minutes",
cache=True
)
fast_sma = data.run("talib_func:sma", timeperiod=20)
slow_sma = data.run("talib_func:sma", timeperiod=50)
long_entries = fast_sma.vbt.crossed_above(slow_sma)
short_entries = fast_sma.vbt.crossed_below(slow_sma)
single_pf = vbt.PF.from_signals(
data,
long_entries=long_entries,
short_entries=short_entries,
)
data_splits = data.split(by="month")
long_entries_splits = long_entries.vbt.split(by="month", into=None)
short_entries_splits = short_entries.vbt.split(by="month", into=None)
pf_list = []
last_state = None
for i in range(len(data_splits)):
pf = vbt.PF.from_signals(
data_splits.iloc[i],
long_entries=long_entries_splits.iloc[i],
short_entries=short_entries_splits.iloc[i],
last_state=last_state,
)
pf_list.append(pf)
last_state = pf.last_state
stacked_pf = vbt.PF.row_stack(*pf_list, chained=True)
print(stacked_pf.returns.equals(single_pf.returns))TrueRelated pages
- Backtesting engineSimulate orders, signals, and callbacks across many assets and parameters at once
- Event-driven backtestingWrite compiled callbacks for cooldowns, position limits, and custom simulators
- Orders and executionSimulate limit and stop-entry orders, fill prices, timing, rejections, and real fills
- Signal backtestingTurn entry and exit signals into long, short, reversing, and pyramided positions
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.