从数据到策略:用Tushare Pro搭建个人量化交易系统实战复盘
1. 从看K线到跑程序我的第一套量化系统是怎么来的先说个背景。我本身不是金融科班出身做后端开发五六年股票投资的经验也就两三年属于那种白天写业务代码晚上盯盘看K线的业余选手。2022年上半年行情还不错的时候我还能靠手动交易跟上一点节奏但到了下半年震荡市里人性弱点是真顶不住——追涨杀跌、拿不住盈利单、止损不坚决这些问题几乎每一条都在我身上重复上演。痛定思痛之后我做了一个决定与其跟自己的情绪作斗争不如把交易逻辑写成代码让机器去执行。这个想法本身不新鲜但真正动手之后才发现摆在自己面前的第一道难关根本不是策略设计而是——数据从哪来。我一开始尝试过从财经网站爬数据写了个爬虫去抓日K线。结果呢反爬机制、字段缺失、复权价格对不上、历史数据不全光是清洗数据就快把我劝退了。后来也试过一些免费数据接口有的需要实时行情权限有的历史数据只能回溯两三年根本满足不了回测需求。折腾了一圈之后我锁定了Tushare Pro。说实话刚开始我对这个平台是有顾虑的——它需要注册获取token部分接口还有积分门槛。但真正用下来之后我最大的感受是API设计干净、文档齐全、数据质量靠谱对于个人开发者来说已经属于良心配置了。整个系统从零到一我花了大概一个月业余时间。期间经历了数据入仓、策略编写、回测框架搭建、参数过拟合、模拟盘验证这几个阶段中间踩的坑足够写满一页备忘录。这篇文章不打算讲什么高大上的机器学习模型也不涉及高频交易的复杂架构就老老实实复盘一下一个普通程序员是如何用Tushare数据搞定人生第一套量化交易系统的。如果你也处于想入门量化但不知道从哪下手的阶段或者已经在写策略但回测结果总觉得不可信这篇文章应该能帮你绕开我走过的那些弯路。2. 数据源选型Tushare Pro相比免费接口和爬虫的硬核优势2.1 我对比过的几种数据获取方案在敲定Tushare之前我前后试过三条路每条路都有让人抓狂的地方。第一是爬虫方案。用requests去拉某个财经网站的K线数据看起来简单实际维护成本极高。网站改版、字段变化、IP封锁、AJAX动态加载每一步都在消耗你的耐心。更要命的是财务数据和复权因子这种关键字段网页上展示的往往已经处理过你拿到的原始数据很可能跟真实交易口径有偏差策略回测的准确性直接打折。第二是第三方免费API。国内有一些平台提供免费的行情接口但限制非常明显——历史数据往往只有最近几年的日线分钟线数据基本没有部分接口还要求定时轮询拿增量。做中长线策略可能勉强够用一旦想验证行情剧烈波动时段的策略表现数据缺失会导致回测结果完全失真。第三就是Tushare Pro。它提供的数据覆盖面广从基础的日线行情、分钟线行情到复权因子、财务指标、资金流向、龙虎榜、指数成分等基本覆盖了个人量化开发者能想到的绝大多数场景。关键是它有统一的HTTP接口规范Python SDK封装到位一次认证token搞定全局调用省去了跟各种数据源做适配的功夫。2.2 Tushare的核心接口能力盘点我实际用到的接口主要集中在以下几类这里列出来给后来者一个参考接口名称返回内容我的使用场景daily日线行情开高低收、成交量额核心策略数据源adj_factor前复权/后复权因子计算准确收益率daily_basic每日指标换手率、PE、PB、总市值股票筛选、市值过滤trade_cal交易日历回测中的交易日对齐index_daily指数日线行情大盘环境判断、基准对比moneyflow个股资金流向资金面辅助因子这几个接口日常开发完全够用。值得一说的是Tushare将很多接口做了积分分级比如高频接口、财务数据需要更高积分才能调用。但基础行情接口只要注册后就能用对于刚入门的朋友来说先把日线级别的策略跑通远比追求高频数据更有意义。我当时做的一件事是先把A股全部股票的日线数据批量拉取到本地。全市场五千多只股票逐日拉取大概需要一段时间但好在Tushare是按日批量返回的每只股票一次请求就能拿到全历史日K配合多线程能大幅压缩耗时。拉完之后存到本地后面写策略和跑回测就再也不用来回请求接口了。2.3 为什么我不推荐先用模拟数据写代码网上很多教程习惯用随机生成的数据去演示策略框架代码看起来跑得通但真到了接入真实数据那天你会发现到处都是坑。用真实数据的意义在于你的代码从第一天就在处理脏数据、停牌数据、涨跌停边界这些真实场景。比如某些股票会停牌几个月复牌当天价格跳空如果代码没处理停牌期间的占位收益率计算就会错位。又比如有些股票在某个时间段没有成交记录接口返回空值怎么处理这些NaN直接决定的策略的鲁棒性。所以我的建议是不要偷懒一开始就用Tushare的真实数据。虽然前期多花了两天时间做数据清洗但这部分工作沉淀下来就是你自己的一套数据基建后面做什么策略都受益。3. 数据层建设从申请Token到全市场日线入库的完整链路3.1 注册与Token获取的小细节Tushare Pro的使用门槛很低官网注册账号之后在个人主页-接口TOKEN页面就能看到你自己的token字符串。这个token相当于你的API密钥调用任何接口都要带上它。需要注意几个细节token不要硬编码在代码里。我当时图省事直接写在脚本里后来代码传到Git仓库差点泄露。正确做法是配置到环境变量或者本地配置文件中并在.gitignore里排除。积分机制决定了你能调哪些接口。注册初始有120积分能调用的接口有限。通过完善个人信息、每日签到、社区贡献等方式可以攒积分。我建议至少攒到2000分这样能解锁更多历史数据接口比如分钟线和财务数据。接口有频率限制。基础接口每分钟调用次数和每天总调用次数都有限制批量拉数的时候要控制节奏配合重试机制防止请求被拒。3.2 Python端初始化两行代码搞定SDK接入Tushare官方提供了Python SDK安装很简单pip install tushare接入的代码更加简洁import tushare as ts # 初始化设置token ts.set_token(你的TOKEN) # 获取数据连接 pro ts.pro_api() # 拉取贵州茅台全部历史日线 df pro.daily(ts_code600519.SH, start_date20100101, end_date20241231) print(df.head())返回的是Pandas DataFrame列名包含trade_date、open、high、low、close、pre_close、change、pct_chg、vol、amount这些标准字段。拿到数据之后排序、去重、缺失值处理这些脏活就得自己做了——别指望数据源给你全包。3.3 因子对齐复权处理是新手最容易忽略的坑这是我认为整个数据建设中最关键的环节没有之一。直接用原始价格算收益率在分红送股的时候会出现假亏损。举个例子某股票股价100元每10股送10股除权后股价变成50元。如果不做复权处理你看到的是单日暴跌50%但实际持仓市值并没有变化。如果策略在这个位置触发了止损逻辑结果就是白白卖飞。Tushare提供了adj_factor接口返回每日的复权因子。正确的做法是# 获取股票列表 stock_list pro.stock_basic(exchange, list_statusL) # 对每只股票拉取复权因子并与行情合并 adj pro.adj_factor(ts_code600519.SH) daily pro.daily(ts_code600519.SH) # 合并后计算后复权价格 merged daily.merge(adj, on[ts_code, trade_date]) merged[close_adj] merged[close] * merged[adj_factor]计算收益率的时候用复权价才能真实反映持有这只股票的实际收益。我自己在这个问题上吃过亏——第一版策略回测年化收益28%结果一检查发现一半的收益来自除权日的跳空当时那个悔啊。3.4 本地存储方案为什么我最后选了Parquet数据拉到本地之后存哪也是门学问。我一开始用CSV文件一只股票一个文件结果五千多个文件散落在磁盘里读取速度还慢。后来试过SQLite读写是快了但做因子计算的时候要从库里搬数据到内存性能瓶颈明显。最终我的方案是用Parquet格式按股票分文件存储配合一个简单的元数据索引。Parquet是列式存储读取速度快占用空间小还能保留DataFrame的完整数据类型。在策略回测中我只需要按ts_code读取单只股票的数据性能完全够用。存储目录结构大概长这样data/ daily/ 600519.SH.parquet 000001.SZ.parquet ... adj_factor/ 600519.SH.parquet ...读取的时候一行代码搞定df pd.read_parquet(data/daily/600519.SH.parquet)这套方案还有一个额外好处后续如果我要升级到分钟线数据或者加入财务因子只需要按照同样的方式新增目录不需要改动策略代码的数据访问层。4. 策略开发我的第一个动量策略是怎么一步步设计出来的4.1 策略选型的底层逻辑先求稳再求快很多新手一上来就想着搞复杂的多因子模型或者神经网络预测我的建议是先把一个简单的、逻辑清晰的策略吃透。我选的第一个策略是双均线动量策略——本质上是一个趋势跟踪策略。当短期均线上穿长期均线时买入金叉下穿时卖出死叉。这个策略的优点是逻辑简单、参数少、容易调试而且趋势跟踪策略在A股这种波动大的市场里长期来看是有正期望的。选择这个策略还有一个更实际的原因它的交易频率低对数据精度和执行延迟要求都不高。用日线数据就足够不需要处理tick级数据这让我可以把精力集中在数据管道和回测框架上而不是一开始就陷入微观结构优化的泥潭。4.2 策略代码实现从信号生成到持仓计算的完整流程策略核心代码大概是这样import pandas as pd import numpy as np def generate_signals(df, short_window20, long_window60): 双均线策略信号生成 df df.copy() df[ma_short] df[close_adj].rolling(short_window).mean() df[ma_long] df[close_adj].rolling(long_window).mean() # 金叉买入死叉卖出 df[signal] 0 df.loc[df[ma_short] df[ma_long], signal] 1 df.loc[df[ma_short] df[ma_long], signal] -1 # 计算持仓状态信号变化时才交易 df[position] df[signal].diff().fillna(0) return df这里有一个细节值得注意signal字段表示的是持仓方向1持币/空仓或持有-1空仓而position字段表示的是当天的交易动作。两者区分开后续计算换手率、手续费的时候才不会乱。4.3 为什么回测要用次日开盘价而不是当日收盘价这是我踩过的一个大坑也是新手最容易犯错的地方。很多人写回测的时候当天出信号当天按收盘价成交。但在实盘中信号通常是收盘后才能计算出来的——你得等当天数据全部落地才能算出均线是否金叉然后第二天开盘才能下单。如果用收盘价成交就相当于偷看了未来数据这在量化领域叫未来函数是回测结果虚高的头号元凶。正确的做法是# 信号T日产生T1日开盘价成交 df[trade_price] df[open].shift(-1) # 次日开盘价 df[trade_signal] df[position].shift(1) # 次日才执行就这么一行shift我的策略年化收益从虚高的35%降到了真实的20%左右。差距不大但真实多了。4.4 交易成本模型佣金、印花税、滑点一个都不能少回测结果可信不可信很大程度上取决于交易成本模型够不够真实。A股的成本构成比较明确佣金双边收取默认万2.5左右最低5元印花税卖出时单边收取目前是成交金额的0.05%注2023年8月后从0.1%下调过户费沪深两市都有费率极低滑点实际成交价与信号价的偏差保守估计单边0.1%我在回测代码里把这部分统一封装成了成本函数def calculate_trade_cost(trade_price, trade_amount, is_sellFalse): 计算交易成本 commission max(trade_amount * 0.00025, 5) # 佣金万2.5最低5元 stamp_tax trade_amount * 0.0005 if is_sell else 0 # 印花税卖出收 slippage trade_amount * 0.001 # 滑点0.1% return commission stamp_tax slippage把这部分成本加进去之后策略的净收益又掉了几个百分点。但这就是实盘的真实损耗早点认清总比实盘之后被毒打好。5. 回测引擎不只是一个循环而是一套严谨的验证体系5.1 向量化回测和事件驱动回测我为什么选后者市面上常见的回测框架有两类向量化回测和事件驱动回测。向量化用Pandas的shift、rolling直接计算全序列速度快代码简洁事件驱动则是一个bar一个bar地模拟交易过程每个时间点都模拟完整的数据到达→信号产生→订单执行流程。对于双均线这种简单策略向量化完全够用。但为了以后扩展更复杂的策略比如多股票组合、条件单、止损止盈我直接用事件驱动的思路自己写了一个轻量级回测引擎。核心结构包括三个部分数据加载器按时间顺序逐日推送行情数据策略模块接收数据计算指标输出交易信号组合管理器维护持仓、现金、交易记录计算每日净值这个架构的好处是策略和回测框架解耦以后换策略只需要实现新的策略模块回测引擎完全不用动。5.2 回测引擎核心代码骨架不想引入backtrader或者zipline这些重量级框架自己写一个其实并不难。核心是下面这个事件循环class BacktestEngine: def __init__(self, data, strategy, initial_capital1000000): self.data data self.strategy strategy self.capital initial_capital self.position 0 # 当前持仓 self.trades [] # 交易记录 self.equity_curve [] # 净值曲线 def run(self): for i in range(len(self.data)): current_bar self.data.iloc[i] # 1. 策略生成信号基于截至当前的数据 signal self.strategy.on_bar(current_bar, self.position) # 2. 如果有信号则执行交易 if signal ! 0 and self.position * signal 0: trade_price current_bar[open] * (1 0.001) # 模拟滑点 self._execute_trade(signal, trade_price) # 3. 记录每日市值 market_value self.position * current_bar[close_adj] self.equity_curve.append(self.capital market_value) return self._generate_report()这个引擎虽然简陋但五脏俱全。它支持任意时间频率的数据、任意交易标的、任意策略逻辑关键是——所有交易决策都严格基于当时可得的数据从根本上杜绝了未来函数。5.3 回测报告年化收益、最大回撤、夏普比率怎么看引擎跑完之后我生成了一份标准的回测报告包含几个核心指标。搞量化的人对这些指标都不陌生但真正理解它们背后的含义是需要实盘经历来沉淀的。年化收益率是最直观的指标但它有很多统计陷阱。比如回测周期太短年化收益可能被某一段极端行情拉高又比如使用了复利计算初始资金规模对收益影响很大。最大回撤是衡量策略风险的核心指标。它表示从净值峰值到谷底的最大跌幅。我的双均线策略在2015年股灾、2018年熊市、2022年震荡市这三个阶段都出现了明显的回撤最大回撤约18%。这个数字提醒我策略虽然有正期望但通道不好受的时候心理压力是真实存在的。夏普比率衡量的是每承受一单位风险能获得多少超额收益。一般认为大于1就算合格大于2就相当优秀。我的策略夏普比率大概在1.2左右属于能看但不出众的水平但考虑到我的目标是跑赢沪深300指数这个成绩已经算及格了。我习惯把这些指标跟基准指数放在一起对比。如果你跑了一个年化25%回撤5%的策略但同期沪深300年化涨了40%那你这个策略实际上跑输了大盘——你把钱扔在指数基金里躺赢就好了何必辛辛苦苦写代码。5.4 参数敏感性分析与过拟合规避双均线策略有两个关键参数短期均线窗口和长期均线窗口。我最初用(20, 60)这个经典组合回测表现不错。但一个核心问题始终萦绕在心头这个参数组合是真实的策略优势还是恰好适合这段回测区间为了验证这个问题我做了一个简单的参数敏感性测试把短期窗口从5到50、长期窗口从30到200分别跑一遍看收益率的分布情况。结果让我心里有底了不少虽然不同参数组合的表现有差异但大多数参数组合都能实现正收益说明趋势跟踪逻辑本身在A股是有效的并不完全依赖某一组特定参数。相反如果你发现某个参数组合表现特别好但相邻几组参数表现都很差那大概率是过拟合了——这个最优参数放到未来大概率失效。这也是为什么我坚持用事件驱动回测而不是向量化回测的另一个原因参数扫描需要大量运行事件驱动虽然慢一些但每次运行的结果都包含了完整的交易细节方便我逐笔检查确认策略行为是否符合直觉。6. 踩坑记录从回测到模拟盘之间那些看不见的差异6.1 涨跌停板回测里能成交实盘里排不上队我的策略在2022年4月的一次信号中买入标的恰好当天一字涨停。回测代码按照开盘价顺利成交利润计算得明明白白。但实际挂单的时候开盘就是巨量封单我的单子根本没有成交机会。这就是涨停板不可成交的问题。A股的涨跌停制度意味着当股票涨停时买单排队极长散户的资金基本上不可能买到跌停时反过来卖单也出不去。如果在回测中忽略这个约束碰到极端行情时你的策略纸上盈利会远远超过实际能实现的收益。我后面在回测引擎里加了一个限制条件如果当日收盘价等于涨停价则次日禁止买入如果收盘价等于跌停价则次日禁止卖出。虽然这会损失一部分交易机会但换来的是回测结果的真实可信。6.2 停牌数据跳空和空窗期的处理A股上市公司因为重大资产重组等各种原因停牌的情况非常常见。停牌期间没有行情数据如果不处理前复权价格的计算、均线的连续性都会出问题。我的处理方式是停牌期间用前收盘价填充保证时间序列连续。同时标记停牌状态策略在该时间段不允许产生交易信号。这个处理看似简单但对回测结果的连续性至关重要。6.3 模拟盘的定位不是二次回测而是流程验证回测跑通之后我进入了模拟盘阶段。我用的是某券商提供的模拟交易环境策略信号由本地程序每天收盘后计算第二天按信号手动下单到模拟账户。这个过程持续了大概一个月。坦率讲这一个月里模拟盘的收益跟回测结果并没有完全吻合偏差主要来自两个方面一是模拟盘的成交价格受实时盘口影响与回测用的开盘价存在偏差二是模拟盘的撮合规则跟真实交易所还是有区别的。但模拟盘真正的价值在于它让我验证了一整套操作流程是否顺畅。从程序自动拉取数据、计算信号、输出交易指令到人工核对信号、下单、记录成交结果整条链路走通之后我对系统实盘上线的信心才真正建立起来。6.4 实盘前的仓位管理永远不要All in当我准备从模拟盘切换到实盘的时候我给自己定了一条死规矩初始资金只用总资金的一小部分。我的雪球第一笔实盘资金只投入了计划资金的五分之一用最小仓位跑真实行情目的不是赚钱而是验证程序在真实交易环境下的稳定性。这个决策后来被证明极其正确。实盘第二天就遇到策略信号与券商交易软件的X报差异、手续费计算口径不一致等问题但因为仓位小这些问题的代价几乎可以忽略。随着流程逐步跑顺我才慢慢加大仓位。7. 系统持续迭代这套量化系统目前的架构和后续规划7.1 当前系统架构复盘经过几个月的迭代我的量化系统目前的架构大致如下数据层Tushare Pro定时拉取日线数据存入本地Parquet文件每日收盘后自动更新策略层Python编写策略逻辑当前包括双均线策略和改良版的海龟趋势策略回测层自研事件驱动回测引擎支持参数扫描、成本模型、涨跌停限制执行层每个交易日收盘后自动生成次日交易指令人工复核后执行监控层简单的净值曲线记录和异常报警邮件通知这套系统目前的运行方式是半自动状态——信号由程序生成但下单需要人工执行。完全自动化的实盘下单涉及券商API接入、风控机制等一系列复杂问题我还在逐步推进中。7.2 我对Tushare数据使用的几点体会用Tushare做数据支撑这几个月我的整体评价是**够用且好用**。对于个人量化开发者来说它的优势非常明显一是数据覆盖面广从行情到财务到资金流基本都能一套接口搞定二是文档和社区生态好遇到问题搜一搜基本都能找到解决方案三是持续更新新接口和数据维度不断增加满足策略迭代的需求。当然也有小遗憾。比如分钟线数据对积分要求较高某些财务指标的更新存在时滞部分接口的字段说明不够详细。但这些都不影响核心使用——对于日线级别的策略研究Tushare Pro完全能扛住。7.3 下一步方向多因子模型的储备与尝试目前的双均线策略只是我量化之路的开端。我的下一步计划是在现有系统的基础上逐步加入更多维度的因子比如Tushare提供的资金流向数据、北向资金数据、财务质量因子等构建一个多因子选股模型再结合择时模块做仓位管理。这条路的复杂程度比双均线策略高出一个数量级但数据基础已经打好了——通过Tushare我把财务数据、每日指标、资金流向数据都陆续接入了本地库后续做因子计算的时候不需要再折腾数据管道直接读库就行。走到这一步我最大的感触是量化交易的核心竞争力不在策略公式本身而在于数据管道的可靠性、回测框架的严谨性和资金管理的纪律性。策略可以复制但一套经得起推敲的系统需要自己一步步踩坑搭建起来。最后说一点个人经验如果你也想试水量化交易别一上来就追求复杂的模型和完善的架构。先用最简单的策略跑通数据获取→信号生成→回测验证→模拟执行这条最小链路建立正向反馈之后再逐步迭代升级。这个过程比任何教程都有用。