摘要
策略给出交易方向之后,执行系统仍需决定在哪里成交、一次提交多少,以及何时结束等待。本文讨论一种多市场路由方法:先把不同交易接口归一为订单意图,再将深度、手续费、网络开销与等待代价统一到同一计价币种,按实际回报逐批更新计划。通过可手算的双渠道示例,说明最低报价为什么未必对应最低执行成本。
01 / 报价相同,不代表订单等价
一个渠道显示卖一价 10.00,另一个显示 10.02,并不能据此确定买入路径。前者可能只在最优价上挂出很少的数量,后者可能提供更深的流动性;链上交换还需要支付网络费。报价对应的时间、数量、费用口径不同,也会改变比较结果。
订单簿需要沿卖盘逐档计算买入均价,流动性池则需要针对目标数量重新求报价。价格冲击是交易本身改变成交价格的结果;提交后的市场变化又可能让实际成交偏离该报价。二者需要分别归因,不能把已包含在数量报价里的冲击再加一次。相关机制可参见 Uniswap 官方交换说明。
本文只比较经济标的、交割权益和计价资产一致的现货订单。永续合约还涉及合约乘数、保证金及资金费用,应使用独立模型。两个候选渠道的资金均须预先到位;需要充值、提现或跨链搬移资金的渠道不能被当成即时可用的流动性。
02 / 用订单意图连接策略与市场
策略层表达目标,适配层负责把目标翻译为渠道参数。内部意图必须写清楚“买入多少基础资产”和“最多花多少计价资产”,避免把数量字段在不同接口中的含义混为一谈。下面是本文设计的示意结构,不是某个已发布的接口:
{
"intent_id": "example-002",
"instrument": "ASSET/QUOTE",
"side": "buy",
"target_base_qty": "100",
"max_quote_spend": "1006",
"deadline_ms": 15000,
"max_quote_age_ms": 500,
"allowed_venues": ["A", "B"],
"funding_policy": "prefunded_only"
}
适配层返回统一的数量报价、报价时间、费用是否已计入、可用余额及交易限制。金额用十进制定点数处理。精度与最小下单量是两类约束:满足价格步长,不代表满足最低名义金额。CCXT 官方手册明确区分了精度和限额;它的统一订单接口也不能替代对渠道具体规则的检查。
03 / 先统一单位,再比较成本
对候选渠道 i,设 q 为基础资产数量,Pᵢ(q) 为考虑深度后的预计成交均价,P₀ 为决策时固定的参考价格,价格单位均为“计价资产 / 基础资产”。买入令 s = +1,卖出令 s = −1,定义需要最小化的成本分数:
Jᵢ(q) = s · q · [Pᵢ(q) − P₀] + Fᵢ + Gᵢ + Kᵢ + Lᵢ
Lᵢ = q · αᵢ · Tᵢ
Fᵢ 是交易费,Gᵢ 是网络费,Kᵢ 是分摊到本次交易的资金移动成本,三者都换算为计价资产。Lᵢ 是等待期间预期不利价格变化的代价:Tᵢ 以秒计,αᵢ 以“计价资产 / 基础资产 / 秒”计。因此公式每一项都是金额,不能直接把毫秒或一个概率加到价格上。
Pᵢ 的口径必须与 Fᵢ 协调。若渠道返回的交换报价已经包含池交易费,则这部分费用不再进入 Fᵢ。网络费采用本次估计兑换价折算,同时保留原币金额用于复盘。立即执行只允许资金在场的候选;周期性资金再平衡则单独统计实际搬移费用,并按明确规则分摊到 Kᵢ。
αᵢ 是按交易方向估计的非负不利移动率,不是把历史波动率直接当费用。在没有可靠数据时,应将等待限制作为硬约束,并展示不同 α 的敏感性。数量过小、资金不足、报价过期或无法在期限内执行的候选,先退出比较。
04 / 教学算例:低价渠道为什么落选
以下全部数值为教学假设,不代表实盘测量。计划买入 100 单位资产,参考价 P₀ = 10.01;两渠道均预存足够的同一种计价资产,目标数量可执行,无需本次搬资,K = 0。表中均价已含深度影响,但尚未计入另列交易费。
| 比较项 | 渠道 A · 订单簿 | 渠道 B · 流动性池 |
|---|---|---|
| 数量均价 P(q) | 10.02 | 10.00 |
| 交易金额 qP | 1,002.00 | 1,000.00 |
| 交易费 F | 1.00 | 0.50 |
| 网络费 G | 0.00 | 3.00 |
| 预计等待 T | 1 秒 | 4 秒 |
| 等待代价 L | 0.20 | 0.80 |
| 预计现金支出 qP + F + G | 1,003.00 | 1,003.50 |
| 成本分数 J | 2.20 | 3.30 |
两者均设 α = 0.002,因此等待代价分别为 100 × 0.002 × 1 = 0.20,以及 100 × 0.002 × 4 = 0.80。参考金额是 1,001,A 的分数为 1,003 − 1,001 + 0.20 = 2.20,B 为 1,003.50 − 1,001 + 0.80 = 3.30。模型选择 A,优势为 1.10 个计价单位。等待代价是决策中的估计损失,不是一笔已经支付的账单。
如果 B 的网络费降至 1.00,其分数就变为 1.30,排序随之反转。因此路由应针对每次数量重新计算,而不是永久指定一个“最优市场”。
05 / 分批执行与状态回收
一次选路只对当前快照有效。把母订单拆成若干子订单,能在成交后重新观察深度与余额,但每增加一次链上提交也可能新增网络费。切片大小应连同固定费用一起评估,不应默认越细越好。每轮先确定相同的目标切片数量,再比较各渠道,避免用小订单的低总费用去压过大订单。下面的伪代码描述单个母订单的串行执行:
remaining = intent.target_qty
while remaining > 0 and before_deadline():
quotes = collect_fresh_quotes(prefunded_venues)
slice_qty = choose_slice_size(remaining, quotes)
candidates = quote_same_size(quotes, slice_qty)
route = lowest_total_cost(candidates)
if route is None or exceeds_budget(route):
break
child = submit_once(route)
result = reconcile_until_terminal(child)
if result.state == "UNKNOWN":
pause_parent()
break
remaining -= result.confirmed_filled_qty
update_cash_and_fees(result)
子订单从 Created 进入 Submitted,收到受理信息后进入 Accepted,随后可能出现 Partially Filled,最终为 Filled、Canceled 或 Rejected。撤单请求发出不等于撤单成功,最终回报仍可能包含部分成交。母订单只按确认成交数量扣减剩余量;状态未明时先查询回报,不能直接把同一数量再交给另一渠道。
06 / 如何知道路由真的更好
评价至少同时观察费用后执行偏差与成交率。对确认成交的总数量 Q,计算加权均价 P̄,并沿用母订单决策时的 P₀:费用后偏差为 10,000 × [sQ(P̄ − P₀) + 实际费用] / (QP₀),单位为基点,越低越好。实际费用包含该母订单发生的交易费和网络费,包括最终未成交但已经支付的费用;当 Q = 0 时,单独报告失败费用,不计算该比率。
成交率定义为期限内确认成交数量除以母订单目标数量。只报告已成交部分的均价,会让“只成交容易的少量订单”看上去表现优异。还应记录完成时间、未成交数量,以及预测总支出与实际支出的差额,并按数量、深度、渠道和波动环境分组比较。
这个模型的边界在于报价会变化,等待代价需要校准,多条路径也可能消耗同一份底层流动性。分配到共享池的子订单必须联合报价,不能将两个独立快照简单相加。历史回放适合检查成本口径与规则,小规模受控执行才能进一步检验排队、确认和真实费用;在获得这些记录之前,算例只用于说明方法。
参考资料
Uniswap Developers · Understanding Swaps:流动性、价格冲击与交换执行参数。
CCXT Manual · Precision and Limits / Orders:市场精度、下单限额及订单语义。
← 返回研究论文