diff --git a/frontend/src/views/Dashboard.vue b/frontend/src/views/Dashboard.vue
index dc8c847..3ced6ca 100644
--- a/frontend/src/views/Dashboard.vue
+++ b/frontend/src/views/Dashboard.vue
@@ -108,7 +108,7 @@ function statusLabel(s: string): string {
{{ typeLabel(row.type) }}
-
+
{{ statusLabel(row.status) }}
diff --git a/sanguo_api/routes.py b/sanguo_api/routes.py
index fa51c01..f62971a 100644
--- a/sanguo_api/routes.py
+++ b/sanguo_api/routes.py
@@ -139,8 +139,9 @@ def get_result(task_id: str):
# Extract relative metrics from statistics
relative_metrics = {}
relative_fields = [
+ "total_return", "annual_return", "sharpe_ratio", "max_drawdown",
"alpha", "beta", "sortino_ratio", "information_ratio",
- "annual_volatility", "benchmark_return", "benchmark_volatility"
+ "annual_volatility", "benchmark_return", "benchmark_volatility",
]
for field in relative_fields:
if field in r.statistics:
diff --git a/sanguo_backtest/cta_engine.py b/sanguo_backtest/cta_engine.py
index dd2ba87..ff315c4 100644
--- a/sanguo_backtest/cta_engine.py
+++ b/sanguo_backtest/cta_engine.py
@@ -48,7 +48,7 @@ def guess_exchange(symbol: str) -> Exchange:
return Exchange("SSE")
-def run_cta_backtest(strategy_class, symbol: str, params: dict, start: str, end: str, cfg, db_path: str, benchmark: str = "hs300") -> BacktestResult:
+def run_cta_backtest(strategy_class, symbol: str, params: dict, start: str, end: str, cfg, db_path: str, benchmark: str = "hs300", task_id: str | None = None) -> BacktestResult:
"""
Run CTA strategy backtest using vnpy_ctastrategy BacktestingEngine.
@@ -60,12 +60,16 @@ def run_cta_backtest(strategy_class, symbol: str, params: dict, start: str, end:
end: Backtest end date (YYYY-MM-DD format)
cfg: Configuration object (may contain data paths)
db_path: SQLite database path for saving results
+ benchmark: Benchmark code (hs300/zz500)
+ task_id: Optional task ID from runner (reused as the persisted task_id so
+ runner-id == DB task_id; if omitted a fresh uuid is generated).
Returns:
BacktestResult: Result object with backtest statistics and status
"""
- # Generate unique task ID
- task_id = f"cta_{uuid.uuid4().hex[:8]}"
+ # Use runner-provided task_id (durable, single id across pool/DB/URL) or generate
+ if not task_id:
+ task_id = f"cta_{uuid.uuid4().hex[:8]}"
try:
# Lazy import of BacktestingEngine (local env may not have vnpy_ctastrategy)
diff --git a/sanguo_orchestrator/runner.py b/sanguo_orchestrator/runner.py
index 5148490..d0be289 100644
--- a/sanguo_orchestrator/runner.py
+++ b/sanguo_orchestrator/runner.py
@@ -3,6 +3,7 @@ Orchestrator for task coordination and execution
Manages backtesting tasks with lazy imports
"""
import asyncio
+import uuid
from concurrent.futures import Future
from .pool import TaskPool
from .task import TaskState
@@ -32,7 +33,9 @@ class Orchestrator:
async def submit_cta(self, strategy_class, symbol: str, params: dict,
start: str, end: str, cfg, benchmark: str = "hs300") -> str:
"""Submit a CTA backtesting task asynchronously"""
- task_id = f"cta_{symbol}_{id(params)}"
+ # Stable uuid up front → reused as the persisted DB task_id, so runner-id ==
+ # DB task_id (durable across restarts; previously used id(params) memory addr).
+ task_id = f"cta_{uuid.uuid4().hex[:8]}"
self.pool.submit(task_id, "cta")
self._pending[task_id] = dict(
strategy_class=strategy_class,
@@ -48,7 +51,7 @@ class Orchestrator:
spec = self._pending[task_id]
fut: Future = self.pool.submit_work(
task_id, _cta_worker, spec["strategy_class"], spec["symbol"],
- spec["params"], spec["start"], spec["end"], spec["cfg"], spec["benchmark"], self.db_path
+ spec["params"], spec["start"], spec["end"], spec["cfg"], spec["benchmark"], self.db_path, task_id
)
task = self.pool.get_task(task_id)
@@ -165,10 +168,10 @@ class Orchestrator:
# Module-level worker functions (must be top-level for ProcessPoolExecutor pickle)
-def _cta_worker(strategy_class, symbol: str, params: dict, start: str, end: str, cfg, benchmark: str, db_path: str) -> any:
+def _cta_worker(strategy_class, symbol: str, params: dict, start: str, end: str, cfg, benchmark: str, db_path: str, task_id: str) -> any:
"""Worker for CTA backtest (lazy import, spawn-friendly)"""
from sanguo_backtest.cta_engine import run_cta_backtest
- return run_cta_backtest(strategy_class, symbol, params, start, end, cfg, db_path, benchmark=benchmark)
+ return run_cta_backtest(strategy_class, symbol, params, start, end, cfg, db_path, benchmark=benchmark, task_id=task_id)
def _opt_worker(strategy_class, symbol: str, grid: dict, start: str, end: str, cfg, db_path: str) -> any:
diff --git a/tests/orchestrator/test_runner.py b/tests/orchestrator/test_runner.py
index ca1e1f7..9837aeb 100644
--- a/tests/orchestrator/test_runner.py
+++ b/tests/orchestrator/test_runner.py
@@ -52,7 +52,9 @@ class TestOrchestrator:
assert orchestrator._pending[task_id]["end"] == end
assert orchestrator._pending[task_id]["cfg"] == cfg
- assert task_id.startswith("cta_AAPL_")
+ # Durable uuid format: cta_<8hex> (reused as persisted DB task_id)
+ assert task_id.startswith("cta_")
+ assert task_id != "cta_AAPL_" and len(task_id) == len("cta_") + 8
@patch('sanguo_orchestrator.runner.TaskPool')
def test_get_status(self, mock_pool_class):
@@ -144,7 +146,7 @@ class TestOrchestratorAsync:
Mock(), "600000", {}, "2024-01-01", "2024-06-30", cfg=Mock()
)
- assert task_id.startswith("cta_600000")
+ assert task_id.startswith("cta_") and len(task_id) == len("cta_") + 8
orchestrator.pool.executor.submit.assert_called_once()
@pytest.mark.asyncio