返回 Expert 笔记
Expert Day 106

Searcher 策略 / Searcher Strategies

Atomic Arb / Sandwich Detection / JIT Liquidity / Liquidation 四大 searcher 策略的细节与博弈

2026-08-15
Phase 2 - MEV与DEX量化 (Day 103-116)
SearcherAtomicArbSandwichJITLiquidation

日期: 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_callanvil fork、自建 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。 竞争维度:

  1. 延迟: 谁先看到 oracle 更新 → 谁先发清算 tx
  2. Gas 战争: 提高 priority fee
  3. 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()

注意:实际部署需要:

  1. 部署 Executor 合约(处理 flashloan callback + 多 router call)
  2. 接入 Flashbots Bundle API(Day 104 已演示)
  3. 用 erigon txpool_content 或 BloXroute Cloud API 监听 mempool

4. 真实数据 / Real Data

策略月度利润 (top searcher)平均 PnL/tx失败率
Atomic Arb$5-15M$40-2008-15%
Sandwich$10-30M (jaredfromsubway)$150-50025-35%
JIT LP$1-3M$200-20005-10%
Liquidation$0.5-2M$300-5000030-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

  1. Latency game over: Top searcher 用 < 5ms tx → bundle 链路。普通 retail searcher 几乎 0 inclusion 概率。
  2. Toxic flow 反向选择:你抢到的 large swap,往往是 informed trader(背后是 oracle 更新或新闻)—— 你 frontrun 后 victim 的 swap 失败,你持有库存。
  3. Bundle revert 但收 gasrevertingTxHashes 设置错误 → 整个 bundle 收 gas 但没赚到。
  4. Flash loan 攻击向量: Reentrancy、price oracle manipulation。Searcher 的 Executor 合约必须经过审计。
  5. Approval drainer:恶意 EOA 触发你的 Executor 合约的 fallback 路径,drain approvals。
  6. 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 Flashloan0.05% fee
Balancer Flashloan0% fee, but limited assets
dYdX Flashloan0% fee, USDC/DAI/WETH
Eigen Phi searchhttps://eigenphi.io
Top SearcherAddress
jaredfromsubway0x1f2F10D1C40777AE1Da742455c65828FF36Df387
MEV Bot 0x000000...0x00000000003b3cc22aF3aE1EAc0440BcEe416B40
Wintermute searcher0x0000000000Cd61E51c8AE0bAEa6cB16019f27Ce4

9. 面试题 / Interview Questions

  1. 设计一个 atomic arb searcher,从识别机会到 bundle inclusion 的端到端架构。重点说明每一层的延迟预算(端到端 < 200ms)。
  2. JIT LP 是 MEV 还是合理 market-making?请从 LP 视角和 informed trader 视角双向论证。
  3. 如果你是 Aave 协议设计者,你会如何减少清算 MEV 给 searcher 留下的利润,同时保证清算的及时性?(参考 Aave V3 的 efficiency mode)
  4. Sandwich 策略对 retail user 是高度负面外部性。给出三个产品/协议层的解决方案,并比较其优劣。
  5. 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 模型。