Stock_Analysis_For_Quant / 跨语言对照
Stock_Analysis_For_Quant 的收益率、波动率、Sharpe、最大回撤:同一条公式在三个语言目录里的落点与口径差异
在 Stock_Analysis_For_Quant 里找「怎么算 Sharpe」不是找不到,而是找到太多:Excel 侧有 Sharpe_Ratio.xlsx,R 侧有 Sharpe_Ratio.R 与 AdjustedSharpe_Ratio.R,Python 侧有 Stock_Sharpe_Ratio_Chart.ipynb,三者叫同一个名字,但对无风险利率、年化因子、样本起止的处理并不保证一致。
所以这一页不替你断言某个数值,而是做三件事:把四类指标在三套语言里的具体文件名列出来、把三处最容易踩的口径差异讲清、给出一个可以自己跑完的对账流程。这是官方六个目录 README 都没有回答过的问题——每个目录只讲自己有什么。
Stock_Analysis_For_Quant 里四类指标到底落在哪些文件
下表每一格都是仓库里真实存在的文件名,可以按固定提交 df4bd3c58d71 逐个打开核对。
| 任务 | Python 侧(Python_Stock/) | R 侧(R_Stock/) | Excel 侧(Excel_Stock/) | 口径提醒 |
|---|---|---|---|---|
| 收益率 | Stock_Analysis_Returns.ipynb、Annual_Returns.ipynb、StockReturnsAnalysis.ipynb | StockReturnsAnalysis.R、R_Geometric_Return.R、Geometric_Excess_Return.R | Stock_Holding_Period_Return.xlsx、Arithmetic_vs_Geometric.xlsx | 算术收益与几何收益不是同一件事,跨语言比较前先统一 |
| 波动率 | Stock_RiskAndReturn.ipynb、Stock_Measurement_Ratio_Chart/Stock_Realized_Volatility_Chart.ipynb、Stock_Measurement_Ratio_Chart/Stock_Standard_Deviation_Chart.ipynb | R_Volatility/ 下 36 个脚本:Close-to-Close_Volatility.R、Parkinson_Volatility.R、Garman_Klass_Volatility.R、Yang-Zhang_Volatility.R 等 | Stock_Historical_Volatility.xlsx | 不同估计量用的价格字段不同(收盘价 vs 高低价),结果天然不同 |
| Sharpe | Stock_Measurement_Ratio_Chart/Stock_Sharpe_Ratio_Chart.ipynb、Stock_Geometric_Sharpe_Ratio_Chart.ipynb、Stock_Adj_Sharpe_Ratio_Chart.ipynb | R_Risk_Returns_Ratios/Sharpe_Ratio.R、AdjustedSharpe_Ratio.R、ProbSharpe_Ratio.R、OmegaSharpe_Ratio.R | Excel Risk-Adjusted Returns Ratios Measurement/Sharpe_Ratio.xlsx、Alternative_Sharpe_Ratio.xlsx、Modified_Sharpe_Ratio.xlsx、Trading_Sharpe_Ratio.xlsx | 无风险利率怎么取、年化用 252 还是 365,会直接改变数字 |
| 最大回撤 | Stock_Measurement_Ratio_Chart/Stock_Pain_Ratio_Chart.ipynb、Stock_Measurement_Ratio_Chart/Stock_Downside_Risk_Chart.ipynb、Technical_Indicators/Ulcer_Index.ipynb | UlcerIndex.R、Pain_Ratio.R、Martin_Ratio.R、D_Ratio.R | Stock_Drawdown.xlsx、Ulcer_Index.xlsx、Calmar_Ratio.xlsx | 观察窗口长度与是否含分红,都会让回撤数字不一致 |
| Sortino / 下行风险 | Stock_Normalized_Sortino_Ratio_Chart.ipynb、Stock_Lower_Partial_Moment_Chart.ipynb、Stock_Higher_Partial_Moment_Chart.ipynb | Sortino_Ratio.R、M2Sortino_Ratio.R、UpsidePotential_Ratio.R | Sortino_Ratio.xlsx、Gain_Loss_Ratio.xlsx、Payoff_Ratio.xlsx | 「目标收益」这个参数三处默认值可能不同,决定分母口径 |
| Calmar / 回撤类比率 | Stock_Calmar_Ratio_Chart.ipynb、Stock_Burke_Ratio_Chart.ipynb、Stock_Sterling_Ratio_Chart.ipynb | Calmar_Ratio.R、Burke_Ratio.R、Sterling_Ratio.R、BernardoLedoit_Ratio.R | Calmar_Ratio.xlsx、Burke_Ratio.xlsx、Sterling_Ratio.xlsx | 分子用年化收益还是累计收益,各家实现并不统一 |
Stock_Analysis_For_Quant 的四个指标口径定义:先约好,再比数字
跨语言对账做不下去,多数时候不是代码写错,而是四个约定没提前说清。下表把每个约定的常见选项与「该去文件里确认什么」列出来。
| 约定项 | 常见选项 | 怎么改变结果 | 要确认什么 | 适用场景 |
|---|---|---|---|---|
| 收益的定义 | 算术收益 P_t/P_{t-1}-1;对数收益 ln(P_t/P_{t-1}) | 两者在单期差异很小,但累乘后不一致;对数收益可加、算术收益不可加 | 打开 Arithmetic_vs_Geometric.xlsx 与 R_Geometric_Return.R,看它用的是哪一种 | 做统计与建模时优先对数收益;做报表展示时多为算术收益 |
| 多期收益的合成 | 几何累乘 prod(1+r)-1;算术平均 | 波动越大,算术平均越会高估实际所得 | 看 R_Geometric_Excess_Return.R 与 Annual_Returns.ipynb 的合成方式 | 年度收益与长期业绩应看几何口径 |
| 年化因子 | 252(交易日);365(自然日) | 同一份波动率数据,换算成年化后差约 20% | 在 Stock_Historical_Volatility.xlsx 的公式里找 SQRT(...) 的乘数 | 日频数据用 252;加密资产等 7x24 市场常被写成 365 |
| 无风险利率 | 填 0;取短期国债;取某个固定常数 | 直接决定 Sharpe 的分子,是差异来源里最常被忽略的一项 | 看 Sharpe_Ratio.xlsx 与 Sharpe_Ratio.R 里 rf 的来源 | 做横向比较时三处必须取同一个 rf,否则没有可比性 |
| 波动率的价格字段 | 收盘价;最高/最低价;开高低收组合 | 同一段行情,用高低价的估计量(如 Parkinson)会明显不同 | 对照 R_Volatility/ 下各脚本取用的列名,逐个看 | 只有收盘价时用 Close-to-Close;有高低价时可用区间估计量 |
| 样本起止与频率 | 固定区间;滚动窗口;全样本 | 起止日期不同会让回撤与 Sharpe 都变,且无法事后对齐 | 看脚本里 from / to 是写死还是取当前日期 | 写死区间便于复现;取当前日期便于滚动更新但不能跨时间对比 |
| 缺失值与停牌 | 前向填充;直接丢弃;不动 | 填充会压低波动率并悄悄改变回撤起点 | 看有没有 dropna / na.omit / fillna 一类调用 | 研究口径下建议显式丢弃并记录条数,而不是静默填充 |
三语言最小骨架:同一件事各写一遍
下面三段是 Stock_Analysis_For_Quant 的方法骨架,不是从仓库复制的结果。它们对应仓库里真实存在的路径与库(Python 走 yfinance、R 走 quantmod、Excel 走单元格公式),你可以拿它和目录里的文件对照着看。
Python:yfinance 取数 + pandas 计算
import numpy as np, pandas as pd, yfinance as yf
px = yf.Ticker("AAPL").history(start="2016-01-01", end="2018-12-28")["Close"]
ret = px.pct_change().dropna() # 算术日收益;要几何口径用 np.log(px/px.shift(1))
ann_vol = ret.std(ddof=1) * np.sqrt(252) # 年化因子:日频常用 252
rf_d = 0.0 / 252 # 无风险利率必须先声明,别默认 0 又不写出来
sharpe = (ret.mean() - rf_d) / ret.std(ddof=1) * np.sqrt(252)
cum = (1 + ret).cumprod()
mdd = (cum / cum.cummax() - 1).min() # 最大回撤:先算累计净值,再算相对历史高点的最小比值
预期输出:一个浮点数的波动率、一个浮点数的 Sharpe、一个负的最大回撤;三个数字都必须连同「样本区间 + 年化因子 + rf」一起记录。dropna 丢掉了几个点也要记下来——仓库里多数 notebook 没有做这件事。
R:quantmod 取数 + 手工计算
library(quantmod)
getSymbols("AAPL", from = "2016-01-01", to = "2018-12-28", auto.assign = TRUE)
px <- Ad(AAPL) # 调整后收盘价:Ad() 取的就是复权价
ret <- dailyReturn(px, type = "log") # 对数收益;type="arithmetic" 则为算术收益
vol <- sd(ret) * sqrt(252)
rf_d <- 0 / 252
sharpe <- mean(ret - rf_d) / sd(ret) * sqrt(252)
cum <- cumprod(1 + ret)
mdd <- min(cum / cummax(cum) - 1)
预期输出:与 Python 段同形的三个量。注意 R 侧仓库里 R_Risk_Returns_Ratios/ 下是「一个比率一个脚本」,每个脚本各自取数,所以跨脚本比较时务必确认它们取的是不是同一段区间。
Excel:同一件事写成单元格公式
日收益: =B3/B2-1 (向下填充,B 列是收盘价) 年化波动率: =STDEV.S(C3:C1000)*SQRT(252) 年化收益: =(PRODUCT(1+C3:C1000)^(252/COUNT(C3:C1000)))-1 (数组公式口径) Sharpe: =(AVERAGE(C3:C1000)-rf)*SQRT(252)/STDEV.S(C3:C1000) 累计净值: =D2*(1+C3) (D 列自 1 起累乘) 历史高点: =MAX($D$2:D3) (随行扩展的滚动最大值) 回撤: =D3/E3-1 最大回撤: =MIN(F3:F1000)
预期输出:一组停留在工作表里的公式结果。Excel 口径的优势是每一步都看得见、可手工改;代价是公式散落在单元格里,没有版本记录,换个人打开很容易改错一格,所以更适合口头核对与教学,而不是批量复现。
Stock_Analysis_For_Quant 里同一个指标为什么会算出不同数字:七个可核对的差异来源
下表按「查起来最快」到「最难发现」排序。前三项几乎每次都会碰到,后几项属于隐蔽项。
| 差异来源 | 典型表现 | 怎么查 | 处置建议 |
|---|---|---|---|
| 年化因子不同 | 一处用 252、一处用 365,波动率与 Sharpe 同时偏离 | 在三份实现里搜 sqrt / SQRT 的乘数 | 统一写成 252 并在口径记录里写明 |
| 无风险利率不同 | 一处填 0、一处填某个常数,Sharpe 差异明显而波动率一致 | 找 rf / risk_free 的定义行 | 三处取同一个 rf;若填 0 必须显式写出 |
| 样本区间不同 | 回撤与区间收益对不上,但都"看起来对" | 对比 from/to 或 start/end 参数 | 把区间写成固定日期,不用「当前时间」 |
| 收益口径不同 | 累计收益对不上,单期差异很小 | 看用 pct_change 还是 log、用 Ad() 还是原始价格 | 在口径记录里明确是算术还是对数、是否复权 |
| 是否用复权价 | 含分红派息的标的在除权日出现跳空,波动率被抬高 | 看是否取 Adjusted Close / Ad() | 做收益率研究一律用复权价,并注明复权方向 |
| 自由度与标准差定义 | 样本标准差与总体标准差之差,小样本下更明显 | Python 看 ddof、R 看 sd(默认样本标准差)、Excel 看 STDEV.S 还是 STDEV.P | 统一为样本标准差(ddof=1 / STDEV.S) |
| 缺失值处理不同 | 停牌或数据缺口被填平,波动率被压低 | 搜 fillna / dropna / na.omit | 显式丢弃并记录丢弃条数,禁静默填充 |
R 侧 38 个文件的波动率家族:按价格字段分类
仓库里波动率相关文件最密的不是 Python 而是 R:R_Stock/R_Volatility/ 下 36 个脚本加 2 个资源文件。它们不是 36 种"不同指标",而是同一件事的不同估计量——差别主要在用到哪些价格字段。
| 估计量 / 文件名 | 价格字段 | 特点 | 什么时候用 | 注意点 |
|---|---|---|---|---|
Close-to-Close_Volatility.R | 收盘价 | 最基础的实现,可加性更直接,与多数教材一致 | 只有收盘价数据时 | 忽略盘中信息,波动被低估 |
Parkinson_Volatility.R | 当日最高价、最低价 | 用区间幅度估计,理论上效率高于收盘价法 | 有可靠高低价时 | 高低价受撮合与异常报价影响 |
Garman_Klass_Volatility.R | 开、高、低、收 | 同时利用区间与开收,进一步降低估计方差 | 四个价格字段齐全时 | 对跳空敏感 |
Rogers-Satchell_Volatility.R | 开、高、低、收 | 对漂移不敏感,非零均值场景更稳 | 趋势明显的行情 | 参数与实现细节需逐个核对 |
Yang-Zhang_Volatility.R | 开、高、低、收 | 同时处理隔夜跳空与盘中波动 | 存在明显隔夜的品种 | 计算步骤较多,跨语言实现差异大 |
High-Low_Volatility.R / High-Low-Open-Close_Volatility.R | 高、低(+开、收) | 介于收盘价法与完整区间法之间的中间方案 | 想快速对比不同口径时 | 与上面几项有重叠,注意区分 |
Open-to-Close_Volatility.R / Overnight_Volatility.R | 开、收 / 隔夜价 | 把盘中波动与隔夜波动分开 | 研究跳空与开盘冲击 | 两段相加才等于日波动,别混用 |
Annualized_Volatility.R / Historical_Volatility.R | 收盘价 | 把日频结果年化,直接对应报表口径 | 对外汇报 | 年化因子(252 或 365)必须在表里写清 |
30-Days_Actual_Current_Volatility.R / 90-Days_Actual_Current_Volatility.R | 收盘价 | 固定窗口的"实际波动率"口径 | 需要短周期对比时 | 窗口长度是硬编码,改窗口要动代码 |
Alizadeh-Brandt-Diebold_Volatility.R、Hodges-Tompkins_Volatility.R、Kunitomo_Volatility.R、Meilijson_Volatility.R、Crude_Volatility_Estimation.R | 视实现而定 | 学术文献里更专门的估计量,仓库里各有一个脚本 | 做方法对比研究 | 不是常用口径,先读脚本再决定是否采用 |
Day_Of_The_Week_Is_Stocks_Most_Volatile.R / Day_Of_The_Month_Is_Stocks_Most_Volatile.R | 收盘价 | 按星期几/每月第几天分组统计波动率 | 做日历效应探索 | 样本量不足时结论不稳定 |
Relative_Volatility_Index.R、R_VROC.R、R_VIX.R、R_VXN.R | 收盘价 / 指数数据 | 更偏指标而非"估计量" | 技术面研究 | 属另一主题,别和上面的估计量混为一谈 |
Stock_Analysis_For_Quant 对账五步:把三套实现拉到一个基准上
下面每一步都给出要执行的命令或检查动作,以及你应该看到的输出形态。这套流程不依赖任何本站未运行过的代码。
先固定一份「基准数据」,不要让三套代码各取各的
选一个标的与一段写死的区间(例如某只美股 2016-01-01 至 2018-12-28 的日线),把取到的收盘价导出成一份 CSV,之后三套实现都读这一份 CSV。预期输出:一个只有日期与收盘价两列的本地文件,行数固定;这样数据源差异被彻底排除。仓库里各 notebook 各自调用
yfinance或quantmod,取的时点不同就可能拿到不同数据,必须先统一。写一份口径记录,只写七个字段
收益口径(算术/对数)、合成方式(几何/算术)、年化因子、无风险利率来源、复权方式、样本起止、缺失值处理。预期输出:七行文本,贴在代码开头当注释。这七项就是上一节表里的差异来源,先声明再算,能省掉后面大部分返工。
用同一份数据跑三套实现,只比数字不比代码
Python 用
pct_change与cumprod,R 用dailyReturn与cumprod,Excel 用单元格公式一一对应。预期输出:一张三列对照表(Python / R / Excel × 四个指标)。这一步只记录,不修改任何东西。逐个差异做归因,一次只动一个约定
出现不一致时,按「年化因子 → 无风险利率 → 样本区间 → 收益口径 → 复权 → 自由度 → 缺失值」的顺序排查。预期输出:每个差异后面写清它由哪一项引起。若七项都对齐后仍有差异,基本可以判定其中一处实现有问题。
把结论写成可复现记录,而不是截图
记录内容:数据文件名与行数、七个口径字段、三套实现各自的输出、归因结论、执行日期与语言版本。预期输出:一份别人照着能重跑出同样数字的记录。仓库本身不提供这类记录(无依赖清单、无版本发布),所以要由你自己建。
| 你的情况 | 该走哪条路 | 理由 | 注意点 |
|---|---|---|---|
| 只想快速拿到一个区间指标,不想装环境 | 走本机已装的中文数据技能:akshare-finance(A 股/指数/基金/期货等取数)、tushare-finance(需账号与 token) | 一句话取数并直接算出区间涨跌幅、年化波动率一类的指标,省掉装库这一步 | 技能侧的产出是即时结果,不留口径记录;要写进报告前先补齐上一节的七个字段 |
| 要读现成的比率实现、想看公式怎么写 | 用 Excel 的 41 个工作簿 + R 的 35 个脚本 | 一个文件一个比率,公式直接可读,适合逐条核对 | R 侧脚本各自取数,区间可能不一致 |
| 要批量算几十个标的、要能复现 | 自己写 Python,把数据与口径都固定下来 | 只有代码化才能把口径写进版本记录 | 别直接改仓库里的 notebook 当生产脚本——它没有测试也没有依赖声明 |
| 要给别人看结论 | Excel 或仪表板工具出图 | 公式与数字同屏,便于口头核对 | Excel 公式没有版本记录,改一格不易被发现 |
| 要研究波动率的不同估计量 | 从 R 侧 R_Volatility/ 起步 | 那里把 36 种估计量按文件拆开,逐个对照最省力 | 先确认你的数据有没有高低价字段 |
| 要做期权相关的风险指标 | Python 的 Options_Strategies/ + 回测审计页的检查表 | 希腊字母与定价的示例集中在那一处 | 期权链数据本仓库不提供,要自备 |
关于跨语言计算的常见问题
以下内容为 Stock_Analysis_For_Quant 的方法说明与文件定位;具体实现以固定提交 df4bd3c58d71 的仓库文件与你本机的运行结果为准。
三套实现算出来的 Sharpe 不一样,是不是有一个错了?
不一定。先按本页的七个差异来源逐项核对:年化因子(252 与 365 会让结果差约 20%)、无风险利率(填 0 与填常数差异明显)、样本区间、收益口径、是否用复权价、标准差自由度、缺失值处理。多数情况下把其中两三项对齐后数字就一致了。若七项都对齐仍不一致,才需要怀疑实现有问题——这也是跨语言对照最有价值的产出之一。以各文件的实际公式为准。
做 A 股时这套对账方法还成立吗?
方法成立,但要多处理三件事:一是复权方向(前复权/后复权/不复权对收益率序列的影响不同),二是停牌与涨跌停会造成没有成交的交易日,三是交易日历与美股不同,年化因子仍然是 252 但要按 A 股实际交易日核对。本站的 A 股适配页把这几项整理成了改造清单。以交易所规则与你所用数据源的口径为准。
波动率到底该用哪个估计量?
看你的数据有哪些价格字段:只有收盘价就用 Close-to-Close;有可靠的高低价可以用 Parkinson 或 Garman-Klass;有明显隔夜跳空时 Yang-Zhang 更合适。R_Stock/R_Volatility/ 下把这 36 个估计量按文件拆开了,本页上方的表按价格字段做了分类。选择依据应当是数据条件与要回答的问题,而不是哪个名字更"高级"。以各脚本实现为准。
能用仓库里的 Excel 公式直接抄到自己的表里吗?
可以读、可以借鉴,但抄之前务必确认三件事:年化因子乘数、STDEV.S 还是 STDEV.P、以及回撤公式里滚动最大值 MAX($D$2:D3) 的锚点写法。仓库里 41 个比率工作簿各自独立,没有统一的公式规范说明。以工作簿内的实际公式为准。
不做环境能不能先算这些指标?
可以先用本机已装的中文数据技能跑一版快速结果(例如 akshare-finance 取数后直接算区间涨跌幅与年化波动率),把它当"先看数"的手段;但技能侧不留口径记录,若结果要进报告或对外,仍要回到本页的对账五步补齐七个字段。两部分是分工关系,不是替代关系。以各技能当前文档为准。
为什么本页不给具体的收益率或波动率数值?
因为一个脱离口径的数字没有意义,而且会随时间过期。同一只标的、同一段区间,换一个年化因子或换一次复权就会得到不同结果;本站没有在本机运行过这个仓库的任何 notebook,所以也不会替你断言某个数字。本页给的是可执行的定位方法与对账流程,你可以用它得到属于自己口径的数字。以你的实际运行结果为准。
收益率研究一定要用复权价吗?
做收益率序列研究时应当用复权价,否则除权除息日的价格跳空会被当成真实收益,进而抬高波动率、扭曲回撤。仓库里的示例多数取调整后收盘价(Python 侧常见 Adj Close,R 侧 Ad()),但也有直接用原始价格的写法。打开文件时先确认这一步,再谈结果。以各文件的实际取列为准。