mcpField
E
etf-premium

etf-premium

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

关于 etf-premium

etf-premium 通过 yfinance 使用 Yahoo Finance 数据,分析 ETF 市价相对 NAV 的溢价或折价,支持单只分析、比较、筛选、背景分析及突发行情拆解。

主要功能

  1. 01计算市价相对 NAV 的溢价或折价
  2. 02单只 ETF 快照与同类比较
  3. 03多只 ETF 排名与筛选
  4. 04结合历史及市场背景分析
  5. 05拆解 NAV 变动与期权 Gamma

适用场景

  1. 01查看 ETF 当前相对 NAV 的溢价
  2. 02比较多只 ETF 的溢折价
  3. 03筛选折价幅度异常的 ETF
  4. 04分析 ETF 突然大幅上涨

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/himself65/finance-skills --skill etf-premium

安装后在 Claude Code 中输入/etf-premium确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

README.md
references/
etf_premium_reference.md
gamma_squeeze_reference.md

详细介绍

ETF 溢价/折价分析技能

如果宿主尚未执行以下 shell 探测(例如在 Codex 中),请在选择设置路径前,使用其 shell 工具运行 ! 包裹的命令。请使用实际输出;如果无法访问 shell,请说明这一限制。

使用 yfinance 从 Yahoo Finance 获取数据,计算 ETF 市场价格相对于其净资产价值(NAV)的溢价或折价。

为何重要: ETF 的市场价格可能偏离其底层持仓的价值(NAV)。溢价买入意味着相对于资产价值多付了钱;折价买入则意味着买到了便宜货。对于流动性强的美国股票 ETF,这种偏离通常很小,但对于债券 ETF、国际 ETF、杠杆/反向产品和加密货币 ETF,偏离可能很显著,尤其是在市场承压期间。

重要提示:仅供研究和教育用途,不构成财务建议。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:选择正确的子技能

对用户的请求进行分类,并跳转到相应部分。如果用户只是笼统询问某只 ETF 的溢价或折价,没有指定具体分析类型,则默认使用 子技能 A:单只 ETF 快照。

用户请求跳转到示例
单只 ETF 的溢价/折价子技能 A:单只 ETF 快照“SPY 是否处于溢价?”、“AGG 相对 NAV 的溢价”、“BITO 溢价”
比较多只 ETF子技能 B:多只 ETF 比较“比较债券 ETF 的折价”、“IBIT 还是 BITO 的溢价更高”、“按溢价对这些 ETF 排名”
筛选器 / 查找极端溢价子技能 C:溢价筛选器“哪些 ETF 的折价最大”、“查找交易价格低于 NAV 的 ETF”、“溢价筛选器”
包含背景信息的深入分析子技能 D:溢价深入分析“HYG 为何处于折价”、“ARKK 的溢价是否正常”、“结合背景分析 ETF 溢价”
溢价突然飙升 / gamma squeeze子技能 E:溢价飙升分解“KWEB 今天为何上涨 13%”、“这只 ETF 的上涨是否由 gamma 驱动”、“分解今天的 ETF 行情”、“SOXL 的做市商 GEX”、“溢价还要多久才会收敛”
默认值
参数默认值
数据源yfinance navPrice 字段
价格字段regularMarketPrice(回退至 previousClose)
筛选器范围按类别划分的常见 ETF 列表(见子技能 C)

子技能 A:单只 ETF 快照

目标:展示一只 ETF 当前的溢价/折价,并说明其通常水平,同时与同类 ETF 比较,展示其相对表现。

A1:获取并计算
import yfinance as yf

# Peer groups by category — used to automatically compare the target ETF against its closest peers
CATEGORY_PEERS = {
    "Digital Assets": ["IBIT", "BITO", "FBTC", "ETHA", "ARKB", "GBTC"],
    "Intermediate Core Bond": ["AGG", "BND", "SCHZ"],
    "High Yield Bond": ["HYG", "JNK", "USHY"],
    "Long Government": ["TLT", "VGLT", "SPTL"],
    "Emerging Markets Bond": ["EMB", "VWOB", "PCY"],
    "Large Growth": ["QQQ", "VUG", "IWF", "SCHG"],
    "Large Blend": ["SPY", "VOO", "IVV", "VTI"],
    "Commodities Focused": ["GLD", "IAU", "SLV", "DBC"],
    "China Region": ["KWEB", "FXI", "MCHI"],
    "Trading--Leveraged Equity": ["TQQQ", "UPRO", "SOXL", "JNUG"],
    "Trading--Inverse Equity": ["SQQQ", "SPXU", "SOXS", "JDST"],
    "Derivative Income": ["JEPI", "JEPQ", "QYLD"],
    "Large Value": ["SCHD", "VYM", "DVY", "HDV"],
}

