Finanzmanager/docs/wertpapiere.md
fds2610-bot e56c21bc9b Wertpapiergeschäft aus einer Kontobuchung
Der Bankabruf bringt die Wertpapierabrechnung als Umsatz auf dem Girokonto, und
erst dort entscheidet sich, dass es ein Kauf war. Wer die Buchung einem Depot
zuordnete, buchte bisher eine Umbuchung – ohne Stückzahl, ohne Kurs, ohne
Einstand. Die Zuordnungsliste im Kontoblatt kennt jetzt neben Kategorie und
Gegenkonto eine dritte Gruppe "Wertpapiergeschäft im Depot", die statt zu
speichern die Wertpapiermaske öffnet; ohne JavaScript führt derselbe Weg auf
/buchungen/{id}/wertpapier.

Die Maske ist auf den "Wertpapier kaufen"-Dialog des FinanzManagers
zugeschnitten: vorne Vorgangsart, Datum, Wertpapier, Stück und Kurs, dahinter
hinter dem Knopf "Kosten …" die Nebenkosten nach Arten – sechs Gebühren und
sechs Steuern. ISIN und WKN werden aus dem Verwendungszweck gelesen, ein
bekanntes Papier ist damit vorausgewählt. Ein Abfluss vom Konto kann nur ein
Kauf sein, ein Zufluss ein Verkauf oder eine Dividende.

Der Buchungsbetrag ist die feste Größe: Die Nebenkosten werden aus ihm
herausgerechnet, was übrig bleibt ist der Kurswert und damit der
Einstandspreis. Im Kontoblatt entsteht eine Splitbuchung mit je einer Zeile pro
belegter Kostenart und dem Rest als Umbuchung ins Depot; ohne Nebenkosten
bleibt es bei der schlichten Umbuchung. Die Depotbuchung hängt sich an die
vorhandene Buchung, statt eine zweite anzulegen (neue Spalte
depot_buchungen.buchung_erzeugt) – wird sie gelöscht, bleibt die Buchung der
Bank stehen und ist nur wieder unzugeordnet.

Die Kategorien der Kostenarten liegen im Einstellungsdialog unter "Wertpapiere",
einmalig je Art; für "Sonstige Kosten" ist die Einstellung nur die Vorgabe und
im Dialog überschreibbar. Arten ohne Kategorie werden zu Splitzeilen ohne
Zuordnung, nicht zu einem Fehler.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-13 13:27:52 +02:00

14 KiB
Raw Blame History

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.

Beide Richtungen kommen vor, und buchung_erzeugt hält fest, welche es war:

  • Depotbuchung zuerst (Maske unter Wertpapiere, QIF-Import): Sie legt die Geldbuchung an. Wird sie gelöscht, geht die Geldbuchung mit – sie hat ohne das Geschäft keinen Sinn.
  • Geldbuchung zuerst (Bankabruf, siehe Geschäft aus einer Kontobuchung): Die Depotbuchung hängt sich an die vorhandene Buchung. Wird sie gelöscht, bleibt die Buchung der Bank stehen und ist nur wieder unzugeordnet – sie ist deren Beleg, nicht unsere.

Ein- und Auslieferungen rühren kein Konto an und haben deshalb keine Geldbuchung.

Geschäft aus einer Kontobuchung

Der Weg, den der Alltag nimmt: Der Bankabruf liefert die Wertpapierabrechnung als Umsatz auf dem Girokonto – ein Betrag, ein Verwendungszweck, sonst nichts. Im Kontoblatt öffnet die Zuordnungsliste unter Wertpapiergeschäft im Depot die Wertpapiermaske; die schlichte Umbuchung auf dasselbe Depot steht daneben weiter zur Wahl.

Zwei Dialoge, wie im FinanzManager: vorne Vorgangsart, Datum, Wertpapier, Stück und Kurs, dahinter hinter Kosten … die Nebenkosten nach Arten. ISIN und WKN liest Wertpapier::ausText() aus dem Verwendungszweck („WKN LYX01W … ISIN LU1834983477“) und wählt das Papier vor. Ohne JavaScript führt derselbe Weg auf /buchungen/{id}/wertpapier – dieselben Felder, untereinander statt hinter einem Knopf.

Der Buchungsbetrag ist die feste Größe. Er kommt von der Bank und wird nicht nachgerechnet. Die Nebenkosten gehen aus ihm heraus, der Rest ist der Kurswert und damit der Einstandspreis:

Kurswert = Buchungsbetrag + Summe der Nebenkosten

Kauf     −255,00 € + 5,00 €  = −250,00 €   → Einstand 250,00 €
Verkauf 3.103,17 € + 84,83 € = 3.188,00 €  → Erlös  3.188,00 €

