Amihud非流动性指标在R语言中的实现与应用

张开发
2026/5/11 17:00:24 15 分钟阅读

分享文章

Amihud非流动性指标在R语言中的实现与应用
1. 什么是Amihud非流动性指标我第一次接触Amihud指标是在分析A股市场流动性的时候。这个由耶鲁大学教授Yakov Amihud在2002年提出的指标本质上衡量的是价格冲击成本——你想买卖股票时市场会让你付出多大代价。举个生活中的例子假设你想在二手市场卖一部手机。如果刚挂出去就有人按标价买走说明流动性好如果必须降价才能成交就产生了流动性成本。Amihud指标就是量化这种被迫降价程度的工具它的核心公式是非流动性 |收益率| / 交易金额在R语言中实现这个指标时我发现有三个关键点需要注意首先收益率要取绝对值因为涨跌都会消耗流动性其次交易金额建议用百万元为单位避免数值过小最后需要按月或按年取均值消除短期波动的影响。2. 数据准备清洗与整理2.1 原始数据获取实际操作中我通常从CSMAR或Wind导出以下字段股票代码(Stkcd)交易日期(Trddt)日收益率(Dretwd)成交金额(Dnvaltrd)最近帮券商做项目时他们提供的Excel文件就像原始文章里那样分散在多个sheet中。这时候用readxl包比基础R的read.csv更可靠特别是当数据包含中文时library(readxl) library(dplyr) r1 - read_excel(r1.xlsx, sheet 1) rr1 - read_excel(rr1.xlsx, sheet 1)2.2 数据清洗实战技巧原始数据常有表头多余行用slice处理比直接删文件更可复现。我习惯先用glimpse()查看结构r1_clean - r1 %% slice(-(1:2)) # 删除前两行说明文字合并多个数据框时bind_rows比rbind更智能能自动匹配列名。有次我忘了处理日期格式导致后续计算全错现在都会先标准化merged_data - bind_rows(rr9, r1, r2) %% mutate(Trdmnt as.numeric(str_sub(str_replace_all(Trddt, -, ), 1, 6)))3. 核心计算步骤详解3.1 交易金额标准化成交金额单位不统一是常见坑。有次发现某只股票的流动性指标异常高查了三天才发现是单位混用——有的数据是元有的是万元。现在我固定用这个处理tvn - merged_data %% mutate(tvn Dnvaltrd / 1000000) %% # 转为百万元 select(Stkcd, Trdmnt, Dretwd, tvn)3.2 指标计算与异常值处理分组计算时用group_bysummarise组合最清晰。注意处理除零错误和缺失值illiq - tvn %% group_by(Stkcd, Trdmnt) %% summarise( illiq mean(abs(Dretwd) / tvn, na.rm TRUE), .groups drop ) %% filter(!is.na(illiq), !is.infinite(illiq))实际项目中我发现加入交易日数作为权重会更准确illiq_weighted - tvn %% group_by(Stkcd, Trdmnt) %% summarise( trading_days n(), illiq sum(abs(Dretwd) / tvn) / trading_days, .groups drop )4. 结果分析与可视化4.1 描述性统计计算完记得先看分布。金融数据常有极端值我常用quantile快速检查summary_stats - illiq %% summarise( mean mean(illiq), p25 quantile(illiq, 0.25), p75 quantile(illiq, 0.75), max max(illiq) )4.2 可视化技巧用ggplot2画时间序列时对Y轴取对数更清晰library(ggplot2) illiq %% filter(Stkcd 600519) %% # 茅台示例 ggplot(aes(x Trdmnt, y illiq)) geom_line() scale_y_log10() labs(title 贵州茅台非流动性指标变化, x 年月, y Amihud指标(log))热力图适合分析行业流动性差异。先用cut分箱illiq %% mutate(liquidity_level cut(illiq, breaks 5, labels c(高, 较高, 中, 较低, 低))) %% count(Trdmnt, liquidity_level) %% ggplot(aes(x Trdmnt, y liquidity_level, fill n)) geom_tile()5. 实际应用案例去年帮私募做因子策略时我们发现Amihud指标与未来收益呈显著负相关。但直接使用原始指标效果不好经过以下改进后策略收益提升明显# 行业中性化处理 illiq_enhanced - illiq %% group_by(Trdmnt, industry) %% # 假设已有行业分类 mutate(illiq_rank scale(illiq)) %% # 行业内部标准化 ungroup()另一个坑是指标的周期性。最好先做12个月移动平均library(zoo) illiq_smooth - illiq %% arrange(Stkcd, Trdmnt) %% group_by(Stkcd) %% mutate(illiq_ma rollmean(illiq, k 12, fill NA, align right)) %% ungroup()6. 性能优化建议处理全市场数据时原始方法可能很慢。我测试过几种优化方案data.table加速对于千万级数据速度提升10倍以上library(data.table) dt - as.data.table(tvn) illiq_dt - dt[, .(illiq sum(abs(Dretwd)/tvn)/.N), by .(Stkcd, Trdmnt)]并行计算用furrr包轻松实现library(furrr) plan(multisession) illiq_parallel - tvn %% group_by(Stkcd) %% nest() %% mutate(illiq future_map(data, ~{ .x %% group_by(Trdmnt) %% summarise(illiq mean(abs(Dretwd)/tvn)) }))内存管理大数据时用vroom替代readxllibrary(vroom) files - fs::dir_ls(data/, glob *.csv) multi_df - vroom(files)7. 常见问题排查遇到过最头疼的问题是计算结果全为NA。后来总结出这个检查清单数据类型检查用str()确认数值列不是字符型零值处理交易金额为零会导致除零错误日期格式确保分组用的年月列已正确生成缺失值传播检查na.rmTRUE是否遗漏最近还发现一个隐蔽的bug某些股票的停牌期会被计算为无限大流动性。现在我会先标记停牌日tvn_clean - tvn %% mutate(is_suspended ifelse(Dnvaltrd 10000, TRUE, FALSE)) %% # 假设成交小于1万视为停牌 filter(!is_suspended)8. 扩展应用方向除了传统的流动性度量这个指标还可以构建组合做多低流动性股票做空高流动性股票风险预警流动性突然恶化往往预示股价下跌事件研究分析财报发布前后的流动性变化在科创板分析中我改良了一个截面调整版本cross_section_illiq - tvn %% group_by(Trddt) %% # 按日分组 mutate( illiq_day abs(Dretwd)/tvn, illiq_rank percent_rank(illiq_day) ) %% ungroup()最后分享一个实用技巧把计算结果保存为RDS格式比CSV更省空间而且保留数据类型saveRDS(illiq, data/illiq_2023.rds) readRDS(data/illiq_2023.rds) # 读取时自动恢复原有类型

更多文章