Skip to content

Restrict portfolio greeks to open positions - #4700

Open
folknor wants to merge 1 commit into
nautechsystems:developfrom
folknor:fix-greeks-stale-state
Open

Restrict portfolio greeks to open positions#4700
folknor wants to merge 1 commit into
nautechsystems:developfrom
folknor:fix-greeks-stale-state

Conversation

@folknor

@folknor folknor commented Aug 9, 2026

Copy link
Copy Markdown
Collaborator

GreeksCalculator::portfolio_greeks documents itself as aggregating open positions but queries the full position index.

A closed position can abort the whole call

portfolio_greeks binds cache.positions(...) to a local named open_positions. Cache::positions returns every position, open and closed, while Cache::positions_open sits directly beside it. A closed position has signed_qty == 0.0, so quantity * &instrument_greeks contributes exactly nothing to the total - but instrument_greeks is still called for it and its error propagates, so a position the portfolio no longer holds can fail an entire portfolio snapshot once its price data is gone or its contract has expired. The query now uses positions_open with the same filter arguments, which also stops greeks being computed for closed positions and multiplied by zero.

Error propagation for positions actually held is unchanged.

Behaviour change with side = Flat

portfolio_greeks(side = PositionSide::Flat) previously selected closed positions, computed their greeks and returned zero or an error. It now selects nothing and returns zero. An open position cannot be flat under the Position invariants, so this removes behaviour that contradicted the method's own contract. NoPositionSide, Long and Short are unaffected.

Testing

test_portfolio_greeks_ignores_closed_position_with_missing_price holds one open option position and one closed futures position whose instrument is registered without a price. The closed position is built the way the engine builds one - opened, offsetting fill applied, then cache.update_position - and the test asserts it satisfies is_closed() and has left the open index before calling. Against the unfixed query it fails with No price available for CLOSED.GLBX; with the fix it returns the open position's aggregate.

test_portfolio_greeks_preserves_open_position_aggregate_and_side_filters characterizes the success path over a long and a short position with non-unit quantities, comparing every PortfolioGreeks field against an independently computed signed_qty * greeks sum, asserting those expectations are non-zero first, then repeating for the Long and Short filters. It passes against the unfixed code by design - a closed position already contributed zero, so the open-book aggregate is unchanged - and the missing-price test is the one that fails without the fix.

`GreeksCalculator::portfolio_greeks` bound `cache.positions(...)` to a
local named `open_positions`, but that query returns every position, open
and closed. A closed position has `signed_qty == 0.0` and so contributes
exactly nothing to the aggregate, yet `instrument_greeks` was still called
for it and its error propagated - letting a position the portfolio no
longer holds fail an entire portfolio snapshot once its price data is gone
or its contract has expired.

The query now uses `positions_open` with the same filter arguments, which
also stops greeks being computed for closed positions and multiplied by
zero. Error propagation for positions actually held is unchanged.

With `side = PositionSide::Flat` the call previously selected closed
positions and returned zero or an error; it now selects nothing and
returns zero.

Coded by an LLM.
@folknor
folknor requested a review from cjdsellers August 9, 2026 10:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant