新建回测系统,并提交
This commit is contained in:
255
web_viewer.py
Normal file
255
web_viewer.py
Normal 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)
|
||||
Reference in New Issue
Block a user