From c795ea4f48f9fdb41db8c92cb6243c0af13de2e1 Mon Sep 17 00:00:00 2001 From: cfdaily Date: Thu, 30 Apr 2026 23:09:14 +0800 Subject: [PATCH] auto-sync: 2026-04-30 23:09:14 --- data_platform/backtest_report.py | 77 ++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 data_platform/backtest_report.py diff --git a/data_platform/backtest_report.py b/data_platform/backtest_report.py new file mode 100644 index 000000000..b9a9cad26 --- /dev/null +++ b/data_platform/backtest_report.py @@ -0,0 +1,77 @@ +""" +BacktestReport - 标准化回测报告 + +将 BacktestResult 格式化输出为文本/JSON。 +""" + +import json +from typing import Optional + +from data_platform.backtest_runner import BacktestResult + + +class BacktestReport: + """回测报告生成器""" + + def __init__(self, result: BacktestResult): + self.result = result + + def to_text(self) -> str: + """生成文本格式报告""" + r = self.result + lines = [ + "=" * 60, + f" 回测报告 | {r.strategy_name} | {r.code}", + "=" * 60, + f" 回测区间: {r.start_date.date()} ~ {r.end_date.date()}", + f" 初始资金: {r.initial_capital:,.0f}", + f" 最终权益: {r.final_capital:,.0f}", + "-" * 60, + f" 总收益率: {r.total_return:>8.2%}", + f" 年化收益率: {r.annual_return:>8.2%}", + f" 最大回撤: {r.max_drawdown:>8.2%}", + f" 夏普比率: {r.sharpe_ratio:>8.2f}", + f" 胜率: {r.win_rate:>8.2%}", + f" 交易次数: {r.total_trades:>8d}", + "-" * 60, + ] + + # 交易明细(最多显示20条) + if r.trades: + lines.append(f" {'买入日':>12s} {'卖出日':>12s} {'买入价':>8s} " + f"{'卖出价':>8s} {'收益率':>8s} {'股数':>6s}") + lines.append(" " + "-" * 68) + for t in r.trades[:20]: + profit_str = f"{t.profit_pct:7.2%}" if t.profit_pct is not None else " N/A" + lines.append( + f" {t.entry_date.date()!s:>12s} {t.exit_date.date()!s:>12s} " + f"{t.entry_price:>8.2f} {t.exit_price:>8.2f} " + f"{profit_str} {t.shares:>6d}" + ) + if len(r.trades) > 20: + lines.append(f" ... 共 {len(r.trades)} 条,仅显示前20条") + + lines.append("=" * 60) + return "\n".join(lines) + + def to_dict(self) -> dict: + """导出为字典""" + r = self.result + return { + "strategy": r.strategy_name, + "code": r.code, + "start_date": str(r.start_date.date()), + "end_date": str(r.end_date.date()), + "initial_capital": r.initial_capital, + "final_capital": round(r.final_capital, 2), + "total_return": round(r.total_return, 4), + "annual_return": round(r.annual_return, 4), + "max_drawdown": round(r.max_drawdown, 4), + "sharpe_ratio": round(r.sharpe_ratio, 2), + "win_rate": round(r.win_rate, 4), + "total_trades": r.total_trades, + } + + def to_json(self, indent: int = 2) -> str: + """导出为JSON""" + return json.dumps(self.to_dict(), indent=indent, ensure_ascii=False)