feat: VBT dashboard — asset badges, interval/bar selectors, sort/filter
Dashboard (vbt.html): - Interval selector: 1m, 5m, 15m, 1h, 4h, 1d (all Hyperliquid intervals) - Candle limit selector: 100-5000 bars (6 levels) - Asset selector: auto/BTC/ETH/SOL for run - Strategy filter dropdown - Sort dropdown: Latest, Sharpe, Return%, Min DD, Trades - Asset badge on every result item in sidebar - Asset interval filter for results list - Improved layout: compact 3-row control panel API (server.py): - /api/vbt/results: new sort param (sharpe/return/dd/trades/date) new interval filter, asset field with _infer_asset() - /api/vbt/run: new coin param, interval already supported coin suffix in saved filenames - _infer_asset(): maps strategy names to BTC/ETH/BTC-ETH/SOL Verified: sort=sharpe shows A-S S=+11.37, interval=1h filters correctly, 7 dashboard controls rendered, asset badges on all items
This commit is contained in:
+60
-4
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user