Class PortfolioBacktester
optimization package meets the backtester — feed optimizer
weights, vol-target overlays, or momentum rankings straight in.
The survivorship-aware overload additionally consumes a
PointInTimeUniverse and per-symbol cash dividends, closing the
engine-side gaps that make naive backtests survivorship-biased:
- Delistings terminate positions at
lastClose × (1 + delistingReturn)on the event bar — a bankruptcy really costs −100%, instead of a forward-filled flat line; - Mergers convert positions to cash and/or acquirer shares at the recorded deal terms;
- Index drops (membership ends, security lives on) force a sale at that bar's close — non-members are untradeable thereafter;
- Cash dividends credit
position × amounton the ex-date (shorts pay), so unadjusted price series carry the full total return and cash drag is real.
Feed this overload unadjusted prices (dividends are cash here;
adjusted prices would double-count them) aligned via
SeriesAligner.unionForwardFill; the forward-filled ghost bars of a
dead symbol are harmless because its position terminates at the event.
Signals, however, must not be computed on unadjusted prices: a 2:1 split
reads as a −50% return. The
price-pair overload hands the strategy an adjusted copy for signals
(PortfolioStrategy.init(Map, Map)) while the engine trades,
values and pays dividends on the unadjusted series. The engine cannot
remove the bias in the data: the universe and delisting returns
must come from a point-in-time dataset that includes dead tickers.
Fill timing. Target weights are decided at a bar's close and
filled according to PortfolioBacktester.Config.fillPolicy(): at the next bar's open
(default), the next bar's close, or the decision bar's own close
(CLOSE, the optimistic zero-latency mode). Next-open orders are
sized against the portfolio value marked at that open. Lifecycle events
and dividends of the fill bar are processed first; dead and non-member
names are not traded. A decision on the last bar under a next-bar policy
is dropped.
Financing (PortfolioBacktester.Config.withFinancing(PortfolioBacktester.Financing)) accrues each bar on
the book held over it: interest on positive cash (short proceeds sit in
cash, so the short rebate is implicit), a margin rate on negative cash,
and a borrow fee on short notional, per symbol or by default. Without it
shorts, cash and leverage are free and a 150/50 book shows the same
equity as an unlevered one.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic final recordcostModel, when set, supersedes the flatcommissionRatefor every trade (rebalances and forced index-drop sales alike), the sharedTradeCostModelseam that makes a run survivorship-aware and execution-aware at once.static final recordFinancing terms, all annual.static final recordParticipation cap: each symbol trades at mostmaxFractionOfAdv × ADVshares per bar, with ADV the mean volume of theadvWindowbars before the fill bar (fewer at the start of the data; none on bar 0, so nothing trades there).static final classdividendCashCreditedandlifecycleEventsAppliedare populated by the survivorship-aware overload (zero otherwise). -
Method Summary
Modifier and TypeMethodDescriptionstatic PortfolioBacktester.Resultrun(PortfolioStrategy strategy, Map<String, BarSeries> data, PortfolioBacktester.Config config) Classic run: every supplied symbol is tradeable on every bar.static PortfolioBacktester.Resultrun(PortfolioStrategy strategy, Map<String, BarSeries> data, PortfolioBacktester.Config config, PointInTimeUniverse universe, Map<String, List<CorporateActions.CorporateAction>> cashDividends) Survivorship-aware run (see the class doc for semantics).static PortfolioBacktester.Resultrun(PortfolioStrategy strategy, Map<String, BarSeries> data, Map<String, BarSeries> signalData, PortfolioBacktester.Config config, PointInTimeUniverse universe, Map<String, List<CorporateActions.CorporateAction>> cashDividends) Survivorship-aware run on a price pair:signalData(split and dividend adjusted, for example viaCorporateActions.adjust(BarSeries, List)) is what the strategy sees for its signals;data(unadjusted) is what the engine trades, values and pays dividends on.static PortfolioBacktester.Resultrun(PortfolioStrategy strategy, Map<String, BarSeries> data, Map<String, BarSeries> signalData, PortfolioBacktester.Config config, PointInTimeUniverse universe, Map<String, List<CorporateActions.CorporateAction>> cashDividends, PortfolioBacktester.Participation participation) The price-pair run with a participation cap (null= none).static PortfolioBacktester.Resultrun(PortfolioStrategy strategy, Map<String, BarSeries> data, Map<String, BarSeries> signalData, PortfolioBacktester.Config config, PointInTimeUniverse universe, Map<String, List<CorporateActions.CorporateAction>> cashDividends, PortfolioBacktester.Participation participation, Map<String, List<CorporateEvents.Event>> corporateEvents) The participation-capped run plus extendedCorporateEvents, booked on the position book on their ex-date (after the 2.1 cash dividends, before terminal events).
-
Method Details
-
run
public static PortfolioBacktester.Result run(PortfolioStrategy strategy, Map<String, BarSeries> data, PortfolioBacktester.Config config) Classic run: every supplied symbol is tradeable on every bar. -
run
public static PortfolioBacktester.Result run(PortfolioStrategy strategy, Map<String, BarSeries> data, PortfolioBacktester.Config config, PointInTimeUniverse universe, Map<String, List<CorporateActions.CorporateAction>> cashDividends) Survivorship-aware run (see the class doc for semantics).- Parameters:
universe- point-in-time membership and terminal events;nullbehaves like the classic overloadcashDividends- per-symbolCASH_DIVIDENDactions applied as cash on the ex-date (other action types are ignored here — apply splits to the price series viaCorporateActions.adjust(BarSeries, List)instead)
-
run
public static PortfolioBacktester.Result run(PortfolioStrategy strategy, Map<String, BarSeries> data, Map<String, BarSeries> signalData, PortfolioBacktester.Config config, PointInTimeUniverse universe, Map<String, List<CorporateActions.CorporateAction>> cashDividends) Survivorship-aware run on a price pair:signalData(split and dividend adjusted, for example viaCorporateActions.adjust(BarSeries, List)) is what the strategy sees for its signals;data(unadjusted) is what the engine trades, values and pays dividends on. Both maps must hold the same symbols on the same timeline. -
run
public static PortfolioBacktester.Result run(PortfolioStrategy strategy, Map<String, BarSeries> data, Map<String, BarSeries> signalData, PortfolioBacktester.Config config, PointInTimeUniverse universe, Map<String, List<CorporateActions.CorporateAction>> cashDividends, PortfolioBacktester.Participation participation) The price-pair run with a participation cap (null= none). -
run
public static PortfolioBacktester.Result run(PortfolioStrategy strategy, Map<String, BarSeries> data, Map<String, BarSeries> signalData, PortfolioBacktester.Config config, PointInTimeUniverse universe, Map<String, List<CorporateActions.CorporateAction>> cashDividends, PortfolioBacktester.Participation participation, Map<String, List<CorporateEvents.Event>> corporateEvents) The participation-capped run plus extendedCorporateEvents, booked on the position book on their ex-date (after the 2.1 cash dividends, before terminal events). Opt-in: an empty map gives the 2.1 behaviour exactly.datamust be the unadjusted series. Shares and cash move as follows, withPthe last close before the ex-date and shorts mirrored:- stock dividend: quantity times
1 + rate; - reverse split: quantity divided by
k, fractions paid in cash at the theoretical ex pricek * P; - dividend (regular or special): cash
qty * amount, counted inPortfolioBacktester.Result.dividendCashCredited(); - spin-off:
qty * ratioshares of the child, which must be a live series indata; - rights issue: rights are assumed sold at their theoretical
value, cash
qty * (P - TERP); - symbol change: the position moves to the new symbol when that
symbol is a series in
data, else it stays put; - cash-and-stock merger: terminal for the target; the holder is
assumed to elect cash on exactly
maxCashFraction(so no proration), stock on the rest, fractions paid in cash. The acquirer must be a live series.
PortfolioBacktester.Result.lifecycleEventsApplied(). - stock dividend: quantity times
-