initial commit

This commit is contained in:
2025-08-10 15:18:12 +02:00
commit 262ba13e7a
6 changed files with 814 additions and 0 deletions

19
environment.yaml Normal file
View File

@@ -0,0 +1,19 @@
name: aktien_analyse
channels:
- conda-forge
- defaults
dependencies:
- python=3.11
- pandas
- numpy
- yfinance
- sqlalchemy # Für eine etwas höhere Abstraktion bei der DB-Interaktion, optional aber nützlich
- openpyxl # Nützlich, falls CSVs auch mal in Excel geöffnet/gespeichert werden
- pip
- pip:
- matplotlib
- pyqt6
# Hier könnten spezifische Pakete, die nicht direkt auf conda-forge sind, per pip installiert werden.
# Für dieses Projekt sollte alles über conda-forge verfügbar sein.

131
financial_tools.py Normal file
View File

@@ -0,0 +1,131 @@
import pandas as pd
import numpy as np
class FinancialTools:
@staticmethod
def calculate_returns(prices_series, in_percent=True): # <-- NEUER PARAMETER HIER!
"""Berechnet die täglichen Renditen."""
if prices_series.empty:
return pd.Series(dtype='float64')
if isinstance(prices_series, pd.DataFrame):
if 'adj_close' in prices_series.columns:
prices_series = prices_series['adj_close']
elif 'close' in prices_series.columns:
prices_series = prices_series['close']
else:
return pd.Series(dtype='float64')
returns = prices_series.pct_change().dropna()
if in_percent:
return returns * 100 # Für die Anzeige in Prozent
return returns # Für interne Berechnungen (Dezimalwerte)
@staticmethod
def calculate_cumulative_returns(prices_series):
"""Berechnet die kumulativen Renditen."""
if prices_series.empty:
return pd.Series(dtype='float64')
# HIER: Rufe calculate_returns mit in_percent=False auf, um Dezimalwerte zu bekommen
daily_returns_raw = FinancialTools.calculate_returns(prices_series, in_percent=False)
if daily_returns_raw.empty:
return pd.Series(dtype='float64')
return (1 + daily_returns_raw).cumprod() - 1
@staticmethod
def calculate_moving_average(prices_series, window=20):
"""Berechnet den gleitenden Durchschnitt."""
if prices_series.empty:
return pd.Series(dtype='float64')
if isinstance(prices_series, pd.DataFrame):
if 'adj_close' in prices_series.columns:
prices_series = prices_series['adj_close']
elif 'close' in prices_series.columns:
prices_series = prices_series['close']
else:
return pd.Series(dtype='float64')
return prices_series.rolling(window=window).mean()
@staticmethod
def calculate_volatility(prices_series, window=20):
"""Berechnet die rollierende Volatilität (Standardabweichung der Renditen)."""
if prices_series.empty:
return pd.Series(dtype='float64')
# HIER: Rufe calculate_returns mit in_percent=False auf, um Dezimalwerte zu bekommen
daily_returns_for_vol = FinancialTools.calculate_returns(prices_series, in_percent=False)
if daily_returns_for_vol.empty:
return pd.Series(dtype='float64')
# Annualisiere die Volatilität
return daily_returns_for_vol.rolling(window=window).std() * np.sqrt(252)
@staticmethod
def calculate_beta(stock_prices, market_prices, window=60):
"""
Berechnet das rollierende Beta eines Wertpapiers relativ zu einem Marktindex.
stock_prices: Pandas Series der Aktie
market_prices: Pandas Series des Marktindex
"""
if stock_prices.empty or market_prices.empty:
return pd.Series(dtype='float64')
# Sicherstellen, dass es Series sind, falls doch DataFrames übergeben werden
if isinstance(stock_prices, pd.DataFrame):
if 'adj_close' in stock_prices.columns:
stock_prices = stock_prices['adj_close']
elif 'close' in stock_prices.columns:
stock_prices = stock_prices['close']
else:
return pd.Series(dtype='float64')
if isinstance(market_prices, pd.DataFrame):
if 'adj_close' in market_prices.columns:
market_prices = market_prices['adj_close']
elif 'close' in market_prices.columns:
market_prices = market_prices['close']
else:
return pd.Series(dtype='float64')
# HIER: Rufe calculate_returns mit in_percent=False auf, um Dezimalwerte zu bekommen
returns_stock = FinancialTools.calculate_returns(stock_prices, in_percent=False)
returns_market = FinancialTools.calculate_returns(market_prices, in_percent=False)
# Kombiniere die Renditen und richte sie an den Daten aus
combined_returns = pd.concat([returns_stock.rename('stock'), returns_market.rename('market')], axis=1).dropna()
if combined_returns.empty:
return pd.Series(dtype='float64')
# Berechne die rollierende Kovarianz und Varianz
rolling_covariance = combined_returns['stock'].rolling(window=window).cov(combined_returns['market'])
rolling_variance = combined_returns['market'].rolling(window=window).var()
# Berechne Beta
beta = rolling_covariance / rolling_variance
return beta.dropna()
# Beispielnutzung (für Tests) - Muss an die neuen Funktionssignaturen angepasst werden
if __name__ == "__main__":
# Erzeuge fiktive Daten für Tests
dates = pd.to_datetime(pd.date_range(start='2023-01-01', periods=100))
stock_prices_series = pd.Series(np.random.rand(100) * 100 + 50, index=dates) # direkt als Series
market_prices_series = pd.Series(np.random.rand(100) * 10 + 200, index=dates) # direkt als Series
print("Tägliche Renditen (in Prozent):")
print(FinancialTools.calculate_returns(stock_prices_series, in_percent=True).head()) # Für Anzeige
print("\nKumulative Renditen (Dezimal):")
print(FinancialTools.calculate_cumulative_returns(stock_prices_series).head())
print("\nGleitender 20-Tage-Durchschnitt:")
print(FinancialTools.calculate_moving_average(stock_prices_series, window=20).head())
print("\nRollierende 20-Tage-Volatilität (annualisiert, Dezimal):")
print(FinancialTools.calculate_volatility(stock_prices_series, window=20).head())
print("\nRollierendes 60-Tage-Beta (Aktie vs. Markt, Dezimal):")
print(FinancialTools.calculate_beta(stock_prices_series, market_prices_series, window=60).head())

