Stock_Analysis_For_Quant / 均线实现
同一条均线策略,四种工具各写一遍:门槛、骨架与对不齐的地方
「把一条均线策略实现出来」这件事,在这个仓库里能做四遍:Python 侧有 Moving_Averages.ipynb 与 SMA.ipynb,R 侧有 R_Moving_Average.R,Excel 侧有 Stock_Plot_MA.xlsx 与 7 个启用宏的工作簿,Matlab 侧有 DailyReturn.m 与三个 Live Script。
但要注意一个前提:这四条路的成本完全不对等。Matlab 目录全目录只有 6 个文件,按其余三条路的预期去用它一定会失望。本页给出每一条路的最小骨架、四份结果对齐的检查表,以及按你自身条件该怎么选——所有骨架都标注为方法说明,本站在本机没有运行过 Stock_Analysis_For_Quant 里的任何文件。
四条实现路径的依据
Stock_Analysis_For_Quant 里四工具实现同一条均线策略:逐维度对照
下表每一格都指到仓库里真实存在的文件或目录。比「哪个语言好」更实际的问题是:你手上的条件能走通哪一条。
| 对照维度 | Python | R | Excel | Matlab |
|---|---|---|---|---|
| 实现依据文件 | Technical_Indicators/SMA.ipynb、Moving_Averages.ipynb、Golden_Death_Cross.ipynb | R_Technical_Analysis/R_Moving_Average.R、R_EMA.R、R_MA_High_Low.R | Stock_Plot_MA.xlsx、Stock_Trend.xlsx、Excel_Stock_VBA_Macros/ 下 7 个 .xlsm | DailyStock.mlx、financial_analysis.mlx、DailyReturn.m |
| 代码形态与粒度 | 一个指标一个 notebook,代码与图表混在同一个文件里 | 一个方法一个脚本,纯代码、无输出缓存 | 单元格公式 + 按钮式宏,逻辑藏在公式与 VBA 里 | 脚本版与实时脚本版各一份,粒度最粗 |
| 数据接入 | yfinance(yf.Ticker('AAPL') 这类写法) | quantmod::getSymbols('AAPL', ...) | 工作簿内自带数据快照,需自己贴新数据 | 取数逻辑写在 Live Script 里,需自己核对 |
| 依赖与安装门槛 | 需 Python 3 与若干库;仓库不提供依赖清单 | 需 R 与 quantmod 等包;同样无清单 | 只需表格软件(含宏功能),但 .xlsm 会触发宏安全提示 | 需 Matlab(README 写 R2016a 或更新),属商业软件 |
| 指标库覆盖(全目录) | 指标区 213 个文件,均线类含 SMA / EMA / WMA / DEMA / TEMA / TRIMA / KAMA / Hull / ZLEMA / Guppy 等 | 技术分析区 159 个脚本,含 R_EMA.R、R_WMA、R_Band_SMA.R、R_MA_Oscillator.R | 指标区只有 4 个文件,均线主要靠公式自行搭建 | 无独立指标目录,均线需自己写 |
| 出图能力 | matplotlib 一类库,图与代码同源,便于复现 | 基座绘图够用,代码短、出图快 | 原生图表,交互好但公式改动需人工同步 | 绘图能力完整,但本站未实测 |
| 结果可重复性 | 高:一段代码可原样重跑(前提是自己的依赖记下来) | 高:同 Python,脚本粒度更小 | 中:公式可追溯,但人工操作环节多 | 中:依赖本机版本,未实测无法判断 |
| 更容易踩的坑 | 库里同名指标写法不同(如 talib 与 ta 两套),混用会得到不同数值 | 参数默认值与 Python 库不同,直接对比会出现偏差 | 复权价与原始价混用最隐蔽;宏在工作簿里的行为不易审查 | 样本量太小,缺少可对照的第二个实现 |
先把「同一条均线策略」定义清楚:八个必须写死的口径
四份实现之所以经常对不上,绝大多数不是代码写错,而是这八项里有一项没统一。先填完这张表,再动代码。
| 口径项 | 常见写法 | 写错/不统一的后果 | 对齐建议 |
|---|---|---|---|
| 用什么价 | 收盘价;或复权后的收盘价;或典型价(高+低+收)/3 | 四个工具对「价」的默认理解不同,均线本身就不一样 | 在策略记录里写死「用哪一个字段」,并写清是否复权 |
| 均线类型 | SMA 简单平均;EMA 指数加权;WMA 加权 | 同名「20 日均线」在两个工具里可能是不同东西 | 写清类型与周期,例如 SMA(20) 而不是「20 日线」 |
| 周期参数 | 短 5/10/20,长 60/120/200 | 参数不同则信号完全不同,跨语言对比失去意义 | 四份实现用同一组参数,并把参数写进结果文件名 |
| 信号定义 | 短线上穿长线为买入(金叉),下穿为卖出(死叉) | 有的实现用「价格穿越均线」,有的用「双均线交叉」 | 用一句话把信号写成布尔条件,四个实现照抄这一句 |
| 信号日与成交日 | 信号当日收盘成交,或次日开盘成交 | 次日成交会显著改变结果,也是未来函数的高发点 | 明确写「T 日信号、T+1 成交」或「T 日收盘成交」 |
| 成本假设 | 手续费按比例、滑点按最小变动单位 | 不计成本的收益曲线通常明显偏高 | 统一给出费率与滑点公式,并注明以实际费率与数据源为准 |
| 样本区间与缺失值 | 固定起止日期;停牌或缺失日如何处理 | 区间不同直接导致收益不可比;缺失值填充方式会改变指标 | 四份实现共用同一份数据文件与同一段起止日期 |
| 空仓规则 | 无信号时空仓,或始终持有 | 「空仓」与「持有」是两种策略,不能互相对比 | 把空仓规则写成第三种状态并明确标注 |
Python 与 R 的最小骨架:把上面八项写成代码
下面两段是方法骨架,不是 Stock_Analysis_For_Quant 里文件的原文,也没有在本站运行过。写出来是为了让你看清两种实现的形态差异:Python 侧一段 pandas 就能表达,R 侧靠 quantmod 与内置函数更短。
# Python 骨架:SMA 交叉(方法说明,未在本站运行)
import yfinance as yf, pandas as pd
px = yf.Ticker("AAPL").history(start="2020-01-01", end="2023-12-31")
close = px["Close"] # 口径①:用收盘价(是否复权需自行确认)
short = close.rolling(20).mean() # 口径②③:SMA,参数 20 / 60
long_ = close.rolling(60).mean()
signal = (short > long_).astype(int) # 口径④:短线上穿长线为持仓状态
trade = signal.diff().fillna(0) # 口径⑤:T 日信号,T+1 成交需再 shift(1)
print(close.tail(), signal.sum(), trade.sum()) # 预期输出:价格尾部 + 持仓天数 + 交易次数
# R 骨架:SMA 交叉(方法说明,未在本站运行)
library(quantmod)
getSymbols("AAPL", from = "2020-01-01", to = "2023-12-31", auto.assign = TRUE)
close <- Cl(AAPL) # 口径①:收盘价,系列由取数函数决定
short <- SMA(close, n = 20) # 口径②③:SMA,参数 20 / 60
long_ <- SMA(close, n = 60)
state <- ifelse(short > long_, 1, 0) # 口径④:与 Python 侧同一句条件
flip <- diff(state) # 口径⑤:信号变化处才是交易发生点
tail(close); sum(state, na.rm = TRUE); sum(abs(flip), na.rm = TRUE) # 预期输出:价格尾部 + 持仓 + 换手
| 骨架里的节点 | 预期看到什么 | 看不到时先查哪一项 | 注意点 |
|---|---|---|---|
| 取数 | 一张按日期索引的价格表,行数等于区间内交易日数 | 网络、标的代码、起止日期格式 | Python 与 R 的默认复权处理不同,需显式确认 |
| 滚动均值 | 前 19(Python)或 19 行(R)为空值,之后才有值 | 是不是把窗口写成了 20 个「自然日」 | 空值段不能直接参与信号判断,否则会伪造出早期信号 |
| 信号序列 | 一条只有 0/1 的序列,与价格表等长 | 比较方向是否写反(短线在上应为 1) | 写成 0/1 状态比写成「买卖点」更容易跨语言对齐 |
| 交易次数 | 远小于持仓天数的一个整数 | 是否把每日的持仓状态当成了交易 | 交易次数是后续套成本的关键输入,必须先跑通 |
| 两语言结果对比 | 持仓天数与换手次数应非常接近 | 复权、时区、缺失日处理三处差异 | 完全相等并不现实,量级一致即可先往下走 |
Excel 这一路:公式可追溯,但 7 个启用宏的工作簿要留意
Excel 侧做均线不靠库,靠公式与宏。好处是每一步都能肉眼追溯,代价是人工环节多、批量处理不便。下表把这一路要碰到的文件列清楚。
| 文件 | 形态 | 大概做什么 | 注意点 |
|---|---|---|---|
Excel_Stock/Stock_Plot_MA.xlsx | 顶层工作簿 | 均线绘制与趋势可视化的表格实现 | 数据是写入工作簿的快照,换成新标的要自己替换数据区 |
Excel_Stock/Excel_Stock_VBA_Macros/Stock_Calculate_Daily_Returns.xlsm | 启用宏的工作簿 | 用宏批量算日收益,是均线信号的前置计算 | 需要启用宏并接受安全提示 |
Excel_Stock/Excel_Stock_VBA_Macros/Stock_Functions_DailyReturns.xlsm | 启用宏的工作簿 | 把日收益计算封装成可复用函数 | 与上一个文件功能相邻,注意区分哪个是你需要的 |
Excel_Stock/Excel_Stock_VBA_Macros/Stock_Button_Sheets.xlsm 与 Stock_Button_Sheets_Simple.xlsx | 宏 + 普通工作簿各一 | 按钮式整理工作表结构 | 同一功能的宏版与普通版并存,别混用 |
Excel_Stock/Excel_Stock_VBA_Macros/Stock_Button_Sort_Symbols.xlsm 与 Stock_Button_Delete_Symbols.xlsm | 启用宏的工作簿 | 批量排序与清理标的列表 | 删除类宏操作前先备份工作簿 |
Excel_Stock/Excel_Stock_VBA_Macros/Stock_Index_Links.xlsm 与 Stock_Button_Website.xlsm | 启用宏的工作簿 | 生成索引链接与打开外部站点 | 涉及外部链接,注意公司网络策略 |
Excel_Stock/Excel_Stock_VBA_Macros/Stock_Highlight_Every_Other_Row.xlsx、Stock_VLOOKUP.xlsx、Stock_Companies_LOOKUP.xlsx | 普通工作簿 | 格式与查表类辅助 | 不需要宏,可安全打开 |
Excel_Stock/Bullock_Gold_Mining_Case/VBA_Script.txt 与 Payback_Function.txt | 纯文本源码 | 把宏代码以文本形式留存,便于阅读代码本身 | 这是唯一能「直接读源码」的地方,属于投资回收期课程案例 |
AVERAGE 配合位移区间(例如对 B 列的第 20 行到第 39 行求平均),再单列一条「短线上穿长线」的判断列,取值只写 0/1。这样做的最大好处是每个中间值都能点开看,排查口径问题比代码快;代价是要换标的或换区间时得手工重排数据区,而且宏一旦改动,历史结果就不可复现了。以仓库里工作簿的实际公式为准。Matlab 这一路有多薄:全目录只有 4 个代码文件
官方 README 把 Matlab 与其它五个工具并列展示,容易让人以为体量相当。按固定提交清点,Matlab_Stock/ 一共 6 个文件,其中代码文件只有 4 个。
| 文件 | 类型 | 大致内容 | 放到均线策略里能做什么 | 本站状态 |
|---|---|---|---|---|
DailyReturn.m | 脚本 | 日收益计算 | 可作为信号收益化的前置步骤 | 未实测(本机无授权) |
DailyReturn.mlx | 实时脚本 | 与上一个文件同题,带可交互输出 | 对照脚本版与实时脚本版的差异 | 未实测 |
DailyStock.mlx | 实时脚本 | 日行情获取与处理 | Matlab 侧的取数写法参考 | 未实测 |
financial_analysis.mlx | 实时脚本 | 金融分析示例 | 最接近「一条完整分析流程」的那一份 | 未实测 |
MATLAB.PNG 与 README.md | 图片 + 说明 | 目录封面与目录级说明 | 说明里只有描述、软件下载链接与免责声明,无运行步骤 | 可直接读(文本与图片) |
四份结果对不上时,按这张表逐项排查
把四个实现放在一起,结果几乎不可能完全相等。下表的顺序是按「排查成本从低到高」排的,建议从上往下查。
| 检查项 | 怎么查 | 不一致时先改哪一边 | 允许的偏差范围 |
|---|---|---|---|
| 样本区间是否相同 | 比对两端第一行与最后一行的日期 | 把四份实现都改成同一段起止日期 | 必须完全一致,否则后续对比无意义 |
| 交易日历是否相同 | 数一下行数:同一区间内行数应当接近 | 统一用交易所日历,剔除多余的自然日 | 行数差异应能解释为节假日处理差异 |
| 价格字段是否相同 | 看用的是收盘价、复权收盘价还是典型价 | 统一到一个字段,并记录下来 | 必须一致 |
| 均线类型与参数 | 确认都是 SMA(20) 与 SMA(60),不是 EMA | 改参数或改类型,不要两处同时改 | 必须一致 |
| 滚动窗口的空值处理 | 看前 19 行是空值还是被填成了数值 | 把空值保留为缺失,不要提前填 | 必须一致,否则会伪造早期信号 |
| 信号与成交的时点 | 确认是 T 日收盘成交还是 T+1 成交 | 统一成 T+1,更接近真实可执行性 | 必须一致 |
| 成本与滑点是否计入 | 看收益序列里有没有按换手扣减 | 先都不计成本做一次对齐,再统一加成本 | 分两步做,便于归因 |
| 数值精度与舍入 | 比对同一天的均线值差异是否在小数点后几位 | 保留足够精度,不要中途四舍五入 | 小幅差异属正常,量级异常才需回查 |
先对齐数据,不碰策略
四个实现读同一份价格文件(可从仓库的样例数据或自己导出的 CSV 开始),只做「读入 + 打印首尾日期」。预期输出:四个实现的起止日期与行数完全一致。这一步不过,后面所有对比都是无效的。
再对齐单条均线
只算 SMA(20) 这一列,导出到同一个对照文件。预期输出:同一交易日的均线值在四个实现里量级一致;若差异出现在开头几行,问题在空值处理。
然后对齐信号状态
把信号写成 0/1 状态列,而不是买卖点。预期输出:持仓天数接近;若差异很大,多半是「价格穿越均线」与「双均线交叉」两种定义混用了。
最后才加成本
在已有换手次数的基础上套统一费率与滑点。预期输出:收益曲线整体下移,但四个实现的相对形态保持一致;若形态变了,说明成本被重复扣减。
把口径写进结果文件
文件名带上标的、区间、参数与成本假设,例如包含
SMA20_60与日期区间。预期输出:半年后你还能凭文件名判断这份结果是怎么算出来的——这正是这个仓库缺的那一环。
按你的条件选一条路:五种情况对应的实现方式
四条路没有优劣,只有合不合适。下表按「你手上有什么」来给答案。
| 你的情况 | 建议走哪条 | 理由 | 注意点 |
|---|---|---|---|
| 不会编程,但有 Excel | Excel 公式路线 | 零环境成本,中间值可肉眼追溯,适合把「均线是什么」弄懂 | 换标的与区间需手工重排;涉及 7 个 .xlsm 时注意宏安全 |
| 会一点 Python | Python 路线 | Stock_Analysis_For_Quant 的指标区有 213 个文件可对照,粒度最细 | 仓库不给依赖清单,先按 notebook 的 import 反推再装 |
| 会用 R | R 路线 | 技术分析区 159 个脚本多为纯代码,代码短、跑得快,适合改参数做批量对比 | R 侧默认参数与 Python 库不同,跨语言对比前先统一口径 |
| 有 Matlab 授权 | Matlab 路线 + 一个脚本语言做对照 | Matlab 侧只有 4 个代码文件,单独用它无法完成完整对照 | 本站未实测;以官方文件与你本机版本为准 |
| 只想先看结果、不想装环境 | 先走免部署的技能路线 | 取数与出图可以一次性完成,用来验证你对口径的理解是否正确 | 技能侧产出静态结果,做工程化批量对比仍需脚本语言 |
| 要交付可复核的研究 | Python 或 R 路线,并附口径记录 | 代码可原样重跑,配合上面第二张口径表即可复现 | 务必锁 commit 与记录依赖版本,仓库本身不提供版本信息 |
先跑通再优化
不管走哪条路,第一次都只做「取数→一条均线→一个 0/1 信号→两个统计数」这四步。跑通之后再叠加第二条均线、成本与参数扫描。反过来的顺序——先写完整策略再排错——通常会把时间都花在环境问题上。
别跳过对齐
四份实现里有两份结果不一致,最常见的根因不是代码错误,而是价格字段或成交时点不同。先把口径表填完,再去比数字,能省掉大量「以为是 bug」的排查。
成本必须算进去
均线策略换手相对频繁,成本对结果的影响会被放大。哪怕只是给换手次数乘上一个固定的单边费率,也比完全不计成本更接近真实;费率请以交易所、券商与你使用的数据源为准。
结果要能说明来源
Stock_Analysis_For_Quant 没有版本发布,复现只能锁 commit。建议把 commit、依赖版本、数据日期写进结果文件名或同目录的说明文件,这样半年后回看仍然能解释这条曲线怎么来的。
关于均线策略四实现的常见问题
以下关于 Stock_Analysis_For_Quant 各目录的结论,基于固定提交 df4bd3c58d71 的文件清单与官方 README;本站未运行过仓库中的任何文件,具体实现请以仓库代码与你本机环境为准。
为什么本站只给骨架,不直接给仓库里的 notebook 原文?
因为直接搬运原文既不能说明口径,也不适合作为你自己的研究记录。Stock_Analysis_For_Quant 里的实现分散在不同语言与不同目录,逐份抄写反而会掩盖「同一件事四种写法」的对照价值。骨架的作用是把八项口径显式写出来,你拿它去对照仓库里的实现,才能判断差异出在哪里。仓库文件本身仍是最权威的参照,以其当前状态为准。
四种实现的结果应该完全一致吗?
不应该期待完全一致。即使口径全部对齐,不同语言在浮点精度、缺失值处理、默认年化方式上仍有细微差别;再加上数据源在不同工具里的复权处理可能不同,出现小幅差异是正常的。真正需要警惕的是量级差异,例如持仓天数差一倍——那通常意味着信号定义或数据字段被换掉了,按本页的对齐检查表从上往下查即可。
Matlab 那一份为什么这么薄?
这是仓库的实际情况:按固定提交清点,Matlab_Stock/ 一共 6 个文件,代码文件只有 DailyReturn.m、DailyReturn.mlx、DailyStock.mlx 与 financial_analysis.mlx。官方 README 把六个工具并列展示,但没有说明各目录的体量差异。所以在本仓库里做四语言对照时,Matlab 一路更适合当参考样本,而不是完整实现。以官方文件清单为准。
Excel 里能不能做出和代码一样的均线信号?
可以。做法是给每个交易日写一条「短均线是否大于长均线」的判断列,取值只保留 0 或 1,交易发生点再看该列的相邻变化。这样算出来的信号与代码侧可以逐日对照。代价是换标的后要重排数据区,而且用宏做的部分一旦修改就无法复现历史结果。仓库里的 Excel_Stock_VBA_Macros/ 提供了 7 个启用宏的工作簿作为参考,打开时需启用宏。以工作簿实际公式为准。
去哪找现成的均线实现来对照?
Python 侧看 Python_Stock/Technical_Indicators/ 下的 Moving_Averages.ipynb、SMA.ipynb、Golden_Death_Cross.ipynb,以及两份库总表 1_Technical_Indicators_talib.ipynb 与 1_Technical_Indicators_ta.ipynb;R 侧看 R_Stock/R_Technical_Analysis/R_Moving_Average.R 与相邻的 R_EMA.R;Excel 侧看 Stock_Plot_MA.xlsx。注意 talib 与 ta 两套库的同名指标算法可能有差异,混用会得到不同数值。以仓库当前文件与你安装的库版本为准。
要不要考虑做空与仓位管理?
先把多头单标的、固定仓位跑通,再考虑其它。做空会引入借券成本与可融券标的限制,仓位管理会引入再平衡规则,两者都会让四份实现之间的对齐难度明显上升。建议在口径表里先把「空仓」定义为第三种状态(既不是持有也不是卖空),等对齐完成后再各自扩展。本站不提供任何具体仓位建议,也不涉及实盘执行。
为什么强调把口径写进文件名?
因为这个仓库没有版本发布、没有依赖清单,也没有统一的实验记录格式。同一个目录里存在大量 _Part2、_2 结尾的文件,光看名字无法判断哪个更新、用了什么参数。自己做的每一次实现都带上标的、区间、参数与成本假设,是最省事的可复现手段。这是本站的实践建议,不是官方要求。