新建回测系统,并提交

This commit is contained in:
2026-01-17 21:37:42 +08:00
commit fe50ea935a
68 changed files with 108208 additions and 0 deletions

255
web_viewer.py Normal file
View File

@@ -0,0 +1,255 @@
"""回测结果Web查看器 - Flask后端服务。
提供API接口读取results目录下的CSV文件供前端展示。
"""
from flask import Flask, jsonify, render_template, send_from_directory
from pathlib import Path
import pandas as pd
import json
from datetime import datetime
app = Flask(__name__, static_folder='static', template_folder='templates')
# 结果文件路径
RESULTS_DIR = Path(__file__).parent / "results"
OPTIMIZATION_DIR = RESULTS_DIR / "optimization"
TRADES_DIR = RESULTS_DIR / "trades"
EQUITY_DIR = RESULTS_DIR / "equity"
@app.route('/')
def index():
"""主页面。"""
return render_template('index.html')
@app.route('/api/optimization/list')
def list_optimization_results():
"""获取所有参数优化结果文件列表。"""
try:
files = list(OPTIMIZATION_DIR.glob("grid_search_*.csv"))
file_info = []
for file in sorted(files, reverse=True):
# 从文件名提取时间戳
timestamp_str = file.stem.replace("grid_search_", "")
try:
timestamp = datetime.strptime(timestamp_str, "%Y%m%d_%H%M%S")
time_display = timestamp.strftime("%Y-%m-%d %H:%M:%S")
except:
time_display = timestamp_str
# 读取文件统计信息
df = pd.read_csv(file)
file_info.append({
"filename": file.name,
"timestamp": timestamp_str,
"time_display": time_display,
"num_results": len(df),
"size": file.stat().st_size
})
return jsonify({"success": True, "files": file_info})
except Exception as e:
return jsonify({"success": False, "error": str(e)})
@app.route('/api/optimization/<filename>')
def get_optimization_result(filename):
"""获取指定参数优化结果详情。"""
try:
file_path = OPTIMIZATION_DIR / filename
if not file_path.exists():
return jsonify({"success": False, "error": "文件不存在"})
df = pd.read_csv(file_path)
# 转换为JSON格式
data = df.to_dict(orient='records')
# 计算统计信息
stats = {
"total_combinations": len(df),
"best_sharpe": float(df['sharpe'].max()) if 'sharpe' in df.columns else None,
"best_return": float(df['total_return'].max()) if 'total_return' in df.columns else None,
"worst_drawdown": float(df['max_drawdown'].min()) if 'max_drawdown' in df.columns else None,
}
return jsonify({
"success": True,
"data": data,
"stats": stats,
"columns": list(df.columns)
})
except Exception as e:
return jsonify({"success": False, "error": str(e)})
@app.route('/api/trades/list')
def list_trade_results():
"""获取所有交易记录文件列表。"""
try:
files = list(TRADES_DIR.glob("*.csv"))
file_info = []
for file in sorted(files, reverse=True):
# 从文件名提取信息
parts = file.stem.split("_")
timestamp_str = f"{parts[0]}_{parts[1]}"
try:
timestamp = datetime.strptime(timestamp_str, "%Y%m%d_%H%M%S")
time_display = timestamp.strftime("%Y-%m-%d %H:%M:%S")
except:
time_display = timestamp_str
# 提取策略和参数信息
strategy_params = "_".join(parts[2:])
# 读取文件统计信息
df = pd.read_csv(file)
# 计算胜率和盈亏
total_trades = len(df)
win_trades = df['is_win'].sum() if 'is_win' in df.columns else 0
total_profit = df['profit_amount'].sum() if 'profit_amount' in df.columns else 0
file_info.append({
"filename": file.name,
"timestamp": timestamp_str,
"time_display": time_display,
"strategy_params": strategy_params,
"total_trades": total_trades,
"win_rate": f"{win_trades/total_trades*100:.2f}%" if total_trades > 0 else "0%",
"total_profit": f"{total_profit:.2f}",
"size": file.stat().st_size
})
return jsonify({"success": True, "files": file_info})
except Exception as e:
return jsonify({"success": False, "error": str(e)})
@app.route('/api/trades/<filename>')
def get_trade_result(filename):
"""获取指定交易记录详情。"""
try:
file_path = TRADES_DIR / filename
if not file_path.exists():
return jsonify({"success": False, "error": "文件不存在"})
df = pd.read_csv(file_path)
# 转换为JSON格式
data = df.to_dict(orient='records')
# 计算详细统计
total_trades = len(df)
win_trades = df[df['is_win'] == True] if 'is_win' in df.columns else pd.DataFrame()
loss_trades = df[df['is_win'] == False] if 'is_win' in df.columns else pd.DataFrame()
stats = {
"total_trades": total_trades,
"win_count": len(win_trades),
"loss_count": len(loss_trades),
"win_rate": f"{len(win_trades)/total_trades*100:.2f}%" if total_trades > 0 else "0%",
"total_profit": float(df['profit_amount'].sum()) if 'profit_amount' in df.columns else 0,
"avg_profit": float(df['profit_amount'].mean()) if 'profit_amount' in df.columns else 0,
"max_profit": float(df['profit_amount'].max()) if 'profit_amount' in df.columns else 0,
"max_loss": float(df['profit_amount'].min()) if 'profit_amount' in df.columns else 0,
"avg_win": float(win_trades['profit_amount'].mean()) if len(win_trades) > 0 else 0,
"avg_loss": float(loss_trades['profit_amount'].mean()) if len(loss_trades) > 0 else 0,
}
return jsonify({
"success": True,
"data": data,
"stats": stats,
"columns": list(df.columns)
})
except Exception as e:
return jsonify({"success": False, "error": str(e)})
@app.route('/api/equity/list')
def list_equity_results():
"""获取所有资金曲线文件列表。"""
try:
files = list(EQUITY_DIR.glob("*.csv"))
file_info = []
for file in sorted(files):
# 读取文件统计信息
df = pd.read_csv(file)
initial_asset = df['total_asset'].iloc[0] if len(df) > 0 else 0
final_asset = df['total_asset'].iloc[-1] if len(df) > 0 else 0
total_return = (final_asset - initial_asset) / initial_asset if initial_asset > 0 else 0
file_info.append({
"filename": file.name,
"strategy": file.stem.replace("_equity", ""),
"initial_asset": float(initial_asset),
"final_asset": float(final_asset),
"total_return": f"{total_return*100:.2f}%",
"num_days": len(df),
"size": file.stat().st_size
})
return jsonify({"success": True, "files": file_info})
except Exception as e:
return jsonify({"success": False, "error": str(e)})
@app.route('/api/equity/<filename>')
def get_equity_result(filename):
"""获取指定资金曲线详情。"""
try:
file_path = EQUITY_DIR / filename
if not file_path.exists():
return jsonify({"success": False, "error": "文件不存在"})
df = pd.read_csv(file_path)
# 转换为JSON格式只取部分数据避免过大
data = df.to_dict(orient='records')
# 计算统计信息
initial_asset = df['total_asset'].iloc[0] if len(df) > 0 else 0
final_asset = df['total_asset'].iloc[-1] if len(df) > 0 else 0
max_asset = df['total_asset'].max() if len(df) > 0 else 0
min_asset = df['total_asset'].min() if len(df) > 0 else 0
# 计算最大回撤
cummax = df['total_asset'].cummax()
drawdown = (df['total_asset'] - cummax) / cummax
max_drawdown = drawdown.min()
stats = {
"initial_asset": float(initial_asset),
"final_asset": float(final_asset),
"total_return": float((final_asset - initial_asset) / initial_asset) if initial_asset > 0 else 0,
"max_asset": float(max_asset),
"min_asset": float(min_asset),
"max_drawdown": float(max_drawdown),
"num_days": len(df)
}
return jsonify({
"success": True,
"data": data,
"stats": stats,
"columns": list(df.columns)
})
except Exception as e:
return jsonify({"success": False, "error": str(e)})
if __name__ == '__main__':
print("=" * 60)
print("回测结果Web查看器")
print("=" * 60)
print(f"正在启动Flask服务...")
print(f"结果目录: {RESULTS_DIR}")
print(f"请在浏览器打开: http://localhost:5000")
print("=" * 60)
app.run(debug=True, host='0.0.0.0', port=5000)