fix: preserve FIFO cost basis through depot transfers#59
Conversation
… TRANSFER_IN) When securities are transferred between brokers using Portfolio Performance's Wertpapierübertrag feature, PP records a TRANSFER_OUT from the source depot and a TRANSFER_IN to the destination depot. The FIFO cost basis was silently lost in this case because: 1. _get_remaining_lots_after_fifo_matching only processed SELL and DELIVERY_OUTBOUND as outgoing transactions — TRANSFER_OUT was ignored, leaving BUY lots stranded in the source account. 2. prepare_share_sell_df filtered transactions by account_id, so only the destination account's transactions were visible to enrich_fifo_lots. The original BUY lots in the source account were never seen. Fix: process TRANSFER_OUT → TRANSFER_IN pairs in the FIFO matching loop by moving lots from the source account to the destination account while preserving the original acquisition cost (purchasePrice and fees). This matches the German tax treatment under § 43a EStG, where cost basis carries over unchanged on transfers between an investor's own accounts. Also change prepare_share_sell_df to pass all transactions for a security across all accounts (filter_by_security instead of filter_by_account_and_security), deduplicated per security via seen_security_ids, so that the FIFO algorithm can see the full transfer chain.
Exposes securities account transactions via: - CLI: `pp-terminal view transactions [SECURITY] [--account-id] [--from] [--to] [--type]` - MCP: `query_transactions(security, account_id, from_date, to_date, transaction_type)` The core `prepare_transactions_df()` function applies filters for security (by ISIN or UUID), account, date range, and transaction type, then enriches each row with the resolved security name. Covered by 14 unit tests and one integration test with a golden CSV.
Extract _build_transfer_in_lookup() and _consume_lots_fifo() from _get_remaining_lots_after_fifo_matching() to bring cognitive complexity below the allowed threshold of 15.
…arning Extract shared ISIN/UUID resolution logic into resolve_security_id() in portfolio.py. CLI uses it via InputError→BadParameter conversion, MCP uses the existing _resolve_security() which also handles WKN. Fixes duplicate-code warning from static analysis. Also fix mypy type annotations in cost_basis.py (Any instead of object for lot dicts, tuple[object, ...] for transfer lookup keys).
|
|
@vfpdev thanks for your contribution, very much appreciated 🙏 The Some issues I discovered during review, could you have a look at it please? Perhaps it could make sense to split the TRANSFER fix and the Blocking
Worth a look
Nice work on splitting Happy to discuss any of the above! (Hand-reviewed, Claude linted my brain.) |
|
As suggested I created the follow-up PR #73 for the depot transfer issue only ✔️ |



Problem
When securities are transferred between brokers using Portfolio Performance's Wertpapierübertrag feature, the FIFO cost basis was silently lost. Affected positions showed up with empty lots and no tax calculation.
Root cause was two separate issues:
_get_remaining_lots_after_fifo_matchingonly processedSELLandDELIVERY_OUTBOUNDas outgoing transactions.TRANSFER_OUTwas ignored, leaving the original BUY lots stranded in the source account.prepare_share_sell_dffiltered transactions byaccount_id, so only the destination account's transactions were passed toenrich_fifo_lots. The original BUY lots in the source account were never visible.Fix
TRANSFER_OUT→TRANSFER_INpairs in the FIFO matching loop by moving lots from the source account to the destination account, preserving the originalpurchasePriceandfees.prepare_share_sell_dfto pass all transactions for a security across all accounts (filter_by_securityinstead offilter_by_account_and_security), deduplicated per security viaseen_security_ids.Tested with
A portfolio where Hugo Boss, PayPal, EasyJet, and Parrot were purchased at one broker and transferred to another via Wertpapierübertrag. Before the fix,
simulate share-sellshowed empty results for all four. After the fix, the original acquisition costs are correctly carried over.