交易的真实成本:四笔费用、Gas 假设与到手的价格

一笔买入有四段费用,spread 从不写进收据;gas 早已不是 2021 年的 gas;USDT 还有 6 位和 18 位两个版本。把真实成本算清楚。

交易的真实成本:四笔费用、Gas 假设与到手的价格

一句话结论:你为一次加密交易付的钱,从来不是"手续费"三个字能概括的。至少分了四段:入金、交易(或点差)、货币换算、提现;其中 spread 从不作为一行费用出现在收据上,它只是让你被报的价格悄悄变差。另一半成本在链上:2026 年 gas 常低于 1 gwei,很多代码还活在 2021 年,硬编码 40 gwei 不只是贵,而且算错了。本文编译 2026 年 10 月的四篇材料:四段费用模型与 Python 实现(@nutshellcrypto,10-08)、EIP-1559 时代的 Gas 假设更新清单(@swapcoreexchange,10-14)、DEX 与 CEX 报价为何不同(@katie_hargreaves,10-08)、以及精度如何变成真金白银的损耗(@polycratia,10-10)。

披露:@nutshellcrypto 的文章文末推广自家手续费指南与 DCA 计算器(费率取自 2026-10-08 的公开费率页,spread 与网络费为假设值);@swapcoreexchange 的作者任职于 SwapCore(非托管即时兑换,文内披露);@katie_hargreaves 的文章推广 Dexlyn 产品(机制讲解为主);@polycratia 为开源库 cryptomoney 作者。数字均按"方法与口径"引用。


一、一笔买入的四段费用

如果你用"交易手续费"对比过两家交易所,那你比较的很可能是错的数字。一笔买入的成本最多经过四个阶段:

  1. 入金(Funding):银行转账通常免费;信用卡、PayPal 可能收几个百分点。
  2. 交易(Trading):在订单簿/"专业版"界面按 maker / taker 付费;点"一键买入"的简单界面,通常付一笔便利费加上已隐含在报价里的 spread。
  3. 换算(Conversion):用法币 A 买入以法币 B 计价的交易对,可能多一笔外汇费。
  4. 提现(Withdrawal):把币提到自己的钱包要付网络费,有时再加平台处理费。

spread 是最隐蔽的一项:它是买价与卖价之间的差,永远不会以"费用"为名列在你的收据上——它只是让你拿到的报价差一点点。

maker 与 taker,一段话

如果你的订单立刻和挂单成交,你是 taker,付较高的费率;如果你的订单挂在盘口等别人来成交,你是 maker,付较低的费率。市价单永远是 taker;挂在当前价格之外的限价单通常是 maker;部分成交的限价单可能两头都付——立即成交的部分按 taker,剩下的按 maker。

用 Python 把四段费用加起来

思路和我们在工程里处理任何成本一样:每个组成部分都建模,然后求和。

def all_in_cost(amount, trade_fee_pct=0.0, spread_pct=0.0,
                funding_fee_pct=0.0, fx_fee_pct=0.0, network_fee=0.0):
    """近似估算:买入 `amount` 金额的加密资产并提现的总成本。
    百分比以小数给出。"""
    funding = amount * funding_fee_pct
    trading = amount * trade_fee_pct
    spread = amount * spread_pct
    fx = amount * fx_fee_pct
    total = funding + trading + spread + fx + network_fee
    return round(total, 2), round(total / amount * 100, 2)

# 示例输入;请以你自己交易所的当前费率表为准
scenarios = {
    "专业版限价单(maker)":      dict(trade_fee_pct=0.004),
    "专业版市价单(taker)":      dict(trade_fee_pct=0.008),
    "一键买入 + spread":         dict(trade_fee_pct=0.01, spread_pct=0.005),
    "taker + 借记卡入金":        dict(trade_fee_pct=0.012, funding_fee_pct=0.0349),
}
for name, fees in scenarios.items():
    cost, pct = all_in_cost(1_000, network_fee=2.0, **fees)
    print(f"{name:24} ${cost:>6}  ({pct}%)")

按 2026-10-08 公开费率页整理的场景(spread 0.5% 与网络费 $2 为假设值),$1,000 买入并提现的差异是这样的:

场景 计算 总成本 占比
专业版限价单(maker 0.4%) 4 + 2 $6.00 0.60%
专业版市价单(taker 0.8%) 8 + 2 $10.00 1.00%
一键买入(1% + spread 0.5%) 10 + 5 + 2 $17.00 1.70%
taker 1.2% + 借记卡入金 3.49% 12 + 34.9 + 2 $48.90 4.89%

