mcpField
G
gmgn-wallet-score

gmgn-wallet-score

github.com/gmgnaiデータ・データベース6352.7k 回インストール1 日前に更新
リポジトリ

gmgn-wallet-score について

gmgn-wallet-score は、ウォレットの取引実績、コピー取引の実行可能性、主にトークンを発行するウォレットの開発者としての評判を評価するエージェントスキルです。GMGNデータからスコアと、遅延・スリッページ・ガスを反映したバックテストを算出し、実績とコピーのしやすさを分けて示します。

主な機能

  1. 01結果分布に基づく取引実績スコア
  2. 02約定条件を考慮したコピー取引スコア
  3. 03遅延・スリッページ・ガスを反映したバックテスト
  4. 04トークン作成者の開発者評価
  5. 05履歴不足時の適切な処理

活用シーン

  1. 01ウォレットの取引収益性の評価
  2. 02ウォレットがコピー取引に適するかの判断
  3. 03トークン作成者のローンチ実績の確認

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/gmgnai/gmgn-skills --skill gmgn-wallet-score

インストール後、Claude Code で/gmgn-wallet-scoreを入力して読み込まれたか確認します。

詳細

コマンドを実行する前に必ず、gmgn-cli config --check を実行してください。終了コードが 0 の場合は、そのまま続行してください。終了コードが 1 の場合は、(1)gmgn-cli config を実行し、出力をユーザーに表示してください。(2)ユーザーから API Key が送られたら、gmgn-cli config --apply <KEY> を実行して設定と検証を完了し、出力をユーザーに表示してください。--check でエラー(不明なオプションまたはコマンドが見つからない)が返された場合は、更新するために npm install -g gmgn-cli を実行してから再試行するよう、ユーザーに伝えてください。

重要:必ず gmgn-cli コマンドを使用してください。Web 検索、WebFetch、curl の使用や、gmgn.ai へのアクセスは禁止です。このウェブサイトはログインが必要で、構造化データを公開していません。

重要:Field Reference に記載されていないフィールド名や値を推測しないでください。記載のないフィールドは、実際の API で確認されていません。スクリプトによる防御的な検索で値が取得できなかった場合は、値を捏造せず、Notes に従って適切に対処してください。

⚠️ IPv6 はサポートされていません:認証情報に問題がないように見えるにもかかわらず 401 または 403 エラーが発生した場合は、すぐに IPv6 を確認してください。(1)すべてのネットワークインターフェースと IPv6 アドレスを一覧表示します。macOS では ifconfig | grep inet6、Linux では ip addr show | grep inet6 を実行してください。(2)https://ipv6.icanhazip.com にテストリクエストを送信します。応答が IPv6 アドレスなら、送信トラフィックは IPv6 経由です。すぐにユーザーに次のように伝えてください:「ネットワークインターフェースで IPv6 を無効にしてください。gmgn-cli コマンドは IPv4 経由でのみ動作します。」

ユーザーがウォレットアドレスを送ったら、メッセージから --chain と --wallet を抽出し、以下の分析スクリプトを実行してください。このスクリプトは、ユーザーが実際にどの項目について尋ねたかにかかわらず、1 回の共通データ取得から 3 つの観点(収益性、コピー取引の適性、Dev の評判)をすべて算出します。また、ユーザーの言語を判定してください。ユーザーが中国語で書いた場合は LANG を 'zh'、英語で書いた場合は 'en' に設定します(デフォルトは 'zh')。ユーザーがカスタマイズしたバックテストを希望する場合に限り、--latency/--slippage/--gas を尋ねてください。それ以外の場合は、スクリプトに組み込まれたデフォルト値を使用します。スクリプトの実行後は、返信でどのセクションを最初に提示するかを決めてください。詳しくは以下の質問に応じたレポートの構成を参照してください。

コアコンセプト

このスキルでは、「このトレーダーは優秀か」と「コピーできるか」を意図的に分けています。ウォレットはトレードが非常に上手でもコピーには向かない場合があり、トレードは平凡でも追従しやすい場合があります。

  • トラックレコードスコア(0~100) — 勝率だけではなく、結果の分布に基づいて、このトレーダーが実際に優秀かを評価します。勝率が30%のウォレットでも、損失をすばやく抑え、勝った30%で残りを補えていれば、このスコアは高くなる可能性があります。これは運ではなく、規律あるリスク管理です。
  • コピートレード適性スコア(0~100) — ウォレットに本当にスキルがあったとしても、あなたがその優位性を得られるでしょうか。時価総額が10万ドル未満の市場で先回りして買い付けたり、5秒で売買を完了したりするウォレットはコピーできません。あなたの取引が約定するころには、ウォレットはすでに売却を終えており、あなたが売り抜け先になっています。このスコアでは、早すぎるエントリー、トレードごとの薄い利益、極端に短い保有期間、ボット並みのトレード頻度が減点対象になります。
  • バックテストの推定値 — 「実際に手元に残る金額」を具体的に示します。ウォレットのリターンから、エントリー遅延による価格変動、往復スリッページ、ガス代を差し引いて算出します。計算にはユーザーが指定した遅延・スリッページ・ガス代を使用し、指定がない場合は妥当なデフォルト値を使います。
  • 開発者としての評判スコア — ウォレット自体がトークンの作成者である場合にのみ算出します(created_token_count が取引したトークン数の半分を超える場合。つまり、主にトレードではなくトークンの発行を行っています)。ウォレットを開発者として評価し、作成したトークンの存続率、ボンディングカーブに留まり卒業できていないトークンの数、最近のローンチが基本的なセキュリティチェックに合格したかどうかを確認します。この場合、ウォレットは自分のトークンの価格や保有者リストを操作できるため、エントリータイミングや勝率に関する要素の信頼性は低くなります。トラックレコードスコアとコピートレード適性スコアは大幅に割り引いて表示し、評価の中心は開発者としての評判に切り替わります。
  • 取引履歴なし — サンプル期間中にウォレットの買い・売りのトランザクションがなく(送金やエアドロップのみの場合)、上記のいずれも誠実に算出できません。スクリプトはこれを検出し、空のデータに無理にスコアを付けるのではなく、その旨を明確に報告します。

