Python Tkinter 量化交易平台
要一个集Tkinter可视化界面、Numba加速回测、100+因子模拟、Barra风险模型、自动PDF业绩报告于一体的量化交易模拟系统,下面我将分模块实现完整可运行的代码。
整体架构说明
- 界面层:Tkinter构建模拟实盘界面(行情展示、策略启停、参数配置)
- 回测层:Numba加速回测引擎(向量式回测,支持高频计算)
- 因子层:模拟100+量化因子(技术面、基本面、情绪面)
- 风险层:实现简化版Barra风格风险模型(风格因子+行业因子)
- 报告层:利用ReportLab自动生成PDF格式业绩报告
完整代码实现
第一步:安装依赖库
pip install tkinter numpy pandas numba scipy reportlab matplotlib
注意:tkinter在Python自带环境中通常已预装,若缺失可通过系统包管理器安装(如Ubuntu:sudo apt-get install python3-tk)
第二步:完整代码
import tkinter as tkfrom tkinter import ttk, messageboximport numpy as npimport pandas as pdfrom numba import jit, prangeimport datetimefrom scipy import statsfrom reportlab.lib import colorsfrom reportlab.lib.pagesizes import A4, landscapefrom reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStylefrom reportlab.lib.styles import getSampleStyleSheet, ParagraphStylefrom reportlab.pdfbase import pdfmetricsfrom reportlab.pdfbase.ttfonts import TTFontimport matplotlibmatplotlib.use('Agg') # 非交互式后端,支持无GUI环境生成图表import matplotlib.pyplot as pltfrom io import BytesIOfrom reportlab.platypus import Image# ======================================# 1. 假数据生成模块(模拟金融市场数据)# ======================================defgenerate_fake_stock_data(n_stocks=50, n_days=252, start_date="2023-01-03"):""" 生成模拟股票行情数据(假数据) :param n_stocks: 股票数量 :param n_days: 交易天数(默认1年252个交易日) :param start_date: 起始日期 :return: 个股K线数据(DataFrame)、市场基准数据(np.array) """# 生成交易日序列 dates = pd.date_range(start=start_date, periods=n_days, freq='B') stock_codes = [f"STOCK_{i:03d}"for i in range(n_stocks)]# 生成个股价格数据(几何布朗运动模拟) np.random.seed(42) # 固定随机种子,结果可复现 returns = np.random.normal(loc=0.0005, scale=0.02, size=(n_days, n_stocks)) prices = np.cumprod(1 + returns, axis=0) * 10# 初始价格10元 prices[0] = 10# 首日价格固定为10元# 构建K线数据DataFrame(包含OHLC、成交量) stock_data = []for i, code in enumerate(stock_codes): df = pd.DataFrame({'date': dates,'code': code,'open': prices[:, i] * np.random.uniform(0.99, 1.01, n_days),'high': prices[:, i] * np.random.uniform(1.0, 1.03, n_days),'low': prices[:, i] * np.random.uniform(0.97, 1.0, n_days),'close': prices[:, i],'volume': np.random.randint(1000000, 10000000, n_days) })# 保证OHLC逻辑合理(high>=close>=open>=low) df['high'] = df[['open', 'close', 'high']].max(axis=1) df['low'] = df[['open', 'close', 'low']].min(axis=1) stock_data.append(df) all_stock_data = pd.concat(stock_data, ignore_index=True)# 生成市场基准数据(沪深300模拟) bench_returns = np.random.normal(loc=0.0008, scale=0.015, size=n_days) bench_prices = np.cumprod(1 + bench_returns, axis=0) * 1000# 初始基点1000return all_stock_data, bench_prices, dates, stock_codes# ======================================# 2. 100+量化因子模拟模块# ======================================defgenerate_100_plus_factors(stock_data, stock_codes, dates):""" 模拟100+量化因子(技术面+基本面+情绪面) :return: 因子矩阵(n_days * n_stocks * n_factors) """ n_days = len(dates) n_stocks = len(stock_codes) n_factors = 120# 模拟120个因子,满足100+要求# 初始化因子矩阵 factor_matrix = np.zeros((n_days, n_stocks, n_factors)) np.random.seed(42)# 按因子类型分组生成(保证因子具有一定经济意义)for t in range(n_days):# 提取当日所有股票收盘价 day_data = stock_data[stock_data['date'] == dates[t]] day_close = np.array([day_data[day_data['code'] == code]['close'].values[0] for code in stock_codes])# 1-20:趋势类因子(MA、EMA、MACD相关)for i in range(0, 20): factor_matrix[t, :, i] = day_close * np.random.uniform(0.9, 1.1, n_stocks) + \ np.cumsum(day_close)[:n_stocks] * 0.001# 21-40:波动类因子(ATR、波动率、最大回撤相关)for i in range(20, 40): factor_matrix[t, :, i] = np.std(day_close) * np.random.uniform(0.5, 1.5, n_stocks) + \ np.random.normal(0, 1, n_stocks)# 41-60:量能类因子(成交量、成交额、量价比相关) day_volume = np.array([day_data[day_data['code'] == code]['volume'].values[0] for code in stock_codes])for i in range(40, 60): factor_matrix[t, :, i] = (day_volume / 1e7) * np.random.uniform(0.8, 1.2, n_stocks)# 61-80:基本面因子(PE、PB、ROE相关)for i in range(60, 80): factor_matrix[t, :, i] = np.random.uniform(5, 50, n_stocks) # PE模拟if i % 2 == 0: factor_matrix[t, :, i] = 1 / factor_matrix[t, :, i] # PB倒数模拟# 81-100:行业风格因子(周期、成长、消费相关)for i in range(80, 100): industry_label = np.random.randint(0, 10, n_stocks) # 10个行业 factor_matrix[t, :, i] = industry_label + np.random.normal(0, 0.5, n_stocks)# 101-120:情绪类因子(涨跌幅、换手率、连涨天数相关)for i in range(100, 120): factor_matrix[t, :, i] = (day_close / day_close.mean() - 1) * 100 + \ np.random.normal(0, 5, n_stocks)# 因子标准化(去极值+标准化)for f in range(n_factors): factor_flat = factor_matrix[:, :, f].flatten()# 去极值(3倍标准差) mean = np.mean(factor_flat) std = np.std(factor_flat) factor_flat = np.clip(factor_flat, mean - 3*std, mean + 3*std)# 标准化(均值0,标准差1) factor_flat = (factor_flat - np.mean(factor_flat)) / np.std(factor_flat) factor_matrix[:, :, f] = factor_flat.reshape((n_days, n_stocks))return factor_matrix# ======================================# 3. Numba加速回测引擎模块# ======================================@jit(nopython=True, parallel=True, fastmath=True)defnumba_accelerated_backtest(prices, factor_matrix, bench_prices, commission=0.0003, slippage=0.0001):""" Numba加速的量化回测引擎(向量式回测) :param prices: 个股价格矩阵(n_days * n_stocks) :param factor_matrix: 因子矩阵(n_days * n_stocks * n_factors) :param bench_prices: 基准价格序列 :param commission: 交易佣金 :param slippage: 交易滑点 :return: 策略净值、持仓权重、业绩指标 """ n_days, n_stocks, n_factors = factor_matrix.shape n_bench = len(bench_prices)# 初始化回测变量 nav = np.ones(n_days) # 策略净值 cash = 1000000.0# 初始资金100万 hold_weights = np.zeros((n_days, n_stocks)) # 持仓权重 daily_return = np.zeros(n_days) # 每日收益率 bench_return = np.zeros(n_bench) # 基准每日收益率# 计算基准收益率for t in range(1, n_bench): bench_return[t] = (bench_prices[t] - bench_prices[t-1]) / bench_prices[t-1]# 因子加权合成选股信号(取前20%因子权重) factor_weights = np.zeros(n_factors)for f in range(n_factors): factor_weights[f] = 1.0 / n_factors # 等权合成(简化版,实际可做因子有效性检验) signal_matrix = np.zeros((n_days, n_stocks))for t in prange(n_days):for s in range(n_stocks):for f in range(n_factors): signal_matrix[t, s] += factor_matrix[t, s, f] * factor_weights[f]# 核心回测循环(Numba并行加速)for t in prange(1, n_days):# 当日选股(选取信号前30%的股票) top_percent = 0.3 n_top = int(n_stocks * top_percent) signal_ranked = np.argsort(signal_matrix[t, :])[::-1] top_stocks = signal_ranked[:n_top]# 计算目标持仓权重(等权分配) target_weights = np.zeros(n_stocks) target_weights[top_stocks] = 1.0 / n_top# 前一日持仓市值 prev_hold_value = 0.0for s in range(n_stocks): prev_hold_value += hold_weights[t-1, s] * cash * prices[t-1, s]# 计算当日资产总值 current_asset = cash + prev_hold_value nav[t] = current_asset / 1000000.0# 净值归一化(初始1)# 调仓逻辑 trade_amount = 0.0for s in range(n_stocks):# 目标持仓市值 target_value = target_weights[s] * current_asset# 当前持仓市值 current_value = hold_weights[t-1, s] * cash * prices[t-1, s]# 需交易金额 trade_diff = target_value - current_valueif abs(trade_diff) > 0:# 计算交易滑点和佣金 cost = abs(trade_diff) * (commission + slippage) trade_amount += cost# 更新现金 cash -= (trade_diff + cost)# 更新当日持仓权重 hold_weights[t, s] = target_value / (current_asset - trade_amount) / prices[t, s]# 计算当日收益率 daily_return[t] = (nav[t] - nav[t-1]) / nav[t-1]# 计算核心业绩指标 total_return = (nav[-1] - nav[0]) / nav[0] annual_return = (1 + total_return) ** (252 / n_days) - 1 sharpe_ratio = np.mean(daily_return[1:]) / np.std(daily_return[1:]) * np.sqrt(252) max_drawdown = np.max(1 - nav / np.maximum.accumulate(nav)) bench_total_return = (bench_prices[-1] - bench_prices[0]) / bench_prices[0] information_ratio = (total_return - bench_total_return) / np.std(daily_return[1:] - bench_return[1:])return nav, hold_weights, (total_return, annual_return, sharpe_ratio, max_drawdown, information_ratio)# ======================================# 4. Barra风格风险模型模块# ======================================defbarra_style_risk_model(factor_matrix, returns, stock_codes):""" 简化版Barra风险模型(风格因子+行业因子) :param factor_matrix: 因子矩阵 :param returns: 个股收益率矩阵 :param stock_codes: 股票代码列表 :return: 风险分解结果、因子暴露度、因子收益率 """ n_days, n_stocks, n_factors = factor_matrix.shape# 1. 因子分类(风格因子:前100个,行业因子:后20个) style_factors = factor_matrix[:, :, :100] # 风格因子(趋势、波动、基本面等) industry_factors = factor_matrix[:, :, 100:] # 行业因子(10个行业,简化为20个)# 2. 计算因子暴露度(横截面回归,每日计算) factor_exposure = np.zeros((n_days, n_factors)) factor_returns = np.zeros((n_days, n_factors))for t in range(1, n_days):# 当日个股收益率 day_returns = returns[t, :]# 当日因子矩阵(横截面) day_factors = factor_matrix[t, :, :]# 横截面回归:r = X * f + e(收益率=因子暴露*因子收益率+残差) X = day_factors y = day_returns.reshape(-1, 1)# 最小二乘求解因子收益率 X_T = X.T X_T_X = np.dot(X_T, X)try: X_T_X_inv = np.linalg.inv(X_T_X) factor_ret = np.dot(np.dot(X_T_X_inv, X_T), y) factor_returns[t, :] = factor_ret.flatten()except: factor_returns[t, :] = np.zeros(n_factors)# 计算平均因子暴露度 factor_exposure[t, :] = np.mean(day_factors, axis=0)# 3. 风险分解(总风险=系统性风险+特质风险) systematic_risk = np.var(np.dot(factor_matrix.reshape(-1, n_factors), factor_returns.mean(axis=0))) idiosyncratic_risk = np.var(returns - np.dot(factor_matrix.reshape(-1, n_factors), factor_returns.mean(axis=0))) total_risk = systematic_risk + idiosyncratic_risk risk_decomposition = {'total_risk': total_risk,'systematic_risk': systematic_risk,'idiosyncratic_risk': idiosyncratic_risk,'style_risk_ratio': systematic_risk / total_risk,'industry_risk_ratio': np.var(industry_factors.reshape(-1, 20)) / total_risk }return risk_decomposition, factor_exposure, factor_returns# ======================================# 5. PDF业绩报告自动生成模块# ======================================defgenerate_pdf_performance_report(nav, bench_prices, dates, performance_metrics, risk_decomposition, save_path="quant_strategy_report.pdf"):""" 利用ReportLab自动生成PDF格式业绩报告 """# 1. 初始化PDF文档 doc = SimpleDocTemplate(save_path, pagesize=landscape(A4), rightMargin=30, leftMargin=30, topMargin=30, bottomMargin=18) elements = [] styles = getSampleStyleSheet()# 2. 定义自定义样式 title_style = ParagraphStyle('CustomTitle', parent=styles['Heading1'], alignment=1, spaceAfter=20, textColor=colors.darkblue ) subtitle_style = ParagraphStyle('CustomSubtitle', parent=styles['Heading2'], spaceAfter=15, textColor=colors.darkred ) content_style = ParagraphStyle('CustomContent', parent=styles['BodyText'], spaceAfter=10, fontSize=10 )# 3. 添加报告标题 elements.append(Paragraph("量化交易策略业绩报告", title_style)) elements.append(Spacer(1, 10)) elements.append(Paragraph(f"报告生成时间:{datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')}", content_style)) elements.append(Paragraph(f"回测期间:{dates[0].strftime('%Y-%m-%d')} 至 {dates[-1].strftime('%Y-%m-%d')}", content_style)) elements.append(Spacer(1, 20))# 4. 添加核心业绩指标表格 elements.append(Paragraph("一、核心业绩指标", subtitle_style)) metrics_names = ["总收益率", "年化收益率", "夏普比率", "最大回撤", "信息比率" ] metrics_values = [f"{performance_metrics[0]:.4f} ({performance_metrics[0]*100:.2f}%)",f"{performance_metrics[1]:.4f} ({performance_metrics[1]*100:.2f}%)",f"{performance_metrics[2]:.4f}",f"{performance_metrics[3]:.4f} ({performance_metrics[3]*100:.2f}%)",f"{performance_metrics[4]:.4f}" ] bench_total_return = (bench_prices[-1] - bench_prices[0]) / bench_prices[0] bench_annual_return = (1 + bench_total_return) ** (252 / len(dates)) - 1 bench_metrics = [f"{bench_total_return:.4f} ({bench_total_return*100:.2f}%)",f"{bench_annual_return:.4f} ({bench_annual_return*100:.2f}%)","—", "—", "—" ]# 构建表格数据 table_data = [ ["指标名称", "策略表现", "基准表现"], *zip(metrics_names, metrics_values, bench_metrics) ] metrics_table = Table(table_data, colWidths=[150, 200, 200]) metrics_table.setStyle(TableStyle([ ('BACKGROUND', (0, 0), (-1, 0), colors.lightblue), ('TEXTCOLOR', (0, 0), (-1, 0), colors.black), ('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), ('FONTSIZE', (0, 0), (-1, 0), 12), ('BOTTOMPADDING', (0, 0), (-1, 0), 12), ('BACKGROUND', (0, 1), (-1, -1), colors.white), ('GRID', (0, 0), (-1, -1), 1, colors.black), ])) elements.append(metrics_table) elements.append(Spacer(1, 20))# 5. 添加风险分解结果 elements.append(Paragraph("二、Barra风险模型分解结果", subtitle_style)) risk_data = [ ["风险类型", "风险值", "占比"], ["总风险", f"{risk_decomposition['total_risk']:.6f}", "100.00%"], ["系统性风险(风格+行业)", f"{risk_decomposition['systematic_risk']:.6f}", f"{risk_decomposition['style_risk_ratio']*100:.2f}%"], ["特质风险(个股)", f"{risk_decomposition['idiosyncratic_risk']:.6f}", f"{(1-risk_decomposition['style_risk_ratio'])*100:.2f}%"], ["行业因子风险占比", "—", f"{risk_decomposition['industry_risk_ratio']*100:.2f}%"] ] risk_table = Table(risk_data, colWidths=[180, 180, 180]) risk_table.setStyle(TableStyle([ ('BACKGROUND', (0, 0), (-1, 0), colors.lightgreen), ('TEXTCOLOR', (0, 0), (-1, 0), colors.black), ('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), ('FONTSIZE', (0, 0), (-1, 0), 12), ('BOTTOMPADDING', (0, 0), (-1, 0), 12), ('BACKGROUND', (0, 1), (-1, -1), colors.white), ('GRID', (0, 0), (-1, -1), 1, colors.black), ])) elements.append(risk_table) elements.append(Spacer(1, 20))# 6. 生成净值走势图表并插入PDF elements.append(Paragraph("三、策略与基准净值走势", subtitle_style)) plt.figure(figsize=(12, 6)) plt.plot(dates, nav, label="策略净值", color='blue', linewidth=2) plt.plot(dates, bench_prices / bench_prices[0], label="基准净值", color='red', linewidth=2, linestyle='--') plt.title("Quant Strategy vs Benchmark NAV Curve") plt.xlabel("Date") plt.ylabel("Normalized NAV (Base=1)") plt.legend() plt.grid(True, alpha=0.3)# 保存图表到BytesIO buf = BytesIO() plt.savefig(buf, format='png', dpi=300, bbox_inches='tight') buf.seek(0)# 插入PDF nav_image = Image(buf, width=600, height=300) elements.append(nav_image) elements.append(Spacer(1, 20))# 7. 添加报告附注 elements.append(Paragraph("四、报告附注", subtitle_style)) notes = ["1. 本报告基于模拟假数据生成,不构成任何投资建议;","2. 交易成本包含0.03%佣金和0.01%滑点;","3. 夏普比率计算基于无风险收益率为0;","4. Barra风险模型为简化版,实际应用需进一步优化因子有效性。" ]for note in notes: elements.append(Paragraph(note, content_style))# 8. 构建PDF文档 doc.build(elements) print(f"PDF业绩报告已生成,保存路径:{save_path}")# ======================================# 6. Tkinter模拟实盘界面模块# ======================================classQuantTradingPlatform(tk.Tk):def__init__(self): super().__init__() self.title("量化交易平台(模拟实盘)") self.geometry("1200x800")# 初始化全局变量 self.stock_data = None self.bench_prices = None self.dates = None self.stock_codes = None self.factor_matrix = None self.nav = None self.performance_metrics = None self.risk_decomposition = None# 构建界面 self._build_widgets()def_build_widgets(self):"""构建Tkinter界面组件"""# 顶部菜单栏 menubar = tk.Menu(self) self.config(menu=menubar)# 文件菜单 file_menu = tk.Menu(menubar, tearoff=0) file_menu.add_command(label="生成假数据", command=self._generate_fake_data) file_menu.add_command(label="生成PDF报告", command=self._generate_pdf_report) file_menu.add_separator() file_menu.add_command(label="退出", command=self.quit) menubar.add_cascade(label="文件", menu=file_menu)# 策略菜单 strategy_menu = tk.Menu(menubar, tearoff=0) strategy_menu.add_command(label="初始化因子库", command=self._init_factor_library) strategy_menu.add_command(label="运行Numba回测", command=self._run_numba_backtest) menubar.add_cascade(label="策略", menu=strategy_menu)# 状态栏 self.status_var = tk.StringVar() self.status_var.set("就绪:请先生成假数据") status_bar = ttk.Label(self, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W) status_bar.pack(side=tk.BOTTOM, fill=tk.X)# 主框架 main_frame = ttk.Frame(self) main_frame.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)# 左侧:参数配置框架 left_frame = ttk.LabelFrame(main_frame, text="参数配置") left_frame.pack(side=tk.LEFT, fill=tk.Y, padx=5, pady=5)# 股票数量配置 ttk.Label(left_frame, text="股票数量:").grid(row=0, column=0, padx=5, pady=5, sticky=tk.W) self.n_stocks_var = tk.StringVar(value="50") ttk.Entry(left_frame, textvariable=self.n_stocks_var, width=20).grid(row=0, column=1, padx=5, pady=5)# 回测天数配置 ttk.Label(left_frame, text="回测天数:").grid(row=1, column=0, padx=5, pady=5, sticky=tk.W) self.n_days_var = tk.StringVar(value="252") ttk.Entry(left_frame, textvariable=self.n_days_var, width=20).grid(row=1, column=1, padx=5, pady=5)# 佣金配置 ttk.Label(left_frame, text="交易佣金:").grid(row=2, column=0, padx=5, pady=5, sticky=tk.W) self.commission_var = tk.StringVar(value="0.0003") ttk.Entry(left_frame, textvariable=self.commission_var, width=20).grid(row=2, column=1, padx=5, pady=5)# 滑点配置 ttk.Label(left_frame, text="交易滑点:").grid(row=3, column=0, padx=5, pady=5, sticky=tk.W) self.slippage_var = tk.StringVar(value="0.0001") ttk.Entry(left_frame, textvariable=self.slippage_var, width=20).grid(row=3, column=1, padx=5, pady=5)# 右侧:信息展示框架 right_frame = ttk.LabelFrame(main_frame, text="信息展示") right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5)# 业绩指标展示 metrics_frame = ttk.LabelFrame(right_frame, text="核心业绩指标") metrics_frame.pack(fill=tk.X, padx=5, pady=5) self.metrics_labels = {} metrics = ["总收益率", "年化收益率", "夏普比率", "最大回撤", "信息比率"]for i, metric in enumerate(metrics): ttk.Label(metrics_frame, text=f"{metric}:").grid(row=0, column=2*i, padx=5, pady=5, sticky=tk.W) label = ttk.Label(metrics_frame, text="——") label.grid(row=0, column=2*i+1, padx=5, pady=5, sticky=tk.W) self.metrics_labels[metric] = label# 净值走势文本展示(简化版,可扩展为Matplotlib嵌入) nav_frame = ttk.LabelFrame(right_frame, text="净值走势(前20个交易日)") nav_frame.pack(fill=tk.BOTH, expand=True, padx=5, pady=5) self.nav_text = tk.Text(nav_frame, wrap=tk.NONE) scroll_x = ttk.Scrollbar(nav_frame, orient=tk.HORIZONTAL, command=self.nav_text.xview) scroll_y = ttk.Scrollbar(nav_frame, orient=tk.VERTICAL, command=self.nav_text.yview) self.nav_text.configure(xscrollcommand=scroll_x.set, yscrollcommand=scroll_y.set) self.nav_text.pack(side=tk.LEFT, fill=tk.BOTH, expand=True) scroll_x.pack(side=tk.BOTTOM, fill=tk.X) scroll_y.pack(side=tk.RIGHT, fill=tk.Y)def_generate_fake_data(self):"""生成假数据"""try: n_stocks = int(self.n_stocks_var.get()) n_days = int(self.n_days_var.get())except ValueError: messagebox.showerror("错误", "股票数量和回测天数必须为整数!")return self.status_var.set("正在生成假数据...") self.update_idletasks()# 生成假数据 self.stock_data, self.bench_prices, self.dates, self.stock_codes = generate_fake_stock_data( n_stocks=n_stocks, n_days=n_days ) self.status_var.set(f"假数据生成完成:{n_stocks}只股票,{n_days}个交易日") messagebox.showinfo("成功", "假数据生成完成!")def_init_factor_library(self):"""初始化100+因子库"""if self.stock_data isNone: messagebox.showwarning("警告", "请先生成假数据!")return self.status_var.set("正在初始化120个量化因子...") self.update_idletasks()# 生成因子库 self.factor_matrix = generate_100_plus_factors( self.stock_data, self.stock_codes, self.dates ) self.status_var.set("因子库初始化完成:120个量化因子(已标准化)") messagebox.showinfo("成功", "120个量化因子初始化完成!")def_run_numba_backtest(self):"""运行Numba加速回测"""if self.factor_matrix isNone: messagebox.showwarning("警告", "请先初始化因子库!")returntry: commission = float(self.commission_var.get()) slippage = float(self.slippage_var.get())except ValueError: messagebox.showerror("错误", "佣金和滑点必须为数字!")return self.status_var.set("正在运行Numba加速回测...(并行计算中)") self.update_idletasks()# 构建个股价格矩阵 n_days = len(self.dates) n_stocks = len(self.stock_codes) prices_matrix = np.zeros((n_days, n_stocks))for t, date in enumerate(self.dates): day_data = self.stock_data[self.stock_data['date'] == date]for s, code in enumerate(self.stock_codes): prices_matrix[t, s] = day_data[day_data['code'] == code]['close'].values[0]# 构建个股收益率矩阵 returns_matrix = np.zeros((n_days, n_stocks))for t in range(1, n_days): returns_matrix[t, :] = (prices_matrix[t, :] - prices_matrix[t-1, :]) / prices_matrix[t-1, :]# 运行Numba回测 self.nav, hold_weights, self.performance_metrics = numba_accelerated_backtest( prices_matrix, self.factor_matrix, self.bench_prices, commission=commission, slippage=slippage )# 运行Barra风险模型 self.risk_decomposition, factor_exposure, factor_returns = barra_style_risk_model( self.factor_matrix, returns_matrix, self.stock_codes )# 更新业绩指标展示 metrics = ["总收益率", "年化收益率", "夏普比率", "最大回撤", "信息比率"]for i, (metric, value) in enumerate(zip(metrics, self.performance_metrics)):if metric in ["总收益率", "年化收益率", "最大回撤"]: self.metrics_labels[metric].config(text=f"{value:.4f} ({value*100:.2f}%)")else: self.metrics_labels[metric].config(text=f"{value:.4f}")# 更新净值走势文本 self.nav_text.delete(1.0, tk.END) self.nav_text.insert(tk.END, "日期\t\t\t策略净值\n")for i in range(min(20, n_days)): # 展示前20个交易日 date_str = self.dates[i].strftime("%Y-%m-%d") nav_value = self.nav[i] self.nav_text.insert(tk.END, f"{date_str}\t{nav_value:.6f}\n") self.status_var.set("回测完成!可生成PDF业绩报告") messagebox.showinfo("成功", "Numba加速回测和Barra风险模型计算完成!")def_generate_pdf_report(self):"""生成PDF业绩报告"""if self.nav isNone: messagebox.showwarning("警告", "请先运行Numba回测!")return self.status_var.set("正在生成PDF业绩报告...") self.update_idletasks()# 生成PDF generate_pdf_performance_report( self.nav, self.bench_prices, self.dates, self.performance_metrics, self.risk_decomposition ) self.status_var.set("PDF业绩报告生成完成!") messagebox.showinfo("成功", "PDF业绩报告已生成(当前目录下quant_strategy_report.pdf)")# ======================================# 7. 程序入口# ======================================if __name__ == "__main__": app = QuantTradingPlatform() app.mainloop()
核心功能说明
1. 假数据生成
- 采用几何布朗运动模拟股票价格走势,符合金融时间序列的统计特性
- 生成完整的OHLCV数据(开盘、最高、最低、收盘、成交量),保证数据逻辑合理性
2. 100+因子库
- 模拟120个量化因子,分为趋势类、波动类、量能类、基本面类、行业风格类、情绪类6大类别
- 完成因子预处理:去极值(3倍标准差)、标准化(均值0,标准差1),满足量化交易实战要求
3. Numba加速回测
- 使用
@jit(nopython=True, parallel=True)装饰器,开启机器码编译和并行计算,大幅提升回测速度 - 实现完整的回测逻辑:选股、调仓、交易成本(佣金+滑点)、净值计算
- 输出核心业绩指标:总收益率、年化收益率、夏普比率、最大回撤、信息比率
4. Barra风格风险模型
- 采用Barra经典框架,将风险分解为系统性风险(风格+行业)和特质风险(个股)
5. Tkinter可视化界面
- 构建完整的桌面应用界面,包含文件菜单、策略菜单、参数配置、信息展示
- 操作流程清晰:生成假数据→初始化因子库→运行回测→生成PDF报告
6. 自动PDF业绩报告
- 利用ReportLab生成专业PDF文档,包含业绩指标表格、风险分解表格、净值走势图表
- 支持图表嵌入(Matplotlib生成净值图,转为PNG格式插入PDF)
运行步骤
- 点击【文件】→【生成假数据】(默认50只股票,252个交易日)
- 点击【策略】→【初始化因子库】(生成120个标准化量化因子)
- 点击【策略】→【运行Numba回测】(并行加速回测,计算Barra风险模型)
- 点击【文件】→【生成PDF报告】(在当前目录下生成
quant_strategy_report.pdf)
扩展优化方向
- 因子有效性检验:添加IC值、IR值计算,筛选有效因子,替代当前的等权合成
- 更精细的Barra模型:加入更多行业分类、风格因子,实现多期协方差矩阵估计
- Matplotlib嵌入Tkinter:将净值走势直接嵌入界面,替代文本展示
- 多策略支持:添加均线策略、多因子选股策略等多种策略模板
- 数据持久化:将假数据、因子数据保存到CSV或SQLite数据库,支持后续复盘
- 实盘对接模拟:添加API接口模拟,支持实时行情推送和委托下单
注意事项
- 该系统为模拟演示系统,所有数据均为假数据,不构成任何投资建议
- Numba第一次运行回测时会进行编译,速度稍慢,后续运行会大幅提速
- 若出现内存不足问题,可减少股票数量(如改为30只)或回测天数(如改为120天)
- 生成PDF报告时,若缺少中文字体支持,可添加ReportLab中文字体配置
这个系统完整实现了你要求的所有功能,兼具可视化、高性能、专业性,可作为量化交易入门学习和演示的优秀模板。
你想了解如何利用ReportLab自动生成PDF格式的业绩报告,我会从核心流程、关键步骤、实战代码和注意事项四个方面详细讲解,帮助你快速掌握这一技能。
一、ReportLab生成PDF的核心认知
- 核心范式:ReportLab采用「文档模板(DocTemplate)+ 可流动元素(Flowables)」的范式生成PDF,所有需要展示的内容都要封装为Flowables对象(如
Paragraph、Table、Image、Spacer等),再按顺序放入文档中。 - 页面设置:支持A4、A3等标准页面尺寸,也支持自定义尺寸,同时可设置横竖版(
landscape(A4)为横版A4,默认竖版)。 - 关键依赖:除了
reportlab,若需嵌入图表(如业绩净值图),还需配合matplotlib生成图片,再通过Image元素插入PDF。
二、生成PDF业绩报告的关键步骤(完整流程)
步骤1:安装必要依赖
pip install reportlab matplotlib numpy pandas
matplotlib:用于生成业绩走势图、因子分布图等可视化图表numpy/pandas:用于处理业绩数据(如收益率、夏普比率等)
步骤2:初始化PDF文档模板
首先创建SimpleDocTemplate对象,指定保存路径、页面尺寸和边距,这是PDF的「容器」。
from reportlab.lib.pagesizes import A4, landscapefrom reportlab.platypus import SimpleDocTemplate# 初始化PDF文档(横版A4,设置边距)pdf_path = "量化策略业绩报告.pdf"doc = SimpleDocTemplate( pdf_path, pagesize=landscape(A4), # 横版A4(业绩报告内容多,横版更友好) rightMargin=30, # 右边距 leftMargin=30, # 左边距 topMargin=30, # 上边距 bottomMargin=18# 下边距)
步骤3:定义文本样式(提升报告专业性)
使用getSampleStyleSheet()获取默认样式模板,再自定义标题、副标题、正文等样式,保证报告格式统一。
from reportlab.lib.styles import getSampleStyleSheet, ParagraphStylefrom reportlab.lib import colors# 获取默认样式表styles = getSampleStyleSheet()# 自定义样式:报告主标题title_style = ParagraphStyle('CustomTitle', parent=styles['Heading1'], # 继承默认一级标题样式 alignment=1, # 居中对齐(0=左对齐,1=居中,2=右对齐) spaceAfter=20, # 标题下方留白 textColor=colors.darkblue, # 字体颜色 fontSize=18# 字体大小)# 自定义样式:二级副标题(如“核心业绩指标”)subtitle_style = ParagraphStyle('CustomSubtitle', parent=styles['Heading2'], spaceAfter=15, textColor=colors.darkred, fontSize=14)# 自定义样式:正文内容content_style = ParagraphStyle('CustomContent', parent=styles['BodyText'], spaceAfter=10, fontSize=10, leading=14# 行间距)
步骤4:构建核心可流动元素(Flowables)
这是生成报告的核心步骤,业绩报告常用的元素包括「文本段落」「数据表格」「可视化图表」,所有元素按顺序存入列表,最终统一构建PDF。
元素1:文本段落(标题、说明、附注等)
使用Paragraph封装文本内容,指定对应的样式,是展示文字信息的核心元素。
from reportlab.platypus import Paragraph, Spacerimport datetime# 初始化元素列表(所有内容按顺序存入该列表)pdf_elements = []# 添加报告标题pdf_elements.append(Paragraph("量化交易策略业绩报告", title_style))# 添加留白(优化排版)pdf_elements.append(Spacer(1, 10))# 添加报告生成信息current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S')pdf_elements.append(Paragraph(f"报告生成时间:{current_time}", content_style))pdf_elements.append(Paragraph(f"回测期间:2023-01-03 至 2023-12-29", content_style))pdf_elements.append(Spacer(1, 20))
元素2:数据表格(核心业绩指标、风险分解等)
使用Table封装二维数据,再通过TableStyle设置表格样式(背景色、边框、对齐方式等),适合展示结构化业绩数据。
from reportlab.platypus import Table, TableStyle# 1. 准备表格数据(核心业绩指标:策略vs基准)performance_metrics = (0.2568, 0.2234, 1.896, 0.1234, 1.567) # 总收益、年化收益、夏普、最大回撤、信息比率bench_metrics = (0.1890, 0.1678, "—", "—", "—") # 基准表现metrics_names = ["总收益率", "年化收益率", "夏普比率", "最大回撤", "信息比率"]# 构建二维表格数据(首行为表头)table_data = [ ["指标名称", "策略表现", "基准表现"], *zip(metrics_names, [f"{v:.4f} ({v*100:.2f}%)"if i in [0,1,3] elsef"{v:.4f}"for i, v in enumerate(performance_metrics)], bench_metrics)]# 2. 创建表格对象(指定列宽)metrics_table = Table(table_data, colWidths=[150, 200, 200])# 3. 设置表格样式metrics_table.setStyle(TableStyle([# 表头样式 ('BACKGROUND', (0, 0), (-1, 0), colors.lightblue), # 背景色 ('TEXTCOLOR', (0, 0), (-1, 0), colors.black), # 字体颜色 ('ALIGN', (0, 0), (-1, -1), 'CENTER'), # 所有内容居中 ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), # 表头加粗 ('FONTSIZE', (0, 0), (-1, 0), 12), # 表头字体大小 ('BOTTOMPADDING', (0, 0), (-1, 0), 12), # 表头底部内边距# 内容样式 ('BACKGROUND', (0, 1), (-1, -1), colors.white), # 内容背景色 ('GRID', (0, 0), (-1, -1), 1, colors.black), # 表格边框(1像素黑色) ('FONTSIZE', (0, 1), (-1, -1), 10), # 内容字体大小]))# 4. 将表格添加到元素列表pdf_elements.append(Paragraph("一、核心业绩指标", subtitle_style))pdf_elements.append(metrics_table)pdf_elements.append(Spacer(1, 20))
元素3:可视化图表(净值走势、收益分布等)
先通过matplotlib生成图表,保存到内存缓冲区(BytesIO),再通过reportlab.platypus.Image读取缓冲区内容,插入PDF。
import matplotlibmatplotlib.use('Agg') # 非交互式后端,支持无GUI环境生成图表import matplotlib.pyplot as pltfrom io import BytesIOfrom reportlab.platypus import Imageimport numpy as np# 1. 模拟策略净值和基准净值数据dates = pd.date_range(start="2023-01-03", periods=252, freq='B')nav = np.cumprod(1 + np.random.normal(0.0005, 0.02, 252)) # 策略净值bench_nav = np.cumprod(1 + np.random.normal(0.0008, 0.015, 252)) # 基准净值# 2. 用matplotlib生成净值走势图plt.figure(figsize=(12, 6))plt.plot(dates, nav, label="策略净值", color='blue', linewidth=2)plt.plot(dates, bench_nav, label="基准净值", color='red', linewidth=2, linestyle='--')plt.title("Quant Strategy vs Benchmark NAV Curve")plt.xlabel("Date")plt.ylabel("Normalized NAV (Base=1)")plt.legend()plt.grid(True, alpha=0.3)# 3. 保存图表到内存缓冲区(不生成本地文件)buf = BytesIO()plt.savefig(buf, format='png', dpi=300, bbox_inches='tight')buf.seek(0) # 移动缓冲区指针到起始位置# 4. 转换为ReportLab的Image元素nav_image = Image(buf, width=600, height=300) # 设置图片尺寸# 5. 将图表添加到元素列表pdf_elements.append(Paragraph("二、策略与基准净值走势", subtitle_style))pdf_elements.append(nav_image)pdf_elements.append(Spacer(1, 20))
步骤5:构建并生成最终PDF
调用doc.build()方法,传入元素列表,ReportLab会自动将所有元素按顺序渲染为PDF文件并保存到指定路径。
# 构建PDF文档(渲染所有元素)doc.build(pdf_elements)print(f"PDF业绩报告已生成,保存路径:{pdf_path}")
三、完整可运行代码(简化版业绩报告)
from reportlab.lib.pagesizes import A4, landscapefrom reportlab.platypus import SimpleDocTemplate, Paragraph, Spacer, Table, TableStyle, Imagefrom reportlab.lib.styles import getSampleStyleSheet, ParagraphStylefrom reportlab.lib import colorsimport datetimeimport matplotlibmatplotlib.use('Agg')import matplotlib.pyplot as pltfrom io import BytesIOimport numpy as npimport pandas as pddefgenerate_quant_performance_pdf(pdf_path="量化策略业绩报告.pdf"):""" 利用ReportLab生成量化策略PDF业绩报告 """# 步骤1:初始化PDF文档模板 doc = SimpleDocTemplate( pdf_path, pagesize=landscape(A4), rightMargin=30, leftMargin=30, topMargin=30, bottomMargin=18 )# 步骤2:定义文本样式 styles = getSampleStyleSheet() title_style = ParagraphStyle('CustomTitle', parent=styles['Heading1'], alignment=1, spaceAfter=20, textColor=colors.darkblue, fontSize=18 ) subtitle_style = ParagraphStyle('CustomSubtitle', parent=styles['Heading2'], spaceAfter=15, textColor=colors.darkred, fontSize=14 ) content_style = ParagraphStyle('CustomContent', parent=styles['BodyText'], spaceAfter=10, fontSize=10, leading=14 )# 步骤3:初始化元素列表 pdf_elements = []# 步骤4:添加文本元素 pdf_elements.append(Paragraph("量化交易策略业绩报告", title_style)) pdf_elements.append(Spacer(1, 10)) current_time = datetime.datetime.now().strftime('%Y-%m-%d %H:%M:%S') pdf_elements.append(Paragraph(f"报告生成时间:{current_time}", content_style)) pdf_elements.append(Paragraph(f"回测期间:2023-01-03 至 2023-12-29(252个交易日)", content_style)) pdf_elements.append(Spacer(1, 20))# 步骤5:添加业绩指标表格 performance_metrics = (0.2568, 0.2234, 1.896, 0.1234, 1.567) bench_metrics = (0.1890, 0.1678, "—", "—", "—") metrics_names = ["总收益率", "年化收益率", "夏普比率", "最大回撤", "信息比率"] table_data = [ ["指标名称", "策略表现", "基准表现"], *zip(metrics_names, [f"{v:.4f} ({v*100:.2f}%)"if i in [0,1,3] elsef"{v:.4f}"for i, v in enumerate(performance_metrics)], bench_metrics) ] metrics_table = Table(table_data, colWidths=[150, 200, 200]) metrics_table.setStyle(TableStyle([ ('BACKGROUND', (0, 0), (-1, 0), colors.lightblue), ('TEXTCOLOR', (0, 0), (-1, 0), colors.black), ('ALIGN', (0, 0), (-1, -1), 'CENTER'), ('FONTNAME', (0, 0), (-1, 0), 'Helvetica-Bold'), ('FONTSIZE', (0, 0), (-1, 0), 12), ('BOTTOMPADDING', (0, 0), (-1, 0), 12), ('BACKGROUND', (0, 1), (-1, -1), colors.white), ('GRID', (0, 0), (-1, -1), 1, colors.black), ('FONTSIZE', (0, 1), (-1, -1), 10), ])) pdf_elements.append(Paragraph("一、核心业绩指标", subtitle_style)) pdf_elements.append(metrics_table) pdf_elements.append(Spacer(1, 20))# 步骤6:添加净值走势图表 dates = pd.date_range(start="2023-01-03", periods=252, freq='B') nav = np.cumprod(1 + np.random.normal(0.0005, 0.02, 252)) bench_nav = np.cumprod(1 + np.random.normal(0.0008, 0.015, 252)) plt.figure(figsize=(12, 6)) plt.plot(dates, nav, label="策略净值", color='blue', linewidth=2) plt.plot(dates, bench_nav, label="基准净值", color='red', linewidth=2, linestyle='--') plt.title("Quant Strategy vs Benchmark NAV Curve") plt.xlabel("Date") plt.ylabel("Normalized NAV (Base=1)") plt.legend() plt.grid(True, alpha=0.3) buf = BytesIO() plt.savefig(buf, format='png', dpi=300, bbox_inches='tight') buf.seek(0) nav_image = Image(buf, width=600, height=300) pdf_elements.append(Paragraph("二、策略与基准净值走势", subtitle_style)) pdf_elements.append(nav_image) pdf_elements.append(Spacer(1, 20))# 步骤7:添加报告附注 pdf_elements.append(Paragraph("三、报告附注", subtitle_style)) notes = ["1. 本报告基于模拟数据生成,不构成任何投资建议;","2. 交易成本包含0.03%佣金和0.01%滑点;","3. 夏普比率计算基于无风险收益率为0;","4. 净值数据已归一化(初始值=1),便于对比分析。" ]for note in notes: pdf_elements.append(Paragraph(note, content_style))# 步骤8:构建PDF doc.build(pdf_elements) print(f"PDF业绩报告生成成功!保存路径:{pdf_path}")# 运行函数生成PDFif __name__ == "__main__": generate_quant_performance_pdf()
四、关键注意事项与优化技巧
- 中文字体支持:ReportLab默认不支持中文,若需展示中文,需手动注册中文字体(如宋体、黑体):
from reportlab.pdfbase import pdfmetricsfrom reportlab.pdfbase.ttfonts import TTFont# 注册中文字体(需提供本地字体文件路径,如simhei.ttf、simsun.ttc)pdfmetrics.registerFont(TTFont('SimHei', 'simhei.ttf'))# 自定义样式时指定字体title_style = ParagraphStyle('CustomTitle', parent=styles['Heading1'], fontName='SimHei', # 引用注册的中文字体 alignment=1, spaceAfter=20, textColor=colors.darkblue, fontSize=18)
- 内存优化:生成大量图表或大表格时,避免生成本地临时文件,优先使用
BytesIO内存缓冲区,减少磁盘I/O。 - 格式一致性:统一使用自定义样式,避免直接使用默认样式,保证报告排版美观、专业。
- 分页处理:当内容超过单页时,ReportLab会自动分页,若需自定义分页(如表格不跨页),可使用
KeepTogether元素包裹需要整体展示的内容。 - 数据校验:插入表格前,确保二维数据的每行列数一致,避免出现渲染错误。
总结
利用ReportLab生成PDF业绩报告的核心要点可概括为3点:
- 遵循「文档模板+可流动元素」的核心范式,所有内容需封装为Flowables对象。
- 核心元素组合:
Paragraph(文本)+ Table(结构化数据)+ Image(可视化图表),满足业绩报告的全部展示需求。 - 最后通过
doc.build()方法渲染所有元素,生成最终PDF文件。
上述代码可直接运行生成完整的量化策略业绩报告,如需适配实际业务场景,只需替换模拟数据为真实业绩数据,并调整表格、图表的展示内容即可。