关于 stock-liquidity
stock-liquidity 是通过 yfinance 获取 Yahoo Finance 数据、评估股票流动性的研究技能。它适用于分析交易成本和多只股票流动性的研究者,覆盖价差、成交量、深度代理指标、市场冲击和换手率。
主要功能
- 01生成多指标流动性概览
- 02分析报价和期权价差
- 03衡量成交量与换手模式
- 04估算订单簿深度代理指标
- 05建模市场冲击与滑点
适用场景
- 01比较多只股票的流动性
- 02评估某只股票的交易成本
- 03估算拟议订单的市场冲击
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ himself65/ finance-skills --skill stock-liquidity安装后在 Claude Code 中输入/stock-liquidity确认已加载。
附带文件README.mdreferences/liquidity_reference.md
详细介绍
股票流动性分析技能
如果宿主尚未执行以下 shell 探测(例如,在 Codex 中),请先使用其 shell 工具运行 ! 包裹的命令,再选择设置路径。请使用实际输出;如果无法访问 shell,请说明这一限制。
使用来自 Yahoo Finance 的数据,通过 yfinance 从多个维度分析股票流动性——买卖价差、成交量模式、订单簿深度、预估市场冲击和换手率。
流动性很重要,因为它决定了交易的真实成本。报价并非你实际支付的价格——价差、滑点和市场冲击都会侵蚀收益,尤其是在持仓规模较大或流动性较差的股票上。
重要提示:本内容仅供研究和教育用途,不构成财务建议。yfinance 与 Yahoo, Inc. 无关联。
步骤 1:确保依赖项可用
当前环境状态:
!`python3 -c "exec('try:\n import yfinance, pandas, numpy\n print(f\'yfinance={yfinance.__version__} pandas={pandas.__version__} numpy={numpy.__version__}\')\nexcept Exception:\n print(\'DEPS_MISSING\')')"`
如果显示 DEPS_MISSING,请安装所需软件包:
import subprocess, sys
subprocess.check_call([sys.executable, "-m", "pip", "install", "-q", "yfinance", "pandas", "numpy"])
如果已安装,请跳过并继续。
步骤 2:转到正确的子技能
对用户的请求进行分类,并跳转到相应章节。如果用户未指定具体指标,而是要求进行总体流动性评估,请运行 子技能 A:流动性仪表板,一次性计算所有关键指标。
| 用户请求 | 转到 | 示例 |
|---|---|---|
| 总体流动性检查、“X 的流动性如何” | 子技能 A:流动性仪表板 | “AAPL 的流动性如何”、“TSLA 流动性分析”、“这只股票的流动性够好吗” |
| 买卖价差、交易成本、有效价差 | 子技能 B:价差分析 | “AMD 的买卖价差”、“NVDA 期权的价差是多少”、“交易成本估算” |
| 成交量、ADTV、美元成交量、成交量分布 | 子技能 C:成交量分析 | “MSFT 成交量分析”、“平均日成交量”、“SPY 的成交量分布” |
| 订单簿深度、市场深度、Level 2 | 子技能 D:订单簿深度 | “AAPL 的订单簿深度”、“市场深度”、“显示订单簿” |
| 大额订单的市场冲击、滑点、执行成本 | 子技能 E:市场冲击 | “50k 股会让价格变动多少”、“滑点估算”、“1M 美元订单的市场冲击” |
| 换手率、相对于流通股数的交易活跃度 | 子技能 F:换手率 | “GME 的换手率”、“流通股换手率”、“这只股票交易有多活跃” |
| 比较多只股票的流动性 | 子技能 A(多股票代码模式) | “比较 AAPL 和 TSLA 的流动性”、“AMD 和 INTC 哪只流动性更好” |
默认值
| 参数 | 默认值 |
|---|---|
| 回溯周期 | 3mo(3 个月) |
| 数据间隔 | 1d(每日) |
| 市场冲击模型 | 平方根模型 |
| 日内间隔(需要时) | 5m |
子技能 A:流动性仪表板
目标:为一个或多个股票代码生成综合流动性概览,汇总所有关键指标。
A1:获取数据并计算所有指标
import yfinance as yf
import pandas as pd
import numpy as np
def liquidity_dashboard(ticker_symbol, period="3mo"):
ticker = yf.Ticker(ticker_symbol)
info = ticker.info
hist = ticker.history(period=period)
if hist.empty:
return None
# --- Spread metrics (from current quote) ---
bid = info.get("bid", None)
ask = info.get("ask", None)
current_price = info.get("currentPrice") or info.get("regularMarketPrice") or hist["Close"].iloc[-1]
spread = None
spread_pct = None
if bid and ask and bid > 0 and ask > 0:
spread = round(ask - bid, 4)
midpoint = (ask + bid) / 2
spread_pct = round((spread / midpoint) * 100, 4)
# --- Volume metrics ---
avg_volume = hist["Volume"].mean()
median_volume = hist["Volume"].median()
avg_dollar_volume = (hist["Close"] * hist["Volume"]).mean()
volume_std = hist["Volume"].std()
volume_cv = volume_std / avg_volume if avg_volume > 0 else None # coefficient of variation
# --- Turnover ratio ---
shares_outstanding = info.get("sharesOutstanding", None)
float_shares = info.get("floatShares", None)
base_shares = float_shares or shares_outstanding
turnover_ratio = round(avg_volume / base_shares, 6) if base_shares else None
# --- Amihud illiquidity ratio ---
# Average of |daily return| / daily dollar volume
returns = hist["Close"].pct_change().dropna()
dollar_volume = (hist["Close"] * hist["Volume"]).iloc[1:] # align with returns
amihud_values = returns.abs() / dollar_volume
amihud = amihud_values[amihud_values.replace([np.inf, -np.inf], np.nan).notna()].mean()
# --- Market impact estimate (square-root model) ---
# For a hypothetical order of 1% of ADV
adv = avg_volume
order_size = adv * 0.01
daily_volatility = returns.std()
sigma = daily_volatility
participation_rate = order_size / adv if adv > 0 else 0
impact_bps = sigma * np.sqrt(participation_rate) * 10000 # in basis points
return {
"ticker": ticker_symbol,
"current_price": round(current_price, 2),
"bid": bid,
"ask": ask,
"spread": spread,
"spread_pct": spread_pct,
"avg_daily_volume": int(avg_volume),
"median_daily_volume": int(median_volume),
"avg_dollar_volume": round(avg_dollar_volume, 0),
"volume_cv": round(volume_cv, 3) if volume_cv else None,
"shares_outstanding": shares_outstanding,
"float_shares": float_shares,
"turnover_ratio": turnover_ratio,
"amihud_illiquidity": round(amihud * 1e9, 4) if not np.isnan(amihud) else None,
"daily_volatility": round(daily_volatility * 100, 2),
"impact_1pct_adv_bps": round(impact_bps, 2),
"observations": len(hist),
}
A2:解读并呈现
以摘要卡片的形式呈现。为了便于阅读,将 Amihud 非流动性比率乘以 1e9(标准惯例)。
流动性评级(美国股票采用以下粗略阈值):
| 评级 | 平均美元成交量 | 价差(%) | Amihud(×10⁹) |
|---|---|---|---|
| 很高 | > $500M/日 | < 0.03% | < 0.01 |
| 高 | $50M–$500M/日 | 0.03–0.10% | 0.01–0.1 |
| 中等 | $5M–$50M/日 | 0.10–0.50% | 0.1–1.0 |
| 低 | $500K–$5M/日 | 0.50–2.00% | 1.0–10 |
| 很低 | < $500K/日 | > 2.00% | > 10 |
比较多个股票代码时,请使用并排表格,并突出说明哪只股票的流动性更好以及原因。
子技能 B:价差分析
目标:详细分析买卖价差,包括当前价差、根据期权数据得出的历史背景,以及有效价差估算。
B1:从报价获取当前价差
import yfinance as yf
def spread_analysis(ticker_symbol):
ticker = yf.Ticker(ticker_symbol)
info = ticker.info
bid = info.get("bid", 0)
ask = info.get("ask", 0)
bid_size = info.get("bidSize", None)
ask_size = info.get("askSize", None)
current_price = info.get("currentPrice") or info.get("regularMarketPrice", 0)
result = {"bid": bid, "ask": ask, "bid_size": bid_size, "ask_size": ask_size}
if bid > 0 and ask > 0:
midpoint = (bid + ask) / 2
result["absolute_spread"] = round(ask - bid, 4)
result["relative_spread_pct"] = round((ask - bid) / midpoint * 100, 4)
result["relative_spread_bps"] = round((ask - bid) / midpoint * 10000, 2)
return result
B2:期权价差背景
来自 yfinance 的期权数据包含各行权价的买价/卖价,可用于了解衍生品流动性。使用最近到期日,提取接近平值的看涨期权和看跌期权,并计算每个期权的价差及价差百分比。
完整代码模板请参阅 references/liquidity_reference.md §「期权价差分析」。
B3:呈现结果
展示:
- 当前报价价差(绝对值、相对百分比、基点)
- 买卖盘数量(如有)
- 作为参考的接近平值期权价差
- 该价差与此市值层级的典型范围相比如何
子技能 C:成交量分析
目标:分析成交量模式——平均值、趋势、相对成交量和成交额。
C1:计算成交量指标
import yfinance as yf
import pandas as pd
import numpy as np
def volume_analysis(ticker_symbol, period="3mo"):
ticker = yf.Ticker(ticker_symbol)
hist = ticker.history(period=period)
if hist.empty:
return None
vol = hist["Volume"]
close = hist["Close"]
dollar_vol = vol * close
# Relative volume (today vs average)
rvol = vol.iloc[-1] / vol.mean() if vol.mean() > 0 else None
# Volume trend (linear regression slope over the period)
x = np.arange(len(vol))
slope, _ = np.polyfit(x, vol.values, 1) if len(vol) > 1 else (0, 0)
trend_pct = (slope * len(vol)) / vol.mean() * 100 # % change over period
# Volume profile by day of week
hist_copy = hist.copy()
hist_copy["DayOfWeek"] = hist_copy.index.dayofweek
day_names = {0: "Mon", 1: "Tue", 2: "Wed", 3: "Thu", 4: "Fri"}
vol_by_day = hist_copy.groupby("DayOfWeek")["Volume"].mean()
vol_by_day.index = vol_by_day.index.map(day_names)
# High/low volume days
high_vol_days = hist.nlargest(5, "Volume")[["Close", "Volume"]]
low_vol_days = hist.nsmallest(5, "Volume")[["Close", "Volume"]]
return {
"avg_volume": int(vol.mean()),
"median_volume": int(vol.median()),
"avg_dollar_volume": round(dollar_vol.mean(), 0),
"current_volume": int(vol.iloc[-1]),
"relative_volume": round(rvol, 2) if rvol else None,
"volume_trend_pct": round(trend_pct, 1),
"volume_by_day": vol_by_day.to_dict(),
"high_vol_days": high_vol_days,
"low_vol_days": low_vol_days,
"max_volume": int(vol.max()),
"min_volume": int(vol.min()),
}
C2:呈现结果
展示:
- 日均成交量(股数和金额),并以中位数作比较
- 相对成交量(RVOL)——当日成交量与平均值之比。RVOL > 1.5 表示成交量偏高;RVOL < 0.5 表示成交量异常低迷
- 成交量趋势——交易活动是在增加还是减少?
- 星期分布模式(如存在明显差异)
- 成交量最高的 5 个交易日及其背景(财报?新闻?)
子技能 D:订单簿深度
目标:使用股票报价和期权链中的可用买卖盘数据,估算订单簿深度。
Yahoo Finance 不提供完整的 Level 2/订单簿数据。请坦诚说明这一限制。我们可以做的是:
- 股票报价:买价、卖价、买入数量、卖出数量(仅最优档)
- 期权链:各行权价的买价/卖价和未平仓合约数,可作为衍生品深度的代理指标
- 日内成交量分布:成交量在一天内的分布情况,可反映连续交易市场的深度
D1:收集可用的深度数据
收集以下三个数据点:
- 最优档——来自
ticker.info的 bid、ask、bidSize、askSize - 日内成交量分布——过去 5 天的 5 分钟 K 线,按日内时段分组,并归一化为每日成交量的百分比
- 期权未平仓合约数——最近到期日的看涨/看跌期权总未平仓合约数和成交量,作为衍生品深度的代理指标
完整代码模板请参阅 references/liquidity_reference.md §「订单簿深度代理指标」。
D2:呈现结果
展示:
- 最优档:当前买价/卖价及数量
- 日内成交量形态:成交量主要集中在哪些时段(开盘/收盘还是午间)
- 期权深度:总未平仓合约数和成交量,作为衍生品流动性的代理指标
- 如实说明限制:「Yahoo Finance 仅提供最优档数据。若要获取完整的 Level 2 深度,需要直接接入市场数据源(例如 NYSE OpenBook、NASDAQ TotalView)。」
子技能 E:市场冲击
目标:使用平方根市场冲击模型,估算特定订单规模对价格的影响。
实践中常用的标准模型为:冲击(%)= σ × √(Q / V),其中 σ 为日波动率,Q 为订单股数,V 为日均成交量。这是机构交易员使用的 Almgren-Chriss 框架的简化版本。
E1:计算市场冲击估算值
import yfinance as yf
import numpy as np
def market_impact(ticker_symbol, order_shares=None, order_dollars=None, period="3mo"):
ticker = yf.Ticker(ticker_symbol)
hist = ticker.history(period=period)
info = ticker.info
if hist.empty:
return None
current_price = info.get("currentPrice") or hist["Close"].iloc[-1]
avg_volume = hist["Volume"].mean()
daily_volatility = hist["Close"].pct_change().dropna().std()
# Determine order size in shares
if order_dollars and not order_shares:
order_shares = order_dollars / current_price
elif not order_shares:
# Default: estimate for various sizes
order_shares = avg_volume * 0.01 # 1% of ADV
participation_rate = order_shares / avg_volume if avg_volume > 0 else 0
pct_adv = (order_shares / avg_volume * 100) if avg_volume > 0 else 0
# Square-root impact model
impact_pct = daily_volatility * np.sqrt(participation_rate) * 100
impact_bps = impact_pct * 100
impact_dollars = impact_pct / 100 * current_price * order_shares
# Generate impact curve for multiple order sizes
sizes = [0.001, 0.005, 0.01, 0.02, 0.05, 0.10, 0.20, 0.50] # as fraction of ADV
curve = []
for s in sizes:
q = avg_volume * s
imp = daily_volatility * np.sqrt(s) * 100
curve.append({
"pct_adv": round(s * 100, 1),
"shares": int(q),
"dollars": round(q * current_price, 0),
"impact_bps": round(imp * 100, 1),
"impact_dollars_per_share": round(imp / 100 * current_price, 4),
})
return {
"ticker": ticker_symbol,
"current_price": round(current_price, 2),
"avg_daily_volume": int(avg_volume),
"daily_volatility_pct": round(daily_volatility * 100, 2),
"order_shares": int(order_shares),
"order_dollars": round(order_shares * current_price, 0),
"pct_of_adv": round(pct_adv, 2),
"estimated_impact_bps": round(impact_bps, 1),
"estimated_impact_pct": round(impact_pct, 4),
"estimated_impact_total_dollars": round(impact_dollars, 2),
"impact_curve": curve,
}
E2:呈现结果
展示:
- 用户指定订单规模的预估冲击
- 展示成本如何随订单规模变化的冲击曲线表
- 背景说明:「此估算采用平方根市场冲击模型,这是机构常用的标准估算方法。实际冲击取决于执行策略(VWAP、TWAP 等)、交易时段和当前市场状况。」
- 超过约 25 bps 时,指出该订单相对于该股票的流动性较大;超过约 50 bps 时,明确提示风险,并建议用户考虑算法执行或分多日拆单
子技能 F:换手率
目标:衡量股票相对于其流通股和自由流通股的交易活跃程度。
F1:计算换手率指标
import yfinance as yf
import pandas as pd
import numpy as np
def turnover_analysis(ticker_symbol, period="3mo"):
ticker = yf.Ticker(ticker_symbol)
hist = ticker.history(period=period)
info = ticker.info
if hist.empty:
return None
avg_volume = hist["Volume"].mean()
shares_outstanding = info.get("sharesOutstanding")
float_shares = info.get("floatShares")
result = {
"avg_daily_volume": int(avg_volume),
"shares_outstanding": shares_outstanding,
"float_shares": float_shares,
}
if shares_outstanding:
daily_turnover = avg_volume / shares_outstanding
result["daily_turnover_ratio"] = round(daily_turnover, 6)
result["annualized_turnover"] = round(daily_turnover * 252, 2)
result["days_to_trade_float"] = round(
(float_shares or shares_outstanding) / avg_volume, 1
) if avg_volume > 0 else None
if float_shares:
float_turnover = avg_volume / float_shares
result["float_turnover_daily"] = round(float_turnover, 6)
result["float_turnover_annualized"] = round(float_turnover * 252, 2)
# Turnover trend
vol = hist["Volume"]
base = float_shares or shares_outstanding
if base:
hist_copy = hist.copy()
hist_copy["turnover"] = hist_copy["Volume"] / base
recent_turnover = hist_copy["turnover"].tail(20).mean()
older_turnover = hist_copy["turnover"].head(20).mean()
if older_turnover > 0:
result["turnover_trend_pct"] = round(
(recent_turnover - older_turnover) / older_turnover * 100, 1
)
return result
F2:呈现结果
展示:
- 日换手率和年化换手率(相对于总股本和流通股本)
- “流通股换手天数”——按平均成交量计算,换完整个自由流通股需要多少天
- 换手趋势——该股票的交易活跃度是在上升还是下降?
- 背景说明:
| 年化换手率 | 解读 |
|---|---|
| > 500% | 极其活跃——可能由投机或动量驱动 |
| 100–500% | 交易活跃 |
| 30–100% | 活跃度中等 |
| < 30% | 交易清淡——可能是机构买入并持有,或被市场忽视 |
步骤 3:回复用户
运行适当的子技能后:
始终包含
- 历史指标所用的回溯周期
- 数据时间戳——价差和报价是快照,并非实时数据
- 返回空数据的所有股票代码(无效代码、已退市等)
始终注明
- Yahoo Finance 的报价数据对大多数交易所存在 15 分钟延迟——显示的价差可能无法反映当前实时市场情况
- Yahoo Finance 不提供完整订单簿(Level 2)数据
- 市场冲击估算是模型结果,并非保证——实际执行成本取决于策略、时机和市场状况
- 流动性可能迅速变化——今天流动的股票明天未必仍然流动(尤其是在事件、停牌或延长交易时段期间)
实用建议(相关时提及)
- 仓位规模:如果估算的冲击超过约 25 bps,该仓位对于这只股票的流动性而言可能过大(参见子技能 E 中的阈值)
- 小盘股/微盘股警告:日成交额低于 $1M 的股票需要谨慎执行交易
- 价差成本会累积:往返交易(买入 + 卖出)中的 0.10% 价差会产生 0.20% 的成本——对活跃策略而言,这笔成本会逐渐累积
- 流动性溢价:历史上,流动性较差的股票回报率较高,作为补偿——但交易成本可能会侵蚀这部分溢价
展示流动性数据,让用户自行决策;不要推荐具体交易。
参考文件
references/liquidity_reference.md— 所有流动性指标的详细公式、扩展代码模板、指标解读指南和学术参考资料
需要精确公式、边缘情况处理方式或有关流动性指标的深入背景时,请阅读参考文件。