"""回测结果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/') 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/') 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/') 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)