Dieselbe Rechnung in beiden Richtungen: Nebenkosten sind Aufwand, ob gekauft oder verkauft wird. Der Kurs ist demgegenüber eine Angabe der Abrechnung; fehlt er, ergibt er sich aus Einstand und Stückzahl – nie umgekehrt.

Im Kontoblatt entsteht daraus eine Splitbuchung: je belegter Kostenart eine Zeile in ihrer Kategorie, der Rest als Umbuchung ins Depot. Die Zeilen ergeben genau den Buchungsbetrag. Ohne Nebenkosten bleibt es bei der schlichten Umbuchung – eine Splitbuchung mit einer einzigen Zeile wäre nur umständlicher zu lesen.

Kostenarten und ihre Kategorien

Services\Wertpapierkosten kennt sechs Gebühren- und sechs Steuerarten – dieselben, die der FinanzManager im Kostendialog führt:

Gebühren Steuern
Bankprovision Zinsabschlagsteuer
Maklercourtage Kapitalertragsteuer
Börsenplatzentgelt Solidaritätszuschlag
Spesen Ausländische Quellensteuer
Spesen Ausland Abgeltungsteuer
Sonstige Kosten Kirchensteuer

Welche Kategorie zu welcher Art gehört, steht einmal unter Einstellungen → Wertpapiere (wertpapiere_kategorie_<art>); der Dialog fragt dann nur noch Beträge ab. Einzige Ausnahme ist Sonstige Kosten: Deren Kategorie lässt sich an Ort und Stelle wählen, weil sie nicht immer dieselbe ist. Arten ohne hinterlegte Kategorie werden zu Splitzeilen ohne Zuordnung, nicht zu einem Fehler.

In der Depotbuchung stehen die Kosten nur als zwei Summen (gebuehren, steuern) – für den Einstand genügt das. Die Aufschlüsselung lebt in den Splitzeilen, wo sie gebraucht wird.

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:

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.

Depotbuchungen löschen

Die Liste der Depotbuchungen löscht einzeln – Löschen je Zeile – oder mehrere auf einmal, genau wie das Kontoblatt: Ankreuzfeld je Zeile, das Feld in der Titelzeile wählt alle angezeigten, Auswahl löschen in der Aktionsleiste schickt sie an POST /wertpapiere/buchungen/loeschen. Der Knopf nennt die Anzahl und bleibt ohne Auswahl gesperrt; die Rückfrage kommt vor dem Absenden. Ohne JavaScript funktioniert alles weiter – dann ist der Knopf offen und der Controller weist eine leere Auswahl ab.

Der eingestellte Filter hängt am Ziel beider Formulare, deshalb steht danach wieder dieselbe Ansicht da. Zusammengesetzt wird er im Controller neu, damit kein fremdes Ziel eingeschleust werden kann.

Mit jeder Depotbuchung geht ihre selbst erzeugte Geldbuchung (siehe Zwei Seiten je Geschäft). Hing sie dagegen an einer Buchung des Bankabrufs, bleibt diese stehen und verliert nur die Aufteilung des Geschäfts – sie steht danach wieder unzugeordnet im Kontoblatt. Am Bestand ist nichts nachzuziehen: Er wird bei jedem Aufruf aus den Depotbuchungen gerechnet und gibt die Löschung von selbst wieder her – ebenso realisierter Gewinn, Veräußerungen und Erträge. Das Wertpapier selbst bleibt in den Stammdaten stehen.

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/22_DepotbuchungenLoeschenTest.php – das Sammellöschen: die Geldbuchungen gehen mit, das Verrechnungskonto stimmt danach wieder, Bestand, Einstand, realisierter Gewinn, Erträge und die Veräußerungsauswertung geben die Löschung her, unbekannte Nummern in der Auswahl bleiben folgenlos. Den Ablauf im Browser prüft tests/e2e/specs/module.spec.js.

tests/25_WertpapiergeschaeftTest.php – das Geschäft aus einer Kontobuchung: das Depot als eigener Wert der Zuordnungsliste, ISIN und WKN aus dem Verwendungszweck, Kauf mit Kosten (Einstand, Splitzeilen, Summe gleich dem Buchungsbetrag, keine zweite Buchung daneben), Kauf ohne Kosten als schlichte Umbuchung mit errechnetem Kurs, Verkauf mit Gebühren und Steuern, das Löschen, das die Bankbuchung stehen lässt, sowie die Abweisungen: doppeltes Geschäft, Kosten in Höhe der Buchung, Konto statt Depot, Kauf ohne Stückzahl. Die beiden Dialoge prüft tests/e2e/specs/wertpapiergeschaeft.spec.js.

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.