From 922ef89c1ed55c2cdd82b06b8edfde6ccf2f0037 Mon Sep 17 00:00:00 2001 From: shokollm <270575765+shokollm@users.noreply.github.com> Date: Fri, 10 Apr 2026 10:43:04 +0000 Subject: [PATCH] feat: add backtest progress tracking and fix stop functionality --- src/backend/app/api/backtest.py | 12 +++++- src/backend/app/db/schemas.py | 1 + src/backend/app/services/backtest/engine.py | 16 +++++++ src/frontend/src/lib/api/types.ts | 3 +- .../src/routes/bot/[id]/backtest/+page.svelte | 42 ++++++++++++++++++- 5 files changed, 70 insertions(+), 4 deletions(-) diff --git a/src/backend/app/api/backtest.py b/src/backend/app/api/backtest.py index 747a005..66c46ef 100644 --- a/src/backend/app/api/backtest.py +++ b/src/backend/app/api/backtest.py @@ -169,6 +169,11 @@ def get_backtest( status_code=status.HTTP_404_NOT_FOUND, detail="Backtest not found" ) + # Add progress from running engine if available + if backtest.status == "running" and run_id in running_backtests: + engine = running_backtests[run_id] + backtest.progress = engine.progress + return backtest @@ -226,7 +231,12 @@ def stop_backtest( if run_id in running_backtests: engine = running_backtests[run_id] - asyncio.create_task(engine.stop()) + engine.running = False # Direct sync access to running flag + backtest.status = "stopped" + backtest.ended_at = datetime.utcnow() + db.commit() + elif backtest.status == "running": + # Engine already finished but status not updated backtest.status = "stopped" backtest.ended_at = datetime.utcnow() db.commit() diff --git a/src/backend/app/db/schemas.py b/src/backend/app/db/schemas.py index c89f963..fffe017 100644 --- a/src/backend/app/db/schemas.py +++ b/src/backend/app/db/schemas.py @@ -90,6 +90,7 @@ class BacktestResponse(BaseModel): status: str config: dict result: Optional[dict] + progress: Optional[int] = None class Config: from_attributes = True diff --git a/src/backend/app/services/backtest/engine.py b/src/backend/app/services/backtest/engine.py index 6229790..07da8ee 100644 --- a/src/backend/app/services/backtest/engine.py +++ b/src/backend/app/services/backtest/engine.py @@ -31,6 +31,8 @@ class BacktestEngine: self.entry_time: Optional[int] = None self.trades: List[Dict[str, Any]] = [] self.running = False + self.progress = 0 + self.total_klines = 0 async def run(self) -> Dict[str, Any]: self.running = True @@ -98,10 +100,13 @@ class BacktestEngine: return self.results async def _process_klines(self, klines: List[Dict[str, Any]]): + self.total_klines = len(klines) for i, kline in enumerate(klines): if not self.running: break + self.progress = int((i / self.total_klines) * 100) if self.total_klines > 0 else 0 + price = float(kline.get("close", 0)) if price <= 0: continue @@ -384,6 +389,8 @@ class BacktestEngine: async def stop(self): self.running = False + self.progress = 0 + self.total_klines = 0 self.status = "stopped" self._calculate_metrics() @@ -393,4 +400,13 @@ class BacktestEngine: "status": self.status, "results": self.results, "signals": self.signals, + "progress": self.progress, + "total_klines": self.total_klines, + } + + def get_status(self) -> Dict[str, Any]: + return { + "status": self.status, + "progress": self.progress, + "total_klines": self.total_klines, } diff --git a/src/frontend/src/lib/api/types.ts b/src/frontend/src/lib/api/types.ts index 1b5761f..8fd857d 100644 --- a/src/frontend/src/lib/api/types.ts +++ b/src/frontend/src/lib/api/types.ts @@ -62,9 +62,10 @@ export interface Backtest { bot_id: string; started_at: string; ended_at: string | null; - status: 'running' | 'completed' | 'failed'; + status: 'running' | 'completed' | 'failed' | 'stopped'; config: BacktestConfig; result: BacktestResult | null; + progress?: number; } export interface BacktestConfig { diff --git a/src/frontend/src/routes/bot/[id]/backtest/+page.svelte b/src/frontend/src/routes/bot/[id]/backtest/+page.svelte index e9032b7..993057e 100644 --- a/src/frontend/src/routes/bot/[id]/backtest/+page.svelte +++ b/src/frontend/src/routes/bot/[id]/backtest/+page.svelte @@ -33,12 +33,12 @@ await loadBot(); await loadBacktests(); - // Poll for backtest updates every 5 seconds if any are running + // Poll for backtest updates every 2 seconds if any are running const pollInterval = setInterval(async () => { if ($backtestStore.backtestHistory.some(b => b.status === 'running')) { await loadBacktests(); } - }, 5000); + }, 2000); return () => clearInterval(pollInterval); } @@ -201,6 +201,12 @@ {/if} {#if backtest.status === 'running'} +