能交易多少?数量、手续费与滑点

套利课程第 10 课。结合手续费和订单簿遍历,计算不同交易数量下的利润,并找到利润最高的数量。附带可手工核对数字的简短 Python 代码。

《自己动手做套利机器人》课程第 10 课。第 9 课你学会了遍历订单簿。今天我们加上手续费,并回答每个机器人都必须回答的问题:应该交易多少个币?

两种相互拉扯的力量

多交易一些币会带来两件事:

  • 如果价差存在,币的数量更多,赚到的钱也更多。
  • 交易数量更多,会触及订单簿更深的档位,价格更差。这就是滑点。

数量较小时,价差带来的收益占上风;数量大时,滑点占上风。两者之间会有一个利润最高的数量。如果连最小数量都会亏钱,那就不应该交易。

还要考虑手续费。手续费是每笔交易金额的一定比例。假设两个交易所的手续费都是 0.1%(0.001)。买入时,你要多付 0.1%;卖出时,收到的钱要少 0.1%。

手工计算一个数量的利润

使用第 9 课的订单簿:

  • 交易所 A 的卖单:2 个币,价格 100.00;接着 3 个币,价格 100.20。在这里买入。
  • 交易所 B 的买单:1 个币,价格 100.30;接着 5 个币,价格 99.90。在这里卖出。

交易 1 个币:

  • 在 A 买 1 个:100.00。加上手续费:100.00 × 1.001 = 100.10。
  • 在 B 卖 1 个:100.30。扣除手续费:100.30 × 0.999 = 100.1997。
  • 利润:100.1997 − 100.10 = 0.0997。

交易 1.5 个币:

  • 在 A 买 1.5 个:都来自第一档,1.5 × 100.00 = 150.00。加手续费后是 150.15。
  • 在 B 卖 1.5 个:1 × 100.30 + 0.5 × 99.90 = 150.25。扣手续费后是 150.0998。
  • 利润:150.0998 − 150.15 = −0.0502,出现亏损。

在 B 的第二档卖出的半个币,价格是 99.90,低于买入成本。多卖了半个币,就让盈利变成了亏损。

利润如何随数量变化

直接寻找最佳数量的方法是尝试许多数量并保留利润最高的一个,下面的代码就是这样做的。对于单个交易对,尝试几百个数量完全够用。每次价格更新都要检查数千个交易对的机器人,可以使用下一节的边际利润规则提前停止。

把利润写成一个函数:输入数量,得到赚到的钱。代码使用第 9 课的 walk 函数。

def walk(levels, amount):
    money, left = 0.0, amount
    for price, size in levels:
        take = min(size, left)
        money += take * price
        left -= take
        if left <= 0:
            break
    return money, amount - left

asks_A = [(100.00, 2), (100.20, 3)]
bids_B = [(100.30, 1), (99.90, 5)]
FEE = 0.001

def profit(q):
    paid, got_a = walk(asks_A, q)
    received, got_b = walk(bids_B, q)
    if got_a < q or got_b < q:          # 订单簿深度不足以成交这么多
        return None
    return received * (1 - FEE) - paid * (1 + FEE)

for q in [0.5, 1, 1.5, 2, 3, 4, 5]:
    p = profit(q)
    print(q, None if p is None else round(p, 4))

best = max((q / 10 for q in range(1, 51)), key=lambda q: profit(q) if profit(q) is not None else -1e9)
print("best size", best, "profit", round(profit(best), 4))

输出:

0.5 0.0498
1 0.0997
1.5 -0.0502
2 -0.2002
3 -0.7003
4 -1.2004
5 -1.7005
best size 1.0 0.0997

利润在交易 1 个币时达到最高;卖出数量超过 B 的最优档深度后,就会下降,因为后续价格是 99.90。最佳数量是 1 个币,利润为 0.0997。这大约是 100 的 0.1%,而普通交易者的手续费也在相近水平。这就是为什么流动性好的市场中的套利利润空间很小。

利润曲线背后的规则

遍历订单簿时,每增加一个币都有自己的价格,也就是它所在档位的价格。下一枚币带来的额外利润叫边际利润:扣除手续费后,在 B 卖出它收到的钱,减去在 A 买入它花的钱。边际利润为正时,增加数量有帮助;边际利润变成负数时就该停止。最佳数量正是边际利润越过零点的位置。

用例子核对:第一个币,在 B 卖出后得到 100.30 × 0.999 = 100.1997,在 A 买入要花 100.00 × 1.001 = 100.10。边际利润为 +0.0997。第二个币在 B 的价格是 99.90,扣手续费后得到 99.90 × 0.999 = 99.8001,在 A 仍要花 100.10。边际利润为 −0.2999。因此在一个币时停止。

这意味着不必尝试每一种数量。让两边的订单簿同步向下遍历,每次处理一个币或一个档位;边际利润不再为正时就停。在真实机器人中,第 4 课比较最优价格的做法是快速初筛,而这次遍历则是第二步的精确检查。

数量的其他限制

即使按订单簿算出的最佳数量合适,实际交易量仍可能太大或太小:

  • 你的余额。你不能买入超过自己报价币余额的数量。
  • 交易所规则。每个交易对都有最小下单量和数量步长(不能交易 0.0000001 个币)。
  • 你自己的风险限额。预算只有 10 美元的机器人不会交易 1000 美元。

最终数量取以下各项中的最小值:min(best size by book, balance, risk limit),然后向下取整到交易所规定的步长。如果结果低于交易所的最小下单量,就跳过这笔交易。

让 AI 智能体编写并测试代码

一个含糊的请求:

计算最佳交易数量。

一个带有核对数字的请求:

编写 size.py。复用 book.py 中的 walk。添加 profit(asks, bids, q, fee),计算数量 q 扣手续费后的利润;如果任一订单簿深度不足则返回 None。测试:asks [(100.00, 2), (100.20, 3)],bids [(100.30, 1), (99.90, 5)],fee 为 0.001。q = 1 时 profit 必须为 0.0997,q = 1.5 时必须为 -0.0502,q = 6 时必须为 None。然后添加 best_size(asks, bids, fee, step),按 step 的倍数尝试数量并返回最佳值;step 为 0.1 时必须返回 1.0。不要使用随机数。

像本课一样,手工重算前两个测试值。如果智能体在 q = 1.5 时给出不同利润,让它展示买卖两侧的遍历过程。

练习

  1. 运行代码并与上面的表格比较。
  2. 把手续费改成 0,再改成 0.003。最佳数量和最大利润有什么变化?手续费达到多少时交易完全不再盈利?(提示:一个币的利润为 0.30 − 200.3 × fee,手续费约为 0.15% 时利润为零。)
  3. 在 B 的第一档之后添加第三档买单 (99.95, 3)。手工计算交易 2 个币时利润如何变化,再用代码检查。
  4. 修改 walk,让它也返回平均价格,并在最佳数量下打印平均买入价和平均卖出价。

接下来学什么

现在机器人根据屏幕上的数字知道该交易什么、交易多少。但订单到达时屏幕上的数字已经过时,订单可能只部分成交,第二条交易腿也可能失败。第 11 课会直面现实:计划到资金到账之间可能发生什么。