# Live simulation (/features/backtesting/live-simulation)

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.

```python title="Replay the last 35 days of 2023 one bar at a time"
>>> 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,  # (1)
... )

>>> emitted = []
>>> for i in range(330, len(data.index)):
...     available = data.iloc[:i + 1]  # (2)
...     entries, exits = get_signals(available)
...     pf = pf.update(  # (3)
...         available.iloc[-1:],
...         entries=entries.iloc[-1:],
...         exits=exits.iloc[-1:],
...     )
...     orders = pf.orders.readable
...     emitted.append(orders[orders["Fill Index"] == available.index[-1]])  # (4)
>>> 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      None

>>> entries, exits = get_signals(data)
>>> full_pf = vbt.PF.from_signals(data, entries, exits, tsl_stop=0.05)  # (5)
>>> pf.orders.records.equals(full_pf.orders.records), pf.value.equals(full_pf.value)
(True, True)
```

1.  Keep the simulation arguments so later updates reuse them.
2.  Only the data up to the new bar exists. The signals are computed from it, as they would be live.
3.  Simulate only the new bar, starting from the stored state, and append it to the history.
4.  The orders this bar produced, ready to hand to an execution system.
5.  Run the whole year at once to check the incremental result.

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 \[#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 \[#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](/features/backtesting/event-driven-backtesting/) page
introduces these rules, and the live tutorial shows how to carry their memory forward.

## Resume after a restart \[#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](#portfolio-continuation) highlight below verifies a trailing stop and a
time stop across updates.

## Chaining and hybrid runs \[#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](#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 \[#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.

!!! info "Tutorial"
    The members-only [From Python to Rust](https://members.vectorbt.pro/tutorials/from-python-to-rust/live/)
    tutorial continues a stateful strategy in Python, in Numba, and with a native Rust stepper, and
    checks that all three agree.

## Live pipelines \[#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:

```python title="Structure of a polling loop"
>>> 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.update
```

## Connecting to a broker \[#connecting-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](/features/backtesting/orders-and-execution/#live-fills-versus-the-backtest)
    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:

```python title="Read the trailing stop level to place at the broker"
>>> 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  # (1)
>>> 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)  # (2)
3168.621728515625
```

1.  Cash, position, open position info, and every pending stop and limit order after the last bar.
2.  8% below the highest price since entry. The helper returns `NaN` when no trailing stop is active.

The 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.

## Portfolio continuation \[#portfolio-continuation]

New in v2026.9.5

✅ 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.

=== "Trailing stop"
    ```python title="Preserve a trailing stop across two updates"
    >>> 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,  # (1)
    ...     **pf_kwargs,
    ... )
    >>> for start in [2, 4]:
    ...     pf = pf.update(  # (2)
    ...         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      None

    >>> full_pf = vbt.PF.from_signals(close, entries=entries, **pf_kwargs)  # (3)
    >>> pf.orders.records.equals(full_pf.orders.records) and pf.value.equals(full_pf.value)
    True
    ```

    1.  Keep the simulation arguments available for subsequent updates.
    2.  Preserve the $110 trailing high across updates. The 5% stop exits at $98, the next available
        close below the threshold. Updates join the history by default. Use `stack=False` to return
        only the new segment.
    3.  Compare the combined order records and portfolio value with a single run over the full dataset.

=== "Time stop"
    ```python title="Preserve a three-day time stop across an update"
    >>> 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 = vbt.PF.from_signals(
    ...     close.iloc[:2],
    ...     entries=entries.iloc[:2],
    ...     td_stop="3 days",
    ...     size=1,
    ...     init_cash=1000,
    ...     attach_preparer=True,
    ...     freq="1D",
    ... )
    >>> pf = pf.update(close.iloc[2:], entries=entries.iloc[2:])  # (1)

    >>> print(pf.orders.readable[["Fill Index", "Side", "Stop Type"]])
      Fill Index  Side Stop Type
    0 2026-09-01   Buy      None
    1 2026-09-04  Sell        TD
    2 2026-09-05   Buy      None
    ```

    1.  The entry timestamp remains September 1, so the three-day stop exits on September 4. The next
        entry on September 5 starts a new holding period.

!!! info "Tutorial"
    Learn more in the [Live simulation](https://members.vectorbt.pro/tutorials/from-python-to-rust/live/) tutorial.

## Chaining simulations \[#chaining-simulations]

New in v2026.3.1

✅ 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.

=== "Example 1: Monthly"
    ```python title="Simulate a SMA crossover strategy on a monthly basis"
    >>> data = vbt.BinanceData.pull(  # (1)
    ...     "BTCUSDT",
    ...     start="one year ago",
    ...     timeframe="5 minutes",
    ...     cache=True
    ... )

    >>> fast_sma = data.run("talib_func:sma", timeperiod=20)  # (2)
    >>> 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(  # (3)
    ...     data,
    ...     long_entries=long_entries,
    ...     short_entries=short_entries,
    ... )

    >>> data_splits = data.split(by="month")  # (4)
    >>> 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)):  # (5)
    ...     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)  # (6)
    >>> print(stacked_pf.returns.equals(single_pf.returns))
    True
    ```

    1.  Pull 5-minute BTC data for the past year from Binance and cache it for faster access.
    2.  Generate long and short entry signals for full dataset based on a simple SMA crossover
        strategy.
    3.  Run a single simulation on the full dataset as a reference.
    4.  Split the data and signals by month to run simulations on a monthly basis.
    5.  Iterate through each month, running a simulation with the corresponding data and signals, and
        passing the last state from the previous simulation to the next one.
    6.  Row-stack the resulting portfolio objects together. The result is identical to the single
        simulation on the full dataset.

=== "Example 2: Live data stream"
    ```python title="Simulate a SMA crossover strategy on a live data stream"
    >>> def generate_signals(data):
    ...     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)
    ...     return long_entries, short_entries

    >>> data = vbt.BinanceData.pull(  # (1)
    ...     "BTCUSDT",
    ...     start="1 day ago",
    ...     end="1 minute ago",
    ...     timeframe="1 minute"
    ... )
    >>> long_entries, short_entries = generate_signals(data)
    >>> pf = vbt.PF.from_signals(  # (2)
    ...     data,
    ...     long_entries=long_entries,
    ...     short_entries=short_entries,
    ...     attach_preparer=True
    ... )

    >>> with vbt.ProgressBar() as pbar:
    ...     while True:
    ...         try:
    ...             vbt.wait("1 minute", floor=True)  # (3)
    ...         except KeyboardInterrupt:
    ...             break
    ...
    ...         n_bars = len(data.index)
    ...         data = data.update()  # (4)
    ...         if len(data.index) == n_bars:
    ...             pbar.set_postfix(str(data.index[-1]))
    ...             pbar.update()
    ...             continue
    ...
    ...         long_entries, short_entries = generate_signals(data)
    ...         new_pf = pf.update(  # (5)
    ...             data.iloc[n_bars:],
    ...             long_entries=long_entries.iloc[n_bars:],
    ...             short_entries=short_entries.iloc[n_bars:],
    ...             stack=False
    ...         )
    ...         new_orders = new_pf.orders.readable
    ...         if len(new_orders) > 0:  # (6)
    ...             for i in range(len(new_orders)):
    ...                 print("NEW ORDER:")
    ...                 print(new_orders.iloc[i])
    ...                 print()
    ...
    ...         pf = vbt.PF.row_stack(  # (7)
    ...             (pf, new_pf),
    ...             chained=True,
    ...             preparer=new_pf.preparer
    ...         )
    ...         pbar.set_postfix(str(data.index[-1]))
    ...         pbar.update()
    ```

    1.  Pull 1-minute BTC data for the past day from Binance. Don't include the most recent minute to
        avoid incomplete data.
    2.  Run an initial simulation on the full dataset and attach the preparer to access the original
        simulation arguments later.
    3.  Wait for the next minute to start, ensuring that we have new data to work with. Flooring the
        wait time ensures that we align with the start of the minute.
    4.  Update the data to get the latest bars. If no new bars are available, skip the rest of the
        loop.
    5.  Update the portfolio with the new data and signals, without stacking it with the previous
        portfolio to keep it separate.
    6.  Check for new orders generated by the update and print them out.
    7.  Row-stack the new portfolio with the previous one, enabling seamless continuation while keeping
        the history of the simulation intact.


## Related pages

*   [Backtesting engine](/features/backtesting/backtesting-engine/): Simulate orders, signals, and callbacks across many assets and parameters at once
*   [Event-driven backtesting](/features/backtesting/event-driven-backtesting/): Write compiled callbacks for cooldowns, position limits, and custom simulators
*   [Orders and execution](/features/backtesting/orders-and-execution/): Simulate limit and stop-entry orders, fill prices, timing, rejections, and real fills
*   [Signal backtesting](/features/backtesting/signal-backtesting/): Turn entry and exit signals into long, short, reversing, and pyramided positions