惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
IntelliJ IDEA : IntelliJ IDEA – the Leading IDE for Professional Development in Java and Kotlin | The JetBrains Blog
C
CXSECURITY Database RSS Feed - CXSecurity.com
博客园_首页
H
Hackread – Cybersecurity News, Data Breaches, AI and More
T
ThreatConnect
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
博客园 - 聂微东
H
Help Net Security
T
Threat Research - Cisco Blogs
Blog — PlanetScale
Blog — PlanetScale
A
Arctic Wolf
G
Google Developers Blog
量子位
U
Unit 42
I
InfoQ
V
V2EX
F
Fox-IT International blog
P
Privacy & Cybersecurity Law Blog
V
Visual Studio Blog
J
Java Code Geeks
大猫的无限游戏
大猫的无限游戏
C
CERT Recently Published Vulnerability Notes
博客园 - 三生石上(FineUI控件)
T
The Exploit Database - CXSecurity.com
T
Tailwind CSS Blog
SecWiki News
SecWiki News
Know Your Adversary
Know Your Adversary
MyScale Blog
MyScale Blog
宝玉的分享
宝玉的分享
The Hacker News
The Hacker News
Project Zero
Project Zero
Application and Cybersecurity Blog
Application and Cybersecurity Blog
月光博客
月光博客
Recent Commits to openclaw:main
Recent Commits to openclaw:main
奇客Solidot–传递最新科技情报
奇客Solidot–传递最新科技情报
G
GRAHAM CLULEY
C
Cisco Blogs
I
Intezer
Simon Willison's Weblog
Simon Willison's Weblog
O
OpenAI News
Recorded Future
Recorded Future
T
Tenable Blog
W
WeLiveSecurity
腾讯CDC
Stack Overflow Blog
Stack Overflow Blog
T
The Blog of Author Tim Ferriss
www.infosecurity-magazine.com
www.infosecurity-magazine.com
D
Docker
C
Cybersecurity and Infrastructure Security Agency CISA
PCI Perspectives
PCI Perspectives

追梦人物的博客

0x05:Merkle Tree & Patricia Trie 0x04:ECDSA LeetCode 105 从前序与中序遍历构造二叉树 迭代算法原理 + 完整证明 0x03:Address 0x02:Secp256k1 - 以太坊设计与实现 0x00:专栏开篇 0x01:RLP 编码 - 以太坊设计与实现 Bellman-Ford 算法原理及其在 DeFi 套利中的应用 迪杰斯特拉(Dijkstra)最短路径算法原理、实现与证明 CEX-DEX 稳定币套利模型 Uniswap 手续费和协议费机制剖析 - 追梦人物的博客 Uniswap 流动性机制及相关数学原理分析 uv 替代 pyenv + pipx + poetry 环境管理实践 Rust 项目从创建到发布 VS Code 调试 Python 比特币跨市场套利的数学模型 ERC-20 相关知识点总结 Django 老项目如何从 SQLite 迁到 PostgreSQL 自动生成接口文档 单元测试 限制接口访问频率 API 版本管理 如何在 Windows 下搭建高效的 django 开发环境 拓展Python Markdown 加缓存为接口提速 基于 drf-haystack 实现文章搜索接口 评论接口 实现分类、标签、归档日期接口 在接口返回Markdown解析后的内容 文章详情接口 分页 使用视图集简化代码 用类视图实现首页 API 实现博客首页文章列表 API 初始化 RESTful API 风格的博客系统 django-rest-framework 是什么鬼? 结束 or 开始? Coverage.py 统计测试覆盖率 单元测试:测试评论应用 单元测试:测试 blog 应用 Django Haystack 全文检索与关键词高亮 Django 博客实现简单的全文搜索 开启 Django 博客的 RSS 功能 统计各个分类和标签下的文章数 稳定易用的 Django 分页库,完善分页功能 通过 Django Pagination 实现简单分页 在脚本中使用 ORM:Faker 批量生成测试数据 Django 官方推荐的姿势:类视图 Django 使用 union 合并不同模型(Model) 的查询集(QuerySet) 开发博客文章阅读量统计功能 使用 Docker 让部署 Django 项目更加轻松 使用 Certbot 向 Let's Encrypt 免费申请 HTTPS 证书 使用 Fabric 自动化部署 博客代码开源啦 (赠书)推荐一本django书籍:Django企业开发实战 Nginx+Gunicorn+Supervisor 部署 Django 博客应用 优化博客功能细节,提升使用体验 交流的桥梁:评论功能 分类、归档和标签页 页面侧边栏:使用自定义模板标签 自动生成文章摘要 Markdown 文章自动生成目录,提升阅读体验 让博客支持 Markdown 语法和代码高亮 开发博客文章详情页 创作后台开启,请开始你的表演 博客从“裸奔”到“有皮肤” Django 的接客之道 Django 迁移、操作数据库 创建 Django 博客的数据库模型 开始进入 django 开发之旅 "空空如也"的博客应用 一种自顶而下的Python装饰器设计方法 Python提取支付宝和微信支付二维码 2018 - 我的学生生涯最后一年回顾 批量清除todo练习参考答案 筛选练习参考答案 删除todo练习参考答案 编辑todo练习参考答案 添加todo练习参考答案 标为完成练习参考答案 入门仪式_Hello_Vue练习参考答案 组件化todo应用 批量清除todo 本地存储 筛选 还剩多少todo未完成 全部标为完成 自定义指令实现自动聚焦 删除todo 编辑todo 添加todo 标为完成 UI 显示todo列表 入门仪式:Hello Vue 在学习django-rest-framework时收集的学习资料推荐 区块链理论与应用研究小组成员招募书 Python界网红,豆瓣工程师董伟明加了我的QQ后 招募Django学习小组项目组核心成员
实战:CEX-DEX 稳定币套利监控程序开发 - 追梦人物的博客
2025-12-11 · via 追梦人物的博客

