From 9c933573088423235b766683536cb138b07a86ef Mon Sep 17 00:00:00 2001 From: Alexander Ott Date: Sat, 11 Oct 2025 07:28:13 +0200 Subject: [PATCH] Modify Saxo Bank PDF Importer to support new transaction Closes #5062 --- .../datatransfer/pdf/saxobank/Buy04.txt | 49 ++++++++++++++ .../datatransfer/pdf/saxobank/Buy05.txt | 53 +++++++++++++++ .../saxobank/SaxoBankPDFExtractorTest.java | 64 +++++++++++++++++++ .../pdf/SaxoBankPDFExtractor.java | 40 ++++++++++-- 4 files changed, 200 insertions(+), 6 deletions(-) create mode 100644 name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/saxobank/Buy04.txt create mode 100644 name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/saxobank/Buy05.txt diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/saxobank/Buy04.txt b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/saxobank/Buy04.txt new file mode 100644 index 0000000000..0c881610c8 --- /dev/null +++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/saxobank/Buy04.txt @@ -0,0 +1,49 @@ +PDFBox Version: 3.0.5 +Portfolio Performance Version: 0.80.3 +System: win32 | x86_64 | 21.0.5+11-LTS | Azul Systems, Inc. +----------------------------------------- +CH-00000000 - 00000000 +Trade Details Report +Reporting period: 05-Aug-2025 - 05-Aug-2025 +Generated at: 02-Okt-2025 10:37:44 10:37:44 (W. Europe Standard Time) +CH-00000000 Saxo Bank CH +Switzerland The Circle 38 +8058 Zürich-Flughafen +Switzerland +Saxo Bank CH / The Circle 38 / 8058 Zürich-Flughafen / Switzerland CH-00000000 Reporting period +Currency: CHF 05-Aug-2025 - 05-Aug-2025 +Page 1 of 3 Account(s): AutoInvest 00000/000000 Generated at: 02-Okt-2025 +Trade details, CHF +Reporting period +05-Aug-2025 to 05-Aug-2025 +ETF +Instrument iShares MSCI ACWI USD Acc UCITS ETF Trade time 05-Aug-2025 12:46:23 +ISIN IE00B6R52259 Value Date 07-Aug-2025 +Symbol SSAC_CHF:xswx Order ID 5311227095 +Exchange Description TRADEWEB EUROPE LIMITED (TREU) Trade ID 6358696418 +Primary Exchange SIX Swiss Exchange (ETFs) Traded Value -1.126,53 CHF +Venue Exchange Price 80,4666 CHF +Order Type Market Order Quantity 14,00 +B/S Buy Spread Costs 0,00 CHF +Open/Close OPEN Total Trading Costs -1,69 CHF +Saxo is counterparty No +Booking amount Amount Conversion cost Booked Amount +Trade Date Value Date Conversion Rate +ID CHF CHF CHF +Share Amount 46404269826 05-Aug-2025 07-Aug-2025 -1.126,53 1,000000 0,00 -1.126,53 +Swiss Stamp Duty +46406463093 05-Aug-2025 07-Aug-2025 -1,69 1,000000 0,00 -1,69 +Foreign +Saxo Bank CH / The Circle 38 / 8058 Zürich-Flughafen / Switzerland CH-00000000 Reporting period +Currency: CHF 05-Aug-2025 - 05-Aug-2025 +Page 2 of 3 Account(s): AutoInvest 00000/000000 Generated at: 02-Okt-2025 +Trade details, CHF +Reporting period +05-Aug-2025 to 05-Aug-2025 +Booking amount Amount Conversion cost Booked Amount +Trade Date Value Date Conversion Rate +ID CHF CHF CHF +Net Amount - - - - - 0,00 -1.128,22 +Saxo Bank CH / The Circle 38 / 8058 Zürich-Flughafen / Switzerland CH-00000000 Reporting period +Currency: CHF 05-Aug-2025 - 05-Aug-2025 +Page 3 of 3 Account(s): AutoInvest 00000/000000 Generated at: 02-Okt-2025 \ No newline at end of file diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/saxobank/Buy05.txt b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/saxobank/Buy05.txt new file mode 100644 index 0000000000..018356cd5d --- /dev/null +++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/saxobank/Buy05.txt @@ -0,0 +1,53 @@ +PDFBox Version: 3.0.5 +Portfolio Performance Version: 0.80.3 +System: win32 | x86_64 | 21.0.5+11-LTS | Azul Systems, Inc. +----------------------------------------- +CH-20672455 - 20672455 +Trade Details Report +Reporting period: 05-Aug-2025 - 05-Aug-2025 +Generated at: 02-Okt-2025 10:37:51 10:37:51 (W. Europe Standard Time) +CH-20672455 Saxo Bank CH +Switzerland The Circle 38 +8058 Zürich-Flughafen +Switzerland +Saxo Bank CH / The Circle 38 / 8058 Zürich-Flughafen / Switzerland CH-20672455 Reporting period +Currency: CHF 05-Aug-2025 - 05-Aug-2025 +Page 1 of 3 Account(s): AutoInvest 80000/167617 Generated at: 02-Okt-2025 +Trade details, CHF +Reporting period +05-Aug-2025 to 05-Aug-2025 +ETF +iShares Swiss Domestic Government Bnd 3-7 +Instrument Trade time 05-Aug-2025 13:02:36 +(CH) ETF +ISIN CH0016999846 Value Date 07-Aug-2025 +Symbol CSBGC7:xswx Order ID 5311259140 +Exchange Description J.P. MORGAN SE (JPEU) Trade ID 6358773884 +SIX Swiss Exchange ETFs on Bonds of the +Primary Exchange Traded Value -375,99 CHF +Swiss Conf +Venue Exchange Price 75,19880769 CHF +Order Type Market Order Quantity 5,00 +B/S Buy Spread Costs 0,00 CHF +Open/Close OPEN Total Trading Costs -0,28 CHF +Saxo is counterparty No +Booking amount Amount Conversion cost Booked Amount +Trade Date Value Date Conversion Rate +ID CHF CHF CHF +Share Amount 46403752826 05-Aug-2025 07-Aug-2025 -375,99 1,000000 0,00 -375,99 +Swiss Stamp Duty +46406605890 05-Aug-2025 07-Aug-2025 -0,28 1,000000 0,00 -0,28 +Domestic +Saxo Bank CH / The Circle 38 / 8058 Zürich-Flughafen / Switzerland CH-20672455 Reporting period +Currency: CHF 05-Aug-2025 - 05-Aug-2025 +Page 2 of 3 Account(s): AutoInvest 80000/167617 Generated at: 02-Okt-2025 +Trade details, CHF +Reporting period +05-Aug-2025 to 05-Aug-2025 +Booking amount Amount Conversion cost Booked Amount +Trade Date Value Date Conversion Rate +ID CHF CHF CHF +Net Amount - - - - - 0,00 -376,27 +Saxo Bank CH / The Circle 38 / 8058 Zürich-Flughafen / Switzerland CH-20672455 Reporting period +Currency: CHF 05-Aug-2025 - 05-Aug-2025 +Page 3 of 3 Account(s): AutoInvest 80000/167617 Generated at: 02-Okt-2025 diff --git a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/saxobank/SaxoBankPDFExtractorTest.java b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/saxobank/SaxoBankPDFExtractorTest.java index c5ccdfdc41..5f9ee94ad8 100644 --- a/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/saxobank/SaxoBankPDFExtractorTest.java +++ b/name.abuchen.portfolio.tests/src/name/abuchen/portfolio/datatransfer/pdf/saxobank/SaxoBankPDFExtractorTest.java @@ -601,6 +601,70 @@ public void testSecurityBuy03() hasTaxes("EUR", 0.00), hasFees("EUR", 20.00 + 30.82)))); } + @Test + public void testSecurityBuy04() + { + var extractor = new SaxoBankPDFExtractor(new Client()); + + List errors = new ArrayList<>(); + + var results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Buy04.txt"), errors); + + assertThat(errors, empty()); + assertThat(countSecurities(results), is(1L)); + assertThat(countBuySell(results), is(1L)); + assertThat(countAccountTransactions(results), is(0L)); + assertThat(countAccountTransfers(results), is(0L)); + assertThat(results.size(), is(2)); + new AssertImportActions().check(results, "CHF"); + + // check security + assertThat(results, hasItem(security( // + hasIsin("IE00B6R52259"), hasWkn(null), hasTicker("SSAC_CHF"), // + hasName("iShares MSCI ACWI USD Acc UCITS ETF"), // + hasCurrencyCode("CHF")))); + + // check buy sell transaction + assertThat(results, hasItem(purchase( // + hasDate("2025-08-05T12:46:23"), hasShares(14.00), // + hasSource("Buy04.txt"), // + hasNote("Order ID 5311227095 | Trade ID 6358696418"), // + hasAmount("CHF", 1128.22), hasGrossValue("CHF", 1126.53), // + hasTaxes("CHF", 0.00), hasFees("CHF", 1.69)))); + } + + @Test + public void testSecurityBuy05() + { + var extractor = new SaxoBankPDFExtractor(new Client()); + + List errors = new ArrayList<>(); + + var results = extractor.extract(PDFInputFile.loadTestCase(getClass(), "Buy05.txt"), errors); + + assertThat(errors, empty()); + assertThat(countSecurities(results), is(1L)); + assertThat(countBuySell(results), is(1L)); + assertThat(countAccountTransactions(results), is(0L)); + assertThat(countAccountTransfers(results), is(0L)); + assertThat(results.size(), is(2)); + new AssertImportActions().check(results, "CHF"); + + // check security + assertThat(results, hasItem(security( // + hasIsin("CH0016999846"), hasWkn(null), hasTicker("CSBGC7"), // + hasName(null), // + hasCurrencyCode("CHF")))); + + // check buy sell transaction + assertThat(results, hasItem(purchase( // + hasDate("2025-08-05T13:02:36"), hasShares(5.00), // + hasSource("Buy05.txt"), // + hasNote("Order ID 5311259140 | Trade ID 6358773884"), // + hasAmount("CHF", 376.27), hasGrossValue("CHF", 375.99), // + hasTaxes("CHF", 0.00), hasFees("CHF", 0.28)))); + } + @Test public void testCashTransfer01() { diff --git a/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/SaxoBankPDFExtractor.java b/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/SaxoBankPDFExtractor.java index abdd4b12d9..bb97cb72c5 100644 --- a/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/SaxoBankPDFExtractor.java +++ b/name.abuchen.portfolio/src/name/abuchen/portfolio/datatransfer/pdf/SaxoBankPDFExtractor.java @@ -48,6 +48,7 @@ private void addBuySellTransaction() // @formatter:off // Währung: CHF 05-Dez-2024 - 05-Dez-2024 // / Phone No.: +45 39 77 40 00 / Fax No.: +45 39 77 42 00 / Email: info@saxobank.com Currency: USD 09-Apr-2025 - 09-Apr-2025 + // Currency: CHF 05-Aug-2025 - 05-Aug-2025 // @formatter:on .section("currency") // .match("^.*(W.hrung|Currency): (?[A-Z]{3}).*$") // @@ -94,7 +95,7 @@ private void addBuySellTransaction() .attributes("name", "isin", "tickerSymbol", "currency") // .match("^Instrument (?.*) Handelszeit.*$") // .match("^ISIN (?[A-Z]{2}[A-Z0-9]{9}[0-9]) Valuta.*$") // - .match("^Symbol (?[A-Z0-9]{1,6}(?:\\.[A-Z]{1,4})?):.*$") // + .match("^Symbol (?[A-Z0-9\\._-]{1,10}(?:\\.[A-Z]{1,4})?):.*$") // .match("^Ordertyp .* (\\-)?[\\.,'\\d]+ (?[A-Z]{3})$") // .assign((t, v) -> t.setSecurity(getOrCreateSecurity(v))), // @formatter:off @@ -107,7 +108,7 @@ private void addBuySellTransaction() .attributes("name", "isin", "tickerSymbol", "currency") // .match("^Instrument (?.*) Handelszeit.*$") // .match("^ISIN (?[A-Z]{2}[A-Z0-9]{9}[0-9]) Valuta.*$") // - .match("^Symbol (?[A-Z0-9]{1,6}(?:\\.[A-Z]{1,4})?):.*$") // + .match("^Symbol (?[A-Z0-9\\._-]{1,10}(?:\\.[A-Z]{1,4})?):.*$") // .match("^Hauptb.rse .* Gehandelter Wert (\\-)?[\\.,'\\d]+ (?[A-Z]{3})$") // .assign((t, v) -> t.setSecurity(getOrCreateSecurity(v))), // @formatter:off @@ -124,7 +125,7 @@ private void addBuySellTransaction() section -> section // .attributes("isin", "tickerSymbol", "currency") // .match("^ISIN (?[A-Z]{2}[A-Z0-9]{9}[0-9]) Valuta.*$") // - .match("^Symbol (?[A-Z0-9]{1,6}(?:\\.[A-Z]{1,4})?):.*$") // + .match("^Symbol (?[A-Z0-9\\._-]{1,10}(?:\\.[A-Z]{1,4})?):.*$") // .match("^Hauptb.rse .* Gehandelter Wert (\\-)?[\\.,'\\d]+ (?[A-Z]{3})$") // .assign((t, v) -> t.setSecurity(getOrCreateSecurity(v))), // @formatter:off @@ -132,12 +133,29 @@ private void addBuySellTransaction() // ISIN US26923G8226 Value Date 10-Apr-2025 // Symbol PFFA:arcx Order ID 5276831204 // Venue Exchange Traded Value -980,98 USD + // + // Instrument iShares MSCI ACWI USD Acc UCITS ETF Trade time 05-Aug-2025 12:46:23 + // ISIN IE00B6R52259 Value Date 07-Aug-2025 + // Symbol Symbol SSAC_CHF:xswx Order ID 5311227095 + // Venue Exchange Price 80,4666 CHF // @formatter:on section -> section // .attributes("name", "isin", "tickerSymbol", "currency") // .match("^Instrument (?.*) Trade time.*$") // .match("^ISIN (?[A-Z]{2}[A-Z0-9]{9}[0-9]) Value.*$") // - .match("^Symbol (?[A-Z0-9]{1,6}(?:\\.[A-Z]{1,4})?):.*$") // + .match("^Symbol (?[A-Z0-9\\._-]{1,10}(?:\\.[A-Z]{1,4})?):.*$") // + .match("^.*Traded Value (\\-)?[\\.,'\\d]+ (?[A-Z]{3})$") // + .assign((t, v) -> t.setSecurity(getOrCreateSecurity(v))), + // @formatter:off + + // ISIN CH0016999846 Value Date 07-Aug-2025 + // Symbol CSBGC7:xswx Order ID 5311259140 + // Venue Exchange Price 75,19880769 CHF + // @formatter:on + section -> section // + .attributes("isin", "tickerSymbol", "currency") /// + .match("^ISIN (?[A-Z]{2}[A-Z0-9]{9}[0-9]) Value.*$") // + .match("^Symbol (?[A-Z0-9\\._-]{1,10}(?:\\.[A-Z]{1,4})?):.*$") // .match("^.*Traded Value (\\-)?[\\.,'\\d]+ (?[A-Z]{3})$") // .assign((t, v) -> t.setSecurity(getOrCreateSecurity(v)))) @@ -330,7 +348,7 @@ private void addDividendeTransaction() section -> section // .attributes("name", "currency", "tickerSymbol", "isin") // .match("^Description (?.*) Dividend per share [\\.,'\\d]+ (?[A-Z]{3})$") // - .match("^Symbol (?[A-Z0-9]{1,6}(?:\\.[A-Z]{1,4})?):.*$") // + .match("^Symbol (?[A-Z0-9\\._-]{1,10}(?:\\.[A-Z]{1,4})?):.*$") // .match("^ISIN (?[A-Z]{2}[A-Z0-9]{9}[0-9]).*$") // .assign((t, v) -> t.setSecurity(getOrCreateSecurity(v))), // @formatter:off @@ -341,7 +359,7 @@ private void addDividendeTransaction() section -> section // .attributes("name", "currency", "tickerSymbol", "isin") // .match("^Description (?.*) Dividende pro Aktie [\\.,'\\d]+ (?[A-Z]{3})$") // - .match("^Symbol (?[A-Z0-9]{1,6}(?:\\.[A-Z]{1,4})?):.*$") // + .match("^Symbol (?[A-Z0-9\\._-]{1,10}(?:\\.[A-Z]{1,4})?):.*$") // .match("^ISIN (?[A-Z]{2}[A-Z0-9]{9}[0-9]).*$") // .assign((t, v) -> t.setSecurity(getOrCreateSecurity(v)))) @@ -647,6 +665,16 @@ private > void addFeesSectionsTransaction(T transaction .match("^Stempelgeb.hr .* \\-(?[\\.,'\\d]+) \\-[\\.,'\\d]+$") // .assign((t, v) -> processFeeEntries(t, v, type)) + // @formatter:off + // Swiss Stamp Duty + // 46406463093 05-Aug-2025 07-Aug-2025 -1,69 1,000000 0,00 -1,69 + // @formatter:on + .section("fee").optional() // + .documentContext("currency") // + .find("Swiss Stamp Duty.*") // + .match("^[\\d]+ [\\d]{2}\\-[\\w]+\\-[\\d]{4} [\\d]{2}\\-[\\w]+\\-[\\d]{4} \\-(?[\\.,'\\d]+) [\\.,'\\d]+ [\\.,'\\d]+ \\-[\\.,'\\d]+$") // + .assign((t, v) -> processFeeEntries(t, v, type)) + // @formatter:off // Aktienbetrag 41308584615 05-Feb-2025 07-Feb-2025 -23.714,22 0,904466 -53,48 -21.448,69 // @formatter:on