fix(holder-analysis): the token's own contract address was scored as a holder wallet

Upstream returns the token's own CA in `token holders` with `addr_type: 0`, i.e.
as an ordinary wallet. Measured on musebook (robinhood,
0x91a2dae9699f0b82540b5886b0d8759c22820ba3): `addr_type: 0`, 0 buys, 0 sells,
0 transfer-out, 15.01% of total supply, $5.06M, carrying a `fresh_wallet` tag.

Left in the wallet cohort that one row polluted `biggest`, Top10, Top20, airdrop,
fresh wallets, risk wallets and the Top5 display at once, and on its own tripped
the danger gate as "largest wallet holds 16.30% — extreme concentration", rating
the token 🔴 Not Recommended. The largest genuine wallet there is 4.45% of float.

Controls on the same batch — JOLLY (robinhood), SI (sol), ARGUS (arc) — carry no
such row, so this is a per-token upstream classification gap, not a chain
convention, and cannot be handled by skipping a chain.

The address is partitioned out of `normal` and reported on its own line on a
total-supply basis, with its own warn gate at >10% of supply. It is not removed
from the judgement: contract-held supply reaches the market as soon as one release
transaction lands, so it stays visible and still counts toward the rating. Warn
rather than danger because that release is an observable prior step, unlike a
whale who can sell at will; no higher danger tier is added, because one measured
sample cannot calibrate a second threshold.

It is deliberately not deducted from the float denominator. Burn is permanent and
the DEX pool is the market itself, so neither can dump; self-held supply can.
Removing it from the denominator would raise every other wallet's percentage on
every token carrying this row, manufacturing new false positives while fixing one.

The same address is also excluded from `top100_map`, which feeds the dev
sock-puppet danger gate: chips sent back to the token contract, or gas paid to it,
is not "transferred to an internal wallet".

