Finanzmanager/docs/wertpapiere.md
fds2610-bot 4484ddd90d Wertpapiere: Depotbestand aus einer CSV-Datei übernehmen
Ein Depot, das nie im FinanzManager geführt wurde, ließ sich bisher nur Buchung
für Buchung nachtragen. `Services\DepotbestandImport` liest stattdessen die
Bestandsliste, die die Bank zum Herunterladen anbietet (erprobt mit der DKB):
Kopfzeile mit Wertpapierbezeichnung, WKN, ISIN, Einstiegskurs, Bewertungskurs,
Stückzahl und Assetklasse; Trennzeichen und Zeichensatz erkennt er selbst,
Beträge wie "1.471,88 €" und Bruchstückzahlen wie 55,6863 liest er, wie sie
kommen.

Der Export kennt keine Historie – er beschreibt einen Stichtag. Je Position
entsteht deshalb *eine* Depotbuchung: ein Kauf zum Stichtag über die volle
Stückzahl zum Einstiegskurs. Der Einstand stimmt damit auf den Cent; frühere
Käufe, Verkäufe und Dividenden bleiben leer und lassen sich nachtragen.

Der Bewertungskurs wird als Kurs des Stichtags hinterlegt, sonst stünde der
frisch übernommene Bestand sofort wieder ohne Bewertung da. Am echten Export
nachgerechnet: 33.176,12 € Einstand, 47.120,25 € Wert – die Bank weist zehn Cent
weniger Gewinn aus, was an ihren auf zwei Stellen gerundeten Kursen liegt.

Kein Geldfluss: Ein Verrechnungskonto ist wählbar, bleibt aber vorbelegt leer.
Das Geld ist vor Jahren geflossen, eine Belastung zum heutigen Tag würde das
Girokonto verfälschen.

Der Ablauf ist zweistufig – hochladen, Vorschau prüfen, übernehmen. Ohne
ausdrückliche Wahl findet der Import das Depot über die Kontonummer, die zur
Depotnummer der Datei passt; ein `import_hash` je Position verhindert, dass
dieselbe Datei zweimal denselben Bestand anlegt. `Buchung::QUELLEN` kennt dafür
jetzt die Herkunft `csv`.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-12 14:26:50 +02:00