def etf_premium_snapshot(ticker_symbol):
    ticker = yf.Ticker(ticker_symbol)
    info = ticker.info

    # Verify this is an ETF
    quote_type = info.get("quoteType", "")
    if quote_type != "ETF":
        return {"error": f"{ticker_symbol} is not an ETF (quoteType={quote_type})"}

    price = info.get("regularMarketPrice") or info.get("previousClose")
    nav = info.get("navPrice")

    if not price or not nav or nav <= 0:
        return {"error": f"NAV data not available for {ticker_symbol}"}

    premium_pct = (price - nav) / nav * 100
    premium_dollar = price - nav

    # Additional context
    result = {
        "ticker": ticker_symbol,
        "name": info.get("longName") or info.get("shortName", ""),
        "market_price": round(price, 4),
        "nav": round(nav, 4),
        "premium_discount_pct": round(premium_pct, 4),
        "premium_discount_dollar": round(premium_dollar, 4),
        "status": "PREMIUM" if premium_pct > 0 else "DISCOUNT" if premium_pct < 0 else "AT NAV",
        "category": info.get("category", "N/A"),
        "fund_family": info.get("fundFamily", "N/A"),
        "total_assets": info.get("totalAssets"),
        "net_expense_ratio": info.get("netExpenseRatio"),
        "avg_volume": info.get("averageVolume"),
        "bid": info.get("bid"),
        "ask": info.get("ask"),
        "yield_pct": info.get("yield"),
        "ytd_return": info.get("ytdReturn"),
    }

    # Bid-ask spread as context for whether the premium is meaningful
    bid = info.get("bid")
    ask = info.get("ask")
    if bid and ask and bid > 0:
        spread_pct = (ask - bid) / ((ask + bid) / 2) * 100
        result["bid_ask_spread_pct"] = round(spread_pct, 4)

    return result
A2:获取同类 ETF 对比数据

计算目标 ETF 的快照后,查找其 category,并获取同一类别中同类 ETF 的溢价数据。这能让用户立即了解该溢价是 ETF 特有的,还是整个市场普遍存在的。

使用目标 ETF 的 category 选择 CATEGORY_PEERS,移除目标 ETF,并对每只同类 ETF 执行相同的价格/NAV 计算。跳过无法获取 NAV 的行,但要报告请求和返回的同类 ETF 数量,以便用户了解数据缺失情况。

在主要快照之后,以小表格展示同类 ETF 对比。这有助于用户判断溢价是该 ETF 独有的,还是整个类别都存在的——例如,如果所有加密货币 ETF 的溢价都约为 1.5%,那么用户关注的 ETF 就不是异常值。

A3:解读结果

使用此框架说明溢价/折价是否具有意义:

溢价/折价解读
在 +/- 0.05% 以内基本处于 NAV 水平——大型、高流动性 ETF 的正常情况
+/- 0.05% 至 0.25%轻微偏差——很常见,通常无需采取行动
+/- 0.25% 至 1.0%值得注意——值得提及。检查买卖价差和类别
+/- 1.0% 至 3.0%显著偏差——流动性较低、国际型或特色 ETF 中很常见
超过 +/- 3.0%偏差较大——可能表明市场承压、流动性不足或存在结构性问题

类别不同,背景也不同:

  • 美国大盘股(SPY、QQQ、IVV):溢价 > 0.10% 并不常见
  • 债券 ETF(AGG、HYG、LQD、TLT):波动期间出现 0.5-2% 的折价并不少见
  • 国际/新兴市场(EEM、VWO、KWEB):时区不匹配会导致经常出现 0.3-1% 的偏差
  • 杠杆/反向(TQQQ、SQQQ、JNUG):由于每日重置机制,0.3-1.5% 属于正常范围
  • 加密货币(IBIT、BITO):1-3% 的溢价很常见,尤其是较新的基金
  • 大宗商品(GLD、USO、UNG):取决于期货的正价差/逆价差

