简介:这份资源是tushare 0.2.8版本的Python源码包,面向从事金融数据分析与量化交易策略开发的Python学习者,帮助读者通过阅读真实库源码理解金融数据接口的底层实现。压缩包共26个文件,以19个py源码文件为核心,辅以3个txt说明、2个pkg-info元数据、1个csv数据样例和1个cfg配置文件,整体约181KB,体积轻量便于本地研读。源码覆盖数据获取、清洗转换、本地存储、实时行情轮询、技术指标计算及网络与数据异常处理等模块,可帮助读者掌握HTTP请求封装、数据对象构建与接口设计思路。目前已有6234人学习下载,适合希望从调用API进阶到理解实现原理、提升Python工程能力并积累量化交易实战经验的开发者参考。
1. 从一份 tushare 量化交易 Python 源码说起:它到底能跑出什么
很多人第一次接触量化交易,是从一份“能直接跑”的 Python 源码开始的。这份 tushare 量化交易 Python 源码,核心价值不在于策略多赚钱,而在于它把数据获取、因子计算、信号生成、回测统计这条链路完整串了起来,让你能在一个下午内看到自己的第一个净值曲线。它适合两类人:一是刚转量化、想搞懂“一个策略从数据到下单信号到底经过哪些环节”的新手;二是手里有零散策略想法、缺一套可复用脚手架的老手。源码里通常包含数据拉取封装、均线/动量类因子、简单择时逻辑和回测输出,依赖 tushare 的免费接口拿 A 股日线数据。你不需要先成为金融工程专家,但得能看懂 pandas 的 DataFrame 操作,否则后面调参时会很痛苦。
2. 数据层拆解:tushare 接口怎么接、token 怎么管、字段怎么对齐
2.1 为什么选 tushare 而不是自己爬
做量化第一步永远是数据。自己爬行情站点的血泪经验是:字段对不齐、复权处理混乱、停牌日缺失,最后花在清洗上的时间远超写策略。tushare 的优势在于它把 A 股日线、复权因子、交易日历这些基础数据标准化了,返回的就是规整的 DataFrame,列名固定,省掉大量对齐工作。源码里一般会封装一个DataLoader类,把 token、起止日期、股票池作为参数,内部调用pro.daily()和pro.adj_factor()两个接口,再合并成后复权价格。这样做的原因是:回测必须用后复权价,否则除权除息那天会出现假跳空,信号直接失真。
常见做法是把 token 放在环境变量里,而不是硬编码在源码中。下面这段是数据层最典型的写法:
import os import tushare as ts import pandas as pd # token 从环境变量读取,避免提交到仓库 ts.set_token(os.environ["TUSHARE_TOKEN"]) pro = ts.pro_api() def load_daily(ts_code: str, start: str, end: str) -> pd.DataFrame: # 拉取未复权日线 df = pro.daily(ts_code=ts_code, start_date=start, end_date=end) # 拉取复权因子 adj = pro.adj_factor(ts_code=ts_code, start_date=start, end_date=end) # 按交易日合并 df = df.merge(adj[["trade_date", "adj_factor"]], on="trade_date", how="left") # 计算后复权收盘价 df["close_adj"] = df["close"] * df["adj_factor"] # 按日期升序,回测必须时间正序 df = df.sort_values("trade_date").reset_index(drop=True) return df[["trade_date", "open", "high", "low", "close_adj", "vol"]]逻辑说明:pro.daily()返回的是未复权价,adj_factor是复权因子,两者按trade_date合并后相乘得到后复权价。参数上,ts_code格式是“代码.交易所”,比如000001.SZ;start_date和end_date用YYYYMMDD字符串。注意merge必须用how="left",因为复权因子在某些日期可能缺失,用内连接会丢数据。拉完一只股票后,建议立刻检查df.isnull().sum(),如果close_adj有空值,说明复权因子没对齐,后面所有计算都会崩。
2.2 股票池和交易日历的处理
源码里通常有一个get_universe()函数,用来确定回测标的。简单版本是硬编码一个列表,进阶版本会按市值或成交额筛选。这里有个容易翻车的地方:如果你用当前市值选股票池,再拿去做历史回测,就引入了未来函数——你用了今天才知道的信息去选过去的股票。正确做法是按每个调仓日的截面数据动态选池,或者干脆用指数成分股。交易日历用pro.trade_cal()拉,过滤is_open=1的日期,回测循环必须在这个日期序列上跑,否则周末和节假日会产生空信号。参数上,exchange填SSE或SZSE,start_date和end_date覆盖回测区间即可。
3. 策略层实现:均线、动量因子与信号生成的代码骨架
3.1 因子计算:滚动窗口和对齐
策略层的核心是把价格序列转成因子。源码里最常见的是双均线:短均线上穿长均线买入,下穿卖出。计算均线本身一行代码,但坑在窗口对齐。rolling(window=20).mean()默认从第 20 行才有值,前面全是 NaN,如果你直接拿去做信号比较,NaN 参与比较会返回 False,导致前期没有信号,这本身没问题,但如果你用fillna(0)填了零,就会在开头产生假信号。我一般会在因子计算后显式dropna(),或者用min_periods参数控制。下面是一个动量因子的实现:
def momentum_factor(df: pd.DataFrame, window: int = 20) -> pd.DataFrame: # 计算 window 日收益率作为动量 df["mom"] = df["close_adj"].pct_change(window) # 短均线和长均线 df["ma_short"] = df["close_adj"].rolling(window=5, min_periods=5).mean() df["ma_long"] = df["close_adj"].rolling(window=window, min_periods=window).mean() # 信号:短均线在长均线上方且动量为正 df["signal"] = ((df["ma_short"] > df["ma_long"]) & (df["mom"] > 0)).astype(int) # 信号差分,1 为买入,-1 为卖出 df["trade"] = df["signal"].diff().fillna(0) return df逻辑说明:pct_change(window)算的是过去 window 天的累计收益率,作为动量代理。min_periods保证窗口不满时不计算,避免用不完整数据。signal是持仓状态,trade是交易动作。参数上,window控制长均线周期,常见取值 20、60;短均线一般取 5 或 10。注意diff()后第一行是 NaN,用fillna(0)处理,否则回测时第一笔交易会报错。这段代码跑完,你可以用df["trade"].value_counts()看买卖次数,如果一年只有一两次,说明参数太迟钝,需要调小窗口。
3.2 回测循环:手续费、滑点和仓位控制
信号有了,接下来是回测。很多源码在这一步偷懒,直接拿信号乘以收益率累加,忽略了手续费和滑点,结果净值虚高。一个合格的回测循环至少要扣双边手续费和单边滑点。下面是一个简化但可用的回测函数:
def backtest(df: pd.DataFrame, fee: float = 0.0003, slippage: float = 0.001) -> pd.DataFrame: # 日收益率 df["ret"] = df["close_adj"].pct_change().fillna(0) # 持仓:信号滞后一天,避免用当日收盘信号做当日交易 df["position"] = df["signal"].shift(1).fillna(0) # 策略收益 = 持仓 * 日收益 df["strategy_ret"] = df["position"] * df["ret"] # 交易成本:换仓时扣除 df["cost"] = df["trade"].abs() * (fee + slippage) df["strategy_ret_net"] = df["strategy_ret"] - df["cost"] # 净值曲线 df["nav"] = (1 + df["strategy_ret_net"]).cumprod() return df逻辑说明:shift(1)是关键,它保证你今天收盘看到的信号,明天才能执行,否则就是未来函数。fee是佣金,A 股常见万三;slippage是滑点,按成交价的千分之一估算。trade的绝对值表示换仓次数,每次换仓扣一次成本。参数上,如果你做的是高频调仓,slippage要调大,否则回测会过于乐观。跑完后看df["nav"].iloc[-1],如果净值小于 1,先别急着改策略,检查一下是不是成本扣多了或者信号滞后设错了。
3.3 绩效统计:夏普、回撤和胜率
回测输出不能只有净值曲线,源码里一般会带一个performance()函数,算年化收益、最大回撤、夏普比率和胜率。年化收益用nav[-1] ** (252 / len(df)) - 1,最大回撤用(nav / nav.cummax() - 1).min(),夏普用strategy_ret_net.mean() / strategy_ret_net.std() * sqrt(252)。胜率统计的是盈利交易次数除以总交易次数,需要从trade序列里提取每次买卖配对。这些指标里,最大回撤比年化收益更重要,因为它决定你能不能拿得住。常见坑是夏普算出来特别高,一看净值曲线只有几笔交易,这种统计上不可信,样本太少。
4. 避坑与排查:这份源码跑不起来时先查这五处
4.1 现象:报错“抱歉,您没有访问该接口的权限”
原因:tushare 的积分制,新注册账号积分不够,pro.daily()这类基础接口通常需要 120 积分,而adj_factor可能需要更高。解决:先查pro.query('api_limits')看当前积分和可用接口,或者用ts.pro_bar()替代,它封装了复权逻辑,对积分要求略低。如果实在不够,先用ts.get_k_data()这个老接口拿日线,虽然字段少但能跑通流程。
4.2 现象:回测净值曲线在除权日出现巨大跳空
原因:用了未复权价。pro.daily()返回的close是未复权的,除权除息当天价格会向下跳,但你的持仓数量没变,导致假亏损。解决:必须合并adj_factor计算后复权价,或者直接用ts.pro_bar(adj='hfq')。检查方法是找一只分红除权的股票,对比除权日前后close和close_adj的差异。
4.3 现象:信号在回测里表现很好,实盘完全不是那么回事
原因:未来函数。最常见的是用当日收盘价算信号,又用当日收盘价成交;或者用全样本的均线去判断历史某天的状态。解决:所有信号必须shift(1),所有因子计算只能用当前及之前的数据。检查方法是把回测循环里的iloc[i]改成iloc[:i+1]重新算一遍因子,如果结果差异很大,说明有未来函数。
4.4 现象:回测跑得特别慢,几千行数据要几分钟
原因:在循环里逐行调用 tushare 接口,或者用iterrows()遍历 DataFrame。解决:数据一次性拉取,用向量化操作替代循环。pct_change、rolling、shift都是向量化的,比for循环快几百倍。如果股票池很大,用pro.daily(trade_date=...)按日拉全市场,再在本地按股票分组,比逐只拉快得多。
4.5 现象:手续费扣了但净值没变化
原因:trade序列全是 0,说明信号没有发生切换。可能是均线窗口设得太大,短均线始终在长均线上方或下方;也可能是signal计算时用了&连接两个条件,但其中一个条件因为 NaN 全是 False。解决:打印df[["ma_short", "ma_long", "signal"]].tail(20)看均线是否交叉,如果两条线平行,调小window;如果signal全 0,检查mom列是否有 NaN。
5. 进阶技巧:把单标的回测改成多标的轮动,以及参数怎么扫
单标的回测跑通后,下一步通常是多标的轮动。思路是:每个调仓日,对所有候选股票算因子,选因子值最高的前 N 只买入,持有到下一个调仓日。这里的关键是调仓频率和持仓数量的平衡。调仓太频繁,手续费吃掉收益;持仓太集中,回撤控制不住。我一般会设rebalance_days=5或10,持仓top_n=5到10。
参数扫描是另一个绕不开的环节。手动改参数跑回测效率太低,常见做法是用itertools.product生成参数组合,循环调用回测函数,把结果存到列表再转 DataFrame。下面是一个参数扫描的骨架:
import itertools import pandas as pd results = [] # 短均线、长均线、持仓数三个参数做网格 for short, long, top_n in itertools.product([5, 10, 20], [20, 60, 120], [5, 10]): if short >= long: continue # 短均线必须小于长均线 # 这里调用你的多标的回测函数,返回绩效字典 perf = run_multi_backtest(short=short, long=long, top_n=top_n) perf.update({"short": short, "long": long, "top_n": top_n}) results.append(perf) df_res = pd.DataFrame(results) # 按夏普排序,看最优参数组合 df_res = df_res.sort_values("sharpe", ascending=False) print(df_res.head(10))逻辑说明:itertools.product生成笛卡尔积,if short >= long过滤掉不合理的组合。run_multi_backtest是你自己封装的多标的回测函数,返回年化、回撤、夏普等指标。参数上,短均线一般不超过 20,长均线不超过 120,再大就接近买入持有。跑完看排序结果,但别直接选夏普最高的那组——要检查它在不同市场阶段的表现是否稳定,如果只有某一段特别好,其他段都亏,那就是过拟合。
验证参数稳健性的一个习惯是:把回测区间切成三段,分别看每段的夏普。如果三段夏普都是正的且差距不大,说明参数比较稳;如果一段特别高、两段为负,直接放弃。从那以后我每次扫完参数,都会强制走一遍分段验证,不然实盘翻车只是时间问题。希望这份源码能帮你把量化交易的第一条链路跑通,后面的事,就是在这条链路上不断换因子、换风控、换执行逻辑了。
本文还有配套的精品资源,点击获取