Moin moin 💸! Willst du schon immer mal wissen, wie viel ein 10.000 € Kredit bei 5 % Zinsen
wirklich kostet? (Spoiler: Die Bank verdient gut daran 😉).
Willkommen im echten Leben! Ein Ort, an dem Dinge teuer sind und Banken einem Geld leihen – aber leider
nicht aus Nächstenliebe. Wer die Zinsen nicht versteht, zahlt am Ende drauf. Wer sie aber selbst in
Python berechnen kann, behält die Kontrolle. In diesem Paket findest du den vollständigen Code
für eine echte Finanz-App mit Benutzeroberfläche (PyQt6), Tabellen und Live-Diagrammen (Matplotlib).
Wenn du das Feld „Tilgung“ leer lasst, rechnet die App im Hintergrund magisch die perfekte
Monatsrate für dich aus. Speichere den Code ab, installiert die zwei Bibliotheken (Pip-Packages)
und schau dir an, wie aus einfachen Formeln eine Python - Anwendung wird!
Das Eingabefeld „Tilgung“ ist leer! Klassischer Verbraucherkredit.
Ein klassischer Verbraucherkredit (auch Ratenkredit genannt) wird als Annuitätendarlehen berechnet.
Das bedeutet, dass die monatliche Kreditrate über die gesamte Laufzeit hinweg konstant gleichbleibt.
Die Rate setzt sich jeden Monat neu aus einem Zinsanteil und einem Tilgungsanteil zusammen. Da der
Zinssatz und die Laufzeit vorgegeben sind und am Ende keine Restschuld verbleiben darf, ergibt sich
die anfängliche Tilgung automatisch aus der Laufzeit.
Verbraucherkredit: CSV-Export. Visualisierung in Excel.
Wie wird ein Verbraucherkredit berechnet? Das obige Bild zeigt die mathematischen Zusammenhänge.
#!/usr/bin/env python3
# *****************************************************************************
# Darlehensrechner (PyQt6). Berechnet die monatliche Rate eines Darlehens.
# Ist das Tilgung-Feld leer, wird die Tilgung automatisch so berechnet, dass
# das Darlehen am Ende der Laufzeit exakt auf 0 EUR steht. (klassischer
# Verbraucherkredit / Annuitätendarlehen). Ist eine Tilgung angegeben, wird
# die monatliche Rate aus Zinssatz + Tilgungssatz gebildet (klassisches
# Annuitätendarlehen mit Zinsbindung). Reicht die Laufzeit nicht für die
# Volltilgung, wird die verbleibende Restschuld ausgewiesen.
# Benötigte Pakete: pip install PyQt6 matplotlib ; pip install openpyxl
# optional, für Excel-Export. (c) Hans Busche Datum: 04.09.2026
# *****************************************************************************
import sys, os
import csv
from PyQt6.QtWidgets import (
QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
QFormLayout, QGroupBox, QLineEdit, QLabel, QPushButton,
QTableWidget, QTableWidgetItem, QHeaderView, QSplitter, QFrame,
QComboBox, QFileDialog, QMessageBox,
)
from PyQt6.QtCore import Qt
from PyQt6.QtGui import QDoubleValidator, QIcon
import matplotlib
matplotlib.use("QtAgg")
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg
from matplotlib.figure import Figure
try:
from openpyxl import Workbook
from openpyxl.styles import Font, PatternFill, Alignment
OPENPYXL_AVAILABLE = True
except ImportError:
OPENPYXL_AVAILABLE = False
# Verzeichnis dieser Datei, damit das Icon unabhängig vom Startort gefunden wird
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
ICON_PATH = os.path.join(BASE_DIR, "Schaefer.ico")
# *****************************************************************************
# Farben / Dark-Theme
# *****************************************************************************
BG_MAIN = "#1e1f26"
BG_PANEL = "#262832"
BG_INPUT = "#2f313d"
BORDER = "#3c3f4c"
ACCENT = "#5b8def"
ACCENT_HOVER = "#75a1f5"
TEXT_MAIN = "#e6e6ea"
TEXT_MUTED = "#9a9cab"
COLOR_OK = "#4caf7d"
COLOR_ERROR = "#e0645f"
COLOR_WARN = "#e0a85f"
DARK_STYLESHEET = f"""
QMainWindow {{
background-color: {BG_MAIN};
}}
QWidget {{
background-color: {BG_MAIN};
color: {TEXT_MAIN};
font-family: "Segoe UI", Arial, sans-serif;
font-size: 13px;
}}
QGroupBox {{
background-color: {BG_PANEL};
border: 1px solid {BORDER};
border-radius: 8px;
margin-top: 14px;
padding: 14px 10px 10px 10px;
font-weight: 600;
color: {TEXT_MAIN};
}}
QGroupBox::title {{
subcontrol-origin: margin;
left: 12px;
padding: 0 6px;
color: {ACCENT};
}}
QLabel {{
background: transparent;
color: {TEXT_MAIN};
}}
QLabel[role="fieldLabel"] {{
color: {TEXT_MUTED};
}}
QLabel[role="outputValue"] {{
font-size: 16px;
font-weight: 700;
color: {ACCENT};
}}
QLabel[role="status"] {{
font-weight: 600;
}}
QLineEdit {{
background-color: {BG_INPUT};
border: 1px solid {BORDER};
border-radius: 6px;
padding: 6px 8px;
color: {TEXT_MAIN};
selection-background-color: {ACCENT};
}}
QLineEdit:focus {{
border: 1px solid {ACCENT};
}}
QComboBox {{
background-color: {BG_INPUT};
border: 1px solid {BORDER};
border-radius: 6px;
padding: 6px 8px;
color: {TEXT_MAIN};
}}
QComboBox:focus {{
border: 1px solid {ACCENT};
}}
QComboBox::drop-down {{
border: none;
width: 22px;
}}
QComboBox QAbstractItemView {{
background-color: {BG_INPUT};
border: 1px solid {BORDER};
selection-background-color: {ACCENT};
selection-color: #12131a;
color: {TEXT_MAIN};
outline: none;
}}
QPushButton {{
background-color: {ACCENT};
color: #12131a;
border: none;
border-radius: 6px;
padding: 9px 18px;
font-weight: 700;
}}
QPushButton[role="secondary"] {{
background-color: {BG_INPUT};
color: {TEXT_MAIN};
border: 1px solid {BORDER};
}}
QPushButton[role="secondary"]:hover {{
background-color: {BORDER};
}}
QPushButton:hover {{
background-color: {ACCENT_HOVER};
}}
QPushButton:pressed {{
background-color: #4472c4;
}}
QTableWidget {{
background-color: {BG_INPUT};
alternate-background-color: #35374296;
gridline-color: {BORDER};
border: 1px solid {BORDER};
border-radius: 6px;
color: {TEXT_MAIN};
}}
QHeaderView::section {{
background-color: {BG_PANEL};
color: {TEXT_MUTED};
padding: 6px;
border: none;
border-bottom: 1px solid {BORDER};
font-weight: 600;
}}
QTableWidget::item:selected {{
background-color: {ACCENT};
color: #12131a;
}}
QScrollBar:vertical {{
background: {BG_PANEL};
width: 12px;
margin: 0;
}}
QScrollBar::handle:vertical {{
background: {BORDER};
min-height: 20px;
border-radius: 6px;
}}
QScrollBar::add-line:vertical, QScrollBar::sub-line:vertical {{
height: 0;
}}
QFrame[role="divider"] {{
background-color: {BORDER};
max-height: 1px;
min-height: 1px;
}}
"""
# *****************************************************************************
# Wandelt Text (mit Komma oder Punkt) in float um. None bei leerem Feld.
# *****************************************************************************
def parse_float(text: str):
text = text.strip().replace(".", "").replace(",", ".") if "," in text else text.strip()
if text == "":
return None
return float(text)
# *****************************************************************************
# Hauptfenster
# *****************************************************************************
class LoanCalculator(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Darlehensrechner")
self.resize(1180, 760)
# Icon
if os.path.exists(ICON_PATH):
self.setWindowIcon(QIcon(ICON_PATH))
self.setStyleSheet(DARK_STYLESHEET)
self._last_rows = None
self._last_meta = None
central = QWidget()
self.setCentralWidget(central)
root = QVBoxLayout(central)
root.setContentsMargins(16, 16, 16, 16)
root.setSpacing(12)
root.addWidget(self._build_input_group())
root.addWidget(self._build_output_group())
splitter = QSplitter(Qt.Orientation.Horizontal)
splitter.addWidget(self._build_table_group())
splitter.addWidget(self._build_chart_group())
splitter.setStretchFactor(0, 1)
splitter.setStretchFactor(1, 1)
root.addWidget(splitter, stretch=1)
# *****************************************************************************
# UI-Aufbau
# *****************************************************************************
def _build_input_group(self) -> QGroupBox:
box = QGroupBox("Darlehensdaten")
layout = QHBoxLayout()
layout.setSpacing(20)
box.setLayout(layout)
validator = QDoubleValidator(0.0, 999999999.0, 2)
validator.setNotation(QDoubleValidator.Notation.StandardNotation)
def make_field(label_text, placeholder=""):
v_layout = QVBoxLayout()
lbl = QLabel(label_text)
lbl.setProperty("role", "fieldLabel")
edit = QLineEdit()
edit.setPlaceholderText(placeholder)
v_layout.addWidget(lbl)
v_layout.addWidget(edit)
return v_layout, edit
l1, self.input_darlehen = make_field("Darlehen (EUR)", "z. B. 250000")
l3, self.input_zins = make_field("Zinssatz (% p. a.)", "z. B. 3,5")
l4, self.input_tilgung = make_field("Tilgung (% p. a.) – optional", "leer = Verbraucherkredit")
# Laufzeit-Feld mit Einheiten-Umschaltung (Jahre / Monate)
l2 = QVBoxLayout()
laufzeit_lbl = QLabel("Laufzeit")
laufzeit_lbl.setProperty("role", "fieldLabel")
laufzeit_row = QHBoxLayout()
self.input_laufzeit = QLineEdit()
self.input_laufzeit.setPlaceholderText("z. B. 20")
self.laufzeit_unit = QComboBox()
self.laufzeit_unit.addItems(["Jahre", "Monate"])
self.laufzeit_unit.currentTextChanged.connect(self._on_laufzeit_unit_changed)
laufzeit_row.addWidget(self.input_laufzeit, stretch=2)
laufzeit_row.addWidget(self.laufzeit_unit, stretch=1)
l2.addWidget(laufzeit_lbl)
l2.addLayout(laufzeit_row)
for l in (l1, l2, l3, l4):
layout.addLayout(l)
btn_layout = QVBoxLayout()
btn_layout.addWidget(QLabel("")) # Ausrichtung zur Feldhöhe
self.btn_calc = QPushButton("Berechnen")
self.btn_calc.clicked.connect(self.calculate)
btn_layout.addWidget(self.btn_calc)
layout.addLayout(btn_layout)
return box
def _on_laufzeit_unit_changed(self, unit_text: str):
if unit_text == "Jahre":
self.input_laufzeit.setPlaceholderText("z. B. 20")
else:
self.input_laufzeit.setPlaceholderText("z. B. 240")
def _build_output_group(self) -> QGroupBox:
box = QGroupBox("Ergebnis")
layout = QHBoxLayout()
layout.setSpacing(30)
box.setLayout(layout)
def make_output(title):
v = QVBoxLayout()
lbl_title = QLabel(title)
lbl_title.setProperty("role", "fieldLabel")
lbl_value = QLabel("–")
lbl_value.setProperty("role", "outputValue")
v.addWidget(lbl_title)
v.addWidget(lbl_value)
return v, lbl_value
v1, self.out_rate = make_output("Monatliche Zahlung")
v2, self.out_gesamt = make_output("Gesamt bezahlt")
v3, self.out_zinsen = make_output("Bezahlte Zinsen")
v4, self.out_restschuld = make_output("Restschuld nach Laufzeit")
for v in (v1, v2, v3, v4):
layout.addLayout(v)
layout.addStretch(1)
status_layout = QVBoxLayout()
status_title = QLabel("Status")
status_title.setProperty("role", "fieldLabel")
self.out_status = QLabel("Bereit.")
self.out_status.setProperty("role", "status")
self.out_status.setStyleSheet(f"color: {TEXT_MUTED};")
self.out_status.setWordWrap(True)
status_layout.addWidget(status_title)
status_layout.addWidget(self.out_status)
layout.addLayout(status_layout, stretch=1)
return box
def _build_table_group(self) -> QGroupBox:
box = QGroupBox("Tilgungsplan")
layout = QVBoxLayout()
box.setLayout(layout)
self.table = QTableWidget(0, 5)
self.table.setHorizontalHeaderLabels(
["Monat", "Rate (EUR)", "Zinsanteil (EUR)", "Tilgungsanteil (EUR)", "Restschuld (EUR)"]
)
self.table.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
self.table.verticalHeader().setVisible(False)
self.table.setAlternatingRowColors(True)
self.table.setEditTriggers(QTableWidget.EditTrigger.NoEditTriggers)
layout.addWidget(self.table)
export_row = QHBoxLayout()
self.btn_export_csv = QPushButton("Als CSV exportieren")
self.btn_export_csv.setProperty("role", "secondary")
self.btn_export_csv.clicked.connect(self.export_csv)
self.btn_export_excel = QPushButton("Als Excel exportieren")
self.btn_export_excel.setProperty("role", "secondary")
self.btn_export_excel.clicked.connect(self.export_excel)
if not OPENPYXL_AVAILABLE:
self.btn_export_excel.setToolTip("Benötigt 'openpyxl' (pip install openpyxl)")
export_row.addWidget(self.btn_export_csv)
export_row.addWidget(self.btn_export_excel)
export_row.addStretch(1)
layout.addLayout(export_row)
return box
def _build_chart_group(self) -> QGroupBox:
box = QGroupBox("Saldo- und Rückzahlungsverlauf")
layout = QVBoxLayout()
box.setLayout(layout)
self.figure = Figure(figsize=(5, 4), facecolor=BG_PANEL)
self.canvas = FigureCanvasQTAgg(self.figure)
layout.addWidget(self.canvas)
self._draw_empty_chart()
return box
def _style_axes(self, ax):
ax.set_facecolor(BG_INPUT)
ax.tick_params(colors=TEXT_MUTED)
for spine in ax.spines.values():
spine.set_color(BORDER)
ax.xaxis.label.set_color(TEXT_MUTED)
ax.yaxis.label.set_color(TEXT_MUTED)
ax.title.set_color(TEXT_MAIN)
ax.grid(True, color=BORDER, linewidth=0.6, alpha=0.5)
def _draw_empty_chart(self):
self.figure.clear()
ax = self.figure.add_subplot(111)
self._style_axes(ax)
ax.set_title("Noch keine Berechnung")
ax.set_xlabel("Monat")
ax.set_ylabel("EUR")
self.figure.tight_layout()
self.canvas.draw()
# *****************************************************************************
# Berechnung
# *****************************************************************************
def _set_status(self, text: str, kind: str = "info"):
color = {"info": TEXT_MUTED, "ok": COLOR_OK, "error": COLOR_ERROR, "warn": COLOR_WARN}[kind]
self.out_status.setStyleSheet(f"color: {color};")
self.out_status.setText(text)
def _reset_outputs(self):
for lbl in (self.out_rate, self.out_gesamt, self.out_zinsen, self.out_restschuld):
lbl.setText("–")
self.table.setRowCount(0)
self._draw_empty_chart()
self._last_rows = None
self._last_meta = None
def calculate(self):
self._reset_outputs()
# ---------- Eingaben einlesen & validieren ----------
try:
darlehen = parse_float(self.input_darlehen.text())
laufzeit_wert = parse_float(self.input_laufzeit.text())
zins_prozent = parse_float(self.input_zins.text())
tilgung_prozent = parse_float(self.input_tilgung.text())
except ValueError:
self._set_status("Bitte gültige Zahlen eingeben (z. B. 3,5 oder 3.5).", "error")
return
laufzeit_einheit = self.laufzeit_unit.currentText() # "Jahre" oder "Monate"
if darlehen is None or darlehen <= 0:
self._set_status("Bitte ein Darlehen größer 0 EUR eingeben.", "error")
return
if laufzeit_wert is None or laufzeit_wert <= 0:
self._set_status(f"Bitte eine Laufzeit größer 0 {laufzeit_einheit} eingeben.", "error")
return
if zins_prozent is None or zins_prozent < 0:
self._set_status("Bitte einen gültigen Zinssatz (>= 0 %) eingeben.", "error")
return
if tilgung_prozent is not None and tilgung_prozent < 0:
self._set_status("Die Tilgung darf nicht negativ sein.", "error")
return
if laufzeit_einheit == "Jahre":
n_monate = int(round(laufzeit_wert * 12))
else:
n_monate = int(round(laufzeit_wert))
if n_monate <= 0:
self._set_status("Die Laufzeit muss mindestens 1 Monat betragen.", "error")
return
zins_monat = zins_prozent / 100 / 12
# ---------- Monatsrate ermitteln ----------
auto_tilgung = tilgung_prozent is None
if auto_tilgung:
# Klassischer Verbraucherkredit: Rate so, dass am Laufzeitende exakt 0 EUR
if zins_monat > 0:
rate = darlehen * zins_monat / (1 - (1 + zins_monat) ** (-n_monate))
else:
rate = darlehen / n_monate
planungs_monate = n_monate
else:
# Annuitätendarlehen mit vorgegebenem Tilgungssatz
rate = darlehen * (zins_prozent / 100 + tilgung_prozent / 100) / 12
if rate <= darlehen * zins_monat and zins_monat > 0:
self._set_status(
"Die Rate aus Zins + Tilgung deckt nicht einmal die Zinsen. "
"Bitte Tilgungssatz erhöhen.", "error"
)
return
planungs_monate = n_monate # Tabelle zeigt max. die eingegebene Laufzeit
# ---------- Tilgungsplan berechnen ----------
rows = [] # (monat, rate, zins, tilgung, restschuld)
saldo = darlehen
gesamt_gezahlt = 0.0
gesamt_zinsen = 0.0
monat = 0
vollstaendig_getilgt = False
while monat < planungs_monate and saldo > 0.01:
monat += 1
zins_anteil = saldo * zins_monat
tilgung_anteil = rate - zins_anteil
# Letzte Rate ggf. kappen, falls Restschuld kleiner als Tilgungsanteil
if tilgung_anteil >= saldo:
tilgung_anteil = saldo
akt_rate = zins_anteil + tilgung_anteil
saldo = 0.0
vollstaendig_getilgt = True
else:
akt_rate = rate
saldo -= tilgung_anteil
gesamt_gezahlt += akt_rate
gesamt_zinsen += zins_anteil
rows.append((monat, akt_rate, zins_anteil, tilgung_anteil, saldo))
if auto_tilgung:
vollstaendig_getilgt = True # per Konstruktion exakt 0
restschuld_final = rows[-1][4] if rows else darlehen
# ---------- Tabelle füllen ----------
self.table.setRowCount(len(rows))
for i, (m, r_, z_, t_, s_) in enumerate(rows):
values = [
str(m),
f"{r_:,.2f}",
f"{z_:,.2f}",
f"{t_:,.2f}",
f"{s_:,.2f}",
]
for col, val in enumerate(values):
item = QTableWidgetItem(val)
if col > 0:
item.setTextAlignment(Qt.AlignmentFlag.AlignRight | Qt.AlignmentFlag.AlignVCenter)
self.table.setItem(i, col, item)
# ---------- Ausgabefelder ----------
self.out_rate.setText(f"{rate:,.2f} EUR")
self.out_gesamt.setText(f"{gesamt_gezahlt:,.2f} EUR")
self.out_zinsen.setText(f"{gesamt_zinsen:,.2f} EUR")
if vollstaendig_getilgt:
self.out_restschuld.setText("0,00 EUR")
else:
self.out_restschuld.setText(f"{restschuld_final:,.2f} EUR")
if vollstaendig_getilgt:
self._set_status(
f"Berechnung erfolgreich. Das Darlehen ist nach {len(rows)} Monaten vollständig getilgt.",
"ok",
)
else:
self._set_status(
"Berechnung erfolgreich. Mit dem gewählten Tilgungssatz ist das Darlehen "
"innerhalb der Laufzeit nicht vollständig getilgt (siehe Restschuld).",
"warn",
)
# ---------- Chart zeichnen ----------
self._draw_chart(darlehen, rows)
# ---------- Für Export zwischenspeichern ----------
self._last_rows = rows
self._last_meta = {
"darlehen": darlehen,
"laufzeit_wert": laufzeit_wert,
"laufzeit_einheit": laufzeit_einheit,
"zins_prozent": zins_prozent,
"tilgung_prozent": tilgung_prozent,
"auto_tilgung": auto_tilgung,
"rate": rate,
"gesamt_gezahlt": gesamt_gezahlt,
"gesamt_zinsen": gesamt_zinsen,
"restschuld": 0.0 if vollstaendig_getilgt else restschuld_final,
"vollstaendig_getilgt": vollstaendig_getilgt,
}
def _draw_chart(self, darlehen: float, rows):
self.figure.clear()
ax = self.figure.add_subplot(111)
self._style_axes(ax)
monate = [r[0] for r in rows]
restschuld = [r[4] for r in rows]
kumuliert_gezahlt = []
laufsumme = 0.0
for r in rows:
laufsumme += r[1]
kumuliert_gezahlt.append(laufsumme)
ax.plot(monate, restschuld, color=ACCENT, linewidth=2.2, label="Kredit-Saldo (Restschuld)")
ax.plot(monate, kumuliert_gezahlt, color=COLOR_WARN, linewidth=2.0, linestyle="--",
label="Gesamtrückzahlung (kumuliert)")
ax.axhline(darlehen, color=TEXT_MUTED, linewidth=1.0, linestyle=":", label="Ursprüngliches Darlehen")
ax.set_xlabel("Monat")
ax.set_ylabel("EUR")
ax.set_title("Darlehen: Saldo- und Rückzahlungsverlauf")
legend = ax.legend(facecolor=BG_INPUT, edgecolor=BORDER, labelcolor=TEXT_MAIN, fontsize=9)
legend.get_frame().set_alpha(0.9)
self.figure.tight_layout()
self.canvas.draw()
# *****************************************************************************
# Export
# *****************************************************************************
def _summary_lines(self) -> list:
m = self._last_meta
art = "automatisch berechnet" if m["auto_tilgung"] else f"{m['tilgung_prozent']:.2f} %"
return [
("Darlehen (EUR)", f"{m['darlehen']:.2f}"),
("Laufzeit", f"{m['laufzeit_wert']:g} {m['laufzeit_einheit']}"),
("Zinssatz (% p. a.)", f"{m['zins_prozent']:.2f}"),
("Tilgung (% p. a.)", art),
("Monatliche Rate (EUR)", f"{m['rate']:.2f}"),
("Gesamt bezahlt (EUR)", f"{m['gesamt_gezahlt']:.2f}"),
("Bezahlte Zinsen (EUR)", f"{m['gesamt_zinsen']:.2f}"),
("Restschuld nach Laufzeit (EUR)", f"{m['restschuld']:.2f}"),
]
def export_csv(self):
if not self._last_rows:
self._set_status("Bitte zuerst eine Berechnung durchführen.", "error")
return
path, _ = QFileDialog.getSaveFileName(
self, "Tilgungsplan als CSV speichern", "tilgungsplan.csv", "CSV-Dateien (*.csv)"
)
if not path:
return
try:
with open(path, "w", newline="", encoding="utf-8-sig") as f:
writer = csv.writer(f, delimiter=";")
writer.writerow(["Darlehensrechner – Zusammenfassung"])
for label, value in self._summary_lines():
writer.writerow([label, value])
writer.writerow([])
writer.writerow(
["Monat", "Rate (EUR)", "Zinsanteil (EUR)", "Tilgungsanteil (EUR)", "Restschuld (EUR)"]
)
for m_, r_, z_, t_, s_ in self._last_rows:
writer.writerow([m_, f"{r_:.2f}", f"{z_:.2f}", f"{t_:.2f}", f"{s_:.2f}"])
self._set_status(f"CSV erfolgreich gespeichert: {path}", "ok")
except OSError as exc:
self._set_status(f"CSV konnte nicht gespeichert werden: {exc}", "error")
def export_excel(self):
if not self._last_rows:
self._set_status("Bitte zuerst eine Berechnung durchführen.", "error")
return
if not OPENPYXL_AVAILABLE:
QMessageBox.warning(
self, "openpyxl fehlt",
"Für den Excel-Export wird das Paket 'openpyxl' benötigt.\n"
"Bitte installieren mit: pip install openpyxl",
)
return
path, _ = QFileDialog.getSaveFileName(
self, "Tilgungsplan als Excel speichern", "tilgungsplan.xlsx", "Excel-Dateien (*.xlsx)"
)
if not path:
return
try:
wb = Workbook()
ws_summary = wb.active
ws_summary.title = "Zusammenfassung"
header_font = Font(bold=True, color="FFFFFF")
header_fill = PatternFill(start_color="5B8DEF", end_color="5B8DEF", fill_type="solid")
ws_summary.append(["Darlehensrechner – Zusammenfassung", ""])
ws_summary["A1"].font = Font(bold=True, size=13)
ws_summary.append([])
for label, value in self._summary_lines():
ws_summary.append([label, value])
ws_summary.column_dimensions["A"].width = 30
ws_summary.column_dimensions["B"].width = 20
ws_plan = wb.create_sheet("Tilgungsplan")
headers = ["Monat", "Rate (EUR)", "Zinsanteil (EUR)", "Tilgungsanteil (EUR)", "Restschuld (EUR)"]
ws_plan.append(headers)
for col in range(1, len(headers) + 1):
cell = ws_plan.cell(row=1, column=col)
cell.font = header_font
cell.fill = header_fill
cell.alignment = Alignment(horizontal="center")
for m_, r_, z_, t_, s_ in self._last_rows:
ws_plan.append([m_, round(r_, 2), round(z_, 2), round(t_, 2), round(s_, 2)])
for i, width in enumerate([8, 14, 16, 18, 16], start=1):
ws_plan.column_dimensions[ws_plan.cell(row=1, column=i).column_letter].width = width
wb.save(path)
self._set_status(f"Excel-Datei erfolgreich gespeichert: {path}", "ok")
except OSError as exc:
self._set_status(f"Excel-Datei konnte nicht gespeichert werden: {exc}", "error")
def main():
app = QApplication(sys.argv)
app.setStyle("Fusion")
window = LoanCalculator()
window.show()
sys.exit(app.exec())
if __name__ == "__main__":
main()
# *****************************************************************************
# Ende
# *****************************************************************************
Dark Mode: Abgedunkelte Oberflächen mit farbigen Akzenten (Blau, Rot, Grün
für den Graphen).
Dynamische Berechnung: Wenn Tilgung leer ist,
berechnet Python mittels Annuitätenformel die exakte monatliche Rate, sodass am Ende genau Null €
Restschuld bleiben. wenn Tilgung ausgefüllt ist, wird die monatliche Rate
basierend auf Zins + Tilgung angesetzt und der Tilgungsplan bis zum Erreichen der Restschuld von Null €
abgetragen.
Ergebnisse & Validierung: Auswertung von Rate, Gesamtbetrag
und Zinsen inklusive Fehlerdialogen bei ungültigen Eingaben.
Tabelle & Graph:
Der Verlauf zeigt übersichtlich die kumulierte Rückzahlung sowie der monatlich sinkende Saldo.
Das Eingabefeld „Tilgung“ ist ausgefüllt! Diese Rate bleibt fix, der Zinsanteil
sinkt, der Tilgungsanteil steigt automatisch. Ist die Laufzeit kürzer als die tatsächliche Volltilgungsdauer,
gibt es eine Restschuld – genau wie bei einem echten Bankdarlehen mit Zinsbindung.
Wie wird ein Darlehen, mit vorgegebener Tilgung, berechnet? Das obige Bild zeigt die mathematischen
Zusammenhänge.
kapital = 25000
zinsen = 0.036
tilgen = 0.02
monatliche_rate = (kapital * (zinsen + tilgen)) / 12
aktueller_kontostand = kapital
for m in range(1, 344):
der_zins = aktueller_kontostand * (zinsen / 12)
getilgt = monatliche_rate - der_zins
aktueller_kontostand -= getilgt
if m == 120:
print(f"Nach 120 Monaten (10 Jahre): Restschuld = {aktueller_kontostand:.2f}")
if m == 240:
print(f"Nach 240 Monaten (20 Jahre): Restschuld = {aktueller_kontostand:.2f}")
if m == 343:
print(f"Nach 343 Monaten (28 Jahre): Restschuld = {aktueller_kontostand:.2f}")
print("Das Programm ist fertig.")
# Verhindert das automatische Schließen
input("\nDrücke Enter, um das Programm zu beenden...")
Eine Python Konsolen Anwendung für ein Darlehen. Hier kannst du schnell etwas ausprobieren.
p = 5000
zinsen = 0.034
n_monate = 24
i_monate = zinsen / 12
mon_rate = p * (i_monate * (1 + i_monate)**n_monate) / ((1 + i_monate)**n_monate - 1)
print(f"Monatliche Rate: {mon_rate:.2f}")
restschuld = p
gesamtzinsen = 0
for m in range(1, n_monate + 1):
zinszahlung = restschuld * i_monate
rueckzahlung_kapital = mon_rate - zinszahlung
restschuld -= rueckzahlung_kapital
gesamtzinsen += zinszahlung
print(f"Gesamtzinsen: {gesamtzinsen:.2f}")
print(f"Gesamtzahlung: {p + gesamtzinsen:.2f}")
print("Das Programm ist fertig.")
# Verhindert das automatische Schließen
input("\nDrücke Enter, um das Programm zu beenden...")
Eine Python Konsolen Anwendung für ein Verbraucherkredit. Hier kannst du schnell etwas ausprobieren.
PyQt6. Geld. Ohne Gewähr © 2026 Hans Busche