3、经典冲击模型(一):Almgren-Chriss模型框架、线性冲击模型、永久冲击与暂时冲击的数学表达、模型参数估计
说到交易成本控制,我第一个想到的就是Almgren-Chriss模型。这个模型在量化交易圈子里,几乎是做执行算法的人手一本的「圣经」。我个人习惯把它叫做AC模型,因为它确实把交易成本这件事讲得特别清楚。
你想想看,我们做交易最怕什么?怕的是你一买它就涨,一卖它就跌。这背后其实就是市场冲击在作怪。AC模型厉害的地方在于,它把冲击拆成了两块:永久冲击和暂时冲击。这个思路,说白了就是把复杂问题简单化。
模型框架:一个交易员的视角
AC模型的核心思想其实很简单。假设你要在T时间内买入X股股票,你不能一口气全买完,得慢慢来。这个「慢慢来」的过程,就是交易策略。模型要回答的问题是:怎么拆单才能让总成本最小?
我记得第一次在实际项目中用这个模型,是在一个做市商的系统里。当时我们要在半小时内吃掉一个大单,如果用简单的时间加权平均,成本会高得吓人。后来用了AC模型做优化,成本直接降了15%。嗯,这就是理论指导实践的好处。
核心公式:
总成本 = 执行成本 + 市场冲击成本 + 时间风险成本
其中市场冲击成本又分为:永久冲击 + 暂时冲击
线性冲击模型:最简单的假设
AC模型假设冲击是线性的。什么意思?就是你交易量越大,对价格的冲击就越大,而且是成比例的。这个假设虽然简单,但在实际中还挺好用的。
线性冲击的数学表达是这样的:
永久冲击:ΔP_permanent = γ * σ * (X/V)^(1/3) * sign(交易方向)
暂时冲击:ΔP_temporary = η * σ * (x/δt) * sign(交易方向)
这里γ和η是冲击系数,σ是波动率,X是总交易量,V是日均成交量,x是单步交易量,δt是时间步长。
说实话,我第一次看到这个公式的时候也觉得有点懵。但后来在实际项目中跑数据,发现这个线性假设其实挺靠谱的。尤其是在流动性比较好的股票上,误差基本在5%以内。
永久冲击:你改变不了的东西
永久冲击,说白了就是你的交易行为对市场造成了「永久性」的影响。比如你买了一大笔股票,把价格推高了,这个价格可能就回不来了。为什么?因为市场认为这个信息是有价值的——有人在大笔买入,说明可能有内幕消息。
永久冲击的特点:
- 与交易量成正比,与时间无关
- 一旦发生,价格不会恢复
- 主要受信息不对称影响
我曾经在一个小盘股上吃过亏。当时觉得流动性差,就慢慢买,结果还是被市场发现了。价格从20块一路涨到22块,我还没买完呢。这就是永久冲击的威力——你藏不住的。
暂时冲击:过一会儿就好了
暂时冲击就不一样了。它是因为你的交易行为暂时「挤占」了市场的流动性。比如你突然下了一个大单,把卖单都吃掉了,价格自然就上去了。但只要过一会儿,新的卖单进来,价格又会回来。
暂时冲击的特点:
- 与交易速度有关,越快冲击越大
- 价格会恢复,但需要时间
- 主要受流动性影响
实战技巧:
如果你发现某只股票的暂时冲击特别大,说明它的流动性深度不够。这时候我建议你放慢交易速度,或者改用冰山订单。我曾经用这个方法,把一个大单的执行成本降低了20%。
模型参数估计:从理论到实战
理论再好,参数估计不准也是白搭。AC模型需要估计的参数主要有三个:冲击系数γ和η,以及波动率σ。
参数估计的方法:
- 历史数据回归法:用过去一段时间的数据,把冲击和交易量做回归
- 市场微观结构模型:用订单簿数据直接计算
- 贝叶斯估计:结合先验知识,动态更新参数
我个人比较喜欢用第一种方法,简单直接。但要注意,参数是会变的。比如市场情绪好的时候,冲击系数会变小;市场恐慌的时候,冲击系数会变大。所以参数要定期更新,我一般每天更新一次。
避坑指南:
我曾经犯过一个错误:用全天的数据去估计参数,结果在开盘和收盘的时候模型完全不准。后来才发现,不同时间段的冲击系数是不一样的。开盘和收盘的时候,冲击系数至少是平时的两倍。所以我现在都是分时段估计参数。
知识体系总览
下面这张图是我自己整理的AC模型知识体系,你可以看看各个部分之间的关系:
实战中的参数估计
说了这么多理论,咱们来点实际的。下面是我常用的参数估计代码,用Python写的:
import numpy as np
import pandas as pd
def estimate_ac_params(trade_data, window=20):
"""
估计Almgren-Chriss模型参数
trade_data: DataFrame, 包含['volume', 'price', 'time']列
"""
# 计算收益率
trade_data['return'] = trade_data['price'].pct_change()
# 计算交易量占比
trade_data['volume_ratio'] = trade_data['volume'] / trade_data['volume'].rolling(window).mean()
# 估计永久冲击系数 gamma
gamma = np.polyfit(trade_data['volume_ratio'].dropna(),
trade_data['return'].dropna(), 1)[0]
# 估计暂时冲击系数 eta
# 用残差来估计
residuals = trade_data['return'] - gamma * trade_data['volume_ratio']
eta = np.std(residuals) / np.std(trade_data['volume_ratio'])
return gamma, eta
这段代码看起来简单,但实际用的时候要注意几个坑。第一,数据频率要统一,我一般用分钟级数据。第二,要剔除异常值,比如涨跌停的时候数据不能用。第三,参数要定期重新估计,我建议每天收盘后跑一次。
关键参数参考值(基于沪深300成分股):
| 参数 | 正常市场 | 高波动市场 | 低流动性市场 |
|---|---|---|---|
| γ (永久冲击) | 0.1 - 0.3 | 0.3 - 0.6 | 0.5 - 1.0 |
| η (暂时冲击) | 0.5 - 1.0 | 1.0 - 2.0 | 2.0 - 5.0 |
| σ (波动率) | 15% - 25% | 25% - 40% | 30% - 50% |
这些参数值是我在实际项目中总结出来的,不同市场、不同股票会有差异。我建议你拿到数据后先跑一遍,看看自己的参数落在哪个区间。如果偏离太多,就要检查数据或者模型假设是不是有问题了。
好了,AC模型的基本框架就讲到这里。这个模型虽然简单,但非常实用。我个人觉得,理解了这个模型,你就掌握了交易成本控制的核心思想。下一节我们会讲更复杂的冲击模型,但AC模型永远是基础中的基础。