新建回测系统,并提交
This commit is contained in:
145
utils/performance.py
Normal file
145
utils/performance.py
Normal file
@@ -0,0 +1,145 @@
|
||||
"""绩效计算模块。
|
||||
|
||||
根据资金曲线计算收益、年化收益、夏普比率、最大回撤等指标。
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
from utils.logger import setup_logger
|
||||
|
||||
logger = setup_logger(__name__)
|
||||
|
||||
|
||||
def calc_performance(
|
||||
equity_df: pd.DataFrame,
|
||||
trade_count: int = 0,
|
||||
trade_history: list = None,
|
||||
trading_days_per_year: int = 252,
|
||||
) -> dict:
|
||||
"""根据资金曲线计算常用绩效指标。
|
||||
|
||||
参数:
|
||||
- equity_df: 包含列 ['trade_date', 'total_asset', 'cash', 'market_value'] 的 DataFrame;
|
||||
- trade_count: 总交易次数(买入+卖出);
|
||||
- trade_history: 交易历史记录(用于计算胜率和盈亏比);
|
||||
- trading_days_per_year: 年化使用的交易日数,默认 252。
|
||||
|
||||
返回:
|
||||
- dict,包含累积收益、年化收益、夏普比率、最大回撤、资金利用率、胜率、盈亏比等。
|
||||
"""
|
||||
if equity_df.empty:
|
||||
logger.warning("资金曲线为空,无法计算绩效")
|
||||
return {}
|
||||
|
||||
df = equity_df.copy()
|
||||
df = df.sort_values("trade_date").reset_index(drop=True)
|
||||
df["ret"] = df["total_asset"].pct_change().fillna(0.0)
|
||||
|
||||
# 累积收益
|
||||
cum_return = df["total_asset"].iloc[-1] / df["total_asset"].iloc[0] - 1
|
||||
|
||||
# 年化收益
|
||||
n = len(df)
|
||||
if n <= 1:
|
||||
ann_return = 0.0
|
||||
years = 0.0
|
||||
else:
|
||||
ann_return = (1 + cum_return) ** (trading_days_per_year / n) - 1
|
||||
years = n / trading_days_per_year
|
||||
|
||||
# 夏普比率(假设无无风险利率)
|
||||
ret_mean = df["ret"].mean()
|
||||
ret_std = df["ret"].std(ddof=1)
|
||||
if ret_std == 0:
|
||||
sharpe = 0.0
|
||||
else:
|
||||
sharpe = (ret_mean * trading_days_per_year) / (ret_std * (trading_days_per_year**0.5))
|
||||
|
||||
# 最大回撤
|
||||
cummax = df["total_asset"].cummax()
|
||||
drawdown = df["total_asset"] / cummax - 1
|
||||
max_drawdown = float(drawdown.min())
|
||||
|
||||
# 资金利用率统计(每日持仓市值 / 总资产)
|
||||
if "market_value" in df.columns and "total_asset" in df.columns:
|
||||
df["capital_utilization"] = df["market_value"] / df["total_asset"]
|
||||
avg_capital_utilization = df["capital_utilization"].mean()
|
||||
else:
|
||||
avg_capital_utilization = 0.0
|
||||
|
||||
# 交易次数统计
|
||||
total_trades = trade_count
|
||||
if years > 0:
|
||||
avg_trades_per_year = total_trades / years
|
||||
else:
|
||||
avg_trades_per_year = 0.0
|
||||
|
||||
# 计算胜率和盈亏比(从交易历史中获取)
|
||||
win_rate = 0.0
|
||||
profit_loss_ratio = 0.0
|
||||
win_count = 0
|
||||
loss_count = 0
|
||||
total_win_pct = 0.0
|
||||
total_loss_pct = 0.0
|
||||
|
||||
if trade_history and len(trade_history) > 0:
|
||||
for trade in trade_history:
|
||||
if trade.get("is_win", False):
|
||||
win_count += 1
|
||||
total_win_pct += trade.get("profit_pct", 0.0)
|
||||
else:
|
||||
loss_count += 1
|
||||
total_loss_pct += abs(trade.get("profit_pct", 0.0))
|
||||
|
||||
total_complete_trades = win_count + loss_count
|
||||
if total_complete_trades > 0:
|
||||
win_rate = win_count / total_complete_trades
|
||||
|
||||
# 计算平均盈亏比:平均盈利 / 平均亏损
|
||||
avg_win = total_win_pct / win_count if win_count > 0 else 0.0
|
||||
avg_loss = total_loss_pct / loss_count if loss_count > 0 else 0.0
|
||||
|
||||
if avg_loss > 0:
|
||||
profit_loss_ratio = avg_win / avg_loss
|
||||
else:
|
||||
profit_loss_ratio = 0.0 if avg_win == 0 else float('inf')
|
||||
|
||||
res = {
|
||||
"cum_return": float(cum_return),
|
||||
"ann_return": float(ann_return),
|
||||
"sharpe": float(sharpe),
|
||||
"max_drawdown": max_drawdown,
|
||||
"avg_capital_utilization": float(avg_capital_utilization),
|
||||
"total_trades": int(total_trades),
|
||||
"avg_trades_per_year": float(avg_trades_per_year),
|
||||
"backtest_years": float(years),
|
||||
"win_rate": float(win_rate),
|
||||
"profit_loss_ratio": float(profit_loss_ratio),
|
||||
"win_count": int(win_count),
|
||||
"loss_count": int(loss_count),
|
||||
}
|
||||
|
||||
# 格式化输出绩效指标(中文、分行、百分比)
|
||||
logger.info("=" * 60)
|
||||
logger.info("回测绩效指标汇总")
|
||||
logger.info("=" * 60)
|
||||
logger.info(f"回测年数: {years:.2f} 年")
|
||||
logger.info(f"总交易次数: {total_trades} 次")
|
||||
logger.info(f"年平均交易次数: {avg_trades_per_year:.2f} 次/年")
|
||||
logger.info("-" * 60)
|
||||
logger.info(f"累计收益率: {cum_return * 100:+.2f}%")
|
||||
logger.info(f"年化收益率: {ann_return * 100:+.2f}%")
|
||||
logger.info(f"夏普比率: {sharpe:.4f}")
|
||||
logger.info(f"最大回撤: {max_drawdown * 100:.2f}%")
|
||||
logger.info(f"平均资金利用率: {avg_capital_utilization * 100:.2f}%")
|
||||
logger.info("-" * 60)
|
||||
logger.info(f"胜率: {win_rate * 100:.2f}% ({win_count}胜 / {loss_count}败)")
|
||||
if profit_loss_ratio == float('inf'):
|
||||
logger.info(f"平均盈亏比: ∞ (无亏损交易)")
|
||||
else:
|
||||
logger.info(f"平均盈亏比: {profit_loss_ratio:.2f}")
|
||||
logger.info("=" * 60)
|
||||
|
||||
return res
|
||||
Reference in New Issue
Block a user