173 lines
8.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Wertpapiere und Depots
Ein Depot ist ein Konto der Kontoart **Depot**. Was darin liegt, steht nicht in
`buchungen`, sondern in `depot_buchungen` – dort gehören Stückzahl und Kurs dazu, und der
Saldo ist keine Summe von Beträgen, sondern der bewertete Bestand.
## Zwei Seiten je Geschäft
Jedes Wertpapiergeschäft erzeugt bis zu zwei Datensätze:
* die **Depotbuchung** mit Stück, Kurs, Gebühren und Steuern,
* die **Geldbuchung** auf dem Verrechnungskonto – eine gewöhnliche Buchung, die im
Kontoblatt steht wie jede andere.
Verbunden sind sie über `buchung_id`. So bleibt das Girokonto stimmig, ohne dass
Kontoblätter Stückzahlen führen müssten. Wird die Depotbuchung gelöscht, geht die
Geldbuchung mit; sie hat ohne das Geschäft keinen Sinn.
Ein- und Auslieferungen rühren kein Konto an und haben deshalb keine Geldbuchung.
## Was in welchem Feld steht
`betrag` ist der **Bruttowert**: Stück × Kurs, bei Dividenden der Bruttoertrag. Gebühren
und Steuern stehen *daneben*, nicht darin. Daraus folgt der Geldfluss:
| Vorgang | Verrechnungskonto |
|---|---|
| Kauf | −(Betrag + Gebühren + Steuern) |
| Verkauf | +(Betrag − Gebühren − Steuern) |
| Dividende | +(Betrag − Steuern) |
Am echten Export nachgerechnet: Ein Verkauf über 4 × 797,00 € mit 15,37 € Gebühren und
69,46 € Steuern ergibt 3.103,17 € Gutschrift – genau der Wert, den das QIF im Feld `$`
nennt. Diese Trennung ist nötig, damit der Einstand die Nebenkosten **genau einmal**
enthält.
`stueck` trägt die Richtung: Zugänge positiv, Abgänge negativ, Dividenden null.
## Einstand nach gleitendem Durchschnitt
Bei jedem Kauf steigt der Einstand um Kaufpreis samt Gebühren, bei jedem Verkauf sinkt er
anteilig um den bis dahin durchschnittlichen Stückpreis. So rechnet auch der
FinanzManager, und die Frage, *welches* Stück verkauft wurde, stellt sich gar nicht.
Der **realisierte Gewinn** eines Verkaufs ist Erlös minus Kosten minus anteiligem
Einstand.
> Für die Steuererklärung gilt in Deutschland FIFO. Maßgeblich ist dort die Abrechnung der
> Bank – diese Übersicht ersetzt sie nicht.
Vollständig verkaufte Positionen verschwinden aus der Bestandsliste; ihr realisierter
Gewinn zählt weiter mit.
## Kurse
Ein Kurs je Wertpapier und Tag. Mehr Auflösung braucht eine Vermögensübersicht nicht, und
die Freikontingente der Kursdienste geben nicht mehr her.
**Ohne Kurs wird nicht bewertet**: Die Position zeigt „kein Kurs“ und fällt aus der
Wertsumme; die Übersicht sagt ausdrücklich, dass sie unvollständig ist. Der Einstand als
Ersatzwert wäre bequem, verstellte aber den Blick.
### Kursabruf
`Services\Kursabruf` kennt zwei Anbieter (siehe `Aktienkurse.md`):
* **Leeway.tech** – 50 Abrufe/Tag, kennt ISINs direkt
* **Financial Modeling Prep** – 250 Abrufe/Tag
Beide arbeiten in zwei Schritten: ISIN → Kürzel, Kürzel → Kurs. Das gefundene Kürzel wird
am Wertpapier gespeichert, damit der erste Schritt künftig entfällt – das halbiert den
Verbrauch. Abgerufen wird nur für Papiere **mit Bestand**, und nur, wenn für heute noch
kein Kurs vorliegt.
Anbieter und Schlüssel stehen im Einstellungsdialog unter *Wertpapiere*. Der tägliche Lauf
gehört in einen Cron-Eintrag:
```bash
docker compose exec -u www-data php php scripts/kursabruf.php
```
Das Skript bricht ab, solange der automatische Abruf in den Einstellungen nicht
eingeschaltet ist – ein vergessener Cron-Eintrag kostet so kein Kontingent. Der Knopf auf
der Wertpapierseite arbeitet unabhängig davon.
Die Feldnamen der Antworten werden nachsichtig gelesen (`close`, `price`, `last` …): Die
Dienste benennen sie unterschiedlich und gelegentlich um. Der API-Schlüssel steht in der
Adresse und wird vor jedem Logeintrag maskiert.
## QIF-Import (`!Type:Invst`)
Der Import legt Wertpapier, Depotbuchung und Geldbuchung in einem Zug an. Zwei
Eigenheiten des Formats:
* **Das `L`-Feld trägt bei Wertpapiergeschäften mehr als sonst.** Dem Verrechnungskonto
kann eine Kategorie vorangehen: `LKursgewinne:Realisierte Gewinne|[Verrechnungskonto]`.
Gelesen wird deshalb nur der Teil hinter dem letzten senkrechten Strich.
* **Das Feld `O` führt zwölf Beträge.** Sicher zugeordnet sind Position 4 (Provision) und
10 (fremde Spesen) – beides Gebühren. Alles Übrige sind Abzüge des Fiskus und werden zu
„Steuern“ zusammengefasst. Damit fällt keine Position heraus, ohne dass jede einzeln
gedeutet werden müsste.
Fehlt der Kurs – bei Dividenden immer –, wird der Bruttobetrag aus der Gutschrift plus den
Abzügen zurückgerechnet.
`DivIn` und `DivAus` landen beide auf „dividende“; die ursprüngliche Bezeichnung bleibt in
`vorgang_qif` erhalten, weil die Zuordnung nicht in jedem Fall umkehrbar ist.
**Dabei behoben:** Bis Phase 5 buchte der Import den Geldfluss *jedes*
Wertpapiergeschäfts als Abgang – auch bei Verkäufen und Dividenden. Das QIF nennt im Feld
`$` immer einen positiven Betrag; die Richtung steckt allein im Vorgang. Verrechnungskonten
von Depots wiesen dadurch zu niedrige Salden aus.
## CSV-Import einer Bestandsliste
*Wertpapiere → Bestände → Import* übernimmt die Depotbestandsliste, wie Banken sie zum
Herunterladen anbieten (erprobt mit der DKB). Erwartet wird eine Kopfzeile mit den Spalten
*Wertpapierbezeichnung*, *WKN*, *ISIN*, *Einstiegskurs*, *Bewertungskurs*, *Stückzahl* und
*Assetklasse*; *Datum der Erstellung* und *Depotnummer* werden mitgelesen, wenn vorhanden.
Trennzeichen (`;`, `,`, Tabulator) und Zeichensatz (UTF-8 mit oder ohne BOM,
Windows-1252) erkennt der Import selbst.
**Der Export kennt keine Historie.** Er beschreibt einen Stichtag: was im Depot liegt und
was es im Schnitt gekostet hat. Die Geschäfte, die dorthin geführt haben, fehlen. Der
Import legt deshalb je Position **eine** Depotbuchung an – einen Kauf zum Stichtag über
die volle Stückzahl zum Einstiegskurs. Der Einstand stimmt damit auf den Cent, frühere
Käufe, Verkäufe und Dividenden bleiben naturgemäß leer und lassen sich später von Hand
nachtragen.
**Zwei Kurse, zwei Rollen.** Der *Einstiegskurs* ist der durchschnittliche Kaufpreis und
wird zum Kurs der Buchung; der *Bewertungskurs* ist der aktuelle Kurs und landet als Kurs
des Stichtags in `kurse`. Erst dadurch ist der frisch übernommene Bestand sofort bewertbar
und zeigt denselben Gewinn wie die Bank – bis auf wenige Cent, die in den auf zwei Stellen
gerundeten Kursen des Exports stecken.
**Kein Geldfluss.** Ein Verrechnungskonto ist wählbar, bleibt aber in aller Regel leer:
Das Geld ist vor Jahren geflossen, eine Belastung zum heutigen Tag würde das Girokonto
verfälschen. Ohne Verrechnungskonto entsteht keine Geldbuchung.
**Assetklasse → Art**: *Aktien* → Aktie, *ETFs* und *Fonds* → Fonds, *Anleihen* →
Anleihe, *Zertifikate* → Zertifikat, alles Übrige → Sonstiges.
Das Zieldepot lässt sich wählen; ohne Auswahl sucht der Import das Depot, dessen
**Kontonummer** der Depotnummer aus der Datei entspricht. Jede Position bekommt einen
`import_hash` aus Depot, Datum, Vorgang, Stück, Betrag und ISIN – dieselbe Datei zweimal
eingelesen legt nichts doppelt an. Ein *anderer* Stichtag gilt dagegen als neuer Vorgang.
Der Ablauf ist zweistufig: Der Upload zeigt erst eine Vorschau mit allen Positionen,
Einstand, Wert und dem Status je Zeile; erst *Jetzt übernehmen* schreibt. Die Datei bleibt
im Importverzeichnis liegen.
## Kontenübersicht
Depots erscheinen dort mit ihrem **bewerteten Bestand**, nicht mit einem Buchungssaldo. Der
Anfangssaldo bleibt dabei: Führt ein Depotkonto eine Barreserve, steht sie dort.
Damit ist die Voraussetzung erfüllt, um die Summe der Kontostände mit dem FinanzManager zu
vergleichen (`PLAN.md`, Kapitel 7) – sobald Kurse für alle Positionen vorliegen.
## Tests
`tests/17_WertpapiereTest.php` – Wiederfinden über ISIN, WKN und Name samt Nachtragen
fehlender Felder; Bestand und Einstand über mehrere Käufe; Geldseite mit richtigem
Vorzeichen; Verkauf mit anteiligem Einstand und realisiertem Gewinn; Dividende ohne
Bestandsänderung; fehlende Kurse; Veräußerungsrechnung über die Historie hinweg; der
QIF-Import eines echten Ausschnitts (Kauf, Dividende, Verkauf) inklusive Idempotenz;
Löschen samt Geldbuchung.
`tests/21_DepotbestandImportTest.php` – der CSV-Bestandsimport an einem Ausschnitt des
echten DKB-Exports: Vorschau ohne Nebenwirkung, Einstand und Wert aus beiden Kursen,
Bruchstückzahlen, Assetklasse → Art, hinterlegte Bewertungskurse, Idempotenz beim zweiten
Lauf, Zuordnung des Depots über die Depotnummer, Geldseite mit und ohne
Verrechnungskonto, sowie die Abwehr einer fremden oder leeren Datei.