news 2026/10/15 11:31:18

双均线量化策略入门:pandas与tushare实战回测指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
双均线量化策略入门:pandas与tushare实战回测指南

1. 双均线策略的核心逻辑与方案选型

1.1 为什么选择双均线作为入门量化策略

双均线策略是量化交易领域最经典的入门策略之一,核心逻辑非常朴素:用两条不同周期的移动平均线来判断趋势方向。短期均线代表近期价格走势,长期均线代表中长期趋势,当短期均线上穿长期均线时视为买入信号,下穿时视为卖出信号。

这个策略之所以适合入门,原因有三。第一,逻辑直观,不需要复杂的数学推导就能理解。第二,参数少,只有两个核心参数——短周期和长周期,调参空间可控。第三,它天然具备趋势跟踪的特性,在单边行情中表现不错,能帮助新手建立对量化交易的基本认知。

但我要提前说清楚:双均线策略不是印钞机。它在震荡行情中会频繁发出假信号,导致反复止损。理解这一点很重要,否则你回测出一个漂亮曲线就以为找到了圣杯,实盘会教你做人。

1.2 技术栈选型:pandas + tushare 的组合优势

选择 pandas 搭配 tushare 这套组合,是我经过多次实践后认为最适合个人量化入门者的方案。

pandas 负责数据处理和策略计算。它的 DataFrame 结构天然适合处理时间序列数据,rolling 方法可以一行代码算出移动平均线,向量化运算比循环快几个数量级。你不需要自己去写循环遍历每一行数据,这对新手来说大大降低了门槛。

tushare 负责数据获取。它提供了 A 股市场的日线、周线、分钟线等行情数据,接口简单,返回的就是 DataFrame 格式,和 pandas 无缝衔接。对于个人研究者来说,tushare 的免费额度基本够用,日常回测几个标的完全没问题。

注意:tushare 的部分接口需要注册后获取 token,并且不同积分等级能调用的接口不同。日线行情接口对积分要求较低,适合入门使用。

1.3 整体架构设计思路

整个策略的实现分为四个层次:

  • 数据层:通过 tushare 获取标的的日线行情数据,包括开盘价、收盘价、最高价、最低价、成交量等字段。
  • 计算层:用 pandas 计算短期和长期移动平均线,并生成买卖信号。
  • 回测层:模拟交易过程,计算每次交易的盈亏,统计策略的整体表现。
  • 评估层:计算年化收益、最大回撤、夏普比率等指标,判断策略是否值得进一步优化。

这个分层设计的好处是每一层职责清晰,后续想换数据源、改策略逻辑或者加评估指标,都只需要动对应的那一层,不会牵一发而动全身。

2. 数据获取与预处理的实操细节

2.1 tushare 环境配置与数据拉取

首先你需要安装必要的库:

pip install tushare pandas numpy matplotlib

然后初始化 tushare 接口:

import tushare as ts import pandas as pd import numpy as np # 设置你的 token ts.set_token('你的token') pro = ts.pro_api()

拉取日线数据的代码:

# 获取某只股票在某段时间的日线数据 df = pro.daily(ts_code='000001.SZ', start_date='20200101', end_date='20231231') # 数据默认按日期降序排列,需要转为升序 df = df.sort_values('trade_date').reset_index(drop=True) # 将日期列转为 datetime 类型 df['trade_date'] = pd.to_datetime(df['trade_date']) df.set_index('trade_date', inplace=True)

这里有几个容易踩的坑。第一,tushare 返回的数据默认是降序的,也就是最新的日期在最前面,如果你不排序直接用,均线计算会完全错误。第二,trade_date 字段是字符串格式,必须转成 datetime 才能正确做时间序列操作。第三,记得把日期设为索引,后续 pandas 的 rolling 操作和绘图都会方便很多。

2.2 数据清洗与缺失值处理

实际拉下来的数据往往不干净。常见问题包括停牌期间没有数据、某些字段存在空值、复权问题等。

