""" VBT Report Generator — produces Markdown and HTML research reports. Consolidates backtest results, validation reports, performance metrics, and visualizations into a single shareable document. Usage: python -m backtests.vbt_report --strategy pairs --interval 1h python -m backtests.vbt_report --strategy all --output reports/weekly.md """ from __future__ import annotations import argparse import json import logging import os import sys from datetime import datetime, timezone from pathlib import Path from typing import Any, Optional sys.path.insert(0, str(Path(__file__).resolve().parent.parent)) import numpy as np logger = logging.getLogger(__name__) REPORT_DIR = Path(__file__).resolve().parent / "reports" REPORT_DIR.mkdir(parents=True, exist_ok=True) def format_metric(value: Any, decimals: int = 2) -> str: if isinstance(value, float): return f"{value:.{decimals}f}" return str(value) def generate_markdown_report( result: dict, validation_report=None, includes_viz: bool = False, viz_path: str = "", ) -> str: """Generate a Markdown research report from a backtest result.""" strategy = result.get("strategy", "unknown") interval = result.get("interval", "unknown") pnl = result.get("pnl", 0) total_return = result.get("total_return_pct", 0) sharpe = result.get("sharpe", 0) sortino = result.get("sortino", 0) max_dd = result.get("max_drawdown_pct", 0) win_rate = result.get("win_rate", 0) profit_factor = result.get("profit_factor", 0) expectancy = result.get("expectancy", 0) n_bars = result.get("n_bars", 0) trades = result.get("trades", []) n_trades = result.get("total_trades", len(trades)) params = result.get("params", {}) fee_info = result.get("fee_info", {}) timing = result.get("generated_at", datetime.now(timezone.utc).isoformat()) lines = [] lines.append(f"# VBT Backtest Report — {strategy} ({interval})") lines.append("") lines.append(f"**Generated:** {timing}") lines.append(f"**Data:** Hyperliquid {'mainnet' if 'mainnet' in str(result.get('coin', '')) else 'testnet'}") lines.append("") lines.append("---") lines.append("") lines.append("## 1. Implementation Summary") lines.append("") lines.append(f"- **Strategy:** `{strategy}`") lines.append(f"- **Interval:** `{interval}`") lines.append(f"- **Bars:** {n_bars}") lines.append(f"- **Trades:** {n_trades}") lines.append("") strategy_type = params.get("type", "Unknown") lines.append(f"- **Strategy Type:** {strategy_type}") if params: param_str = ", ".join(f"{k}={v}" for k, v in params.items() if k != "type") lines.append(f"- **Parameters:** {param_str}") lines.append("") lines.append("## 2. Performance Metrics") lines.append("") lines.append("| Metric | Value |") lines.append("|--------|-------|") lines.append(f"| Start Equity | ${result.get('start_equity', 10000.0):,.2f} |") lines.append(f"| End Equity | ${result.get('end_equity', 10000.0):,.2f} |") lines.append(f"| Net PnL | ${pnl:,.2f} |") lines.append(f"| Total Return | {total_return:.2f}% |") lines.append(f"| Sharpe Ratio | {sharpe:.3f} |") lines.append(f"| Sortino Ratio | {sortino:.3f} |") lines.append(f"| Max Drawdown | {max_dd:.2f}% |") lines.append(f"| Win Rate | {win_rate:.1%} |") lines.append(f"| Profit Factor | {profit_factor:.3f} |") lines.append(f"| Expectancy | {expectancy:.3f} |") lines.append(f"| Total Trades | {n_trades} |") if fee_info: lines.append(f"| Fee Rate | {fee_info.get('effective_rate_pct', 0):.4f}% |") lines.append(f"| Fee Tier | {fee_info.get('tier_name', 'N/A')} |") lines.append(f"| Staking Tier | {fee_info.get('staking_name', 'N/A')} |") lines.append(f"| Fee Model | {fee_info.get('fee_model', 'N/A')} |") lines.append("") lines.append("## 3. Cost Analysis") lines.append("") if trades: gross_pnls = [float(t.get("pnl_gross", t.get("pnl", 0))) for t in trades] net_pnls = [float(t.get("pnl_net", t.get("pnl", 0))) for t in trades] fees = [float(t.get("fee", 0)) for t in trades] total_gross = sum(gross_pnls) total_net = sum(net_pnls) total_fees = sum(fees) slippage_est = n_trades * 0.001 * 10000.0 * 0.001 lines.append("| Component | Amount |") lines.append("|-----------|--------|") lines.append(f"| Gross PnL | ${total_gross:,.4f} |") lines.append(f"| Total Fees | ${total_fees:,.4f} |") lines.append(f"| Est. Slippage | ${slippage_est:,.4f} |") lines.append(f"| Net PnL | ${total_net:,.4f} |") cost_pct = (total_fees / abs(total_gross) * 100) if abs(total_gross) > 0 else 0 lines.append(f"| Fee/Gross Ratio | {cost_pct:.1f}% |") lines.append("") lines.append("## 4. Validation Results") lines.append("") if validation_report: if hasattr(validation_report, 'summary'): lines.append("```") lines.append(validation_report.summary()) lines.append("```") else: lines.append("```") lines.append(str(validation_report)) lines.append("```") else: lines.append("⚠ No validation report available.") lines.append("") lines.append("## 5. Signal Analysis") lines.append("") lines.append(f"- **Total signals:** {result.get('n_bars', 0)} bars processed") if trades: holds = [] for t in trades: dur = str(t.get("duration", "")) if dur: try: td = pd_from_timedelta(dur) if td: holds.append(td.total_seconds() / 3600) except Exception: pass if holds: lines.append(f"- **Avg holding period:** {np.mean(holds):.2f} hours") lines.append(f"- **Median holding period:** {np.median(holds):.2f} hours") lines.append(f"- **Max holding period:** {np.max(holds):.2f} hours") lines.append("") lines.append("## 6. Known Limitations") lines.append("") lines.append("1. **VBT is candle-level backtesting only.** It cannot model:") lines.append(" - Queue position / price-time priority") lines.append(" - Realistic adverse selection at tick-level") lines.append(" - Latency-dependent fill probability") lines.append(" - VPIN-gated market making") lines.append("2. **Volume-based OBI is a proxy.** Real OBI requires L2 order book data.") lines.append("3. **A-S MM simulation is synthetic.** Uses candle high/low as virtual orderbook,") lines.append(" not real exchange order book queue position.") lines.append(f"4. **Signal frequency:** {n_trades} trades in {n_bars} bars — " f"this is a {'scalping' if n_bars > 0 and n_trades / n_bars > 0.01 else 'low-frequency'} strategy.") lines.append("5. **No walk-forward validation** performed in this report. " "Run `python -m cli walkforward --strategy {strategy}` for OOS testing.") lines.append("") lines.append("## 7. Next Steps") lines.append("") lines.append(f"1. Run walk-forward validation: `python -m cli walkforward --strategy {strategy} --interval {interval}`") lines.append(f"2. Run tick-level backtest: `python -m cli tick --maker vpin_as_mm --coin BTC`") lines.append(f"3. Paper trade for 7+ days before live deployment") lines.append(f"4. Correlate strategy with other strategies to build diversified portfolio") if includes_viz and viz_path: lines.append("") lines.append("## 8. Visualizations") lines.append("") lines.append(f"Interactive dashboard: [{viz_path}]({viz_path})") lines.append("") lines.append("---") lines.append(f"*Generated by FTDT Quant Lab VBT Pipeline*") return "\n".join(lines) def pd_from_timedelta(dur_str: str): """Safe Timedelta parsing.""" try: import pandas as pd return pd.Timedelta(dur_str) except Exception: return None def generate_html_report( result: dict, validation_report=None, viz_path: str = "", ) -> str: """Wrap the Markdown report in HTML with styling.""" md_body = generate_markdown_report(result, validation_report, bool(viz_path), viz_path) try: import markdown body = markdown.markdown(md_body, extensions=["tables", "fenced_code"]) except ImportError: body = "
" + md_body.replace("<", "<") + ""
html = f"""