Positions
How QuantJourney records realized share or unit exposure in weight mode and order mode.
Positions are realized units held through time. In weight mode they are derived from actual weights, NAV and prices. In order mode they are updated from fills.
backtester/core.py + backtester/portfolio/portf_data.pyportfolio_data.positions DataFrameportfolio_data.positionsEngine Contract
Signals answer "what do I like?" Weights answer "how much exposure do I want?" Positions answer "how many units does the portfolio hold after the engine has applied timing, costs, rebalancing or fills?"
Data Contract
| Contract item | Meaning |
|---|---|
| Shape | dates x instruments DataFrame |
| Unit | shares, contracts or instrument units depending on instrument model |
| Sign | positive long, negative short, zero flat |
| Weight mode source | actual_weights * NAV / price |
| Order mode source | accumulated buy/sell fills |
| Storage | portfolio_data.positions |
Level 1: Inspect Positions After A Run
await strategy.run_strategy()
positions = strategy.portfolio_data.positions
latest_positions = positions.iloc[-1]
print(latest_positions[latest_positions != 0])Level 2: Compare Weights And Positions
Positions are not always intuitive when NAV changes. Inspect values and weights together.
close = strategy.instruments_data.get_feature("adj_close")
nav = strategy.portfolio_data.net_asset_value
positions = strategy.portfolio_data.positions
weights = strategy.portfolio_data.weights
date = positions.index[-1]
audit = pd.DataFrame({
"price": close.loc[date],
"units": positions.loc[date],
"market_value": positions.loc[date] * close.loc[date],
"weight": weights.loc[date],
})
print(audit.sort_values("weight", ascending=False))Level 3: Order Mode State In A Strategy
In order mode, current_positions is passed into
_compute_orders(...). Use it to prevent duplicate entries and to size exits correctly.
class PositionAwareOrders(Backtester):
def _compute_orders(self, date, bars, current_positions, nav):
inst = "AAPL"
bar = bars[inst]
pos = current_positions.get(inst, 0.0)
if pos == 0 and self._entry_signal(date, inst):
qty = int(nav * 0.15 / bar.close)
self.fill_engine.submit(Order(
instrument=inst,
side=OrderSide.BUY,
quantity=qty,
order_type=OrderType.MARKET,
))
elif pos > 0 and self._exit_signal(date, inst):
self.fill_engine.cancel_all(instrument=inst)
self.fill_engine.submit(Order(
instrument=inst,
side=OrderSide.SELL,
quantity=pos,
order_type=OrderType.MARKET,
))Weight Mode Positions
In weight mode positions are a derived accounting frame:
positions = actual_weights * nav / closeThis means weight mode does not model order queues, pending orders or partial fills. It records the share/unit exposure required to represent the realized weight path.
Order Mode Positions
In order mode positions change only when fills occur. A pending stop or limit does not change the position until the fill engine emits a fill and the portfolio accounting path applies it.
Failure Modes
- Using raw target weights when you meant realized positions.
- Assuming a pending order has already changed the position.
- Forgetting that a signal exit and a stop-loss exit are different paths.
- Interpreting position units without checking instrument contract specs.
- Comparing position counts across assets without converting to market value.
Audit Checklist
- In weight mode, compare
weights,positions,NAVandclose. - In order mode, compare fills to changes in
current_positions. - Check whether the strategy is flat because no orders filled or because no entry was submitted.
- Inspect stale pending orders when positions do not change as expected.