d6c802ef26
Batch of the pre-launch audit campaign (BUG-01…14 plus three new features): Pricing / TP-SL protection - Add app/services/hl_price_feed.py: supplemental HL allMids poller for HL-native assets (HYPE, PURR) not listed on Binance. Pumps price_store + tp_sl_monitor.on_price_tick so bot trades on these assets keep full stop-loss / take-profit / trailing protection instead of max-hold only. - Wire feed into main.py lifespan (startup task + graceful shutdown cancel). Telegram - Add format_trump_mention + PATH B in _dispatch: crypto-relevant Trump posts with no directional signal (relevant=True, signal=hold) now alert the public channel only (no per-subscriber noise). - Rate limiter (slowapi) on the API; assorted bot/digest fixes. KOL on-chain - seed_kol_wallets.py: KOL_FEEDS coverage cross-check; reversibly deactivate orphaned wallets (handle not in KOL_FEEDS → can never produce divergence) so the scanner stops burning cycles on them. Tests / misc - Fix brittle test_macro_ahr999_uses_same_formula_as_scanner: mock now uses realistic ms timestamps so the in-progress-day drop fires, matching the fetcher's bar count (was 0.3179 vs 0.3178 off-by-one). - Refresh stale notify_signal comment in truth_social.py. Frontend reduce-action type fix lives in the sibling repo. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
224 lines
10 KiB
Python
Executable File
224 lines
10 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""
|
|
Seed the kol_wallets table with publicly attributed on-chain addresses.
|
|
|
|
Why this exists:
|
|
The talks-vs-trades divergence module can only fire on KOLs whose wallets
|
|
we know. As of 2026-05-24 only 3 KOLs had wallets configured, which is
|
|
why we had ~3 divergence detections across the whole dataset.
|
|
|
|
Adding a new wallet here REQUIRES:
|
|
1. Public attribution — Arkham label, the KOL's own X bio, a public
|
|
investigation by ZachXBT / Inspex / similar, or the KOL's ENS clearly
|
|
visible in transactions.
|
|
2. The `handle` field MUST exactly match a `handle` value in
|
|
app/services/kol_substack.py KOL_FEEDS — otherwise the divergence
|
|
scanner cannot match "post by handle X" against "wallet activity by
|
|
handle X".
|
|
3. The `source_url` must link to that public attestation. Don't add
|
|
speculative addresses, even if "everyone knows" — misattribution
|
|
damages both the KOL and our credibility.
|
|
|
|
Idempotent: runs UPSERT-style. Existing rows for (handle, address) are
|
|
preserved; only new ones are inserted.
|
|
|
|
Usage:
|
|
DATABASE_URL='sqlite+aiosqlite:///./trumpsignal.db' \\
|
|
venv/bin/python scripts/seed_kol_wallets.py
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import asyncio
|
|
import sys
|
|
from datetime import datetime, timezone
|
|
from pathlib import Path
|
|
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
|
|
|
|
from sqlalchemy import select
|
|
from app.database import AsyncSessionLocal
|
|
from app.models import KolWallet
|
|
|
|
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
# Verified seed list.
|
|
#
|
|
# Each entry MUST have a working source_url. If you can't link to a public
|
|
# attestation, DON'T add it.
|
|
#
|
|
# To find more candidates yourself:
|
|
# • https://platform.arkhamintelligence.com/ — search by handle, click
|
|
# "labels" tab. Labels prefixed with "Entity:" are Arkham-verified.
|
|
# • https://etherscan.io/labelcloud — Etherscan public label registry.
|
|
# • ZachXBT investigations on X — he posts the underlying tx evidence.
|
|
# • Many KOLs put their address in their X bio or pinned tweet.
|
|
#
|
|
# When the KOL's handle in KOL_FEEDS doesn't match the on-chain handle here,
|
|
# add it under the handle that's IN KOL_FEEDS — divergence join is by handle.
|
|
# ────────────────────────────────────────────────────────────────────────────
|
|
|
|
SEED_WALLETS: list[dict] = [
|
|
# ── Already in DB (kept here so the script is self-documenting) ──────
|
|
{
|
|
"handle": "cryptohayes",
|
|
"chain": "ethereum",
|
|
"address": "0xa86e3d1c80a750a310b484fb9bdc470753a7506f",
|
|
"label": "Arthur Hayes (main)",
|
|
"source_url": "https://etherscan.io/address/0xa86e3d1c80a750a310b484fb9bdc470753a7506f",
|
|
},
|
|
{
|
|
"handle": "cryptohayes",
|
|
"chain": "ethereum",
|
|
"address": "0x534a0076fb7c2b1f83fa21497429ad7ad3bd7587",
|
|
"label": "Arthur Hayes (secondary)",
|
|
"source_url": "https://etherscan.io/address/0x534a0076fb7c2b1f83fa21497429ad7ad3bd7587",
|
|
},
|
|
# ⚠️ ORPHANED: the two entries below have VERIFIED on-chain attribution
|
|
# but their `handle` does NOT match any handle in KOL_FEEDS, so the
|
|
# divergence scanner has no post-side data to join against — they produce
|
|
# ZERO divergence detections today. They are kept (a) because the wallet
|
|
# attribution is sound and (b) so that if/when X/Twitter ingestion or a
|
|
# matching RSS feed is added under these handles, the wallets light up
|
|
# automatically. The cross-check in main() prints a warning for these.
|
|
# • andrewkang — publishes on X/@Rewkang only (no Substack) → needs
|
|
# X ingestion before post-side data exists.
|
|
# • murad — not currently in KOL_FEEDS at all.
|
|
{
|
|
"handle": "andrewkang",
|
|
"chain": "ethereum",
|
|
"address": "0xff3879b8a363aed92a6eaba8f61f1a96a9ec3c1e",
|
|
"label": "Andrew Kang (beanwhale.eth)",
|
|
"source_url": "https://etherscan.io/address/0xff3879b8a363aed92a6eaba8f61f1a96a9ec3c1e",
|
|
},
|
|
{
|
|
"handle": "murad",
|
|
"chain": "ethereum",
|
|
"address": "0x93f019699ef400df7dc3477dbb6400ed9445a657",
|
|
"label": "Murad Mahmudov (via ZachXBT investigation)",
|
|
"source_url": "https://www.blocmates.com/news-posts/24million-in-memecoins-zachxbt-unveils-murad-mahmudov-s-alleged-wallets",
|
|
},
|
|
|
|
# ── Add new VERIFIED entries below this line ─────────────────────────
|
|
#
|
|
# Template — copy, replace fields, push only after verifying source_url:
|
|
#
|
|
# {
|
|
# "handle": "<matches handle in KOL_FEEDS>",
|
|
# "chain": "ethereum", # or "solana", "base", "arbitrum"
|
|
# "address": "0x...",
|
|
# "label": "<KOL display name (annotation)>",
|
|
# "source_url": "<public link proving attribution>",
|
|
# },
|
|
#
|
|
# Candidates to research (NOT seeded — verify before adding):
|
|
#
|
|
# • niccarter — Nic Carter has spoken openly about his wallet
|
|
# history on podcasts; check Coin Center filings.
|
|
# • pomp — Pompliano has a public BTC-only treasury; less
|
|
# useful for ETH-side divergence detection.
|
|
# • dragonfly — Dragonfly Capital portfolio wallets often
|
|
# labeled on Arkham as "Dragonfly Fund".
|
|
# • placeholder — Placeholder VC fund wallets per their public
|
|
# investment disclosures.
|
|
# • eugene — Eugene Ng Ah Sio — verify before adding.
|
|
#
|
|
# Also worth tracking even if not in KOL_FEEDS (would need to add the
|
|
# corresponding feed entry first or they'll never have post-side data):
|
|
#
|
|
# • justinsuntron — Tron founder, very active ETH trader, well-labeled.
|
|
# • cobie — Jordan Fish, address known via Arkham labels.
|
|
# • gcr / sam — anon traders, no reliably verifiable address.
|
|
]
|
|
|
|
|
|
async def main() -> int:
|
|
inserted = 0
|
|
skipped = 0
|
|
async with AsyncSessionLocal() as session:
|
|
for entry in SEED_WALLETS:
|
|
# Idempotency: skip if (handle, address) already present.
|
|
existing = await session.execute(
|
|
select(KolWallet).where(
|
|
KolWallet.handle == entry["handle"],
|
|
KolWallet.address == entry["address"].lower(),
|
|
)
|
|
)
|
|
if existing.scalar_one_or_none():
|
|
skipped += 1
|
|
continue
|
|
row = KolWallet(
|
|
handle=entry["handle"],
|
|
chain=entry["chain"],
|
|
address=entry["address"].lower(),
|
|
label=entry["label"],
|
|
source_url=entry["source_url"],
|
|
active=True,
|
|
added_at=datetime.now(timezone.utc).replace(tzinfo=None),
|
|
)
|
|
session.add(row)
|
|
inserted += 1
|
|
print(f" + {entry['handle']:18s} {entry['address']} ({entry['label']})")
|
|
await session.commit()
|
|
|
|
print()
|
|
print(f"Inserted {inserted} wallets, skipped {skipped} existing.")
|
|
|
|
# ── Coverage cross-check ─────────────────────────────────────────────
|
|
# A wallet is only useful for divergence detection if its handle has a
|
|
# matching post-side feed in KOL_FEEDS. Flag any orphans loudly, and
|
|
# report which feeds still have no wallet at all (the real coverage gap).
|
|
try:
|
|
from app.services.kol_substack import KOL_FEEDS
|
|
feed_handles = {f["handle"] for f in KOL_FEEDS}
|
|
except Exception as exc: # pragma: no cover - diagnostic only
|
|
print(f"\n(could not import KOL_FEEDS for cross-check: {exc})")
|
|
return 0
|
|
|
|
wallet_handles = {e["handle"] for e in SEED_WALLETS}
|
|
orphaned = sorted(wallet_handles - feed_handles)
|
|
feeds_without_wallet = sorted(feed_handles - wallet_handles)
|
|
|
|
print()
|
|
print(f"Coverage: {len(wallet_handles - set(orphaned))}/{len(feed_handles)} "
|
|
f"KOL_FEEDS handles have ≥1 wallet.")
|
|
|
|
# ── Orphan reconciliation ────────────────────────────────────────────
|
|
# An orphaned wallet (handle ∉ KOL_FEEDS) can NEVER produce a divergence
|
|
# because there's no post-side data to join against — but kol_onchain
|
|
# still burns a scan cycle (HL clearinghouseState + Etherscan) on it every
|
|
# run. Park such rows as active=False so the scanner skips them. This is
|
|
# REVERSIBLE: the row + its verified attribution stay in the DB, and the
|
|
# moment a matching feed (e.g. X/Twitter ingestion) is added, flip it back.
|
|
if orphaned:
|
|
async with AsyncSessionLocal() as session:
|
|
res = await session.execute(
|
|
select(KolWallet).where(
|
|
KolWallet.handle.in_(orphaned),
|
|
KolWallet.active.is_(True),
|
|
)
|
|
)
|
|
rows = res.scalars().all()
|
|
for row in rows:
|
|
row.active = False
|
|
await session.commit()
|
|
deactivated = len(rows)
|
|
print()
|
|
print(f"⚠️ ORPHANED wallets (handle not in KOL_FEEDS → ZERO divergence) "
|
|
f"— deactivated {deactivated} so the scanner skips them:")
|
|
for h in orphaned:
|
|
print(f" {h} — re-activate once a feed under this handle exists.")
|
|
if feeds_without_wallet:
|
|
print()
|
|
print(f"📭 {len(feeds_without_wallet)} feeds have NO wallet (no on-chain "
|
|
f"divergence possible):")
|
|
print(" " + ", ".join(feeds_without_wallet))
|
|
|
|
print()
|
|
print("Next step: edit SEED_WALLETS above and re-run. Each new wallet")
|
|
print("MUST cite a public attestation in source_url — see the docstring.")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(asyncio.run(main()))
|