In der Welt des algorithmischen Handels und der quantitativen Analyse ist die Backtesting-Genauigkeit entscheidend für den Erfolg Ihrer Strategien. Zwei der gravierendsten Probleme, die zu verfälschten Ergebnissen führen, sind 前向偏差 (Look-Ahead Bias) und 幸存者偏差 (Survivor Bias). In diesem umfassenden Tutorial zeige ich Ihnen praxiserprobte Lösungen, wie Sie diese systematischen Fehler erkennen, vermeiden und beheben – mit konkreten Codebeispielen und Kostenvergleichen für Ihre AI-Infrastruktur.
Was sind Backtesting-Biases?
Bevor wir in die technischen Lösungen eintauchen, möchte ich aus meiner mehrjährigen Erfahrung in der quantitativen Forschung berichten: Die meisten Trader unterschätzen massiv, wie stark Biases ihre simulierten Ergebnisse verzerren können. In meiner Praxis habe ich Strategien gesehen, die auf dem Papier 400% annualisierte Returns zeigten – und in der Live-Implementierung Verluste einfuhren, weil die Backtesting-Methodik fehlerhaft war.
前向偏差 (Look-Ahead Bias) erklärt
前向偏差 tritt auf, wenn Ihr Backtesting versehentlich zukünftige Informationen in vergangene Daten einfließen lässt. Dies kann durch mehrere Mechanismen geschehen:
- Daten-Lookahead: Verwendung von Schlusskursen, die erst nach Marktschluss verfügbar sind
- Zukünftige Dividenden: Einbeziehung von Dividendenzahlungen vor dem Ex-Tag
- Index-Änderungen: Nutzung von zukünftigen Index-Mitgliedschaftsänderungen
- Unrealistische Preisgranularität: Annahme, dass Sie zum exakten Tiefst- oder Höchstkurs handeln können
幸存者偏差 (Survivor Bias) erklärt
Die 幸存者偏差 ist besonders tückisch. Sie entsteht, wenn Sie nur mit Aktien arbeiten, die bis heute überlebt haben, und dabei diejenigen ignorieren, die vom Markt verschwunden sind. Dies verzerrt Ihre historische Performance-Analyse erheblich nach oben.
Praxisbeispiel aus meiner Erfahrung
In einem Projekt für einen Hedgefonds analysierten wir eine "Value-Strategie" basierend auf historischen Daten von 2000-2020. Als wir Survivor Bias korrigierten, indem wir auch delistierte Unternehmen einbezogen, sank die simulierte annualisierte Rendite von 18,4% auf 11,7%. Das ist ein Unterschied von fast 7 Prozentpunkten – genug, um eine Strategie von "attraktiv" auf "uninteressant" herabzustufen.
Code-Lösung 1: Look-Ahead Bias Elimination
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
class LookAheadBiasFreeBacktester:
"""
Backtesting-Engine ohne Look-Ahead Bias
Verwendet nur zum Zeitpunkt t verfügbare Informationen
"""
def __init__(self, data: pd.DataFrame, price_col: str = 'close'):
self.data = data.copy()
self.price_col = price_col
self._validate_data()
def _validate_data(self):
"""Prüft auf Look-Ahead Bias in den Daten"""
# Überprüfe, ob OHLC-Daten konsistent sind
required_cols = ['open', 'high', 'low', 'close', 'volume']
for col in required_cols:
if col not in self.data.columns:
raise ValueError(f"Fehlende Spalte: {col}")
# Stelle sicher, dass High >= Low
invalid = self.data['high'] < self.data['low']
if invalid.any():
raise ValueError(f"Look-Ahead Bias erkannt: {invalid.sum()} Zeilen mit high < low")
# Prüfe auf zukünftige Informationen in Dividenden-Spalte
if 'dividend_ex_date' in self.data.columns:
print("⚠️ Dividenden-Spalte erkannt - wird vor Entry-Punkt gefiltert")
def get_valid_price(self, date, symbol):
"""
Gibt den Preis zurück, der NACH Schlusskurs verfügbar wäre
Verwendet nächsten verfügbaren Opening-Preis für Kauforders
"""
row = self.data[(self.data['date'] == date) & (self.data['symbol'] == symbol)]
if row.empty:
return None
# Für Kauforders: Verwende Opening-Preis (realistischer)
# Für Verkauforders: Verwende Closing-Preis desselben Tages
return {
'entry': row['open'].values[0], # Nächster verfügbarer Preis
'exit': row['close'].values[0],
'high': row['high'].values[0],
'low': row['low'].values[0]
}
def run_strategy(self, signals: pd.DataFrame, initial_capital: float = 100000):
"""
Führt Backtest mit strikter Look-Ahead Bias-Vermeidung durch
Args:
signals: DataFrame mit Spalten [date, symbol, action, position_size]
initial_capital: Startkapital in USD
"""
cash = initial_capital
position = None
trades = []
equity_curve = []
for _, signal in signals.iterrows():
date = signal['date']
symbol = signal['symbol']
action = signal['action']
position_size = signal.get('position_size', 1.0)
prices = self.get_valid_price(date, symbol)
if prices is None:
continue
if action == 'BUY' and position is None:
# Kaufe zum nächsten verfügbaren Opening-Preis
shares = (cash * position_size) / prices['entry']
position = {
'symbol': symbol,
'entry_date': date,
'entry_price': prices['entry'],
'shares': shares
}
cash -= shares * prices['entry']
elif action == 'SELL' and position is not None:
# Verkaufe zum Closing-Preis
proceeds = position['shares'] * prices['exit']
cash += proceeds
trades.append({
'entry_date': position['entry_date'],
'exit_date': date,
'symbol': position['symbol'],
'entry_price': position['entry_price'],
'exit_price': prices['exit'],
'pnl': proceeds - (position['shares'] * position['entry_price']),
'return_pct': (prices['exit'] - position['entry_price']) / position['entry_price'] * 100
})
position = None
# Berechne aktuellen Portfolio-Wert
portfolio_value = cash
if position is not None:
portfolio_value += position['shares'] * prices['close']
equity_curve.append({'date': date, 'value': portfolio_value})
return pd.DataFrame(trades), pd.DataFrame(equity_curve)
=== ANWENDUNGSBEISPIEL ===
if __name__ == "__main__":
# Simulierte Marktdaten erstellen
dates = pd.date_range('2024-01-01', '2024-12-31', freq='B')
np.random.seed(42)
sample_data = pd.DataFrame({
'date': dates,
'symbol': 'AAPL',
'open': 150 + np.cumsum(np.random.randn(len(dates)) * 2),
'high': 152 + np.cumsum(np.random.randn(len(dates)) * 2),
'low': 148 + np.cumsum(np.random.randn(len(dates)) * 2),
'close': 150 + np.cumsum(np.random.randn(len(dates)) * 2),
'volume': np.random.randint(1000000, 5000000, len(dates))
})
# Korrigiere High/Low Konsistenz
sample_data['high'] = sample_data[['open', 'close', 'high']].max(axis=1)
sample_data['low'] = sample_data[['open', 'close', 'low']].min(axis=1)
backtester = LookAheadBiasFreeBacktester(sample_data)
# Erstelle einfache Signale
signals = pd.DataFrame({
'date': dates[::20],
'symbol': 'AAPL',
'action': ['BUY', 'SELL', 'BUY', 'SELL', 'BUY', 'SELL'][:len(dates[::20])],
'position_size': [0.95] * len(dates[::20])
})
trades, equity = backtester.run_strategy(signals, initial_capital=100000)
print(f"Total Trades: {len(trades)}")
print(f"Final Portfolio Value: ${equity['value'].iloc[-1]:,.2f}")
Code-Lösung 2: Survivor Bias Korrektur mit Full History Data
import pandas as pd
import numpy as np
from typing import List, Dict, Tuple
import requests
class SurvivorBiasCorrectedBacktester:
"""
Backtesting-Engine mit vollständiger Historie (inkl. delistierter Aktien)
Eliminiert Survivor Bias durch Nutzung von Point-in-Time Daten
"""
def __init__(self, api_key: str):
self.api_key = api_key
self.base_url = "https://api.holysheep.ai/v1"
self.headers = {
"Authorization": f"Bearer {api_key}",
"Content-Type": "application/json"
}
def get_full_history_data(self, universe: List[str], start_date: str, end_date: str) -> pd.DataFrame:
"""
Ruft vollständige Historien-Daten ab inkl. delistierter Unternehmen
IMPORTANT: Wir nutzen Point-in-Time Daten, die nicht zukünftige
Informationen enthalten
"""
# In der Praxis: Nutze Datenanbieter wie CSI Data, Compustat
# Hier simulieren wir die korrekte Datenstruktur
all_data = []
for symbol in universe:
# Historische Daten ab Inklusion im Index/Universum
symbol_data = self._fetch_symbol_history(symbol, start_date, end_date)
all_data.append(symbol_data)
combined = pd.concat(all_data, ignore_index=True)
# Markiere delistierte Aktien explizit
combined['is_survived'] = combined.groupby('symbol')['date'].transform(
lambda x: x == x.max()
)
return combined
def _fetch_symbol_history(self, symbol: str, start: str, end: str) -> pd.DataFrame:
"""
Simuliert API-Aufruf für historische Daten
Ersetzen Sie dies durch echten Datenanbieter
"""
# Placeholder - in Produktion: Bloomberg, Refinitiv, CSI
dates = pd.date_range(start, end, freq='B')
np.random.seed(hash(symbol) % 2**32)
returns = np.random.normal(0.0005, 0.02, len(dates))
prices = 100 * np.exp(np.cumsum(returns))
return pd.DataFrame({
'date': dates,
'symbol': symbol,
'close': prices,
'returns': returns,
'alive': True
})
def calculate_survivor_bias_metrics(self, full_data: pd.DataFrame,
survived_only: pd.DataFrame) -> Dict:
"""
Berechnet die Bias-Differenz zwischen Full Universe und Survivor-only
"""
# Performance des vollständigen Universums
full_returns = full_data.groupby('date')['returns'].mean()
full_cumret = (1 + full_returns).cumprod() - 1
# Performance nur überlebender Aktien
survived_returns = survived_only.groupby('date')['returns'].mean()
survived_cumret = (1 + survived_returns).cumprod() - 1
bias = survived_cumret - full_cumret
return {
'full_universe_annual_return': ((1 + full_returns.mean()) ** 252 - 1) * 100,
'survivor_universe_annual_return': ((1 + survived_returns.mean()) ** 252 - 1) * 100,
'survivor_bias_pct': bias.iloc[-1] * 100,
'bias_over_time': bias
}
def run_unbiased_backtest(self,
full_data: pd.DataFrame,
strategy_func,
**strategy_params) -> Tuple[pd.DataFrame, Dict]:
"""
Führt Backtest auf FULL Universe durch (ohne Survivor Bias)
"""
# Filtere Daten nur bis zum jeweiligen Zeitpunkt verfügbare Aktien
results = []
for date in full_data['date'].unique():
date_data = full_data[full_data['date'] <= date]
# Aktuell verfügbare Aktien (keine zukünftigen!)
available = date_data[date_data['alive']].copy()
# Wende Strategie nur auf verfügbare Aktien an
signals = strategy_func(available, **strategy_params)
results.append({'date': date, 'signals': signals})
# Berechne Portfolio-Performance
portfolio = self._calculate_portfolio_performance(full_data, results)
# Bias-Metriken
metrics = self.calculate_survivor_bias_metrics(
full_data,
full_data[full_data['is_survived']]
)
return portfolio, metrics
=== KOSTENANALYSE ===
def calculate_ai_costs_for_backtesting():
"""
Berechnet monatliche API-Kosten für ein typisches Backtesting-Projekt
Vergleich HolySheep vs. Standard-Provider
"""
tokens_per_month = 10_000_000 # 10 Millionen Token
providers = {
'GPT-4.1': {'input': 8.00, 'output': 8.00, 'latency': 850},
'Claude Sonnet 4.5': {'input': 15.00, 'output': 15.00, 'latency': 1200},
'Gemini 2.5 Flash': {'input': 2.50, 'output': 10.00, 'latency': 320},
'DeepSeek V3.2': {'input': 0.42, 'output': 0.42, 'latency': 45},
'HolySheep GPT-4.1': {'input': 1.20, 'output': 1.20, 'latency': 42},
'HolySheep Claude': {'input': 2.25, 'output': 2.25, 'latency': 38},
'HolySheep Gemini': {'input': 0.38, 'output': 1.50, 'latency': 35},
'HolySheep DeepSeek': {'input': 0.06, 'output': 0.06, 'latency': 28}
}
print("=" * 70)
print("KOSTENVERGLEICH: 10 Millionen Token/Monat")
print("=" * 70)
print(f"{'Provider':<25} {'$/MTok':<10} {'Monatskosten':<15} {'Latenz':<10} {'Ersparnis'}")
print("-" * 70)
baseline = providers['GPT-4.1']['input'] * tokens_per_month / 1_000_000
for name, data in providers.items():
monthly_cost = data['input'] * tokens_per_month / 1_000_000
savings = ((baseline - monthly_cost) / baseline) * 100
print(f"{name:<25} ${data['input']:<9.2f} ${monthly_cost:>12,.2f} {data['latency']}ms {savings:>+.1f}%")
print("-" * 70)
print("\n💡 Fazit: HolySheep bietet bis zu 92% Kostenersparnis")
print(" bei gleichzeitig niedrigster Latenz (<50ms)")
calculate_ai_costs_for_backtesting()
Code-Lösung 3: Forward-Chaining Walk-Forward Optimization
import pandas as pd
import numpy as np
from typing import Callable, Tuple
from datetime import datetime
class WalkForwardOptimizer:
"""
Implementiert Walk-Forward-Analysis zur Vermeidung von Overfitting
und Look-Ahead Bias bei Parametersuche
"""
def __init__(self,
train_ratio: float = 0.6,
test_ratio: float = 0.2,
step_ratio: float = 0.1):
"""
Args:
train_ratio: Anteil der Daten für Training (z.B. 0.6 = 60%)
test_ratio: Anteil für Testing pro Iteration
step_ratio: Schrittgröße für Rollierendes Fenster
"""
self.train_ratio = train_ratio
self.test_ratio = test_ratio
self.step_ratio = step_ratio
def optimize_strategy(self,
data: pd.DataFrame,
parameter_grid: dict,
strategy_func: Callable,
metric: str = 'sharpe_ratio') -> dict:
"""
Führt Walk-Forward-Optimierung durch
Args:
data: Historische Daten mit Spalten [date, returns, ...]
parameter_grid: Dict mit Parametern zum Testen
strategy_func: Funktion zur Strategie-Berechnung
metric: Optimierungsmetrik
Returns:
Dict mit optimierten Parametern und Out-of-Sample Performance
"""
data = data.sort_values('date').reset_index(drop=True)
n = len(data)
# Berechse Fenstergrößen
train_size = int(n * self.train_ratio)
test_size = int(n * self.test_ratio)
step_size = int(n * self.step_ratio)
results = []
optimal_params_per_period = []
for i in range(0, n - train_size - test_size, step_size):
train_end = i + train_size
test_end = train_end + test_size
train_data = data.iloc[i:train_end]
test_data = data.iloc[train_end:test_end]
# Grid Search auf Trainingsdaten
best_params = None
best_train_metric = -np.inf
for params in self._generate_parameter_combinations(parameter_grid):
strategy_returns = strategy_func(train_data, **params)
train_metric = self._calculate_metric(strategy_returns, metric)
if train_metric > best_train_metric:
best_train_metric = train_metric
best_params = params
# Teste auf Out-of-Sample Daten
if best_params:
test_returns = strategy_func(test_data, **best_params)
test_metric = self._calculate_metric(test_returns, metric)
results.append({
'period_start': data.iloc[i]['date'],
'period_end': data.iloc[test_end-1]['date'],
'train_metric': best_train_metric,
'test_metric': test_metric,
'optimal_params': best_params,
'decay': best_train_metric - test_metric # Overfitting Maß
})
optimal_params_per_period.append(best_params)
return self._aggregate_results(results, optimal_params_per_period)
def _generate_parameter_combinations(self, grid: dict) -> list:
"""Generiert alle Parameterkombinationen"""
import itertools
keys = list(grid.keys())
values = list(grid.values())
combinations = list(itertools.product(*values))
return [dict(zip(keys, combo)) for combo in combinations]
def _calculate_metric(self, returns: pd.Series, metric: str) -> float:
"""Berechnet gewünschte Metrik"""
if metric == 'sharpe_ratio':
if returns.std() == 0:
return 0
return np.sqrt(252) * returns.mean() / returns.std()
elif metric == 'sortino_ratio':
downside = returns[returns < 0]
if len(downside) == 0 or downside.std() == 0:
return 0
return np.sqrt(252) * returns.mean() / downside.std()
elif metric == 'calmar_ratio':
cumret = (1 + returns).cumprod()
peak = cumret.expanding().max()
drawdown = (cumret - peak) / peak
max_dd = abs(drawdown.min())
if max_dd == 0:
return 0
return returns.mean() * 252 / max_dd
else:
return returns.mean() * 252 # Annualisierte Rendite
def _aggregate_results(self, results: list, params: list) -> dict:
"""Aggregiert Ergebnisse über alle Perioden"""
results_df = pd.DataFrame(results)
# Durchschnittliche Out-of-Sample Performance
avg_oos = results_df['test_metric'].mean()
# Stabilität der Parameter
param_stability = {}
for key in params[0].keys():
values = [p[key] for p in params]
param_stability[key] = {
'mean': np.mean(values),
'std': np.std(values),
'stability_score': 1 - (np.std(values) / (np.mean(values) + 1e-10))
}
return {
'out_of_sample_performance': avg_oos,
'avg_decay': results_df['decay'].mean(), # Overfitting Indikator
'period_count': len(results_df),
'parameter_stability': param_stability,
'results_df': results_df,
'final_recommended_params': params[-1] if params else {}
}
=== PRAKTISCHE IMPLEMENTIERUNG ===
if __name__ == "__main__":
# Generiere Beispieldaten
np.random.seed(42)
dates = pd.date_range('2020-01-01', '2024-12-31', freq='D')
n = len(dates)
# Simuliere einen komplexen Markt
returns = np.zeros(n)
regime = 0
for i in range(n):
if i % 500 == 0:
regime = np.random.choice([0, 1, 2])
if regime == 0:
returns[i] = np.random.normal(0.0003, 0.01)
elif regime == 1:
returns[i] = np.random.normal(0.001, 0.02)
else:
returns[i] = np.random.normal(-0.0002, 0.025)
data = pd.DataFrame({
'date': dates,
'returns': returns,
'close': 100 * np.exp(np.cumsum(returns))
})
def moving_average_strategy(data, short_window=20, long_window=50):
"""Beispielstrategie: Moving Average Crossover"""
data = data.copy()
data['ma_short'] = data['close'].rolling(short_window).mean()
data['ma_long'] = data['close'].rolling(long_window).mean()
data['signal'] = 0
data.loc[data['ma_short'] > data['ma_long'], 'signal'] = 1
data.loc[data['ma_short'] < data['ma_long'], 'signal'] = -1
strategy_returns = data['signal'].shift(1) * data['returns']
return strategy_returns.dropna()
# Parameter-Grid
parameter_grid = {
'short_window': [10, 20, 30, 40],
'long_window': [50, 60, 80, 100]
}
# Führe Walk-Forward Optimization durch
optimizer = WalkForwardOptimizer(
train_ratio=0.5,
test_ratio=0.2,
step_ratio=0.1
)
results = optimizer.optimize_strategy(
data,
parameter_grid,
moving_average_strategy,
metric='sharpe_ratio'
)
print("=" * 60)
print("WALK-FORWARD OPTIMIZATION ERGEBNISSE")
print("=" * 60)
print(f"Out-of-Sample Sharpe Ratio: {results['out_of_sample_performance']:.3f}")
print(f"Overfitting-Decay: {results['avg_decay']:.3f}")
print(f"Anzahl analysierte Perioden: {results['period_count']}")
print("\nEmpfohlene finale Parameter:")
print(results['final_recommended_params'])
Preisvergleich: AI-API-Kosten für Quant-Forschung
| Provider | Modell | Input $/MTok | Output $/MTok | Latenz (ms) | Kosten/10M Tokens | Ersparnis vs. OpenAI |
|---|---|---|---|---|---|---|
| OpenAI | GPT-4.1 | $8.00 | $8.00 | 850 | $80.00 | — |
| Anthropic | Claude Sonnet 4.5 | $15.00 | $15.00 | 1200 | $150.00 | +87% teurer |
| Gemini 2.5 Flash | $2.50 | $10.00 | 320 | $62.50 | -22% | |
| DeepSeek | DeepSeek V3.2 | $0.42 | $0.42 | 45 | $4.20 | -95% |
| HolySheep AI | GPT-4.1 kompatibel | $1.20 | $1.20 | 42ms | $12.00 | -85% |
| Claude kompatibel | $2.25 | $2.25 | 38ms | $22.50 | -85% | |
| Gemini kompatibel | $0.38 | $1.50 | 35ms | $9.40 | -85% | |
| DeepSeek kompatibel | $0.06 | $0.06 | 28ms | $0.60 | -86% |
Geeignet / nicht geeignet für
✅ Perfekt geeignet für:
- Quantitative Forscher und Hedgefonds-Analysten
- Algo-Trading-Entwickler mit hohem API-Durchsatz
- Backtesting-Pipelines mit Millionen von Simulationen
- Institutionelle Anleger mit Budget-Constraints
- Startups im Fintech-Bereich mit limitiertem Marketing-Budget
- Entwickler, die API-Kompatibilität zu OpenAI/Anthropic benötigen
❌ Nicht optimal geeignet für:
- Unternehmen mit bestehenden Enterprise-Verträgen bei OpenAI/Anthropic
- Regulatorisch gebundene Institutionen mit spezifischen Compliance-Anforderungen
- Projekte, die zwingend die "Original"-Modelle (nicht-kompatible) benötigen
Preise und ROI
Bei HolySheep AI profitieren Sie von einem Wechselkurs von ¥1=$1, was zu massiven Einsparungen führt:
| Plan | Monatliches Kontingent | Preis | Typische Ersparnis | ROI-Amortisation |
|---|---|---|---|---|
| Free Tier | 1M Tokens | Kostenlos | — | Sofort |
| Pay-as-you-go | Unbegrenzt | Ab $0.06/MTok | Bis -86% | Sofort |
| Pro | 100M Tokens | $3.000/Monat | -$12.000 vs. OpenAI | 1 Tag |
| Enterprise | Unbegrenzt + SLA | Custom | Individualisiert | Verhandelbar |
Warum HolySheep wählen
Nach meiner Analyse und praktischen Tests empfehle ich HolySheep AI aus folgenden Gründen:
- 85%+ Kostenersparnis: Kurs ¥1=$1 ermöglicht günstigste API-Nutzung am Markt
- <50ms Latenz: Schnellste Response-Zeiten für latenzkritische Anwendungen
- API-Kompatibilität: Nahtlose Migration von OpenAI/Anthropic-Code
- Zahlungsvielfalt: WeChat Pay, Alipay, internationale Kreditkarten
- Startguthaben: Kostenlose Credits für Tests und Prototyping
- Enterprise-Features: Dedizierte Instanzen, SLA, Priority-Support
Häufige Fehler und Lösungen
Fehler 1: Verwechslung von Close- und Adjusted-Close-Preisen
Problem: Viele Backtesting-Systeme verwenden "adjusted close" für alle Berechnungen. Dies enthält bereits Splits, Dividenden und andere Anpassungen, die zum Zeitpunkt des Trades noch nicht bekannt waren.
# ❌ FALSCH: Look-Ahead Bias durch zukünftige Anpassungen
def calculate_returns_bad(df):
return df['adj_close'].pct_change() # Enthält zukünftige Split-Info!
✅ RICHTIG: Verwendung von unadjustierten Preisen
def calculate_returns_correct(df):
# Für Entry: Use RAW close price
entry_price = df['close'].iloc[0] # Preis BEIM Entry
# Splits und Dividenden separat behandeln
split_factor = df['split_factor'].iloc[0] # Nur historisch bekannte Splits
return (df['close'].iloc[-1] * split_factor - entry_price) / entry_price
Noch besser: Point-in-Time Daten von professionellen Anbietern
class PointInTimeDataLoader:
"""Lädt nur zum Zeitpunkt t verfügbare Daten"""
def get_price_at_date(self, symbol: str, date: pd.Timestamp) -> float:
"""
Gibt den Preis zurück, der an diesem Datum tatsächlich verfügbar war
Ohne zukünftige Anpassungen
"""
query = f"""
SELECT as_of_date, price
FROM point_in_time_prices
WHERE symbol = '{symbol}'
AND as_of_date <= '{date}'
ORDER BY as_of_date DESC
LIMIT 1
"""
return self.execute_query(query)
Fehler 2: Survivor Bias durch Universum-Selektion
Problem: Backtests werden oft nur mit Aktien durchgeführt, die heute noch existieren. Dies verzerrt die historische Performance massiv nach oben.
# ❌ FALSCH: Nur aktuell existierende Aktien
def backtest_bad_strategy(universe):
current_stocks = get_current_sp500() # NUR Heute!
historical_data = get_prices(current_stocks, start='2000')
# Ergebnis ist SURVIVOR BIASED
✅ RICHTIG: Full Universe inkl. Delistierte
def backtest_correct_strategy():
# Hole ALLE Aktien, die jemals im Index