Skip to content

Commit 1fd5c3c

Browse files
authored
Better implementation of SWAP transactions (#394)
1 parent d5c0389 commit 1fd5c3c

8 files changed

Lines changed: 357 additions & 34 deletions

pytr/event.py

Lines changed: 33 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,7 @@ class PPEventType(EventType):
155155
# Swap
156156
"Aufruf von Zwischenpapieren": PPEventType.SWAP,
157157
"Reverse Split": PPEventType.SWAP,
158+
"Tausch": PPEventType.SWAP,
158159
"Teilrückzahlung ohne Reduzierung des Poolfaktors": PPEventType.SWAP,
159160
"Zusammenschluss": PPEventType.SWAP,
160161
# Taxes
@@ -313,6 +314,14 @@ class PPEventType(EventType):
313314
"Worldline": "FR0011981968",
314315
}
315316

317+
# Known ISIN renames/swaps: old ISIN -> new ISIN.
318+
# Used to resolve paired TR swap events (one event per ISIN) into a single SWAP transaction.
319+
_swap_old_to_new_isin: dict[str, str] = {
320+
"DE0005785604": "DE000FRE5EN2", # Fresenius SE ISIN change 2026-08
321+
"IE000BI8OT95": "LU1781541179", # MSCI World USD (Acc) fund change
322+
"US4385161066": "US4385162056", # Honeywell International reverse split 2026
323+
}
324+
316325
logger = None
317326

318327

@@ -619,6 +628,7 @@ def from_dict(cls, event_dict: Dict[Any, Any]):
619628
"Aktiensplit",
620629
"Bonusaktien",
621630
"Reverse Split",
631+
"Tausch",
622632
"Teilrückzahlung ohne Reduzierung des Poolfaktors",
623633
"Zusammenschluss",
624634
]
@@ -682,11 +692,12 @@ def from_dict(cls, event_dict: Dict[Any, Any]):
682692
isin2 = isin
683693
isin = _note_to_isin.get(note, isin2) if note else isin2
684694
elif event_type == PPEventType.SWAP:
685-
if note == "Honeywell International":
686-
isin2 = isin
687-
isin = "US4385161066"
688-
elif note == "MSCI World USD (Acc)" and isin == "LU1781541179":
689-
isin2 = "IE000BI8OT95"
695+
if isin in _swap_old_to_new_isin:
696+
isin2 = _swap_old_to_new_isin[isin]
697+
elif isin in _swap_old_to_new_isin.values():
698+
# Duplicate event for the new ISIN side — suppress it; the old-ISIN event is canonical
699+
event_type = None
700+
ignoreEvent = True
690701
else:
691702
isin2 = (_swap_note_to_isin_overrides.get(note) or _note_to_isin.get(note, note)) if note else None
692703

@@ -750,19 +761,23 @@ def from_dict(cls, event_dict: Dict[Any, Any]):
750761
elif isin == "LU3170240538":
751762
note = "Apollo"
752763

753-
if event_type is PPEventType.SWAP and uebersicht_dict:
754-
foundentfernt = False
755-
for item in uebersicht_dict.get("data", []):
756-
ititle = item.get("title")
757-
if ititle == "Aktien entfernt":
758-
foundentfernt = True
759-
if not foundentfernt:
760-
event_type = ConditionalEventType.TRADE_INVOICE
761-
isin2 = None
762-
if title == "WORLDLINE S.A. ANR":
763-
title = "Worldline"
764-
isin = "FR0011981968"
765-
note = None
764+
if event_type is PPEventType.SWAP:
765+
if not uebersicht_dict:
766+
event_type = None
767+
ignoreEvent = True
768+
else:
769+
foundentfernt = False
770+
for item in uebersicht_dict.get("data", []):
771+
ititle = item.get("title")
772+
if ititle == "Aktien entfernt":
773+
foundentfernt = True
774+
if not foundentfernt:
775+
event_type = ConditionalEventType.TRADE_INVOICE
776+
isin2 = None
777+
if title == "WORLDLINE S.A. ANR":
778+
title = "Worldline"
779+
isin = "FR0011981968"
780+
note = None
766781