質問に応じたレポートの構成

このスキルは、1回の共通データ取得から、互いに関連しつつも異なる3つの質問に回答します。ユーザーがどの観点から尋ねた場合でも、必ず分析スクリプトをすべて実行してください。基盤となるportfolio stats/activity/created-tokens/token securityの呼び出しは変わりません。変わるのは、返信で何を最初に取り上げるかだけです。観点ごとにスクリプトを再実行しないでください。すでに取得したデータを使うために、余分なレート制限枠を消費するだけです。

ユーザーの質問最初に取り上げる内容簡潔に触れる内容
収益性 — 「这个钱包盈利能力怎么样」、「钱包战绩怎么样」、「このウォレットは利益を上げていますか」🎯 トラックレコードスコアと評価要素スタイルタグ。ウォレットが開発者に該当すると判明した場合を除き、バックテストと開発者セクションは省略します(下記参照)
コピートレードの価値 — 「值不值得跟单」、「このウォレットをコピーする価値はありますか」、「このウォレットをコピーした場合、実際のリターンはいくらですか」🚀 コピートレード適性スコアと 🧮 バックテスト補足としてトラックレコードスコアと最終判定
開発者/ローンチの評判 — 「这个钱包发盘情况怎么样」、「是不是发币方钱包」、「开发者としての評判はどうですか」、「このウォレットはトークン作成者ですか」👨‍💻 開発者としての評判セクション他の2つのスコアが割り引かれること(自己取引のため)。各評価要素を詳しく説明する必要はありません
一般的な質問 — 「帮我看看这个钱包」、「钱包评分」、または特定の観点を伴わないアドレス全セクションを含む完全なレポート—

安全上の優先事項:ユーザーが収益性またはコピートレード適性について尋ねた場合でも、ウォレットが開発者ウォレット(dev is not None)に分類された場合は、開発者としての評判セクションと、自己取引による割引についての注意を最初に示してください。主にトークンを発行するウォレットについては、質問された観点にかかわらず、この注意書きが必要です。他の2つのスコアをどの程度信頼できるかに影響するためです。

分析スクリプト

この Python スクリプトをインラインで実行し、<FILL_IN_*> のプレースホルダーを実際の値に置き換えてください。ユーザーが指定していない場合、LATENCY_S/SLIPPAGE_PCT/GAS_USD/SAMPLE のデフォルト値はそれぞれ 3.0/0.05/0.2/200 です。未指定の値には、これらのリテラルを渡してください。

python3 << 'PYEOF'
import json, math, subprocess

CHAIN        = "<FILL_IN_CHAIN>"
WALLET       = "<FILL_IN_WALLET_ADDRESS>"
LANG         = "<FILL_IN_LANG>"          # 'zh' or 'en'
LATENCY_S    = <FILL_IN_LATENCY>         # seconds you'd lag entering after this wallet (default 3.0)
SLIPPAGE_PCT = <FILL_IN_SLIPPAGE>        # one-sided slippage fraction, e.g. 0.05 = 5% (default 0.05)
GAS_USD      = <FILL_IN_GAS>             # your cost per trade in USD (default 0.2)
SAMPLE       = <FILL_IN_SAMPLE>          # activity rows to sample, max 400 (default 200)

ZH = (LANG == 'zh')
def _(zh, en): return zh if ZH else en

def run_cli(args, timeout=30):
    r = subprocess.run(['gmgn-cli'] + args + ['--raw'], capture_output=True, text=True, timeout=timeout)
    if r.returncode != 0:
        raise RuntimeError(r.stderr)
    return json.loads(r.stdout)

def unwrap(resp):
    # gmgn-cli --raw already unwraps to the data object; tolerate either shape.
    return resp.get('data', resp) if isinstance(resp, dict) and 'data' in resp else resp

def _f(v, default=0.0):
    try: return float(v)
    except (TypeError, ValueError): return default

def _clamp(x, lo=0.0, hi=1.0):
    return lo if x < lo else hi if x > hi else x

def _b(v):
    if isinstance(v, bool): return v
    if isinstance(v, (int, float)): return v != 0
    if isinstance(v, str): return v.strip().lower() in ('1', 'true', 'yes')
    return False

def fmt_dur(sec):
    sec = _f(sec)
    if sec < 60:    return f"{int(sec)}{_(' 秒','s')}"
    if sec < 3600:  return f"{round(sec/60)}{_(' 分','m')}"
    if sec < 86400: return f"{round(sec/3600,1)}{_(' 小时','h')}"
    return f"{round(sec/86400,1)}{_(' 天','d')}"

def usd(v):
    v = _f(v)
    if abs(v) >= 1_000_000: return f"${v/1_000_000:.2f}M"
    if abs(v) >= 1_000:     return f"${v/1_000:.1f}K"
    return f"${v:.2f}"

# ── 1. Trading stats (7D) ─────────────────────────────────
stats_raw = unwrap(run_cli(['portfolio', 'stats', '--chain', CHAIN, '--wallet', WALLET, '--period', '7d']))
pnl    = stats_raw.get('pnl_stat') or {}
common = stats_raw.get('common') or {}