结论很清楚:你按哪个按钮、用什么方式入金,可能比选哪家交易所更重要。 一次买入能藏住小差异,每周定投会把每一段费用重复一遍,卖出时再收一次——这也是为什么在自动化之前,先用自己的参数把 all-in 成本跑一遍。

省钱的动作并不复杂:银行转账而不是卡(ACH / Interac);用专业版限价单拿 maker 费率;合并提现(网络费按笔收,一次大额优于多次小额);避免没必要的货币换算;先小额试转、确认网络;并且把费用记录下来——报税时它们影响成本基础。

二、Gas 假设更新:2026 年不是 2021 年

第二半成本在链上,而且很多代码还活在过去。@swapcoreexchange 的判断很直接:2026 年标准 gas 大多远低于 1 gwei,ERC-20 转账只要几美分。这不只让旧代码"贵",还让一部分旧代码错。

1. 停止硬编码 gas 价格与最低入金额

如果你的代码库还有这样的常量,它可能已经高估了两个数量级:

# 写于另一个费率时代
PAYOUT_GAS_PRICE_GWEI = 40
MIN_DEPOSIT_USDT = 50

伦敦升级后费率是 EIP-1559:协议设定的 base fee + 优先费(tip),两者都应该在出款的当时从实时数据估算:

def estimate_fees(w3, priority_percentile=50):
    hist = w3.eth.fee_history(10, "latest", [priority_percentile])
    base_fee = hist["baseFeePerGas"][-1]   # 下一个区块的 base fee
    tips = [r[0] for r in hist["reward"]]
    tip = sorted(tips)[len(tips) // 2]
    # 给 base fee 留几个区块的上涨空间
    max_fee = base_fee * 2 + tip
    return {"maxFeePerGas": max_fee, "maxPriorityFeePerGas": tip}

最低入金额也应该由实时估算推导,而不是固定值:按 40 gwei 设定的门槛会把今天本可盈利服务的用户挡在门外;按 0.2 gwei 设定的门槛,下次网络拥堵时又会算错。每次都重新算。

2. 检测内部交易,而不只是顶层转账

最简单的入账监听只看:每块里 to 是充值地址、value 非零的交易。这会漏掉从智能合约钱包发出的 ETH——比如 Safe 多签通过合约调用执行转账,链上表现为内部调用,不是一笔发到你地址的顶层交易,天真的监听器永远看不到。

两种可靠做法:

  • 追踪调用:用 debug_traceTransaction 或 trace_block(取决于服务商),在任何调用深度上寻找发往你地址的 value 转移;
  • 盯余额:每个区块轮询充值地址的 eth_getBalance,把任何增长与已知交易对账——更粗,但不会漏。

Pectra 升级引入的 EIP-7702 允许普通账户委托给合约代码,随着采用率上升,"通过合约执行到达的 ETH 充值"会变成常态;ERC-20 没有这个问题,因为不管谁调用代币合约,都会发出 Transfer 事件——监听日志即可。

3. 用最终性(finality),而不只是确认数

以太坊每 12 秒一个块;PoS 还多了一个信号:最终性。一个区块在两个 epoch 后(约 13 分钟)被 finalize,回滚它需要销毁大量质押 ETH。很多服务还在用 PoW 时代留下的确认数习惯,其实可以直接用 JSON-RPC 的区块标签:

latest    = w3.eth.get_block("latest")
safe      = w3.eth.get_block("safe")       # 已被证明,极不可能重组
finalized = w3.eth.get_block("finalized")  # 经济上最终

def is_credited(deposit_block, amount_usd):
    threshold = safe if amount_usd < 10_000 else finalized
    return deposit_block <= threshold["number"]

safe 通常落后链头几分钟,finalized 约 13 分钟。按金额分层:小额用户拿到快速入账,大额存款等经济最终性。

4. 把 L2 上的 ETH 当另一种资产

主网、Arbitrum、Base、Optimism 上的 ETH 用完全相同的 0x 地址:你为主网生成的充值地址,在每条 L2 上都是字节级合法的。用户会把 L2 的 ETH 发到主网充值地址,而主网监听器看不到。两个动作:

  • 把所有地方(报价、订单、UI)的资产建模成 (chain_id, asset) 对,"Base 上的 ETH"永远不要和"主网 ETH"混为一谈;
  • 可选地在主要 L2 上也监听你的充值地址——不自动入账,但可以主动标记给客服,而不是等用户几天后报告"资金丢失"。

5. 提醒"油箱见底"问题

用户把全部 ETH 换成 USDT,就会得到一堆无法转走的代币——因为没有 ETH 付 gas。如果你的界面提供 ETH 的"Max"按钮,考虑默认留一小笔储备、允许显式覆盖:

const RESERVE_WEI = estimateReserve(liveFees, 5); // 约 5 次未来转账
const maxSendable = balance - gasForThisTx - RESERVE_WEI;

在当前费率下储备很小,但重点是它不为零。

6. 充值流程永远不要请求授权

充值地址模型就是一笔普通转账,不需要 approve、不需要 permit、不需要签名。如果这是你的产品形态,就把这句话明确写进 UI 文案:用户已经学到"正规的兑换有时会要授权"(基于 router 的 DEX 兑换确实会),你明确说"我们永远不会",就等于给用户一个识别仿冒站的方法。

Gas 时代的核验清单:费率用 EIP-1559 实时估算、最低额每次报价推导、入账检测覆盖内部交易、按 safe / finalized 分层入账、资产按 chain ID 建键、Max 默认留 gas 储备、充值流程绝不要求授权或签名。

三、为什么 DEX 的报价和 CEX 不一样

这也是"到手价格"的一部分。同一个代币,CEX 显示 $100、DEX 显示 $101——不是谁错了,是两个市场的价格形成机制不同。

  • CEX 来自订单:买单 $100、$99.80、$99.50,卖单 $100.20、$100.50、$101,价格由买卖盘和成交推动;
  • DEX 来自流动性:经典恒定乘积 AMM 满足 x × y = k,一笔交易取出一种资产、放入另一种,储备比例改变,下一笔的价格随之改变。DEX 里没有"一个固定价格",报价取决于池子、当前储备和这笔交易的大小。

套利会把价格拉回去,但不需要也不应该分秒相等:手续费、交易规模、网络状况、流动性都会留下差价。

流动性深度决定"这一笔会不会把价格打飞":两个池子都标价约 $100,第一个有数百万美元流动性,第二个只有一点点——一笔 $20,000 的交易在第一个池子几乎不动价格,在第二个池子会显著移动。所以只看显示的代币价格,不足以知道你会拿到什么价格。 Chainlink 的文档也警告:低流动性、低交易量的资产更难定价,也更暴露于价格操纵。

集中流动性又改变了图景:LP 可以把资金放在选定价格区间内,价格在区间内时仓位参与交易并赚手续费,价格离开区间后仓位失活。两个 DEX 的 TVL 相近,在某个具体价格上的交易条件可能完全不同。此外还有第三个价格:预言机参考价(例如 Chainlink 聚合多数据源)——于是你同时能看到 DEX 池价、CEX 价、预言机价,它们不一样是正常的。

大额兑换前该看的清单:你实际会收到多少、池子可用流动性、预期滑点、交易手续费,以及"交易在等待确认期间市场是否已经移动"。跨链交易还要考虑两条链的流动性状态不同。

四、精度也是成本:6 位和 18 位的 USDT

最后一个容易被忽视的成本来源:同一个符号在不同部署里的精度不同。USDT 在有些链上是 6 位小数,在另一些部署里是 18 位。以符号为键的金额类型,会把 6 位余额和 18 位余额直接相加而不报错,然后返回一个错了 10¹² 倍、但符号完全正确的数字。

这不是"舍入误差",是两种方向都昂贵的 bug:

  • 用 6 位精度读一个 18 位的整数,金额放大 10¹² 倍——余额检查通过、付款发出,金库先于用户发现问题;
  • 用 18 位精度读 6 位整数,同一笔金额塌缩成尘埃、在屏幕上四舍五入为零,客服一小时内就会发现问题。

两边运算都是精确的,错的只是代码假设的小数位数。所以 cryptomoney 这类库把资产定义为 (symbol, decimals) 对,等号比较两个字段,任何二元运算前先 _require_same_asset;6 位和 18 位的 USDT 混合相加会抛出 CurrencyMismatch——报错信息读起来很怪:"USDT 和 USDT 是不同的资产",但这正是工程师需要在堆栈里看到的那句话:符号相同,精度不同。

两个顺带的工程结论:

  • 注册表按部署划分:一个全局 registry 结构上装不下同一符号的两个定义。把 registry 当部署配置、在每个把文本/整数变成金额的调用点显式传入;register 遇到冲突要响亮地失败,而不是合并出一个平均值;
  • 跨精度是换算,要写明余数:18 位转 6 位会产生一段没有着落的 dust(例如 901234567890 个基本单位)——把它当一笔有名字的账:向下取整归平台、下次转账补上,或直接拒绝低于目标精度的金额。把它埋进总额里,就再也分不清是坏汇率还是漏收的费用了。

一句话:符号给人看,数对(symbol, decimals)给算术用。

五、把真实成本放在一张表里

算清一笔交易

  • [ ] 列出四段费用:入金 / 交易或 spread / 换算 / 提现;
  • [ ] 市价单按 taker 算、限价单按 maker 算,部分成交要做好两头费率;
  • [ ] 用实时 EIP-1559 数据估 gas,不引用任何硬编码 gwei;
  • [ ] 把滑点与池子深度也算进去——报价不是成交价;
  • [ ] 检查精度:资产按 (chain_id, symbol, decimals) 建键,跨精度换算的 dust 要有归宿;
  • [ ] 记录数据来源与抓取时间(费率页、gas、池子储备),方便复盘。

校验一笔入账 / 出款

  • [ ] 内部交易(Safe、合约钱包、EIP-7702)是否被监听?
  • [ ] 入账用的是 safe / finalized 分层,而不是"约等于几次确认"?
  • [ ] L2 误充主网地址能否被主动发现并转客服?
  • [ ] 提现后用户是否还有 gas 余额?"Max"是否留了储备?
  • [ ] 充值流程有没有多余地要求 approval / permit / 签名?

从今天开始

  • [ ] 垃圾数据不如没有数据:把上面每个数字连同来源和时间写进一行日志;
  • [ ] 定期复跑——gas 会变、费率会变、spread 会变,2021 年的假设今天可能错两个数量级。

相关工具:需要发币/多链部署用 TokenDIY;检查并撤销不需要的授权用 Revoke(仅 http)。

相关阅读

本系列另两篇(三站相关系列 · 成本体检)

来源

  • @nutshellcrypto, Your Crypto Buy Has Four Fees, Not One: Modeling the All-In Cost in Python(dev.to,2026-10-08;费率取自公开费率页 2026-10-08,spread 0.5% 与网络费 $2 为假设值;AI 辅助撰写):https://dev.to/nutshellcrypto/your-crypto-buy-has-four-fees-not-one-modeling-the-all-in-cost-in-python-2mn2
  • @swapcoreexchange, Your Ethereum Gas Assumptions Are Probably From 2021(dev.to,2026-10-14;作者任职于 SwapCore,文内披露):https://dev.to/swapcoreexchange/your-ethereum-gas-assumptions-are-probably-from-2021-d64
  • @katie_hargreaves, Why DEX Prices Can Differ From Centralized Exchange Prices(dev.to,2026-10-08;内容推广 Dexlyn,机制讲解为主):https://dev.to/katie_hargreaves/why-dex-prices-can-differ-from-centralized-exchange-prices-427b
  • @polycratia, A ticker is not an asset: USDT at six decimals and USDT at eighteen(dev.to,2026-10-10;作者为开源库 cryptomoney 作者):https://dev.to/polycratia/a-ticker-is-not-an-asset-usdt-at-six-decimals-and-usdt-at-eighteen-30n2

本文由 DefiPlot 翻译整理,转载请注明出处。

免责声明

本文仅用于学习与研究目的,不构成任何投资建议、财务建议或交易建议。文中费率、gas 与流动性数据来自原文作者引用的公开页面及其自述口径(部分为假设值),未做独立复测;相关作者与所在机构均带有各自立场(已在文内披露)。加密资产与链上交互存在合约漏洞、精度错误、网络拥堵、滑点与钓鱼风险;请勿使用无法承受损失的资金参与,任何操作前请自行核对官方费率页与链上数据并 DYOR(Do Your Own Research)。

本文属于「量化交易」专题。查看专题全部文章 →
免责声明:本站资源仅用于学习目的,也不应被视为投资建议,读者在采取任何行动之前应自行研究并对自己的决定承担全部责任。