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 都没有回答过的问题——每个目录只讲自己有什么。

Excel:41 个比率工作簿R:35 个比率脚本 + 38 个波动率脚本Python:45 个比率绘图 notebook本站未运行这些文件
Python:yfinance + 45 notebookStock_Measurement_Ratio_Chart/ 下每个比率一个绘图 notebook
R:quantmod + 35 脚本 + 38 波动率脚本R_Risk_Returns_Ratios/ 与 R_Volatility/ 两个目录
Excel:41 个比率工作簿「一个比率一个 xlsx」,公式写在单元格里
同一指标 → 三处口径差异年化因子 / 无风险利率 / 样本区间,见下方对照表
三语言同题对照的目录依据(按固定提交 df4bd3c58d71 统计);示意非官方图表。三条路径的口径差异(算术 vs 几何收益、日频年化、无风险利率)见本页正文表格。
File map

Stock_Analysis_For_Quant 里四类指标到底落在哪些文件

下表每一格都是仓库里真实存在的文件名,可以按固定提交 df4bd3c58d71 逐个打开核对。

任务Python 侧(Python_Stock/)R 侧(R_Stock/)Excel 侧(Excel_Stock/)口径提醒
收益率Stock_Analysis_Returns.ipynb、Annual_Returns.ipynb、StockReturnsAnalysis.ipynbStockReturnsAnalysis.R、R_Geometric_Return.R、Geometric_Excess_Return.RStock_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.ipynbR_Volatility/ 下 36 个脚本:Close-to-Close_Volatility.R、Parkinson_Volatility.R、Garman_Klass_Volatility.R、Yang-Zhang_Volatility.R 等Stock_Historical_Volatility.xlsx不同估计量用的价格字段不同(收盘价 vs 高低价),结果天然不同
SharpeStock_Measurement_Ratio_Chart/Stock_Sharpe_Ratio_Chart.ipynb、Stock_Geometric_Sharpe_Ratio_Chart.ipynb、Stock_Adj_Sharpe_Ratio_Chart.ipynbR_Risk_Returns_Ratios/Sharpe_Ratio.R、AdjustedSharpe_Ratio.R、ProbSharpe_Ratio.R、OmegaSharpe_Ratio.RExcel 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.ipynbUlcerIndex.R、Pain_Ratio.R、Martin_Ratio.R、D_Ratio.RStock_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.ipynbSortino_Ratio.R、M2Sortino_Ratio.R、UpsidePotential_Ratio.RSortino_Ratio.xlsx、Gain_Loss_Ratio.xlsx、Payoff_Ratio.xlsx「目标收益」这个参数三处默认值可能不同,决定分母口径
Calmar / 回撤类比率Stock_Calmar_Ratio_Chart.ipynb、Stock_Burke_Ratio_Chart.ipynb、Stock_Sterling_Ratio_Chart.ipynbCalmar_Ratio.R、Burke_Ratio.R、Sterling_Ratio.R、BernardoLedoit_Ratio.RCalmar_Ratio.xlsx、Burke_Ratio.xlsx、Sterling_Ratio.xlsx分子用年化收益还是累计收益,各家实现并不统一
这张表的用法:先按你要算的指标定位到三份文件,再并排打开读公式。不要只看其中一份就下结论——Stock_Analysis_For_Quant 的价值恰恰在于同一件事有三种写法,把它们摆在一起才看得出差异在哪。
Conventions

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 一类调用研究口径下建议显式丢弃并记录条数,而不是静默填充
顺序建议:先把上面七项写成一行「口径记录」,再去跑三套代码。否则你会得到三个都"没错"但互不可比的数字。以各文件的实际实现与官方仓库当前状态为准。
Skeletons

三语言最小骨架:同一件事各写一遍

下面三段是 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 口径的优势是每一步都看得见、可手工改;代价是公式散落在单元格里,没有版本记录,换个人打开很容易改错一格,所以更适合口头核对与教学,而不是批量复现。

