Skip to content

fix(degiro): recognize newer German export formats and fix dividend/withholding reconciliation - #434

Merged
vroonhof merged 2 commits into
vroonhof:mainfrom
Carismarkus:fix/degiro-german-export-2026
Sep 20, 2026
Merged

vroonhof merged 2 commits into
vroonhof:mainfrom
Carismarkus:fix/degiro-german-export-2026

Conversation

@Carismarkus

Copy link
Copy Markdown

Summary

While processing a real 2026 DEGIRO German-language export, two independent bugs
surfaced:

  1. New German export row formats not recognized (first commit): buy/sell rows
    switched from Kauf 60 iShares@20.08 EUR (ISIN) to Kauf 3 zu je 64,91 EUR (ISIN), which affected every trade row in a current German export — all
    silently skipped, no trades imported at all. Three other row types
    (Einrichtung von Handelsmodalitäten ..., Währungswechsel (Einbuchung/Ausbuchung), Geldmarktfonds Kompensation) were also
    unclassified and caused a hard NotImplementedError abort.

  2. Dividend + withholding tax merged into one payment breaks reconciliation
    (second commit): _process_dividend() merged the dividend amount and
    withholding tax onto a single SecurityPayment, but
    PaymentReconciliationCalculator._accumulate_broker() expects them as two
    separate payments (the convention the IBKR/Fidelity importers already
    follow) — causing every DEGIRO dividend with withholding tax to show a false
    "Broker div: None" mismatch in the reconciliation report.

Both are fixed with minimal, targeted changes; see the individual commit
messages for full detail. Each is covered by new/extended unit tests.

Test plan

  • pytest tests/importers/degiro/ — 109 passed
  • Full suite: pytest tests/ — same result as main (2 pre-existing, unrelated
    Windows-only failures in fidelity/build_web_app tests)
  • Verified end-to-end against a real 2026 DEGIRO export: import now completes
    without warnings/aborts, and payment reconciliation correctly matches
    dividends that were previously flagged as false mismatches
  • black / flake8 clean

AI used: Claude Sonnet 5

Markus and others added 2 commits September 16, 2026 20:25
DEGIRO's German-language export now uses different wording for several
row types than what the importer expected, causing real accounts to
either lose all trades silently or abort entirely:

- Buy/sell rows dropped the inline product name and "@" price marker in
  favor of "zu je" ("at each"), e.g. "Kauf 3 zu je 64,91 EUR (ISIN)"
  instead of "Kauf 60 iShares@20.08 EUR (ISIN)". This affected every
  buy/sell row in a real 2026 export, none of which matched _TRADE_RE,
  so no trades were imported at all (only a warning was logged).
- A new connection-fee wording, "Einrichtung von Handelsmodalitäten
  YYYY (Exchange)", was unclassified and aborted the import with
  NotImplementedError.
- A new FX wording, "Währungswechsel (Einbuchung/Ausbuchung)", was
  likewise unclassified and aborted the import.
- A new money-market-fund compensation row, "Geldmarktfonds
  Kompensation", was also unclassified and aborted the import.

Extend _TRADE_RE to accept the "zu je" variant and add the three new
descriptions to the classification tables, mirroring the existing
English/Italian/French/German entries. Covered by new parametrized
test cases in test_degiro_account_parser.py and test_degiro_importer.py.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_process_dividend() built a single SecurityPayment and then applied
apply_withholding_tax_fields() to that same object whenever a matching
Dividend Tax row existed, merging the dividend amount and the
withholding fields onto one payment.

PaymentReconciliationCalculator._accumulate_broker() assumes dividend
and withholding tax live on two separate payments -- the convention
the IBKR and Fidelity importers already follow, since their broker
exports report dividend and tax as separate cash events. It returns
early as soon as it sees withholding fields set, before accounting for
the payment's dividend amount:

    if withholding_claim is not None and withholding_claim != 0:
        agg.withholding += withholding_claim
        ...
        return
    amount = payment.amount or Decimal("0")
    agg.dividend += amount   # never reached for a merged DEGIRO payment

Effect: for every DEGIRO dividend with a matching withholding-tax row,
payment reconciliation reported "Broker div: None" and a false
mismatch, e.g.:

    MISMATCH DE ALLIANZ SE 2026-05-12: KL div 31.354 CHF / KL wht
    4.7031 CHF vs Broker div None None / Broker wht 9.02 EUR; ...

Split _process_dividend() into two SecurityPayment objects (plain
dividend, and a separate is_withholding=True payment) to match the
IBKR/Fidelity convention. Covered by a new regression test.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@vroonhof vroonhof left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you. It is bit sad DEGIRO cannot produce something more robust.

@github-actions

Copy link
Copy Markdown

📄 Sample PDF and XML Preview

A sample Steuerauszug has been generated from vtandchill_2025.xml for this PR.

⬇️ Download

(Artifact expires in 14 days. Requires GitHub login to download. View workflow run)

@vroonhof
vroonhof merged commit 494abe6 into vroonhof:main Sep 20, 2026
2 checks passed
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.

2 participants