LPSignal 能做什么
LPSignal 监控 Ethereum、BNB Chain、Base、Arbitrum、Optimism 和 Polygon 上的集中流动性池(Uniswap v3 与 v4、PancakeSwap v3、Aerodrome 与 Velodrome Slipstream)。它每小时针对一组价格区间,计算在每个池中做 LP 扣除无常损失 (IL) 后本可获得的收益。当某个区间过去一周收益良好、今天仍在持续产生收益时,你会收到一条信号,其中给出应开立的精确仓位。
池中的两种代币都必须在我们的蓝筹列表中,因此你不会看到新币或流动性差的代币的信号。Uniswap v4 池仅在无 hook 且费率固定时才会纳入。
净 APR 如何计算
净 APR = 手续费 APR − 无常损失 (IL),由基于该池自身逐小时历史数据的回测年化得出。(API 中该损失以负值 ilApr 表示,因此 netApr = feeApr + ilApr。)
- 手续费来自池合约的手续费增长(fee growth)计数器,在每小时结束时读取。这是每单位区间内流动性实际赚取的精确金额,包含动态费率。我们从不用交易量估算的手续费来触发信号。
- 无常损失是仓位价值相对于直接持有你所存入的两种代币的差额,两者都按窗口最后一个小时的收盘价计价。
- 区间内时间占比是价格处于区间内的小时数占比。区间外的小时没有收益,但仍计入平均值。
每个池按其类型测试相应的区间宽度。±5% 的区间表示仓位覆盖从入场价格下方 5% 到上方 5% 的价格,并按该池的 tick spacing 向外取整。
| 池类型 | 示例 | 测试区间 |
|---|---|---|
| 稳定型 | USDT/USDC | ±0.05% · ±0.1% · ±0.5% |
| 相关型 | wstETH/WETH, cbBTC/WETH | ±0.5% · ±1% · ±3% |
| 波动型 | WETH/USDC, WBNB/USDT | ±5% · ±10% · ±20% · full range |
统计窗口为最近 24 小时、7 天和 30 天。Aerodrome 和 Velodrome 的数据针对未质押的流动性。若在 gauge 中质押可获得排放奖励,我们会在池旁显示该收益率供参考,但它不会触发信号,也不参与回测。
信号何时触发
在整点时,若某个池的某一区间同时满足以下全部条件,该池即触发机会信号:
| 条件 | 稳定型 | 相关型 | 波动型 |
|---|---|---|---|
| 最近 7 天净 APR | ≥ 8% | ≥ 10% | ≥ 30% |
| 最近 24 小时净 APR | ≥ 8% | ≥ 10% | ≥ 30% |
| 7 天区间内时间占比 | ≥ 80% | ||
- 只计入精确的手续费数据。若某个窗口缺少手续费增长读数,该池不会触发。
- 若有多个区间符合条件,你只会收到 7 天净 APR 最高的那个。
- 触发信号后,同一个池会静默 72 小时,除非其 7 天净 APR 升至上次信号时的 1.5 倍。
- 落后超过 2 小时的链在追上进度前不会触发任何信号,因此你不会收到基于过时数据的信号。
短时机会
有些区间会在一两个小时内收益很高——一段剧烈行情、新的激励、一波集中的交易——远早于 7 天平均值反映出来。短时机会信号(kind: "burst")在整点时,某个区间同时满足以下全部条件时触发:
| 条件 | 稳定型 | 相关型 | 波动型 |
|---|---|---|---|
| 最近 1 小时净 APR | ≥ 30% | ≥ 45% | ≥ 110% |
| 这 1 小时内在区间内的时间 | ≥ 67% | ||
| 这 1 小时的成交笔数(单笔大额成交不算高峰) | ≥ 3 | ||
- 和所有机会信号一样,只计入精确的手续费数据。
- 同一小时已触发 7 天信号的池子不会再触发短时机会。短时机会触发后,该池 12 小时内保持静默,除非其 1 小时净 APR 再提高一半。
- 每条短时机会在触发后的 24 小时评分(7 天信号为触发后 7 天),有单独的历史表现:
GET /v1/signals/stats?kind=burst。 - 信号在其描述的整点之后 30–90 分钟到达(归档只包含已最终确认的区块)。1 小时的高峰可能在你操作前就已结束。数据按小时汇总,因此最短窗口是 1 小时。
高收益 · 高风险
DEX 网站按池子整体 APR 给池子排名:最近 24 小时的交易手续费除以池子的流动性,再年化。高收益信号(kind: "hot_pool")在大池子的这个数字暴涨时触发,条件是整点时以下全部满足:
| 条件 | 阈值 |
|---|---|
| 最近 24 小时池子整体手续费 APR | ≥ 50% |
| 相对该池自己的 7 天平均 | ≥ 2× |
| 池子 TVL(3 小时内刷新过) | ≥ $500k |
- 手续费按成交量 × 池子费率估算,用最新价格折算。Uniswap v4 池子不参与:它的 TVL 是价格附近的深度,会把这个数字算高。
- 这是池子整体的数据,不是推荐仓位(
tickLower和tickUpper为 0),也不参与评分。旁边的bestNet24h是同一天里我们最好的区间扣除无常损失后的实际净 APR(bestRangeBp说明是哪个区间),没有 24 小时回测时为 null。 - 同一个池子 24 小时内只触发一次。付费套餐实时收到,免费套餐 24 小时后,与其他机会信号相同。
历史表现
机会信号触发 7 天后,我们会用这 7 天的数据回测它推荐的精确 tick,并将结果公布在该信号上。每个信号都会如此,包括亏损的信号。
- 结果:实际实现的净 APR,以及其中的手续费和无常损失 (IL) 部分。
- 待定:尚未满 7 天。
- 未评分:该周的精确手续费数据始终没有到位。这类信号仍会显示,但不计入平均值,因此历史表现绝不会用估算值充数。
风险提醒
所有套餐的风险提醒均免费且实时推送。
| 提醒 | 触发条件 |
|---|---|
| 流动性流出 | 池的流动性在 3 小时内下降 30% 或以上,且链上实时状态连续两次读数(间隔至少一分钟)都如此(撤出后下一个区块又加回的不算)。按当前价格计价,因此代币价格下跌本身不会触发。仅适用于规模高于最低门槛的池。 |
| 脱锚 | 在稳定型或相关型交易对上,链上实时价格连续两次读数(间隔至少一分钟)均偏离 7 天中位数至少 0.5% 且方向相同(或最近两个小时收盘价均如此)。偏离达 2% 时标记为严重。 |
聪明 LP 评分
我们在 Uniswap v3、PancakeSwap v3 和 Slipstream 上追踪仓位 NFT,从存入一直到最终提取。仓位关闭时,其得分为所有者收回的金额减去继续持有所存代币的价值,均按该次提取时刻的池价格计价。
- 钱包需至少有 3 次已评分的平仓、资金达 $10,000,才会进入排名。
- 由多个钱包出资、中途转给新所有者、在 gauge 中质押,或开仓后才首次被发现的仓位不参与评分。
- 机器人和金库不参与排名:合约地址、每天平仓超过 10 次的钱包,以及仓位持有时间中位数不到 1 小时的钱包。信号在开仓后几分钟到一小时才能到达,来不及跟随它们。你关注的钱包仍会照常提醒。
- 当排名前 20 的钱包或你关注的钱包开立 $10,000 及以上的仓位时,Pro 会员会收到提醒。
套餐与延迟
| 免费版 | Basic | Pro | |
|---|---|---|---|
| 机会信号 | 触发 24 小时后 | 实时 | 实时 |
| 短时机会 | 触发 24 小时后 | 实时(需订阅) | 实时(需订阅) |
| 高收益 · 高风险 | 触发 24 小时后 | 实时(需订阅) | 实时(需订阅) |
| 风险提醒 | 实时 | 实时 | 实时 |
| 聪明 LP 提醒与完整排行榜 | – | – | 支持 |
| Telegram | 支持(延迟) | 支持 | 支持 |
| Webhook、WebSocket、API 密钥 | – | 支持 | 支持 |
| 自定义规则 | – | 3 | 20 |
每条消息发送时都会重新检查套餐权限,因此套餐到期前排队的信号不会在到期后送达。
自定义规则
Basic(3 条规则)和 Pro(20 条规则)可以设置你自己的阈值。每条规则每小时都会用与默认信号相同的精确数据检查一次。命中只发送给你,通过你连接的所有渠道推送;它带有 rule 字段(规则的 id 和名称),不参与评分,也不显示在公开的历史表现中。
| kind | 阈值 | 默认信号(作为参考) |
|---|---|---|
| net_apr | minNet7d、minNet24h(默认等于 minNet7d)、minInRange7d(默认 0.8) | 按池子类型分别为 8% / 10% / 30%,区间内时间 80% |
| depeg | minDeviation(0.001–0.5),仅限稳定型和相关型交易对 | 0.5% |
| tvl_outflow | minDrop(0.05–0.95)、windowHours(1–24,默认 3) | 3 小时内下降 30% |
任何规则都可以用 chains、pairClasses、pools("chain:address",最多 50 个)和 minTvlUsd(至少 100,000,默认 1,000,000)缩小范围。cooldownHours(24–720,默认 24)让规则触发后在同一池子上保持静默。每个账户每 24 小时最多 50 次命中。
无论订阅了哪些类型,规则命中都会推送(只想通过规则接收的类型可以退订)。如果某次命中与你已收到的同一池子、同一小时的信号重复,会被跳过。降级后规则会保留,但只有套餐允许数量内的规则会运行(active)。
推送给你的信号
Telegram、webhook 和 WebSocket 信号流推送你订阅的信号类型。核心事件默认开启:net_apr、tvl_outflow、depeg 和 smart_lp。短时机会 burst 和高收益 hot_pool 需要你添加后才会推送。能收到哪些仍由套餐决定,自定义规则的命中总会推送。
可在账户页面修改,或调用 PUT /v1/me/subscriptions 并传入 {"kinds": ["net_apr", "burst", "depeg"]}。WebSocket 连接可以用 ?kinds=net_apr,burst 单独指定类型,REST 也接受同样的 kinds 参数;source=subscribed 返回的正是你的推送渠道收到的内容。
SDK
官方 SDK 支持 Node.js (20+) 和 Python (3.10+),封装了完整的 API、信号流和 webhook 验证,以 MIT 许可证开源:LPSignals/lpsignal-node 与 LPSignals/lpsignal-python。
npm install lpsignal
pip install lpsignal
- REST 客户端:响应带类型,API 错误码以异常抛出,触发频率限制后自动重试。
- 不漏任何信号的信号流。每次连接前,SDK 会先通过 REST 拉取比你上一条信号更新的全部信号,再从其后续接信号流。任意时长的中断都会被补齐,进程运行期间每条信号只送达一次。进程崩溃后最后一条可能重复送达,因此请按 id 幂等处理信号。
- 重启后续接。用内置的文件存储保存最后一条信号的 id;Node 与 Python 使用相同的文件格式。
- Webhook 验证:一次调用完成,返回解析后的投递内容。
认证
REST API 地址为 https://api.lpsignal.app/v1。公开接口无需密钥。以 bearer token 方式发送密钥,即可按你的套餐查看信号并使用账户接口。密钥以 lps_ 开头,可在账户页面创建或替换密钥。
import { LPSignal } from 'lpsignal'; const lps = new LPSignal({ apiKey: process.env.LPSIGNAL_API_KEY }); const { signals, next } = await lps.signals({ kind: 'net_apr', limit: 20 }); // next page: lps.signals({ kind: 'net_apr', before: next })
import os from lpsignal import LPSignal lps = LPSignal(api_key=os.environ["LPSIGNAL_API_KEY"]) page = lps.signals(kind="net_apr", limit=20) # next page: lps.signals(kind="net_apr", before=page["next"])
curl "https://api.lpsignal.app/v1/signals?kind=net_apr&limit=20" \ -H "Authorization: Bearer lps_live_..."
匿名及免费版调用方只能看到触发已满 24 小时的机会信号。风险提醒始终包含在内。
频率限制与错误
| 限制项 | 请求数 / 分钟 |
|---|---|
| 每个 API 密钥 | 300 |
| 无密钥时,每个 IP | 120 |
| 全部流量,每个 IP | 600 |
区间回测(/backtest) | 30 |
超出限制时返回 429,并附带 Retry-After 响应头。错误以 JSON 返回,带有稳定的 error 错误码:
| 状态码 | error | 含义 |
|---|---|---|
| 400 | invalid_request | 参数校验失败。issues 列出每个出错字段。 |
| 401 | invalid_api_key | 密钥错误或已被替换。 |
| 403 | pro_required | 该接口需要 Pro 套餐。 |
| 404 | pool_not_found | 该链上没有这个被追踪的池。 |
| 429 | rate_limited | 请等待 retryAfterSec 秒。 |
| 500 | internal_error | 服务端出错。联系支持时请附上 requestId。 |
接口
| 路径 | 密钥 | 返回 | |
|---|---|---|---|
| GET | /v1/chains | – | 各链的扫描进度与池数量 |
| GET | /v1/pools? | – | 各池及其最佳区间。window = 1、24、168 或 720 小时。sort = netApr(默认)、feeApr、ilApr、inRange、emissionApr、tvl、fee、volume24h、fees24h 或 poolApr;order = desc(默认)或 asc。每个池带 volume24hUsd 与 fees24hUsd(过去 24 小时交易手续费的估算:成交量 × 当前费率,未扣协议分成;动态费率池为近似值)以及 best.net24h(同一区间的 24 小时净 APR),还有 poolApr24h:DEX 网站上常见的池子 24h APR(估算 24h 手续费 ÷ TVL × 365,不计无常损失;Uniswap v4 为 null);minPoolApr 只保留不低于该值的池子(0.3 = 30%)。返回 total,用 offset 翻页。TVL 低于 $10k 的池默认不列出,除非 minTvlUsd = 0(几乎没有流动性的池,其按流动性折算的 APR 没有意义) |
| GET | /v1/pools/:chain/:address | – | 单个池的全部区间 × 窗口指标。v4 池使用其 32 字节的 pool id |
| GET | /v1/pools/:chain/:address/hours? | – | 逐小时手续费、交易量和价格,最多 720 小时 |
| GET | /v1/pools/:chain/:address/backtest? | – | 回测任意区间(0 = 全区间),最长 30 天 |
| GET | /v1/signals? | 可选 | kinds = 多个类型,例如 net_apr,burst。默认按时间倒序,用上一次响应中的 next 值作为 before 翻页,或用 offset 翻页(此时返回 total);或 sort = return(信号发出时的 APR)或 outcome(7 天实际结果),order = desc(默认)或 asc,用 offset 翻页(返回 total;没有该数值的信号排在最后)。source = default(全局信号)、rules(你的规则命中)或 subscribed(与推送渠道收到的完全一致) |
| GET | /v1/signals/stats? | – | kind 为 net_apr(默认)或 burst 的历史表现 |
| GET | /v1/signals/:id | 可选 | 单条信号 |
| GET | /v1/smart-lps? | 可选 | 排行榜。sort = rank(默认,即盈亏排名)、pnl、return、capital、closes、wins、apr 或 winRate;无论怎么排,rank 始终是盈亏排名。每个钱包带 aprVsHold(相对持有的年化超额收益,按资金 × 占用时间计)、winRate 与 avgHoldH。返回 total。非 Pro:仅前 10 名,隐藏地址 |
| GET | /v1/smart-lps/:owner/positions? | Pro | 某个钱包的未平仓与已平仓仓位,两个列表各自分页、排序:openSort = lastEvent、openedAt 或 entryUsd;closedSort = closedAt、openedAt、capitalUsd 或 pnlUsd;总数见 openTotal / closedTotal;未平仓仓位带 poolTick(所在池当前的 tick) |
| GET | /v1/me | 需要 | 你的套餐与已连接的通知渠道 |
| PUT | /v1/me/webhook | 需要 | 设置 webhook URL。签名密钥仅返回一次 |
| DELETE | /v1/me/webhook | 需要 | 删除 webhook |
| POST | /v1/me/telegram-link | 需要 | 用于 Telegram 机器人的一次性代码 |
| GET | /v1/me/follows | Pro | 你关注的钱包 |
| PUT | /v1/me/follows/:owner | Pro | 关注钱包(最多 50 个) |
| DELETE | /v1/me/follows/:owner | Pro | 取消关注 |
| GET | /v1/me/rules | 需要 | 你的规则、套餐上限和最近 24 小时的命中次数 |
| POST | /v1/me/rules | 付费版 | 创建规则(Basic:3 条,Pro:20 条) |
| PUT | /v1/me/rules/:id | 需要 | 替换一条规则 |
| DELETE | /v1/me/rules/:id | 需要 | 删除一条规则 |
| PUT | /v1/me/subscriptions | 需要 | 推送给你的信号类型 |
| WS | /v1/stream? | 付费版 | 实时信号,见下文 |
所有 APR 与比率字段均为小数:0.345 表示 34.5%。fee 的单位是百分之一基点,因此 500 表示 0.05% 费率档。时间戳为 UTC 的 ISO 8601 格式。id 均为字符串。
信号对象
REST、webhook 和信号流都以如下结构发送信号。
{ "id": "4821", "kind": "net_apr", "firedAt": "2026-09-30T09:00:00.000Z", "chain": "base", "dex": "uniswap_v3", "pool": "0xd0b53d9277642d899df5c87a3966a349a798f224", "pair": "WETH/USDC", "fee": 500, "pairClass": "volatile", "tvlUsd": 38200000, "rangeBp": 500, "entryTick": -200720, "tickLower": -201210, "tickUpper": -200230, "net24h": 0.41, "net7d": 0.345, "net30d": 0.298, "fee7d": 0.518, "il7d": -0.173, "inRange7d": 0.94, "rule": null, "outcome": null }
| 字段 | 含义 |
|---|---|
| kind | net_apr 机会信号,burst 短时机会,hot_pool 高收益(高风险),tvl_outflow 或 depeg 风险提醒,smart_lp 钱包入场 |
| rule | 当信号由你的某条自定义规则产生时存在:{ id, name }。其他信号为 null。 |
| tickLower, tickUpper | 推荐的仓位。可直接使用这些 tick,它们已按该池的 tick spacing 对齐。 |
| rangeBp | 所测试的区间档位,以价格的基点表示(500 = ±5%,0 = 全区间) |
| fee7d, il7d | 7 天净 APR 的两个组成部分。il7d 是相对于持有两种代币的损失,因此为零或负值:net7d = fee7d + il7d。API 中所有的 feeApr、ilApr 和 netApr 均同理。 |
| net24h, net7d, net30d | 该仓位在各窗口内的净 APR。net30d 在该池积累满 30 天历史前为 null。 |
| stakedEmissionApr | 仅限 Slipstream:若改为质押,按当前速率可获得的 gauge 排放收益。仅供参考。 |
| outcome | 评分前为 null,之后为 { status, netApr, feeApr, ilApr, evaluatedAt }。status 为 done 或 inexact。 |
风险提醒包含 drop、tvlBeforeUsd、tvlNowUsd 和 windowHours(流动性流出)或 deviation 和 severe(脱锚)。聪明 LP 提醒包含 owner、rank、top、entryUsd 和 wallet30d。高收益信号包含 poolApr24h、poolApr7d、volume24hUsd、fees24hUsd、bestNet24h、bestRangeBp 和 risk。
Webhook
每条信号以 {"type": "signal", "deliveryId": "…", "signal": {…}} 的形式 POST 到你的 URL,并附带以下请求头:
| x-lpsignal-delivery | 每次投递唯一。同一信号可能到达不止一次,请跳过已处理过的 id。 |
| x-lpsignal-timestamp | 签名该请求时的 Unix 时间戳(秒) |
| x-lpsignal-signature | sha256= 前缀加上用你的签名密钥对 "<timestamp>.<raw body>" 计算的 HMAC-SHA256 |
import express from 'express'; import { verifyWebhook, WebhookVerificationError } from 'lpsignal'; app.post('/lpsignal', express.raw({ type: 'application/json' }), (req, res) => { try { const event = verifyWebhook(req.body, req.headers, process.env.LPSIGNAL_WEBHOOK_SECRET); // at-least-once: skip event.deliveryId if you have already handled it res.sendStatus(200); } catch (e) { if (e instanceof WebhookVerificationError) return res.status(400).send(e.reason); throw e; } });
from fastapi import FastAPI, Request, Response from lpsignal import WebhookVerificationError, verify_webhook app = FastAPI() @app.post("/lpsignal") async def lpsignal(request: Request): try: event = verify_webhook(await request.body(), request.headers, SECRET) except WebhookVerificationError as e: return Response(e.reason, status_code=400) # at-least-once: skip event["deliveryId"] if you have already handled it return Response(status_code=200)
import crypto from 'node:crypto'; // without the SDK: use the raw request body, re-serialised JSON will not match function verify(rawBody, headers, secret) { const ts = headers['x-lpsignal-timestamp']; if (Math.abs(Date.now() / 1000 - Number(ts)) > 300) return false; // replay window const want = 'sha256=' + crypto.createHmac('sha256', secret).update(`${ts}.`).update(rawBody).digest('hex'); const got = headers['x-lpsignal-signature'] ?? ''; return got.length === want.length && crypto.timingSafeEqual(Buffer.from(got), Buffer.from(want)); }
- 请在 15 秒内返回任意 2xx 响应。不会跟随重定向。
- 投递失败后会在 1 分钟、5 分钟、30 分钟和 2 小时后依次重试,之后放弃。
- URL 必须为 HTTPS 且解析到公网地址,每次投递时都会重新检查。
WebSocket 信号流
SDK 会替你处理以下全部细节,包括中断超过 24 小时后的补齐。连接 wss://api.lpsignal.app/v1/stream,并在 Authorization 请求头中携带密钥,浏览器中则以 ?key= 传入。信号按 id 升序到达,同一连接上不会重复。保存你处理过的最后一个 id,并用 ?since=<id> 重连,即可补齐最近 24 小时内的全部信号。加上 ?kinds=net_apr,burst 可为单个连接指定类型;不加则按你订阅的类型推送。
import { LPSignal, SignalStream, FileLastIdStore } from 'lpsignal'; const stream = new SignalStream({ client: new LPSignal({ apiKey: process.env.LPSIGNAL_API_KEY }), store: new FileLastIdStore('./lpsignal-state.json'), // resume point survives restarts onSignal: async (signal, { source }) => { // once per signal, in id order; source = 'rest' | 'replay' | 'live' }, onEvent: (e) => { if (e.type === 'fatal') console.error(e.error.message); }, }); await stream.start();
import asyncio, os from lpsignal import AsyncLPSignal, FileLastIdStore, SignalStream async def on_signal(signal, source): # "rest" | "replay" | "live" ... async def main(): lps = AsyncLPSignal(api_key=os.environ["LPSIGNAL_API_KEY"]) await SignalStream(lps, on_signal, store=FileLastIdStore("lpsignal-state.json")).run() asyncio.run(main())
| 消息或关闭码 | 含义 |
|---|---|
| {"type":"signal"} | 一条信号。replay: true 表示这是 ?since= 之后的补齐消息。 |
| {"type":"ready"} | 补齐完成,此后均为实时消息。 |
| {"type":"replay_truncated"} | 待补齐的信号超过 1,000 条。请将 since 设为返回的 lastId 后重连。 |
| close 4409 | 补齐未能完成。请用你收到的最后一个 id 重连。 |
| close 4402 | 你的套餐已不再包含信号流。 |
| close 1001 | 服务器重启。请带上 since 重连。 |
服务器每 30 秒发送一次 ping,标准客户端会自动响应。
Telegram
- 在账户页面点击连接 Telegram,获取一次性代码。
- 打开 @LPsignals_bot 并发送
/start <code>。 - 此后你的套餐所包含的信号都会发送到该聊天。免费版在机会信号触发 24 小时后收到。