whale_hodl_wave
HODL Wave UTXO age distribution from `whale_hodl_waves`.
Top whale addresses' UTXOs grouped by age bucket. Daily snapshots.
Args:
days: Look-back window (default 30, max 365).
Returns:
rows: list of {snapshot_date, age_bucket, total_btc, utxo_count, address_count}
age_bucket values: '1d', '1w', '1m', '3m', '6m', '1y', '2y', '3y', '5y+'.
Note that bucket strings sort alphabetically, not chronologically,
when displayed naively — apply the BUCKET_ORDER list from _methodology
for proper time-axis ordering.
age_bucket_order: chronological ordering for UI display.
_methodology: documents the methodology-discontinuity at 2026-04-29.
METHODOLOGY DISCONTINUITY (read this before charting time-series):
Snapshots BEFORE 2026-04-29 used a stale `address_metrics_cache`
matview (the cache was 7.6x stale at refresh-day, with 4.7M rows
vs 36M actual). On 2026-04-29 the matview was rebuilt from the
truth-source pipeline (daily `scantxoutset` + spent-tracking).
Effect on the time-series:
- Pre-2026-04-29 totals UNDERSTATED (especially high-balance addresses
and long-dormant UTXOs which had stale balances in the matview).
- From 2026-04-29 onward: corrected, byte-perfect against mempool.space
for top-1164 addresses + miner pool wallets.
Cross-day comparisons across this boundary are NOT meaningful. The
2026-04-29 snapshot is the new baseline.
Plausibility-check on the new baseline:
~340 addresses (filter: 100-100,000 BTC holdings, ≥3 hits, non-exchange)
with 4.5M BTC total UTXOs. Average ~13,200 BTC per address. The
5y+ bucket holds ~17,400 BTC per address — consistent with
Patoshi-era / early-miner / Mt.Gox-era addresses, which is what
we expect for the long-dormant cohort.
whale_sopr
Whale-specific SOPR (Spent Output Profit Ratio) time series.
SOPR > 1.0 = whales spending at profit on average; SOPR < 1.0 = at loss.
Computed from `whale_sopr_daily`.
Args:
days: Look-back window (default 30, max 365). Returns exactly N rows
(window = CURRENT_DATE - (days-1) to today, identical to
whale_exchange_flows / cohort_breakdown).
Returns:
days: echo of normalized parameter.
rows: list of {snapshot_date, sopr, total_txs, profitable_txs,
loss_txs, avg_pnl_pct, realized_profit_usd}, NEWEST FIRST.
_methodology: weighting + reconciliation note.
SOPR vs avg_pnl_pct — different weightings (F16 fix 2026-04-29):
sopr is VOLUME-weighted: sum(spent_value_usd) / sum(cost_basis_usd).
avg_pnl_pct is COUNT-weighted: average of (spent_usd/cost - 1) per TX.
These can diverge: a day with 4 small profitable TXs + 1 large loss
TX may show sopr<1 (volume-dominant loss) but avg_pnl_pct>0 (count-
majority profit). When they disagree, the disagreement itself is
the signal — small whales bullish, large whales bearish (or vice
versa). realized_profit_usd is in absolute dollars (cumulative
sum of realized P&L for the day).
whale_miner_balances
Mining-pool BTC hot-wallet balances over time (15+ tracked pools).
Args:
days: Look-back window (default 30, max 365).
Returns:
days: echo of normalized parameter.
rows: list of {snapshot_date, pool_name, total_btc, address_count},
sorted newest-first then alphabetically by pool_name. Values
with total_btc > MAX_REASONABLE_HOT_WALLET (50,000 BTC) are
filtered out as suspect (mining pools rarely hold >5,000 BTC
in hot-wallet — the rest flows to miners on the same block).
_methodology: pipeline disclosure.
Data-pipeline disclosure:
Snapshots from 2026-04-28 onwards use the truth-source pipeline
(`miner_balance_scanner_pg.py` → daily `scantxoutset` against
Bitcoin-Core full-node, byte-perfect for tracked pool addresses).
Snapshots BEFORE 2026-04-28 used a deprecated electrs-based scanner
that occasionally returned cumulative-payout aggregates rather than
hot-wallet balances (audit-confirmed: AntPool 357k-398k BTC across
2-3 addresses on 25-27 Apr — physically implausible). Those rows
were quarantined / deleted on 2026-04-29.
Sanity cap (50k BTC) is applied at tool-time as a safety net for
any future pipeline regression: hot-wallet balances above that
threshold are dropped from the response with a counter in
`_methodology.suspect_rows_filtered`.
Use this tool to track:
- Net miner accumulation/distribution trend (rising = HODLing,
falling = selling pressure)
- Pool-share-shifts over time (Foundry vs AntPool vs F2Pool)
- Hot-wallet sweep events (sudden drop = miner-payout-day)
whale_frequency_context
How common is a whale-move of this size + flow this week?
Returns the count of similar moves (same volume bucket + same flow type)
in the last 7 days. Used for "Nth similar move this week" framing.
Args:
btc: BTC amount of the hypothetical move.
flow_type: 'to_exchange' / 'from_exchange' / 'wallet_to_wallet' / 'exchange_to_exchange'.
Returns:
Dict with bucket name, count, and ordinal phrase.
whale_my_status
Show your current MCP-server access tier, scopes, and rate limits.
Useful to check what you can call. Returns same data as the OAuth
/oauth/userinfo endpoint plus per-tool documentation.
Bug-fix 2026-04-28 (QA): client_id ist eine OAuth-Application-ID
(= "welche App hat connected"), KEIN Account-Identifier. Wir
annotieren das transparent damit caller den Wert nicht versehentlich
als User-ID interpretieren. tier + authenticated + expires_at_unix
sind die identitäts-relevanten Felder.
whale_btc_price
BTC price time series with optional whale-event overlay.
Returns a list of (timestamp, price) points sampled at the requested
granularity, plus a separate list of whale events ≥500 BTC during the
period for overlay analysis (volume × price impact).
Use this to answer questions like:
- "Was there a price spike when whale X moved Y BTC?"
- "Show BTC price for the last week with whale exits highlighted"
Args:
hours: Lookback window (default 24, max 720 = 30 days for premium,
7 days = 168 hours for free tier).
granularity_minutes: Sampling interval (default 60 = hourly).
include_self_send: Include self_send transactions (exchange
hot-cold sweeps and other internal consolidations). Default
False — these flow-types dominate raw mempool noise (~90%
of ≥500 BTC events) but rarely represent genuine whale
movement. Set True for forensic/research views.
Returns:
dict with:
- prices: list of {bucket, price, volume_usd_24h_rolling}
* bucket: datetime at granularity-minute boundary (UTC)
* price: AVG BTC/USD across rows in this bucket (CoinGecko + alt.me)
* volume_usd_24h_rolling: AVG of the 24h-rolling-volume column from
CoinGecko across rows in this bucket. NOT the volume *during* the
bucket — at every minute CoinGecko reports the trailing 24h
volume. AVG-ing minute-rows in a 1h bucket therefore yields a
running 24h-volume snapshot, not a 1h-flow. Field name kept for
API back-compat; semantically it is "average 24h-volume during
this hour". May be NULL if CoinGecko enrichment was rate-limited
(HTTP 429) for the entire bucket.
- whale_events: list of {datetime, btc, flow_type, txid}
datetime is the BTC timestamp; for confirmed rows = block-time
(sec resolution), for mempool-only rows = arrival microsec.
Filter total_btc >= 500. Hard-cap 200 rows newest-first. If you
want full data without truncation use a smaller `hours` window
or smaller `granularity_minutes`.
whale_top_coins
Snapshot of 16 tracked non-stablecoin coins with price + 24h change.
UNIVERSE: 16 coins selected for whale-relevance (BTC, ETH, SOL, BNB,
XRP, SUI, ADA, DOGE, AVAX, LINK, NEAR, TRX, ATOM, ARB, OP, XLM).
Stablecoins (USDT, USDC, DAI, etc.) are intentionally NOT in this
list because they distort whale-flow analysis (they exist mostly as
on/off-ramp instruments). Their CoinGecko ranks (typically 3, 6, 9,
11, 12 depending on day) appear as gaps in the rank-sorted output —
this is not a bug, it's the filter design. Use a general crypto-data
provider if you need stablecoin coverage.
Args:
symbol: Optional. If given (e.g. 'BTC'), returns that coin's
latest enriched row plus a 24h price sparkline. If omitted,
returns all 16 coins WITHOUT sparklines (snapshot-only).
Returns:
dict with either:
- coins (list) when symbol is None: latest snapshot per coin.
Per-row fields: symbol, name, price (USD, rounded 2 dp),
chg_24h, chg_7d (percent, rounded 2 dp), volume_24h,
market_cap, rank.
- coin (single dict) + sparkline (list of {datetime, price})
when symbol is given. Sparkline is minute-granular raw rows
from crypto_prices over the last 24h (~1440 points, not
24×1h-aggregated). Bug-fix 2026-04-29 (Audit M-1): doc
previously claimed "24×1h prices" — implementation never
aggregated, returns raw minute-cadence.
whale_fear_greed
Fear & Greed Index daily values from alternative.me.
Range 0-100. Empirical alternative.me thresholds (verified against
their own classification labels in our DB):
<25 = Extreme Fear
25-46 = Fear
47-54 = Neutral
55-74 = Greed
>=75 = Extreme Greed
Note: alternative.me's official boundaries are tighter than the
symmetrical 25/45/55/75 documented elsewhere — we report their
actual classification verbatim. Whales are statistically more
active during Fear and Extreme Greed extremes.
Args:
days: Lookback window (default 30, max 365).
Returns:
dict with:
- history: list of daily rows, OLDEST FIRST. Each row:
* day (date string YYYY-MM-DD)
* fear_greed_index (0-100)
* fear_greed_classification (Extreme Fear / Fear / Neutral /
Greed / Extreme Greed — exact alternative.me string)
- current: convenience pointer to history[-1] (latest day).
Identical to the last history row — exposed for callers that
only need today's value without looping.
- days: echo of normalized lookback parameter.
- _note: source attribution.
Note: when days=N, you get N+1 rows in history (today + N previous
days). DISTINCT ON keeps one row per day, sampled daily.
whale_dominance
BTC and ETH dominance (% of total market cap) time series.
BTC dominance trending up → flight to safety; trending down → altseason.
ETH dominance is a leading indicator of altcoin appetite.
Args:
days: Lookback window (default 30, max 365).
Returns:
dict with:
- history: list of daily rows, OLDEST FIRST. Each row:
* day (date string YYYY-MM-DD)
* btc_d (BTC dominance %, rounded to 2 dp)
* eth_d (ETH dominance %, rounded to 2 dp)
* total_mcap (total crypto market cap in USD)
- current: convenience pointer to history[-1] (today). Same row.
- days: echo of normalized parameter.
- _note: methodology + interpretation.
Note: when days=N, history has exactly N rows (CURRENT_DATE-(N-1) ..
today). Doc-fix 2026-05-02 per external audit (was incorrectly stated
as "N+1 rows" — the F18 cron-fix in 2026-04-29 normalized this).
whale_btc_indicators
Current state of two macro Bitcoin top/cycle indicators.
- Pi Cycle Top: 111-DMA crossing above 350-DMA×2 has historically
marked 4 cycle tops within ±3 days.
- Stock-to-Flow ratio: current S2F vs model price (post-halving 2024
model is ~$110k-$200k for cycle).
Returns: dict with two sub-objects:
pi_cycle:
date (most-recent BTC daily date, YYYY-MM-DD)
btc_price (latest BTC price tick — same in both objects)
daily_avg_today (today's BTC AVG used in the MA calculation;
differs from btc_price when significant
intraday movement happened)
ma111 (111-day moving average)
ma350x2 (350-day moving average × 2)
distance_pct ((ma111 - ma350x2) / ma350x2 × 100)
signal ('TOP_TRIGGERED' | 'approaching' | 'neutral')
interpretation (descriptive note on historical behaviour)
stock_to_flow:
supply_estimate (BTC, current circulating estimate)
annual_flow_btc (BTC issued per year post-2024 halving)
s2f_ratio (supply / annual_flow)
model_price_usd (Plan B model fair-value, USD)
current_price_usd (latest BTC tick — identical to pi_cycle.btc_price)
deviation_pct ((current / model - 1) × 100)
interpretation (descriptive note)
Note: there is intentionally NO `last_top_signal` field. Historical
Pi Cycle crossings (2013, 2017, 2019, 2021) are referenced narratively
in the `interpretation` string instead of as structured data — the
exact "last crossing" timestamp depends on definition (first close
above? first day of crossing-band?) and we don't want to expose a
single fragile number.
btc_price vs daily_avg_today: the former is the live tick (use this
for "current price"). The latter is the AVG used in MA computation
(use this only if reproducing the MA from raw `crypto_prices`).
whale_address_mvrv
Per-address MVRV-style cost-basis breakdown.
Computes for the address:
- balance_btc (cross-validated against address_metrics_cache)
- realized_cost_usd (sum of inbound BTC × historical BTC price at receipt)
- market_value_usd (balance × current BTC price)
- mvrv_ratio (market / realized)
MVRV > 1 = unrealized profit. < 1 = unrealized loss. > 3 = historically
a sell zone for individual whales. Per-address MVRV is unique to this
MCP server — Glassnode/CryptoQuant only expose network-aggregate MVRV.
Data-quality safety: For high-rotation addresses where our spent-
tracking backfill (Phase 1, ongoing) is incomplete, the unspent-UTXO
sum can exceed the true balance. We cross-check against
`address_metrics_cache.estimated_holdings_btc`, and if the unspent
sum exceeds 1.2× cache estimate, we use the cache as ground truth
and scale realized_cost proportionally (FIFO/LIFO-agnostic). The
response includes a `_data_quality` flag explaining what happened.
Args:
address: Bitcoin address.
Returns:
dict with balance_btc, realized_cost_usd, market_value_usd,
mvrv_ratio, unrealized_pnl_usd, utxo_count, current_btc_price,
_data_quality, _methodology.
whale_address_cluster
Show all addresses controlled by the same entity (canonical Meiklejohn cluster).
Uses common-input-heuristic (Meiklejohn et al. 2013, cited 1900+):
addresses that appear together as inputs in any whale transaction are
inferred to be controlled by the same wallet/entity. Cluster table
rebuilt from 1.68M multi-input whale transactions.
For "pure-receiver" addresses with no outbound history (e.g. cold-vault
single-key custody), the canonical cluster_size = 1. In that case the
tool falls back to co-occurrence-on-outputs analysis, marked clearly
in the response.
Args:
address: Bitcoin address.
limit: Max member addresses to return (default 50, max 500).
Returns:
dict with cluster_id, cluster_size, cluster_total_btc, members[],
and either method='canonical' or 'cooccurrence' (with explanation).
whale_top_holders
Top whale addresses by current BTC holdings (UTXO truth-source).
Primary source: `address_balance_truth` — refreshed daily via Bitcoin
Core `scantxoutset`, which is the canonical UTXO set walk. Returns
cryptographic on-chain balance (matches mempool.space byte-for-byte).
Fallback for addresses NOT in the truth table: legacy estimate from
`address_metrics_cache` (whale-event-volume balance, less accurate but
covers the long tail). Each row is annotated with `balance_source`
so callers can decide which to trust.
Args:
limit: Number of addresses (default 20, max 200).
min_btc: Minimum balance (default 100).
include_exchanges: Include exchange-tagged addresses (default False).
Note: exchange holdings are split across many hot wallets, so
ranking individual exchange addresses underestimates the true
exchange position. Use whale_exchange_flows for total flows.
confidence: 'high' | 'medium' | 'low' | 'any' (default 'any' since
truth-source has its own freshness signal in last_scanned_at).
Returns:
dict with `holders` list (ranked by truth balance, then estimate),
`filters` (echo of input), `_summary`, `_methodology`, `_glossary`.
Each holder row contains:
- address, is_exchange, balance_btc, balance_source
('utxo_truth' | 'event_estimate_DEPRECATED'), utxo_count,
last_scanned_at, freshness_age_hours
- entity_label: internal label (NULL for most top-holders since
they're identified primarily via external_labels — see below)
- external_labels: comma-separated string aggregating all known
external attributions (bitinfocharts, walletexplorer, arkham,
manual). For TOP holders, this is the primary label-bearing
field — internal entity_label is often NULL because internal
clustering depends on our seed-set which doesn't cover the
largest known cold wallets. (Doc-fix 2026-05-02 per external
audit — was previously implied entity_label was the primary
label source.)
- whale_move_count, tx_frequency_hours, last_whale_at,
turnover_btc_365d, volume_index, volume_index_calibrated,
volume_index_calibrated_status (active_rank | excluded_label
| no_index_data | deranked | queued)
NOTE: last_whale_at here is read from address_metrics_cache
matview (refreshed daily 03:00 UTC). For real-time accuracy
use whale_lookup, which COALESCEs the matview value with a
live MAX(timestamp) FROM whale_trades query — top_holders
does not do this per-row to keep the response fast for
limit=200. Cache lag can be up to 24 hours. Doc-fix
2026-05-02 per external audit (was undocumented).
- legacy_estimate_btc, legacy_confidence, _balance_warning
(only present when balance_source='event_estimate_DEPRECATED')
whale_dormant_wakeups
Find whale addresses that became active after a real dormancy gap.
Bug-fix 2026-04-28 (QA): previous version used
`last_seen - first_seen` as `dormancy_years` — that's the *lifespan*
of the address, not the inactivity gap. A 6-year-old address with
24 TXs across 6 years would falsely qualify as a "wake-up" even
though it was never actually dormant.
Now: `dormancy_gap_years` = time between the most recent whale-tier
TX (`last_seen`) and the previous whale-tier TX (`prior_seen`).
That's the actual quiet period that was just broken. Addresses
with only one whale-tier TX ever are excluded (no prior to measure
against).
B3 fix 2026-04-29 (QA): `prior_seen` is now sourced from
`whale_trades` (whale-tier only) instead of `whale_outputs` (all
outputs to the address). Previously sub-whale outputs in the address
history could cause `prior_seen` to fall BEFORE `first_seen` and
falsely shrink the dormancy gap. With the fix, both `first_seen`
and `prior_seen` are at the same aggregation level (whale-tier
only) and cross-comparison is meaningful.
Args:
days: Recent activity window — `last_seen` must be within (default 30).
min_age_years: Minimum **dormancy gap** (default 5). The address
must have been silent for at least this long before its
most-recent TX.
limit: Max results.
Returns:
Dict with `items` (list of {address, first_seen, last_seen,
prior_seen, dormancy_gap_years, lifespan_years, tx_count,
total_btc, entity_label, is_exchange, volume_index_calibrated}),
`filters` (echo of normalized inputs), and `_methodology`.
Wrapper shape consistent with whale_top_holders / whale_recent
(Audit Befund #1, 2026-04-28 — fixes streaming-list-bug).
`dormancy_gap_years` is the wake-up signal; `lifespan_years` is
exposed for transparency (= legacy metric).
whale_eth_recent
Recent Ethereum whale transactions.
Same pattern as whale_recent but for ETH. Glassnode's MCP is BTC-only;
we cover both chains.
Args:
min_eth: Minimum ETH amount (default 100).
hours: Lookback (default 24, max 720).
limit: Max rows (default 20, max 100).
Returns:
Dict with `items` (list of {txid, timestamp, eth_amount, amount_usd,
from_address, to_address, whale_class, flow_type, from_entity_name,
from_entity_type, to_entity_name, to_entity_type, score,
contract_address, token_symbol, block_number}), `filters` (echo),
and `_methodology`.
Wrapper shape consistent with whale_recent / whale_top_holders
(Audit Befund #1, 2026-04-28 — fixes streaming-list-bug).
whale_class is computed from eth_amount with these thresholds:
- small: < 1,000 ETH
- standard: 1,000–4,999 ETH
- major: 5,000–9,999 ETH
- mega: ≥ 10,000 ETH
(Doc-fix 2026-05-02 per external audit — thresholds were only in
response._methodology, not in this docstring.)
Note: when calling with default min_eth=100, all returned items will
be `small` (since 100 ≤ x < 1000). To see standard/major/mega class
distribution, use min_eth=1000+ or call whale_eth_cohort_breakdown.
flow_type is derived live from from_entity_type/to_entity_type
(self-audit 2026-04-29 #35 — BTC-parity).
whale_eth_cohort_breakdown
ETH whale movements grouped by amount bucket × flow_type.
Audit M-7 fix 2026-04-29: previously bucketed by `whale_type` which
was always "standard" in the DB (Importer never set varied values) —
useless for distribution analysis. Now derives `flow_type` live from
`from_entity_type`/`to_entity_type`, giving BTC-parity + ETH-specific
buckets (defi, bridge, staking).
Args:
days: Lookback (default 7, max 365).
include_self_send: Include self_send transactions (default False).
Mirrors whale_cohort_breakdown semantics.
Returns:
Dict with `rows` (cohort counts per flow_type×bucket), `filters`
(echo of normalized inputs), and `_methodology` (descriptive note).
Wrapper shape consistent with whale_cohort_breakdown.
Flow types (ETH-derived):
- exchange_internal: from=to AND either side tagged exchange
- exchange_to_exchange: both sides tagged exchange (different addresses)
- from_exchange: sender exchange, recipient elsewhere
- to_exchange: recipient exchange, sender elsewhere
- defi: recipient is DeFi-protocol contract
- bridge: recipient is cross-chain bridge contract
- staking: recipient is staking contract
- self_send: sender=recipient AND neither side tagged
- wallet_to_wallet: neither side tagged, different addresses
whale_eth_address_cluster
ETH address → cluster_id with confidence-tier transparency.
Per ADR-001 (Hybrid Layer-1 + Layer-3): combines external entity-labels
(confidence 0.95) with safety-filtered transaction-graph proximity
(confidence 0.40). UI consumers MUST render the confidence — L3 matches
are behavioral clusters, not provenance-confirmed.
Args:
address: ETH address (0x-prefixed, 42 chars)
Returns:
dict with cluster_id, cluster_kind, cluster_source, confidence,
evidence (label or graph-edge details), and `_methodology` /
`_doctrine_note`.
whale_eth_cluster_members
All addresses belonging to a given ETH cluster_id.
Use after whale_eth_address_cluster to expand the cluster.
Args:
cluster_id: Composite cluster ID like "wallet_cluster:exchange:Binance"
or "l3_graph:0x1e86f4234..."
limit: Max members returned (default 50, max 500)
Returns:
dict with cluster_id, cluster_size, cluster_source (mixed if
heterogeneous), members[] with per-address volume_index +
mvrv (when applicable), and methodology.
whale_eth_mvrv
ETH MVRV-equivalent for a given address.
Per Phase 2 W3: realized_cost_basis_usd = sum(inflow.amount_usd) at TX time.
current_value_usd = current_holdings_eth × current_eth_price.
mvrv_ratio = current_value / unrealized_cost_basis.
Args:
address: ETH address (0x-prefixed, 42 chars)
Returns:
dict with all MVRV metrics + data_completeness tier
('whale_tier_only' | 'whale_tier_partial' | 'aggregator_wallet').
whale_address_history
Full transaction history for a Bitcoin address (Premium-only).
Returns a chronologically-sorted list of every whale transaction this
address was involved in, with USD value at the time, fee, flow_type,
and the counterparty address (sender or recipient depending on direction).
Most useful for forensic deep-dive: reconstruct an entity's trading
pattern, accumulation cadence, or timing intelligence.
Tier-required: intelligence (Telegram/Google login → 90-day trial).
Args:
address: Bitcoin address.
limit: Max rows (default 50, max 500).
days: Lookback window (default 365, max 3650).
Returns:
dict with `address`, `total_inbound_btc`, `total_outbound_btc`,
`transactions` list, `_methodology`.
whale_export_csv
Bulk CSV export of whale data (Research-tier only).
Returns a delimited CSV string ready for paste-into-Excel or Pandas
DataFrame ingestion. Pre-filtered by min_btc and lookback window.
Subject to research-tier rate limits (100k calls/month).
Tier-required: research (149 CHF/month).
Args:
table: 'whale_trades' (default) | 'whale_outputs' | 'eth_whale_trades'
days: Lookback window (default 7, max 365).
min_btc: Minimum amount filter (default 100).
limit: Max rows (default 1000, max 100000).
Returns:
dict with `csv` (string), `row_count`, `byte_size`.
whale_lookup_any
Public-address lookup for any BTC address (Research-tier).
Unlike `whale_lookup` which is restricted to addresses appearing in
`whale_trades`, this tool resolves ANY valid Bitcoin address.
Useful for ad-hoc forensics, academic research, and journalist
source-checks where the target address isn't a tracked whale.
Tier-required: research (149 CHF/month).
Args:
address: Bitcoin address (P2PKH, P2SH, bech32, taproot — all valid).
include_utxos: When True, include up to 50 unspent UTXOs.
include_recent_txs: When True, include up to `tx_limit` recent TXs.
tx_limit: Max recent TXs to include (default 25, max 50).
prefer_source: Where to read the data from.
- 'auto' (default): mempool.space (200ms cold, 5min DB cache).
Best UX, fastest response.
- 'electrs': our local electrs node (byte-perfect, no rate
limit, but 5-15s cold-cache latency on 60GB DB). Use when
you need vendor-independent verification or are running
bulk batch queries where mempool.space rate-limits would
apply.
- 'mempool': force mempool.space, skip electrs entirely.
Same effect as 'auto' for now — kept for future when we
add other upstreams.
Returns:
Dict with `address`, `summary` (balance_btc, tx_count, mempool stats),
optionally `utxos` (list) + `recent_txs` (list), and `_methodology`.
Returns {"error": "not_found"} for unknown / never-active addresses.
Cross-references:
- For tracked whales: prefer `whale_lookup` (richer entity context)
- For full TX detail: pipe `recent_txs[i].txid` into `whale_tx_detail`
whale_benchmark_prices
Daily-close prices for traditional benchmark assets.
Reads the `stock_indices` table (yfinance-sourced ETF/index data),
which we ingest daily via benchmark_fetcher.py. ETF prices (SPY, GLD,
AGG) are dividend-adjusted total-return — the honest baseline for
"what would a buy-and-hold investor actually have earned including
cash distributions". SP500 (^GSPC) is the raw index without dividend
adjustment, useful for price-only comparisons.
Use this to answer questions like:
- "How has gold performed over the past year?"
- "What's the SPY price 6 months ago vs today?"
- "Show me 2 years of daily closes for SPY and GLD."
Args:
symbols: Comma-separated list. Valid: SPY, GLD, AGG, SP500, NASDAQ.
days: Lookback window (default 365). Free tier capped at 7 days.
Returns dict with one entry per requested symbol, each containing
a list of {date, close, volume} ordered ascending by date.
whale_benchmark_compare
Cumulative-return comparison: BTC vs S&P 500, Gold, 70/30 portfolio.
Normalizes all assets to 100 at the start of the window, then computes
cumulative percentage return. Designed for "BTC vs traditional assets"
Storytelling — the kind of comparison an analyst pulls into a quarterly
report or a journalist into an article.
70/30 portfolio is computed on-the-fly: 70% SPY total-return + 30% AGG
total-return, monthly rebalanced to target weights on the first
trading day of each month. Standard conservative-investor baseline.
Use this to answer questions like:
- "How has BTC performed vs S&P 500 over the past year?"
- "Did the 70/30 portfolio beat gold over the last 2 years?"
- "What's the cumulative return of each major asset since 2024?"
Args:
period_days: Lookback window in days (default 365 = 1 year).
Free tier capped at 90 days; paid tiers up to 12000 (~33y).
baseline: Asset to base relative outperformance on.
Options: BTC (default), SPY, GLD, AGG, SP500.
Returns:
{
"period_days": int,
"from_date": str,
"to_date": str,
"baseline": str,
"series": {
"BTC": {"cum_return_pct": float, "n_days": int, "start_price": float, "end_price": float},
"SPY": {...},
"GLD": {...},
"AGG": {...},
"SP500": {...},
"70_30": {...}, # synthetic
},
"outperformance_pct_vs_baseline": { # alpha-like, additive in pct points
"BTC": 0.0 if baseline=="BTC" else <delta>,
"SPY": <delta>,
...
},
"_doctrine": "...",
}
Doctrine note: cumulative returns are observations of past prices, not
forecasts. "Outperformance vs baseline" is descriptive — what happened,
not what will happen. No annualization without an explicit horizon
argument because that requires more careful return-distribution work.