ADD: added optimizer workflow
This commit is contained in:
115
optimizer.py
115
optimizer.py
@@ -4,8 +4,8 @@ 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 backtesting import Backtest
|
||||
from strategies import RsiStrategy
|
||||
from alpaca.data.historical import StockHistoricalDataClient
|
||||
from alpaca.data.requests import StockBarsRequest
|
||||
from alpaca.data.timeframe import TimeFrame
|
||||
@@ -14,60 +14,99 @@ from alpaca.data.timeframe import TimeFrame
|
||||
load_dotenv()
|
||||
API_KEY = os.getenv('ALPACA_API_KEY')
|
||||
SECRET_KEY = os.getenv('ALPACA_SECRET_KEY')
|
||||
PATH_TO_OUTPUT = "output/"
|
||||
os.makedirs(PATH_TO_OUTPUT, exist_ok=True)
|
||||
|
||||
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)
|
||||
request_params = StockBarsRequest(symbol_or_symbols=[symbol], timeframe=TimeFrame.Hour, 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()
|
||||
# Strategy classes moved to strategies.py
|
||||
|
||||
# --- 3. OPTIMIERUNGS-ENGINE ---
|
||||
def run_optimized_backtest(symbol):
|
||||
def run_optimized_backtest(symbol, strategy_cls=RsiStrategy, optimize_kwargs=None, report_tag=None):
|
||||
data = get_data(symbol)
|
||||
bt = Backtest(data, RsiStrategy, cash=10000, commission=0.001)
|
||||
bt = Backtest(data, strategy_cls, cash=10000, commission=0.001, finalize_trades=True)
|
||||
|
||||
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(f"--- Starte Optimierung für {symbol} using {strategy_cls.__name__} ---")
|
||||
|
||||
# Build common optimization params for strategy if not provided
|
||||
if optimize_kwargs is None:
|
||||
optimize_kwargs = dict(
|
||||
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
|
||||
)
|
||||
# Extend with ATR/stop params if strategy supports them
|
||||
if hasattr(strategy_cls, 'atr_period'):
|
||||
optimize_kwargs['atr_period'] = range(10, 20, 2)
|
||||
if hasattr(strategy_cls, 'stop_loss_atr_multiplier'):
|
||||
optimize_kwargs['stop_loss_atr_multiplier'] = [2.0, 2.5, 3.0, 3.5, 4.0]
|
||||
|
||||
# Run optimization
|
||||
stats = bt.optimize(**optimize_kwargs)
|
||||
|
||||
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}")
|
||||
|
||||
# Print only attributes that the strategy actually has
|
||||
for attr in ('rsi_period', 'rsi_lower', 'rsi_upper', 'short_ema', 'long_ema', 'atr_period', 'stop_loss_atr_multiplier'):
|
||||
if hasattr(stats._strategy, attr):
|
||||
print(f"{attr.replace('_', ' ').capitalize()}: {getattr(stats._strategy, attr)}")
|
||||
# Speichere den Chart der besten Strategie
|
||||
bt.plot(filename=f"optimized_report_{symbol}.html", open_browser=False)
|
||||
tag = f"_{report_tag}" if report_tag else ""
|
||||
out_path = os.path.join(PATH_TO_OUTPUT, f"optimized_report_{symbol}{tag}.html")
|
||||
bt.plot(filename=out_path, open_browser=False)
|
||||
print(f"Optimized report saved: {out_path}")
|
||||
|
||||
# Build a result dict to return to callers
|
||||
result = {
|
||||
'symbol': symbol,
|
||||
'rsi_period': getattr(stats._strategy, 'rsi_period', None),
|
||||
'rsi_lower': getattr(stats._strategy, 'rsi_lower', None),
|
||||
'rsi_upper': getattr(stats._strategy, 'rsi_upper', None),
|
||||
'short_ema': getattr(stats._strategy, 'short_ema', None),
|
||||
'long_ema': getattr(stats._strategy, 'long_ema', None),
|
||||
'atr_period': getattr(stats._strategy, 'atr_period', None),
|
||||
'stop_loss_atr_multiplier': getattr(stats._strategy, 'stop_loss_atr_multiplier', None),
|
||||
'return_pct': stats.get('Return [%]') if hasattr(stats, 'get') else None,
|
||||
'equity_final_$': stats.get('Equity Final [$]') if hasattr(stats, 'get') else None,
|
||||
'max_drawdown_pct': stats.get('Max. Drawdown [%]') if hasattr(stats, 'get') else None,
|
||||
'n_trades': stats.get('# Trades') if hasattr(stats, 'get') else None,
|
||||
'win_rate_pct': stats.get('Win Rate [%]') if hasattr(stats, 'get') else None,
|
||||
}
|
||||
|
||||
# Run a final backtest with the best-found parameters to export the full trades list
|
||||
best_params = {}
|
||||
for attr in ('rsi_period', 'rsi_lower', 'rsi_upper', 'short_ema', 'long_ema', 'atr_period', 'stop_loss_atr_multiplier'):
|
||||
if hasattr(stats._strategy, attr):
|
||||
best_params[attr] = getattr(stats._strategy, attr)
|
||||
|
||||
try:
|
||||
if best_params:
|
||||
print(f"Running final backtest for {symbol} with best params: {best_params}")
|
||||
final_stats = bt.run(**best_params)
|
||||
trades = final_stats.get('_trades')
|
||||
if trades is not None and not trades.empty:
|
||||
# add duration column and export
|
||||
trades['Duration'] = trades['ExitTime'] - trades['EntryTime']
|
||||
tag_name = report_tag if report_tag else strategy_cls.__name__
|
||||
trades_file = os.path.join(PATH_TO_OUTPUT, f"Backtest_Trades_{symbol}_{tag_name}.xlsx")
|
||||
trades.to_excel(trades_file, index=False)
|
||||
print(f"✅ Trades exported: {trades_file}")
|
||||
result['trades_file'] = trades_file
|
||||
except Exception as e:
|
||||
print(f"Failed to run final backtest for trades export: {e}")
|
||||
|
||||
return result
|
||||
|
||||
if __name__ == "__main__":
|
||||
run_optimized_backtest("AAPL")
|
||||
run_optimized_backtest("GOLD")
|
||||
Reference in New Issue
Block a user