还要将溢价/折价与买卖价差进行比较:如果溢价小于价差,那只是噪声,而不是信号。


子技能 B:多只 ETF 对比

目标:并排比较多只 ETF 的溢价/折价。

B1:获取数据并排名
import yfinance as yf
import pandas as pd

def compare_etf_premiums(tickers):
    rows = []
    for sym in tickers:
        try:
            t = yf.Ticker(sym)
            info = t.info
            if info.get("quoteType") != "ETF":
                rows.append({"ticker": sym, "error": "Not an ETF"})
                continue
            price = info.get("regularMarketPrice") or info.get("previousClose")
            nav = info.get("navPrice")
            if price and nav and nav > 0:
                prem = (price - nav) / nav * 100
                bid = info.get("bid", 0)
                ask = info.get("ask", 0)
                spread = (ask - bid) / ((ask + bid) / 2) * 100 if bid and ask and bid > 0 else None
                rows.append({
                    "ticker": sym,
                    "name": info.get("shortName", ""),
                    "price": round(price, 2),
                    "nav": round(nav, 2),
                    "premium_pct": round(prem, 4),
                    "spread_pct": round(spread, 4) if spread else None,
                    "category": info.get("category", "N/A"),
                    "total_assets": info.get("totalAssets"),
                })
            else:
                rows.append({"ticker": sym, "error": "NAV unavailable"})
        except Exception as e:
            rows.append({"ticker": sym, "error": str(e)})

    df = pd.DataFrame(rows)
    if "premium_pct" in df.columns:
        df = df.sort_values("premium_pct", ascending=True)
    return df
B2:以排名表格呈现

按溢价/折价排序(折价最大的排在最前)。突出显示:

  • 折价最大的 ETF
  • 溢价最高的 ETF
  • 溢价/折价是否超过买卖价差(若没有超过,则属于市场微观结构噪声)

子技能 C:溢价筛选器

目标:扫描常见 ETF 范围,找出溢价或折价最大的 ETF。

C1:定义范围并扫描

使用 references/etf_premium_reference.md 中按类别整理的范围,或使用用户自己的列表。对每个代码应用子技能 A 的计算,保留类别标签,按用户要求的绝对溢价阈值筛选,并从折价最大到溢价最高排序。将失败或缺少 NAV 的数量明确显示出来,不要默默将其视为零。

C2:呈现结果

显示按溢价排序的排名表格(折价最大的排在最前)。如果列表较长,则按类别分组。指出:

  • 折价最大的前 5 只——可能存在买入机会(也可能是市场承压的迹象)
  • 溢价最高的前 5 只——存在支付过高价格的风险
  • 类别模式——债券 ETF 是否都处于折价?加密货币 ETF 是否都处于溢价?

提醒用户,扫描范围较大时可能需要 1-2 分钟。


子技能 D:溢价深入分析

目标:结合溢价/折价数据和其他背景信息,帮助用户理解溢价存在的原因以及它是否可能持续。

D1:收集全面数据

运行子技能 A 的快照,然后获取三个月的每日历史数据并添加:

  • 年化波动率:std(daily returns) * sqrt(252)
  • 平均每日美元成交额:mean(close * volume)
  • 距离三个月收盘价高点的百分比
  • 资产管理规模、费用率、收益率、年初至今回报率和三年 Beta
  • 买卖价差百分比,以及绝对溢价是否超过该价差

无法获取的字段保留为 null,不要编造数值。为价格和 NAV 输入添加时间戳,让用户判断二者是否同步。

D2:解释原因

收集数据后,使用以下诊断框架解释溢价/折价:

溢价的常见原因:

  • 需求激增 — 买家多于授权参与者所能创建的份额(加密货币等新推出或热门 ETF 常见此情况)
  • 时区不匹配 — 国际 ETF 交易时,标的市场已休市;价格反映了对后续走势的预期
  • 创建机制受阻 — 授权参与者在创建新份额时受到限制
  • 情绪溢价 — 热潮周期中,散户需求推动价格高于公允价值