musebook: 🔴 Not Recommended → ⚠️ Caution. Top10 34.7% → 19.4%, Top20 43.4% →
27.8%, risk wallets 21.01% → 4.71%, fresh wallets 18.25% → 1.95%.
Regression on the three control tokens: JOLLY and SI byte-identical, ARGUS differs
only on the market-cap header line (live price drift between runs).

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
chenjunfeng
2026-09-23 16:05:19 +08:00
co-authored by Claude Opus 5
parent 5c61823fad
commit 338b0efdb9
2 changed files with 65 additions and 3 deletions
+27 -1
View File
@@ -54,6 +54,32 @@ amounts, USD values, and market caps still print — they do not pass through `f
Percentages on a **total-supply** basis also still print (`burn`, `DEX`, float share itself, and
the chip-quality buckets), because those denominators are unaffected.
### The token's own contract address is not a holder wallet
Upstream returns it as an ordinary wallet — measured on musebook (robinhood): `addr_type: 0`,
0 buys, 0 sells, 15.01% of total supply, $5.06M, carrying a `fresh_wallet` tag. Left in the wallet
cohort it pollutes `biggest`, Top10/Top20, airdrop, fresh, risk wallets and Top5 at once, and on its
own trips the 🔴 danger gate as "largest wallet holds 16.30% — extreme concentration". The largest
genuine wallet on that token is 4.10% of supply. Controls on the same batch — JOLLY (robinhood),
SI (sol), ARGUS (arc) — carry no such row, so this is a per-token upstream classification gap, not a
chain convention.
So the script partitions it out of `normal` and reports it on its own line, **on a total-supply
basis** (`合约自持 / Contract self-held`), with its own warn gate at >10% of supply. It is **not**
removed from the judgement: contract-held supply reaches the market as soon as one release
transaction lands, so it stays visible and still counts toward the rating. It is a warn rather than
a danger because that release is an observable prior step, unlike a whale who can sell at will — and
there is no higher danger tier, because only one sample has been measured and a second threshold off
one sample would be a guess.
It is deliberately **not** deducted from the float denominator. Burn is permanent and the DEX pool is
the market itself, so neither can dump; self-held supply can. Removing it from the denominator would
raise every other wallet's percentage on every token carrying this row, manufacturing new false
positives while fixing one.
The same address is also excluded from the dev sock-puppet map: chips sent back to the token contract,
or gas paid to it, is not "transferred to an internal wallet".
### Holder object key fields
| Field | Type | Meaning |
@@ -110,7 +136,7 @@ Entry timing pressure (批次浮盈/出货) does **NOT** affect the overall rati
|-------------|-------------|-------|-----------|
| 无法评估 | Cannot Assess | ⚪ | Tradeable float <2% of supply, **or** upstream returned zero holders (all percentage rules suppressed; dev sock puppet still escalates to 🔴) |
| 不建议买 | Not Recommended | 🔴 | Any: rat traders >5% / largest wallet >10% / dev sock puppet |
| 谨慎参与 | Caution | ⚠️ | ≥2 of: Dev still holding >1% / airdrop >20% / risk wallets >35% / linked >15% |
| 谨慎参与 | Caution | ⚠️ | ≥2 of: Dev still holding >1% / airdrop >20% / risk wallets >35% / linked >15% / contract self-holds >10% of supply |
| 可轻仓 | Light Position | 🟡 | Exactly 1 of above warns |
| 正常参与 | Normal | ✅ | None of the above |
+38 -2
View File
@@ -72,7 +72,21 @@ with ThreadPoolExecutor(max_workers=4) as ex:
except Exception:
NATIVE_PRICE = None
normal = [h for h in holders if h.get('addr_type', 0) == 0]
# ── 代币自身的合约地址不是持仓钱包 ──────────────────────────────────────
# 上游把它当普通钱包返回(实测 musebook / robinhood:addr_type=0、0 买 0 卖、
# 占总供应 15.01%、$5.06M,还带着 fresh_wallet 标签),于是它一个地址同时污染
# biggest / top10 / top20 / airdrop / fresh / risk_all / top5,并单独把评级顶到
# 🔴「最大单钱包持仓 16.30%,筹码极度集中」。剔掉它之后该币最大的真实普通钱包是
# 4.10% 总供应,离 10% 闸门很远 —— 那个 16.30% 是把合约储备读成了大户。
# 同批对照:JOLLY(robinhood) / SI(sol) / ARGUS(arc) 的持仓表里都没有自身地址,
# 所以这不是某条链的固定约定,而是按币出现的上游分类缺陷。
# 剔除不等于不判:合约自持的供应是可以被释放出来砸盘的,所以它在下方 §自持
# 有独立的展示行和独立的告警闸门,见 self_pct。
SELF_ADDR = TOKEN_ADDR.lower()
selfheld = [h for h in holders if (h.get('address') or '').lower() == SELF_ADDR]
normal = [h for h in holders
if h.get('addr_type', 0) == 0 and (h.get('address') or '').lower() != SELF_ADDR]
burn = [h for h in holders if h.get('addr_type', 0) == 1]
dex = [h for h in holders if h.get('addr_type', 0) == 2]
@@ -126,6 +140,12 @@ dex_pct = sum(h['amount_percentage'] for h in dex)
float_raw = 1.0 - burn_pct - dex_pct
float_share = max(float_raw, 1e-9)
# 合约自持份额,按总供应基准 —— 和 burn_pct / dex_pct 同一口径。
# 故意不从 float 分母里扣掉:销毁是永久消失、DEX 池就是市场本身,两者砸不动;
# 合约自持只要一笔释放交易就能进入流通,性质不同。把它扣出分母会抬高报告里
# 其余每一个钱包的占比,在所有带这一行的币上制造出新的误报。
self_pct = sum(h.get('amount_percentage') or 0 for h in selfheld)
# ── 流通盘退化保护 ──────────────────────────────────────────────────────
# DEX 池(或销毁地址)吃掉几乎全部供应时 float_share 趋零,于是每个 `/ float_share`
# 都把尘埃钱包放大成两位数甚至 100%:实测过一个未迁移的盘,LP 占 99.9%,一个只握着
@@ -321,7 +341,10 @@ kol_holding = [h for h in kol if (h.get('sell_tx_count_cur') or 0) == 0 and
smart_selling = [h for h in smart if (h.get('sell_tx_count_cur') or 0) > 0]
smart_holding = [h for h in smart if (h.get('sell_tx_count_cur') or 0) == 0 and (h.get('balance') or 0) >= 1]
top100_map = {h['address']: h for h in holders}
# 自身合约地址排除在外:dev 把筹码转回代币合约、或给合约打 gas,都不是"转给内部马甲"。
# 同一个分类缺陷的另一个出口,一并关掉。
top100_map = {h['address']: h for h in holders
if (h.get('address') or '').lower() != SELF_ADDR}
creator = next((d for d in devs if 'creator' in (d.get('maker_token_tags') or [])), None)
sub_devs = [d for d in devs if 'creator' not in (d.get('maker_token_tags') or [])]
dev_realized = sum(d.get('realized_profit') or 0 for d in devs)
@@ -484,6 +507,13 @@ else:
if hold_pct_val > 0.01:
warns.append(_( f"Dev 仍持仓 {pct(hold_pct_val):.2f}%",
f"Dev still holds {pct(hold_pct_val):.2f}%"))
if self_pct > 0.10:
# 合约自持走 warn 而不是 danger:这笔筹码要砸盘必须先有一笔链上释放交易,
# 是可观察的前置动作,和"一个大户随时可以卖"不是同一种风险;判成 danger
# 只是把同一个 🔴 换个名字再发一遍。也不设更高的 danger 档 —— 目前只有
# 一个实测样本,凭一个样本定第二道阈值就是猜。
warns.append(_( f"合约自持 {pct(self_pct):.1f}% 供应,释放后即为抛压",
f"Contract self-holds {pct(self_pct):.1f}% of supply — sell pressure once released"))
if airdrop_pct > 0.2:
warns.append(_( f"空降筹码 {pct(airdrop_pct):.1f}%,来源不透明",
f"Airdrop supply {pct(airdrop_pct):.1f}% — opaque origin"))
@@ -626,6 +656,12 @@ airf = pf("🔴" if airdrop_pct>0.25 else ("🟡" if airdrop_pct>0.1 else "🟢
riskf = pf("🔴" if risk_pct>0.35 else ("🟡" if risk_pct>0.15 else "🟢"))
print(f" {_('转入筹码', 'Airdrop')} {len(airdrop)}{_('个', '')}({fpct(airdrop_pct)}) {airf} · {_('风险钱包', 'Risk')} {len(risk_all)}{_('个', '')}({fpct(risk_pct)}) {riskf}")
if selfheld:
# 总供应基准,不经过 float_share,所以退化盘也照常打印真实数字
selff = "🔴" if self_pct > 0.30 else ("🟡" if self_pct > 0.10 else "🟢")
print(f" {_('合约自持', 'Contract self-held')} {pct(self_pct):.2f}%"
f"{_('(总供应基准,已排除在钱包统计外)', ' (of supply, excluded from wallet stats)')} {selff}")
any_risk = any(g for _lb, g, _fl in RISK_GROUPS)
if any_risk:
for label, group, flag in RISK_GROUPS: