auto-sync: 2026-04-30 23:09:14

This commit is contained in:
cfdaily
2026-04-30 23:09:14 +08:00
parent 8d3d14d585
commit c795ea4f48
+77
View File
@@ -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)