quantlab.runs.backtest_stats.round_trips#
- quantlab.runs.backtest_stats.round_trips(fills, close, *, cash_flows=None)[source]#
Return the position round trips of a run’s fills, one per flat-to-flat holding.
A round trip of a symbol starts with the fill that opens a position from flat and ends with the fill that brings it back to flat; adding to or trimming the position does not end it, and a fill that crosses zero ends it and opens the opposite one. This is vectorbt’s position trade view (
trades_type="positions"), computed with vectorbt’s arithmetic and tolerances, so on a vectorbt run’s own fills every number equals vectorbt’s. The entry price is the size-weighted average of the fills that built the position and the exit price that of the fills that reduced it, each fill’s fee split pro rata. A position still held after the last fill is an open round trip, marked at its last valuation price, ending on the last bar without an exit fee.The PnL of a round trip is its exit value minus its entry value (the other way round for a short) minus its fees, plus the
cash_flowsit received while open, and its return is that PnL over its entry value. A cash flow is timestamped with the bar a position had to be held into to earn it (for a dividend, its ex-date bar): it belongs to the round trip of its symbol held at the end of the previous bar, that is entered before the flow’s bar and exited on it or later (or still open). A position bought on the ex-date bar does not earn it, and on a bar where one round trip ends and the next begins it belongs to the one that ends. Splits are not an input: give the fills andcloseon one adjustment basis, so a split neither ends a round trip nor turns its exit into a reversal.- Parameters:
- fillsxarray.Dataset
One fill per entry of a single dimension, with the variables
timestamp(a bar ofclose),symbol,size(signed: positive buys, negative sells),priceandfees(the fee paid, in cash). Fills of one symbol are taken in time order, and two fills of one bar in the order given. Quantities and prices must be on one adjustment basis, so a split is not a fill. A dataset without entries means no fills.- closexarray.DataArray
Valuation prices on
(timestamp, symbol): the bar axis (round trip lengths are counted in its bars) and, forward-filled, the price an open round trip is marked at. Round trips are listed in the order of itssymbolaxis, then in time order.- cash_flowsxarray.Dataset, optional
Cash a position received while open (a dividend or distribution, or a negative amount paid on a short), one per entry of a single dimension, with
timestamp(the bar the position had to be held into, see above),symbolandamount.
- Returns:
- xarray.Dataset
On a
tradedimension:symbol,direction("Long"or"Short"),status("Open"or"Closed"),entry_timestamp,exit_timestamp(the last bar for an open round trip),bars(exit bar minus entry bar, on thecloseaxis),size,entry_price,exit_price,fees,cash_flow,pnlandreturn.
- Raises:
- ValueError
If a fill’s or cash flow’s bar is not on the
closeaxis, its symbol is not on it either, or no round trip of a cash flow’s symbol was held into its bar.
Examples
>>> bars = pd.bdate_range("2024-01-01", periods=4) >>> close = xr.DataArray( ... [[10.0], [11.0], [12.0], [13.0]], dims=("timestamp", "symbol"), ... coords={"timestamp": bars, "symbol": ["AAA"]}, ... ) >>> fills = xr.Dataset({ ... "timestamp": ("fill", bars[[0, 1, 2]].values), ... "symbol": ("fill", ["AAA"] * 3), ... "size": ("fill", [10.0, -4.0, -6.0]), ... "price": ("fill", [10.0, 11.0, 12.0]), ... "fees": ("fill", [0.0, 0.0, 0.0]), ... }) >>> flows = xr.Dataset({ ... "timestamp": ("flow", bars[[1]].values), ... "symbol": ("flow", ["AAA"]), ... "amount": ("flow", [5.0]), ... }) >>> trips = round_trips(fills, close, cash_flows=flows) >>> trips["status"].values.tolist(), trips["bars"].values.tolist() (['Closed'], [2]) >>> float(trips["pnl"][0]), float(trips["return"][0]) (21.0, 0.21)