停牌导致的数据缺失最容易被忽略。如果一只股票停牌了半个月,你的数据里就直接少了这半个月的记录,均线计算时会把停牌前后的价格直接连起来算,导致信号失真。处理办法是检查日期是否连续:

# 生成完整的交易日序列 all_dates = pd.date_range(start=df.index.min(), end=df.index.max(), freq='B') missing = all_dates.difference(df.index) print(f"缺失的交易日数量: {len(missing)}")

如果缺失较多,可以考虑用前值填充或者直接换一个标的。对于入门回测,建议选择流动性好、停牌少的大盘股,减少数据问题带来的干扰。

复权问题也值得注意。tushare 的 daily 接口返回的是未复权价格,如果标的在回测期间有分红送股,价格会出现跳空,均线会被扭曲。建议使用 pro_bar 接口并设置 adj='qfq' 获取前复权数据:

df = ts.pro_bar(ts_code='000001.SZ', adj='qfq', start_date='20200101', end_date='20231231')

2.3 数据字段的选择与衍生

原始数据有很多字段,但双均线策略核心只用到收盘价。不过我建议保留开盘价、最高价、最低价和成交量,原因有两个:一是后续如果想加入其他过滤条件(比如用成交量确认信号),这些字段直接可用;二是计算最大回撤时需要用到每日的实际价格波动。

我通常会额外计算一个日收益率字段,方便后续统计:

df['returns'] = df['close'].pct_change()

这个字段在计算夏普比率和累计收益曲线时非常有用。

3. 双均线策略的代码实现与参数计算

3.1 移动平均线的计算原理与 pandas 实现

移动平均线的本质是对过去 N 天的收盘价取算术平均。以 5 日均线为例,第 t 天的 5 日均线值等于第 t-4 天到第 t 天收盘价之和除以 5。随着时间推移,这个窗口不断向前滑动,所以叫"移动"平均线。

用 pandas 实现非常简单:

df['ma_short'] = df['close'].rolling(window=5).mean() df['ma_long'] = df['close'].rolling(window=20).mean()

rolling(window=N).mean() 这一行代码背后做的事情是:对每一个位置,取包括当前行在内的前 N 行数据,计算均值。前 N-1 行因为数据不够,结果会是 NaN,这是正常的。

这里有个细节:window 参数的选择直接影响策略表现。短周期越小,信号越灵敏但假信号越多;长周期越大,趋势判断越稳定但信号越滞后。常见的组合有 5/20、10/30、5/60 等,没有绝对最优,需要根据标的特性和回测结果来定。

3.2 买卖信号的生成逻辑

信号生成的核心是判断两条均线的相对位置变化:

# 当短期均线在长期均线之上时,signal 为 1,否则为 0 df['signal'] = np.where(df['ma_short'] > df['ma_long'], 1, 0) # 信号变化点即为买卖点 df['position'] = df['signal'].diff()

position 为 1 表示金叉(短期上穿长期),为 -1 表示死叉(短期下穿长期),为 0 表示无变化。

但这里有一个实战中必须处理的问题:均线计算初期的 NaN 值会导致信号异常。前 19 天(以 20 日均线为例)的 ma_long 是 NaN,比较结果会是 False,signal 为 0,这本身没问题。但如果你在 NaN 期间就产生了交易信号,那肯定是错的。所以建议在计算完均线后,先去掉 NaN 行再做信号判断:

df.dropna(subset=['ma_short', 'ma_long'], inplace=True)

3.3 参数选择的量化依据

很多人调参就是凭感觉试,今天用 5/20,明天用 10/30,没有一个系统的方法。我建议用网格搜索的方式,把短周期和长周期的组合都跑一遍,用夏普比率或者年化收益作为评价标准。

results = [] for short in [5, 10, 15, 20]: for long in [30, 40, 50, 60]: if short >= long: continue df['ma_s'] = df['close'].rolling(short).mean() df['ma_l'] = df['close'].rolling(long).mean() # ... 执行回测逻辑 # results.append({'short': short, 'long': long, 'sharpe': sharpe})

