ARTICLE DETAIL

资讯详情

深耕编程入门与网站建设的一线实战洞察。

Python股票量化系统实战:从数据清洗到回测引擎的完整实现

Python股票量化系统实战:从数据清洗到回测引擎的完整实现 简介Python股票量化系统源码及教程是一套面向量化投资入门者与Python开发者的完整项目提供可运行的量化系统与配套教学说明。系统依赖MySQL存储市场数据通过pip命令批量安装依赖后即可启动适合希望快速搭建本地量化策略测试环境的学习者。资源共244个文件压缩包仅3.53MB包含71个py源码、80个pyc文件、20个json配置、7个ui界面以及png图表和前端页面等覆盖策略逻辑、配置管理、界面交互与可视化展示目录结构清晰。教程涉及数据获取、历史分析、策略编写、回测执行、结果评估与风险控制并对win_main.py主脚本进行解读帮助用户从零理解量化系统开发全流程。目前已有38人学习下载对想掌握Python量化交易系统搭建和策略开发思路的用户而言是一份轻量且实用的参考资料。1. Python股票量化系统的边界与起点用Python写股票量化系统多数人第一步就错了不是先找源码而是先问自己手里的K线数据从哪来。我见过太多人从网上下了一份源码装上依赖跑出漂亮的资金曲线投入实盘后却连续止损。原因往往不在策略而在数据复权、信号滞后、手续费模型这些代码里看不见的地方。围绕一个可运行的Python股票量化系统源码下面会完整覆盖数据清洗、策略编写、回测验证到实盘部署前的检查适合刚读完Python教程想要动手做完整系统的开发者也适合想审视自己框架的老手。2. 从行情源到本地数据仓库Python量化系统的数据层实现数据是一切量化系统的地基。如果数据里的复权因子算错回测里的所有盈亏都会失真。常见做法是用akshare或tushare拉取历史行情理由很简单它们都免费而且返回的字段足够支撑一般策略。相比自己写爬虫去抓网页使用这些库可以将精力集中在数据校验上。另外如果你还在对照python安装教程配环境建议直接把Python升到3.10以上——pandas和sqlite3的新特性会减少后续兼容性问题。2.1 选型免费数据源与本地存储格式akshare不需要tokentushare需要积分我的建议是先用akshare等需要分钟线或因子数据再迁移。存储格式常用SQLite或者HDF5。SQLite适合单机回测和增量更新CSV虽然直观但多标的时文件量大且难以事务性更新。如果需要按日期切片、跨股票查询SQLite是更稳的选择。有些“免费python源码大全”里会把所有数据都存CSV结果一运行就内存溢出这不是代码问题是存储选型问题。2.2 数据获取与清洗代码从API到DataFrameimport akshare as ak import pandas as pd import sqlite3 df ak.stock_zh_a_hist( symbol000001, perioddaily, start_date20200101, end_date20241231, adjustqfq ) # 统一列名避免后续代码使用中文索引 df.rename(columns{ 日期: date, 开盘: open, 收盘: close, 最高: high, 最低: low, 成交量: volume }, inplaceTrue) print(df.head())symbol是股票代码字符串period支持daily/weekly/monthlystart_date和end_date按YYYYMMDD传adjust设为qfq表示前复权。这里最需要注意的是返回值顺序是正序还是倒序akshare默认正序但最好主动按date排序。另外日期列要转换成datetime类型避免后面对比时类型不统一。存储到SQLite时to_sql会做一次全量替换适合首次写入增量更新应使用INSERT OR REPLACE否则重复写入会生成重复行。conn sqlite3.connect(market.db) df[date] pd.to_datetime(df[date]) df.sort_values(date, inplaceTrue) df.to_sql(daily_000001, conn, if_existsreplace, indexFalse) conn.close()2.3 复权、对齐与去重量化数据仓库的几个硬指标前复权是动态的会导致历史价格不断变化因此对于策略回测我一般存两种原始价和复权因子方便切换。字段表如下。| 字段名 | 类型 | 说明 | | date | datetime | 交易日唯一索引 | | open/high/low/close | float | OHLC价格前复权处理 | | volume | int | 成交量单位手 | | factor | float | 复权因子用于后复权还原 | | turnover | float | 换手率可选 |多标的做因子计算时最容易出问题的不是价格而是对齐。用pivot把多只股票的close转成宽表后不同股票的停牌日会留下NaN。常见做法是ffill延续前值或者在计算因子时逐列删除NaN而不是整个数据集一起删。去重也一样同一个交易日同时出现两个收盘价时保留成交量较大的那条另一条删除。3. 因子计算与信号生成策略代码的源码写法策略代码的核心不是买卖时机而是把原始价格转换成可解释的因子。常见的双均线、RSI、布林带都可以在几分钟内写出但真正考验人的是信号生成的前视偏差。这里用一个成交量加权均线系统作为例子演示如何写因子、生成信号并检查未来函数。这一节里的源码风格可以直接复用到其他因子项目中。3.1 从行情到因子用向量化计算避免循环def add_factors(df: pd.DataFrame) - pd.DataFrame: # 5日与20日均线 df[ma5] df[close].rolling(5).mean() df[ma20] df[close].rolling(20).mean() # 量能比值当日成交量 / 过去10日平均成交量 df[vol_ratio] df[volume] / df[volume].rolling(10).mean() # 避免第一行出现NaN影响后续运算 df.dropna(inplaceTrue) return dfrolling窗口默认右对齐意味着当根K线收盘后计算不含未来数据。这里要明确指出pandas的rolling天然避免了未来函数但如果后续手动用shift方向就很容易搞反。比如要取“昨天均线”应该用df[ma5].shift(1)而不是shift(-1)。成交量比值的分母用10日平滑能过滤单日放量的噪音。3.2 信号生成与交易指令的分离df[signal] 0 df.loc[df[ma5] df[ma20], signal] 1 # 多头持仓 df.loc[df[ma5] df[ma20], signal] 0 # 空仓或空头 # 根据信号转变点发出交易单 df[trade] df[signal].diff().fillna(0) # trade1 买入trade-1 卖出 orders df[df[trade] ! 0]signal是目标仓位trade是仓位变化量。这样设计的好处是策略逻辑只负责给出目标状态回测引擎只负责按trade字段执行二者解耦。很多新手在策略里直接写买入卖出导致换仓逻辑难以测试。diff函数计算相邻两行的差值等于1说明刚从空仓变成持仓等于-1说明刚刚清仓。3.3 未来函数检查最容易写错的一段未来函数的典型错误是使用shift(-1)提前取下一根K线的数据。比如想在收盘前判断均线交叉就会写出df[ma20].iloc[i1]这类代码。自查方法也很简单把信号日为1的样本对比当天收盘价和次一天开盘价如果平均跳空收益显著为正说明信号可能泄漏了未来信息。另一个技巧是打开回测引擎的逐笔打印检查每一笔委托的发出时间一定晚于信号K线的收盘时间。4. 事件驱动回测引擎逐K线模拟交易与绩效评估在量化系统里因子和信号解决“什么时候买”回测引擎解决“买了之后会发生什么”。很多人直接用向量化方式对资金曲线做一次性计算但这样做无法准确模拟涨跌停、停牌和账户资金约束。我一般会实现一个极简的事件驱动引擎每一根K线触发一个事件引擎更新账户状态。这是量化系统源码里最值得反复打磨的部分。4.1 回测引擎的最小架构K线循环、账户状态、订单队列class Backtester: def __init__(self, df, initial_cash100000, fee0.001, slippage0.002): self.df df.reset_index(dropTrue) self.cash initial_cash self.position 0 self.fee fee self.slippage slippage self.equity_curve [] def run(self): for i in range(1, len(self.df)): bar self.df.iloc[i] signal self.df.iloc[i - 1][trade] # 上一根K线的信号 if signal 1 and self.position 0: buy_price bar[open] * (1 self.slippage) qty int(self.cash / (buy_price * (1 self.fee))) self.cash - qty * buy_price * (1 self.fee) self.position qty elif signal -1 and self.position 0: sell_price bar[open] * (1 - self.slippage) self.cash self.position * sell_price * (1 - self.fee) self.position 0 self.equity_curve.append(self.cash self.position * bar[close])信号在K线结束后生成用下一根K线的开盘价成交这样的实现天然避免前视偏差。int(self.cash / ...)做向下取整保证买入数量是手数整数倍。如果没有把trade信号与执行分离这段代码里的signal就只能是历史值。这里我把循环从range(1, len(self.df))开始是因为第0根K线没有前一根信号。4.2 撮合细节手续费、滑点与涨跌停限制参数默认值影响fee0.001高频率策略对费率敏感slippage0.002小盘股需要调大initial_cash100000仓位计算基数真实交易中涨停板时可能买不进跌停板时卖不出。如果bar[open]已经涨停即open round(previous_close * 1.1, 2)应该跳过买入。否则回测资金会在实盘中变成一堆无法成交的委托单。滑点参数在小市值股票上需要调到0.005以上因为流动性不足会导致更大的买卖价差。4.3 绩效指标计算年化收益、最大回撤、夏普比率equity pd.Series(self.equity_curve) returns equity.pct_change().dropna() annual_return (equity.iloc[-1] / equity.iloc[0]) ** (252 / len(equity)) - 1 max_drawdown (equity / equity.cummax() - 1).min() sharpe returns.mean() / returns.std() * (252 ** 0.5)年化收益按252个交易日计算最大回撤是每个时点净值相对历史最高点的最大回落。夏普比率使用日收益率均值除以标准差再开方年化。这里不要忽略无风险利率但A股散户实盘通常直接用0代替误差不大。equity.cummax()计算历史最高净值任何时间点的回撤都是相对这个最高点的。5. 参数敏感性、样本外测试与上线前检查5.1 参数敏感性热力图找出过拟合区间双均线参数ma1和ma2的固定组合如果在某个窄区间内表现极好而邻近参数收益骤降说明是过拟合。用两层循环生成热力图数据并输出所有参数结果分布。import numpy as np results [] for ma1 in range(3, 30, 2): for ma2 in range(20, 90, 5): if ma1 ma2: continue ret run_backtest(df, ma1, ma2) # 重复执行策略回测 results.append((ma1, ma2, ret))观察最优参数周边区域的收益均值。如果最优参数的收益和次优参数差别超过20%且周边参数收益骤降说明模型参数没有稳定性。正确做法是选择一块收益高原的中央参数而不是单纯选最高点。5.2 样本外滚动测试与随机种子无关性将数据按时间分为训练、验证、测试三段时间用训练段调参验证段看稳定性最后在测试段跑一次。如果策略中任何地方用到随机数例如随机森林需要固定随机种子并多跑几次确认结果不是随机种子碰出来的。滚动测试的做法是每间隔一年回测一次观察逐年的收益分布不要只看累计曲线。5.3 上线前的最小检查清单数据是否使用最新复权因子信号是否比实际执行晚一根K线手续费和滑点是否偏离当前市场流动性账户起始资金是否覆盖最小交易单元时间时区是否统一。把回测引擎中的委托日志打开逐笔打印成交比对最后一笔委托的时间与权益曲线确认没有未来的资金参与。本文还有配套的精品资源点击获取
返回列表