mirror of
https://github.com/NicolasBohn/NexQuant.git
synced 2026-07-29 00:17:44 +00:00
feat: unified backtest engine, LLM error handling, strategy refactor
- Add vbt_backtest.py as single source of truth for all metric formulas (Sharpe, drawdown, IC, transaction costs) — backtest_engine.py and strategy_orchestrator.py now delegate to it - Add LLMUnavailableError to exception.py; rd_loop.py catches it at the proposal stage and raises LoopResumeError to avoid corrupting trace history with None hypotheses - Guard record() against None exp/hypothesis so loop resets leave trace.hist in a consistent state - Refactor strategy_orchestrator and optuna_optimizer to use unified backtest path; remove duplicate metric calculation code - Add predix_rebacktest_unified.py script for offline re-evaluation - Update tests and README Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -86,20 +86,26 @@ class TestBacktestMetricsCalculateIC:
|
||||
class TestBacktestMetricsCalculateSharpe:
|
||||
"""Tests für BacktestMetrics.calculate_sharpe()"""
|
||||
|
||||
def test_calculate_sharpe_normal_data(self, backtest_metrics, sample_returns_data):
|
||||
"""Sharpe Ratio mit normalen Daten sollte korrekt berechnet werden"""
|
||||
returns, equity = sample_returns_data
|
||||
sharpe = backtest_metrics.calculate_sharpe(returns)
|
||||
|
||||
# Sharpe sollte im typischen Bereich liegen (-5 bis 5)
|
||||
def test_calculate_sharpe_normal_data(self, sample_returns_data):
|
||||
"""Sharpe Ratio mit Daily-Daten sollte im typischen Bereich liegen."""
|
||||
from rdagent.components.backtesting.backtest_engine import BacktestMetrics
|
||||
|
||||
returns, _ = sample_returns_data
|
||||
# sample_returns_data is business-daily → use daily annualization.
|
||||
bm_daily = BacktestMetrics(risk_free_rate=0.02, bars_per_year=252)
|
||||
sharpe = bm_daily.calculate_sharpe(returns)
|
||||
|
||||
assert -5 <= sharpe <= 5, f"Sharpe {sharpe} liegt außerhalb typischen Bereichs"
|
||||
|
||||
def test_calculate_sharpe_annualized_vs_raw(self, backtest_metrics, sample_returns_data):
|
||||
"""Annualisierte Sharpe sollte sqrt(252) * raw Sharpe sein"""
|
||||
returns, equity = sample_returns_data
|
||||
sharpe_raw = backtest_metrics.calculate_sharpe(returns, annualize=False)
|
||||
sharpe_ann = backtest_metrics.calculate_sharpe(returns, annualize=True)
|
||||
|
||||
def test_calculate_sharpe_annualized_vs_raw(self, sample_returns_data):
|
||||
"""Annualisierte Sharpe = √(bars_per_year) * raw Sharpe — convention-agnostic."""
|
||||
from rdagent.components.backtesting.backtest_engine import BacktestMetrics
|
||||
|
||||
returns, _ = sample_returns_data
|
||||
bm_daily = BacktestMetrics(risk_free_rate=0.02, bars_per_year=252)
|
||||
sharpe_raw = bm_daily.calculate_sharpe(returns, annualize=False)
|
||||
sharpe_ann = bm_daily.calculate_sharpe(returns, annualize=True)
|
||||
|
||||
expected_ann = sharpe_raw * np.sqrt(252)
|
||||
assert abs(sharpe_ann - expected_ann) < 1e-10, \
|
||||
f"Annualisierte Sharpe {sharpe_ann} != erwartet {expected_ann}"
|
||||
@@ -127,13 +133,16 @@ class TestBacktestMetricsCalculateSharpe:
|
||||
# Die Implementierung gibt keinen NaN zurück wenn std != 0
|
||||
assert np.isfinite(sharpe) or np.isnan(sharpe), "Sharpe sollte finite oder NaN sein"
|
||||
|
||||
def test_calculate_sharpe_negative_returns(self, backtest_metrics):
|
||||
"""Sharpe sollte mit negativen Returns korrekt umgehen"""
|
||||
def test_calculate_sharpe_negative_returns(self):
|
||||
"""Sharpe sollte mit negativen Daily-Returns korrekt umgehen"""
|
||||
from rdagent.components.backtesting.backtest_engine import BacktestMetrics
|
||||
|
||||
n = 100
|
||||
dates = pd.date_range(start='2024-01-01', periods=n, freq='B')
|
||||
returns = pd.Series(np.random.randn(n) * 0.02 - 0.001, index=dates)
|
||||
|
||||
sharpe = backtest_metrics.calculate_sharpe(returns)
|
||||
|
||||
bm_daily = BacktestMetrics(risk_free_rate=0.02, bars_per_year=252)
|
||||
sharpe = bm_daily.calculate_sharpe(returns)
|
||||
assert -5 <= sharpe <= 5, f"Sharpe {sharpe} liegt außerhalb typischen Bereichs"
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user