但要注意过拟合问题。如果你在同一个时间段上反复调参,找到的"最优参数"很可能只是拟合了那段历史的噪声。正确的做法是划分训练集和测试集,在训练集上选参数,在测试集上验证。

4. 回测框架的搭建与绩效评估

4.1 简易回测引擎的实现

回测的核心是模拟"如果按照信号交易,资金曲线会怎么走"。最简化的版本不考虑手续费和滑点:

initial_capital = 100000 cash = initial_capital shares = 0 portfolio_values = [] for i in range(len(df)): price = df['close'].iloc[i] if df['position'].iloc[i] == 1 and cash > 0: shares = cash / price cash = 0 elif df['position'].iloc[i] == -1 and shares > 0: cash = shares * price shares = 0 portfolio_values.append(cash + shares * price) df['portfolio'] = portfolio_values

这段代码的逻辑是:金叉时全仓买入,死叉时全部卖出。每次交易都是全仓进出,这是最简单的仓位管理方式。

但实际回测中必须考虑手续费。A 股买卖通常有万分之几的佣金和千分之一的印花税(卖出时收取)。加上这些成本后,高频交易策略的收益会明显下降:

commission = 0.0003 # 佣金 stamp_tax = 0.001 # 印花税(仅卖出)

4.2 绩效指标的计算与解读

回测跑完后,你需要几个关键指标来判断策略好坏:

指标计算方式含义
累计收益率(最终市值 - 初始资金) / 初始资金整体赚了多少
年化收益率(1 + 累计收益率)^(252/交易天数) - 1折算到每年的收益
最大回撤max(1 - 当前市值/历史最高市值)最坏情况下亏多少
夏普比率(年化收益 - 无风险利率) / 年化波动率单位风险获得的超额收益
胜率盈利交易次数 / 总交易次数交易的胜算有多大

最大回撤是我最看重的指标。一个策略哪怕年化收益很高,如果最大回撤超过 50%,实盘时你很可能在最低点割肉离场。一般来说,最大回撤控制在 20% 以内比较舒适。

夏普比率大于 1 算不错,大于 2 算优秀。但要注意,夏普比率高不一定代表策略好,如果交易次数太少,统计意义不足,夏普比率再高也不可信。

4.3 交易明细的记录与分析

光看总收益不够,还需要记录每一笔交易的细节:

trades = [] entry_price = 0 entry_date = None for i in range(len(df)): if df['position'].iloc[i] == 1: entry_price = df['close'].iloc[i] entry_date = df.index[i] elif df['position'].iloc[i] == -1 and entry_price > 0: exit_price = df['close'].iloc[i] profit = (exit_price - entry_price) / entry_price trades.append({ 'entry_date': entry_date, 'exit_date': df.index[i], 'entry_price': entry_price, 'exit_price': exit_price, 'profit': profit }) entry_price = 0 trades_df = pd.DataFrame(trades)

有了交易明细,你可以分析:平均持仓周期多长?盈利交易和亏损交易的分布如何?是不是某几笔大额盈利撑起了整体收益?这些分析能帮你判断策略的收益来源是否可持续。

5. 常见问题与实战避坑指南

5.1 回测结果与实盘差异的根源

很多人回测出一条漂亮的资金曲线,实盘却亏得一塌糊涂。差异主要来自几个方面。

未来函数问题是最隐蔽的杀手。如果你的信号计算用到了当天收盘价,但交易执行也是按当天收盘价,那实际上你是在用当天收盘后才知道的信息做当天的交易,这在实盘中不可能实现。正确的做法是:用当天收盘价计算信号,第二天开盘时执行交易。

滑点是另一个被忽视的因素。回测中你假设能按收盘价精确成交,但实盘中大额买卖会推动价格变动,尤其是流动性差的标的。建议在回测中人为加入 0.1% 到 0.2% 的滑点。

手续费累积也不可小觑。双均线策略在震荡市中会频繁交易,如果每次交易成本是 0.13%(佣金加印花税),一年交易 20 次就是 2.6% 的成本,直接吃掉大部分利润。

