First Commit
This commit is contained in:
61
Backtest_Report_AAPL.html
Normal file
61
Backtest_Report_AAPL.html
Normal file
File diff suppressed because one or more lines are too long
61
Backtest_Report_TSLA.html
Normal file
61
Backtest_Report_TSLA.html
Normal file
File diff suppressed because one or more lines are too long
BIN
Backtest_Trades_AAPL.xlsx
Normal file
BIN
Backtest_Trades_AAPL.xlsx
Normal file
Binary file not shown.
BIN
Backtest_Trades_TSLA.xlsx
Normal file
BIN
Backtest_Trades_TSLA.xlsx
Normal file
Binary file not shown.
105
backtester.py
Normal file
105
backtester.py
Normal file
@@ -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)
|
||||
83
checkStocks.py
Normal file
83
checkStocks.py
Normal file
@@ -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.")
|
||||
89
live_bot.py
Normal file
89
live_bot.py
Normal file
@@ -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()
|
||||
BIN
markt_scan_20260121.xlsx
Normal file
BIN
markt_scan_20260121.xlsx
Normal file
Binary file not shown.
61
optimized_report_AAPL.html
Normal file
61
optimized_report_AAPL.html
Normal file
File diff suppressed because one or more lines are too long
73
optimizer.py
Normal file
73
optimizer.py
Normal file
@@ -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")
|
||||
Reference in New Issue
Block a user