diff --git a/backend/app/backtest/matrix.py b/backend/app/backtest/matrix.py index 6398797..d343682 100644 --- a/backend/app/backtest/matrix.py +++ b/backend/app/backtest/matrix.py @@ -686,7 +686,7 @@ def load_market_data_matrix_from_parquet( raise ValueError(f"matrix parquet root does not exist: {root}") available_start, available_end = _partition_date_bounds(root) if available_start is None or available_end is None: - raise ValueError("matrix parquet root contains no dated partitions") + raise ValueError("本地指标数据为空,请先在数据页面同步日K并完成指标计算") effective_start = max(start, available_start) effective_end = min(end, available_end) if effective_start > effective_end: diff --git a/backend/tests/backtest/test_matrix_strategy.py b/backend/tests/backtest/test_matrix_strategy.py index fc7815f..8f1b8be 100644 --- a/backend/tests/backtest/test_matrix_strategy.py +++ b/backend/tests/backtest/test_matrix_strategy.py @@ -294,6 +294,19 @@ def test_market_matrix_derives_live_raw_close_when_requested(): np.testing.assert_array_equal(snapshot.field("raw_close")[-1], snapshot.close[-1]) +def test_direct_parquet_matrix_reports_actionable_error_when_enriched_is_empty(tmp_path): + market_root = tmp_path / "kline_daily_enriched" + market_root.mkdir() + + with pytest.raises(ValueError, match="本地指标数据为空"): + load_market_data_matrix_from_parquet( + market_root, + date(2024, 1, 1), + date(2024, 1, 31), + field_columns=set(), + ) + + def test_direct_parquet_matrix_matches_panel_builder_and_reuses_mmap(tmp_path): market_root = tmp_path / "kline_daily_enriched" days = (date(2024, 1, 2), date(2024, 1, 3), date(2024, 1, 4)) diff --git a/frontend/src/pages/backtest/StrategyBacktest.tsx b/frontend/src/pages/backtest/StrategyBacktest.tsx index 2e26393..1f78dca 100644 --- a/frontend/src/pages/backtest/StrategyBacktest.tsx +++ b/frontend/src/pages/backtest/StrategyBacktest.tsx @@ -921,7 +921,12 @@ export function StrategyBacktest() { const isPending = backtestTask?.isPending ?? false const dataStatus = useDataStatus() - const earliestDate = dataStatus.data?.daily?.earliest_date ?? null + const backtestDataStatus = assetType === 'etf' + ? dataStatus.data?.etf_enriched + : dataStatus.data?.enriched + const earliestDate = backtestDataStatus?.earliest_date ?? null + const backtestDataUnavailable = dataStatus.isSuccess && !earliestDate + const backtestDataLabel = assetType === 'etf' ? 'ETF 指标数据' : '股票指标数据' const resetConfigFromDetail = (detail: StrategyDetail) => { setStrategyParams(strategyDefaultParams(detail)) @@ -981,7 +986,7 @@ export function StrategyBacktest() { }, [backtestTask]) const handleRun = () => { - if (!selectedStrategy) return + if (!selectedStrategy || backtestDataUnavailable) return const requestOverrides = detail ? normalizeStrategyOverrides(detail, overrides) : overrides @@ -1587,6 +1592,13 @@ export function StrategyBacktest() { )} + {backtestDataUnavailable && ( +