关于 etf-premium
etf-premium 通过 yfinance 使用 Yahoo Finance 数据,分析 ETF 市价相对 NAV 的溢价或折价,支持单只分析、比较、筛选、背景分析及突发行情拆解。
主要功能
- 01计算市价相对 NAV 的溢价或折价
- 02单只 ETF 快照与同类比较
- 03多只 ETF 排名与筛选
- 04结合历史及市场背景分析
- 05拆解 NAV 变动与期权 Gamma
适用场景
- 01查看 ETF 当前相对 NAV 的溢价
- 02比较多只 ETF 的溢折价
- 03筛选折价幅度异常的 ETF
- 04分析 ETF 突然大幅上涨
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ himself65/ finance-skills --skill etf-premium安装后在 Claude Code 中输入/etf-premium确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.mdreferences/etf_premium_reference.mdgamma_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:呈现分解结果
按以下顺序组织答案:
-
核心数字:ETF 今日涨跌幅、NAV 代理值的涨跌幅,以及超额溢价(以百分点计)。
-
分解表格:
组成部分 贡献 NAV 驱动(持仓 × 权重) +X.X% 超额溢价(残差) +Y.Y% ETF 总涨跌幅 +Z.Z% -
做市商对冲量化:
- 净 GEX(SqueezeMetrics 约定)
- 推算的做市商当日美元买入额与实际美元成交额
- 估算的做市商买入压力占比
-
风险指标:ATM IV、看涨/看跌 OI 比率、按集中度排序前三的行权价/到期日。
-
回归展望:列出数小时/数日/数周的各项机制,并说明每项机制当前的状态。
-
注意事项: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 分解方法。