feat: VBT visualization + validation pipeline, HFT tick viz, DuckDB loader
Track 1 — VBT Candle-Frequency Pipeline: - backtests/vbt_validator.py: VBTValidator with 11 checks — timestamp monotonicity, duplicates, NaN, data gaps, lookahead bias, signal alignment, density, coincident entry/exit, min trade count, fee application, benchmark comparison. ValidationReport dataclass with errors/warnings/stats. Validates VBT results or raw signal arrays. - backtests/vbt_viz.py: VBTVisualizer with 10+ Plotly chart methods — equity curve with benchmark, drawdown, rolling Sharpe/Sortino/vol, trade markers, returns distribution with normal fit, monthly PnL heatmap, gross vs net, holding periods, parameter sensitivity heatmaps, dashboard compositor, HTML save (self-contained, CDN Plotly). All methods handle empty/null inputs. - backtests/vbt_report.py: Markdown + HTML report generator — structured sections for implementation summary, performance metrics, cost analysis, validation results, signal analysis, known limitations, next steps. batch_report() for mass report generation from results directory. - backtests/vbt_runner.py: Added run_benchmark() (buy-and-hold VBT portfolio), validate() (integrated VBTValidator), run_with_report() (fetch→validate→ backtest→visualize→save in one call). Track 2 — HFT Tick Pipeline: - backtests/tick_viz.py: 9-panel HFT dashboard — price+trade markers, spread dynamics, top-of-book depth, microprice vs mid, OBI/OFI panel, VPIN toxicity with thresholds, event timeline (PnL from tick_runner), markout curves at 6 horizons. Parquet→pandas→Plotly pipeline. Dark-themed HTML output for microstructure review. - data/duckdb_load.py: Parquet→DuckDB loader — creates l2_snapshots, trades, funding tables with schema. Pre-computed 1s rollup views for microprice, OFI, trade imbalance. Markout queries directly in SQL. Incremental loading with load_state tracking. CLI Integration: - cli.py: Added 'report' (full VBT report), 'validate' (check existing results), 'hft' (tick dashboard generation) commands. Fixed argparse help string escaping. 355 tests passing (34 new).
This commit is contained in:
@@ -545,6 +545,98 @@ def cmd_funding(args):
|
||||
print(f" Then 'python -m cli funding --coin BTC' to re-run.")
|
||||
|
||||
|
||||
def cmd_report(args):
|
||||
"""Generate a combined VBT report: backtest + validation + visualization."""
|
||||
import json as _json
|
||||
from backtests.vbt_runner import VBTBacktestRunner
|
||||
|
||||
runner = VBTBacktestRunner()
|
||||
result = runner.run_with_report(
|
||||
strategy=args.strategy,
|
||||
interval=args.interval,
|
||||
testnet=False,
|
||||
limit=args.limit,
|
||||
output_dir=args.output_dir,
|
||||
)
|
||||
|
||||
if result:
|
||||
print(f"Report generated for {args.strategy} ({args.interval})")
|
||||
print(f" Strategy: {result['strategy']}")
|
||||
print(f" Sharpe: {result.get('sharpe', 0):.3f}")
|
||||
print(f" Net PnL: ${result.get('pnl', 0):.2f}")
|
||||
print(f" Trades: {result.get('total_trades', 0)}")
|
||||
print(f" Validation: {len(result.get('validation_errors', []))} errors, "
|
||||
f"{len(result.get('validation_warnings', []))} warnings")
|
||||
print(f" Output: {args.output_dir}/")
|
||||
else:
|
||||
print(f"No data available for {args.strategy}. "
|
||||
f"Try: python -m cli backtest --strategy {args.strategy} --interval {args.interval}")
|
||||
|
||||
|
||||
def cmd_validate(args):
|
||||
"""Validate existing backtest results without re-running."""
|
||||
import json as _json
|
||||
from pathlib import Path
|
||||
from backtests.vbt_validator import VBTValidator, ValidationReport
|
||||
|
||||
rd = Path(args.results_dir)
|
||||
files = sorted(rd.glob("*.json"))
|
||||
if not files:
|
||||
print(f"No backtest results found in {args.results_dir}")
|
||||
return
|
||||
|
||||
validator = VBTValidator()
|
||||
|
||||
total = 0
|
||||
passed = 0
|
||||
|
||||
for fp in files:
|
||||
try:
|
||||
data = _json.loads(fp.read_text())
|
||||
except Exception:
|
||||
continue
|
||||
|
||||
strat = data.get("strategy", "?")
|
||||
if args.strategy != "all" and args.strategy.lower() != strat.lower():
|
||||
continue
|
||||
|
||||
total += 1
|
||||
report = ValidationReport(
|
||||
strategy=strat,
|
||||
interval=data.get("interval", "?"),
|
||||
)
|
||||
|
||||
trades = data.get("trades", [])
|
||||
n_trades = data.get("total_trades", len(trades))
|
||||
if n_trades < 10:
|
||||
report.warnings.append(
|
||||
f"{fp.name}: only {n_trades} trades — insufficient for stats"
|
||||
)
|
||||
|
||||
sharpe = data.get("sharpe", 0)
|
||||
if n_trades > 0 and abs(sharpe) > 5:
|
||||
report.warnings.append(
|
||||
f"{fp.name}: extreme Sharpe {sharpe:.2f} with {n_trades} trades"
|
||||
)
|
||||
|
||||
if report.errors or report.warnings:
|
||||
print(report.summary())
|
||||
else:
|
||||
passed += 1
|
||||
|
||||
print(f"\n{passed}/{total} backtests clear validation")
|
||||
if total > 0 and passed == 0:
|
||||
print("⚠ All backtests have warnings/errors. Review needed.")
|
||||
print(f"\nFull validation requires re-running with VBTValidator.validate().")
|
||||
print(f"Use: python -m cli report --strategy <name> for full validation.")
|
||||
|
||||
|
||||
def cmd_hft_viz(args):
|
||||
"""Generate HFT tick visualization dashboard."""
|
||||
from backtests.tick_viz import cmd_tick_viz
|
||||
cmd_tick_viz(args)
|
||||
|
||||
|
||||
def main():
|
||||
import argparse
|
||||
p = argparse.ArgumentParser(description="FTDT Quant Lab CLI")
|
||||
@@ -609,8 +701,8 @@ def main():
|
||||
pt.add_argument("--skew-factor", type=float, default=0.5)
|
||||
pt.add_argument("--vpin-threshold", type=float, default=0.30)
|
||||
pt.add_argument("--vpin-alarm", type=float, default=0.50)
|
||||
pt.add_argument("--maker-fee", type=float, default=0.02, help="Maker fee in %")
|
||||
pt.add_argument("--taker-fee", type=float, default=0.05, help="Taker fee in %")
|
||||
pt.add_argument("--maker-fee", type=float, default=0.02, help="Maker fee (e.g. 0.02 = 2bps)")
|
||||
pt.add_argument("--taker-fee", type=float, default=0.05, help="Taker fee (e.g. 0.05 = 5bps)")
|
||||
pt.add_argument("--adverse-prob", type=float, default=0.15)
|
||||
pt.add_argument("--cancel-after-ms", type=float, default=5000.0)
|
||||
pt.add_argument("--quote-refresh-ms", type=float, default=2000.0)
|
||||
@@ -631,6 +723,28 @@ def main():
|
||||
pf.add_argument("--start-date", default="2026-01-01")
|
||||
pf.add_argument("--end-date", default="2030-01-01")
|
||||
|
||||
# report
|
||||
prp = sp.add_parser("report", help="Generate VBT backtest report (Markdown + HTML + dashboard)")
|
||||
prp.add_argument("--strategy", default="pairs")
|
||||
prp.add_argument("--interval", default="1h")
|
||||
prp.add_argument("--limit", type=int, default=5000)
|
||||
prp.add_argument("--output-dir", default="backtests/reports")
|
||||
prp.add_argument("--format", default="html", choices=["md", "html"])
|
||||
|
||||
# validate
|
||||
pv = sp.add_parser("validate", help="Validate existing backtest results without re-running")
|
||||
pv.add_argument("--strategy", default="all", help="Strategy name or 'all'")
|
||||
pv.add_argument("--results-dir", default="backtests/results")
|
||||
|
||||
# hft
|
||||
ph = sp.add_parser("hft", help="Generate HFT tick visualization dashboard")
|
||||
ph.add_argument("--data-dir", default="data/raw")
|
||||
ph.add_argument("--coin", default="BTC")
|
||||
ph.add_argument("--start-date", default="2026-08-01")
|
||||
ph.add_argument("--end-date", default="2026-08-02")
|
||||
ph.add_argument("--tick-result", default=None, help="Path to tick_runner JSON result")
|
||||
ph.add_argument("--output-dir", default="backtests/reports")
|
||||
|
||||
args = p.parse_args()
|
||||
|
||||
import json as _json
|
||||
@@ -653,6 +767,12 @@ def main():
|
||||
cmd_discover(args)
|
||||
elif args.command == "funding":
|
||||
cmd_funding(args)
|
||||
elif args.command == "report":
|
||||
cmd_report(args)
|
||||
elif args.command == "validate":
|
||||
cmd_validate(args)
|
||||
elif args.command == "hft":
|
||||
cmd_hft_viz(args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user