From da68106666328af47c622094123f202ba5ad537e Mon Sep 17 00:00:00 2001 From: hwinkel Date: Fri, 23 Jan 2026 15:39:50 +0100 Subject: [PATCH] First Commit --- Backtest_Report_AAPL.html | 61 +++++++++++++++++++++ Backtest_Report_TSLA.html | 61 +++++++++++++++++++++ Backtest_Trades_AAPL.xlsx | Bin 0 -> 5361 bytes Backtest_Trades_TSLA.xlsx | Bin 0 -> 5333 bytes backtester.py | 105 +++++++++++++++++++++++++++++++++++++ checkStocks.py | 83 +++++++++++++++++++++++++++++ live_bot.py | 89 +++++++++++++++++++++++++++++++ markt_scan_20260121.xlsx | Bin 0 -> 5219 bytes optimized_report_AAPL.html | 61 +++++++++++++++++++++ optimizer.py | 73 ++++++++++++++++++++++++++ 10 files changed, 533 insertions(+) create mode 100644 Backtest_Report_AAPL.html create mode 100644 Backtest_Report_TSLA.html create mode 100644 Backtest_Trades_AAPL.xlsx create mode 100644 Backtest_Trades_TSLA.xlsx create mode 100644 backtester.py create mode 100644 checkStocks.py create mode 100644 live_bot.py create mode 100644 markt_scan_20260121.xlsx create mode 100644 optimized_report_AAPL.html create mode 100644 optimizer.py diff --git a/Backtest_Report_AAPL.html b/Backtest_Report_AAPL.html new file mode 100644 index 0000000..f9686a8 --- /dev/null +++ b/Backtest_Report_AAPL.html @@ -0,0 +1,61 @@ + + + + + Backtest_Report_AAPL.html + + + + + +
+ + + + + \ No newline at end of file diff --git a/Backtest_Report_TSLA.html b/Backtest_Report_TSLA.html new file mode 100644 index 0000000..c3be272 --- /dev/null +++ b/Backtest_Report_TSLA.html @@ -0,0 +1,61 @@ + + + + + Backtest_Report_TSLA.html + + + + + +
+ + + + + \ No newline at end of file diff --git a/Backtest_Trades_AAPL.xlsx b/Backtest_Trades_AAPL.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..45be3f30a2243bc645c98471d32c7999e8427712 GIT binary patch literal 5361 zcmZ`-1yodP*B%h01qP5VDFcQYq}!oWiJ?"uX|4yC1KNTsAvYCsyKQ)(zl2?=2? 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 literal 0 HcmV?d00001 diff --git a/backtester.py b/backtester.py new file mode 100644 index 0000000..f98b3e1 --- /dev/null +++ b/backtester.py @@ -0,0 +1,105 @@ +import os +import pandas as pd +import pandas_ta as ta +from dotenv import load_dotenv +from datetime import datetime, timedelta + +# Backtesting-Spezialisten +from backtesting import Backtest, Strategy +from backtesting.lib import crossover + +# Alpaca API +from alpaca.data.historical import StockHistoricalDataClient +from alpaca.data.requests import StockBarsRequest +from alpaca.data.timeframe import TimeFrame + +# --- CONFIG --- +load_dotenv() +API_KEY = os.getenv('ALPACA_API_KEY') +SECRET_KEY = os.getenv('ALPACA_SECRET_KEY') + +# --- KLASSE: DATEN-ENGINE --- +class DataEngine: + @staticmethod + def get_alpaca_data(symbol, days=365): + client = StockHistoricalDataClient(API_KEY, SECRET_KEY) + start_date = datetime.now() - timedelta(days=days) + + request = StockBarsRequest( + symbol_or_symbols=[symbol], + timeframe=TimeFrame.Day, + start=start_date + ) + + df = client.get_stock_bars(request).df + df = df.reset_index(level=0, drop=True) + + # Formatierung für Backtesting.py (Spalten müssen groß geschrieben sein) + df.columns = [c.capitalize() for c in df.columns] + # Zeitzonen entfernen (wichtig für Excel!) + df.index = df.index.tz_localize(None) + return df + +# --- KLASSE: STRATEGIE (RSI) --- +class MyRsiStrategy(Strategy): + # Parameter - diese können später optimiert werden + rsi_period = 25 + rsi_low = 30 + rsi_high = 70 + + def init(self): + # Indikator berechnen (self.I stellt sicher, dass er im Chart erscheint) + self.rsi = self.I(ta.rsi, pd.Series(self.data.Close), length=self.rsi_period) + + def next(self): + # KAUFEN: Wenn RSI die untere Grenze von unten nach oben kreuzt + if crossover(self.rsi, self.rsi_low): + self.buy() + + # VERKAUFEN: Wenn RSI die obere Grenze von oben nach unten kreuzt + elif crossover(self.rsi_high, self.rsi): + if self.position: + self.position.close() + +# --- HAUPTPROGRAMM --- +def run_backtest(symbol="AAPL", cash=10000, commission=0.002): + # 1. Daten laden + print(f"Lade Daten für {symbol}...") + data = DataEngine.get_alpaca_data(symbol) + + # 2. Backtest initialisieren + # commission=0.002 bedeutet 0.2% Gebühren pro Trade + bt = Backtest(data, MyRsiStrategy, cash=cash, commission=commission) + + # 3. Backtest ausführen + stats = bt.run() + + # --- AUSWERTUNG --- + print("\n" + "="*30) + print(f"BACKTEST ERGEBNISSE FÜR {symbol}") + print("="*30) + print(f"Startkapital: {cash}$") + print(f"Endkapital: {stats['Equity Final [$]']:.2f}$") + print(f"Rendite [%]: {stats['Return [%]']:.2f}%") + print(f"Max. Drawdown: {stats['Max. Drawdown [%]']:.2f}%") + print(f"Anzahl Trades: {stats['# Trades']}") + print(f"Win Rate [%]: {stats['Win Rate [%]']:.2f}%") + print("="*30) + + # 4. EXPORT: Trades nach Excel + trades = stats['_trades'] + if not trades.empty: + # Dauer der Trades berechnen + trades['Duration'] = trades['ExitTime'] - trades['EntryTime'] + excel_name = f"Backtest_Trades_{symbol}.xlsx" + trades.to_excel(excel_name) + print(f"✅ Excel-Liste gespeichert: {excel_name}") + + # 5. EXPORT: Interaktiver HTML Report + report_name = f"Backtest_Report_{symbol}.html" + bt.plot(filename=report_name, open_browser=False) + print(f"✅ Interaktiver Chart gespeichert: {report_name}") + +if __name__ == "__main__": + # Starte den Backtest + run_backtest(symbol="AAPL", cash=10000) \ No newline at end of file diff --git a/checkStocks.py b/checkStocks.py new file mode 100644 index 0000000..4f4564f --- /dev/null +++ b/checkStocks.py @@ -0,0 +1,83 @@ +import os +import pandas as pd +import pandas_ta as ta +from dotenv import load_dotenv +from datetime import datetime, timedelta +from alpaca.data.historical import StockHistoricalDataClient +from alpaca.data.requests import StockBarsRequest +from alpaca.data.timeframe import TimeFrame + +# --- SETUP --- +load_dotenv() +API_KEY = os.getenv('ALPACA_API_KEY') +SECRET_KEY = os.getenv('ALPACA_SECRET_KEY') +data_client = StockHistoricalDataClient(API_KEY, SECRET_KEY) + +def scan_markets(tickers, rsi_period=25): + results = [] + print(f"--- Scanne {len(tickers)} Aktien auf Signale ---") + + for symbol in tickers: + try: + # Daten für die letzten 30 Tage holen (reicht für RSI 14) + start_date = datetime.now() - timedelta(days=50) + request_params = StockBarsRequest( + symbol_or_symbols=[symbol], + timeframe=TimeFrame.Day, + start=start_date + ) + + bars = data_client.get_stock_bars(request_params).df + if bars.empty: continue + + df = bars.reset_index(level=0, drop=True) + + # RSI berechnen + df['RSI'] = ta.rsi(df['close'], length=rsi_period) + + # Letzte Werte extrahieren + current_price = df['close'].iloc[-1] + current_rsi = df['RSI'].iloc[-1] + + # Signal-Logik + signal = "NEUTRAL" + if current_rsi < 30: + signal = "KAUFEN (Überverkauft)" + elif current_rsi > 70: + signal = "VERKAUFEN (Überkauft)" + + results.append({ + 'Symbol': symbol, + 'Preis': round(current_price, 2), + 'RSI': round(current_rsi, 2), + 'Signal': signal, + 'Zeitpunkt': datetime.now().strftime('%Y-%m-%d %H:%M') + }) + print(f"{symbol}: RSI {current_rsi:.2f} -> {signal}") + + except Exception as e: + print(f"Fehler beim Scannen von {symbol}: {e}") + + # Ergebnisse in DataFrame umwandeln + scan_df = pd.DataFrame(results) + + # Als Excel speichern + filename = f"markt_scan_{datetime.now().strftime('%Y%m%d')}.xlsx" + scan_df.to_excel(filename, index=False) + print(f"\n✅ Scan abgeschlossen. Ergebnisse gespeichert in: {filename}") + + return scan_df + +if __name__ == "__main__": + # Liste der zu scannenden Aktien + my_watchlist = ["AAPL", "TSLA", "MSFT", "AMZN", "GOOGL", "NVDA", "AMD", "META"] + + scan_results = scan_markets(my_watchlist) + + # Optional: Nur die echten Signale anzeigen + hits = scan_results[scan_results['Signal'] != "NEUTRAL"] + if not hits.empty: + print("\n🔥 AKTUELLE SIGNALE:") + print(hits) + else: + print("\nKeine extremen RSI-Werte gefunden.") \ No newline at end of file diff --git a/live_bot.py b/live_bot.py new file mode 100644 index 0000000..517ca96 --- /dev/null +++ b/live_bot.py @@ -0,0 +1,89 @@ +import os +import time +import pandas as pd +import pandas_ta as ta +from dotenv import load_dotenv +from datetime import datetime, timedelta, timezone +from colorama import Fore, Style, init + +from alpaca.trading.client import TradingClient +from alpaca.trading.requests import MarketOrderRequest, StopLossRequest, TakeProfitRequest +from alpaca.trading.enums import OrderSide, TimeInForce, OrderClass +from alpaca.data.historical import StockHistoricalDataClient +from alpaca.data.requests import StockBarsRequest +from alpaca.data.timeframe import TimeFrame + +init(autoreset=True) +load_dotenv() + +# --- PARAMETER --- +SYMBOL = "AAPL" +QTY_PER_TRADE = 10 # Anzahl der Aktien +RSI_LOWER = 30 +RSI_UPPER = 70 +STOP_LOSS_PCT = 0.05 # 5% Stop Loss +TAKE_PROFIT_PCT = 0.10 # 10% Take Profit (Optionales Ziel) + +# Clients +trading_client = TradingClient(os.getenv('ALPACA_API_KEY'), os.getenv('ALPACA_SECRET_KEY'), paper=True) +data_client = StockHistoricalDataClient(os.getenv('ALPACA_API_KEY'), os.getenv('ALPACA_SECRET_KEY')) + +def get_rsi(): + try: + start_time = datetime.now(timezone.utc) - timedelta(days=50) + bars = data_client.get_stock_bars(StockBarsRequest(symbol_or_symbols=[SYMBOL], timeframe=TimeFrame.Hour, start=start_time)) + df = bars.df.xs(SYMBOL) + rsi = ta.rsi(df['close'], length=14) + return rsi.iloc[-1], df['close'].iloc[-1] + except: + return None, None + +def execute_trade(side, price): + """Sendet eine automatisierte Order mit Stop-Loss an Alpaca""" + if side == "BUY": + # Berechnung der Stop-Loss und Take-Profit Preise + sl_price = price * (1 - STOP_LOSS_PCT) + tp_price = price * (1 + TAKE_PROFIT_PCT) + + # Erstellung einer "Bracket Order" (Kauf + SL + TP gleichzeitig) + order_request = MarketOrderRequest( + symbol=SYMBOL, + qty=QTY_PER_TRADE, + side=OrderSide.BUY, + time_in_force=TimeInForce.GTC, + order_class=OrderClass.BRACKET, # Verbindet Kauf mit SL/TP + stop_loss=StopLossRequest(stop_price=round(sl_price, 2)), + take_profit=TakeProfitRequest(limit_price=round(tp_price, 2)) + ) + trading_client.submit_order(order_request) + print(f"{Fore.GREEN}>>> AUTOMATISCHER KAUF: {QTY_PER_TRADE} {SYMBOL} @ {price}$") + print(f"{Fore.YELLOW}>>> STOP-LOSS GESETZT BEI: {sl_price:.2f}$") + +def main_loop(): + print(f"{Fore.CYAN}=== BOT STARTET IM AUTOMATIK-MODUS (PAPER) ===") + while True: + rsi, price = get_rsi() + + # Check ob bereits eine Position offen ist + try: + position = trading_client.get_open_position(SYMBOL) + has_position = True + except: + has_position = False + + if rsi: + print(f"[{datetime.now().strftime('%H:%M:%S')}] {SYMBOL} Preis: {price}$ | RSI: {rsi:.2f}") + + # KAUFEN: RSI zu niedrig & wir haben noch nichts gekauft + if rsi < RSI_LOWER and not has_position: + execute_trade("BUY", price) + + # VERKAUFEN: RSI zu hoch & wir haben eine Position + elif rsi > RSI_UPPER and has_position: + trading_client.close_position(SYMBOL) + print(f"{Fore.RED}>>> AUTOMATISCHER VERKAUF (RSI TARGET ERREICHT)") + + time.sleep(60) + +if __name__ == "__main__": + main_loop() \ No newline at end of file diff --git a/markt_scan_20260121.xlsx b/markt_scan_20260121.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..21a06933bc9c20d3ceb178f13daca74471df7ba9 GIT binary patch literal 5219 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 XmZmBOa=-)t;2=+_Um2YA*W3RAi5(w^ literal 0 HcmV?d00001 diff --git a/optimized_report_AAPL.html b/optimized_report_AAPL.html new file mode 100644 index 0000000..7ebc345 --- /dev/null +++ b/optimized_report_AAPL.html @@ -0,0 +1,61 @@ + + + + + optimized_report_AAPL.html + + + + + +
+ + + + + \ No newline at end of file diff --git a/optimizer.py b/optimizer.py new file mode 100644 index 0000000..a30a978 --- /dev/null +++ b/optimizer.py @@ -0,0 +1,73 @@ +import os +import pandas as pd +import pandas_ta as ta +from dotenv import load_dotenv +from datetime import datetime, timedelta + +from backtesting import Backtest, Strategy +from backtesting.lib import crossover +from alpaca.data.historical import StockHistoricalDataClient +from alpaca.data.requests import StockBarsRequest +from alpaca.data.timeframe import TimeFrame + +# --- 1. DATEN HOLEN --- +load_dotenv() +API_KEY = os.getenv('ALPACA_API_KEY') +SECRET_KEY = os.getenv('ALPACA_SECRET_KEY') + +def get_data(symbol, days=365): + client = StockHistoricalDataClient(API_KEY, SECRET_KEY) + start_date = datetime.now() - timedelta(days=days) + request_params = StockBarsRequest(symbol_or_symbols=[symbol], timeframe=TimeFrame.Day, start=start_date) + df = client.get_stock_bars(request_params).df + df = df.reset_index(level=0, drop=True) + df.columns = [c.capitalize() for c in df.columns] + df.index = df.index.tz_localize(None) + return df + +# --- 2. STRATEGIE MIT OPTIMIERBAREN PARAMETERN --- +class RsiStrategy(Strategy): + # Diese Klassenvariablen werden von der Optimize-Funktion überschrieben + rsi_period = 14 + rsi_lower = 30 + rsi_upper = 70 + + def init(self): + # Wichtig: Wir übergeben die Parameter an pandas_ta + self.rsi = self.I(ta.rsi, pd.Series(self.data.Close), length=self.rsi_period) + + def next(self): + if crossover(self.rsi, self.rsi_lower): + self.buy() + elif crossover(self.rsi_upper, self.rsi): + if self.position: + self.position.close() + +# --- 3. OPTIMIERUNGS-ENGINE --- +def run_optimized_backtest(symbol): + data = get_data(symbol) + bt = Backtest(data, RsiStrategy, cash=10000, commission=0.001) + + print(f"--- Starte Optimierung für {symbol} ---") + + # Hier passiert die Magie: + stats = bt.optimize( + rsi_period=range(7, 30, 2), # Teste Perioden von 7 bis 29 in 2er Schritten + rsi_lower=range(20, 40, 5), # Teste Kaufsignale von 20 bis 35 + rsi_upper=range(60, 80, 5), # Teste Verkaufsignale von 60 bis 75 + maximize='Return [%]', # Wir wollen den höchsten Gewinn (oder 'Sharpe Ratio') + constraint=lambda p: p.rsi_upper > p.rsi_lower # Logik-Check + ) + + print("\n--- BESTE PARAMETER GEFUNDEN ---") + print(stats) + print("\nDetails der besten Strategie:") + print(f"RSI Periode: {stats._strategy.rsi_period}") + print(f"RSI Untergrenze: {stats._strategy.rsi_lower}") + print(f"RSI Obergrenze: {stats._strategy.rsi_upper}") + + # Speichere den Chart der besten Strategie + bt.plot(filename=f"optimized_report_{symbol}.html", open_browser=False) + +if __name__ == "__main__": + run_optimized_backtest("AAPL") \ No newline at end of file