diff --git a/sanguo_backtest/cta_engine.py b/sanguo_backtest/cta_engine.py index 70991b2..627d2a8 100644 --- a/sanguo_backtest/cta_engine.py +++ b/sanguo_backtest/cta_engine.py @@ -79,14 +79,14 @@ def run_cta_backtest(strategy_class, symbol: str, params: dict, start: str, end: # Set parameters with A-share specific values engine.set_parameters( vt_symbol=vt_symbol, - interval="1d", # Daily interval for A-shares + interval="d", # Interval.DAILY.value — vnpy enum uses "d" not "1d" start=start_dt, end=end_dt, rate=0.001, # Commission rate (0.1% for A-shares) slippage=0, # No slippage for simplicity size=1, # Contract size (1 for stocks) pricetick=0.01, # Minimum price tick (0.01 yuan for A-shares) - capital=0 # No initial capital limit + capital=1_000_000 # Starting capital — 0 causes instant liquidation on first trade ) # Add strategy @@ -98,8 +98,15 @@ def run_cta_backtest(strategy_class, symbol: str, params: dict, start: str, end: # Run backtesting engine.run_backtesting() - # Calculate statistics - statistics = engine.calculate_result() + # Calculate statistics — calculate_result() returns a daily DataFrame, + # calculate_statistics(df) returns the stats dict (sharpe/drawdown/etc.) + daily_df = engine.calculate_result() + raw_stats = engine.calculate_statistics(daily_df, output=False) or {} + # Ensure JSON-serializable (vnpy may include Timestamp / non-numeric values) + statistics = { + k: (v if isinstance(v, (int, float, str, bool)) or v is None else str(v)) + for k, v in raw_stats.items() + } # Get daily results for equity curve daily_results = engine.get_all_daily_results() diff --git a/scripts/diag_cta.py b/scripts/diag_cta.py new file mode 100644 index 0000000..039a003 --- /dev/null +++ b/scripts/diag_cta.py @@ -0,0 +1,51 @@ +"""Diagnostic: real CTA backtest of DoubleMaStrategy on 600000 (A-share daily). +Guarded entry for spawn. Throwaway.""" +import sys +import os +import traceback + +_VNPY_SRC = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "vnpy_v4.4.0")) +_REPO = os.path.dirname(_VNPY_SRC) +for _p in (_REPO, _VNPY_SRC): + if _p not in sys.path: + sys.path.insert(0, _p) + + +def main(): + # Configure vnpy DB to the real quant_trading.db BEFORE engine.load_data() + from vnpy.trader.setting import SETTINGS + from sanguo_data.config import load_config + cfg = load_config("/app/config/data_platform.yaml") + SETTINGS["database.name"] = "sqlite" + SETTINGS["database.database"] = cfg.data_paths["vnpy_db"] + print("DB:", SETTINGS["database.database"]) + + from vnpy_ctastrategy.strategies.double_ma_strategy import DoubleMaStrategy + print("DoubleMaStrategy.parameters:", getattr(DoubleMaStrategy, "parameters", "?")) + + from sanguo_backtest.cta_engine import run_cta_backtest + # Classic double-MA params (vnpy example defaults); fixed_size=1 + params = {"fast_window": 10, "slow_window": 20, "fixed_size": 1} + + print(f"run_cta_backtest DoubleMaStrategy on 600000, 2024-01-01..2024-06-30, params={params}") + try: + result = run_cta_backtest( + DoubleMaStrategy, "600000", params, + "2024-01-01", "2024-06-30", cfg, "/tmp/cta_results.db", + ) + print("=== status:", result.status) + print("=== statistics ===") + if result.statistics: + for k, v in result.statistics.items(): + print(f" {k}: {v}") + else: + print(" (empty)") + if result.status == "failed": + print("=== error ===") + print(result.error_msg) + except Exception: + traceback.print_exc() + + +if __name__ == "__main__": + main() diff --git a/tests/backtest/test_cta_engine.py b/tests/backtest/test_cta_engine.py index a10274a..4c69e2f 100644 --- a/tests/backtest/test_cta_engine.py +++ b/tests/backtest/test_cta_engine.py @@ -15,9 +15,11 @@ class TestRunCtaBacktest: mock_strategy_class = Mock() mock_strategy_class.__name__ = "TestStrategy" - # Mock BacktestingEngine + # Mock BacktestingEngine — calculate_result() returns daily_df (DataFrame), + # calculate_statistics(df) returns the stats dict (vnpy API, matches cta_engine) mock_engine = MagicMock() - mock_engine.calculate_result.return_value = { + mock_engine.calculate_result.return_value = MagicMock(name="daily_df") + mock_engine.calculate_statistics.return_value = { "total_return": 0.15, "sharpe_ratio": 1.2, "max_drawdown": -0.08, @@ -68,6 +70,7 @@ class TestRunCtaBacktest: mock_engine.load_data.assert_called_once() mock_engine.run_backtesting.assert_called_once() mock_engine.calculate_result.assert_called_once() + mock_engine.calculate_statistics.assert_called_once() def test_run_cta_backtest_handles_exception(self, temp_db_path): """Test that exceptions during backtest are handled properly."""