Cron 驱动的 Uniswap V3 集中流动性自动调仓机器人,运行在 EVM L2 链上,通过 onchainos CLI 执行 DeFi 操作。核心思路: 波动率决定范围宽度 — 低波动率时收紧范围提高资本效率,高波动率时放宽范围减少调仓频率和无常损失。
每个 tick:获取价格 → 波动率分析 → 范围计算 → 调仓决策 → 执行调仓 → 报告。
text
Cron (5min) → Python script → onchainos CLI → OKX Web3 API → Chain
↓ ↓
cl_lp_state.json Wallet (TEE signing)
↓
┌──────────────┐
│ Price Fetch │ ← onchainos swap quote / market price
│ K-line ATR │ ← onchainos market kline (1H × 24)
│ MTF Analysis │ ← price_history (288 bars = 24h)
└──────┬───────┘
↓
Range Calculation (vol-adaptive)
↓
Rebalance Decision
↓
┌──────────────┐
│ Claim Fees │ ← onchainos defi claim
│ Remove Liq │ ← onchainos defi redeem
│ Swap Ratio │ ← onchainos swap swap
│ Add Liq │ ← onchainos defi deposit
└──────┬───────┘
↓
Structured JSON output
OKX Skill Dependencies (via onchainos CLI — 处理认证、链解析、错误重试):
onchainos market price --address <token> --chain <chain>onchainos market kline --address <token> --chain <chain> --bar 1H --limit 24onchainos swap quote --from <A> --to <B> --amount <amt> --chain <chain>onchainos swap swap --from <A> --to <B> --amount <amt> --chain <chain> --wallet <addr> --slippage <pct>onchainos swap approve --token <addr> --amount <amt> --chain <chain>onchainos defi search --chain <chain> --token "<token0>,<token1>" --product-group DEX_POOLonchainos defi detail --investment-id <id> --chain <chain>onchainos defi calculate-entry --investment-id <id> --chain <chain> --tick-lower <tick> --tick-upper <tick>onchainos defi deposit --investment-id <id> --chain <chain> --amount0 <amt> --amount1 <amt> --tick-lower <tick> --tick-upper <tick>onchainos defi redeem --investment-id <id> --chain <chain> --token-id <id> --percent 100onchainos defi claim --investment-id <id> --chain <chain> --token-id <id>onchainos defi positions --chain <chain>onchainos defi position-detail --investment-id <id> --chain <chain> --token-id <id>When user has no config.json or asks to set up a new pool, trigger this step.
核心原则:AI 应从用户的自然语言中提取意图,自动推断尽可能多的参数,只在信息不足时才追问。
从用户输入中提取:
信息示例用户输入提取结果链"在 Base 上做 LP"chain = base代币对"ETH/USDC 的流动性"token0 = ETH, token1 = USDC风险偏好"稳定一点的"pool_type = stablecoinFee tier"0.3% 的池子"fee_tier = 0.3%
缺失信息的默认推断:
根据代币对自动分类, 不需要问用户:
类型判断规则默认参数集稳定币对两个都是稳定币(USDC/USDT/DAI/FRAX)窄范围、低止损Native/稳定币一个是 ETH/WETH/WBTC,另一个是稳定币标准参数非稳定币对两个都不是稳定币,但都是主流币宽范围、高止损含 Meme 币代币不在主流币列表中(市值低、无 Coingecko 排名)极宽范围 + 强制风险确认
主流币白名单:ETH, WETH, WBTC, USDC, USDT, DAI, FRAX, ARB, OP, MATIC, BNB, AVAX, SOL
仅当检测到 meme/低市值代币时触发。MUST display warning before proceeding:
text
⚠️ Meme 币 LP 额外风险:
1. 极端无常损失 — 价格可能单方向暴涨/暴跌 90%+
2. 流动性枯竭 — 池子 TVL 可能骤降,头寸无法退出
3. 合约风险 — 代币可能有 honeypot/税收/暂停转账等恶意机制
4. 调仓失败 — 低流动性导致 swap 滑点过大
Must get explicit user confirmation before proceeding.
bash
onchainos defi search --chain <chain> --token "<token0>,<token1>" --product-group DEX_POOL
自动选择逻辑(用户无需手动选):
rate 字段)Fee tier 参考(仅在用户问及或多池需选择时展示):
自动 fetch detail ( onchainos defi detail) 并生成 config, 无需用户手动填写。
字段映射:
investment_id ← search investmentIdchain_id ← search chainIndexplatform_id ← detail analysisPlatformId(注意不是 platformId)fee_tier ← search feeRatetick_spacing ← 根据 fee tier 推导:0.01%→1, 0.05%→10, 0.3%→60, 1%→200token0/token1 ← detail underlyingToken。如果 token 是 native ETH( 0xeee...),LP 合约用 WETH,需映射(Base: 0x4200000000000000000000000000000000000006)native_token ← 始终 0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee(用于 swap 和余额查询)json
{
"investment_id": "<auto>",
"pool_chain": "<auto>",
"chain_id": "<auto>",
"platform_id": "<auto>",
"native_token": "0xeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeeee",
"fee_tier": "<auto>",
"tick_spacing": "<auto>",
"token0": { "symbol": "<auto>", "address": "<auto>", "decimals": "<auto>" },
"token1": { "symbol": "<auto>", "address": "<auto>", "decimals": "<auto>" },
"range_mult": { "low": 1.0, "medium": 1.2, "high": 1.5, "extreme": 2.0 },
"min_range_pct": 2,
"max_range_pct": 5,
"asym_factor": 0.3,
"min_position_age_seconds": 3600,
"max_rebalances_24h": 6,
"gas_to_fee_ratio": 0.5,
"max_il_tolerance_pct": 5.0,
"edge_proximity_threshold": 0.15,
"stop_loss_pct": 0.15,
"trailing_stop_pct": 0.1,
"slippage_pct": 1,
"gas_reserve_eth": 0.02,
"min_trade_usd": 5.0,
"quiet_interval_seconds": 1800,
"max_consecutive_errors": 5,
"cooldown_after_errors_seconds": 3600
}
Pool-type-specific defaults(自动应用,无需用户选择):
Parameter稳定币对Native/稳定币非稳定币Meme 池min_range_pct0.5235max_range_pct25815range_mult.low0.51.01.21.5range_mult.extreme1.02.02.53.0stop_loss_pct0.050.150.200.30trailing_stop_pct0.030.100.150.20max_il_tolerance_pct1.05.08.015.0gas_reserve_eth0.0050.020.020.02
config.json written with valid investment_id, token0, token1.env configured (copy from .env.example, fill in API credentials + WALLET_ADDR)onchainos wallet balance returns valid balances for the target chainCRITICAL RULE: Steps MUST execute in order. Do NOT skip steps or proceed past a gate that has not been satisfied.
Actions:
onchainos market price or onchainos swap quoteonchainos defi position-detail (if position exists)price_history (append, cap at 288 = 24h @ 5min)onchainos wallet balanceGate (ALL must pass):
consecutive_errors < 5)stop_triggered == null)Actions:
Output: atr_pct float, vol_class string, mtf dict
Gate:
atr_pct is non-null and > 0vol_class is one of: low, medium, high, extrememtf dict has trend and strength fields (graceful fallback to neutral)Actions:
2 × ATR each side → tight range, max capital efficiency3 × ATR each side → balanced5 × ATR each side → wide range, fewer rebalances8 × ATR each side → safety firsttick_spacing)price / (upper - lower)Output: tick_lower, tick_upper, range_width_pct, capital_efficiency
Gate:
tick_lower < current_tick < tick_uppertick_spacingActions:
MIN_POSITION_AGE (2h)MAX_REBALANCES_24H (6/day)GAS_TO_FEE_RATIO × expected fees (50%)Gate:
Actions (if rebalancing):
onchainos defi claimonchainos defi redeem --percent 100onchainos defi calculate-entryonchainos swap swap (if needed)onchainos defi depositActions (always): 8. Calculate performance metrics (PnL, fees claimed, IL, time-in-range) 9. Build structured notification output (see Notification Tiers below)
Output: tiered notification JSON (via ---JSON--- block)
bash
which onchainos # must be installed
# Auth via environment variables
OKX_API_KEY=...
OKX_SECRET_KEY=...
OKX_PASSPHRASE=...
OperationCommandKey ParametersSearch Poolsonchainos defi search --chain base --token "ETH,USDC" --product-group DEX_POOLchain, token, product-groupPool Detailonchainos defi detail --investment-id <id> --chain baseinvestment-idCalculate Entryonchainos defi calculate-entry --investment-id <id> --chain base --tick-lower <t> --tick-upper <t>ticks, amountsDepositonchainos defi deposit --investment-id <id> --chain base --amount0 <a> --amount1 <a> --tick-lower <t> --tick-upper <t>amounts, ticksRedeemonchainos defi redeem --investment-id <id> --chain base --token-id <nft> --percent 100token-id, percentClaim Feesonchainos defi claim --investment-id <id> --chain base --token-id <nft>token-idMy Positionsonchainos defi positions --chain basechainPosition Detailonchainos defi position-detail --investment-id <id> --chain base --token-id <nft>token-id
OperationCommandKey ParametersGet Priceonchainos market price --address <token> --chain basetoken addressGet K-lineonchainos market kline --address <token> --chain base --bar 1H --limit 24bar size, limitSwap Quoteonchainos swap quote --from <A> --to <B> --amount <amt> --chain basetokens, amountExecute Swaponchainos swap swap --from <A> --to <B> --amount <amt> --chain base --wallet <addr> --slippage 1wallet, slippageApprove Tokenonchainos swap approve --token <addr> --amount <amt> --chain basetoken, amount
Every function returns (result, failure_info). Failure info is structured:
python
failure_info = {
"reason": str, # machine-readable: "claim_failed", "redeem_failed", "deposit_failed", etc.
"detail": str, # human-readable context
"retriable": bool, # safe to auto-retry?
"hint": str # "transient_api_error", "retry_with_fresh_quote", "insufficient_balance"
}
Auto-retry policy: 1 retry for retriable=True with 3s delay.
Rebalance failure fallback: if deposit fails after remove, emergency deploy at 3× normal width.
ParameterDefaultDescriptionVOL_MULTIPLIER_LOW``2.0ATR multiplier for low volatility (<1.5%)VOL_MULTIPLIER_MED``3.0ATR multiplier for medium volatility (1.5-3%)VOL_MULTIPLIER_HIGH``5.0ATR multiplier for high volatility (3-5%)VOL_MULTIPLIER_EXTREME``8.0ATR multiplier for extreme volatility (>5%)VOL_THRESHOLD_LOW``1.5Low/medium volatility boundary (%)VOL_THRESHOLD_HIGH``3.0Medium/high volatility boundary (%)VOL_THRESHOLD_EXTREME``5.0High/extreme volatility boundary (%)TREND_ASYM_FACTOR``0.3Max trend asymmetry ratio (0=symmetric, 1=fully asymmetric)TREND_ASYM_THRESHOLD``0.3Minimum trend strength to activate asymmetry
ParameterDefaultDescriptionMIN_RANGE_CHANGE_PCT``0.05Skip rebalance if new range <5% different
ParameterDefaultDescriptionMIN_POSITION_AGE``72002h minimum position hold time (seconds)MAX_REBALANCES_24H``6Maximum rebalances per 24h periodGAS_TO_FEE_RATIO``0.5Skip if gas > 50% of expected fees
ParameterDefaultDescriptionMTF_SHORT_PERIOD``55-bar EMA (25min @ 5min tick)MTF_MEDIUM_PERIOD``1212-bar EMA (1h @ 5min tick)MTF_LONG_PERIOD``4848-bar EMA (4h @ 5min tick)MTF_STRUCTURE_PERIOD``9696-bar (8h) for structure detection
ParameterDefaultDescriptionSTOP_LOSS_PCT``0.15Stop if portfolio drops 15% below cost basisTRAILING_STOP_PCT``0.10Stop if portfolio drops 10% from peakMAX_IL_TOLERANCE_PCT``0.05Hard stop if IL exceeds 5%MAX_CONSECUTIVE_ERRORS``5Circuit breaker thresholdCOOLDOWN_AFTER_ERRORS``36001h cooldown after circuit breaker tripsGAS_RESERVE``0.003Native token reserved for gas
ParameterDefaultDescriptionSLIPPAGE_PCT``1Slippage tolerance for swapsEMERGENCY_WIDTH_MULT``3.0Emergency fallback range = 3× normal widthDRY_RUN``falseFetch real data but simulate operations
text
[1] stop_triggered → refuse all operations, emit alert JSON
[2] circuit_breaker (consecutive_errors >= 5) → 1h cooldown, refuse
[3] data validation (price/balance/position null) → refuse
[4] stop-loss / trailing-stop / IL tolerance → set stop_triggered, alert
[5] rebalance frequency (>6/day) → skip rebalance
[6] position age (<2h) → skip rebalance
[7] gas cost check (>50% of expected fees) → skip rebalance
[8] minimum range change (<5%) → skip rebalance
[9] execute rebalance → success / failure with emergency fallback
CommandPurposeTriggerNotificationtick主循环:采集→分析→决策→执行,结果缓存到 _cached_snapshotCron 每 5min🔔 Trade Alert / ⚠️ Risk Alert / 📊 Hourly Pulsestatus用户查询入口:Portfolio 总览 + 头寸 + 收益 + 范围。优先读 5min 缓存(秒级响应),过期才实时查询用户主动终端输出(不推送)report每日绩效报告Cron 每日 00:00 UTC📈 Daily Reporthistory调仓历史用户主动终端输出(不推送)analyze详细 JSON 分析(波动率、范围、效率)AI agent终端输出(不推送)reset关闭头寸并重新部署手动🔔 Trade Alertclose完全退出头寸手动🔔 Trade Alert
status 触发词(用户说以下任意内容时应调用 status):
python
COMMANDS = {
"tick": tick, "status": status, "report": report,
"history": history_cmd, "reset": reset, "close": close,
"analyze": analyze
}
if __name__ == "__main__":
cmd = sys.argv[1] if len(sys.argv) > 1 else "tick"
COMMANDS.get(cmd, tick)()
脚本内部封装完整的消息格式化和可视化逻辑,通过 ---JSON--- 块输出结构化数据(含 notification 渲染块),由调度器路由到通信渠道。
Tier触发条件推送Trade Alert调仓成功或失败每次交易立即推送Risk Alert止损/熔断/连续错误立即推送Hourly Pulse距上次推送 ≥ QUIET_INTERVAL 且无交易每小时推送Daily Reportreport 命令 (cron daily)每日推送
status/ analyze 命令为用户主动查询,直接输出到终端,不推送。 status 优先从 _cached_snapshot(每 5min tick 时写入)读取已计算好的数据,无需任何 API 调用即可返回完整状态(Portfolio 跨平台总览 + 头寸 + PnL + IL + APY + LP 代币拆分)。缓存过期(>360s)时自动 fallback 到实时查询。
脚本负责:消息标题、字段组织、范围位置可视化、颜色级别、footer 上下文。调度器只需透传 notification 块到通信平台。
json
{
"version": 1,
"pool": {
"investment_id": "uniswap-v3-base-eth-usdc-3000",
"chain": "base",
"chain_id": 8453,
"token0": "0x4200000000000000000000000000000000000006",
"token1": "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913",
"fee_tier": 3000,
"tick_spacing": 60
},
"position": {
"token_id": null,
"tick_lower": null,
"tick_upper": null,
"price_lower": null,
"price_upper": null,
"created_at": null,
"created_atr_pct": null,
"created_vol_class": null
},
"price_history": [],
"vol_history": [],
"rebalance_history": [
{
"time": "ISO timestamp",
"trigger": "out_of_range",
"old_range": [-198120, -197400],
"new_range": [-198300, -197100],
"fees_claimed_usd": 1.25
}
],
"stats": {
"total_rebalances": 0,
"total_fees_claimed_usd": 0,
"unclaimed_fee_usd": 0,
"time_in_range_pct": 100,
"initial_portfolio_usd": null,
"total_deposits_usd": 0,
"portfolio_peak_usd": null,
"started_at": null,
"last_check": null
},
"errors": {
"consecutive": 0,
"cooldown_until": null
},
"stop_triggered": null,
"kline_cache": null,
"mtf_cache": null,
"last_quiet_report": null
}
Key fields:
pool: target pool configuration (chain, tokens, fee tier, tick spacing)position.token_id: NFT position ID (null if no active position)position.created_atr_pct: ATR at position creation (for vol shift detection)rebalance_history: full audit trail of all rebalances with costsstats.time_in_range_pct: key performance metric — % of ticks where price was in rangestats.initial_portfolio_usd + total_deposits_usd: PnL 计算的成本基准stats.total_fees_claimed_usd + unclaimed_fee_usd: LP 手续费总收入,用于推导 ILstop_triggered: string describing trigger condition, or nulltext
1. Fetch current price
2. Fetch position detail (if exists)
3. Update price_history (cap at 288 = 24h)
4. Fetch K-line data (1H × 24) → compute ATR volatility (hourly cache)
5. Classify volatility → vol_class (low/medium/high/extreme)
6. Multi-timeframe analysis → trend/strength/momentum/structure
7. Compute optimal range:
a. Base width = VOL_MULTIPLIER[vol_class] × ATR each side
b. Apply trend asymmetry (upper/lower sides)
c. Convert to ticks, align to tick_spacing
8. Check rebalance triggers:
a. Out of range → must rebalance
9. Anti-churn gates (position age, frequency, gas cost, range change)
10. If rebalancing:
a. Claim fees → remove liquidity → swap to ratio → deposit at new range
b. On failure: emergency fallback at 3× width
11. Check stop conditions (stop-loss, trailing stop, IL tolerance)
12. Calculate performance metrics
13. Report status (structured JSON)
bash
# Register tick (every 5 minutes)
zeroclaw cron add '*/5 * * * *' \
'cd ~/.openclaw/skills/cl-lp-rebalancer/references && set -a && . ../.env && set +a && python3 cl_lp.py tick'
# Register daily report (08:00 CST = 00:00 UTC)
zeroclaw cron add '0 0 * * *' \
'cd ~/.openclaw/skills/cl-lp-rebalancer/references && set -a && . ../.env && set +a && python3 cl_lp.py report'
bash
# Single tick
python3 cl_lp.py tick
# Dry run (fetch real data, simulate operations)
DRY_RUN=true python3 cl_lp.py tick
# Status check
python3 cl_lp.py status
# Close position and exit
python3 cl_lp.py close
text
IF rebalance sub-step fails:
1. Log failure reason to cl_lp.log
2. Increment errors.consecutive
3. If errors.consecutive >= 5: trigger circuit breaker (1h cooldown)
4. If failure after remove liquidity: emergency deploy at 3× normal width
(priority: get funds back into a position, even if suboptimal)
5. Report failure via JSON output
6. On next tick: retry from last successful sub-step if possible
PatternProblemRebalance every tickGas costs eat all fee incomeToo tight range in high volConstant out-of-range, excessive rebalancingToo wide range in low volCapital inefficiency, minimal fee captureNo minimum position ageRapid back-and-forth rebalancing (churn)Skip emergency fallbackFunds sit idle after failed rebalance (zero yield)Ignore gas costsL1 gas can exceed daily fee incomeSymmetric range in trendsMiss upside in bull, excess downside in bearNo IL trackingCannot detect when IL exceeds fee incomeRebalance on vol change aloneMinor ATR fluctuations cause unnecessary churn — only rebalance when out of rangeNo time-in-range trackingCannot measure strategy effectiveness