Why differ

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显式丢弃并记录丢弃条数,禁静默填充
不要做的事:不要为了让三处数字一致而去改数据或截断区间。正确的做法是先把差异归因到某一个约定项,改约定、再重算。若归因不到,说明至少有一处实现有 bug——这本身就是有价值的发现。
Volatility family

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收盘价 / 指数数据更偏指标而非"估计量"技术面研究属另一主题,别和上面的估计量混为一谈
本页的立场:拥有 36 个波动率脚本不等于"36 个可用结论"。Stock_Analysis_For_Quant 在这块给的是方法清单,选择哪一个取决于你的数据有哪些价格字段、以及你要回答什么问题。上表按价格字段分类,就是为了让这个选择有依据。
Reconcile

Stock_Analysis_For_Quant 对账五步:把三套实现拉到一个基准上

下面每一步都给出要执行的命令或检查动作,以及你应该看到的输出形态。这套流程不依赖任何本站未运行过的代码。

  1. 先固定一份「基准数据」,不要让三套代码各取各的

    选一个标的与一段写死的区间(例如某只美股 2016-01-01 至 2018-12-28 的日线),把取到的收盘价导出成一份 CSV,之后三套实现都读这一份 CSV。预期输出:一个只有日期与收盘价两列的本地文件,行数固定;这样数据源差异被彻底排除。仓库里各 notebook 各自调用 yfinance 或 quantmod,取的时点不同就可能拿到不同数据,必须先统一。

  2. 写一份口径记录,只写七个字段

    收益口径(算术/对数)、合成方式(几何/算术)、年化因子、无风险利率来源、复权方式、样本起止、缺失值处理。预期输出:七行文本,贴在代码开头当注释。这七项就是上一节表里的差异来源,先声明再算,能省掉后面大部分返工。

  3. 用同一份数据跑三套实现,只比数字不比代码

    Python 用 pct_change 与 cumprod,R 用 dailyReturn 与 cumprod,Excel 用单元格公式一一对应。预期输出:一张三列对照表(Python / R / Excel × 四个指标)。这一步只记录,不修改任何东西。

  4. 逐个差异做归因,一次只动一个约定

    出现不一致时,按「年化因子 → 无风险利率 → 样本区间 → 收益口径 → 复权 → 自由度 → 缺失值」的顺序排查。预期输出:每个差异后面写清它由哪一项引起。若七项都对齐后仍有差异,基本可以判定其中一处实现有问题。

  5. 把结论写成可复现记录,而不是截图

    记录内容:数据文件名与行数、七个口径字段、三套实现各自的输出、归因结论、执行日期与语言版本。预期输出:一份别人照着能重跑出同样数字的记录。仓库本身不提供这类记录(无依赖清单、无版本发布),所以要由你自己建。

你的情况该走哪条路理由注意点
只想快速拿到一个区间指标,不想装环境走本机已装的中文数据技能:akshare-finance(A 股/指数/基金/期货等取数)、tushare-finance(需账号与 token)一句话取数并直接算出区间涨跌幅、年化波动率一类的指标,省掉装库这一步技能侧的产出是即时结果,不留口径记录;要写进报告前先补齐上一节的七个字段
要读现成的比率实现、想看公式怎么写用 Excel 的 41 个工作簿 + R 的 35 个脚本一个文件一个比率,公式直接可读,适合逐条核对R 侧脚本各自取数,区间可能不一致
要批量算几十个标的、要能复现自己写 Python,把数据与口径都固定下来只有代码化才能把口径写进版本记录别直接改仓库里的 notebook 当生产脚本——它没有测试也没有依赖声明
要给别人看结论Excel 或仪表板工具出图公式与数字同屏,便于口头核对Excel 公式没有版本记录,改一格不易被发现
要研究波动率的不同估计量从 R 侧 R_Volatility/ 起步那里把 36 种估计量按文件拆开,逐个对照最省力先确认你的数据有没有高低价字段
要做期权相关的风险指标Python 的 Options_Strategies/ + 回测审计页的检查表希腊字母与定价的示例集中在那一处期权链数据本仓库不提供,要自备
FAQ

关于跨语言计算的常见问题

以下内容为 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()),但也有直接用原始价格的写法。打开文件时先确认这一步,再谈结果。以各文件的实际取列为准。

下一步:把同一件对照方法用到一条完整策略上

四个指标对完账,下一步是把「一根均线」这条最简单的策略在四种工具里各实现一遍,看看代码量、依赖、数据接入与可解释性差多少——这也是本仓库最不对称的地方:Matlab 目录里只有 6 个文件。