etf-premium
Yahoo Finance経由でETFの基準価額(NAV)に対するプレミアム・ディスカウントを算出し、急騰・急落の要因をNAV変動起因と構造的要因(ガンマスクイーズ、ディーラーのヘッジ行動、APアービトラージの機能不全)に分解します。「ETFが純資産価値からかい離した理由」「価格とNAVのギャップ」「ディーラーのガンマエクスポージャー」など、ETFの価格乖離に関する質問全般に対応し、レバレッジ型・インバース型・海外株・債券・コモディティ・暗号資産ETFで特に有効です。
description の原文を見る
> Calculate ETF premium/discount vs NAV via Yahoo Finance, and decompose single-day surges into NAV-driven vs structural components (gamma squeeze, dealer hedging, blocked AP arbitrage). Use whenever the user asks about an ETF's premium or discount, NAV comparison, why an ETF diverged from its holdings, or how much of a move is dealer-hedging-driven. Triggers: "ETF premium", "ETF discount", "NAV premium", "is SPY at a premium", "BITO premium", "IBIT premium", "bond ETF discount", "trading above/below NAV", "ETF premium screener", "biggest discount", "compare ETF NAV", "ETF arbitrage", "ETF gamma squeeze", "ETF premium surge", "decompose ETF move", "dealer gamma exposure", "GEX for ETF", "why did this ETF jump", "premium convergence", "AP arbitrage blocked", or any request about the gap between an ETF's price and underlying value. Especially relevant for leveraged, inverse, international, bond, commodity, and crypto ETFs.
SKILL.md 本文
ETF プレミアム/ディスカウント分析スキル
yfinance経由でYahoo Financeのデータを使用して、ETFの市場価格と純資産価値(NAV)の関連するプレミアムまたはディスカウントを計算します。
重要性: ETFの市場価格は基礎資産の価値(NAV)から乖離する可能性があります。プレミアムで購入する場合、資産相対的に割高になります。ディスカウントで購入する場合、割安になります。この乖離は流動性の高い米国株式ETFではわずかですが、債券ETF、国際ETF、レバレッジ/逆相関商品、および暗号資産ETF、特に市場ストレス時には大きくなる可能性があります。
重要: 研究および教育目的のみです。財務アドバイスではありません。yfinanceはYahoo, Inc.と提携していません。
ステップ 1: 依存関係の確認
現在の環境ステータス:
!`python3 -c "import yfinance, pandas, numpy; print(f'yfinance={yfinance.__version__} pandas={pandas.__version__} numpy={numpy.__version__}')" 2>/dev/null || echo "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プレミアム分析」 |
| 急激なプレミアム上昇/ガンマスクイーズ | サブスキルE: プレミアム急増分解 | 「なぜKWEBが本日13%跳ねたのか」、「このETFラリーはガンマ駆動か」、「本日のETFムーブを分解」、「SOXLのディーラーGEX」、「プレミアムはいつ収束するか」 |
デフォルト値
| パラメータ | デフォルト |
|---|---|
| データソース | yfinance navPrice フィールド |
| 価格フィールド | regularMarketPrice(previousClose にフォールバック) |
| スクリーナーユニバース | カテゴリ別の一般的なETFリスト(サブスキルCを参照) |
サブスキルA: 単一ETFスナップショット
目標: 1つのETFの現在のプレミアム/ディスカウントをコンテキスト付きで表示し、類似ETFとのピア比較を追加して、どのように競争相手と比較されるかを示します。
A1: 取得と計算
import yfinance as yf
# カテゴリ別ピアグループ — ターゲットETFを最も近いピアと自動的に比較するために使用
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
# これが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
# 追加コンテキスト
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 = 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のスナップショットを計算した後、そのcategoryを検索し、同じカテゴリのピアのプレミアムデータを取得します。これにより、ユーザーはプレミアムがETF固有か市場全体かについて即座にコンテキストが得られます。
def get_peer_premiums(target_ticker, target_category):
"""同じカテゴリのピアのプレミアム/ディスカウントを取得します。"""
peers = CATEGORY_PEERS.get(target_category, [])
# ピアからターゲット自身を削除
peers = [p for p in peers if p.upper() != target_ticker.upper()]
if not peers:
return []
peer_data = []
for sym in peers:
try:
t = yf.Ticker(sym)
info = t.info
p = info.get("regularMarketPrice") or info.get("previousClose")
n = info.get("navPrice")
if p and n and n > 0:
prem = (p - n) / n * 100
peer_data.append({
"ticker": sym,
"name": info.get("shortName", ""),
"price": round(p, 2),
"nav": round(n, 2),
"premium_pct": round(prem, 4),
"expense_ratio": info.get("netExpenseRatio"),
})
except Exception:
pass
return peer_data
メインのスナップショット後に小さなテーブルとしてピア比較を提示します。これにより、ユーザーはプレミアムがその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%のディスカウントが発生
- 国際/EM (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のユニバースをスキャンして、最大のプレミアムまたはディスカウントを持つものを検索します。
C1: ユニバースを定義してスキャン
このデフォルトユニバースをカテゴリ別に使用します。ユーザーは代わりに独自のリストを提供できます。
DEFAULT_ETF_UNIVERSE = {
"US Equity": ["SPY", "QQQ", "IVV", "VOO", "VTI", "DIA", "IWM", "ARKK"],
"Bond": ["AGG", "BND", "TLT", "HYG", "LQD", "VCIT", "VCSH", "BNDX", "EMB", "JNK", "MUB", "TIP"],
"International": ["EFA", "EEM", "VWO", "IEMG", "KWEB", "FXI", "INDA", "VEA", "EWZ", "EWJ"],
"Commodity": ["GLD", "SLV", "USO", "UNG", "DBC", "IAU", "PDBC", "GSG"],
"Crypto": ["IBIT", "BITO", "FBTC", "ETHA", "ARKB", "GBTC"],
"Leveraged/Inverse": ["TQQQ", "SQQQ", "SPXU", "UPRO", "JNUG", "JDST", "SOXL", "SOXS"],
"Sector": ["XLF", "XLE", "XLK", "XLV", "XLI", "XLP", "XLU", "XLRE", "XLC", "XLB", "XLY"],
"Sector - Semis/Tech": ["SOXX", "SMH", "IGV", "XSD"],
"Sector - Healthcare": ["XBI", "IBB", "IHI"],
"Thematic": ["ARKW", "ARKG", "HACK", "CLOU", "WCLD", "BUG", "BOTZ", "LIT", "ICLN", "TAN"],
"Income": ["JEPI", "JEPQ", "SCHD", "VYM", "DVY", "DIVO", "HDV", "QYLD"],
}
import yfinance as yf
import pandas as pd
def screen_etf_premiums(universe=None, min_abs_premium=0.0):
if universe is None:
universe = DEFAULT_ETF_UNIVERSE
all_tickers = []
for category, tickers in universe.items():
for sym in tickers:
all_tickers.append((sym, category))
rows = []
for sym, category_label in all_tickers:
try:
t = yf.Ticker(sym)
info = t.info
price = info.get("regularMarketPrice") or info.get("previousClose")
nav = info.get("navPrice")
if price and nav and nav > 0:
prem = (price - nav) / nav * 100
if abs(prem) >= min_abs_premium:
rows.append({
"ticker": sym,
"name": info.get("shortName", ""),
"category": category_label,
"price": round(price, 2),
"nav": round(nav, 2),
"premium_pct": round(prem, 4),
"total_assets_B": round(info.get("totalAssets", 0) / 1e9, 2),
"expense_ratio": info.get("netExpenseRatio"),
})
except Exception:
pass
df = pd.DataFrame(rows)
if not df.empty:
df = df.sort_values("premium_pct", ascending=True)
return df
C2: 結果を提示
プレミアム別にソートしたランク付けテーブルを表示します(最も割安なものが最初)。リストが長い場合はカテゴリ別にグループ化します。以下を呼び出します:
- トップ5の最も深いディスカウント — 購入機会の可能性(またはストレスの兆し)
- トップ5の最も高いプレミアム — 割高リスク
- カテゴリパターン — すべての債券ETFがディスカウントにあるか? すべての暗号ETFがプレミアムにあるか?
注: このスクリーナーは1つずつティッカーを取得するため時間がかかります。大規模なユニバース(60以上のETF)では、1-2分かかる可能性があることをユーザーに警告します。
サブスキルD: プレミアム深掘り
目標: プレミアム/ディスカウントデータを追加コンテキストと組み合わせて、ユーザーがなぜ プレミアムが存在するのか、そしてそれが永続する可能性があるかどうかを理解するのを支援します。
D1: 包括的なデータを収集
サブスキルAスナップショットを実行し、次を追加します:
import yfinance as yf
import numpy as np
def premium_deep_dive(ticker_symbol):
ticker = yf.Ticker(ticker_symbol)
info = ticker.info
price = info.get("regularMarketPrice") or info.get("previousClose")
nav = info.get("navPrice")
if not price or not nav or nav <= 0:
return {"error": "NAV data not available"}
premium_pct = (price - nav) / nav * 100
# ボラティリティコンテキストの過去価格データ
hist = ticker.history(period="3mo")
if not hist.empty:
returns = hist["Close"].pct_change().dropna()
daily_vol = returns.std()
annualized_vol = daily_vol * np.sqrt(252)
avg_volume = hist["Volume"].mean()
dollar_volume = (hist["Close"] * hist["Volume"]).mean()
# 価格レンジコンテキスト
high_3m = hist["Close"].max()
low_3m = hist["Close"].min()
pct_from_high = (price - high_3m) / high_3m * 100
else:
daily_vol = annualized_vol = avg_volume = dollar_volume = None
high_3m = low_3m = pct_from_high = None
result = {
"ticker": ticker_symbol,
"name": info.get("longName", ""),
"price": round(price, 4),
"nav": round(nav, 4),
"premium_pct": round(premium_pct, 4),
"category": info.get("category", "N/A"),
"fund_family": info.get("fundFamily", "N/A"),
"total_assets": info.get("totalAssets"),
"expense_ratio": info.get("netExpenseRatio"),
"yield_pct": info.get("yield"),
"ytd_return": info.get("ytdReturn"),
"beta_3y": info.get("beta3Year"),
"annualized_vol": round(annualized_vol * 100, 2) if annualized_vol else None,
"avg_daily_dollar_volume": round(dollar_volume, 0) if dollar_volume else None,
"pct_from_3m_high": round(pct_from_high, 2) if pct_from_high else None,
}
# ビッドアスク・スプレッド
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)
result["premium_exceeds_spread"] = abs(premium_pct) > spread_pct
return result
D2: なぜ を説明
データを収集した後、このディアグノスティックフレームワークを使用してプレミアム/ディスカウントを説明します:
プレミアムの一般的な原因:
- 需要急増 — 承認された参加者が新規株式を作成できるより多くの買い手(新規/ホットETF、例えば暗号でよくある)
- タイムゾーン不一致 — 基礎市場が閉場している際の国際ETF取引。価格は予想されるムーブを反映
- 作成メカニズムのボトルネック — 承認された参加者が新規株式作成に制約に直面している場合
- センチメントプレミアム — リテール需要がハイプサイクル時に価格を公正価値以上に押し上げ
ディスカウントの一般的な原因:
- 流動性ストレス — 売却局面では、債券とクレジットETFは基礎債券がETF自体よりも価格設定/取引が難しいため、しばしばディスカウントで取引
- 償還プレッシャー — 大規模な流出だが承認された参加者の応答が遅い
- 陳腐化NAV — 公式NAVがアフターアワーズのニュースやイベントを反映しない可能性
- 構造的問題 — 先物ベースのETF(USO、UNG)のコンタンゴ
プレミアムが永続する可能性があるか?
- 流動性の高い米国株式ETF: いいえ — 裁定取引は数分以内に乖離を是正
- 債券ETFがストレス時: ディスカウントは数日または数週間持続する可能性
- 暗号ETF: プレミアムはファンドが成熟し、APがより活発になるにつれて狭まる傾向
- 国際ETF: 基礎市場が開場するにつれて日次にリセット
サブスキルE: プレミアム急増分解(ガンマスクイーズ分析)
目標: ETFが基礎保有資産から乖離する劇的な日中ムーブを経験した場合、ムーブを(1)基礎NAV駆動成分と(2)「過度なプレミアム」に分解します。後者は構造的力(最も一般的にはオプションディーラーのガンマヘッジング、AP裁定取引の破綻、またはセンチメント急増)により駆動される。その後、プレミアムがどのくらい収束する可能性があるかを評価します。
このサブスキルはユーザーが以下について報告または質問する場合に適切です:
- ETFが単一セッションで5%以上移動した
- ETFとその指定された基礎商品の乖離(例: 「MSTRが13%上昇したがBTCは3%上昇しただけ」)
- ETFまたは単一名のガンマスクイーズの疑い
- ディーラーヘッジングがムーブを増幅しているかどうか
サブスキルE2を実行する前に、references/gamma_squeeze_reference.md を読んで、GEX公式の完全な導出、ディーラーポジショニング慣行、および実行例を理解してください。
E1: 本日のムーブをNAV駆動対過度なプレミアムに分解
静的なnavPriceフィールドは最も最近のエンドオブデイNAVのみを提供します — 本日の ムーブのどの程度がNAV駆動かを判断することはできません。代わりに、保有資産のリターンからNAVリターンを推定:
import yfinance as yf
import pandas as pd
import numpy as np
def decompose_etf_move(ticker_symbol, holdings_weights=None, window="2d"):
"""
ETFの最新の日次ムーブをNAV駆動対過度なプレミアムに分解します。
holdings_weights: dict like {"MU": 0.20, "005930.KS": 0.22, "000660.KS": 0.27, ...}
Noneの場合、yfinanceのfunds_dataを介した取得を試みます;
利用不可のETFの場合、ユーザー提供の重みにフォールバック。
"""
etf = yf.Ticker(ticker_symbol)
info = etf.info
# 最新セッション上でのETFリターン
etf_hist = etf.history(period=window, auto_adjust=False)
if len(etf_hist) < 2:
return {"error": "Not enough history"}
etf_close_today = etf_hist["Close"].iloc[-1]
etf_close_prev = etf_hist["Close"].iloc[-2]
etf_return_pct = (etf_close_today / etf_close_prev - 1) * 100
# 提供されていない場合、保有資産を自動取得してみる
if holdings_weights is None:
try:
top_holdings = etf.funds_data.top_holdings # DataFrame
holdings_weights = dict(zip(top_holdings.index, top_holdings["Holding Percent"]))
except Exception:
holdings_weights = {}
if not holdings_weights:
return {
"error": "Holdings weights unavailable — supply manually via holdings_weights={'TICKER': weight, ...}",
"etf_return_pct": round(etf_return_pct, 4),
}
# 基礎保有資産の加重リターン(NAVムーブのプロキシ)
weighted_return = 0.0
coverage = 0.0
holding_returns = {}
for sym, w in holdings_weights.items():
try:
h = yf.Ticker(sym).history(period=window, auto_adjust=False)
if len(h) >= 2:
r = (h["Close"].iloc[-1] / h["Close"].iloc[-2] - 1) * 100
holding_returns[sym] = round(r, 4)
weighted_return += w * r
coverage += w
except Exception:
pass
# 部分的な保有でも合理的なNAVプロキシを提供するようにカバレッジに正規化
nav_return_proxy = weighted_return / coverage if coverage > 0 else None
excess_premium_pct = (
etf_return_pct - nav_return_proxy if nav_return_proxy is not None else None
)
return {
"ticker": ticker_symbol,
"etf_return_pct": round(etf_return_pct, 4),
"nav_return_proxy_pct": round(nav_return_proxy, 4) if nav_return_proxy else None,
"excess_premium_pct": round(excess_premium_pct, 4) if excess_premium_pct else None,
"holdings_coverage_pct": round(coverage * 100, 2),
"holding_returns": holding_returns,
"interpretation": (
"Most of the move is NAV-driven — limited structural component"
if excess_premium_pct is not None and abs(excess_premium_pct) < 1
else "Significant excess premium — investigate dealer hedging, AP bottlenecks, or sentiment"
if excess_premium_pct is not None
else "Cannot conclude without holdings data"
),
}
注意: 基礎商品が閉場セッションで取引される国際ETF(例: 米国営業時間中のアジア保有資産)の場合、ADRや先物のような米国上場プロキシを使用する必要があります。どちらも利用できない場合は、NAVプロキシが古くなることをユーザーに通知します。
E2: オプションチェーンからディーラーガンマ露出(GEX)を計算
GEXは基礎商品の1%ムーブあたりディーラーが実行する必要があるヘッジ購入/販売の量を定量化します。コール側の大規模な負のGEX蓄積は、ラリー中のガンマスクイーズが進行中であることを示す古典的な指標です。
import numpy as np
from datetime import datetime, timezone
from math import log, sqrt, exp, pi
def _norm_pdf(x):
return exp(-0.5 * x * x) / sqrt(2 * pi)
def _bsm_gamma(S, K, T, r, sigma):
"""ブラック・ショールズ ガンマ. 退化入力で0を返す。"""
if S <= 0 or K <= 0 or T <= 0 or sigma <= 0:
return 0.0
d1 = (log(S / K) + (r + 0.5 * sigma * sigma) * T) / (sigma * sqrt(T))
return _norm_pdf(d1) / (S * sigma * sqrt(T))
def compute_gex(ticker_symbol, risk_free_rate=0.045, max_expirations=8):
"""
グロスおよびネットディーラーガンマ露出を計算します。
慣行:
- コントラクト当たり、1%ムーブあたりのドルガンマ = OI * 100 * gamma * spot * (spot * 0.01)
= OI * gamma * spot^2 (multiplier=100で)
- SqueezeMetrics慣行(ディーラーがコールをショート、プットをロング、と仮定):
net_gex = call_gamma_$ - put_gamma_$
正のnet_gex = 安定化(ディーラーはラリー時に売却、下落時に購入)
負のnet_gex = 不安定化(ディーラーはラリー時に購入、下落時に売却 → スクイーズ)
- 「顧客ネットロング全てもの」慣行(ディーラーは両方をショート):
gross_hedge = call_gamma_$ + put_gamma_$
これは最大ヘッジプレッシャー仮定です。
"""
t = yf.Ticker(ticker_symbol)
info = t.info
spot = info.get("regularMarketPrice") or info.get("previousClose")
if not spot:
return {"error": "No spot price"}
expirations = t.options[:max_expirations]
if not expirations:
return {"error": "No options chain available"}
now = datetime.now(timezone.utc)
rows = []
for exp_str in expirations:
try:
chain = t.option_chain(exp_str)
except Exception:
continue
exp_date = datetime.strptime(exp_str, "%Y-%m-%d").replace(tzinfo=timezone.utc)
T = max((exp_date - now).total_seconds() / (365.25 * 86400), 1e-6)
for side, df in [("call", chain.calls), ("put", chain.puts)]:
for _, row in df.iterrows():
K = row.get("strike")
iv = row.get("impliedVolatility")
oi = row.get("openInterest", 0) or 0
if not K or not iv or oi <= 0:
continue
gamma = _bsm_gamma(spot, K, T, risk_free_rate, iv)
# 1% スポットムーブあたりのドル値:
gamma_dollars_per_1pct = oi * gamma * spot * spot
rows.append({
"expiration": exp_str,
"side": side,
"strike": K,
"iv": iv,
"oi": oi,
"gamma": gamma,
"gamma_$_per_1pct": gamma_dollars_per_1pct,
})
if not rows:
return {"error": "No usable contracts"}
df = pd.DataFrame(rows)
call_gex = df[df["side"] == "call"]["gamma_$_per_1pct"].sum()
put_gex = df[df["side"] == "put"]["gamma_$_per_1pct"].sum()
# 最上位の集中度: どの満期とストライクが支配的か
top_strikes = (
df.groupby(["expiration", "strike", "side"])["gamma_$_per_1pct"]
.sum()
.sort_values(ascending=False)
.head(10)
.reset_index()
)
total_call_oi = df[df["side"] == "call"]["oi"].sum()
total_put_oi = df[df["side"] == "put"]["oi"].sum()
cp_ratio = total_call_oi / total_put_oi if total_put_oi > 0 else None
# ニアターム ATM IVを単一の代表数として引き出す
df["moneyness"] = abs(df["strike"] / spot - 1)
near_atm = df.sort_values("moneyness").head(20)
atm_iv_pct = near_atm["iv"].median() * 100 if len(near_atm) else None
return {
"ticker": ticker_symbol,
"spot": spot,
"call_gex_per_1pct_$": call_gex,
"put_gex_per_1pct_$": put_gex,
"net_gex_squeezemetrics_$": call_gex - put_gex,
"gross_hedge_pressure_$": call_gex + put_gex,
"total_call_oi": int(total_call_oi),
"total_put_oi": int(total_put_oi),
"call_put_oi_ratio": round(cp_ratio, 2) if cp_ratio else None,
"atm_iv_pct": round(atm_iv_pct, 2) if atm_iv_pct else None,
"expirations_analyzed": len(expirations),
"top_concentrations": top_strikes,
}
出力を解釈:
net_gex_squeezemetrics_$非常に負 → ディーラーはガンマをショート; ラリー時、ディーラーのヘッジ購入により拡大します。古典的なガンマスクイーズの燃料。- 単一のニアデート・ストライクでの集中 (例: 記事の「6月45ドル・コール」) → スクイーズは脆弱で集中。そのストライクが満期になるか、スポットがそれを超えて移動した場合、ガンマは急速に減衰。
- ATM IVが最近の平均をはるかに上回る (記事の例: 78対従来~30-40) → 市場は継続的な大きなムーブを価格設定しています; オプション・プレミアム減衰だけで数日の収束圧力が生じます。
- コール/プット OI比 > 2.5 → コール重視のポジショニング。強気のガンマスクイーズ設定と一貫性があります。
E3: 構造的買い圧力を実際のボリュームと比較
記事の最も具体的な主張は、本日の買い物の~35%がディーラー駆動であったことです。この比較を再現:
def estimate_dealer_share_of_volume(ticker_symbol, gex_per_1pct_dollars, etf_return_pct):
"""
暗黙のディーラー駆動の$購入 = |gex_per_1pct| * |etf_return_pct|
実際のドルボリュームと比較します。
"""
t = yf.Ticker(ticker_symbol)
hist = t.history(period="2d", auto_adjust=False)
if hist.empty:
return None
today = hist.iloc[-1]
actual_dollar_volume = today["Close"] * today["Volume"]
implied_dealer_buying = abs(gex_per_1pct_dollars) * abs(etf_return_pct)
share = implied_dealer_buying / actual_dollar_volume if actual_dollar_volume > 0 else None
return {
"actual_dollar_volume_$": round(actual_dollar_volume, 0),
"implied_dealer_buying_$": round(implied_dealer_buying, 0),
"dealer_share_of_volume_pct": round(share * 100, 2) if share else None,
}
これはおおよその推定です — 単一方向のムーブ中にすべてのコントラクトのフルガンマがヘッジされたことを前提としています。実際のヘッジングは段階的で、すべてのディーラーが同じようにヘッジするわけではありません。上界のヒューリスティックスとして扱い、正確な図ではありません。常に仮定と一緒に提示します。
E4: プレミアム収束タイムラインを評価
記事の3層の収束フレームワーク:
| 時間スケール | メカニズム | 確認すること |
|---|---|---|
| 時間 | AP作成/償還裁定取引 | 基礎市場は開場しているか? 作成ユニットは制限されているか? ビッドアスクスプレッドが拡大しているか(AP退場を示唆)? |
| 日数 | オプション満期 / ガンマ減衰 | 支配的ストライクの満期はいつ到来するか? OIが先送りされているか、それとも閉鎖されているか? IVが圧縮を開始しているか? |
| 週 | ネットフロー正常化 | ETFは大規模な日次インフローを受けている(供給能力を上回る需要を示唆)か? シェアレンディングが構築されているか(潜在的な追加スクイーズ燃料)? |
def assess_convergence(ticker_symbol, top_concentrations_df):
"""定性的な収束シグナルのdictを返します。"""
t = yf.Ticker(ticker_symbol)
info = t.info
# 1. AP裁定取引: 基礎の市場営業時間
region = info.get("region") or info.get("market") or "unknown"
underlying_session_note = (
"International — check whether underlying market overlaps US trading hours; "
"AP arbitrage may be blocked when underlying market is closed"
if "us_market" not in (info.get("market") or "").lower()
else "US-listed underlying — AP arbitrage active during US hours"
)
# 2. オプション満期: ニアデート集中ストライク
if not top_concentrations_df.empty:
next_major_exp = top_concentrations_df.iloc[0]["expiration"]
days_to_exp = (datetime.strptime(next_major_exp, "%Y-%m-%d") - datetime.now()).days
exp_note = f"Largest gamma concentration expires in {days_to_exp} days ({next_major_exp})"
else:
exp_note = "No clear strike concentration"
# 3. フロープロキシ: AUM軌跡(非常におおざっぱ)
aum = info.get("totalAssets")
aum_note = f"Total AUM: ${aum/1e9:.2f}B" if aum else "AUM unavailable"
return {
"ap_arbitrage": underlying_session_note,
"options_window": exp_note,
"flows": aum_note,
}
E5: 分解を提示
この順序で回答を形式化:
-
ヘッドラインナンバー: 本日のETFムーブ、NAVプロキシムーブ、および過度なプレミアム(pp単位)。
-
分解テーブル:
コンポーネント 寄与度 NAV駆動(保有資産×重み) +X.X% 過度なプレミアム(残差) +Y.Y% ETF総ムーブ +Z.Z% -
ディーラーヘッジング定量化:
- ネットGEX(SqueezeMetrics慣行)
- 当日のディーラーは$購入対実際の$ボリュームを暗黙
- 購買圧力のディーラーシェアを推定
-
リスク指標: ATM IV、コール/プット OI比、トップ3ストライク/満期集中度。
-
収束見通し: 時間/日/週メカニズムそれぞれと各メカニズムの現在の状態をリスト。
-
注意事項: GEX推定は均一なディーラーポジショニングを仮定; NAVプロキシはオーバーナイトセッション中に古い; これは将来の価格予測ではありません。
ステップ 3: ユーザーに応答
常に含める
- ETF名とティッカー
- 市場価格 と NAV (計算を示す)
- プレミアム/ディスカウント率 明確にラベル付け
- コンテキスト: この乖離はこのETFカテゴリの正常範囲か?
常に注意
- Yahoo Financeのデータは最新の公式NAVを反映(通常は前営業日のエンドオブデイ) — リアルタイムではない
- 市場価格には取引所に応じて15分の遅延がある可能性
- プレミアム/ディスカウントは営業時間中に急速に変化可能 — これはスナップショット、ライブフィードではない
- 小さいプレミアム/ディスカウント(< ビッドアスク・スプレッド)は市場マイクロストラクチャーノイズで、本当のミスプライシングではない
- プレミアム/ディスカウント単独に基づいて決して売買を推奨しない — データを提示し、ユーザーに判断させる
フォーマット
- マルチETF比較ではマークダウンテーブルを使用
- 公式を表示:
プレミアム/ディスカウント = (市場価格 - NAV) / NAV x 100 - テキストの色指標を使用: 「0.45%ディスカウントで取引」または「1.2%プレミアム」
- 大きさに応じて小数点以下2-4桁に丸める
リファレンスファイル
references/etf_premium_reference.md— 詳細な公式、カテゴリ別の基準、一般的なETFユニバースリスト、作成/償還メカニズムがプレミアムを駆動する方法についての背景references/gamma_squeeze_reference.md— プレミアム分解フレームワーク、ブラック・ショールズ ガンマ + GEX公式(SqueezeMetricsと顧客ネットロング慣行の両方)、収束タイムラインフレームワーク(時間/日/週)、ガンマスクイーズ対ルーチンラリー診断テーブル、および実行例。サブスキルE実行前にこれを読んでください。
ETFプレミアム/ディスカウント力学、歴史的コンテキスト、およびガンマスクイーズ分解方法論の深い技術的詳細についてはリファレンスファイルを読んでください。
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- himself65
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/himself65/finance-skills / ライセンス: MIT
関連スキル
secure-code-guardian
認証・認可の実装、ユーザー入力の保護、OWASP Top 10の脆弱性対策が必要な場合に使用します。bcrypt/argon2によるパスワードハッシング、パラメータ化ステートメントによるSQLインジェクション対策、CORS/CSPヘッダーの設定、Zodによる入力検証、JWTトークンの構築などのカスタムセキュリティ実装に対応します。認証、認可、入力検証、暗号化、OWASP Top 10対策、セッション管理、セキュリティ強化全般で活用できます。ただし、構築済みのOAuth/SSO統合や単独のセキュリティ監査が必要な場合は、より特化したスキルの検討をお勧めします。
claude-authenticity
APIエンドポイントが本物のClaudeによって支えられているか(ラッパーやプロキシ、偽装ではないか)を、claude-verifyプロジェクトを模した9つの重み付きルールベースチェックで検証できます。また、Claudeの正体を上書きしているプロバイダーから注入されたシステムプロンプトも抽出します。完全に自己完結しており、httpx以外の追加パッケージは不要です。Claude APIキーまたはエンドポイントを検証したい場合、サードパーティのClaudeサービスが本物か確認したい場合、APIプロバイダーのClaude正当性を監査したい場合、複数モデルを並行してテストしたい場合、またはプロバイダーが注入したシステムプロンプトを特定したい場合に使用できます。
anth-security-basics
Anthropic Claude APIのセキュリティベストプラクティスを適用し、キー管理、入力値の検証、プロンプトインジェクション対策を実施します。APIキーの保護、Claudeに送信する前のユーザー入力検証、コンテンツセーフティガードレールの実装が必要な場合に活用できます。「anthropic security」「claude api key security」「secure anthropic」「prompt injection defense」といったフレーズでトリガーされます。
x-ray
x-ray.mdプレ監査レポートを生成します。概要、強化された脅威モデル(プロトコルタイプのプロファイリング、Gitの重み付け攻撃面分析、時間軸リスク分析、コンポーザビリティ依存関係マッピング)、不変条件、統合、ドキュメント品質、テスト分析、開発者・Gitの履歴をカバーしています。「x-ray」「audit readiness」「readiness report」「pre-audit report」「prep this protocol」「protocol prep」「summarize this protocol」のキーワードで実行されます。
semgrep
Semgrepスタティック分析スキャンを実行し、カスタム検出ルールを作成します。Semgrepでのコードスキャン、セキュリティ脆弱性の検出、カスタムYAMLルールの作成、または特定のバグパターンの検出が必要な場合に使用します。重要:ユーザーが「バグをスキャンしたい」「コード品質を確認したい」「脆弱性を見つけたい」「スタティック分析」「セキュリティlint」「コード監査」または「コーディング標準を適用したい」と尋ねた場合も、Semgrepという名称を明記していなくても、このスキルを使用してください。Semgrepは30以上の言語に対応したパターンベースのコードスキャンに最適なツールです。
ghost-bits-cast-attack
Java「ゴーストビッツ」/キャストアタック プレイブック(Black Hat Asia 2026)。16ビット文字が8ビットバイトに暗黙的に縮小されるJavaサービスへの攻撃時に使用します。WAF/IDSを回避して、SQLインジェクション、デシリアライゼーション型RCE、ファイルアップロード(Webシェル)、パストトラバーサル、CRLF インジェクション、リクエストスマグリング、SMTPインジェクションを実行できます。Tomcat、Spring、Jetty、Undertow、Vert.x、Jackson、Fastjson、Apache Commons BCEL、Apache HttpClient、Angus Mail、JDK HttpServer、Lettuce、Jodd、XMLWriterに影響し、WAFバイパスにより多くの「パッチ済み」CVEを再度有効化します。