做A股股票数据分布分析,听起来像是个金融专业课的作业题,但真正上手做一遍你才会发现,这活儿远比想象中更有意思,也远比想象中更容易翻车。它不是在预测明天涨跌,而是把市场上成千上万只股票的数据摊开,用统计学的方式看它们到底长什么样——收益率是不是真的服从正态分布?市值分布为什么永远是少数巨无霸拖着长长的尾巴?涨跌停制度会在数据里留下什么奇怪痕迹?
这篇文章就把我这次完整做下来的思路、代码、坑和结果一起说说。
1. 先搞清楚分析对象:A股数据分布到底在分析什么
1.1 为什么核心是“分布”而不是“预测”
很多刚接触量化的人一上来就问“能不能用这个模型预测明天哪个涨”,这是把经济模型理解窄了。股票数据的分布分析,核心目标不是预测个体,而是描述整体规律。就像你研究一个人的身高没有意义,但你统计一万个人的身高,就能得到平均身高、标准差、是不是正态分布——这些整体规律才是有价值的。
放到A股市场上,“分布”解决的是三个层面的问题:个股收益率的时序分布长什么样,全市场某一天的涨跌幅截面分布长什么样,以及不同板块、不同市值股票的指标分布差异有多大。这三个层面合在一起,构成了经济模型的统计地基。比如做风险模型要算VaR,VaR看的就是收益率分布的左尾;做组合优化要估计协方差矩阵,协方差本质上就是多元分布的参数;做因子选股,其实隐含了截面因子的分布假设。
我之前接过一个需求,某团队想做简单的主力资金监控系统,第一步就是要判断“涨跌幅异常”到底怎么定义。如果直接用固定阈值比如3%,遇到市场普涨的日子会发现满屏都是异常。当时就是用分布分析解决了问题:每天先算全市场涨跌幅的分布分位数,把超过99.5分位数定义为异常,这才算真正贴合市场状态。这个例子说明分布分析在实际业务里不是花架子,它能直接产出可用的规则。
1.2 三个维度:截面、时序与尾部
A股数据分布分析要分维度看,不能混为一谈。截面分布是某一时间点上所有股票的某个指标构成的数据集合,比如某一天的涨跌幅、市净率、总市值。时序分布是一只股票在一段时间内的收益率、换手率等指标的分布。还有联合分布,研究多只股票或者多个因子之间是不是独立、相关性如何,这直接关系到组合分散化效果。
这三个维度对应着完全不同的分析工具。截面数据一般先看偏度和峰度,市值的截面分布典型右偏,取对数后才接近正态。时序数据重点看自相关和条件异方差,A股日收益率序列经常出现波动率聚集,所以GARCH族模型在这里比普通正态假设更接近真实。联合分布则要提到Copula,它能分离边缘分布和相关结构,比简单的皮尔逊相关系数更稳健。
我当时做的最重要一个决定,就是无论哪个维度,都必须保留原始数据做多种分布假设,不要一上来就假设正态。业内总说“金融资产收益率接近正态分布”,但在A股样本上你拿JB检验一跑,几乎没有哪只股票能通过。这个结论不是坏事,恰恰说明市场存在大量非线性信息和尾部风险,这正是建模时要重点刻画的地方。
1.3 A股特有的制度因素会直接刻在数据上
做A股分布分析,不能忽略市场微观结构。涨跌停制度是最典型的一个:主板涨跌幅限制在10%,创业板和科创板20%。这意味着日收益率的观测值在极端处是被截断的,真实分布的尾部有一部分被制度“切”掉了。表现在直方图上,你会看到±10%附近有明显的频率堆积,一旦存在连续涨停,还会出现多根阳线紧贴上限的奇异形态。
还有T+1制度、分红除权、ST股票5%涨跌幅限制、新股上市初期无涨跌幅限制等,都会在分布的数据生成过程中留下痕迹。比如ST股票的日收益率分布宽度明显比正常股票窄,你如果把它们混在一起分析,会得到一个奇怪的混合分布,峰度被严重拉低。所以分析前按板块、ST状态分层是很必要的,混在一起是许多新手容易犯的错误。
处理除权除息也一样关键。某只股票每10股派10元,除息日股价会直接跳空低开,看似当天大跌,实际是分红导致的价格调整。如果不用复权数据,收益率分布会凭空多出一堆虚假的极端值。我在第一次做全市场日收益率分布时就踩过这个坑,当年的分红季数据里,10%以上的“假跌幅”异常多,查了半天才发现是复权处理漏了。
2. 数据准备:A股取数与清洗的完整链路
2.1 数据源怎么选:免费接口与商业终端的取舍
做分布分析需要的数据量不小,但也不是越大越好。我这次主要用了一个免费接口做全量历史日线,它支持A股全部股票和指数的日K、周K、月K数据,也提供复权因子、季频财务数据,最重要的是不需要注册Token,对学习和验证场景非常友好。如果你需要的是分钟级Tick数据、资金流、龙虎榜这类高频或另类数据,免费接口覆盖不了的,才需要考虑商业数据终端。
如果只是复现这篇文章的分析,免费接口完全够用。下图是我使用的核心取数逻辑:先初始化会话,再查询指定证券代码在时间区间内的日线数据,最后关闭会话释放连接。整个过程不需要复杂鉴权,但要注意每次运行必须成对调用登录和登出,否则连接会一直占用。
import baostock as bs import pandas as pd # 登录数据服务 lg = bs.login() # 示例:获取单只股票的日线数据,这里换成你关注的真实代码 stock_code = "sh.600XXX" rs = bs.query_history_k_data_plus( stock_code, "date,code,open,high,low,close,volume,amount,tradestatus,pctChg,isST", start_date='2020-01-01', end_date='2024-12-31', frequency="d", adjustflag="2" # 前复权 ) data = [] while (rs.error_code == '0') and rs.next(): data.append(rs.get_row_data()) df = pd.DataFrame(data, columns=rs.fields) bs.logout()这里有个关键点在adjustflag参数。填“1”是后复权,“2”是前复权,“3”是不复权。做收益率分布分析我建议用前复权,它把历史价格按最新股本和分红水平做了调整,更接近投资者实际可获得的收益序列。但前复权也有一个隐患:如果中间发生大比例送转,早期价格可能被调整成负值或者极小的数字,这在计算对数收益率时会报错或产生异常值,后面清洗时要格外留意。
2.2 清洗环节的四个必踩点
拿到原始数据只是万里长征第一步。我总结出四个清洗环节的必踩点,按重要性排序。
第一,停牌处理。长期停牌的股票在停牌期间没有成交,取数接口通常会返回上一交易日的数据或者空值。如果你的分析口径是日收益率,在停牌日填入“0”会让收益率分布多出一簇实心的零值,峰度瞬间高得离谱。更合理的做法是直接剔除停牌日记录,只保留有实际交易的样本。判断停牌可以看成交量是否为0,不过接口返回的tradestatus字段更直接。
第二,ST标记。ST股票的涨跌幅限制通常是5%,而且市场行为和正常股票完全不一样。建议单独拆分出来分析,至少也要在建模时加入虚拟变量。我一般习惯保留isST字段,每次分析前分别统计ST和非ST两个子样本的均值、标准差、偏度、峰度,二者差异通常非常显著。
第三,上市时间过滤。新股上市初期,尤其是前5个交易日没有涨跌幅限制,收益率分布会出现极端值。如果做的是全市场长期截面分析,建议剔除上市不满60个交易日的股票,避免次新股对分布形态造成巨大干扰。这个规则在回测里也常用,本质是规避上市初期的定价波动风险。
第四,退市股保留。做历史分布分析时,如果只取当前还在交易的股票,会引入严重的幸存者偏差。今天的存活样本中自然剔除了已经退市的股票,而退市股往往贡献了极端的负收益尾部。如果是做整体市场分布画像,我建议尽量找到历史快照数据,把曾经交易过但现在已退市的股票也包含进来。免费接口一般无法直接回溯历史成分股,这个限制要心里有数,必要时可以退而求其次,在结论中明确标注样本范围是“当前存续股票”。
2.3 收益率的口径选择决定了分布的形状
分布分析里最核心的变量是收益率。收益率有两种主流口径:简单收益率和对数收益率。
简单收益率计算公式是(P_t - P_{t-1}) / P_{t-1},优点是直观,缺点是当价格出现大幅波动时,它的分布偏度会明显右偏,而且不满足时间可加性。对数收益率是log(P_t / P_{t-1}),它天然满足跨期相加,比如一周的对数收益等于五天的对数收益之和,这在金融建模中非常重要。更重要的是,对数收益率在数值上更接近对称分布,也更接近连续复利假设,因此做分布拟合和VaR计算时优先选择对数收益率。
但要注意一个细节:当收益率绝对值较小时,两种口径算出的结果非常接近,比如5%以内差异可以忽略;但当涨跌幅逼近涨停时,对数收益率比简单收益率数值上略小,极端样本的左侧尾部会有些许收缩。所以在做厚尾分析时,不要频繁切换收益率口径,选定一种后整个分析过程保持一致,否则各个模型参数之间不具备可比性。
除了收益率本身,我还建议同步计算两个辅助变量:一个是振幅,即(最高价-最低价)/前收盘价,它能反映日内波动强度;另一个是换手率,即成交量/流通股本,它能反映交易活跃度。振幅和换手率的分布形态与收益率完全不同,通常是更明显的右偏分布,做对数变换后建模效果更好,这三个变量叠加在一起,基本能构成一个微型市场行为画像。
3. 核心建模:从分布形态到经济模型的搭建
3.1 单变量分布的“三步检验法”
拿到一只股票或者全市场某一天的收益率数据后,我习惯按三步走做分布判断。
第一步,算描述统计量。重点关注均值、标准差、偏度、峰度,以及几个关键分位数(1%、5%、95%、99%)。A股日收益率的经验特征是均值接近0但不等于0,标准差在1%~3%之间,偏度为负或者轻微正偏,峰度显著大于3,说明尖峰厚尾。这里峰度我常用超额峰度(excess kurtosis),即峰度减3,方便与正态分布对比。
第二步,画图。直方图叠加核密度估计曲线是最直观的检查方式,能一眼看出是否双峰、是否截断、尾部是否偏厚。然后是Q-Q图,把样本分位数与理论正态分位数对比,如果点不在一条直线上,尤其是两端偏离明显,就说明正态性假设不成立。Q-Q图虽然简单,但比任何检验统计量都直观,我会把每个分析对象的Q-Q图保存下来,写报告时直接贴上去。
第三步,做正态性检验。JB检验和Shapiro-Wilk检验是最常用的,但它们的结论要带着批判性去看。样本量一大,比如超过5000个观测值,JB检验对微小的偏离也会敏感到拒绝原假设,这时候“显著非正态”未必代表偏离幅度很大,还需要结合偏度峰度的实际数值来判断。样本量小时,检验功效又不足,明明数据偏离正态很多也可能不拒绝,所以检验结果只能作为辅助参考,不能替代图形判断。
from scipy import stats # 假设 log_ret 是 numpy 数组或 pandas Series jb_stat, jb_p = stats.jarque_bera(log_ret) shapiro_stat, shapiro_p = stats.shapiro(log_ret[:3000]) # Shapiro 建议小样本,这里截断 print(f"JB统计量: {jb_stat:.2f}, p值: {jb_p:.4f}") print(f"Shapiro统计量: {shapiro_stat:.4f}, p值: {shapiro_p:.4f}") # 与正态分布做对比拟合 mean, std = stats.norm.fit(log_ret) # 拟合 t 分布 t_df, t_loc, t_scale = stats.t.fit(log_ret) print(f"正态拟合: mean={mean:.4f}, std={std:.4f}") print(f"T拟合: df={t_df:.2f}, loc={t_loc:.4f}, scale={t_scale:.4f}")代码里的t分布拟合很有用。金融收益率数据的典型特征是尖峰厚尾,t分布的自由度参数df直接刻画了尾部厚度,df越小尾部越厚,df大于30时基本接近正态。A股个股日收益率拟合t分布,自由度经常在3到8之间,这个数字比任何文字描述都更有说服力。
3.2 尾部建模:VaR、ES与极值理论的切入点
分布分析落到经济模型上,最典型的应用就是风险度量。VaR的定义是给定置信水平下的最大可能损失,比如95%VaR意味着有95%的概率亏损不会超过这个值。但VaR有个致命缺陷,它只关注分位数本身,完全忽略了超过这个分位数后的尾部形状,也就是“尾部里还有多少风险”它看不见。所以业界越来越倾向于用ES(Expected Shortfall),即损失超过VaR后的平均损失,这个指标能更完整刻画尾部风险。
计算ES最朴素的非参数方法,是把历史收益率排序,直接取尾部样本的平均值,这其实就是一个极值分布思想的雏形。更精细的做法是用极值理论中的GPD分布来拟合超过阈值部分的样本。挑阈值本身有讲究,定太高可用样本太少,参数估计不稳定;定太低则进入尾部区域的样本不够“极端”,近似效果差。实操中通常用样本的90%或95%分位数作为初始阈值,然后查看参数估计对阈值的稳定性,如果阈值小幅变化参数剧烈跳动,说明这个模型不可信。
在A股做ES计算还有一个特殊问题:涨跌停截断。因为日收益率被限制在上下限,历史观测到的尾部数据本身就少,用历史模拟法算ES会系统性低估真实风险。连续跌停的情况下,真实损失远超单日ES估计值,所以在报告里我一般会把单日ES和“连续跌停累积损失”作为两个指标分开披露,避免单日指标给决策者造成虚假安全感。
3.3 从单变量到多变量:相关结构与相依性
很多经济模型逃不开多变量分析。组合风险模型的协方差矩阵、因子模型中的因子暴露矩阵、行业轮动策略中的板块相关性,这些都建立在联合分布之上。经典做法是假设收益率向量服从多元正态分布,这样协方差矩阵就能完全描述变量关系。但A股数据的厚尾性在多元场景下同样存在,极端行情里相关系数会显著上升,也就是所谓“危机时相关性趋于1”,多元正态假设会严重低估极端组合损失。
Copula模型能解决这个问题。它将联合分布分解为每个变量的边缘分布和连接函数,边缘分布可以用前面分析的t分布或其他厚尾分布来刻画,相关结构用Copula函数描述。实操中我常用t-Copula,它比正态Copula多一个自由度参数,能更好捕捉尾部相关性。但Copula参数估计和模拟计算开销比较大,初学者可以先从秩相关系数(Spearman、Kendall)入手,理解变量间的一致性变化方向,再逐步升级到Copula建模。
要注意的是,A股行业间相关性结构并不稳定。经济政策、产业周期、市场风格切换都会让板块相关矩阵发生漂移。所以在做多变量分布分析时,用一个滚动时间窗口(比如250个交易日)来估计相关矩阵,比用全部历史数据一刀切更有意义,这也直接影响到组合优化输入参数的时效性。
4. 实操记录:一次完整的全市场截面分布演练
4.1 环境准备
这次实操我用的是Python 3.10环境,主要依赖库包括pandas、numpy、scipy、statsmodels、matplotlib、seaborn。没有用任何深度学习框架,因为分布分析是统计推断问题,scipy已经覆盖了大部分分布拟合和检验功能。
安装命令如下:
pip install pandas numpy scipy statsmodels matplotlib seaborn baostock如果你之前没装过baostock,建议单独安装,它虽然体积不大,但在取日线数据时相当稳定。整个过程我建议用Jupyter Notebook来跑,因为分布分析是探索性的,画图和调整参数很频繁,交互式环境效率高很多。
4.2 全市场某交易日涨跌幅截面分布
首先实现一个“取全市场截面”的流程。baostock没有直接提供“某个交易日所有股票”的快照接口,但可以通过遍历股票列表来拼装。更主流的方式是换用另一个免费开源库akshare,它提供了历史行情快照,可以指定交易日直接拉全市场涨跌幅数据。
import akshare as ak import pandas as pd import numpy as np from scipy import stats import matplotlib.pyplot as plt import seaborn as sns # 获取指定日期全A股行情快照 df = ak.stock_zh_a_spot_em() # 简化:只看涨跌幅、总市值、换手率 df = df[['代码', '名称', '最新价', '涨跌幅', '换手率', '总市值', '量比']].dropna() # 剔除新股和ST对形态的影响(这里仅演示,实际需按日期过滤) df = df[~df['名称'].str.contains('ST', na=False)] pct = df['涨跌幅'].astype(float) print(f"样本数: {len(pct)}") print(f"均值: {pct.mean():.2f}%") print(f"标准差: {pct.std():.2f}%") print(f"偏度: {pct.skew():.2f}") print(f"超额峰度: {pct.kurtosis():.2f}")这里有一个非常重要的代码细节:涨跌幅字段是字符串类型,必须用astype(float)转换,否则画图时会报错。另外,截面数据里NaN值处理不能简单用dropna(),因为股票停牌时“涨跌幅”字段会是NaN,剔除它们相当于剔除了停牌样本,这符合之前的清洗逻辑。
运行后我会看到涨跌幅均值和标准差都会随着市场行情变化,比如在震荡行情里,标准差大概在1.5%左右;在普涨行情里,均值可能接近2%,标准差反而更大。偏度通常轻微负偏,说明跌停和大幅下跌的股票数量在有些日子会超过大幅上涨的数量。超额峰度几乎总是正的,A股几乎所有交易日都能拒绝正态分布。
接下来画直方图和Q-Q图:
fig, axes = plt.subplots(1, 2, figsize=(12, 4)) # 直方图与KDE axes[0].hist(pct, bins=80, density=True, alpha=0.5, color='steelblue') sns.kdeplot(pct, ax=axes[0], color='black', linewidth=2) axes[0].set_title('全部A股涨跌幅分布') # Q-Q图 stats.probplot(pct, dist="norm", plot=axes[1]) axes[1].set_title('Q-Q图 vs 正态分布') plt.tight_layout() plt.show()Q-Q图通常会在两端出现明显偏离:左端实际数据比理论正态更靠左,右端也是实际数据比理论更靠右,这就是“厚尾”的图形化证据。更微妙的是中间部分,往往比正态线更陡峭,对应“尖峰”。尖峰加厚尾,是A股截面涨跌幅的常态。
4.3 单只股票收益率时序分布与VaR估算
做完截面分析,再做个股的时序分析。以一只科技类股票为例,取它过去三年的日线数据,计算前复权对数收益率,然后做分布拟合和VaR估算。
# 继续沿用 baostock 获取数据的代码,假设已经得到日线 df # 核心处理逻辑如下 df['close'] = df['close'].astype(float) df['pre_close'] = df['close'].shift(1) df['log_ret'] = np.log(df['close'] / df['pre_close']) # 剔除停牌和无收益的交易日 df = df.dropna(subset=['log_ret']) df = df[df['tradestatus'] == '1'] # 描述统计 ret = df['log_ret'] print(ret.describe(percentiles=[0.01, 0.05, 0.95, 0.99])) # 正态分布与t分布拟合 norm_params = stats.norm.fit(ret) t_params = stats.t.fit(ret) # 95% VaR: 分别用经验分布和t分布估计 var_hist = np.percentile(ret, 5) var_t = t_params[1] + t_params[2] * stats.t.ppf(0.05, df=t_params[0]) print(f"历史模拟法 95% VaR: {var_hist:.4f}") print(f"t分布拟合 95% VaR: {var_t:.4f}")代码中t_params返回三个参数:自由度、位置、尺度。用t分布算VaR时不能直接拿np.std(ret)作标准差,要用拟合出来的位置和尺度参数去乘以t分布的分位数。很多新手直接套正态公式,结果低估了尾部风险。
实际跑下来,历史模拟法算出的95%VaR往往会比t分布拟合的VaR更极端,因为t分布拟合虽然描述了厚尾,但它是对平滑分布曲线的近似,无法完全捕捉个别极端事件。两者数字不一样是正常的,关键是在实际应用中想清楚用哪个:如果组合足够分散且数据量大,用t分布拟合更稳定;如果组合里有个别流动性差的股票,历史模拟法更保守。
4.4 市值分布与对数正态化处理
最后看一个市场结构类的截面指标:市值分布。A股总市值分布极度右偏,少数大市值公司占据了极大比例,直接画直方图基本就是一堵紧贴左侧的墙。处理方法是对市值取对数,取对数后的分布会接近正态,这时再用正态分布去描述,就能提取出“对数市值”的均值和标准差。
实操里要注意:对数变换只能部分改善右偏,不能根治。A股市值分布中,头部权重股的对数市值依然存在一条长尾,这说明市场结构并非简单的对数正态。有的研究者用幂律分布来拟合市值尾部,帕累托指数在1.5~2之间,这能说明“少数股票贡献了大多数市值”的头部效应。但幂律分布拟合对样本量要求较高,尾部数据少时结果并不稳健,我一般只把它作为描述性统计,不直接进入经济模型作为动力学假设。
把市值分布与前文涨跌幅分布结合起来看,还能发现一个有意思的现象:小市值股票的收益率波动率和峰度通常更高,这与小盘股流动性差、游资炒作活跃有关。在做多因子模型时,市值因子之所以有效,某种意义上就是因为市值分布与收益率分布之间存在这种内生关联。
5. 常见问题与排查技巧实录
5.1 直方图出现“假双峰”怎么排查
我在做全市场某一天涨跌幅分布时,发现直方图出现一个奇怪的形态:主峰在0附近,但1%附近还有一个小峰。起初我以为是市场分化,不同板块涨跌不一致,后来拆开看才发现,这个小峰完全是由ST股票构造的——ST股票涨跌幅上限5%,且它们更容易涨停,于是在较低的涨幅区间形成聚集。把ST股票剔除后,双峰现象立刻消失。
这个教训说明做A股数据分布分析时,样本分层比任何高级算法都重要。先按板块、ST状态、上市年龄分层,各自看分布,再考虑是否合并,这个顺序不能反。如果一上来就混合建模,你会得到很多无法解释的统计现象,然后花大量时间寻找根本不存在的“市场规律”。
5.2 复权因子导致收益率异常
处理前复权数据时,要特别留意大比例分红送转的股票。比如某股票10转10,前复权后历史价格直接减半,对数收益率在某一天会显示为-69.3%左右,但这并不是真实下跌,而是送转造成的价格调整。虽然理论上复权后的日收益率序列在送转日前后是连续的,但如果接口的复权因子存在除权日错配,就会在收益序列中留下一个锐利的虚假尖峰。
排查技巧很简单:算出日收益率后,把绝对值超过20%的样本全部打印出来,逐个检查日期和当日涨跌幅。如果发现某一天价格变化与当天大盘和个股消息完全不匹配,优先怀疑复权错误。更稳妥的办法是将前复权序列与不复权序列在除权日的收益率做对比,两者差异过大说明复权因子数据有异常。
5.3 计算性能与内存优化
全A股约5000只股票,如果一次性拉取全部日线数据,再把它们拼接成一个大宽表,内存很容易爆掉。某次我尝试直接循环取数后concat,跑了十分钟内存占用到了8G,还没算完就被系统杀了。后来改成分批处理,每批500只股票,算完统计量后只保留压缩后的结果,问题立刻解决。
另一个优化是用float32替代float64保存价格和收益率,内存直接减半。对于分布拟合这种精度要求并不苛刻的场景,float32完全够用。还可以在取数时只取需要的字段,不要无脑把所有列都拉下来,日线数据30多个字段里,做分布分析真正需要的往往不超过6个。
5.4 统计检验的“p值陷阱”
最后分享一个容易被忽略却极其常见的问题。样本量达到几千时,JB检验几乎必定拒绝正态性,p值可能是0.000000,看起来结论非常“显著”,但实际偏度只有0.1,峰度只比3大0.2,这种偏离在业务上可能完全无关紧要。统计显著不等于实际显著。
反过来,样本量只够凑满30个交易日时,即使数据严重偏离正态,检验也可能不拒绝。所以我在给结论时,从来不会只报一个p值,而是同时报偏度、峰度、Q-Q图判断和样本量。如果偏度绝对值小于0.3、超额峰度绝对值小于0.5,即使检验拒绝正态,实际建模中把它当正态近似也问题不大;如果峰度到了10以上,那就必须用厚尾分布或者做极值分析。
写在最后的实操心得
这次做下来,我最大的体会是:A股股票数据分布分析最值钱的部分,不是那些漂亮的统计图表,而是在做分析之前对制度、数据和业务场景的理解。涨跌停截断、ST分层、复权处理、幸存者偏差,任何一个环节不当回事,最后得出的“分布规律”都可能只是数据加工失误的产物。
如果你打算把这类分析真正用到决策场景里,我建议再往前迈一步,把单变量的分布假设放进回测框架里去验证。比如你为了简化模型而假设收益率服从正态,那就应该检查这个假设是否影响了你对风险敞口的判断,尾部拟合误差会不会在极端行情中被放大。毕竟经济模型的最终目的不是完美拟合历史,而是在面对不确定未来时,给决策提供一个尽量可靠的输入。
我个人还有一个习惯,会把每次分析的样本区间、股票池范围、复权方式、收益率口径全部记录在代码注释和报告附录里。细节看似琐碎,但当市场环境发生变化,你想知道历史结论是否仍然成立时,这些记录就是你做判断的依据。希望这篇记录能帮你少走几段弯路。