459
main_gui.py Normal file
View File

@@ -0,0 +1,459 @@
import sys
from PyQt6.QtWidgets import (QApplication, QMainWindow, QTabWidget, QWidget,
QVBoxLayout, QHBoxLayout, QPushButton, QLabel,
QComboBox, QLineEdit, QListWidget, QListWidgetItem,
QMessageBox, QDateEdit, QSizePolicy)
from PyQt6.QtCore import QDate, Qt
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg as FigureCanvas
from matplotlib.figure import Figure
import pandas as pd
import datetime
from stock_data_manager import StockDataManager
from financial_tools import FinancialTools
class MplCanvas(FigureCanvas):
"""Matplotlib Canvas für die Einbettung in PyQt."""
def __init__(self, parent=None, width=5, height=4, dpi=100):
fig = Figure(figsize=(width, height), dpi=dpi)
self.axes = fig.add_subplot(111)
super(MplCanvas, self).__init__(fig)
self.setParent(parent)
self.setSizePolicy(QSizePolicy.Policy.Expanding, QSizePolicy.Policy.Expanding)
self.updateGeometry()
class StockAnalyzer(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Aktienanalyse-Tool")
self.setGeometry(100, 100, 1200, 800)
self.db_manager = StockDataManager("stock_analysis.db")
self.financial_tools = FinancialTools()
self.central_widget = QWidget()
self.setCentralWidget(self.central_widget)
self.main_layout = QVBoxLayout(self.central_widget)
self._create_top_panel()
self._create_tabs()
self._load_initial_data()
def _create_top_panel(self):
"""Erstellt das obere Panel für CSV-Import und Symbol-Auswahl."""
top_panel_layout = QHBoxLayout()
# CSV Import
csv_group_layout = QVBoxLayout()
csv_group_layout.addWidget(QLabel("CSV-Datei mit Symbolen importieren (Symbol,CompanyName):"))
self.csv_path_input = QLineEdit("stocks.csv")
csv_group_layout.addWidget(self.csv_path_input)
import_csv_button = QPushButton("CSV importieren & Daten holen")
import_csv_button.clicked.connect(self._import_csv_and_fetch)
csv_group_layout.addWidget(import_csv_button)
top_panel_layout.addLayout(csv_group_layout)
top_panel_layout.addStretch(1) # Abstandhalter
# Symbol Auswahl
symbol_selection_layout = QVBoxLayout()
symbol_selection_layout.addWidget(QLabel("Aktie auswählen:"))
self.symbol_combo = QComboBox()
self.symbol_combo.currentIndexChanged.connect(self._on_symbol_selected)
symbol_selection_layout.addWidget(self.symbol_combo)
# Datumsbereich
date_range_layout = QHBoxLayout()
date_range_layout.addWidget(QLabel("Von:"))
self.start_date_edit = QDateEdit(QDate.currentDate().addYears(-1))
self.start_date_edit.setCalendarPopup(True)
date_range_layout.addWidget(self.start_date_edit)
date_range_layout.addWidget(QLabel("Bis:"))
self.end_date_edit = QDateEdit(QDate.currentDate())
self.end_date_edit.setCalendarPopup(True)
date_range_layout.addWidget(self.end_date_edit)
symbol_selection_layout.addLayout(date_range_layout)
# Refresh Button
refresh_data_button = QPushButton("Daten aktualisieren")
refresh_data_button.clicked.connect(self._refresh_selected_stock_data)
symbol_selection_layout.addWidget(refresh_data_button)
top_panel_layout.addLayout(symbol_selection_layout)
self.main_layout.addLayout(top_panel_layout)
def _create_tabs(self):
"""Erstellt die Reiter für verschiedene Ansichten."""
self.tabs = QTabWidget()
self.main_layout.addWidget(self.tabs)
self.tab_overview = QWidget()
self.tabs.addTab(self.tab_overview, "Übersicht & Kurse")
self._setup_overview_tab()
self.tab_returns = QWidget()
self.tabs.addTab(self.tab_returns, "Renditen")
self._setup_returns_tab()
self.tab_ma = QWidget()
self.tabs.addTab(self.tab_ma, "Gleitender Durchschnitt")
self._setup_ma_tab()
self.tab_volatility = QWidget()
self.tabs.addTab(self.tab_volatility, "Volatilität")
self._setup_volatility_tab()
self.tab_beta = QWidget()
self.tabs.addTab(self.tab_beta, "Beta (Marktabhängigkeit)")
self._setup_beta_tab()
def _setup_overview_tab(self):
layout = QVBoxLayout(self.tab_overview)
self.overview_canvas = MplCanvas(self.tab_overview, width=10, height=6)
layout.addWidget(self.overview_canvas)
self.overview_text = QLabel("Kursdaten:")
layout.addWidget(self.overview_text)
def _setup_returns_tab(self):
layout = QVBoxLayout(self.tab_returns)
self.returns_canvas = MplCanvas(self.tab_returns, width=10, height=6)
layout.addWidget(self.returns_canvas)
self.returns_text = QLabel("Renditen:")
layout.addWidget(self.returns_text)
def _setup_ma_tab(self):
layout = QVBoxLayout(self.tab_ma)
ma_controls_layout = QHBoxLayout()
ma_controls_layout.addWidget(QLabel("Fenster (Tage):"))
self.ma_window_input = QLineEdit("20")
self.ma_window_input.setFixedWidth(50)
ma_controls_layout.addWidget(self.ma_window_input)
self.ma_apply_button = QPushButton("Anwenden")
self.ma_apply_button.clicked.connect(self._plot_ma)
ma_controls_layout.addWidget(self.ma_apply_button)
ma_controls_layout.addStretch(1)
layout.addLayout(ma_controls_layout)
self.ma_canvas = MplCanvas(self.tab_ma, width=10, height=6)
layout.addWidget(self.ma_canvas)
self.ma_text = QLabel("Gleitender Durchschnitt:")
layout.addWidget(self.ma_text)
def _setup_volatility_tab(self):
layout = QVBoxLayout(self.tab_volatility)
vol_controls_layout = QHBoxLayout()
vol_controls_layout.addWidget(QLabel("Fenster (Tage):"))
self.vol_window_input = QLineEdit("20")
self.vol_window_input.setFixedWidth(50)
vol_controls_layout.addWidget(self.vol_window_input)
self.vol_apply_button = QPushButton("Anwenden")
self.vol_apply_button.clicked.connect(self._plot_volatility)
vol_controls_layout.addStretch(1)
layout.addLayout(vol_controls_layout)
self.vol_canvas = MplCanvas(self.tab_volatility, width=10, height=6)
layout.addWidget(self.vol_canvas)
self.vol_text = QLabel("Volatilität:")
layout.addWidget(self.vol_text)
def _setup_beta_tab(self):
layout = QVBoxLayout(self.tab_beta)
beta_controls_layout = QHBoxLayout()
beta_controls_layout.addWidget(QLabel("Markt-Symbol (z.B. SPY):"))
self.market_symbol_input = QLineEdit("SPY")
self.market_symbol_input.setFixedWidth(100)
beta_controls_layout.addWidget(self.market_symbol_input)
beta_controls_layout.addWidget(QLabel("Fenster (Tage):"))
self.beta_window_input = QLineEdit("60")
self.beta_window_input.setFixedWidth(50)
beta_controls_layout.addWidget(self.beta_window_input)
self.beta_apply_button = QPushButton("Anwenden")
self.beta_apply_button.clicked.connect(self._plot_beta)
beta_controls_layout.addStretch(1)
layout.addLayout(beta_controls_layout)
self.beta_canvas = MplCanvas(self.tab_beta, width=10, height=6)
layout.addWidget(self.beta_canvas)
self.beta_text = QLabel("Beta-Wert:")
layout.addWidget(self.beta_text)
def _load_initial_data(self):
"""Lädt initial alle Symbole in die ComboBox."""
symbols = self.db_manager.get_all_symbols()
if not symbols:
# Füge Standard-Symbole hinzu, wenn DB leer ist
default_symbols = ["AAPL", "MSFT", "GOOGL"]
for s in default_symbols:
self.db_manager.add_stock(s)
symbols = default_symbols # Aktualisiere die Liste
# Optional: Initialdaten für Standard-Symbole holen
# for s in symbols:
# self.db_manager.fetch_and_store_data(s, period="1y")
self.symbol_combo.clear()
self.symbol_combo.addItems(symbols)
if symbols:
self._on_symbol_selected(0) # Wähle das erste Symbol aus und zeige Daten
def _import_csv_and_fetch(self):
"""Importiert Symbole aus CSV und holt Daten."""
csv_file = self.csv_path_input.text()
try:
df_symbols = pd.read_csv(csv_file)
imported_count = 0
fetched_count = 0
for index, row in df_symbols.iterrows():
symbol = row['Symbol'].upper()
company_name = row.get('CompanyName', '')
if self.db_manager.add_stock(symbol, company_name):
imported_count += 1
if self.db_manager.fetch_and_store_data(symbol, period="max"): # Holt max. verfügbare Daten
fetched_count += 1
QMessageBox.information(self, "Import abgeschlossen",
f"{imported_count} neue Symbole hinzugefügt.\n{fetched_count} Symbole aktualisiert/Daten geholt.")
self._load_initial_data() # Symbole in ComboBox aktualisieren
except FileNotFoundError:
QMessageBox.warning(self, "Fehler", f"Datei '{csv_file}' nicht gefunden.")
except KeyError:
QMessageBox.warning(self, "Fehler", f"Die CSV-Datei muss Spalten 'Symbol' und optional 'CompanyName' enthalten.")
except Exception as e:
QMessageBox.critical(self, "Fehler", f"Ein unerwarteter Fehler ist aufgetreten: {e}")
def _refresh_selected_stock_data(self):
"""Holt aktuelle Daten für das gerade ausgewählte Symbol."""
selected_symbol = self.symbol_combo.currentText()
if selected_symbol:
# Holen der neuesten Daten (yfinance holt automatisch nur die fehlenden)
if self.db_manager.fetch_and_store_data(selected_symbol, period="max"):
QMessageBox.information(self, "Aktualisiert", f"Daten für {selected_symbol} wurden aktualisiert.")
self._on_symbol_selected(self.symbol_combo.currentIndex()) # Ansichten neu laden
else:
QMessageBox.warning(self, "Fehler", f"Konnte Daten für {selected_symbol} nicht aktualisieren.")
else:
QMessageBox.information(self, "Info", "Kein Symbol ausgewählt.")
def _get_current_stock_data(self):
"""Hilfsfunktion zum Abrufen der aktuellen Aktiendaten basierend auf Auswahl."""
selected_symbol = self.symbol_combo.currentText()
if not selected_symbol:
return pd.DataFrame()
start_date_q = self.start_date_edit.date()
end_date_q = self.end_date_edit.date()
start_date_str = start_date_q.toString("yyyy-MM-dd")
end_date_str = end_date_q.toString("yyyy-MM-dd")
data = self.db_manager.get_stock_data(selected_symbol, start_date_str, end_date_str)
return data
def _on_symbol_selected(self, index):
"""Wird aufgerufen, wenn ein neues Symbol in der ComboBox ausgewählt wird."""
self.plot_all_tabs()
def plot_all_tabs(self):
"""Aktualisiert alle Diagramme und Texte in den Tabs."""
data = self._get_current_stock_data()
if data.empty:
self._clear_all_plots()
self.overview_text.setText("Keine Daten verfügbar für das ausgewählte Symbol oder den Zeitraum.")
self.returns_text.setText("Keine Daten verfügbar.")
self.ma_text.setText("Keine Daten verfügbar.")
self.vol_text.setText("Keine Daten verfügbar.")
self.beta_text.setText("Keine Daten verfügbar.")
return
self._plot_overview(data)
self._plot_returns(data)
self._plot_ma(data) # Kann überschrieben werden, wenn MA-Button geklickt wird
self._plot_volatility(data) # Kann überschrieben werden, wenn Vol-Button geklickt wird
self._plot_beta(data) # Kann überschrieben werden, wenn Beta-Button geklickt wird
def _clear_plot(self, canvas):
"""Löscht einen Plot."""
canvas.axes.clear()
canvas.draw()
def _clear_all_plots(self):
self._clear_plot(self.overview_canvas)
self._clear_plot(self.returns_canvas)
self._clear_plot(self.ma_canvas)
self._clear_plot(self.vol_canvas)
self._clear_plot(self.beta_canvas)
def _plot_overview(self, data):
self._clear_plot(self.overview_canvas)
ax = self.overview_canvas.axes
ax.plot(data.index, data['adj_close'], label='Schlusskurs', color='blue')
ax.set_title(f"{self.symbol_combo.currentText()} - Schlusskurs")
ax.set_xlabel("Datum")
ax.set_ylabel("Kurs")
ax.legend()
ax.grid(True)
self.overview_canvas.draw()
self.overview_text.setText(f"Aktueller Kurs: {data['adj_close'].iloc[-1]:.2f}\n"
f"Datum: {data.index[-1].strftime('%Y-%m-%d')}\n"
f"Anzahl Datenpunkte: {len(data)}")
def _plot_returns(self, data):
self._clear_plot(self.returns_canvas)
ax = self.returns_canvas.axes
# Sicherstellen, dass calculate_returns und calculate_cumulative_returns die Series erhalten
# und die Ausgabe der calculate_returns Funktion in Dezimalwerten ist, nicht in Prozent.
# Ich habe das in FinancialTools vorgeschlagen, falls du es dort nicht direkt *100 machst.
# Hier ist es wichtig, dass 'data['adj_close']' übergeben wird.
daily_returns = self.financial_tools.calculate_returns(data['adj_close'])
cumulative_returns = self.financial_tools.calculate_cumulative_returns(data['adj_close'])
if not daily_returns.empty:
ax.plot(daily_returns.index, daily_returns, label='Tägliche Rendite', color='green', alpha=0.7)
ax.set_title(f"{self.symbol_combo.currentText()} - Tägliche Renditen")
ax.set_xlabel("Datum")
ax.set_ylabel("Rendite (%)") # Hier ist "%" wichtig
ax.grid(True)
self.returns_canvas.draw()
# Jetzt die Fehlerbehebung für die Textausgabe
avg_daily_return = daily_returns.mean()
# Überprüfe, ob es ein gültiger Zahlenwert ist, bevor formatiert wird
avg_daily_return_str = f"{avg_daily_return:.4f}" if pd.notna(avg_daily_return) else "N/A"
last_cumulative_return = cumulative_returns.iloc[-1]
last_cumulative_return_str = f"{last_cumulative_return:.4f}" if pd.notna(last_cumulative_return) else "N/A"
self.returns_text.setText(f"Durchschn. tägl. Rendite: {avg_daily_return_str}\n"
f"Kumulierte Rendite (Gesamt): {last_cumulative_return_str}")
else:
self._clear_plot(self.returns_canvas)
self.returns_text.setText("Nicht genügend Daten für Renditeberechnung.")
def _plot_ma(self, data):
self._clear_plot(self.ma_canvas)
ax = self.ma_canvas.axes
try:
window = int(self.ma_window_input.text())
except ValueError:
QMessageBox.warning(self, "Eingabefehler", "Gleitender Durchschnitt: Fenster muss eine Zahl sein.")
return
ma = self.financial_tools.calculate_moving_average(data, window=window)
if not ma.empty:
ax.plot(data.index, data['adj_close'], label='Schlusskurs', color='blue', alpha=0.7)
ax.plot(ma.index, ma, label=f'MA {window} Tage', color='red')
ax.set_title(f"{self.symbol_combo.currentText()} - Gleitender Durchschnitt ({window} Tage)")
ax.set_xlabel("Datum")
ax.set_ylabel("Kurs")
ax.legend()
ax.grid(True)
self.ma_canvas.draw()
self.ma_text.setText(f"Gleitender Durchschnitt ({window} Tage) berechnet.")
else:
self._clear_plot(self.ma_canvas)
self.ma_text.setText("Nicht genügend Daten für gleitenden Durchschnitt.")
def _plot_volatility(self, data):
self._clear_plot(self.vol_canvas)
ax = self.vol_canvas.axes
try:
window = int(self.vol_window_input.text())
except ValueError:
QMessageBox.warning(self.tab_volatility, "Eingabefehler", "Volatilität: Fenster muss eine Zahl sein.")
return
volatility = self.financial_tools.calculate_volatility(data, window=window)
if not volatility.empty:
ax.plot(volatility.index, volatility, label=f'Volatilität {window} Tage (annualisiert)', color='purple')
ax.set_title(f"{self.symbol_combo.currentText()} - Rollierende Volatilität ({window} Tage)")
ax.set_xlabel("Datum")
ax.set_ylabel("Volatilität (annualisiert)")
ax.grid(True)
self.vol_canvas.draw()
self.vol_text.setText(f"Rollierende Volatilität ({window} Tage) berechnet.")
else:
self._clear_plot(self.vol_canvas)
self.vol_text.setText("Nicht genügend Daten für Volatilitätsberechnung.")
def _plot_beta(self, data):
self._clear_plot(self.beta_canvas)
ax = self.beta_canvas.axes
market_symbol = self.market_symbol_input.text().upper()
try:
window = int(self.beta_window_input.text())
except ValueError:
QMessageBox.warning(self.tab_beta, "Eingabefehler", "Beta: Fenster muss eine Zahl sein.")
return
if not market_symbol:
QMessageBox.warning(self.tab_beta, "Eingabefehler", "Bitte geben Sie ein Markt-Symbol ein (z.B. SPY).")
return
# Zuerst das Markt-Symbol zur DB hinzufügen und Daten holen, falls nicht vorhanden
if not market_symbol in self.db_manager.get_all_symbols():
if not self.db_manager.add_stock(market_symbol):
QMessageBox.warning(self.tab_beta, "Datenfehler", f"Ungültiges Markt-Symbol: {market_symbol}")
self._clear_plot(self.beta_canvas)
self.beta_text.setText("Ungültiges Markt-Symbol.")
return
if not self.db_manager.fetch_and_store_data(market_symbol, period="max"):
QMessageBox.warning(self.tab_beta, "Datenfehler", f"Konnte Daten für Markt-Symbol {market_symbol} nicht holen.")
self._clear_plot(self.beta_canvas)
self.beta_text.setText("Keine Marktdaten verfügbar.")
return
start_date_q = self.start_date_edit.date()
end_date_q = self.end_date_edit.date()
start_date_str = start_date_q.toString("yyyy-MM-dd")
end_date_str = end_date_q.toString("yyyy-MM-dd")
market_data = self.db_manager.get_stock_data(market_symbol, start_date_str, end_date_str)
if market_data.empty:
QMessageBox.warning(self.tab_beta, "Datenfehler", f"Keine Daten für Markt-Symbol {market_symbol} im angegebenen Zeitraum.")
self._clear_plot(self.beta_canvas)
self.beta_text.setText("Keine Marktdaten für Beta-Berechnung.")
return
beta = self.financial_tools.calculate_beta(data, market_data, window=window)
if not beta.empty:
ax.plot(beta.index, beta, label=f'Beta vs {market_symbol} ({window} Tage)', color='orange')
ax.set_title(f"{self.symbol_combo.currentText()} - Rollierendes Beta vs {market_symbol} ({window} Tage)")
ax.set_xlabel("Datum")
ax.set_ylabel("Beta")
ax.axhline(1, color='gray', linestyle='--', linewidth=0.8, label='Beta = 1')
ax.legend()
ax.grid(True)
self.beta_canvas.draw()
self.beta_text.setText(f"Rollierendes Beta ({window} Tage) vs {market_symbol} berechnet. Aktuelles Beta: {beta.iloc[-1]:.2f}")
else:
self._clear_plot(self.beta_canvas)
self.beta_text.setText("Nicht genügend Daten für Beta-Berechnung. Stellen Sie sicher, dass sowohl Aktie als auch Markt ausreichend historische Daten haben.")
def closeEvent(self, event):
"""Wird aufgerufen, wenn das Fenster geschlossen wird."""
self.db_manager.close()
event.accept()
if __name__ == "__main__":
app = QApplication(sys.argv)
window = StockAnalyzer()
window.show()
sys.exit(app.exec())

BIN
stock_analysis.db Normal file

Binary file not shown.

201
stock_data_manager.py Normal file
View File

@@ -0,0 +1,201 @@
import yfinance as yf
import pandas as pd
import sqlite3
from datetime import datetime
class StockDataManager:
def __init__(self, db_name="stock_data.db"):
self.db_name = db_name
self.conn = None
self.cursor = None
self._connect_db()
self._create_tables()
def _connect_db(self):
"""Stellt eine Verbindung zur SQLite-Datenbank her."""
try:
self.conn = sqlite3.connect(self.db_name)
self.cursor = self.conn.cursor()
print(f"Erfolgreich mit Datenbank '{self.db_name}' verbunden.")
except sqlite3.Error as e:
print(f"Fehler beim Verbinden mit der Datenbank: {e}")
def _create_tables(self):
"""Erstellt die Tabellen für Aktiendaten, falls sie noch nicht existieren."""
if not self.conn:
print("Keine Datenbankverbindung vorhanden.")
return
try:
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS stocks (
symbol TEXT PRIMARY KEY,
company_name TEXT
)
''')
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS daily_prices (
id INTEGER PRIMARY KEY AUTOINCREMENT,
symbol TEXT NOT NULL,
date TEXT NOT NULL,
open REAL,
high REAL,
low REAL,
close REAL,
adj_close REAL,
volume INTEGER,
FOREIGN KEY (symbol) REFERENCES stocks (symbol) ON DELETE CASCADE,
UNIQUE (symbol, date)
)
''')
self.conn.commit()
print("Datenbanktabellen überprüft/erstellt.")
except sqlite3.Error as e:
print(f"Fehler beim Erstellen der Tabellen: {e}")
def add_stock(self, symbol, company_name=""):
"""Fügt ein Aktiensymbol zur 'stocks'-Tabelle hinzu."""
if not self.conn: return
try:
self.cursor.execute("INSERT OR IGNORE INTO stocks (symbol, company_name) VALUES (?, ?)", (symbol.upper(), company_name))
self.conn.commit()
print(f"Aktie '{symbol.upper()}' zur Datenbank hinzugefügt (falls neu).")
return True
except sqlite3.Error as e:
print(f"Fehler beim Hinzufügen der Aktie {symbol}: {e}")
return False
def get_all_symbols(self):
"""Gibt eine Liste aller in der Datenbank gespeicherten Symbole zurück."""
if not self.conn: return []
self.cursor.execute("SELECT symbol FROM stocks")
return [row[0] for row in self.cursor.fetchall()]
def fetch_and_store_data(self, symbol, period="1y"):
"""
Holt historische Kursdaten für ein Symbol und speichert sie in der Datenbank.
period: '1d', '5d', '1mo', '3mo', '6mo', '1y', '2y', '5y', '10y', 'ytd', 'max'
"""
symbol = symbol.upper()
print(f"Hole Daten für {symbol}...")
try:
ticker = yf.Ticker(symbol)
hist = ticker.history(period="1y", auto_adjust=False)
if hist.empty:
print(f"Keine Daten für {symbol} gefunden oder ungültiges Symbol.")
return False
# Füge das Symbol hinzu, falls es noch nicht existiert (z.B. wenn es direkt per API geholt wird)
self.add_stock(symbol, ticker.info.get('longName', ''))
data_to_insert = []
for date, row in hist.iterrows():
# Formatiere Datum als YYYY-MM-DD
date_str = date.strftime('%Y-%m-%d')
data_to_insert.append((
symbol,
date_str,
row['Open'],
row['High'],
row['Low'],
row['Close'],
row['Adj Close'],
row['Volume']
))
# Verwende INSERT OR IGNORE, um Duplikate zu vermeiden
self.cursor.executemany("""
INSERT OR IGNORE INTO daily_prices (symbol, date, open, high, low, close, adj_close, volume)
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
""", data_to_insert)
self.conn.commit()
print(f"Daten für {symbol} erfolgreich gespeichert/aktualisiert.")
return True
except Exception as e:
print(f"Fehler beim Holen/Speichern der Daten für {symbol}: {e}")
return False
def get_stock_data(self, symbol, start_date=None, end_date=None):
"""
Holt historische Kursdaten für ein Symbol aus der Datenbank als Pandas DataFrame.
Optional: Filter nach Start- und Enddatum.
"""
symbol = symbol.upper()
query = "SELECT date, open, high, low, close, adj_close, volume FROM daily_prices WHERE symbol = ?"
params = [symbol]
if start_date and end_date:
query += " AND date BETWEEN ? AND ?"
params.append(start_date)
params.append(end_date)
elif start_date:
query += " AND date >= ?"
params.append(start_date)
elif end_date:
query += " AND date <= ?"
params.append(end_date)
query += " ORDER BY date ASC"
try:
df = pd.read_sql(query, self.conn, params=params, parse_dates=['date'], index_col='date')
if df.empty:
print(f"Keine Daten für {symbol} im angegebenen Zeitraum in der Datenbank gefunden.")
return df
except sqlite3.Error as e:
print(f"Fehler beim Abrufen der Daten für {symbol} aus der Datenbank: {e}")
return pd.DataFrame()
def close(self):
"""Schließt die Datenbankverbindung."""
if self.conn:
self.conn.close()
print("Datenbankverbindung geschlossen.")
# Beispielnutzung (kann später entfernt werden, wenn GUI fertig ist)
if __name__ == "__main__":
manager = StockDataManager("my_stocks.db")
# Symbole aus CSV hinzufügen (Beispiel)
# Angenommen, du hast eine stocks.csv mit 'Symbol,CompanyName'
# Beispiel-CSV-Inhalt:
# Symbol,CompanyName
# AAPL,Apple Inc.
# MSFT,Microsoft Corp.
# GOOGL,Alphabet Inc. (GOOGL)
# AMZN,Amazon.com Inc.
csv_file = "stocks.csv" # Erstelle diese Datei manuell für den Test
try:
df_symbols = pd.read_csv(csv_file)
for index, row in df_symbols.iterrows():
manager.add_stock(row['Symbol'], row.get('CompanyName', ''))
except FileNotFoundError:
print(f"'{csv_file}' nicht gefunden. Bitte erstellen Sie eine CSV-Datei mit 'Symbol,CompanyName'.")
except KeyError:
print(f"Fehler: '{csv_file}' muss Spalten 'Symbol' und optional 'CompanyName' enthalten.")
symbols_to_fetch = manager.get_all_symbols()
if not symbols_to_fetch:
# Fallback: Wenn CSV leer ist oder nicht existiert, einige bekannte Symbole holen
symbols_to_fetch = ["AAPL", "MSFT", "GOOGL"]
for s in symbols_to_fetch:
manager.add_stock(s)
for symbol in symbols_to_fetch:
manager.fetch_and_store_data(symbol, period="1y") # Holt Daten für 1 Jahr
# Testen des Datenabrufs
aapl_data = manager.get_stock_data("AAPL")
if not aapl_data.empty:
print("\nAAPL Daten (erste 5 Reihen):")
print(aapl_data.head())
msft_data = manager.get_stock_data("MSFT", start_date="2024-01-01")
if not msft_data.empty:
print("\nMSFT Daten seit 2024-01-01 (letzte 5 Reihen):")
print(msft_data.tail())
manager.close()

4
stocks.csv Normal file
View File

@@ -0,0 +1,4 @@
Symbol,Name,Branche,Anzahl_Aktien,Kaufpreis_pro_Aktie,Kaufdatum
MSFT,Microsoft Corp,Technologie,10,250.00,2023-01-15
AAPL,Apple Inc,Technologie,15,165.50,2023-03-10
SHEL,Shell PLC,Energie,50,25.75,2023-02-20
1 Symbol Name Branche Anzahl_Aktien Kaufpreis_pro_Aktie Kaufdatum
2 MSFT Microsoft Corp Technologie 10 250.00 2023-01-15
3 AAPL Apple Inc Technologie 15 165.50 2023-03-10
4 SHEL Shell PLC Energie 50 25.75 2023-02-20