buy  = int(_f(stats_raw.get('buy', stats_raw.get('buy_count'))))
sell = int(_f(stats_raw.get('sell', stats_raw.get('sell_count'))))
trades = buy + sell
token_num = int(_f(pnl.get('token_num')))
dist = dict(
    gt_5   = int(_f(pnl.get('pnl_gt_5x_num'))),
    x2_5   = int(_f(pnl.get('pnl_2x_5x_num'))),
    x0_2   = int(_f(pnl.get('pnl_0x_2x_num'))),
    n50_0  = int(_f(pnl.get('pnl_nd5_0x_num'))),
    lt_n50 = int(_f(pnl.get('pnl_lt_nd5_num'))),
)
realized_profit = _f(stats_raw.get('realized_profit'))
bought_cost      = _f(stats_raw.get('bought_cost', stats_raw.get('total_cost')))
created_token_count = int(_f(common.get('created_token_count')))

w = dict(
    realized_profit=realized_profit,
    roi=_f(stats_raw.get('realized_profit_pnl', stats_raw.get('pnl'))),
    buy=buy, sell=sell, trades=trades,
    bought_cost=bought_cost,
    avg_buy_usd=(bought_cost / buy if buy else 0.0),
    avg_trade_usd=(realized_profit / sell if sell else 0.0),
    token_num=token_num, winrate=_f(pnl.get('winrate')), dist=dist,
    avg_hold_s=_f(pnl.get('avg_holding_period')),
    created_token_count=created_token_count,
    identity=common.get('twitter_name') or common.get('name') or common.get('ens') or '',
)

if trades == 0:
    print(_( "该地址近 7 天没有真实买卖记录(可能是新钱包,或持有的代币是转入/空投所得)——无法评估战绩,跳过打分。",
             "This address has no real buy/sell activity in the last 7 days (may be a new wallet, or its holdings were transferred/airdropped in) — not enough data to score, skipping."))
    raise SystemExit(0)

# ── 2. Activity sample (paginated, most-recent-first) ─────
def sample_activity(target):
    target = max(20, min(int(target or 200), 400))
    acts, cursor = [], None
    for _try in range(4):
        args = ['portfolio', 'activity', '--chain', CHAIN, '--wallet', WALLET,
                '--limit', str(min(100, target - len(acts)))]
        if cursor: args += ['--cursor', str(cursor)]
        raw = unwrap(run_cli(args))
        page = raw.get('activities') or []
        acts.extend(page)
        cursor = raw.get('next')
        if not cursor or not page or len(acts) >= target:
            break
    return acts[:target]

acts = sample_activity(SAMPLE)

def ev_type(a):
    return (a.get('event_type') or a.get('type') or '').lower()

mcaps = []
for a in acts:
    if ev_type(a) != 'buy':
        continue
    tok = a.get('token') or {}
    supply = _f(tok.get('total_supply'))
    px = _f(a.get('price_usd'))
    if supply > 0 and px > 0:
        mcaps.append(px * supply)
