diff --git a/pytr/event.py b/pytr/event.py index 37a032e..6a018de 100644 --- a/pytr/event.py +++ b/pytr/event.py @@ -155,6 +155,7 @@ class PPEventType(EventType): # Swap "Aufruf von Zwischenpapieren": PPEventType.SWAP, "Reverse Split": PPEventType.SWAP, + "Tausch": PPEventType.SWAP, "Teilrückzahlung ohne Reduzierung des Poolfaktors": PPEventType.SWAP, "Zusammenschluss": PPEventType.SWAP, # Taxes @@ -313,6 +314,14 @@ class PPEventType(EventType): "Worldline": "FR0011981968", } +# Known ISIN renames/swaps: old ISIN -> new ISIN. +# Used to resolve paired TR swap events (one event per ISIN) into a single SWAP transaction. +_swap_old_to_new_isin: dict[str, str] = { + "DE0005785604": "DE000FRE5EN2", # Fresenius SE ISIN change 2026-08 + "IE000BI8OT95": "LU1781541179", # MSCI World USD (Acc) fund change + "US4385161066": "US4385162056", # Honeywell International reverse split 2026 +} + logger = None @@ -619,6 +628,7 @@ def from_dict(cls, event_dict: Dict[Any, Any]): "Aktiensplit", "Bonusaktien", "Reverse Split", + "Tausch", "Teilrückzahlung ohne Reduzierung des Poolfaktors", "Zusammenschluss", ] @@ -682,11 +692,12 @@ def from_dict(cls, event_dict: Dict[Any, Any]): isin2 = isin isin = _note_to_isin.get(note, isin2) if note else isin2 elif event_type == PPEventType.SWAP: - if note == "Honeywell International": - isin2 = isin - isin = "US4385161066" - elif note == "MSCI World USD (Acc)" and isin == "LU1781541179": - isin2 = "IE000BI8OT95" + if isin in _swap_old_to_new_isin: + isin2 = _swap_old_to_new_isin[isin] + elif isin in _swap_old_to_new_isin.values(): + # Duplicate event for the new ISIN side — suppress it; the old-ISIN event is canonical + event_type = None + ignoreEvent = True else: isin2 = (_swap_note_to_isin_overrides.get(note) or _note_to_isin.get(note, note)) if note else None @@ -750,19 +761,23 @@ def from_dict(cls, event_dict: Dict[Any, Any]): elif isin == "LU3170240538": note = "Apollo" - if event_type is PPEventType.SWAP and uebersicht_dict: - foundentfernt = False - for item in uebersicht_dict.get("data", []): - ititle = item.get("title") - if ititle == "Aktien entfernt": - foundentfernt = True - if not foundentfernt: - event_type = ConditionalEventType.TRADE_INVOICE - isin2 = None - if title == "WORLDLINE S.A. ANR": - title = "Worldline" - isin = "FR0011981968" - note = None + if event_type is PPEventType.SWAP: + if not uebersicht_dict: + event_type = None + ignoreEvent = True + else: + foundentfernt = False + for item in uebersicht_dict.get("data", []): + ititle = item.get("title") + if ititle == "Aktien entfernt": + foundentfernt = True + if not foundentfernt: + event_type = ConditionalEventType.TRADE_INVOICE + isin2 = None + if title == "WORLDLINE S.A. ANR": + title = "Worldline" + isin = "FR0011981968" + note = None if ( subtitle == "Zusammenschluss" diff --git a/tests/events/reverse_split2.json b/tests/events/reverse_split_new_isin.json similarity index 100% rename from tests/events/reverse_split2.json rename to tests/events/reverse_split_new_isin.json diff --git a/tests/events/reverse_split3.json b/tests/events/reverse_split_old_isin.json similarity index 100% rename from tests/events/reverse_split3.json rename to tests/events/reverse_split_old_isin.json diff --git a/tests/events/tausch_informative.json b/tests/events/tausch_informative.json new file mode 100644 index 0000000..67120f1 --- /dev/null +++ b/tests/events/tausch_informative.json @@ -0,0 +1,48 @@ +{ + "id": "bbf2affb-30ab-45bd-a7d6-d3047360e905", + "timestamp": "2025-11-04T08:59:35.225+0000", + "title": "L'Oreal", + "icon": "logos/FR0000120321/v2", + "subtitle": "Tausch", + "action": { + "type": "timelineDetail", + "payload": "bbf2affb-30ab-45bd-a7d6-d3047360e905" + }, + "source": "timelineActivity", + "details": { + "id": "bbf2affb-30ab-45bd-a7d6-d3047360e905", + "sections": [ + { + "title": "Du hast eine Kapitalmaßnahme erhalten", + "data": { + "icon": "logos/FR0000120321/v2", + "timestamp": "2025-11-04T08:59:35.225Z", + "status": "executed" + }, + "type": "header" + }, + { + "title": "Information", + "text": "Das beigefügte Dokument enthält wichtige Informationen zu deiner L'Oreal-Investition. Bitte überprüfe das Dokument.", + "type": "text" + }, + { + "title": "Dokumente", + "data": [ + { + "title": "Kapitalmaßnahmen", + "detail": "04.11.2025", + "action": { + "payload": "", + "type": "browserModal" + }, + "id": "e68a0462-3569-4eea-a33c-f5b10154986a", + "postboxType": "CA_GENERIC_OPTIONS_NOTIFICATION" + } + ], + "type": "documents" + } + ] + }, + "eventType": "SSP_CORPORATE_ACTION_INFORMATIVE" +} diff --git a/tests/events/tausch_no_eventType.json b/tests/events/tausch_informative_no_eventType.json similarity index 100% rename from tests/events/tausch_no_eventType.json rename to tests/events/tausch_informative_no_eventType.json diff --git a/tests/events/tausch_new_isin.json b/tests/events/tausch_new_isin.json new file mode 100644 index 0000000..d40147c --- /dev/null +++ b/tests/events/tausch_new_isin.json @@ -0,0 +1,124 @@ +{ + "id": "cb25d019-05f3-3a6c-8785-e410b70df9cd", + "timestamp": "2026-08-13T15:22:52.025+0000", + "title": "Fresenius SE & Co. KGaA Namens-Aktien o.N.", + "icon": "logos/DE000FRE5EN2/v2", + "subtitle": "Tausch", + "action": { + "type": "timelineDetail", + "payload": "cb25d019-05f3-3a6c-8785-e410b70df9cd" + }, + "trailing": null, + "eventType": "SSP_CORPORATE_ACTION_NO_CASH", + "hidden": false, + "deleted": false, + "source": "timelineActivity", + "details": { + "id": "cb25d019-05f3-3a6c-8785-e410b70df9cd", + "sections": [ + { + "title": "Deine Aktien waren von einer Kapitalmaßnahme betroffen", + "data": { + "icon": { + "asset": "logos/DE000FRE5EN2/v2", + "badge": null + }, + "subtitleText": "13 Aug. · 15:22", + "status": "executed" + }, + "type": "header" + }, + { + "title": "Übersicht", + "data": [ + { + "title": "Status", + "detail": { + "text": "Ausgeführt", + "functionalStyle": "EXECUTED", + "type": "status" + }, + "style": "plain" + }, + { + "title": "Orderart", + "detail": { + "text": "Tausch", + "displayValue": { + "text": "Tausch", + "textDataSensitivity": "PUBLIC" + }, + "type": "text" + }, + "style": "plain" + }, + { + "title": "Wertpapier", + "detail": { + "text": "Fresenius", + "displayValue": { + "text": "Fresenius", + "textDataSensitivity": "PUBLIC" + }, + "type": "text" + }, + "style": "plain" + }, + { + "title": "Aktien entfernt", + "detail": { + "text": "5.431472", + "displayValue": { + "text": "5.431472", + "textDataSensitivity": "PUBLIC" + }, + "type": "text" + }, + "style": "plain" + }, + { + "title": "Wertpapier", + "detail": { + "text": "Fresenius SE & Co. KGaA Namens-Aktien o.N.", + "displayValue": { + "text": "Fresenius SE & Co. KGaA Namens-Aktien o.N.", + "textDataSensitivity": "PUBLIC" + }, + "type": "text" + }, + "style": "plain" + }, + { + "title": "Aktien hinzugefügt", + "detail": { + "text": "5.431472", + "displayValue": { + "text": "5.431472", + "textDataSensitivity": "PUBLIC" + }, + "type": "text" + }, + "style": "plain" + } + ], + "type": "table" + }, + { + "title": "Dokumente", + "data": [ + { + "title": "Dokumente", + "detail": "13.08.2026", + "action": { + "payload": "https://example.com/document.pdf", + "type": "browserModal" + }, + "id": "5310c3d8-e7b5-4ad7-97fb-a48b39f5f579", + "postboxType": "CA_SECURITIES_INVOICE" + } + ], + "type": "documents" + } + ] + } +} diff --git a/tests/events/tausch_old_isin.json b/tests/events/tausch_old_isin.json new file mode 100644 index 0000000..e8023fe --- /dev/null +++ b/tests/events/tausch_old_isin.json @@ -0,0 +1,124 @@ +{ + "id": "d569e3ae-4a62-3bda-9798-4b21ff0a5820", + "timestamp": "2026-08-13T15:22:52.025+0000", + "title": "Fresenius", + "icon": "logos/DE0005785604/v2", + "subtitle": "Tausch", + "action": { + "type": "timelineDetail", + "payload": "d569e3ae-4a62-3bda-9798-4b21ff0a5820" + }, + "trailing": null, + "eventType": "SSP_CORPORATE_ACTION_NO_CASH", + "hidden": false, + "deleted": false, + "source": "timelineActivity", + "details": { + "id": "d569e3ae-4a62-3bda-9798-4b21ff0a5820", + "sections": [ + { + "title": "Deine Aktien waren von einer Kapitalmaßnahme betroffen", + "data": { + "icon": { + "asset": "logos/DE0005785604/v2", + "badge": null + }, + "subtitleText": "13 Aug. · 15:22", + "status": "executed" + }, + "type": "header" + }, + { + "title": "Übersicht", + "data": [ + { + "title": "Status", + "detail": { + "text": "Ausgeführt", + "functionalStyle": "EXECUTED", + "type": "status" + }, + "style": "plain" + }, + { + "title": "Orderart", + "detail": { + "text": "Tausch", + "displayValue": { + "text": "Tausch", + "textDataSensitivity": "PUBLIC" + }, + "type": "text" + }, + "style": "plain" + }, + { + "title": "Wertpapier", + "detail": { + "text": "Fresenius", + "displayValue": { + "text": "Fresenius", + "textDataSensitivity": "PUBLIC" + }, + "type": "text" + }, + "style": "plain" + }, + { + "title": "Aktien entfernt", + "detail": { + "text": "5.431472", + "displayValue": { + "text": "5.431472", + "textDataSensitivity": "PUBLIC" + }, + "type": "text" + }, + "style": "plain" + }, + { + "title": "Wertpapier", + "detail": { + "text": "Fresenius SE & Co. KGaA Namens-Aktien o.N.", + "displayValue": { + "text": "Fresenius SE & Co. KGaA Namens-Aktien o.N.", + "textDataSensitivity": "PUBLIC" + }, + "type": "text" + }, + "style": "plain" + }, + { + "title": "Aktien hinzugefügt", + "detail": { + "text": "5.431472", + "displayValue": { + "text": "5.431472", + "textDataSensitivity": "PUBLIC" + }, + "type": "text" + }, + "style": "plain" + } + ], + "type": "table" + }, + { + "title": "Dokumente", + "data": [ + { + "title": "Dokumente", + "detail": "13.08.2026", + "action": { + "payload": "https://example.com/document.pdf", + "type": "browserModal" + }, + "id": "22b5d029-0f5c-46ad-b07f-c5ad0ae11ee2", + "postboxType": "CA_SECURITIES_INVOICE" + } + ], + "type": "documents" + } + ] + } +} diff --git a/tests/test_events.py b/tests/test_events.py index 0953c79..11bc668 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -1626,7 +1626,7 @@ ], }, { - "filename": "reverse_split2.json", + "filename": "reverse_split_old_isin.json", "event_type": PPEventType.SWAP, "title": "Honeywell International", "isin": "US4385161066", @@ -1649,28 +1649,36 @@ ], }, { - "filename": "reverse_split3.json", + "filename": "reverse_split_new_isin.json", + "event_type": None, # new-ISIN side of a paired swap — suppressed in favour of reverse_split_old_isin.json + }, + { + "filename": "tausch_old_isin.json", "event_type": PPEventType.SWAP, - "title": "Honeywell International", - "isin": "US4385161066", - "isin2": "US4385161066", - "shares": 0.966682, - "shares2": 0.483341, + "title": "Fresenius", + "isin": "DE0005785604", + "isin2": "DE000FRE5EN2", + "shares": 5.431472, + "shares2": 5.431472, "value": 0, - "note": "Honeywell International", + "note": "Fresenius SE & Co. KGaA Namens-Aktien o.N.", "transactions": [ { - "Datum": "2026-06-30T16:46:52", + "Datum": "2026-08-13T15:22:52", "Typ": "Swap", "Wert": 0, - "Notiz": "Honeywell International", - "ISIN": "US4385161066", - "Stück": 0.966682, - "ISIN2": "US4385161066", - "Stück2": 0.483341, + "Notiz": "Fresenius", + "ISIN": "DE0005785604", + "Stück": 5.431472, + "ISIN2": "DE000FRE5EN2", + "Stück2": 5.431472, }, ], }, + { + "filename": "tausch_new_isin.json", + "event_type": None, # new-ISIN side of a paired swap — suppressed in favour of tausch_old_isin.json + }, { "filename": "saveback.json", "event_type": ConditionalEventType.SAVEBACK, @@ -1924,8 +1932,12 @@ ], }, { - "filename": "tausch_no_eventType.json", - "title": "L'Oreal", + "filename": "tausch_informative_no_eventType.json", + "event_type": None, + }, + { + "filename": "tausch_informative.json", + "event_type": None, }, { "filename": "teilrueckzahlung.json",