Files
NexQuant/test/backtesting/conftest.py
T
TPTBusiness cbe1c52e00 refactor: rename project from Predix to NexQuant
Rename all source files, scripts, tests, documentation, and configuration
from Predix/predix to NexQuant/nexquant across the entire codebase.
2026-05-09 17:48:22 +02:00

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