exchange.IO("lp.add", ...)
按计划建仓:需要时先换币,再授权,一笔交易创建计划中的全部仓位,等交易上链后返回仓位ID。
exchange.IO(k, plan)示例
-
在选中的池子里建一个现价±5%的仓位。
javascriptfunction main() { var sel = exchange.IO("lp.select", "ETH", 200, {quote: "USDC"}) var params = {symbol: "ETH_USDC", budget: 200, pool: sel.id, range: {pct: [-5, 5]}} var plan = exchange.IO("lp.plan", params) if (!plan || plan.shortfall) { Log("不能建仓:", plan ? plan.shortfall : GetLastError()) return } var r = exchange.IO("lp.add", plan) if (!r) { Log("建仓失败:", GetLastError()) return } r.positions.forEach(function (p) { Log("仓位", p.id, "区间", p.priceLower, "~", p.priceUpper, "投入", p.amountBase, "ETH +", p.amountQuote, "USDC") }) } -
完整的LP策略:选池、计划、模拟、建仓,每分钟检查仓位,手续费够多就领取,连续在区间外就撤出并重新建仓。
javascript// 区间流动性的完整流程:选池 → 计划 → 模拟 → 建仓 → 监控 → 领手续费 → 出区间后撤出重建 var SYMBOL = "ETH_USDC" var BUDGET = 200 // 每次建仓的预算(USDC) var RANGE = {pct: [-5, 5]} // 现价±5% var OUT_LIMIT = 30 // 连续这么多次(分钟)在区间外就撤出重建 var MIN_FEES = 5 // 未领手续费超过这么多USDC时领取 function open() { var sel = exchange.IO("lp.select", "ETH", BUDGET, {quote: "USDC"}) if (!sel) { Log("选池失败:", GetLastError()) return null } var plan = exchange.IO("lp.plan", {symbol: SYMBOL, budget: BUDGET, pool: sel.id, range: RANGE}) if (!plan || plan.shortfall) { Log("不能建仓:", plan ? plan.shortfall : GetLastError()) return null } Log("池子", plan.pool, plan.protocol, "预估日手续费", plan.estDailyFeesUsd, "美元") // 需要先换币的计划,模拟时钱包里还没有换来的币,跳过模拟(lp.add会先换币) if (!plan.swap && !exchange.IO("lp.simulate", plan)) { Log("模拟失败:", GetLastError()) return null } var r = exchange.IO("lp.add", plan) if (!r) { Log("建仓失败:", GetLastError()) return null } var ids = r.positions.map(function (p) { return p.id }) _G("lpIds", ids) // 记下仓位ID,重启后用lp.track找回 Log("建仓完成", ids, "交易", r.txHash) return ids } function main() { var ids = _G("lpIds") if (ids) { exchange.IO("lp.track", ids) // V4/Infinity CL、Aerodrome/Velodrome的仓位靠它在重启后找回 } var outCount = 0 while (true) { if (!ids) { ids = open() if (!ids) { Sleep(60000) continue } } var mine = (exchange.IO("lp.positions", SYMBOL) || []).filter(function (p) { return ids.indexOf(p.id) >= 0 && !p.closed }) if (mine.length == 0) { ids = null continue } var fees = 0 var inRange = false mine.forEach(function (p) { fees += p.feesValue inRange = inRange || p.inRange }) LogStatus(_D(), "仓位", ids, inRange ? "区间内" : "区间外", "未领手续费", fees, "USDC") if (fees > MIN_FEES) { exchange.IO("lp.collect", ids) } outCount = inRange ? 0 : outCount + 1 if (outCount >= OUT_LIMIT) { // 全部撤出并把拿回的ETH卖成USDC,下一轮重新建仓 var r = exchange.IO("lp.remove", ids, 1, true) Log("撤出", r ? r.txHash : GetLastError()) ids = null _G("lpIds", null) outCount = 0 } Sleep(60000) } }
返回值
| 类型 | 描述 |
object / 空值 |
|
参数
| 名称 | 类型 | 必填 | 描述 |
k | string | 是 | 固定为 |
plan | object | 是 |
|
参考
Uniswap / PancakeSwap Aerodrome / Velodrome exchange.IO exchange.IO("lp.plan") exchange.IO("lp.simulate") exchange.IO("lp.positions") exchange.IO("lp.collect") exchange.IO("lp.remove") exchange.IO("lp.track") exchange.IO("approve") DEX策略示例
备注
执行步骤:按最新池价重算计划,计划有shortfall时报错、不发交易 → 需要时先用市价单换币(swapTxHash) → 授权:V3、Slipstream授权PositionManager,V4/Infinity CL先授权Permit2、再在Permit2里给PositionManager额度,授权方式按exchange.IO("approve")的exact/max → 一笔交易创建全部仓位,等上链后返回。换币之后的步骤失败时,报错里说明换币已经发生(换来的币留在钱包里)。
价格保护与付款:每个仓位的付款上限为计划量加滑点(计划的slippage,默认1%,不超过钱包可用量),执行时价格变动使投入量低于计划量×(1−滑点)则整笔回滚(只损失gas)。V3、Slipstream池子交易对写原生币时按计划的payWith付包装币那一侧:整笔原生币(随交易附带,多的由refundETH退回)或整笔包装币;之后领回来的是包装币。V4/Infinity CL的原生币池子用原生币余额、领回原生币。建仓时原生币按当前gas价留出约300万gas的手续费。
仓位ID:V3为PositionManager的NFT编号(如1381183);Uniswap V4为v4:编号,PancakeSwap Infinity CL为inf:编号;Aerodrome为aero:编号(老工厂)、aero2:编号(新工厂),Velodrome为velo:编号、velo2:编号;基础池子没有NFT,仓位ID为池子地址。V4/Infinity CL与Slipstream仓位在节点查不到完整历史日志时,重启后要用exchange.IO("lp.track", ids)登记才能被lp.positions找到,建议把返回的ID存下来(如用_G())。
写指令在交易上链后返回;之后一分钟内的链上读取都不早于该交易所在的区块,紧接着调用lp.positions等指令能看到新仓位。
Aerodrome、Velodrome的基础池子经Router存入(交易对写原生币时用原生币余额,addLiquidityETH),返回{txHash, swapTxHash, pool, id, lpTokens, amountBase, amountQuote};计划的stake为true时结果另有stake(同exchange.IO("lp.stake")的返回)。
出区间后自动调仓、Aerodrome与Velodrome的gauge质押复投等可以直接运行的完整策略见DEX策略示例。