市场交易时才交易
成交量加权平均价格是所有成交的平均价格,每笔成交按其大小加权:sum(price * volume) / sum(volume)。它是机构交易中最常见的基准,因为它代表当天一个普通参与者付出的价格。VWAP 算法通过*按市场成交量的比例*交易来争取达到或超过它:市场活跃时(开盘、收盘)多做,安静时(午间)少做。TWAP 跟随时钟,VWAP 则跟随人群,所以你的每笔订单只占其他人所做交易的类似比例,对价格的推动更小。
# The benchmark: each price counts as much as the volume that traded at it
def vwap(prices, volumes):
return sum(p * v for p, v in zip(prices, volumes)) / sum(volumes)
prices = [100.0, 101.0, 102.0]
volumes = [100, 300, 600]
print(round(vwap(prices, volumes), 4)) # 101.5 most volume traded near 102, so the VWAP leans that way
print(sum(prices) / len(prices)) # 101.0 the plain average ignores how much traded at each price成交量分布及跟随它的计划
你无法提前知道今天的成交量,所以 VWAP 算法使用根据历史估计的成交量分布:比如过去 20 个交易日里,一天成交量中在各个区间成交的平均占比。日内成交量通常呈 U 形,开盘和收盘时高。计划随后给每个区间分配 total * 占比 股。这些很少是整数,把每个都四舍五入可能多出或少掉股数。最大余数法解决了这个问题:先给每个区间取整,再把剩下的股数每次一股地分给小数部分最大的区间,这样计划的总和始终等于订单量。
# From volumes to a profile, then to whole shares by the largest remainder method
import math
def volume_profile(volumes):
total = sum(volumes)
return [v / total for v in volumes]
def vwap_schedule(total, profile):
exact = [total * share for share in profile]
shares = [math.floor(value + 1e-9) for value in exact]
leftover = total - sum(shares)
for index in sorted(range(len(profile)), key=lambda i: (-(exact[i] - shares[i]), i))[:leftover]:
shares[index] += 1
return shares
profile = volume_profile([300, 100, 100, 200, 500, 800]) # a U-shape: busy at the open and the close
print([round(p, 3) for p in profile]) # [0.15, 0.05, 0.05, 0.1, 0.25, 0.4]
print(vwap_schedule(1003, profile)) # [151, 50, 50, 100, 251, 401] adds up to 1003
print(sum(vwap_schedule(1003, profile))) # 1003参与率上限与跟踪基准
计划只是计划,市场可能比分布预测的更安静。参与率上限把你在每个区间的交易量限制在该区间市场实际成交量的某个比例(比如 10%)之内,这样你永远不会成为交易流的主体。没能成交的股数会结转到之后的区间,因此到最后可能仍有部分未执行;那时你要决定是承担更多风险、延长窗口还是停止。一天结束后,把你的平均价与市场 VWAP 相比:以基点计的差额就是算法的跟踪误差。在上涨的市场里靠晚交易跑赢 VWAP 并不是本事;要用多日的滑点来评判算法。
# A participation cap: never more than a fraction of the market's volume, carrying the rest forward
import math
def cap_participation(schedule, market_volumes, rate):
trades, carry = [], 0
for scheduled, volume in zip(schedule, market_volumes):
wanted = scheduled + carry
done = min(wanted, math.floor(rate * volume + 1e-9))
trades.append(done)
carry = wanted - done
return trades, carry
print(cap_participation([100, 100, 100], [500, 1000, 1000], 0.1)) # ([50, 100, 100], 50)
print(cap_participation([100, 0, 0, 0], [500, 1000, 400, 800], 0.1)) # ([50, 50, 0, 0], 0) the carry is traded when the market allows it# Following the clock (TWAP) and following the volume (VWAP) give different average prices on the same day
prices = [100.0, 100.2, 100.1, 100.4, 100.3, 100.5]
volumes = [300, 100, 100, 200, 500, 800] # busy at the open and the close
market_vwap = sum(p * v for p, v in zip(prices, volumes)) / sum(volumes)
twap_average = sum(prices) / len(prices) # equal pieces at every time
print(round(market_vwap, 4), round(twap_average, 4)) # 100.33 100.25
print(round((twap_average - market_vwap) / market_vwap * 10000, 1)) # -8.0 a TWAP buy beat the VWAP here, by trading light when volume was heavyVWAP 是基准,而不是目标跟上 VWAP 只说明你像普通参与者一样交易,如果是你自己的订单推高了价格,这可能是个糟糕的结果(你的成交本身就是用来比较的 VWAP 的一部分)。还要对照到达价格检查算法,在没有用来估计的日子上测试分布,并预料分布在新闻日、半天交易日和指数再平衡日会失效。为每个子订单保留可读的日志,便于之后分析。
- VWAP 是
sum(price * volume) / sum(volume),常用的基准;VWAP 算法按市场成交量的比例交易。 - 根据历史估计成交量分布,并用最大余数法按它拆分订单,使整数股数正好加总。
- 给每个区间的参与度设上限并把剩余结转;用多日的跟踪误差和滑点来评判算法。
下一节:做市,双边报价并管理由此产生的库存。
实验:评分内容
- 用
vwap计算基准25 分 - 用
volume_profile描出一天的形状25 分 - 用
vwap_schedule拆分订单25 分 - 用
cap_participation尊重市场25 分