mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-27 15:37:44 +00:00
cbe1c52e00
Rename all source files, scripts, tests, documentation, and configuration from Predix/predix to NexQuant/nexquant across the entire codebase.
290 lines
8.9 KiB
Python
290 lines
8.9 KiB
Python
"""
|
|
NexQuant Backtesting Test Fixtures
|
|
Wiederverwendbare Test-Daten und Fixtures für alle Backtesting-Tests
|
|
"""
|
|
import pytest
|
|
import numpy as np
|
|
import pandas as pd
|
|
import tempfile
|
|
import os
|
|
from pathlib import Path
|
|
from datetime import datetime, timedelta
|
|
|
|
# Importiere die zu testenden Klassen
|
|
import sys
|
|
sys.path.insert(0, str(Path(__file__).parent.parent.parent))
|
|
from rdagent.components.backtesting.backtest_engine import BacktestMetrics, FactorBacktester
|
|
from rdagent.components.backtesting.results_db import ResultsDatabase
|
|
from rdagent.components.backtesting.risk_management import (
|
|
CorrelationAnalyzer, PortfolioOptimizer, AdvancedRiskManager
|
|
)
|
|
|
|
|
|
# =============================================================================
|
|
# FIXTURES FÜR BACKTEST METRICS
|
|
# =============================================================================
|
|
|
|
@pytest.fixture
|
|
def sample_factor_data():
|
|
"""Normale Faktor-Daten für Standard-Tests"""
|
|
np.random.seed(42)
|
|
n = 252
|
|
dates = pd.date_range(start='2024-01-01', periods=n, freq='B')
|
|
factor_values = pd.Series(np.random.randn(n), index=dates, name='factor')
|
|
forward_returns = pd.Series(np.random.randn(n) * 0.01 + 0.0001, index=dates, name='fwd_ret')
|
|
return factor_values, forward_returns
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_returns_data():
|
|
"""Returns-Daten für Sharpe und Drawdown Tests"""
|
|
np.random.seed(42)
|
|
n = 252
|
|
dates = pd.date_range(start='2024-01-01', periods=n, freq='B')
|
|
returns = pd.Series(np.random.randn(n) * 0.01 + 0.0005, index=dates)
|
|
equity = (1 + returns).cumprod()
|
|
return returns, equity
|
|
|
|
|
|
@pytest.fixture
|
|
def backtest_metrics():
|
|
"""BacktestMetrics Instanz mit Standard-Parametern"""
|
|
return BacktestMetrics(risk_free_rate=0.02)
|
|
|
|
|
|
# =============================================================================
|
|
# FIXTURES FÜR EDGE CASES
|
|
# =============================================================================
|
|
|
|
@pytest.fixture
|
|
def empty_data():
|
|
"""Leere Daten für Edge-Case Tests"""
|
|
return pd.Series([], dtype=float), pd.Series([], dtype=float)
|
|
|
|
|
|
@pytest.fixture
|
|
def nan_data():
|
|
"""Daten mit vielen NaN-Werten"""
|
|
np.random.seed(42)
|
|
n = 100
|
|
dates = pd.date_range(start='2024-01-01', periods=n, freq='B')
|
|
factor = pd.Series([np.nan] * 50 + list(np.random.randn(50)), index=dates)
|
|
fwd_ret = pd.Series(list(np.random.randn(50)) + [np.nan] * 50, index=dates)
|
|
return factor, fwd_ret
|
|
|
|
|
|
@pytest.fixture
|
|
def insufficient_data():
|
|
"""Zu wenig Daten (< 10 Punkte)"""
|
|
np.random.seed(42)
|
|
n = 5
|
|
dates = pd.date_range(start='2024-01-01', periods=n, freq='B')
|
|
factor = pd.Series(np.random.randn(n), index=dates)
|
|
fwd_ret = pd.Series(np.random.randn(n), index=dates)
|
|
return factor, fwd_ret
|
|
|
|
|
|
@pytest.fixture
|
|
def extreme_values_data():
|
|
"""Daten mit Extremwerten"""
|
|
np.random.seed(42)
|
|
n = 252
|
|
dates = pd.date_range(start='2024-01-01', periods=n, freq='B')
|
|
factor = pd.Series(np.random.randn(n), index=dates)
|
|
factor.iloc[50] = 1000 # Extremwert
|
|
factor.iloc[100] = -1000 # Extremwert negativ
|
|
fwd_ret = pd.Series(np.random.randn(n) * 0.01, index=dates)
|
|
return factor, fwd_ret
|
|
|
|
|
|
@pytest.fixture
|
|
def constant_data():
|
|
"""Konstante Daten (Std = 0)"""
|
|
n = 252
|
|
dates = pd.date_range(start='2024-01-01', periods=n, freq='B')
|
|
factor = pd.Series([1.0] * n, index=dates)
|
|
fwd_ret = pd.Series([0.001] * n, index=dates)
|
|
return factor, fwd_ret
|
|
|
|
|
|
# =============================================================================
|
|
# FIXTURES FÜR DATABASE TESTS
|
|
# =============================================================================
|
|
|
|
@pytest.fixture
|
|
def temp_db_path():
|
|
"""Temporäre Datenbank für Tests"""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
db_path = os.path.join(tmpdir, 'test_backtest.db')
|
|
yield db_path
|
|
|
|
|
|
@pytest.fixture
|
|
def results_database(temp_db_path):
|
|
"""ResultsDatabase Instanz mit temporärer DB"""
|
|
db = ResultsDatabase(db_path=temp_db_path)
|
|
yield db
|
|
db.close()
|
|
|
|
|
|
@pytest.fixture
|
|
def populated_database(results_database):
|
|
"""Datenbank mit Test-Daten befüllt"""
|
|
db = results_database
|
|
|
|
# Faktoren hinzufügen
|
|
db.add_factor("Momentum", "price_based")
|
|
db.add_factor("MeanReversion", "price_based")
|
|
db.add_factor("Volatility", "risk_based")
|
|
db.add_factor("Volume", "volume_based")
|
|
db.add_factor("ML_Factor", "ml_based")
|
|
|
|
# Backtest-Ergebnisse hinzufügen
|
|
db.add_backtest("Momentum", {
|
|
'ic': 0.08, 'sharpe_ratio': 1.5, 'annualized_return': 0.12,
|
|
'max_drawdown': -0.08, 'win_rate': 0.55
|
|
})
|
|
db.add_backtest("MeanReversion", {
|
|
'ic': 0.05, 'sharpe_ratio': 1.2, 'annualized_return': 0.08,
|
|
'max_drawdown': -0.05, 'win_rate': 0.52
|
|
})
|
|
db.add_backtest("Volatility", {
|
|
'ic': -0.03, 'sharpe_ratio': 0.8, 'annualized_return': 0.04,
|
|
'max_drawdown': -0.03, 'win_rate': 0.48
|
|
})
|
|
db.add_backtest("ML_Factor", {
|
|
'ic': 0.12, 'sharpe_ratio': 2.1, 'annualized_return': 0.18,
|
|
'max_drawdown': -0.10, 'win_rate': 0.60
|
|
})
|
|
|
|
# Loop-Ergebnisse hinzufügen
|
|
db.add_loop(1, 4, 6, 0.08, "completed")
|
|
db.add_loop(2, 5, 5, 0.10, "completed")
|
|
db.add_loop(3, 3, 7, 0.05, "completed")
|
|
|
|
return db
|
|
|
|
|
|
# =============================================================================
|
|
# FIXTURES FÜR RISK MANAGEMENT TESTS
|
|
# =============================================================================
|
|
|
|
@pytest.fixture
|
|
def sample_returns_matrix():
|
|
"""Returns-Matrix für Korrelations-Analyse"""
|
|
np.random.seed(42)
|
|
n = 252
|
|
dates = pd.date_range(start='2024-01-01', periods=n, freq='B')
|
|
columns = ['Mom', 'MeanRev', 'Vol', 'Volu', 'ML']
|
|
|
|
# Erzeuge korrelierte Returns
|
|
data = np.random.randn(n, 5)
|
|
data[:, 0] = data[:, 1] * 0.3 + data[:, 0] * 0.7 # Mom korreliert mit MeanRev
|
|
data[:, 3] = data[:, 2] * 0.5 + data[:, 3] * 0.5 # Volu korreliert mit Vol
|
|
|
|
return pd.DataFrame(data, columns=columns, index=dates)
|
|
|
|
|
|
@pytest.fixture
|
|
def correlation_analyzer():
|
|
"""CorrelationAnalyzer Instanz"""
|
|
return CorrelationAnalyzer(lookback=60)
|
|
|
|
|
|
@pytest.fixture
|
|
def portfolio_optimizer():
|
|
"""PortfolioOptimizer Instanz"""
|
|
return PortfolioOptimizer()
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_expected_returns():
|
|
"""Erwartete Returns für Portfolio-Optimierung"""
|
|
return pd.Series({
|
|
'Mom': 0.10, 'MeanRev': 0.08, 'Vol': 0.06,
|
|
'Volu': 0.07, 'ML': 0.12
|
|
})
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_covariance_matrix(sample_returns_matrix):
|
|
"""Kovarianz-Matrix aus Returns"""
|
|
return sample_returns_matrix.cov() * 252
|
|
|
|
|
|
@pytest.fixture
|
|
def risk_manager():
|
|
"""AdvancedRiskManager Instanz"""
|
|
return AdvancedRiskManager(max_pos=0.2, max_lev=5.0, max_dd=0.20)
|
|
|
|
|
|
@pytest.fixture
|
|
def sample_weights():
|
|
"""Test-Gewichtungen"""
|
|
return np.array([0.25, 0.20, 0.15, 0.20, 0.20])
|
|
|
|
|
|
# =============================================================================
|
|
# FIXTURES FÜR BACKTESTER
|
|
# =============================================================================
|
|
|
|
@pytest.fixture
|
|
def factor_backtester():
|
|
"""FactorBacktester Instanz mit temporärem Output-Verzeichnis"""
|
|
with tempfile.TemporaryDirectory() as tmpdir:
|
|
backtester = FactorBacktester()
|
|
backtester.results_path = Path(tmpdir)
|
|
yield backtester
|
|
|
|
|
|
# =============================================================================
|
|
# ZUSÄTZLICHE HILFS-FIXTURES
|
|
# =============================================================================
|
|
|
|
@pytest.fixture
|
|
def realistic_market_data():
|
|
"""Realistischere Markt-Daten mit typischen Eigenschaften"""
|
|
np.random.seed(42)
|
|
n = 504 # 2 Jahre
|
|
dates = pd.date_range(start='2023-01-01', periods=n, freq='B')
|
|
|
|
# Faktor mit etwas Autokorrelation (wie echte Faktoren)
|
|
factor = pd.Series(index=dates)
|
|
factor.iloc[0] = 0
|
|
for i in range(1, n):
|
|
factor.iloc[i] = 0.3 * factor.iloc[i-1] + np.random.randn() * 0.7
|
|
|
|
# Forward Returns mit leichtem positiven Drift
|
|
fwd_ret = pd.Series(np.random.randn(n) * 0.015 + 0.0002, index=dates)
|
|
|
|
# Füge einige Ausreißer hinzu (wie bei echten Marktdaten)
|
|
fwd_ret.iloc[50] = -0.05 # Crash-Tag
|
|
fwd_ret.iloc[150] = 0.04 # Rally-Tag
|
|
|
|
return factor, fwd_ret
|
|
|
|
|
|
@pytest.fixture
|
|
def zero_variance_returns():
|
|
"""Returns mit Varianz = 0 (für Edge-Case Tests)"""
|
|
n = 100
|
|
dates = pd.date_range(start='2024-01-01', periods=n, freq='B')
|
|
returns = pd.Series([0.001] * n, index=dates)
|
|
equity = (1 + returns).cumprod()
|
|
return returns, equity
|
|
|
|
|
|
@pytest.fixture
|
|
def negative_equity_data():
|
|
"""Equity-Daten mit Drawdowns"""
|
|
np.random.seed(42)
|
|
n = 252
|
|
dates = pd.date_range(start='2024-01-01', periods=n, freq='B')
|
|
|
|
# Erzeuge Equity mit signifikantem Drawdown
|
|
returns = pd.Series(np.random.randn(n) * 0.02, index=dates)
|
|
returns.iloc[50:80] = -0.03 # Drawdown-Periode
|
|
equity = (1 + returns).cumprod()
|
|
|
|
return returns, equity
|