折价的常见原因:

  • 流动性压力 — 抛售期间,债券和信贷 ETF 往往以折价交易,因为标的债券比 ETF 本身更难定价/交易
  • 赎回压力 — 资金大量流出,但授权参与者响应缓慢
  • NAV 滞后 — 官方 NAV 可能未反映盘后新闻或事件
  • 结构性问题 — 基于期货的 ETF(USO、UNG)处于期货升水状态时,会持续受到拖累

溢价可能持续多久?

  • 对于流动性较强的美国股票 ETF:不会持续 — 套利会在几分钟内修正偏差
  • 对于压力时期的债券 ETF:折价可能持续数天或数周
  • 对于加密货币 ETF:随着基金成熟且授权参与者更加活跃,溢价往往会收窄
  • 对于国际 ETF:随着标的市场开市,每日重新调整

子技能 E:溢价激增拆解(Gamma 挤压分析)

目标:当 ETF 日内刚经历大幅波动,且走势偏离其标的持仓时,将这段波动拆解为(1)由 NAV 驱动的基本面部分,以及(2)由结构性力量驱动的“超额溢价”部分 — 通常最常见的因素是期权交易商的 gamma 对冲、AP 套利机制失效或情绪激增。然后评估溢价可能需要多久才能回归。

用户报告或询问以下情况时,适用此子技能:

  • ETF 在单个交易时段内涨跌 5% 以上
  • ETF 与其标明的标的资产走势出现背离(例如,“MSTR 涨了 13%,但 BTC 只涨了 3%”)
  • 怀疑 ETF 或单只证券出现 gamma 挤压
  • 询问交易商对冲是否正在放大走势

运行 E2 前,请阅读 references/gamma_squeeze_reference.md,了解完整的 GEX 公式推导、交易商持仓约定和演算示例。

E1:将今日走势拆解为 NAV 驱动部分与超额溢价

静态的 navPrice 字段仅提供最近一个交易日收盘时的 NAV。根据当前持仓权重及持仓在同一交易时段内的回报,估算今日 NAV 回报,并按已覆盖权重进行标准化,然后计算:

NAV proxy return = sum(weight_i x return_i) / covered weight
Excess premium return = ETF return - NAV proxy return

报告持仓覆盖率和所用的各持仓回报。如果 funds_data.top_holdings 不完整,优先使用发行方公布的持仓数据或用户提供的权重。

注意:对于标的资产在交易时段内休市的国际 ETF(例如美国交易时段内亚洲持仓的市场已休市),必须使用这些持仓在美国上市的代理证券(ADR)或期货。如果两者都不可用,请告知用户 — NAV 代理值将会滞后。

E2:根据期权链计算做市商伽马敞口(GEX)

GEX 用于近似衡量标的资产每变动 1% 时做市商对冲的敏感度。阅读 references/gamma_squeeze_reference.md 中的公式和两种持仓约定,根据当前现货价格、行权价、到期时间、无风险利率和 IV 计算合约伽马,然后汇总期权链中各合约的 OI x gamma x spot^2。

返回看涨 GEX、看跌 GEX、SqueezeMetrics 风格的净 GEX、总对冲压力、看涨/看跌 OI 比率、中位数近 ATM IV、分析的到期日,以及 GEX 集中度最高的行权价和到期日。明确说明符号约定;仅凭公开 OI 不要推断实际做市商持仓。

解读输出:

  • net_gex_squeezemetrics_$ 高度为负 → 做市商做空伽马;上涨时,他们的对冲买入会放大涨势。这是典型的伽马挤压助燃因素。
  • 集中在单个近月行权价(例如,下个月的看涨期权 OI 大量集中于一个行权价)→ 挤压脆弱且集中。当该行权价到期或现货价格越过该行权价时,伽马会迅速衰减。
  • ATM IV 显著高于近期均值(例如,78%,而通常为 30–40%)→ 市场预计大幅波动仍将持续;单是期权溢价衰减,在数日内就会带来一定的回归压力。
  • 看涨/看跌 OI 比率 > 2.5 → 持仓偏向看涨,与看涨伽马挤压的情形一致。
E3:比较结构性买入压力与实际成交量

