diff --git a/dashboard/server.py b/dashboard/server.py index 6163c13..85a2b0b 100644 --- a/dashboard/server.py +++ b/dashboard/server.py @@ -528,8 +528,13 @@ def _normalize_vbt_fields(data: dict) -> dict: @app.get("/api/vbt/results") -async def list_vbt_results(strategy: str = "", limit: int = 50): - """List VectorBT backtest results with full metrics.""" +async def list_vbt_results( + strategy: str = "", + interval: str = "", + sort: str = "date", + limit: int = 100, +): + """List VectorBT backtest results with full metrics and filtering.""" results = [] for d in [BACKTEST_DIR, HISTORICAL_DIR]: if not os.path.isdir(d): @@ -544,9 +549,14 @@ async def list_vbt_results(strategy: str = "", limit: int = 50): with open(fpath) as f: data = json.load(f) n = _normalize_vbt_fields(data) + if interval and n.get("interval", "1h") != interval: + continue + # Infer asset from strategy or filename + asset = _infer_asset(n.get("strategy", ""), fname) results.append({ "filename": fname, "strategy": n.get("strategy", "unknown"), + "asset": asset, "engine": n.get("engine", "vectorbt"), "interval": n.get("interval", "1h"), "sharpe": n.get("sharpe", 0), @@ -564,10 +574,52 @@ async def list_vbt_results(strategy: str = "", limit: int = 50): pass if len(results) >= limit: break - results.sort(key=lambda r: r.get("generated_at", ""), reverse=True) + + # Sort + if sort == "sharpe": + results.sort(key=lambda r: r.get("sharpe", -999), reverse=True) + elif sort == "return": + results.sort(key=lambda r: r.get("total_return_pct", -999), reverse=True) + elif sort == "dd": + results.sort(key=lambda r: -abs(r.get("max_drawdown_pct", 999)), reverse=True) + elif sort == "trades": + results.sort(key=lambda r: r.get("total_trades", 0), reverse=True) + else: # date + results.sort(key=lambda r: r.get("generated_at", ""), reverse=True) + return JSONResponse(results[:limit]) +def _infer_asset(strategy_name: str, filename: str) -> str: + """Infer the trading asset from strategy name or filename.""" + name = (strategy_name + " " + filename).lower() + coin_map = { + "pairs": "BTC/ETH", + "order book": "BTC", + "obi": "BTC", + "iceberg": "BTC", + "momentum": "ETH" if "eth" in name else "BTC", + "mean rev": "ETH" if "eth" in name else "BTC", + "hurst": "BTC", + "vpin": "BTC", + "avellaneda": "BTC", + "as_mm": "BTC", + "grid": "BTC", + "composite": "BTC", + "funding": "BTC", + "kalman": "BTC/ETH", + "cartea": "BTC", + "gueant": "BTC", + "hawkes": "BTC", + "deep lob": "BTC", + "queue": "BTC", + } + for key, asset in coin_map.items(): + if key in name: + return asset + return "BTC" + + @app.get("/api/vbt/result/{filename}") async def get_vbt_result(filename: str): """Get full VBT backtest result including equity curve.""" @@ -591,6 +643,7 @@ async def run_vbt_backtest( strategy: str = "pairs", interval: str = "1h", limit: int = 500, + coin: str = "", testnet: bool = False, ): """Run a new VectorBT backtest and return results.""" @@ -599,11 +652,14 @@ async def run_vbt_backtest( runner = VBTBacktestRunner() from datetime import datetime ts = datetime.now().strftime("%Y%m%d-%H%M%S") + coin_suffix = f"_{coin}" if coin else "" result = runner.run_strategy( strategy=strategy, interval=interval, testnet=testnet, limit=limit ) if result: - fname = f"{strategy}_vbt_{ts}.json" + if coin: + result["asset"] = coin.upper() + fname = f"{strategy}{coin_suffix}_vbt_{ts}.json" fpath = os.path.join(BACKTEST_DIR, fname) with open(fpath, "w") as f: json.dump(result, f, default=str) diff --git a/dashboard/static/vbt.html b/dashboard/static/vbt.html index 0f0474a..2f5a76d 100644 --- a/dashboard/static/vbt.html +++ b/dashboard/static/vbt.html @@ -8,49 +8,48 @@ @@ -60,51 +59,113 @@ select:focus{outline:none;border-color:#3b82f6}