5.2 震荡行情中的假信号过滤

双均线策略最大的痛点就是震荡行情中反复被打脸。金叉买入后马上死叉,死叉卖出后马上金叉,来回几次本金就磨损了不少。

几种常见的过滤方法:

  • 加入趋势过滤:只有当长期均线本身向上时才考虑买入信号,长期均线向下时忽略金叉。
  • 加入成交量确认:金叉当天如果成交量明显放大,信号更可靠。
  • 加入价格过滤:要求金叉时价格已经站上长期均线一定幅度,避免均线纠缠时的假突破。
  • 加入时间过滤:两次交易之间设置最小间隔,避免一天内反复进出。

这些过滤条件没有绝对的好坏,需要根据标的特性回测验证。我的经验是,加入趋势过滤后,交易次数会明显减少,胜率提升,但单边行情中可能会错过一部分利润。

5.3 参数过拟合的识别与规避

过拟合是量化策略的头号敌人。识别过拟合有几个信号:

第一,参数敏感度太高。如果短周期从 5 改成 6,收益就从 30% 掉到 5%,说明这个参数是在拟合噪声,不是真实规律。

第二,样本内外表现差异大。在 2020 年到 2022 年数据上表现优异,但 2023 年数据上完全失效,说明策略没有泛化能力。

第三,交易次数太少。如果三年只交易了 5 次,那统计上根本不足以判断策略有效性,高收益可能只是运气。

规避过拟合的方法:用滚动窗口回测,每次用前 N 年数据选参数,后 M 个月验证;或者用多只标的做交叉验证,看策略是否在不同标的上都有效。

5.4 常见报错与排查速查表

报错信息可能原因解决方法
KeyError: 'trade_date'字段名拼写错误或接口返回字段不同打印 df.columns 确认实际字段名
ValueError: window must be an integerrolling 参数传了浮点数确保 window 参数是整数
均线全为 NaN数据量不足或日期未排序检查数据行数是否大于窗口大小
信号全部为 0均线计算前未去除 NaN 或数据未排序先 dropna 再计算信号
回测收益异常高存在未来函数或未考虑交易成本检查信号与执行的时间对齐

提示:每次修改代码后,先打印前 20 行数据肉眼检查,比直接跑回测更容易发现低级错误。

6. 策略优化的几个实用方向

6.1 从固定参数到自适应参数

固定参数的双均线在不同市场环境下表现差异很大。一个改进方向是让参数自适应:用波动率或者趋势强度来动态调整均线周期。比如波动率大的时候用更长的周期过滤噪声,波动率小的时候用更短的周期提高灵敏度。

实现上可以用 ATR(平均真实波幅)来衡量波动率,然后根据 ATR 的分位数来切换参数组。这个思路不复杂,但能明显改善策略在不同市场状态下的适应性。

6.2 加入止损止盈逻辑

纯双均线策略依赖死叉来平仓,但死叉出现时往往已经回吐了不少利润。加入固定比例的止损止盈可以改善这一点:

stop_loss = 0.05 # 亏损 5% 止损 take_profit = 0.15 # 盈利 15% 止盈

在持仓期间,每天检查是否触发止损止盈条件,触发就立即平仓,不等死叉。这样能锁定利润、控制单笔亏损,但也会在某些情况下过早离场,错过后续大涨。需要回测对比两种方式的表现。

6.3 多标的分散与仓位管理

单标的双均线策略的收益波动很大,一个改进方向是同时跑多只标的,每只分配一部分资金。这样单只标的的假信号对整体组合的影响就被稀释了。

仓位管理上,可以等权分配,也可以根据波动率倒数分配(波动率低的标的配更多资金)。后者能让组合的整体波动更平稳。

6.4 从日线到分钟线的注意事项

