Searcher 策略 / Searcher Strategies
Atomic Arb / Sandwich Detection / JIT Liquidity / Liquidation 四大 searcher 策略的细节与博弈
日期: 2026-08-15 方向: MEV / DEX量化 阶段: Phase 2 - MEV与DEX量化 (Day 103-116) 标签: #Searcher #AtomicArb #Sandwich #JIT #Liquidation
今日目标 / Today's Objectives
| 类型 | 内容 |
|---|---|
| 学习 | Atomic Arb / Sandwich Detection / JIT Liquidity / Liquidation 四大 searcher 策略的细节与博弈 |
| 实操 | 实现一个可运行的原子套利 searcher,监听 Uniswap V2 ↔ V3 价差并构造 bundle |
| 产出 | searcher.py — 含价差监控 + atomic arb tx 构造器 |
1. 核心机制 / Core Mechanics
1.1 Atomic Arbitrage(原子套利)
定义:在同一个 tx 内完成跨池/跨协议的买卖,零库存风险。如果任一腿失败,整个 tx revert。
典型路径:
1. flashLoan 1000 USDC from Aave ← 借
2. swap 1000 USDC → 0.4 ETH on Uniswap V3 (cheap pool)
3. swap 0.4 ETH → 1010 USDC on Uniswap V2 (expensive pool)
4. repay 1000 USDC + 0.05% fee to Aave ← 还
5. profit: 9.5 USDC (gross), minus gas
关键合约: 通常 searcher 自己部署一个 Executor 合约统一处理 flash loan callback、多 router 调用、profit sweep。每条策略 tx ≤ 250k gas,gas 成本约 0.005-0.01 ETH(base fee 20 gwei 时)。
1.2 Sandwich Detection & Counter-Sandwich
Sandwich 监测: 监控 mempool tx,对每笔 V2/V3 swap 模拟 frontrun + backrun PnL。
- 工具:
erigon trace_call、anvilfork、自建 EVM emulator (revm) - 判定:当
(gross_profit - gas - bid) > 0时构造 bundle
Counter-Sandwich (反三明治): 检测到对手的三明治构造,自己 frontrun 它的 frontrun,让对手反向被夹。罕见,但 jaredfromsubway 偶尔做这个。
MEV-Share & Hint: Flashbots MEV-Share 让 user tx 释放部分 hint(如 to/from/value),searcher 基于 hint 构造 backrun,与 user 共享利润。这是从 zero-sum sandwich 到 positive-sum backrun 的演化。
1.3 Just-in-Time (JIT) Liquidity
机制:监测 Uniswap V3 大单 swap → 在同一区块的 swap 之前 mint LP position(精确围绕当前 tick)→ swap 后立刻 burn → 拿走绝大部分手续费。
经济学:
Big swap 100 ETH → USDC, 0.3% fee = $300 fee
原 LP 平均 fee 占比: 80% (取决于 in-range share)
JIT searcher 抢占: 通常拿走 70-95% 的该笔 fee
正常 LP 损失: $210-285 from this single swap
真实案例: 2024 年 Uniswap V3 上有 ~3% 的 LP 收入被 JIT 截胡。Wintermute 是大宗 JIT 的主要参与者。
1.4 Liquidation
目标: Aave / Compound / MakerDAO 等借贷协议中健康度 < 1 的头寸。 奖励: 5-15% liquidation bonus。 竞争维度:
- 延迟: 谁先看到 oracle 更新 → 谁先发清算 tx
- Gas 战争: 提高 priority fee
- Bundle 私有化: 通过 Flashbots 私下提交,避免抢跑
实证: 2024 年 Aave 上的 liquidator 集中度极高,前 5 名占 80% 利润。
2. 架构图与数据流 / Architecture & Data Flow
┌────────────────── Strategy Engine ──────────────────┐
│ Mempool ─────► Decoder ─────► Pricer ─────► PnL │
│ ▲ │ │ │ │
│ │ ▼ ▼ ▼ │
│ websocket tx category pool state profit │
│ geth tx pool (swap/lend) cache estimate│
└─────────────────────────┬────────────────────────────┘
│ (if PnL > threshold)
▼
┌────────────── Builder ──────────────┐
│ flash loan callback contract │
│ ABI-encode multi-step swap bytes │
│ Sign & assemble Bundle │
└─────────────────┬───────────────────┘
│ eth_sendBundle (multi-relay)
▼
[ Flashbots / BloXroute / Eden / Titan ]
│
▼
Builder → Validator → Block
3. 代码实现 / Code Implementation
searcher.py — 原子套利 searcher 监听 Uniswap V2/V3 价差。
"""
searcher.py — Minimal atomic arb searcher between Uniswap V2 and V3.
Monitors WETH/USDC price gap. When gap > threshold, builds a bundle that:
1. Flashloan 100 WETH from Balancer (0% fee)
2. Swap WETH -> USDC on cheap venue
3. Swap USDC -> WETH on expensive venue
4. Repay flashloan, send profit to coinbase
"""
import os
import time
from decimal import Decimal
from web3 import Web3
from eth_account import Account
RPC = os.environ["ETH_RPC"]
PK = os.environ["SEARCHER_PK"]
EXECUTOR = os.environ["EXECUTOR_CONTRACT"] # your deployed executor
w3 = Web3(Web3.HTTPProvider(RPC))
acct = Account.from_key(PK)
# Pools
V2_PAIR = w3.to_checksum_address("0xB4e16d0168e52d35CaCD2c6185b44281Ec28C9Dc") # USDC/WETH V2
V3_POOL = w3.to_checksum_address("0x88e6A0c2dDD26FEEb64F039a2c41296FcB3f5640") # USDC/WETH V3 0.05%
WETH = w3.to_checksum_address("0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")
USDC = w3.to_checksum_address("0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48")
V2_PAIR_ABI = [{"name":"getReserves","type":"function","stateMutability":"view","inputs":[],
"outputs":[{"type":"uint112"},{"type":"uint112"},{"type":"uint32"}]}]
V3_POOL_ABI = [{"name":"slot0","type":"function","stateMutability":"view","inputs":[],
"outputs":[{"type":"uint160","name":"sqrtPriceX96"},{"type":"int24"},
{"type":"uint16"},{"type":"uint16"},{"type":"uint16"},{"type":"uint8"},{"type":"bool"}]}]
v2 = w3.eth.contract(address=V2_PAIR, abi=V2_PAIR_ABI)
v3 = w3.eth.contract(address=V3_POOL, abi=V3_POOL_ABI)
def v2_price() -> Decimal:
r = v2.functions.getReserves().call()
# token0 = USDC (6 decimals), token1 = WETH (18 decimals)
usdc_reserve = Decimal(r[0]) / Decimal(10**6)
weth_reserve = Decimal(r[1]) / Decimal(10**18)
return usdc_reserve / weth_reserve # USDC per WETH
def v3_price() -> Decimal:
sqrt_price = v3.functions.slot0().call()[0]
raw = (Decimal(sqrt_price) ** 2) / Decimal(2 ** 192)
# token0 = USDC, token1 = WETH; price = token0 per token1 needs decimals adj
return raw * Decimal(10**12) # USDC per WETH
def expected_profit_usdc(amount_weth: Decimal, p_low: Decimal, p_high: Decimal) -> Decimal:
gross = amount_weth * (p_high - p_low)
# account for fees (V2 0.3% + V3 0.05%) and assume linear price impact
fee = amount_weth * p_high * Decimal("0.0035")
return gross - fee
def build_arb_bundle(direction: str, amount_weth: Decimal):
"""direction = 'v2_to_v3' if buy on v2 (low), sell on v3 (high)."""
# Encode call to your Executor contract.
# Executor.executeArb(uint amountIn, address[] path1, address[] path2)
fn_sig = w3.keccak(text="executeArb(uint256,uint8)")[:4]
direction_code = 0 if direction == "v2_to_v3" else 1
amount_wei = int(amount_weth * Decimal(10**18))
data = fn_sig + amount_wei.to_bytes(32, "big") + direction_code.to_bytes(32, "big")
base_fee = w3.eth.get_block("latest")["baseFeePerGas"]
tx = {
"to": EXECUTOR, "value": 0, "gas": 600_000,
"maxFeePerGas": base_fee * 2,
"maxPriorityFeePerGas": Web3.to_wei(2, "gwei"),
"nonce": w3.eth.get_transaction_count(acct.address),
"chainId": 1, "data": data, "type": 2,
}
signed = acct.sign_transaction(tx)
return signed.rawTransaction
def main_loop(threshold_bps: int = 30):
print(f"[+] searcher starting; threshold = {threshold_bps} bps")
while True:
try:
p2 = v2_price()
p3 = v3_price()
gap_bps = abs(p2 - p3) / min(p2, p3) * Decimal(10000)
if gap_bps > threshold_bps:
direction = "v2_to_v3" if p2 < p3 else "v3_to_v2"
amount = Decimal("50") # WETH
profit = expected_profit_usdc(amount, min(p2, p3), max(p2, p3))
if profit > Decimal("100"): # >$100 profit threshold
print(f"[!] Opportunity: {direction} gap={gap_bps:.1f}bps est_profit=${profit:.2f}")
raw = build_arb_bundle(direction, amount)
# send_bundle(...) via flashbots / BloXroute
print(f" bundle ready: {raw.hex()[:80]}...")
except Exception as e:
print(f"[err] {e}")
time.sleep(0.5)
if __name__ == "__main__":
main_loop()
注意:实际部署需要:
- 部署
Executor合约(处理 flashloan callback + 多 router call) - 接入 Flashbots Bundle API(Day 104 已演示)
- 用 erigon
txpool_content或 BloXroute Cloud API 监听 mempool
4. 真实数据 / Real Data
| 策略 | 月度利润 (top searcher) | 平均 PnL/tx | 失败率 |
|---|---|---|---|
| Atomic Arb | $5-15M | $40-200 | 8-15% |
| Sandwich | $10-30M (jaredfromsubway) | $150-500 | 25-35% |
| JIT LP | $1-3M | $200-2000 | 5-10% |
| Liquidation | $0.5-2M | $300-50000 | 30-50% |
单笔最大记录:
- Liquidation 单笔: 2022-09 Solend 上一笔 $7.5M 清算
- Atomic Arb 单笔: 2023-04 Curve renBTC pool 失衡 $1.6M 套利
- Sandwich 单笔: 2024-01 jaredfromsubway $172K (一笔被夹的 large swap)
5. 经济学分析 / Economic Analysis
5.1 利润分配表 (典型 Atomic Arb)
Gross profit $1000
- Gas (300k @ 30 gwei × $3000 ETH) -$27
- Bundle bid to validator (88%) -$880
-----
Searcher net $93 (~9.3%)
5.2 Searcher 竞争结构
- Top 5 searcher 占 60-70% 利润:规模效应 + builder relationship
- 规模效应来源: (1) flashloan 信用 (2) 自营 EVM simulator (3) 与 builder 私下 deal
- 进入门槛: $200K-1M(infra + risk capital + dev team)
5.3 JIT LP vs 普通 LP 的零和博弈
JIT 是典型的零和博弈:JIT 拿到的费用,原本属于普通 LP。Uniswap V4 hooks 引入了 anti-JIT mechanisms(如:mint 后强制持有 N 个 block),但争议在于这降低了 LP 的灵活性。
6. 机构视角 / Institutional Perspective
机构 searcher 的核心壁垒不是策略 alpha 而是 latency:
- Co-locate 服务器在 builder 数据中心(Frankfurt、London、Tokyo)
- 直接跟 Beaverbuild、Titan 签 SLA:保证 bundle 优先打包
- 自建 fork archive node(每天 swap pool snapshot)做更精准模拟
机构如何选择策略类型:
| 机构类型 | 偏好策略 | 原因 |
|---|---|---|
| 量化基金 (Jump, SCP) | Atomic Arb + Liquidation | 风险可控,alpha 量化 |
| 做市商 (Wintermute) | JIT LP + OFA Filler | 已有 inventory,可承担 toxic flow |
| Crypto-native lab (Symbolic) | 全谱 + R&D 新模式 | 探索 SUAVE / cross-domain |
| Builder 自营 (Beaverbuild) | 集成 internal searcher | 拿到 100% MEV,无需 bundle 竞拍 |
7. 风险与陷阱 / Risks & Pitfalls
- Latency game over: Top searcher 用 < 5ms tx → bundle 链路。普通 retail searcher 几乎 0 inclusion 概率。
- Toxic flow 反向选择:你抢到的 large swap,往往是 informed trader(背后是 oracle 更新或新闻)—— 你 frontrun 后 victim 的 swap 失败,你持有库存。
- Bundle revert 但收 gas:
revertingTxHashes设置错误 → 整个 bundle 收 gas 但没赚到。 - Flash loan 攻击向量: Reentrancy、price oracle manipulation。Searcher 的 Executor 合约必须经过审计。
- Approval drainer:恶意 EOA 触发你的 Executor 合约的 fallback 路径,drain approvals。
- Builder 偷 alpha: 把高利润 bundle 的策略 reverse-engineer,给自家 searcher。Mitigation: 多 builder 提交 + obfuscation。
8. 关键速查 / Quick Reference
| 工具 | 用途 |
|---|---|
Erigon txpool_content | 公共 mempool 查询 |
| BloXroute Cloud API | 商业级 mempool 数据流 (paid) |
| revm (Rust EVM) | 高速 EVM simulator |
| Anvil (Foundry) | mainnet fork simulator |
| Tenderly Simulation API | 第三方 simulation, $$$ |
| Aave Flashloan | 0.05% fee |
| Balancer Flashloan | 0% fee, but limited assets |
| dYdX Flashloan | 0% fee, USDC/DAI/WETH |
| Eigen Phi search | https://eigenphi.io |
| Top Searcher | Address |
|---|---|
| jaredfromsubway | 0x1f2F10D1C40777AE1Da742455c65828FF36Df387 |
| MEV Bot 0x000000... | 0x00000000003b3cc22aF3aE1EAc0440BcEe416B40 |
| Wintermute searcher | 0x0000000000Cd61E51c8AE0bAEa6cB16019f27Ce4 |
9. 面试题 / Interview Questions
- 设计一个 atomic arb searcher,从识别机会到 bundle inclusion 的端到端架构。重点说明每一层的延迟预算(端到端 < 200ms)。
- JIT LP 是 MEV 还是合理 market-making?请从 LP 视角和 informed trader 视角双向论证。
- 如果你是 Aave 协议设计者,你会如何减少清算 MEV 给 searcher 留下的利润,同时保证清算的及时性?(参考 Aave V3 的 efficiency mode)
- Sandwich 策略对 retail user 是高度负面外部性。给出三个产品/协议层的解决方案,并比较其优劣。
- MEV-Share 让 user 与 searcher 共享利润。这种设计如何改变 searcher 的策略偏好?为什么 MEV-Share 在 sandwich 上不工作但在 backrun 上工作?
10. 明日预告 / Tomorrow
Day 107: Cross-domain MEV — 当 MEV 跨越多条链时(如 Arbitrum ↔ Ethereum 套利、Solana ↔ Ethereum CEX-DEX arb),SUAVE、Atlas Protocol 等架构如何统一抽象?我们将研究 SUAVE 的 Kettle 设计与 Atlas 的 solver 模型。