主题切换
4.2 交易成本建模
概念详解
交易成本是连接"纸上策略"与"真实收益"的桥梁。许多策略在忽略成本时看起来利润丰厚,一旦加入合理的成本模型就变得一文不值。交易成本建模的目标是尽可能真实地还原市场摩擦,让回测结果更接近实盘表现。
交易成本可以分为显性成本和隐性成本两大类:
显性成本(直接可见的支出):
- 佣金:券商收取的交易手续费,通常按成交金额的固定比例收取(如万三 = 0.03%)
- 印花税:政府对交易征收的税费(A股卖出时征收成交金额的0.05%)
- 交易所规费:交易所收取的经手费、证管费等
- 过户费:股票过户时收取的费用
隐性成本(不易直接观察但对收益有实质影响):
- 买卖价差:最优买价与最优卖价之间的差额,是市场微观结构的基本成本
- 冲击成本:大额订单导致的成交价偏离预期价格
- 延迟成本:从做出决策到订单执行之间的价格变动
- 机会成本:未能成交导致的潜在收益损失
名词解释:冲击成本
大额订单导致市场价格向不利方向移动的额外成本,与订单大小和市场深度有关。例如买入100万元的股票时,由于你的买盘推高了价格,实际成交均价可能比你最初看到的价格高出0.1%,这0.1%就是冲击成本。
名词解释:滑点
实际成交价格与预期价格之间的差额。滑点的来源包括买卖价差、市场冲击、以及订单提交到成交之间的价格波动。滑点 = 实际成交价 - 信号生成时的参考价。
名词解释:买卖价差
最优卖价(ask)与最优买价(bid)之间的差额。以市价单买入时,你按卖一价成交;以市价单卖出时,你按买一价成交。单边交易的实际成本约等于半个价差。
数学原理
基础成本模型
单次交易的总成本可以表示为:
其中:
是成交金额 是佣金费率 是印花税费率 是买卖价差率 是冲击系数 是日均成交额
冲击成本模型(Almgren-Chriss框架)
市场冲击成本通常分为永久冲击和临时冲击两部分:
其中
简单冲击估算
对于大多数中低频策略,可使用简化公式:
其中
Python实战
📌 案例:加入成本
此处有展示代码展开 ▼
python
cost_rate = 0.001
trades = df['position'].diff().abs()
cost = trades * df['Close'] * cost_rate
net_returns = strategy_returns - cost / capital点击展开可浏览运行结果
📘 本段为代码片段(仅展示函数/类定义,未提供运行入口,无需运行)
📌 案例:完整的成本计算器
此处有展示代码展开 ▼
python
class TransactionCostModel:
"""
完整的A股交易成本模型
"""
def __init__(self,
commission_rate=0.0003, # 佣金万三
stamp_tax_rate=0.0005, # 印花税千0.5(仅卖出)
transfer_fee_rate=0.00002, # 过户费万0.2
min_commission=5.0): # 最低佣金5元
self.commission_rate = commission_rate
self.stamp_tax_rate = stamp_tax_rate
self.transfer_fee_rate = transfer_fee_rate
self.min_commission = min_commission
def calculate_cost(self, price, shares, side, avg_daily_volume=None):
"""
计算单笔交易的总成本
Parameters:
price: 成交均价
shares: 成交股数(正数)
side: 'buy' 或 'sell'
avg_daily_volume: 日均成交量(股),用于估算冲击成本
"""
value = price * shares
# 显性成本
commission = max(value * self.commission_rate, self.min_commission)
stamp_tax = value * self.stamp_tax_rate if side == 'sell' else 0
transfer_fee = max(value * self.transfer_fee_rate, 1.0)
# 隐性成本:买卖价差(假设万二)
spread_cost = value * 0.0002
# 隐性成本:冲击成本(简化模型)
if avg_daily_volume and avg_daily_volume > 0:
participation_rate = shares / avg_daily_volume
# 参与率越高,冲击成本越大
impact_cost = value * (0.1 * np.sqrt(participation_rate))
else:
impact_cost = 0
total_cost = commission + stamp_tax + transfer_fee + spread_cost + impact_cost
return {
'commission': commission,
'stamp_tax': stamp_tax,
'transfer_fee': transfer_fee,
'spread_cost': spread_cost,
'impact_cost': impact_cost,
'total_cost': total_cost,
'cost_rate': total_cost / value
}点击展开可浏览运行结果
📘 本段代码定义了 1 个函数/类:类 `TransactionCostModel`(完整的A股交易成本模型)。该片段为教学展示(未包含独立运行的输入数据),可在实战练习中结合真实数据调用。
📌 案例:在回测中集成成本模型
此处有展示代码展开 ▼
python
def backtest_with_costs(strategy, data, cost_model):
"""
带完整成本模型的回测循环
"""
capital = 1_000_000
positions = {}
equity_curve = []
cost_log = []
for date, row in data.iterrows():
# 策略生成目标持仓
target_positions = strategy.predict(row, positions)
# 计算所需交易
current_symbols = set(positions.keys()) | set(target_positions.keys())
daily_cost = 0
for sym in current_symbols:
target_shares = target_positions.get(sym, 0)
current_shares = positions.get(sym, 0)
diff = target_shares - current_shares
if diff != 0:
side = 'buy' if diff > 0 else 'sell'
cost_info = cost_model.calculate_cost(
price=row['Close', sym],
shares=abs(diff),
side=side,
avg_daily_volume=row.get(('Volume', sym), None)
)
daily_cost += cost_info['total_cost']
cost_log.append({**cost_info, 'date': date, 'symbol': sym})
# 执行交易并扣除成本
for sym in current_symbols:
target_shares = target_positions.get(sym, 0)
current_shares = positions.get(sym, 0)
diff = target_shares - current_shares
if diff != 0:
capital -= diff * row['Close', sym] # 支付买入/收入卖出
capital -= daily_cost # 扣除交易成本
positions = {k: v for k, v in target_positions.items() if v != 0}
# 记录净值
total_value = capital + sum(
positions.get(sym, 0) * row['Close', sym]
for sym in positions
)
equity_curve.append({'date': date, 'equity': total_value})
return pd.DataFrame(equity_curve).set_index('date'), pd.DataFrame(cost_log)点击展开可浏览运行结果
📘 本段代码定义了 1 个函数/类:函数 `backtest_with_costs`(带完整成本模型的回测循环)。该片段为教学展示(未包含独立运行的输入数据),可在实战练习中结合真实数据调用。
常见误区
成本比例选择过于乐观:很多回测只用万1或万2的费率,但实际交易中加上冲击成本和滑点,小票的交易成本可能达到千3甚至更高。
忽略印花税:A股卖出时征收0.05%的印花税。如果你是高换手率策略,这笔费用累积起来非常可观。
用成交均价代替实际成交价:以收盘价作为成交价的假设非常强。如果你的策略信号需要在盘中确认(如突破信号),收盘价可能与实际入场价相差甚远。
不考虑流动性约束:小市值股票的日均成交额可能只有几百万,你的策略如果分配了几十万的权重,在实际中根本无法以下单时的价格成交。
忽略最低佣金:很多券商有最低5元的佣金要求。对于小额交易(如不足1.7万元),实际佣金费率远高于名义费率。
实战练习
用你的回测框架分别计算以下场景的年化收益差异:无成本 vs 仅佣金(万三) vs 完整成本(佣金+印花税+买卖价差万二)。对于月换手率100%的策略,成本侵蚀了多少收益?
编写一个流动性过滤器:对于日均成交额小于1000万元的股票,自动从可交易池中剔除。观察过滤前后策略表现的变化。
模拟冲击成本的非线性特征:假设冲击成本
,其中 取日波动率的2%。计算该策略中每笔交易的冲击成本,找出冲击成本最高的10笔交易。
延伸阅读
- Almgren, R. & Chriss, N. (2001). "Optimal Execution of Portfolio Transactions." Journal of Risk.
- Kissell, R. (2014). The Science of Algorithmic Trading and Portfolio Management. Academic Press. 全面讨论交易成本和执行算法。
- Bouchaud, J. P. et al. (2009). "How Markets Slowly Digest Changes in Supply and Demand." 深入分析冲击成本的微观结构基础。
- Frazzini, A., Israel, R., & Moskowitz, T. J. (2018). "Trading Costs." 实证分析了实际交易成本的大小。
本章要点
- 交易成本分为显性成本(佣金、印花税、规费)和隐性成本(买卖价差、冲击成本、延迟成本)。
- 冲击成本与订单大小非线性相关:订单越大,单位冲击成本越高。核心公式为
。 - A股市场的交易成本结构:卖出时征收印花税(0.05%),买入不征收;佣金通常在万2~万3之间;还有过户费和规费。
- 完整的成本建模应该包含最低佣金约束和流动性过滤。
- 在回测中考虑成本后,许多高换手率策略的预期收益会大幅缩水甚至转负——这是成本建模的价值所在。