用以下公式估算做市商份额的上限:

Implied dealer-driven dollars = abs(GEX per 1% move) x abs(ETF return in percentage points)
Dealer share of volume = implied dealer-driven dollars / (close x volume)

这只是粗略估算——假设每份合约的全部伽马都在价格变动期间沿单一方向进行了对冲。实际对冲是渐进的,而且并非所有做市商的对冲方式都相同。应将其视为上限启发式估算,而非精确数值。务必同时说明所依据的假设。

E4:评估溢价回归时间线

回归分为三个时间尺度(详情参见 references/gamma_squeeze_reference.md 中的 Convergence Timeline 部分):

时间尺度机制检查事项
数小时AP 创建/赎回套利标的市场是否开市?创建份额单位是否受限?买卖价差是否扩大(表明 AP 正在退场)?
数日期权到期 / 伽马衰减主导行权价的期权何时到期?OI 是展期至更远的到期日,还是正在平仓?IV 是否开始收缩?
数周净资金流归一化ETF 是否持续有大量每日资金流入(表明需求超过创建能力)?空头持仓是否增加(可能提供额外的挤压助燃因素)?

对于数小时视角,记录标的市场是否开市,以及创建/赎回是否受限。对于数日视角,计算最大伽马集中度对应期权的到期日还剩多少天,并检查 IV 和 OI 是在衰减还是展期。对于数周视角,在有数据时使用发行方的资金流/创建数据;仅凭 AUM 只能作为粗略的替代指标。

E5:呈现分解结果

按以下顺序组织答案:

  1. 核心数字:ETF 今日涨跌幅、NAV 代理值的涨跌幅,以及超额溢价(以百分点计)。

  2. 分解表格:

    组成部分贡献
    NAV 驱动(持仓 × 权重)+X.X%
    超额溢价(残差)+Y.Y%
    ETF 总涨跌幅+Z.Z%
  3. 做市商对冲量化:

    • 净 GEX(SqueezeMetrics 约定)
    • 推算的做市商当日美元买入额与实际美元成交额
    • 估算的做市商买入压力占比
  4. 风险指标:ATM IV、看涨/看跌 OI 比率、按集中度排序前三的行权价/到期日。

  5. 回归展望:列出数小时/数日/数周的各项机制,并说明每项机制当前的状态。

  6. 注意事项:GEX 估算假设做市商持仓分布均匀;隔夜交易时段的 NAV 代理值会过时;这不是对未来价格的预测。


第 3 步:回复用户

始终包含
  • ETF 名称和代码
  • 市场价格和 NAV,并显示计算过程
  • 明确标注溢价/折价百分比
  • 背景:这种偏离对于该类 ETF 是否正常?
始终注明
  • Yahoo Finance 的 NAV 数据反映的是最新官方 NAV(通常为前一交易日收盘时的数据),并非实时数据
  • 市场价格可能因交易所而有 15 分钟延迟
  • 溢价/折价可能在交易时段内迅速变化——这只是一个快照,并非实时数据流
  • 小幅溢价/折价(小于买卖价差)属于市场微观结构噪声,并非真正的错误定价
  • 不要仅根据溢价/折价建议买入或卖出——只呈现数据,由用户自行决定
格式
  • 比较多个 ETF 时使用 Markdown 表格
  • 显示公式:Premium/Discount = (Market Price - NAV) / NAV x 100
  • 在正文中加粗突出显示关键数字:“交易价格为折价 0.45%”或“处于溢价 1.2%”
  • 根据数值大小,将百分比四舍五入到小数点后 2–4 位

参考文件

  • references/etf_premium_reference.md — 详细公式、按类别划分的基准、常见 ETF 列表,以及推动溢价形成的申购/赎回机制背景
  • references/gamma_squeeze_reference.md — 溢价分解框架、Black-Scholes gamma + GEX 公式(包含 SqueezeMetrics 和客户净多头两种惯例)、收敛时间线框架(小时/天/周)、gamma squeeze 与常规上涨行情的诊断表,以及一个完整示例。运行 Sub-Skill E 之前请先阅读此文件。

阅读参考文件,深入了解 ETF 溢价/折价机制、历史背景及 gamma squeeze 分解方法。

评论

支持 Markdown