mcpField
S
stock-liquidity

stock-liquidity

github.com/himself65数据与数据库3.5k3.3k 次安装3 天前更新
代码仓库

关于 stock-liquidity

stock-liquidity 是通过 yfinance 获取 Yahoo Finance 数据、评估股票流动性的研究技能。它适用于分析交易成本和多只股票流动性的研究者,覆盖价差、成交量、深度代理指标、市场冲击和换手率。

主要功能

  1. 01生成多指标流动性概览
  2. 02分析报价和期权价差
  3. 03衡量成交量与换手模式
  4. 04估算订单簿深度代理指标
  5. 05建模市场冲击与滑点

适用场景

  1. 01比较多只股票的流动性
  2. 02评估某只股票的交易成本
  3. 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/订单簿数据。请坦诚说明这一限制。我们可以做的是:

  1. 股票报价:买价、卖价、买入数量、卖出数量(仅最优档)
  2. 期权链:各行权价的买价/卖价和未平仓合约数,可作为衍生品深度的代理指标
  3. 日内成交量分布:成交量在一天内的分布情况,可反映连续交易市场的深度
D1:收集可用的深度数据

收集以下三个数据点:

  1. 最优档——来自 ticker.info 的 bid、ask、bidSize、askSize
  2. 日内成交量分布——过去 5 天的 5 分钟 K 线,按日内时段分组,并归一化为每日成交量的百分比
  3. 期权未平仓合约数——最近到期日的看涨/看跌期权总未平仓合约数和成交量,作为衍生品深度的代理指标

完整代码模板请参阅 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 — 所有流动性指标的详细公式、扩展代码模板、指标解读指南和学术参考资料

需要精确公式、边缘情况处理方式或有关流动性指标的深入背景时,请阅读参考文件。

评论

支持 Markdown