feat(market): add min/max range filters to trending command

- Register dynamic --min-*/--max-* flags on `market trending`, mirroring the
  trenches pattern; forwarded as min_<metric>/max_<metric> query params to
  /v1/market/rank
- Add RANK_RANGE_FIELDS covering the 18 numeric metrics plus min/max-created
  token-age windows (m/h/d duration strings, passed through as-is)
- Document range filters and web-client filter aliases in SKILL.md, cli-usage.md,
  Readme.md and Readme.zh.md

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
David Lau
2026-06-25 20:30:25 +08:00
co-authored by Claude Opus 4.8
parent 4205bda0af
commit ca55a80d12
5 changed files with 148 additions and 17 deletions
+7
View File
@@ -461,6 +461,13 @@ gmgn-cli market trending \
--order-by volume --limit 20 \
--filter not_risk --filter not_honeypot
# Trending with numeric range filters (min_*/max_* are forwarded as query params)
gmgn-cli market trending \
--chain sol --interval 1h \
--min-liquidity 10000 --max-liquidity 1000000 \
--max-created 30m --min-smart-degen-count 1 \
--order-by volume --limit 30
gmgn-cli market trenches \
--chain sol \
--type new_creation --type near_completion --type completed \
+7
View File
@@ -491,6 +491,13 @@ gmgn-cli market trending \
--order-by volume --limit 20 \
--filter not_risk --filter not_honeypot
# 热门榜 + 数值范围过滤(min_*/max_* 以查询参数透传)
gmgn-cli market trending \
--chain sol --interval 1h \
--min-liquidity 10000 --max-liquidity 1000000 \
--max-created 30m --min-smart-degen-count 1 \
--order-by volume --limit 30
# 战壕新币列表
gmgn-cli market trenches \
--chain sol \
+6 -3
View File
@@ -120,18 +120,21 @@ npx gmgn-cli market trending \
[--direction asc|desc] \
[--filter <tag>] \
[--platform <name>] \
[--min-<metric> <n>] [--max-<metric> <n>] \
[--raw]
```
| Option | Required | Description |
|--------|----------|-------------|
| `--chain` | Yes | `sol` / `bsc` / `base` |
| `--interval` | Yes | `1h` / `3h` / `6h` / `24h` |
| `--chain` | Yes | `sol` / `bsc` / `base` / `eth` |
| `--interval` | Yes | `1m` / `5m` / `1h` / `6h` / `24h` |
| `--limit` | No | Number of results (default 100, max 100) |
| `--order-by` | No | Sort field: `volume` / `swaps` / `liquidity` / `marketcap` / `holders` / `price` / `change` / `change1m` / `change5m` / `change1h` / `renowned_count` / `smart_degen_count` / `bluechip_owner_percentage` / `rank` / `creation_timestamp` / `square_mentions` / `history_highest_market_cap` / `gas_fee` |
| `--direction` | No | Sort direction: `asc` / `desc` (default `desc`) |
| `--filter` | No | Filter tag (repeatable): `has_social` / `not_risk` / `not_honeypot` / `verified` / `locked` / `renounced` / `distributed` / `frozen` / `burn` / `token_burnt` / `creator_hold` / `creator_close` / `creator_add_liquidity` / `creator_remove_liquidity` / `creator_sell` / `creator_buy` / `not_wash_trading` / `not_social_dup` / `not_image_dup` / `is_internal_market` / `is_out_market` |
| `--filter` | No | Filter tag (repeatable): `has_social` / `not_risk` / `not_honeypot` / `verified` / `locked` / `renounced` / `distributed` / `frozen` / `burn` / `token_burnt` / `creator_hold` / `creator_close` / `creator_add_liquidity` / `creator_remove_liquidity` / `creator_sell` / `creator_buy` / `not_wash_trading` / `not_social_dup` / `not_image_dup` / `is_internal_market` / `is_out_market`. The gmgn web client also sends aliases `social_not_duplicate` / `img_not_duplicate` / `is_burnt` / `launching` / `migrated`, which are accepted but only the canonical tags change behavior. |
| `--platform` | No | Platform filter (repeatable). Omit (or pass an empty list) to include **all** platforms. Available values depend on chain — see below. |
| `--min-<metric>` / `--max-<metric>` | No | Numeric range filters (inclusive). Supported metrics: `volume` / `liquidity` / `marketcap` / `history-highest-marketcap` / `swaps` / `holder-count` / `gas-fee` / `renowned-count` / `smart-degen-count` / `bot-degen-count` / `visiting-count` / `price-change-percent` / `insider-rate` / `bundler-rate` / `entrapment-ratio` / `top10-holder-rate` / `top70-sniper-hold-rate` / `dev-team-hold-rate`. Unknown metrics are ignored by the service. |
| `--min-created` / `--max-created` | No | Token-age window, duration string (`1m` / `6h` / `7d`). `--min-created` is a minimum age (excludes younger tokens); `--max-created` a maximum age (excludes older tokens). |
**`sol` platforms:** `Pump.fun` / `pump_mayhem` / `pump_mayhem_agent` / `pump_agent` / `letsbonk` / `bonkers` / `bags` / `memoo` / `liquid` / `bankr` / `zora` / `surge` / `anoncoin` / `moonshot_app` / `wendotdev` / `heaven` / `sugar` / `token_mill` / `believe` / `trendsfun` / `trends_fun` / `jup_studio` / `Moonshot` / `boop` / `xstocks` / `ray_launchpad` / `meteora_virtual_curve` / `pool_ray` / `pool_meteora` / `pool_pump_amm` / `pool_orca`
+48
View File
@@ -146,6 +146,32 @@ The response is an object with a `list` array. Each element in `list` is one can
| `--filter <tag...>` | Repeatable filter tags (chain-specific). **⚠️ SOL defaults: `renounced frozen`; BSC/Base/ETH defaults: `not_honeypot verified renounced`.** Omitting `--filter` is NOT "no filter" — chain defaults always apply. **sol** tags: `renounced` / `frozen` / `burn` / `token_burnt` / `has_social` / `not_social_dup` / `not_image_dup` / `dexscr_update_link` / `not_wash_trading` / `is_internal_market` / `is_out_market`. **evm** tags: `not_honeypot` / `verified` / `renounced` / `locked` / `token_burnt` / `has_social` / `not_social_dup` / `not_image_dup` / `dexscr_update_link` / `is_internal_market` / `is_out_market` |
| `--platform <name...>` | Repeatable platform filter (chain-specific). **sol**: `Pump.fun` / `pump_mayhem` / `pump_mayhem_agent` / `pump_agent` / `letsbonk` / `bonkers` / `bags` / `memoo` / `liquid` / `bankr` / `zora` / `surge` / `anoncoin` / `moonshot_app` / `wendotdev` / `heaven` / `sugar` / `token_mill` / `believe` / `trendsfun` / `trends_fun` / `jup_studio` / `Moonshot` / `boop` / `xstocks` / `ray_launchpad` / `meteora_virtual_curve` / `pool_ray` / `pool_meteora` / `pool_pump_amm` / `pool_orca`. **bsc**: `fourmeme` / `fourmeme_agent` / `bn_fourmeme` / `four_xmode_agent` / `cubepeg` / `likwid` / `goplus_creator` / `goplus_skills` / `openfour` / `flap` / `flap_stocks` / `flap_aioracle` / `clanker` / `lunafun` / `pool_uniswap` / `pool_pancake`. **base**: `clanker` / `bankr` / `flaunch` / `zora` / `zora_creator` / `baseapp` / `basememe` / `virtuals_v2` / `klik`. **eth**: `trench` / `clanker` / `klik` / `livo` / `stroid` / `pool_uniswap_v2` / `pool_uniswap_v3` / `printr` |
### `market trending` Range Filters
Optional `--min-*` / `--max-*` flags apply server-side numeric range filtering (inclusive). Unknown metrics are ignored by the service.
| Option | Description |
|--------|-------------|
| `--min-volume` / `--max-volume` | Trading volume (USD) |
| `--min-liquidity` / `--max-liquidity` | Liquidity (USD) |
| `--min-marketcap` / `--max-marketcap` | Market cap (USD) |
| `--min-history-highest-marketcap` / `--max-history-highest-marketcap` | Historical highest market cap (USD) |
| `--min-swaps` / `--max-swaps` | Swap count |
| `--min-holder-count` / `--max-holder-count` | Holder count |
| `--min-gas-fee` / `--max-gas-fee` | Gas fee |
| `--min-renowned-count` / `--max-renowned-count` | KOL / renowned wallet count |
| `--min-smart-degen-count` / `--max-smart-degen-count` | Smart-money holder count |
| `--min-bot-degen-count` / `--max-bot-degen-count` | Bot-degen wallet count |
| `--min-visiting-count` / `--max-visiting-count` | Visitor count |
| `--min-price-change-percent` / `--max-price-change-percent` | Price change ratio over the interval |
| `--min-insider-rate` / `--max-insider-rate` | Insider trading ratio (01); tokens lacking this field are excluded |
| `--min-bundler-rate` / `--max-bundler-rate` | Bundle-bot trading ratio (01); tokens lacking this field are excluded |
| `--min-entrapment-ratio` / `--max-entrapment-ratio` | Entrapment trading ratio (01); tokens lacking this field are excluded |
| `--min-top10-holder-rate` / `--max-top10-holder-rate` | Top-10 holder concentration (01) |
| `--min-top70-sniper-hold-rate` / `--max-top70-sniper-hold-rate` | Top-70 sniper holding ratio (01) |
| `--min-dev-team-hold-rate` / `--max-dev-team-hold-rate` | Dev-team holding ratio (01); `--min-dev-team-hold-rate` also excludes creator-close tokens |
| `--min-created` / `--max-created` | Token age window, duration string `1m` / `6h` / `7d`. `--min-created` is a minimum age (excludes younger tokens); `--max-created` a maximum age (excludes older tokens) |
## Usage Examples
### Kline
@@ -277,6 +303,28 @@ gmgn-cli market trending \
--order-by smart_degen_count --limit 20 --raw
```
### Trending — Numeric Range Filters
```bash
# SOL 1h trending — liquidity 10k1M, market cap above 50k, sorted by volume
gmgn-cli market trending \
--chain sol --interval 1h \
--min-liquidity 10000 --max-liquidity 1000000 --min-marketcap 50000 \
--order-by volume --limit 30 --raw
# SOL 5m hottest — fresh tokens (under 30 min old) with smart money interest
gmgn-cli market trending \
--chain sol --interval 5m \
--max-created 30m --min-smart-degen-count 1 \
--order-by volume --limit 50 --raw
# SOL 1h trending — exclude high-insider / high-bundler tokens
gmgn-cli market trending \
--chain sol --interval 1h \
--max-insider-rate 0.3 --max-bundler-rate 0.3 \
--order-by volume --limit 20 --raw
```
### Trending — Base by Launchpad Platform
```bash
+80 -14
View File
@@ -48,7 +48,7 @@ export function registerMarketCommands(program: Command): void {
printResult(data, opts.raw);
});
market
const trendingCmd = market
.command("trending")
.description("Get trending token swap data")
.requiredOption("--chain <chain>", "Chain: sol / bsc / base / eth")
@@ -58,20 +58,40 @@ export function registerMarketCommands(program: Command): void {
.option("--direction <dir>", "Sort direction: asc / desc")
.option("--filter <tag...>", "Filter tags, repeatable. sol: renounced / frozen / has_social / not_wash_trading / ... evm: not_honeypot / verified / renounced / locked / ... (see docs for full list)")
.option("--platform <name...>", "Platform filter, repeatable. sol: Pump.fun / letsbonk / moonshot_app / ... bsc: fourmeme / four_xmode_agent / cubepeg / likwid / goplus_creator / goplus_skills / openfour / flap / flap_stocks / flap_aioracle / clanker / ... base: clanker / flaunch / zora / ... eth: trench / clanker / klik / livo / stroid / pool_uniswap_v2 / pool_uniswap_v3 / printr (see docs for full list)")
.option("--raw", "Output raw JSON")
.action(async (opts) => {
validateChain(opts.chain);
const extra: Record<string, string | number | string[]> = {};
if (opts.limit != null) extra["limit"] = opts.limit;
if (opts.orderBy) extra["order_by"] = opts.orderBy;
if (opts.direction) extra["direction"] = opts.direction;
if (opts.filter?.length) extra["filters"] = opts.filter;
if (opts.platform?.length) extra["platforms"] = opts.platform;
.option("--raw", "Output raw JSON");
const client = new OpenApiClient(getConfig());
const data = await client.getTrendingSwaps(opts.chain, opts.interval, extra).catch(exitOnError);
printResult(data, opts.raw);
});
// Dynamically register all server-side min_*/max_* range filter flags
for (const def of RANK_RANGE_FIELDS) {
const flag = def.api.replace(/_/g, "-");
if (def.type === "int") {
trendingCmd.option(`--${flag} <${def.type}>`, def.desc, parseInt);
} else if (def.type === "float") {
trendingCmd.option(`--${flag} <${def.type}>`, def.desc, parseFloat);
} else {
trendingCmd.option(`--${flag} <value>`, def.desc);
}
}
trendingCmd.action(async (opts) => {
validateChain(opts.chain);
const extra: Record<string, string | number | string[]> = {};
if (opts.limit != null) extra["limit"] = opts.limit;
if (opts.orderBy) extra["order_by"] = opts.orderBy;
if (opts.direction) extra["direction"] = opts.direction;
if (opts.filter?.length) extra["filters"] = opts.filter;
if (opts.platform?.length) extra["platforms"] = opts.platform;
// Apply server-side min_*/max_* range filters
const optsMap = opts as Record<string, unknown>;
for (const def of RANK_RANGE_FIELDS) {
const val = optsMap[apiFieldToCliKey(def.api)];
if (val != null) extra[def.api] = val as string | number;
}
const client = new OpenApiClient(getConfig());
const data = await client.getTrendingSwaps(opts.chain, opts.interval, extra).catch(exitOnError);
printResult(data, opts.raw);
});
const trenchesCmd = market
.command("trenches")
@@ -270,6 +290,52 @@ const TRENCHES_FILTER_FIELDS: TrenchesFilterField[] = [
{ api: "max_tg_call_count", type: "int", desc: "Max Telegram call count" },
];
// Server-side numeric range filters for `market trending` (/v1/market/rank).
// Passed through as min_<metric>/max_<metric> query params; the service applies the
// metrics it understands and ignores the rest. min_created/max_created are token-age
// windows expressed as duration strings (e.g. 1m / 6h / 7d) — note these use m/h/d,
// NOT the s/m form used by trenches; min_created is a minimum age, max_created a maximum.
const RANK_RANGE_FIELDS: TrenchesFilterField[] = [
{ api: "min_volume", type: "float", desc: "Min trading volume (USD)" },
{ api: "max_volume", type: "float", desc: "Max trading volume (USD)" },
{ api: "min_liquidity", type: "float", desc: "Min liquidity (USD)" },
{ api: "max_liquidity", type: "float", desc: "Max liquidity (USD)" },
{ api: "min_marketcap", type: "float", desc: "Min market cap (USD)" },
{ api: "max_marketcap", type: "float", desc: "Max market cap (USD)" },
{ api: "min_history_highest_marketcap", type: "float", desc: "Min historical highest market cap (USD)" },
{ api: "max_history_highest_marketcap", type: "float", desc: "Max historical highest market cap (USD)" },
{ api: "min_swaps", type: "int", desc: "Min swap count" },
{ api: "max_swaps", type: "int", desc: "Max swap count" },
{ api: "min_holder_count", type: "int", desc: "Min holder count" },
{ api: "max_holder_count", type: "int", desc: "Max holder count" },
{ api: "min_gas_fee", type: "float", desc: "Min gas fee" },
{ api: "max_gas_fee", type: "float", desc: "Max gas fee" },
{ api: "min_renowned_count", type: "int", desc: "Min KOL / renowned wallet count" },
{ api: "max_renowned_count", type: "int", desc: "Max KOL / renowned wallet count" },
{ api: "min_smart_degen_count", type: "int", desc: "Min smart-money holder count" },
{ api: "max_smart_degen_count", type: "int", desc: "Max smart-money holder count" },
{ api: "min_bot_degen_count", type: "int", desc: "Min bot-degen wallet count" },
{ api: "max_bot_degen_count", type: "int", desc: "Max bot-degen wallet count" },
{ api: "min_visiting_count", type: "int", desc: "Min visitor count" },
{ api: "max_visiting_count", type: "int", desc: "Max visitor count" },
{ api: "min_price_change_percent", type: "float", desc: "Min price change ratio over the interval" },
{ api: "max_price_change_percent", type: "float", desc: "Max price change ratio over the interval" },
{ api: "min_insider_rate", type: "float", desc: "Min insider trading ratio (01); tokens lacking this field are excluded" },
{ api: "max_insider_rate", type: "float", desc: "Max insider trading ratio (01); tokens lacking this field are excluded" },
{ api: "min_bundler_rate", type: "float", desc: "Min bundle-bot trading ratio (01); tokens lacking this field are excluded" },
{ api: "max_bundler_rate", type: "float", desc: "Max bundle-bot trading ratio (01); tokens lacking this field are excluded" },
{ api: "min_entrapment_ratio", type: "float", desc: "Min entrapment trading ratio (01); tokens lacking this field are excluded" },
{ api: "max_entrapment_ratio", type: "float", desc: "Max entrapment trading ratio (01); tokens lacking this field are excluded" },
{ api: "min_top10_holder_rate", type: "float", desc: "Min top-10 holder concentration (01)" },
{ api: "max_top10_holder_rate", type: "float", desc: "Max top-10 holder concentration (01)" },
{ api: "min_top70_sniper_hold_rate", type: "float", desc: "Min top-70 sniper holding ratio (01)" },
{ api: "max_top70_sniper_hold_rate", type: "float", desc: "Max top-70 sniper holding ratio (01)" },
{ api: "min_dev_team_hold_rate", type: "float", desc: "Min dev-team holding ratio (01); also excludes creator-close tokens" },
{ api: "max_dev_team_hold_rate", type: "float", desc: "Max dev-team holding ratio (01)" },
{ api: "min_created", type: "string", desc: "Min token age (minimum age), duration string e.g. 1m / 6h / 7d" },
{ api: "max_created", type: "string", desc: "Max token age (maximum age), duration string e.g. 600m / 24h" },
];
// Named filter presets using actual server-side API field names
const TRENCHES_FILTER_PRESETS: Record<string, Record<string, number | string>> = {
safe: {