有人想把双均线用到分钟线上做日内交易。技术上可行,但有几个坑要注意。分钟线的噪声远大于日线,假信号更频繁;交易成本占比更高,因为交易次数大幅增加;tushare 的分钟线接口对积分要求更高,数据获取可能受限。如果没有充分的准备,不建议新手直接上分钟线。

7. 我个人在实际操作中的几点体会

跑过几十个双均线回测之后,我最大的感受是:策略本身很简单,难的是对结果的理解和取舍。

第一,不要追求回测曲线最漂亮的参数。那条曲线大概率是过拟合的产物。我更倾向于选择参数平原——也就是周围几个参数组合表现都不错的区域,而不是孤立的最高点。

第二,交易成本一定要算进去。我早期回测时忽略了手续费,看到一个策略年化 25% 很兴奋,加上成本后直接掉到 12%,心态完全不一样。

第三,双均线策略适合趋势明显的标的。如果你选的标的长期横盘震荡,再好的参数也救不了。选标的比调参数重要得多。

第四,回测只是起点,不是终点。真正能让你进步的是把策略跑在模拟盘上,观察信号触发时的市场环境,理解为什么这次金叉有效、那次金叉无效。这种盘感是回测给不了的。

最后分享一个小技巧:在回测代码里加一个信号标记图,把买卖点画在 K 线图上。肉眼看一下信号位置是否合理,比看一堆数字指标更直观。很多时候你一眼就能看出某个信号明显是噪声,然后针对性去优化过滤条件。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/15 11:30:35

foobar2000 界面美化实战:3 分钟换装 foobox-cn 上手指南

foobar2000 界面美化实战:3 分钟换装 foobox-cn 上手指南 【免费下载链接】foobox-cn DUI 配置 for foobar2000 项目地址: https://gitcode.com/GitHub_Trending/fo/foobox-cn 打开 foobar2000 时,灰扑扑的默认界面是不是让你差点关掉软件&#x…

作者头像 李华
网站建设 2026/10/15 11:30:13

SpringMVC国际化实战:基于浏览器请求的LocaleResolver与MessageSource配置详解

1. 为什么国际化这件事值得单独拎出来讲做后端开发的人多少都碰过这样的需求:同一个页面,国内用户打开看到中文,海外用户打开看到英文,甚至有些面向多语言地区的产品还要支持日语、韩语、西班牙语。产品经理一句“加个多语言吧”&…

作者头像 李华
网站建设 2026/10/15 11:29:32

算法工程师笔试题高效准备:阿里真题拆解与刷题计划

简介:阿里巴巴最新算法工程师笔试题以PDF形式收录了11道真题,面向准备一线大厂算法岗位面试的求职者与在校学生。题目覆盖面较广:从C继承多态、虚函数调用顺序、构造函数初始化列表等代码题,到猜拳奖金分配、淘宝用户购买行为、新…

作者头像 李华
网站建设 2026/10/15 11:28:24

SAP Fiori Launchpad超时机制配置与优化实践

1. 项目概述在SAP Fiori的企业级应用场景中,Launchpad作为统一入口门户,其会话超时机制直接关系到用户体验与系统安全。最近在给某制造业客户实施Fiori系统时,我们遇到了一个典型问题:用户频繁反映"正在填表单时突然被登出&q…

作者头像 李华
网站建设 2026/10/15 11:28:15

Unity卡通渲染技术:NPR着色器实现与优化

1. 项目概述:NPR渲染的艺术与技术融合在游戏开发领域,非真实感渲染(Non-Photorealistic Rendering,简称NPR)一直是塑造独特视觉风格的重要技术手段。与追求物理精确的PBR渲染不同,NPR通过刻意打破现实规律&…

作者头像 李华
网站建设 2026/10/15 11:28:00

iOS文件传输高效方案:绕过iTunes的5种实战方法

1. 为什么需要绕过iTunes传输文件 iTunes作为苹果官方的数据管理工具,虽然功能全面但存在几个明显痛点:首先它对非Mac电脑用户不够友好,Windows平台运行效率低下;其次复杂的同步逻辑经常导致误删文件;最重要的是传输过…

作者头像 李华