简介:这份资源是面向股票量化交易初学者与进阶学习者的系统学习笔记压缩包,围绕数学模型与计算技术在历史交易数据中挖掘投资机会这一主线展开。内容从量化交易的历史背景与发展现状讲起,深入解析市场微观结构、价格趋势预测、风险管理与量化投资策略,并覆盖时间序列分析、机器学习模型等建模方法,以及数据采集、清洗、特征提取、模型训练验证到策略回测评估的完整流程,还讨论了过拟合、资金管理与交易成本等实操问题。资源共39个文件,以16个Python脚本、6个SQL文件、4个XML配置、4个Markdown笔记及若干Shell脚本为主,另含数据库与工具文件,压缩包约822KB,结构清晰便于按模块查阅。目前已有91人学习。读者可借此建立从理论到落地的量化知识框架,掌握Python、R等工具在策略开发中的应用,并了解最大回撤、VaR、ES等风险度量方法及大数据、人工智能等前沿方向。
1. 一份股票量化学习笔记,到底能帮你省下多少翻文档的时间
如果你正在用 Python 做股票量化,大概率经历过这个阶段:数据接口换了一个又一个,回测框架搭到一半发现复权没处理,策略写完跑出来年化 300%,仔细一查是未来函数。这份《基于股票量化交易分析的学习笔记.zip》就是冲着这些具体问题来的——它不是一本从零讲 K 线的教科书,而是一个从业者在实际做股票组合分析、策略回测、数据清洗过程中沉淀下来的可运行笔记集合。
里面覆盖的东西很实在:怎么用常见数据源拉 A 股历史行情、怎么做前复权后复权、怎么把原始数据整理成能喂给回测引擎的格式、怎么写一个均线策略并验证它没有偷看未来。适合两类人:一是刚转量化、Python 基础还行但不知道从哪下手的新手;二是已经能跑策略、但数据管道和回测细节总出玄学问题的熟手。下面我按实际拆包的顺序,把这份笔记里最值得复现的部分一条条讲清楚。
2. 数据获取与清洗:从接口到可用 DataFrame 的完整链路
2.1 为什么数据源选择决定了后面一半的坑
做股票量化,第一件事不是写策略,是拿数据。很多人一上来就找“免费 API”,结果拿到的是不复权价格,回测时除权除息那天直接给你一根大阴线,策略信号全乱。这份笔记里用的是常见的 A 股日线数据获取方式,核心思路是:先拿原始行情,再自己做复权处理,而不是依赖接口返回的“已复权”字段——因为不同接口对复权的定义不一样,有的前复权基准日会变,你回测跑出来的结果不可复现。
常见做法是走akshare或者tushare这类库拿日线,然后本地存成 CSV 或 Parquet。笔记里更偏向用akshare,原因是它不需要 token,适合快速验证。但要注意,免费接口有频率限制,批量拉全市场股票时容易断,所以笔记里给了一个带重试和本地缓存的拉取脚本。
2.2 拉取日线数据的可复现脚本
import akshare as ak import pandas as pd import time import os def fetch_daily(symbol: str, start: str, end: str, cache_dir: str = "./data"): """ symbol: 6位股票代码,如 '000001' start/end: 'YYYYMMDD' """ os.makedirs(cache_dir, exist_ok=True) cache_file = os.path.join(cache_dir, f"{symbol}_{start}_{end}.csv") if os.path.exists(cache_file): return pd.read_csv(cache_file, parse_dates=["日期"]) for attempt in range(3): try: df = ak.stock_zh_a_hist( symbol=symbol, period="daily", start_date=start, end_date=end, adjust="" # 先拿不复权,复权自己算 ) if df.empty: raise ValueError("empty data") df.to_csv(cache_file, index=False) return df except Exception as e: print(f"retry {attempt+1} for {symbol}: {e}") time.sleep(2) raise RuntimeError(f"failed to fetch {symbol}")逻辑说明:adjust=""表示拿不复权数据,这是刻意为之。很多接口的qfq(前复权)会随着最新价格变动而重新计算历史价格,导致你昨天跑的回测和今天跑的不一样。自己算复权虽然多一步,但结果稳定。参数上,period="daily"是日线,做日内策略要换minute,但免费接口的分钟线通常只有最近几个月,不适合长周期回测。缓存文件按symbol_start_end命名,避免重复请求。
2.3 复权处理:前复权、后复权到底用哪个
拿到不复权数据后,下一步是复权。笔记里给了一个基于除权除息因子的复权方法,但更实用的做法是:如果你只是做策略回测,直接用后复权价格,因为后复权不会因为新的除权事件而改变历史值。前复权适合看盘,不适合回测。
def adjust_price(df: pd.DataFrame, factors: pd.Series) -> pd.DataFrame: """ df: 包含 '日期','收盘' 等列 factors: 索引为日期,值为复权因子 """ df = df.set_index("日期").sort_index() df["adj_close"] = df["收盘"] * factors.reindex(df.index).ffill() return df.reset_index()复权因子可以从数据源单独获取,或者用除权除息日手工推算。这里的关键是ffill()——因子在除权日才更新,其他日期沿用上一个值。如果你用bfill或者不填充,会出现大量 NaN,回测直接崩。
注意:复权因子本身也有坑。有些数据源的因子是“累计因子”,有些是“单次因子”,用之前一定先拿一只已知除权的股票验证一下,比如某只股票10转10,复权后价格应该减半。
3. 策略回测:均线策略的代码实现与未来函数排查
3.1 为什么你的均线策略回测年化 300%
均线策略是最经典的入门策略,但也是最容易写出未来函数的。常见错误是:用当天的收盘价计算均线,然后用当天的均线值判断是否买入,再以当天的收盘价成交。这等于你用了收盘后才知道的信息,在收盘前就做了交易决策。实盘中不可能做到。
正确的做法是:用 T-1 日及之前的数据计算信号,T 日开盘或收盘成交。笔记里的回测框架把信号和成交错开一天,这是最基本的防未来函数手段。
3.2 双均线策略的完整回测代码
import pandas as pd import numpy as np def dual_ma_backtest(df: pd.DataFrame, short=5, long=20, fee=0.0003): """ df: 包含 '日期','开盘','收盘','adj_close' short/long: 短长均线周期 fee: 单边手续费 """ df = df.sort_values("日期").reset_index(drop=True) df["ma_short"] = df["adj_close"].rolling(short).mean() df["ma_long"] = df["adj_close"].rolling(long).mean() # 信号:短均线上穿长均线为 1,下穿为 0 df["signal"] = np.where(df["ma_short"] > df["ma_long"], 1, 0) # 关键:信号滞后一天,避免未来函数 df["position"] = df["signal"].shift(1).fillna(0) df["ret"] = df["adj_close"].pct_change().fillna(0) df["strategy_ret"] = df["position"] * df["ret"] # 扣手续费:仓位变化时收一次 df["trade"] = df["position"].diff().abs().fillna(0) df["strategy_ret"] -= df["trade"] * fee df["cum_ret"] = (1 + df["strategy_ret"]).cumprod() return df逻辑说明:rolling(short).mean()计算均线,shift(1)是防未来函数的核心。position为 1 表示持有,0 表示空仓。手续费按仓位变化收取,diff().abs()捕捉每次开平仓。参数上,short=5, long=20是常见组合,但不同标的适配不同周期,笔记里建议用 10/30 或 20/60 做对比。fee=0.0003是万三,实际还要加印花税和滑点,回测时至少把单边成本设到 0.001 以上才接近真实。
3.3 回测结果怎么验证不是运气
跑完回测不能只看年化收益。笔记里强调三个验证步骤:第一,看最大回撤和收益是否匹配,年化 30% 但回撤 60% 的策略实盘拿不住;第二,分年度看收益,如果只有某一年特别高,其他年份亏,说明策略过拟合;第三,和基准对比,沪深 300 或者中证 500,如果跑不赢基准,不如直接买 ETF。
def yearly_performance(df: pd.DataFrame): df["year"] = pd.to_datetime(df["日期"]).dt.year return df.groupby("year")["strategy_ret"].apply( lambda x: (1 + x).prod() - 1 )这个函数按年输出策略收益,一眼就能看出策略是不是只在某一年有效。如果某年收益 80%,其他年份 -10%,那基本是过拟合。
4. 股票组合分析:从单标的到多标的的仓位管理
4.1 等权、市值加权、风险平价怎么选
单标的策略跑通后,下一步是组合。笔记里给了三种常见加权方式:等权、市值加权、风险平价。等权最简单,每只股票买一样的金额,定期再平衡;市值加权按流通市值分配,大票占比较高;风险平价按波动率倒数分配,波动大的买得少。
对于新手,等权是最稳妥的起点。市值加权在 A 股容易偏向银行白酒,风险平价计算量大但分散效果最好。笔记里用scipy.optimize做了风险平价的简化实现,核心是让每只股票对组合的风险贡献相等。
4.2 组合回测的代码框架
def portfolio_backtest(price_dict: dict, weights: dict, rebalance="M"): """ price_dict: {symbol: df},df 含 '日期','adj_close' weights: {symbol: weight} rebalance: 'M' 月度再平衡 """ # 对齐日期 close_df = pd.DataFrame({ sym: df.set_index("日期")["adj_close"] for sym, df in price_dict.items() }).sort_index().ffill() ret_df = close_df.pct_change().fillna(0) port_ret = sum(ret_df[sym] * w for sym, w in weights.items()) # 再平衡:每月第一个交易日恢复目标权重 if rebalance == "M": mask = close_df.index.to_series().dt.to_period("M").diff() != 0 # 简化处理:再平衡日按目标权重重新计算 # 实际实现需要跟踪持仓市值,这里只做示意 return port_ret逻辑说明:close_df把所有标的的复权收盘价对齐到同一日期索引,ffill()处理停牌。port_ret是加权收益。再平衡部分笔记里做了简化,实际组合回测需要跟踪每只股票的持仓数量和现金,再平衡日按目标权重调整。参数上,rebalance="M"是月度,也可以设"Q"季度,频率越高交易成本越大。
注意:多标的回测最容易忽略停牌和涨跌停。如果某只股票停牌,
ffill会让它的价格不变,但实际你无法交易。涨跌停时也无法成交。笔记里建议在回测中加一个tradeable标记,停牌或涨跌停时跳过调仓。
5. 避坑与常见问题:那些回测跑通但实盘亏钱的细节
5.1 现象:回测收益很高,实盘一上就亏
原因:最常见的是未来函数和幸存者偏差。未来函数前面说了,幸存者偏差是指你回测时用的股票池是“现在还在上市”的股票,那些退市的、ST 的没包含进去,导致收益虚高。解决:用历史成分股列表,或者至少把 ST 和退市股票纳入回测。
5.2 现象:复权后价格出现负值
原因:前复权在除权除息幅度很大时,早期价格可能被算成负数。解决:改用后复权,或者用复权因子而不是价格直接复权。后复权价格只会越来越大,不会为负。
5.3 现象:回测时成交价用收盘价,但实盘收盘价买不到
原因:A 股收盘价是集合竞价产生的,你看到的收盘价是最后一笔成交,但你的单子不一定能成交。解决:回测用次日开盘价成交,或者用 VWAP(成交量加权平均价)。笔记里建议至少用次日开盘价,这样更接近实盘。
5.4 现象:手续费设了万三,实盘成本远不止
原因:万三只是券商佣金,还有印花税(卖出千一)、过户费、滑点。滑点在大单时尤其明显。解决:回测时单边成本至少设 0.0015,小盘股设到 0.003。滑点可以用固定值,也可以按成交量的百分比模拟。
5.5 现象:策略在沪深300上有效,换到中证500就失效
原因:不同指数的波动率、成分股结构不同,均线参数需要重新调。解决:不要一套参数打天下,按标的的波动率调整均线周期。笔记里给了一个经验公式:周期和波动率成反比,波动大的标的用更短的均线。
6. 进阶技巧:用向量化回测把速度提上去
当你把单标的策略验证得差不多了,下一步肯定是想批量测试参数。用for循环跑回测,一只股票跑几十组参数,几分钟就过去了。如果全市场几千只股票,根本跑不完。这时候需要向量化回测。
向量化的核心思路是:把信号计算和收益计算全部用 pandas 的矩阵运算完成,避免逐行循环。比如双均线策略,你可以把所有股票的收盘价拼成一个 DataFrame,然后一次性算均线、信号、收益。
def vectorized_ma(close_df: pd.DataFrame, short=5, long=20): """ close_df: index=日期, columns=股票代码, values=复权收盘价 返回每只股票的累计收益 """ ma_short = close_df.rolling(short).mean() ma_long = close_df.rolling(long).mean() signal = (ma_short > ma_long).astype(int) position = signal.shift(1).fillna(0) ret = close_df.pct_change().fillna(0) strat_ret = position * ret cum_ret = (1 + strat_ret).cumprod() return cum_ret这个函数一次算完所有股票,速度比循环快几十倍。参数上,close_df的列是股票代码,行是日期,要求所有股票日期对齐。如果某只股票上市晚,前面会有 NaN,rolling会自动处理,但pct_change会产生 NaN,需要fillna(0)。
验证向量化结果是否正确,最简单的办法是拿一只股票单独跑循环版,对比累计收益曲线。如果两条曲线完全重合,说明向量化没问题。我一般会随机抽三只股票做这个对比,确认后再批量跑。
从那以后我每次写完回测,都强制走一遍“单标的循环 vs 向量化”的对比,确认没有因为对齐或填充引入偏差。希望帮到你。
本文还有配套的精品资源,点击获取