文章 《CEX-DEX 稳定币套利模型》 分析了套利机会的存在条件,本文将基于该模型的结论,实战开发一款套利机会监控程序。

根据套利模型,核心需求是获取两类价格数据:一是中心化交易所(CEX)的价格 \(p\),二是去中心化交易所(DEX)的价格 \(q\)。其中 CEX 普遍提供完善且统一的接口(通过下文介绍的 ccxt 库),能直接获取盘口报价;但 DEX 接口差异较大,一部分 DEX 会贴心地部署智能合约,供开发者直接查询报价行情;另一部分则未提供这类便捷服务,需开发者根据链上基础数据自行计算价格。

因此 CEX 我们选择币安的行情报价,而 DEX 我们选择了 2 个:

  • 以太链上 V3 协议的 Uniswap
  • Base 链上的 Aerodrome(应用层略有改动但底层仍然沿用 Uniswap V3 协议)

Uniswap 部署了 Quoter 智能合约,可以通过合约调用直接查询报价;而 Aerodrome 则没有可免费调用的报价合约,因此需要通过获取其资金池的流动性和最新价格,根据文章 《Uniswap 流动性机制及相关数学原理分析》 中介绍的相关公式自行计算报价。

核心 Python 库介绍

ccxt:CEX API 聚合工具

聚合全球主流中心化交易所(CEX)的行情与交易 API,提供统一的调用接口与交互逻辑。即便仅需与单一 CEX(如币安)交互,其封装后的 API 也比直接调用交易所官方 API 更简洁、易用,能大幅降低开发成本。

web3.py:区块链合约交互核心库

提供与区块链及智能合约交互的核心 API。可通过它与 Uniswap 报价合约、Aerodrome 资金池合约等进行数据交互,例如查询合约当前状态、读取关键业务数据(如价格、 liquidity 等),是链上数据获取与合约交互的基础工具。

其他辅助库

hexbytes:专门用于处理区块链场景中的十六进制字节数据(如合约地址、交易哈希的字节格式),解决该类数据的编码、解码与格式转换问题。

项目初始化:环境搭建与依赖配置

现在开始初始化项目并安装上面介绍的依赖,推荐使用 uv

# 创建项目
uv init stablecoin-arbitrage-monitor
cd stablecoin-arbitrage-monitor