767782
if (
768783
subtitle == "Zusammenschluss"
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
{
2+
"id": "bbf2affb-30ab-45bd-a7d6-d3047360e905",
3+
"timestamp": "2025-11-04T08:59:35.225+0000",
4+
"title": "L'Oreal",
5+
"icon": "logos/FR0000120321/v2",
6+
"subtitle": "Tausch",
7+
"action": {
8+
"type": "timelineDetail",
9+
"payload": "bbf2affb-30ab-45bd-a7d6-d3047360e905"
10+
},
11+
"source": "timelineActivity",
12+
"details": {
13+
"id": "bbf2affb-30ab-45bd-a7d6-d3047360e905",
14+
"sections": [
15+
{
16+
"title": "Du hast eine Kapitalmaßnahme erhalten",
17+
"data": {
18+
"icon": "logos/FR0000120321/v2",
19+
"timestamp": "2025-11-04T08:59:35.225Z",
20+
"status": "executed"
21+
},
22+
"type": "header"
23+
},
24+
{
25+
"title": "Information",
26+
"text": "Das beigefügte Dokument enthält wichtige Informationen zu deiner L'Oreal-Investition. Bitte überprüfe das Dokument.",
27+
"type": "text"
28+
},
29+
{
30+
"title": "Dokumente",
31+
"data": [
32+
{
33+
"title": "Kapitalmaßnahmen",
34+
"detail": "04.11.2025",
35+
"action": {
36+
"payload": "",
37+
"type": "browserModal"
38+
},
39+
"id": "e68a0462-3569-4eea-a33c-f5b10154986a",
40+
"postboxType": "CA_GENERIC_OPTIONS_NOTIFICATION"
41+
}
42+
],
43+
"type": "documents"
44+
}
45+
]
46+
},
47+
"eventType": "SSP_CORPORATE_ACTION_INFORMATIVE"
48+
}
File renamed without changes.

tests/events/tausch_new_isin.json

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
{
2+
"id": "cb25d019-05f3-3a6c-8785-e410b70df9cd",
3+
"timestamp": "2026-08-13T15:22:52.025+0000",
4+
"title": "Fresenius SE & Co. KGaA Namens-Aktien o.N.",
5+
"icon": "logos/DE000FRE5EN2/v2",
6+
"subtitle": "Tausch",
7+
"action": {
8+
"type": "timelineDetail",
9+
"payload": "cb25d019-05f3-3a6c-8785-e410b70df9cd"
10+
},
11+
"trailing": null,
12+
"eventType": "SSP_CORPORATE_ACTION_NO_CASH",
13+
"hidden": false,
14+
"deleted": false,
15+
"source": "timelineActivity",
16+
"details": {
17+
"id": "cb25d019-05f3-3a6c-8785-e410b70df9cd",
18+
"sections": [
19+
{
20+
"title": "Deine Aktien waren von einer Kapitalmaßnahme betroffen",
21+
"data": {
22+
"icon": {
23+
"asset": "logos/DE000FRE5EN2/v2",
24+
"badge": null
25+
},
26+
"subtitleText": "13 Aug. · 15:22",
27+
"status": "executed"
28+
},
29+
"type": "header"
30+
},
31+
{
32+
"title": "Übersicht",
33+
"data": [
34+
{
35+
"title": "Status",
36+
"detail": {
37+
"text": "Ausgeführt",
38+
"functionalStyle": "EXECUTED",
39+
"type": "status"
40+
},
41+
"style": "plain"
42+
},
43+
{
44+
"title": "Orderart",
45+
"detail": {
46+
"text": "Tausch",
47+
"displayValue": {
48+
"text": "Tausch",
49+
"textDataSensitivity": "PUBLIC"
50+
},
51+
"type": "text"
52+
},
53+
"style": "plain"
54+
},
55+
{
56+
"title": "Wertpapier",
57+
"detail": {
58+
"text": "Fresenius",
59+
"displayValue": {
60+
"text": "Fresenius",
61+
"textDataSensitivity": "PUBLIC"
62+
},
63+
"type": "text"
64+
},
65+
"style": "plain"
66+
},
67+
{
68+
"title": "Aktien entfernt",
69+
"detail": {
70+
"text": "5.431472",
71+
"displayValue": {
72+
"text": "5.431472",
73+
"textDataSensitivity": "PUBLIC"
74+
},
75+
"type": "text"
76+
},
77+
"style": "plain"
78+
},
79+
{
80+
"title": "Wertpapier",
81+
"detail": {
82+
"text": "Fresenius SE & Co. KGaA Namens-Aktien o.N.",
83+
"displayValue": {
84+
"text": "Fresenius SE & Co. KGaA Namens-Aktien o.N.",
85+
"textDataSensitivity": "PUBLIC"
86+
},
87+
"type": "text"
88+
},
89+
"style": "plain"
90+
},
91+
{
92+
"title": "Aktien hinzugefügt",
93+
"detail": {
94+
"text": "5.431472",
95+
"displayValue": {
96+
"text": "5.431472",
97+
"textDataSensitivity": "PUBLIC"
98+
},
99+
"type": "text"
100+
},
101+
"style": "plain"
102+
}
103+
],
104+
"type": "table"
105+
},
106+
{
107+
"title": "Dokumente",
108+
"data": [
109+
{
110+
"title": "Dokumente",
111+
"detail": "13.08.2026",
112+
"action": {
113+
"payload": "https://example.com/document.pdf",
114+
"type": "browserModal"
115+
},
116+
"id": "5310c3d8-e7b5-4ad7-97fb-a48b39f5f579",
117+
"postboxType": "CA_SECURITIES_INVOICE"
118+
}
119+
],
120+
"type": "documents"
121+
}
122+
]
123+
}
124+
}

tests/events/tausch_old_isin.json

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
{
2+
"id": "d569e3ae-4a62-3bda-9798-4b21ff0a5820",
3+
"timestamp": "2026-08-13T15:22:52.025+0000",
4+
"title": "Fresenius",
5+
"icon": "logos/DE0005785604/v2",
6+
"subtitle": "Tausch",
7+
"action": {
8+
"type": "timelineDetail",
9+
"payload": "d569e3ae-4a62-3bda-9798-4b21ff0a5820"
10+
},
11+
"trailing": null,
12+
"eventType": "SSP_CORPORATE_ACTION_NO_CASH",
13+
"hidden": false,
14+
"deleted": false,
15+
"source": "timelineActivity",
16+
"details": {
17+
"id": "d569e3ae-4a62-3bda-9798-4b21ff0a5820",
18+
"sections": [
19+
{
20+
"title": "Deine Aktien waren von einer Kapitalmaßnahme betroffen",
21+
"data": {
22+
"icon": {
23+
"asset": "logos/DE0005785604/v2",
24+
"badge": null
25+
},
26+
"subtitleText": "13 Aug. · 15:22",
27+
"status": "executed"
28+
},
29+
"type": "header"
30+
},
31+
{
32+
"title": "Übersicht",
33+
"data": [
34+
{
35+
"title": "Status",
36+
"detail": {
37+
"text": "Ausgeführt",
38+
"functionalStyle": "EXECUTED",
39+
"type": "status"
40+
},
41+
"style": "plain"
42+
},
43+
{
44+
"title": "Orderart",
45+
"detail": {
46+
"text": "Tausch",
47+
"displayValue": {
48+
"text": "Tausch",
49+
"textDataSensitivity": "PUBLIC"
50+
},
51+
"type": "text"
52+
},
53+
"style": "plain"
54+
},
55+
{
56+
"title": "Wertpapier",
57+
"detail": {
58+
"text": "Fresenius",
59+
"displayValue": {
60+
"text": "Fresenius",
61+
"textDataSensitivity": "PUBLIC"
62+
},
63+
"type": "text"
64+
},
65+
"style": "plain"
66+
},
67+
{
68+
"title": "Aktien entfernt",
69+
"detail": {
70+
"text": "5.431472",
71+
"displayValue": {
72+
"text": "5.431472",
73+
"textDataSensitivity": "PUBLIC"
74+
},
75+
"type": "text"
76+
},
77+
"style": "plain"
78+
},
79+
{
80+
"title": "Wertpapier",
81+
"detail": {
82+
"text": "Fresenius SE & Co. KGaA Namens-Aktien o.N.",
83+
"displayValue": {
84+
"text": "Fresenius SE & Co. KGaA Namens-Aktien o.N.",
85+
"textDataSensitivity": "PUBLIC"
86+
},
87+
"type": "text"
88+
},
89+
"style": "plain"
90+
},
91+
{
92+
"title": "Aktien hinzugefügt",
93+
"detail": {
94+
"text": "5.431472",
95+
"displayValue": {
96+
"text": "5.431472",
97+
"textDataSensitivity": "PUBLIC"
98+
},
99+
"type": "text"
100+
},
101+
"style": "plain"
102+
}
103+
],
104+
"type": "table"
105+
},
106+
{
107+
"title": "Dokumente",
108+
"data": [
109+
{
110+
"title": "Dokumente",
111+
"detail": "13.08.2026",
112+
"action": {
113+
"payload": "https://example.com/document.pdf",
114+
"type": "browserModal"
115+
},
116+
"id": "22b5d029-0f5c-46ad-b07f-c5ad0ae11ee2",
117+
"postboxType": "CA_SECURITIES_INVOICE"
118+
}
119+
],
120+
"type": "documents"
121+
}
122+
]
123+
}
124+
}

0 commit comments

Comments
 (0)