mcaps.sort()
has_mcap_data = bool(mcaps)
entry_under_100k  = (sum(1 for m in mcaps if m < 100_000) / len(mcaps)) if mcaps else 0.0
median_entry_mcap = mcaps[len(mcaps) // 2] if mcaps else 0.0

by_tok = {}
for a in acts:
    addr = (a.get('token') or {}).get('address')
    by_tok.setdefault(addr, []).append(a)
pairs = fast = 0
for evs in by_tok.values():
    evs = sorted(evs, key=lambda e: _f(e.get('timestamp')))
    last_buy = None
    for e in evs:
        et = ev_type(e)
        if et == 'buy':
            last_buy = _f(e.get('timestamp'))
        elif et == 'sell' and last_buy is not None:
            pairs += 1
            if _f(e.get('timestamp')) - last_buy <= 5:
                fast += 1
            last_buy = None
fast_flip_rate = round(fast / pairs, 4) if pairs else 0.0

gas_vals = [_f(a.get('gas_usd')) for a in acts if _f(a.get('gas_usd')) > 0]
has_gas_data = bool(gas_vals)
avg_gas_usd = round(sum(gas_vals) / len(gas_vals), 4) if gas_vals else 0.0

summ = dict(sampled=len(acts), entry_under_100k=round(entry_under_100k, 4),
            median_entry_mcap=round(median_entry_mcap, 2),
            fast_flip_rate=fast_flip_rate, avg_gas_usd=avg_gas_usd)

# ── 3. Dev-reputation (only if this wallet mostly launches, not trades) ──
DEV_SEC_SCAN_N = 3     # how many of its most recent launches to security-scan
is_dev_wallet = created_token_count > 0 and created_token_count > 0.5 * max(1, token_num)

dev = None
if is_dev_wallet:
    try:
        ct = unwrap(run_cli(['portfolio', 'created-tokens', '--chain', CHAIN, '--wallet', WALLET]))
        toks = ct.get('tokens') or []
        open_count  = int(_f(ct.get('open_count')))
        inner_count = int(_f(ct.get('inner_count')))
        if toks or open_count or inner_count:
            # alive = graduated to DEX AND still has real liquidity (not drained)
            alive = sum(1 for t in toks if t.get('is_open') and _f(t.get('pool_liquidity')) >= 4000)
            total = len(toks)
            rug_rate = round((total - alive) / total, 3) if total else 0.0
            ath_mc = _f((ct.get('creator_ath_info') or {}).get('ath_mc'))

            recent = sorted(toks, key=lambda t: -_f(t.get('create_timestamp')))[:DEV_SEC_SCAN_N]
            checked = unsafe = 0
            for t in recent:
                addr = t.get('token_address')
                if not addr:
                    continue
                try:
                    sec = unwrap(run_cli(['token', 'security', '--chain', CHAIN, '--address', addr]))
                except Exception:
                    continue
                checked += 1
                bad = False
                if str(sec.get('is_honeypot', '')).lower() == 'yes':
                    bad = True
                elif CHAIN == 'sol':
                    if not _b(sec.get('renounced_mint')) or not _b(sec.get('renounced_freeze_account')):
                        bad = True
                else:
                    if str(sec.get('open_source', '')).lower() == 'no':
                        bad = True
                if bad:
                    unsafe += 1
            sec_risk_rate = round(unsafe / checked, 3) if checked else 0.0

            surv = (1.0 - rug_rate) if total else _clamp(_f(ct.get('open_ratio')))
            ath_track = _clamp((math.log10(max(1.0, ath_mc)) - 5.0) / 2.0)   # $100k→0, $10M→1
            s = 0.25 + 0.55 * surv
            s -= 0.30 * _clamp((inner_count - 50) / 950.0)   # heavy bonding-curve pileup = factory
            s += 0.15 * ath_track * surv                     # best-ever launch, gated by survival
            s -= 0.35 * sec_risk_rate                        # recent launches failed a security check
            dev = dict(open_count=open_count, inner_count=inner_count,
                       analyzed=total, alive=alive, rugged=max(0, total - alive),
                       rug_rate=rug_rate, ath_mc=ath_mc,
                       sec_checked=checked, sec_unsafe=unsafe, sec_risk_rate=sec_risk_rate,
                       score=round(_clamp(s), 3))
    except Exception:
        dev = None

# ── 4. Style tags ──────────────────────────────────────────
tags = []
def add_tag(emoji, zh, en):
    tags.append(dict(emoji=emoji, text=_(zh, en)))

tn = max(1, token_num)
big_win  = (dist['gt_5'] + dist['x2_5']) / tn
big_loss = dist['lt_n50'] / tn
early, flip = summ['entry_under_100k'], summ['fast_flip_rate']

if dev is not None:
    pct_created = round(created_token_count / tn * 100)
    cnt = created_token_count or dev.get('analyzed', 0)
    rug = dev.get('rug_rate', 0.0)
    if rug >= 0.5:
        add_tag("🏭", f"发币方/Dev:发过 {cnt} 个币(占交易{min(pct_created,100)}%+),rug 率 {round(rug*100)}%(工厂号嫌疑)",
                      f"Token creator/Dev: launched {cnt} tokens (≥{min(pct_created,100)}% of traded tokens), rug rate {round(rug*100)}% (factory suspect)")
    else:
        add_tag("🏭", f"发币方/Dev:发过 {cnt} 个币(占交易{min(pct_created,100)}%+),看 Dev 信誉分",
                      f"Token creator/Dev: launched {cnt} tokens (≥{min(pct_created,100)}% of traded tokens) — check the Dev-reputation score")
    add_tag("🏗️", f"自产自销:交易的币里 {min(pct_created,100)}%+ 是自己发的——进场时机/胜率对这类币没意义",
                  f"Self-dealer: ≥{min(pct_created,100)}% of traded tokens are its own launches — entry-timing/win-rate tags are meaningless here")
if trades >= 2000:
    add_tag("🤖", f"机器人/科学家:7D {trades} 笔,只能机器跟", f"Bot/Quant: {trades} trades in 7D — only a bot can keep up")
if flip >= 0.3:
    add_tag("⚡", f"闪电手:{round(flip*100)}% 的仓位 5 秒内买卖", f"Flash flipper: {round(flip*100)}% of positions bought & sold within 5s")
if dev is None and early >= 0.8:
    add_tag("🎯", f"狙击手:{round(early*100)}% 进场市值 <$100k", f"Sniper: {round(early*100)}% of entries are <$100k mcap")
if w['avg_hold_s'] >= 5*86400 and trades < 200:
    add_tag("💎", "钻石手:持仓久、下手少", "Diamond hands: holds long, trades rarely")
if w['avg_buy_usd'] >= 5000:
    add_tag("🐋", f"巨鲸:单笔平均建仓 {usd(w['avg_buy_usd'])}", f"Whale: avg position size {usd(w['avg_buy_usd'])}")
if dev is None and w['winrate'] >= 0.65 and trades >= 15:
    add_tag("🏆", f"高胜率:{round(w['winrate']*100)}% 的币最终是赚的", f"High win-rate: {round(w['winrate']*100)}% of tokens ended up profitable")
if 0 < w['avg_hold_s'] < 3600 and flip < 0.3 and trades >= 30:
    add_tag("🐇", f"快枪手:平均持仓 {fmt_dur(w['avg_hold_s'])}", f"Quick-draw: avg hold {fmt_dur(w['avg_hold_s'])}")
if dev is None and 0 < summ['median_entry_mcap'] < 30000:
    add_tag("🔦", f"冷门捡漏:中位进场市值仅 {usd(summ['median_entry_mcap'])}", f"Obscure hunter: median entry mcap only {usd(summ['median_entry_mcap'])}")
if w['realized_profit'] > 20000 and big_loss <= 0.05:
    add_tag("📈", "真高手:净赚且极少大亏,止损纪律好", "True skill: net profitable with very few big losses")
elif big_loss >= 0.3 and w['realized_profit'] < 0:
    add_tag("🩸", f"亏损韭菜:{round(big_loss*100)}% 的币亏超 50%,长期净亏", f"Bag holder: {round(big_loss*100)}% of tokens lost over 50%")
elif big_win >= 0.02 and w['winrate'] < 0.35 and w['realized_profit'] > 0:
    add_tag("🎰", "赌狗打法:胜率低但靠少数暴击回本", "Gambler: low win-rate, carried by a few huge hits")
if trades < 60 and w['realized_profit'] > 0 and flip < 0.1:
    add_tag("🐌", "慢工出细活:低频、可复制,最适合跟单", "Slow & steady: low frequency, repeatable — easiest to copy")
if not tags:
    add_tag("🧭", "普通交易者:没有特别突出的风格标签", "Regular trader: no standout style tags")

# ── 5. Track-record score (is this trader actually good?) ──
TRACK_W = dict(tail=0.34, upside=0.28, roi=0.16, win=0.10, size=0.12)
tail_f    = 1 - dist['lt_n50'] / tn
upside_f  = (dist['gt_5'] + dist['x2_5'] + dist['x0_2']) / tn
roi_f     = _clamp((w['roi'] + 0.05) / 0.35)
win_f     = _clamp(w['winrate'] / 0.5)
size_f    = _clamp((tn - 20) / 300)
track_facs = dict(tail=tail_f, upside=upside_f, roi=roi_f, win=win_f, size=size_f)
track_score = round(100 * sum(TRACK_W[k] * _clamp(v) for k, v in track_facs.items()))
TRACK_LABELS = dict(tail=_('止损纪律','Stop-loss discipline'), upside=_('盈利面','Profit share'),
                     roi=_('资金回报','Capital ROI'), win=_('胜率','Win rate'), size=_('样本量','Sample size'))

# ── 6. Copy-tradeability score (can YOU capture it?) ────────
COPY_W = dict(entry=0.22, profit=0.22, hold=0.20, feasible=0.18, edge=0.18)
entry_f    = _clamp(0.12 + (1 - summ['entry_under_100k']))
profit_f   = _clamp(w['avg_trade_usd'] / 80.0)
hold_f     = _clamp((1 - summ['fast_flip_rate'] * 1.6) * _clamp(w['avg_hold_s'] / 172800 + 0.15))
feasible_f = _clamp(1 - w['trades'] / 2500.0)
edge_f     = _clamp(1 - 0.6 * summ['entry_under_100k'] - 0.6 * summ['fast_flip_rate'])
copy_facs = dict(entry=entry_f, profit=profit_f, hold=hold_f, feasible=feasible_f, edge=edge_f)
copy_score = round(100 * sum(COPY_W[k] * v for k, v in copy_facs.items()))
COPY_LABELS = dict(entry=_('进场市值','Entry mcap'), profit=_('单笔利润空间','Profit per trade'),
                    hold=_('持仓 vs 延迟','Hold vs latency'), feasible=_('执行可行性','Execution feasibility'),
                    edge=_('优势类型','Edge type'))

# Self-dealing discount: for a dev wallet, entry-timing/win-rate factors are self-authored — steeply
# discount both display scores (not hidden, just flagged) rather than let them look falsely strong.
SELF_DEAL_DISCOUNT = 0.45
self_dealing = dev is not None
track_disp = round(track_score * SELF_DEAL_DISCOUNT) if self_dealing else track_score
copy_disp  = round(copy_score  * SELF_DEAL_DISCOUNT) if self_dealing else copy_score

# ── 7. Copy-trade backtest ───────────────────────────────────
wallet_pct = (w['realized_profit'] / w['bought_cost']) if w['bought_cost'] > 0 else w['roi']
wallet_pct = _clamp(wallet_pct or 0.0001, -0.9, 3.0)   # clamp: dev wallets have near-zero bought_cost, ratio blows up
LOW_MCAP_DRIFT_PER_S = 0.015
drift_per_s = LOW_MCAP_DRIFT_PER_S * (0.3 + 0.7 * summ['entry_under_100k'])
drift = LATENCY_S * drift_per_s
slip = 2 * SLIPPAGE_PCT
gas_pct = (GAS_USD / w['avg_buy_usd']) if w['avg_buy_usd'] > 0 else 0.0
copy_pct = wallet_pct - drift - slip - gas_pct
copy_7d = w['realized_profit'] * (copy_pct / wallet_pct) if wallet_pct else 0.0
bt = dict(wallet_pct=round(wallet_pct,4), copy_pct=round(copy_pct,4), drift=round(drift,4),
          slip=round(slip,4), gas_pct=round(gas_pct,4), wallet_7d=round(w['realized_profit'],1),
          copy_7d=round(copy_7d,1), trap=round(w['realized_profit']-copy_7d,1))

# ── 8. Verdict ────────────────────────────────────────────────
ts, cs = track_disp, copy_disp
if dev is not None:
    ds = round((dev.get('score') or 0) * 100)
    if ds < 40:
        v_emoji, v_text = "🔴", _( f"发币方钱包,Dev 信誉仅 {ds}/100(连环 rug / 存活率低)—— 别碰它发的新盘。",
                                    f"Token-creator wallet, Dev reputation only {ds}/100 (serial-rug / low survival) — stay away from its new launches.")
    else:
        v_emoji, v_text = "🟡", _( f"发币方钱包,Dev 信誉 {ds}/100 —— 看它的存活率与安全记录再决定是否跟它的新盘。",
                                    f"Token-creator wallet, Dev reputation {ds}/100 — check survival rate and security record before following its new launches.")
elif ts >= 65 and cs < 35:
    v_emoji, v_text = "⚠️", _( "高战绩、低可跟单 —— 学它的止损纪律,别抄它的入场;延迟和滑点会把薄利吃成负。",
                                "High track record, low copy-tradeability — learn the stop-loss discipline, don't copy the entries; latency and slippage will turn thin profit negative.")
elif ts >= 60 and cs >= 55:
    v_emoji, v_text = "🟢", _( "战绩真实且可跟单性高 —— 低频、进场不算太早,值得小额跟一跟验证。",
                                "Genuine track record and high copy-tradeability — low frequency, entries not too early, worth a small copy-trade to verify.")
elif ts < 40:
    v_emoji, v_text = "🔴", _("战绩一般偏弱 —— 不建议作为跟单对象。", "Weak track record — not recommended as a copy-trade target.")
else:
    v_emoji, v_text = "🟡", _( "战绩中等 —— 可观察,跟单前先小额验证延迟/滑点损耗。",
                                "Middling track record — worth watching; verify latency/slippage cost with a small trade before copying.")

# ══════════════════════════════════════════════════════════
# OUTPUT
# ══════════════════════════════════════════════════════════
title = _("跟单评分", "Copy-Trade Score")
print(f"┌{'─'*56}┐")
print(f"│{('  '+title):^56}│")
print(f"│{('  '+WALLET[:6]+'...'+WALLET[-4:]+'  ·  '+CHAIN.upper()):^56}│")
if w['identity']:
    print(f"│{('  '+w['identity']):^56}│")
print(f"└{'─'*56}┘")
print()

sec = _("📊 近7天战绩", "📊 7D Trading Stats")
print(f"━━  {sec}  {'━'*(54-len(sec))}")
print(f"  {_('已实现盈亏','Realized P&L')} {usd(w['realized_profit'])}   ROI {w['roi']*100:+.1f}%   "
      f"{_('胜率','Win rate')} {w['winrate']*100:.0f}%   {_('笔数','Trades')} {trades} ({buy}{_('买','buy')}/{sell}{_('卖','sell')})")
print(f"  {_('交易币数','Tokens traded')} {token_num}   {_('均持仓','Avg hold')} {fmt_dur(w['avg_hold_s'])}   "
      f"{_('均单笔建仓','Avg position')} {usd(w['avg_buy_usd'])}")
print()

sec = _("🏷️ 风格标签", "🏷️ Style Tags")
print(f"━━  {sec}  {'━'*(54-len(sec))}")
for t in tags:
    print(f"  {t['emoji']}  {t['text']}")
print()

if self_dealing:
    print(f"  ⚠️ {_('该钱包主要在发币而非交易——下面两个分已按 ×0.45 打折显示,仅供参考。','This wallet mostly launches tokens rather than trading — the two scores below are shown at a ×0.45 discount for reference only.')}")
    print()

sec = _("🎯 真实战绩分(这交易员是不是真有本事)", "🎯 Track-Record Score (is this trader actually good?)")
print(f"━━  {sec}  {'━'*(max(0,54-len(sec)))}")
print(f"  {track_disp}/100")
for k, v in track_facs.items():
    print(f"    · {TRACK_LABELS[k]:14s}  {round(100*_clamp(v)):3d}/100   ({_('权重','w')} {TRACK_W[k]:.0%})")
print()

sec = _("🚀 可跟单分(你跟进后能拿到多少)", "🚀 Copy-Tradeability Score (can YOU capture it?)")
print(f"━━  {sec}  {'━'*(max(0,54-len(sec)))}")
print(f"  {copy_disp}/100")
for k, v in copy_facs.items():
    print(f"    · {COPY_LABELS[k]:14s}  {round(100*v):3d}/100   ({_('权重','w')} {COPY_W[k]:.0%})")
print()

sec = _("🧮 跟单回测", "🧮 Copy-Trade Backtest")
print(f"━━  {sec}  {'━'*(max(0,54-len(sec)))}")
print(f"  {_('假设','Assuming')}: {_('延迟','latency')} {LATENCY_S:.1f}s   {_('单边滑点','one-sided slippage')} {SLIPPAGE_PCT:.1%}   {_('每笔gas','gas/trade')} {usd(GAS_USD)}")
print(f"  {_('钱包本人单笔收益率','Wallet per-trade return')}  {bt['wallet_pct']*100:+.1f}%")
print(f"  {_('- 延迟漂移','- latency drift')}  {bt['drift']*100:.2f}pp    {_('- 双边滑点','- round-trip slippage')}  {bt['slip']*100:.2f}pp    {_('- gas占比','- gas cost')}  {bt['gas_pct']*100:.2f}pp")
print(f"  {_('= 跟单后单笔收益率','= your per-trade return')}  {bt['copy_pct']*100:+.1f}%")
print()
print(f"  7D  {_('钱包本人','wallet')} {usd(bt['wallet_7d'])}  →  {_('跟单预估','copy estimate')} {usd(bt['copy_7d'])}   ({_('抄单损耗','execution drag')} {usd(bt['trap'])})")
if not has_mcap_data:
    print(f"  ⚠️ {_('未取到进场市值数据,延迟漂移按中性假设估算,可能失真','Entry mcap data unavailable — latency drift uses a neutral assumption and may be inaccurate')}")
print()

if dev is not None:
    sec = _("👨‍💻 Dev 信誉分(该钱包作为发币方)", "👨‍💻 Dev Reputation (as a token creator)")
    print(f"━━  {sec}  {'━'*(max(0,54-len(sec)))}")
    ds = round((dev.get('score') or 0) * 100)
    print(f"  {ds}/100")
    print(f"  {_('已开外盘','Graduated')} {dev['open_count']}   {_('卡在内盘','Stuck on curve')} {dev['inner_count']}   "
          f"{_('抽样存活率','Sampled survival')} {round((1-dev['rug_rate'])*100)}% ({dev['alive']}/{dev['analyzed']})")
    print(f"  {_('历史最高市值','All-time-high mcap')} {usd(dev['ath_mc'])}")
    if dev['sec_checked']:
        print(f"  {_('最近发币安全扫描','Recent launches security scan')}: {dev['sec_unsafe']}/{dev['sec_checked']} {_('未过检','failed check')}")
    print()

sec = _("✅ 结论", "✅ Verdict")
print(f"━━  {sec}  {'━'*(max(0,54-len(sec)))}")
print(f"  {v_emoji}  {v_text}")
PYEOF

フィールドリファレンス

スクリプトが読み取るフィールドです。portfolio stats/portfolio activity/portfolio created-tokens/token security の出力と照合済みです(全項目については、gmgn-portfolio と gmgn-token を参照してください)。

SourceFieldMeaning
portfolio statsrealized_profit、winrate、pnl_stat.token_num、pnl_stat.avg_holding_period基本的な損益分布
portfolio statspnl_stat.pnl_gt_5x_num / pnl_2x_5x_num / pnl_0x_2x_num / pnl_nd5_0x_num / pnl_lt_nd5_num損益の区分別分布:>500% / 200–500% / 0–200% / -50–0% / <-50%
portfolio statscommon.created_token_countトークン作成者(「Dev」)のウォレットを検出するために使用
portfolio activitytoken.address、timestamp、price_usd保有期間と短期売買率の検出に使用
portfolio created-tokensopen_count、inner_count、open_ratio、creator_ath_info.ath_mcDev のローンチ履歴における生存統計
portfolio created-tokenstokens[].is_open、tokens[].pool_liquidity、tokens[].create_timestamp、tokens[].token_addressローンチごとの存続/ラグ判定
token securityis_honeypot、renounced_mint、renounced_freeze_account(SOL)、open_source(EVM)Dev の評判を評価するための最近のローンチに対するセキュリティスキャン

防御的に使用され、すべての API レスポンスに存在するとは限らないフィールド — これらがない場合、スクリプトは失敗せずに適切に縮退します(Notes を参照)。

FieldUsed forDegrade behavior if missing
アクティビティ行の token.total_supplyエントリー時の時価総額の推定(price_usd × total_supply)entry_under_100k/median_entry_mcap は 0 にフォールバックします。コピートレード可能性のエントリー要因とバックテストの価格乖離には、中立的な仮定が使われます
アクティビティ行の gas_usd平均ガスコストの表示0 にフォールバックします。バックテストでは引き続き、ユーザー指定の --gas が使われます
アクティビティ行の event_type買い/売りの分類gmgn-portfolio のリファレンスに従い、type フィールドにフォールバックします

スコアリングルールのリファレンス

トラックレコードスコア(0~100、加重合計) — 単純な勝率ではなく、規律あるリスク管理を評価します。

要素重み測定内容
損切りの規律34%1 − (tokens down >50%) / total
利益の割合28%何らかの水準で最終的に純利益となったトークンの割合
資本ROI16%realized_profit / bought_cost。−5%→0、+30%→100に正規化
勝率10%0%→0、50%→100に正規化(重みは低め — 損切りの規律が優れていれば、勝率が低くても高スコアになることがあります)
サンプルサイズ12%取引した個別トークンが少ないウォレットへの信頼度割引(20→0、320→100)

コピートレード適性スコア(0~100、加重合計) — 実環境のレイテンシーに耐えられないスタイルを減点します。

要素重み測定内容
エントリー時の時価総額22%エントリーが遅いほど高スコア — 10万ドル未満でのエントリーは、ウォレットがすでにポジションを保有した後に買うことを意味します
取引あたりの利益22%取引あたりの平均利益がわずか(約30~80ドル未満)だと、スリッページとガス代で利益が失われます
保有期間とレイテンシー20%5秒以内の高い反転率と、平均保有期間が非常に短い場合の両方を減点します — それほど速く対応することはできません
約定の実現可能性18%取引回数が非常に多い場合(ボット水準、週あたり約1000回超)を減点します — 人間が追いつくことはできません
エッジの種類18%速度や規模に依存するエッジ(早期エントリー、素早い反転)は学習・コピーできません。銘柄選定やタイミングのエッジは可能です

開発者評価スコア(0~100、ウォレットがトークン作成者の場合のみ適用) — 生存率を基準にします。

  • 基本値:0.25 + 0.55 × survival_rate(生存 = 流動性が4,000ドル以上でまだ取引可能なトークン。トークンごとのデータがない場合は open_ratio)
  • − 0.30 × 結合曲線への過度な集中に対するペナルティ(inner_count が50を超える場合。上限は1000 — ファクトリー型/連続ローンチ型ウォレットの特徴です)
  • + 0.15 × 優れた史上最高値ローンチへのボーナス。ただし生存率を条件とします(ファクトリー型ウォレットが一度だけ幸運な急騰を起こしても加点されません)
  • − 0.35 × 最近スキャンされたローンチのうち、基本的なセキュリティチェックに失敗した割合に対するペナルティ
  • 自己取引による割引:ウォレットが開発者(Dev)に分類された場合(自身のローンチが取引したトークンの半数を超える場合)、トラックレコードスコアとコピートレード適性スコアは × 0.45 で表示されます — 自身のエントリータイミングと勝率は自ら作り出したものであり、市場の読みではありません。

判定ルール

条件判定
ウォレットが開発者(Dev)で、開発者評価スコアが40未満🔴 新規ローンチには近づかない
ウォレットが開発者(Dev)で、開発者評価スコアが40以上🟡 新規ローンチを追う前に、生存率/セキュリティ履歴を確認する
トラックレコード ≥65、コピートレード適性 <35⚠️ 規律は参考にし、エントリーはコピーしない
トラックレコード ≥60、コピートレード適性 ≥55🟢 少額のコピートレードで検証する価値があります
トラックレコード <40🔴 コピートレード対象として推奨しません
上記以外🟡 監視する価値があります。まず少額の取引でレイテンシー/スリッページのコストを確認してください

対応チェーン

sol / bsc / base / eth / arbitrum / hyperevm / robinhood / arc / stable

前提条件

  • gmgn-cli がグローバルにインストールされていること — インストールされていない場合は、次を実行します:npm install -g gmgn-cli
  • GMGN_API_KEY が ~/.config/gmgn/.env に設定されていること(認証情報のみで、秘密鍵はこのスキルに必要ありません)

レート制限への対処

このスキルが呼び出すすべてのルートでは、GMGN のプランに基づくリーキーバケット方式が使用されています。Free は 5/5、Plus は 20/20、Pro は 50/50(レート/容量)です。継続的なスループットはおよそ tier rate ÷ weight リクエスト/秒、最大バーストはおよそ floor(tier capacity ÷ weight) です。すべてのルートで exist auth を使用します(API Key のみで、秘密鍵は不要です)。

コマンドルート重み
portfolio statsGET /v1/user/wallet_stats3
portfolio activityGET /v1/user/wallet_activity3
portfolio created-tokensGET /v1/user/created_tokens2
token securityGET /v1/token/security1

このスキルのスクリプトを1回実行すると、次の呼び出しが連続し、レート制限を超えることがあります。portfolio stats × 1回 + portfolio activity 最大4回(ページネーション) + portfolio created-tokens × 1回 + token security 最大3回(Dev セキュリティスキャン)。最後の2種類の呼び出しが発生するのは、ウォレットが Dev と判定された場合のみです。複数のウォレットを続けて一括スコアリングする前に、これらの合計重みを考慮してください。

リクエストが 429 を返した場合は処理を停止し、いつ再試行できるかを正確にユーザーに伝えてください。決して黙って失敗させたり、何も伝えずに再試行し続けたりしてはいけません。

  • RATE_LIMIT_EXCEEDED の場合は、ユーザーに次の内容を正確に伝えてください。已达到当前套餐的限频上限,点击 https://gmgn.ai/ai?chain=bsc&tab=paid_plans 升级套餐,获得更高速率限制。このアップグレード案内は、ユーザーのタスクごとに最大1回だけ表示してください。同じクールダウン期間中に後続の RATE_LIMIT_BANNED 応答があっても、繰り返し表示しないでください。

  • リセット時刻を取得します。レスポンスヘッダーの X-RateLimit-Reset(Unix タイムスタンプ)を読み取るか、レスポンス本文に reset_at(例:{"code":429,"error":"RATE_LIMIT_BANNED","message":"...","reset_at":1775184222})が含まれている場合は、こちらを使用してください。これは BAN が解除される時刻を示す Unix タイムスタンプです(通常、BAN は5分間です)。

  • 取得したタイムスタンプをユーザーの現地時刻に変換し、たとえば 「レート制限中です。14:32:05 以降にこのウォレットを再試行できます(約4分後)。」 のように明確に伝えてください。実行が一部成功している場合も必ず伝えてください(以下を参照)。残りの分析を再開できる時刻をユーザーが知る必要があります。

  • 最初からやり直さず、途中から再開してください。スクリプトの実行中(例:portfolio stats と activity が成功した後、Dev のセキュリティスキャンが 429 を受け取った場合)であれば、すでに取得できた情報を報告してください(stats/タグ/トラックレコードスコアを表示できます)。残りの呼び出しについてリセット時刻を伝え、リセット時刻を過ぎた後は、すでに取得したデータを再取得せず、残りの呼び出しだけを再実行してください。

  • RATE_LIMIT_EXCEEDED または RATE_LIMIT_BANNED の場合、クールダウン中のリクエストを繰り返すたびに、BAN は最大5分まで5秒ずつ延長されます。再試行をループしてはいけません。再試行する前に、提示したリセット時刻まで待ってください。

  • RATE_LIMIT_BANNED_MAX の場合、クールダウン中にリクエストが続いたため、BAN が5分の上限に達しています。このタスクでは、失敗した呼び出しだけでなく、GMGN への呼び出しをすべて停止し、正確なリセット時刻をユーザーに伝えてください。reset_at より前にリクエストすると、BAN は再び5分間にリセットされます。

  • 403 PLAN_UPGRADE_REQUIRED の場合、そのエンドポイントはユーザーのプラン容量を超えるレート制限の重みを必要とするため、このプランでは成功しません。再試行したり待ったりせず、そのエンドポイントは現在のプランでは利用できないことをユーザーに伝え、レスポンスの upgrade_message(アップグレードリンク https://gmgn.ai/ai?chain=bsc&tab=paid_plans を含みます)を表示してください。

  • 1回のリクエストで複数のウォレットをスコアリングする場合(リーダーボード形式の比較)は、呼び出しの間隔を空けるか、--sample を減らしてください。すべてのウォレットについて完全な分析を同時に実行してはいけません。バッチ内のいずれかのウォレットでレート制限が発生した場合は、完了したウォレットの結果をすぐに報告し、残りの結果がいつ準備できるかをユーザーに伝えてください。バッチ全体の処理を黙って保留してはいけません。

注意事項

  • このスキルはデータ(portfolio stats/activity/created-tokens、token security)を読み取るだけで、取引は実行しません。実際にコピー取引を行う場合は、このスキルで🟢の判定が出た後にgmgn-swapを使用してください。
  • 7日間のウィンドウで算出したスコアは、取引数が少ないウォレットではばらつきが大きくなる場合があります。トラックレコードスコアではサンプルサイズの要素によってすでに割り引かれていますが、取引数が10件未満で算出されたスコアは信頼度が低いものとして扱い、その旨を伝えてください。
  • バックテスト(--latency/--slippage/--gas)は概算であり、正確なシミュレーションではありません。実際のスリッページは、取引したであろう時点における対象トークンの流動性によって異なります。
  • 開発者の評判は、created_token_countがウォレットの取引対象トークン数の半分を超える場合にのみ算出されます。通常の取引が中心で、たまたまトークンを1つローンチしたウォレットは開発者とは見なされず、通常どおり取引スタイルのタグとスコアが適用されます。
  • 派生フィールドを信頼する前に生のレスポンスを自分で確認したい場合は、基盤となるgmgn-cliの任意のコマンドで--rawを使用し、1行のJSONを取得してください。

参考資料

スキル説明
gmgn-portfolio基盤となるportfolio stats/activity/created-tokensコマンドと、すべてのフィールドのリファレンス
gmgn-token開発者の評判を確認するセキュリティスキャンで使用するtoken securityコマンド
gmgn-trackスコアリング対象の候補ウォレットを見つけます — スマートマネー/KOL/フォロー中のウォレットの取引フィード
gmgn-swapこのスキルの判定が🟢になった後に実際のコピー取引を実行します

コメント