# 添加依赖
uv add ccxt web3 hexbytes

依赖安装完成后,核心开发流程可分为“行情数据获取 → 套利利润计算 → 循环监控”三步,我们先从 DEX 与 CEX 的行情获取开始。

行情数据获取:DEX 与 CEX 实现方案

Uniswap:通过 Quoter 合约直接获取报价

先来看 Uniswap Quoter 智能合约,它包含多个功能方法,其中 quoteExactInput 是我们需要重点关注的方法。

该方法需接收一个关键参数 path(即“交易路径”),其格式为 输入代币地址 + 交易费率 + 输出代币地址 的二进制编码。这里的“交易费率”指 Uniswap 资金池对应的费率(Uniswap V3 常见费率为 0.01%、0.05%、0.3%、1%,需与目标资金池费率匹配)。

举个例子:在我们的程序中,交易路径可设置为两种场景:一种是 (USDT 地址 + 目标资金池费率 + USDC 地址(用 USDT 兑换 USDC),另一种是 USDC 地址 + 目标资金池费率 + USDT 地址(用 USDC 兑换 USDT)。

所以我们需要 USDT 和 USDC 的合约地址,以及 USDC/USDT 资金池的交易费率。这些数据通常是固定的,我们可通过区块链浏览器查询后,将其硬编码到代码中:

UNISWAP_USDC_ADDR = "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"  # USDC 合约地址
UNISWAP_USDT_ADDR = "0xdAC17F958D2ee523a2206206994597C13D831ec7"  # USDT 合约地址
UNISWAP_USDC_USDT_POOL_FEE = 500  # USDC/USDT 资金池交易费率(0.05%)

现在实现一个辅助函数,以代币合约地址和 fee 为参数,生成输入参数 path

def construct_path(token_in, token_out, fee):
    """
    构造 Uniswap V3 Quoter 合约 `quoteExactInput` 的参数 `path`

    Args:
        token_in (str): 输入代币的合约地址
        token_out (str): 输出代币的合约地址
        fee (int): 交易池的费率(以 basis points 为单位,如 500 表示 0.05%)

    Returns:
        HexBytes: 编码后的交易路径
    """
    # 去掉地址前缀 "0x",将地址转换为字节数组
    token_in_bytes = Web3.to_bytes(hexstr=token_in[2:])
    token_out_bytes = Web3.to_bytes(hexstr=token_out[2:])

    # 将费率转换为 3 字节的字节数组(区块链上的协议字节序大多采用大端序)
    fee_bytes = fee.to_bytes(3, byteorder="big")

    # 按照格式拼接:tokenIn + fee + tokenOut
    path_bytes = token_in_bytes + fee_bytes + token_out_bytes

    return hexbytes.HexBytes(path_bytes)

为了与 Quoter 合约交互,我们需先连接以太坊网络中的某个节点。部分节点会对外提供服务,支持通过 JSON RPC 协议进行交互(例如查询区块链的当前或历史状态、发送交易等操作)。web3.py 已封装好上述交互逻辑,因此我们只需直接实例化合约对象即可。

UNISWAP_RPC_URL = "https://ethereum-rpc.publicnode.com"
UNISWAP_QUOTER_ADDR = "0x5e55C9e631FAE526cd4B0526C4818D6e0a9eF0e3"
UNISWAP_QUOTER_ABI = """[
    {
        "inputs": [
            {"internalType": "bytes", "name": "path", "type": "bytes"},
            {"internalType": "uint256", "name": "amountIn", "type": "uint256"}
        ],
        "name": "quoteExactInput",
        "outputs": [
            {"internalType": "uint256", "name": "amountOut", "type": "uint256"},
            {"internalType": "uint160[]", "name": "sqrtPriceX96AfterList", "type": "uint160[]"},
            {"internalType": "uint32[]", "name": "initializedTicksCrossedList", "type": "uint32[]"},
            {"internalType": "uint256", "name": "gasEstimate", "type": "uint256"}
        ],
        "stateMutability": "view",
        "type": "function"
    }
]"""

def get_uniswap_quoter_contract():
    """获取 Uniswap Quoter 合约实例"""
    w3 = Web3(Web3.HTTPProvider(UNISWAP_RPC_URL))
    contract = w3.eth.contract(address=UNISWAP_QUOTER_ADDR, abi=UNISWAP_QUOTER_ABI)
    return contract

这里我们通过 PublicNode 提供的 RPC 节点连接以太坊网络,该服务为免费性质,且限流政策相对宽松,非常适合做开发测试的节点。

UNISWAP_QUOTER_ABI 定义了 Quoter 合约中 quoteExactInput 方法的接口规范。web3.py 需依赖这一规范,才能明确如何与 Quoter 合约交互(比如识别接口名称、参数类型及返回结果类型)。

万事俱备,Uniswap 的报价查询非常简单:

# 智能合约中代币数量以整数形式存储,decimals 用于定义小数位数,
# 作用是将人类可读的十进制小数转换为整数(通过乘以 10^decimals 消除小数),或反向还原,同时保证数值精度。
# USDC 和 USDT 的 decimals 都等于 6,也就是说假设 USDC 的数量是 1000.5,智能合约中实际存储的值是 1000.5*10^6 = 1000500000
DECIMALS = 6

def uniswap_quote(contract, token_in, token_out, amount_in):
    """
    查询 Uniswap 上的代币兑换汇率

    Args:
        contract: Uniswap Quoter 合约实例
        token_in: 输入代币合约地址
        token_out: 输出代币合约地址
        amount_in: 输入数量(以 USDT/USDC 为单位)

    Returns:
        float: 兑换汇率(输出数量/输入数量)
    """
    # 构造 `quoteExactInput` 的参数 `path`
    path = construct_path(token_in, token_out, fee=UNISWAP_USDC_USDT_POOL_FEE)

    # 转换为链上精度,见 `DECIMALS` 的说明
    in_amt = int(amount_in * 10**DECIMALS)

    # 调用合约查询输出数量
    res = contract.functions.quoteExactInput(path, in_amt).call()
    out_amt = res[0]

    # 返回汇率,实际应为 (out_amt / 10^DECIMALS) / (in_amt / 10^DECIMALS),输入输出代币的 decimals 相等,所以相互抵消了
    return out_amt / in_amt

Aerodrome:基于流动性数据手动计算报价

Aerodrome 获取行情报价的过程相对复杂,它未提供可免费调用的智能合约,因此需要自行计算报价。其计算原理基于《Uniswap 流动性机制及相关数学原理分析》中介绍的 Uniswap V3 数学模型,具体需依据文中公式 (38) 进行计算,为此我们需要先获取以下信息:

  • 资金池当前流动性 \(L\)
  • 资金池当前价格 \(P\)

同样需要构建合约对象,以实现与 Aerodrome 资金池合约的交互:

AERODROM_RPC_URL = "https://base-rpc.publicnode.com"
AERODROM_USDC_USDT_POOL_ADDR = "0xa41Bc0AFfbA7Fd420d186b84899d7ab2aC57fcD1"
AERODROM_POOL_ABI = """[
    {
        "inputs": [],
        "name": "liquidity",
        "outputs": [{ "internalType": "uint128", "name": "", "type": "uint128" }],
        "stateMutability": "view",
        "type": "function"
    },
    {
        "inputs": [],
        "name": "slot0",
        "outputs": [
        { "internalType": "uint160", "name": "sqrtPriceX96", "type": "uint160" },
        { "internalType": "int24", "name": "tick", "type": "int24" },
        {
            "internalType": "uint16",
            "name": "observationIndex",
            "type": "uint16"
        },
        {
            "internalType": "uint16",
            "name": "observationCardinality",
            "type": "uint16"
        },
        {
            "internalType": "uint16",
            "name": "observationCardinalityNext",
            "type": "uint16"
        },
        { "internalType": "bool", "name": "unlocked", "type": "bool" }
        ],
        "stateMutability": "view",
        "type": "function"
    }
]"""

def get_aerodrom_pool_contract():
    """获取 Aerodrome 资金池合约实例"""
    w3 = Web3(Web3.HTTPProvider(AERODROM_RPC_URL))
    contract = w3.eth.contract(
        address=AERODROM_USDC_USDT_POOL_ADDR, abi=AERODROM_POOL_ABI
    )
    return contract

然后我们先依据 Uniswap V3 的数学公式,计算出当输入指定交易量 amount_in 时,价格会被推动至何种水平;再基于这一更新后的价格,进一步算出实际能获得的输出量。

首先需完成的核心计算是:在给定交易输入量 amount_in 的前提下,确定价格会被推到的具体位置。

# Uniswap V3 Q64.96 定点数格式表示价格,具体的原理我们后续会写文章进行详细介绍
Q96 = 2**96

def get_next_sqrt_price_from_input(
    sqrt_p_x96,
    liquidity,
    amount_in,
    zero_for_one,
):
    """
    根据 Uniswap V3 公式计算输入后的新价格

    Args:
        sqrt_p_x96: 当前价格的平方根(Q64.96 定点数格式)
        liquidity: 当前流动性
        amount_in: 输入数量(已扣除手续费)
        zero_for_one: 是否以 token0 换 token1

    Returns:
        int: 新价格的平方根(Q64.96 定点数格式)
    """
    if zero_for_one:
        # token0 -> token1 的价格计算,依据公式 (14)
        numerator1 = liquidity * Q96
        return int(numerator1 / (numerator1 / sqrt_p_x96 + amount_in))
    else:
        # token1 -> token0 的价格计算,依据公式 (38)
        return sqrt_p_x96 + int(amount_in * Q96 / liquidity)

接着,我们就能计算在指定输入量下的实际输出量:

def get_amount0_delta(sqrt_ratio_a_x96, sqrt_ratio_b_x96, liquidity):
    """
    计算 token0 的输出数量

    Args:
        sqrt_ratio_a_x96: 起始价格的平方根
        sqrt_ratio_b_x96: 结束价格的平方根
        liquidity: 流动性

    Returns:
        float: token0 的数量
    """
    return (
        liquidity
        * Q96
        * (sqrt_ratio_b_x96 - sqrt_ratio_a_x96)
        / sqrt_ratio_b_x96
        / sqrt_ratio_a_x96
    )

def get_amount1_delta(sqrt_ratio_a_x96, sqrt_ratio_b_x96, liquidity):
    """
    计算 token1 的输出数量

    Args:
        sqrt_ratio_a_x96: 起始价格的平方根
        sqrt_ratio_b_x96: 结束价格的平方根
        liquidity: 流动性

    Returns:
        float: token1 的数量
    """
    return liquidity * (sqrt_ratio_b_x96 - sqrt_ratio_a_x96) / Q96

AERODROM_USDC_USDT_POOL_FEE = 70

def aerodrom_quote(contract, token_in, token_out, amount_in):
    """
    计算 Aerodrome 上的代币兑换汇率

    Args:
        contract: Aerodrome 资金池合约实例
        token_in: 输入代币合约地址
        token_out: 输出代币合约地址
        amount_in: 输入数量(以 USDT/USDC 为单位)

    Returns:
        float: 兑换汇率(输出数量/输入数量)
    """
    # 转换为链上精度,见 `DECIMALS` 的说明
    amount_in = int(amount_in * 10**DECIMALS)

    # 判断交易方向。资金池中合约地址小的代币排在前面,大的在后面。
    # 因此若输入代币的地址小于输出代币的地址,说明是 token0 换 token1,反之亦然。
    zero_for_one = token_in.lower() < token_out.lower()

    # 获取链上数据
    liquidity = contract.functions.liquidity().call()  # 当前流动性
    sqrt_price_X96 = contract.functions.slot0().call()[0]  # 当前价格

    # 扣除交易手续费
    in_amt_less_fee = amount_in * (1e6 - AERODROM_USDC_USDT_POOL_FEE) / 1e6

    # 计算交易后的新价格
    sqrt_price_next_X96 = get_next_sqrt_price_from_input(
        sqrt_price_X96, liquidity, in_amt_less_fee, zero_for_one
    )

    # 根据价格变化计算输出数量
    if zero_for_one:
        amount_out = get_amount1_delta(
            sqrt_price_X96,
            sqrt_price_next_X96,
            liquidity,
        )
    else:
        amount_out = get_amount0_delta(
            sqrt_price_X96,
            sqrt_price_next_X96,
            liquidity,
        )

    # 返回汇率。输出数量为负数,但我们只关心汇率,所以取绝对值。
    return abs(amount_out / amount_in)

[!WARNING] 价格影响假设
出于简化起见,我们假设输入量不会导致价格偏移太大。如果跨越了流动性区间,以上计算就不正确。如果需要更加精确的计算,就需要调整代码完全模拟 Uniswap V3 的交易逻辑。技术实现并不困难,但会让文章看起来过于复杂,因此这里做了简化处理。

CEX 盘口数据:币安 API 快速调用

获取币安的盘口数据非常简单,ccxt 库已经封装好了 API:

cex = ccxt.binance()
orderbook = cex.fetch_order_book("USDC/USDT", limit=1)

调用输出:

{'symbol': 'USDC/USDT', 'bids': [[0.9996, 104504279.0]], 'asks': [[0.9997, 54831253.0]], 'timestamp': None, 'datetime': None, 'nonce': 1953829892}

套利利润计算

根据套利模型中给出的公式,可计算套利利润:

logger = logging.getLogger("stablecoin")

def calc_pnl(p, q, n, cost):
    """
    计算套利利润

    Args:
        p (float): CEX USDC (USDT) 价格
        q (float): DEX USDC (USDT) 价格
        n (float): 交易数量
        cost (float): 总成本,等于 f + g + r + s
                     f - CEX 交易手续费
                     g - CEX 提现费用
                     r - DEX 交易手续费
                     s - 链上 Gas 费用

    Returns:
        float: 套利利润
    """
    return n * (q - p) - cost


def log_pnl(prefix, pnl):
    printer = logger.warning if pnl > 0 else logger.debug
    printer(f"{prefix} 本轮套利 PNL: {pnl:.6f}")

循环监控:主程序逻辑与运行

万事俱备,主程序的逻辑非常简单,只需要循环拉取数据,代入可套利公式计算即可:

import logging.config
import time

import ccxt
import hexbytes
from web3 import Web3

# 本金,单位 USDT 或者 USDC,取决于套利路径
N = 1000
# f + g + r + s, 详细含义请阅读 https://www.zmrenwu.com/posts/42/
UNISWAP_TOTAL_COST = 0 + 0.5 + 1 + 0.1
AERODROM_TOTAL_COST = 0 + 0.1 + 1 + 0.1

# ... 省略其它代码

def main():
    uniswap_quoter = get_uniswap_quoter_contract()
    aerodrom_pool = get_aerodrom_pool_contract()

    cex = ccxt.binance()
    while True:
        # Uniswap
        uniswap_q_usdc = uniswap_quote(
            uniswap_quoter,
            UNISWAP_USDC_ADDR,
            UNISWAP_USDT_ADDR,
            N,
        )
        uniswap_q_usdt = uniswap_quote(
            uniswap_quoter,
            UNISWAP_USDT_ADDR,
            UNISWAP_USDC_ADDR,
            N,
        )
        logger.debug(
            f"Uniswap 汇率: 1 USDC = {uniswap_q_usdt:.6f} USDT; 1 USDT = {uniswap_q_usdc:.6f} USDC"
        )

        # Aerodrom
        aerodrom_q_usdc = aerodrom_quote(
            aerodrom_pool,
            AERODROM_USDC_ADDR,
            AERODROM_USDT_ADDR,
            N,
        )
        aerodrom_q_usdt = aerodrom_quote(
            aerodrom_pool,
            AERODROM_USDT_ADDR,
            AERODROM_USDC_ADDR,
            N,
        )
        logger.debug(
            f"Aerodrom 汇率: 1 USDC = {aerodrom_q_usdt:.6f} USDT; 1 USDT = {aerodrom_q_usdc:.6f} USDC"
        )

        # CEX
        orderbook = cex.fetch_order_book("USDC/USDT", limit=1)
        logger.debug(orderbook)
        p_usdc = orderbook["asks"][0][0]
        p_usdt = 1 / orderbook["bids"][0][0]

        # USDT -> USDC -> USDT
        circle = "[USDT -> USDC -> USDT]"
        uniswap_pnl = calc_pnl(p_usdc, uniswap_q_usdc, N, UNISWAP_TOTAL_COST)
        aerodrom_pnl = calc_pnl(p_usdc, aerodrom_q_usdc, N, AERODROM_TOTAL_COST)
        log_pnl(circle + " Uniswap", uniswap_pnl)
        log_pnl(circle + " Aerodrom", aerodrom_pnl)

        # USDC -> USDT -> USDC
        circle = "[USDC -> USDT -> USDC]"
        uniswap_pnl = calc_pnl(p_usdt, uniswap_q_usdt, N, UNISWAP_TOTAL_COST)
        aerodrom_pnl = calc_pnl(p_usdt, aerodrom_q_usdt, N, AERODROM_TOTAL_COST)
        log_pnl(circle + " Uniswap", uniswap_pnl)
        log_pnl(circle + " Aerodrom", aerodrom_pnl)

        time.sleep(60)

实际运行结果

2025-12-11 13:14:35,756 [DEBUG] - Uniswap 汇率: 1 USDC = 0.999998 USDT; 1 USDT = 0.998998 USDC
2025-12-11 13:14:42,763 [DEBUG] - Aerodrom 汇率: 1 USDC = 0.998918 USDT; 1 USDT = 1.000943 USDC
2025-12-11 13:14:42,763 [DEBUG] - [USDT -> USDC -> USDT] Uniswap 本轮套利 PNL: -2.302005
2025-12-11 13:14:42,763 [WARNING] - [USDT -> USDC -> USDT] Aerodrom 本轮套利 PNL: 0.042907
2025-12-11 13:14:42,764 [DEBUG] - [USDC -> USDT -> USDC] Uniswap 本轮套利 PNL: -2.001831
2025-12-11 13:14:42,764 [DEBUG] - [USDC -> USDT -> USDC] Aerodrom 本轮套利 PNL: -2.682090

从日志可以看到:

  • WARNING 级别的日志表示发现了套利机会(PNL > 0)
  • Aerodrome 上出现了 USDT → USDC → USDT 的套利机会,1000 USDT 本金下利润约为 0.043 USDT
  • 其他方向的套利都是亏损的(DEBUG 级别日志)

这个价差确实存在,主要原因之一是:将 Base 链上的 USDT 转入其他主流链时会产生跨链费用成本较高,这导致价差没能被套利行为及时抹平。若要实际操作套利,还需额外考虑跨链成本和操作执行时间这两个关键因素。

总结

我们成功开发了套利机会监控程序,实现了两种不同的方案:

  1. 直接获取报价行情:适用于像 Uniswap 这样提供 Quoter 合约的 DEX
  2. 自行计算报价:适用于像 Aerodrome 这样需要根据流动性数据手动计算的 DEX

这两套方案可以迁移到更多的 DEX,实现多平台套利机会监控。

从运行结果看,Aerodrome 确实存在价差,但主要是因为 USDT 跨链成本导致的。如果要实际进行套利,还需完善模型,考虑跨链成本等因素。

相关资源

免责声明

本文内容仅为区块链技术开发的学术探讨与技术实践分享,不构成任何形式的投资建议、交易指导或财务顾问服务。加密货币及区块链相关交易存在极高的市场风险、技术风险与政策风险,可能导致本金大幅亏损。
文中所提供的代码、数据及运行结果仅作技术演示使用,不保证其准确性、完整性、时效性及可操作性。开发者在实际使用时需自行验证代码逻辑、补充安全机制,并承担因代码运行、交易操作所产生的全部法律责任与经济损失。任何依据本文内容进行的投资或交易行为,均由行为人自行决策并承担全部风险,本文作者及相关方不对任何由此产生的损失负责。

-- EOF --