第十六章:算法交易执行——TWAP与VWAP算法、冰山订单、拆单策略、市场冲击成本控制
做基差交易和跨品种套利,策略再好,执行不到位也是白搭。
我见过太多人,策略回测曲线漂亮得不行,一上实盘就亏钱。为什么?
说白了,就是市场冲击成本把利润吃掉了。
你想想看,你发现螺纹钢和热卷的价差偏离了,想一把梭进去。结果你的大单直接把价差打回原形,你反而买在了最贵的点。这就是典型的「滑点吃掉利润」。
所以这一章,我们来聊聊怎么用算法交易来降低冲击成本。我个人习惯把这类算法分成两类:时间驱动型和价格驱动型。TWAP和VWAP属于前者,冰山订单和拆单策略属于后者。
16.1 市场冲击成本:你赚的钱,可能都交了「过路费」
先搞清楚一个概念:市场冲击成本到底是什么?
简单说,就是你的订单对市场价格造成的影响。你买100手和买1手,成交价格肯定不一样。这个差价,就是冲击成本。
我在项目中遇到过最夸张的一次,一个客户用市价单扫货,直接把一个流动性不好的品种拉了2%上去。等他建完仓,行情已经变了,策略逻辑完全失效。
冲击成本可以用一个经验公式估算:
冲击成本 ≈ (订单量 / 市场深度) × 买卖价差 × 价格
嗯,这里要注意,市场深度越浅,你的订单量越大,冲击成本就越高。所以做套利的朋友,尤其是跨品种套利,一定要关注两个品种的流动性差异。
核心观点: 算法交易的核心目标,就是在「时间风险」和「冲击成本」之间找到平衡。你做得越快,冲击越大;你拖得越久,价格变动的风险越高。
16.2 TWAP算法:时间加权平均价格
TWAP,全称Time-Weighted Average Price。说白了,就是把一个大单拆成很多小单,在固定的时间间隔内均匀执行。
举个例子:你想在1小时内买入100手螺纹钢。TWAP的做法是,每分钟买入约1.67手(100手/60分钟)。不管价格怎么变,我就按时间切。
TWAP的适用场景:
- 市场流动性一般,但价格波动不大
- 你希望成交价格接近这段时间的平均价
- 对时效性要求不高,可以慢慢吃
TWAP的缺点也很明显:
- 容易被市场「狙击」。如果大家都知道你在用TWAP,对手盘可以在每个时间点挂单等你
- 无法利用价格波动。价格跌了,你还是在买;价格涨了,你还是在买
我曾经用TWAP做过一个股指期货的移仓换月。当时觉得挺稳的,结果被程序化交易者盯上了,每次我下单前价格就跳一下。后来我学乖了,加了随机偏移量。
16.3 VWAP算法:成交量加权平均价格
VWAP,Volume-Weighted Average Price。这个比TWAP聪明一点。
它的逻辑是:按照历史成交量分布来分配订单。成交量大的时间段多买点,成交量小的时间段少买点。
为什么这样更好?
你想想看,开盘和收盘的时候成交量最大,流动性最好。这时候你多下点单,冲击成本反而小。中午休息的时候成交量稀稀拉拉,你硬要下单,反而容易把价格打飞。
VWAP的实现步骤:
- 获取历史成交量数据(通常用过去5-20个交易日的分钟级数据)
- 计算每个时间段的成交量占比
- 按照占比分配订单量
# 一个简单的VWAP拆单示例
import pandas as pd
import numpy as np
def vwap_schedule(total_qty, hist_volume, periods=240):
"""
total_qty: 总订单量
hist_volume: 历史成交量序列(长度=periods)
"""
volume_ratio = hist_volume / hist_volume.sum()
schedule = total_qty * volume_ratio
return schedule.round().astype(int)
# 假设一天240分钟,历史成交量数据
hist_vol = np.random.rand(240) * 1000
order_schedule = vwap_schedule(10000, hist_vol)
print(f"第一分钟下单量: {order_schedule[0]}手")
print(f"最后一分钟下单量: {order_schedule[-1]}手")
个人经验: VWAP在股指期货和商品期货上效果差异很大。股指期货的成交量分布比较稳定,VWAP效果不错。但有些商品期货,比如某些农产品,成交量分布受季节性因素影响很大,用VWAP反而容易出问题。我建议至少用20个交易日的数据做平滑。
16.4 冰山订单:隐藏你的真实意图
冰山订单,顾名思义,只露出水面的一小部分,大部分藏在下面。
在交易中,你挂一个1000手的买单,所有人都能看到。对手盘会怎么想?「有人要吃货,我先拉高价格。」
但如果你只挂100手,成交了再补100手,别人看到的始终只有100手。这就是冰山订单的精髓。
冰山订单的参数:
| 参数 | 说明 | 我的建议 |
|---|---|---|
| 总订单量 | 你要买入/卖出的总数量 | 根据策略资金量确定 |
| 显示量 | 每次暴露在盘口的数量 | 不超过市场深度1% |
| 补单阈值 | 当剩余多少时补单 | 显示量的50%-80% |
嗯,这里要注意,冰山订单不是万能的。现在很多交易所的Level-2数据可以识别冰山订单。我记得有一次,我用冰山订单在铁矿石上建仓,结果被高频交易者用逐笔成交数据反推出来了。他们算出了我的总订单量,然后反向操作。
避坑指南: 我曾经在流动性很差的品种上用冰山订单,结果显示量太小,半天成交不了一笔。最后价格跑了,我反而亏了。冰山订单适合流动性中等以上的品种,别在冷门合约上硬用。
16.5 拆单策略:把大象装进冰箱分几步
拆单策略,说白了就是「化整为零」。把一个大单拆成无数个小单,分批进场。
拆单策略的核心在于「随机化」。如果你每次都按固定模式拆单,很容易被市场识别。我常用的几种拆单方式:
- 时间随机拆单: 在时间间隔上加入随机偏移,比如本来是每10秒下一单,改成8-12秒随机
- 数量随机拆单: 每次下单量在目标量附近随机波动,比如目标100手,实际在80-120手之间随机
- 价格触发拆单: 不按时间,而是等价格回到某个区间再下单。这个比较高级,适合做均值回归策略
# 时间随机拆单示例
import random
import time
def random_schedule(total_qty, base_interval=10, jitter=2):
"""
base_interval: 基础间隔(秒)
jitter: 随机偏移(秒)
"""
remaining = total_qty
while remaining > 0:
# 每次下剩余量的10%-20%
batch = int(remaining * random.uniform(0.1, 0.2))
batch = min(batch, remaining)
# 执行下单(这里用print模拟)
print(f"下单: {batch}手, 剩余: {remaining - batch}")
remaining -= batch
# 随机等待
wait_time = base_interval + random.uniform(-jitter, jitter)
time.sleep(max(0.1, wait_time))
# 模拟拆单
random_schedule(1000, base_interval=5, jitter=1)
16.6 知识体系总览
说了这么多,我画了一张图帮你理清思路。算法交易执行的核心,就是围绕「降低冲击成本」这个目标展开的。
16.7 实战中的选择建议
讲了这么多,到底怎么选?我根据自己的经验,给一个简单的决策流程:
- 先看流动性: 日均成交量低于1万手的品种,别用算法交易,手动慢慢吃吧
- 再看时间窗口: 你有1小时还是1天?时间紧用TWAP,时间充裕用VWAP
- 最后看对手: 如果这个品种上高频交易者多,一定要加随机化,别让他们摸清你的规律
我的经验之谈: 做基差交易的时候,我通常用VWAP配合冰山订单。先用VWAP规划好每个时间段的订单量,然后用冰山订单隐藏真实意图。两个策略叠加,效果比单独用任何一个都好。但要注意,别把参数设得太死,留点灵活调整的空间。
好了,这一章的内容就到这里。算法交易执行是个实践性很强的东西,光看理论没用。我建议你找个模拟盘,拿小资金试试不同的拆单策略,感受一下冲击成本到底有多大。只有亲手做过,才能真正理解这些算法的价值。