关于 gmgn-wallet-score
gmgn-wallet-score 是一项 Agent 技能,用于评估钱包的交易记录、跟单可行性,以及主要发币钱包的开发者信誉。它根据 GMGN 数据计算评分和经延迟、滑点及 Gas 调整的回测结果,区分交易表现与跟单可行性。
主要功能
- 01基于结果分布的交易记录评分
- 02衡量执行限制的跟单可行性评分
- 03按延迟、滑点和 Gas 调整回测
- 04面向代币创建者的开发者信誉分析
- 05无交易历史时避免强行评分
适用场景
- 01评估钱包的交易盈利表现
- 02判断某钱包是否适合跟单
- 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 命令。不要使用网页搜索、WebFetch、curl,也不要访问 gmgn.ai——该网站需要登录,且不提供结构化数据。
重要:不要猜测字段名称或值。以下字段参考中未列出的字段尚未通过实时 API 确认——如果脚本的防御性查找未能获取这些字段的值,请优雅降级(见说明),不要捏造值。
⚠️ 不支持 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,然后运行以下分析脚本——无论用户实际询问的是哪一项,该脚本都会基于一次共享的数据拉取,计算全部三个方面(盈利能力、跟单交易适用性、Dev 声誉)。同时识别用户使用的语言:如果用户用中文书写,将 LANG 设为 'zh';如果用英文书写,则设为 'en'(默认值为 'zh')。仅当用户想要自定义回测时,才询问 --latency / --slippage / --gas——否则使用脚本中预设的默认值。脚本运行后,确定回复中应优先介绍哪个部分——见下文根据问题组织报告。
核心概念
此技能刻意将“这个交易者表现好吗?”与“你能跟单吗?”分开——一个钱包可能交易表现出色,却很难跟单;也可能交易表现平平,却很容易跟着操作:
- 战绩评分(0–100) — 这个交易者的实际表现如何?评判依据是结果分布,而不只是胜率。一个胜率为 30% 的钱包仍可能获得高分,因为它会果断止损,而且那 30% 的盈利足以弥补其余亏损——这体现的是有纪律的风险管理,而不是运气。
- 跟单可行性评分(0–100) — 即使这个钱包确实很有本事,你能抓住这份优势吗?如果一个钱包专门抢购市值低于 $100k 的代币,或在 5 秒内完成买入再卖出,那就无法跟单——等你的交易成交时,钱包早已退出,而你成了接盘流动性。此评分会惩罚过早进场、单笔交易利润空间太薄、持仓时间极短,以及机器人级别的交易频率。
- 回测估算 — 一个具体的“你实际能留下多少”数值:钱包的原始收益减去你的进场延迟造成的价格偏移、往返滑点和 Gas 费用——使用用户指定的延迟/滑点/Gas 参数(或合理的默认值)。
- 开发者信誉评分 — 仅在钱包本身是代币创建者时计算(其
created_token_count超过交易代币数量的一半——也就是说,它主要在发币,而非交易)。评估的是钱包作为开发者的表现:其创建的代币存活率、有多少代币卡在联合曲线上而始终未能毕业,以及它近期发行的代币是否通过基本的安全检查。出现这种情况时,进场时机/胜率等因素并不可靠(钱包可以控制自己代币的价格和持有者名单)——战绩和跟单可行性评分将大幅折扣显示,结论则转而侧重开发者信誉。 - 没有交易历史 — 如果该钱包在采样期间没有买入/卖出交易(只有转账/空投),就无法据实计算上述指标。脚本会检测这种情况并明确报告,而不是强行给空数据打分。
按问题组织报告
此技能通过一次共享的数据拉取,回答三个相关但不同的问题——无论用户询问哪个角度,都始终运行完整的分析脚本;底层的 portfolio stats/activity/created-tokens/token security 调用不会改变,改变的只有回复中优先呈现的内容。切勿针对不同角度重复运行脚本——那只会浪费额外的速率限制额度,重新获取已有数据。
| 用户的问题 | 优先呈现 | 仍需提及,但简要说明 |
|---|---|---|
| 盈利能力——“这个钱包盈利能力怎么样”,“钱包战绩怎么样”,“is this wallet profitable” | 🎯 战绩评分 + 影响因素 | 风格标签。除非钱包最终被判定为开发者(见下文),否则跳过回测和开发者部分 |
| 跟单价值——“值不值得跟单”,“is this wallet worth copying”,“if I copy this wallet what's my real return” | 🚀 跟单可行性评分 + 🧮 回测 | 作为背景信息的战绩评分,以及最终结论 |
| 开发者/发盘信誉——“这个钱包发盘情况怎么样”,“是不是发币方钱包”,“dev 信誉怎么样”,“is this a token-creator wallet” | 👨💻 开发者信誉部分 | 说明这会折扣另外两项评分(自我交易)——无需详细介绍其影响因素 |
| 通用问题——“帮我看看这个钱包”,“钱包评分”,或仅提供地址而未指明具体角度 | 完整报告,包含所有部分 | — |
安全覆盖规则:如果用户询问盈利能力或跟单可行性,但钱包最终被归类为开发者钱包(dev is not None),仍需优先展示开发者信誉部分和自我交易折扣提示——无论用户询问的是哪个角度,主要用于发币的钱包都需要附上此说明,因为这会影响另外两项评分的可信度。
分析脚本
内联运行此 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):
| 来源 | 字段 | 含义 |
|---|---|---|
portfolio stats | realized_profit、winrate、pnl_stat.token_num、pnl_stat.avg_holding_period | 核心结果分布 |
portfolio stats | pnl_stat.pnl_gt_5x_num / pnl_2x_5x_num / pnl_0x_2x_num / pnl_nd5_0x_num / pnl_lt_nd5_num | 分档 P&L 分布:>500% / 200–500% / 0–200% / -50–0% / <-50% |
portfolio stats | common.created_token_count | 用于检测代币创建者(“Dev”)钱包 |
portfolio activity | token.address、timestamp、price_usd | 用于检测持仓时长和快速买卖频率 |
portfolio created-tokens | open_count、inner_count、open_ratio、creator_ath_info.ath_mc | Dev 历史发币存续统计 |
portfolio created-tokens | tokens[].is_open、tokens[].pool_liquidity、tokens[].create_timestamp、tokens[].token_address | 对每次发币进行存续/跑路分类 |
token security | is_honeypot、renounced_mint、renounced_freeze_account(SOL)、open_source(EVM) | 对近期发行的代币进行安全扫描,以评估 Dev 声誉 |
以防御性方式使用、但并非每个 API 响应都保证包含的字段——如果缺少这些字段,脚本会优雅降级(参见备注),而不会报错:
| 字段 | 用途 | 缺失时的降级行为 |
|---|---|---|
活动记录中的 token.total_supply | 估算入场市值(price_usd × total_supply) | entry_under_100k / median_entry_mcap 回退为 0——复制交易可行性的入场因子和回测漂移采用中性假设 |
活动记录中的 gas_usd | 显示平均 Gas 成本 | 回退为 0;回测仍使用用户指定的 --gas |
活动记录中的 event_type | 买入/卖出分类 | 按照 gmgn-portfolio 参考,回退使用 type 字段 |
评分规则参考
战绩评分(0–100,加权总分)——奖励严谨的风险管理,而非单纯追求胜率:
| 因素 | 权重 | 衡量内容 |
|---|---|---|
| 止损纪律 | 34% | 1 − (tokens down >50%) / total |
| 盈利占比 | 28% | 最终在任意程度上实现净盈利的代币比例 |
| 资金 ROI | 16% | realized_profit / bought_cost,按 −5%→0、+30%→100 进行归一化 |
| 胜率 | 10% | 按 0%→0、50%→100 进行归一化(权重较低——即使胜率低,只要止损纪律出色,仍可能获得高分) |
| 样本量 | 12% | 对交易过的不同代币数量较少的钱包进行置信度折扣(20→0,320→100) |
跟单适配度评分(0–100,加权总分)——对无法承受现实延迟的交易风格予以扣分:
| 因素 | 权重 | 衡量内容 |
|---|---|---|
| 入场市值 | 22% | 越晚入场得分越高——入场市值低于 $100k,意味着你买入时该钱包已经持有仓位 |
| 单笔利润 | 22% | 单笔平均利润过低(< ~$30–80)会被滑点和 Gas 费吞噬 |
| 持仓时间与延迟 | 20% | 对较高的 5 秒内翻仓率和极短的平均持仓时间都会扣分——你无法那么快做出反应 |
| 执行可行性 | 18% | 对交易次数极高的情况扣分(机器人级别,> ~1000/周)——没有人能跟上这种节奏 |
| 优势类型 | 18% | 依赖速度/规模的优势(早期入场、快速翻仓)无法学习或跟单;基于筛选/时机的优势则可以 |
开发者信誉评分(0–100,仅适用于代币创建者钱包)——由存活率决定:
- 基础分:
0.25 + 0.55 × survival_rate(存活 = 流动性仍 ≥$4,000 的未关闭代币;如果没有逐代币数据,则使用open_ratio) − 0.30 ×对大量堆积在 bonding curve 上的情况扣分(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 - 已在
~/.config/gmgn/.env中配置GMGN_API_KEY(仅需存在身份验证——此技能无需私钥)
速率限制处理
此技能调用的所有路由均使用 GMGN 基于套餐的漏桶限流:Free 5/5、Plus 20/20、Pro 50/50(速率/容量)。持续吞吐量约为 套餐速率 ÷ 权重 次请求/秒,最大突发请求数约为 floor(套餐容量 ÷ 权重)。所有路由都使用 exist auth(仅需 API Key,无需私钥)。
| 命令 | 路由 | 权重 |
|---|---|---|
portfolio stats | GET /v1/user/wallet_stats | 3 |
portfolio activity | GET /v1/user/wallet_activity | 3 |
portfolio created-tokens | GET /v1/user/created_tokens | 2 |
token security | GET /v1/token/security | 1 |
此技能的脚本单次运行可能会连续消耗多项额度:1 次 portfolio stats + 最多 4 次 portfolio activity(分页)+ 1 次 portfolio created-tokens + 最多 3 次 token security(Dev 安全扫描)——只有当钱包被归类为 Dev 时,最后两项才会触发。连续批量评分多个钱包前,请先计算这些请求的总权重。
请求返回 429 时,请停止请求并主动明确告知用户何时可以重试——绝不能默默失败,也不能一言不发地持续重试。
-
收到
RATE_LIMIT_EXCEEDED时,准确告知用户:已达到当前套餐的限频上限,点击 https://gmgn.ai/ai?chain=bsc&tab=paid_plans 升级套餐,获得更高速率限制。每项用户任务最多显示一次此升级指引。同一冷却期间后续收到RATE_LIMIT_BANNED时,不要重复显示。 -
提取限流解除时间:读取响应头中的
X-RateLimit-Reset(Unix 时间戳);或者,如果响应正文包含reset_at(例如{"code":429,"error":"RATE_LIMIT_BANNED","message":"...","reset_at":1775184222}),则使用该值——它表示封禁解除的 Unix 时间戳(封禁通常持续 5 分钟)。 -
将获取到的时间戳转换为用户的本地时间,并清楚地说明,例如:“已触发速率限制——此钱包可在 14:32:05 后重试(约 4 分钟后)。” 即使本次运行已部分成功,也要这样告知(见下文)——用户需要知道何时可以继续剩余分析。
-
对于
RATE_LIMIT_EXCEEDED或RATE_LIMIT_BANNED,冷却期间的重复请求每次都会延长封禁 5 秒,最长可达 5 分钟。绝不要循环重试——请等到所述解除时间后再试。 -
收到
RATE_LIMIT_BANNED_MAX时,说明因冷却期间仍有请求,封禁已达到 5 分钟上限。请停止本次任务中的所有 GMGN 调用(不只是失败的调用),并告知用户准确的解除时间;在reset_at之前发起的任何请求都会使封禁重新延长至 5 分钟。 -
收到
403 PLAN_UPGRADE_REQUIRED时,说明该端点的速率限制权重超过了用户套餐的容量,因此在当前套餐下永远无法成功——不要重试或等待。请告知用户当前套餐无法使用该端点,并显示响应中的upgrade_message(其中包含升级链接https://gmgn.ai/ai?chain=bsc&tab=paid_plans)。 -
在单次请求中为多个钱包评分(类似排行榜的对比)时,应错开调用时间,或调低
--sample,不要并发执行所有钱包的完整分析。如果批次中某个钱包触发速率限制,请立即报告已完成的钱包,并告知用户其余结果何时可用,不要默默搁置整个批次。
注意事项
- 此技能仅读取数据(
portfolio stats/activity/created-tokens、token security),绝不会执行交易。若要实际跟单交易,请在此技能给出 🟢 结论后使用 gmgn-swap。 - 交易次数较少的钱包,其 7 天窗口评分可能不稳定——交易记录评分中的样本量因素已对此进行折减,但若评分基于少于 10 笔交易,应将其视为低置信度并明确说明。
- 回测(
--latency/--slippage/--gas)只是粗略估算,并非精确模拟——实际滑点取决于你进行交易时该代币的具体流动性。 - 只有当
created_token_count超过该钱包交易代币数量的一半时,才会计算开发者声誉;如果某个钱包只是顺手发行过一个代币,而大多数时候都在正常交易,则不会被视为开发者,其交易风格标签和评分会照常适用。 - 对任何底层
gmgn-cli命令使用--raw,即可获取单行 JSON;如果想在信任派生字段前自行检查原始响应,可使用此选项。
参考
| 技能 | 描述 |
|---|---|
| gmgn-portfolio | 底层 portfolio stats / activity / created-tokens 命令及完整字段参考 |
| gmgn-token | 用于开发者声誉安全扫描的 token security 命令 |
| gmgn-track | 发现待评分的候选钱包——Smart Money / KOL / 已关注钱包交易动态 |
| gmgn-swap | 此技能给出 🟢 结论后执行实际跟单交易 |