feat(backtest): add SquareRootSlippage + VolumeSlippage

This commit is contained in:
GitHub
2026-06-12 20:47:03 +08:00
parent 4098af02bf
commit d081eeb265
2 changed files with 134 additions and 0 deletions
+60
View File
@@ -4,6 +4,8 @@ from __future__ import annotations
from abc import ABC, abstractmethod
import numpy as np
class SlippageModel(ABC):
"""滑点模型基类。
@@ -67,3 +69,61 @@ class PercentSlippage(SlippageModel):
direction: str,
) -> float:
return price * size * self._rate
class SquareRootSlippage(SlippageModel):
"""方根市场冲击模型(Almgren-Chriss 简化版)。
impact = σ × √(participation_rate) × price × size × impact_coeff
当 volume=0 或 volatility=0 时退化为 PercentSlippage(rate=0.001)。
"""
def __init__(self, impact_coeff: float = 0.1) -> None:
self._impact_coeff = impact_coeff
self._fallback = PercentSlippage(rate=0.001)
def compute(
self,
price: float,
size: float,
volume: float,
volatility: float,
direction: str,
) -> float:
if size <= 0:
return 0.0
if volume <= 0 or volatility <= 0:
return self._fallback.compute(price, size, volume, volatility, direction)
participation_rate = min(size / volume, 1.0)
impact = volatility * np.sqrt(participation_rate) * price * size * self._impact_coeff
return float(impact)
class VolumeSlippage(SlippageModel):
"""成交量比例滑点。
cost = (base_bps / 10000) × (size / volume) × price × size
当 volume=0 时退化为 PercentSlippage(rate=base_bps/10000)。
"""
def __init__(self, base_bps: float = 10.0) -> None:
self._base_bps = base_bps
self._fallback = PercentSlippage(rate=base_bps / 10000.0)
def compute(
self,
price: float,
size: float,
volume: float,
volatility: float,
direction: str,
) -> float:
if size <= 0:
return 0.0
if volume <= 0:
return self._fallback.compute(price, size, volume, volatility, direction)
rate = self._base_bps / 10000.0
participation = min(size / volume, 1.0)
return rate * participation * price * size
+74
View File
@@ -8,6 +8,8 @@ from easy_tdx.backtest.slippage import (
FixedSlippage,
PercentSlippage,
SlippageModel,
SquareRootSlippage,
VolumeSlippage,
)
@@ -85,3 +87,75 @@ class TestPercentSlippage:
cost = model.compute(price=100.0, size=500, volume=20000, volatility=0.25, direction="BUY")
# 100 × 500 × 0.002 = 100.0
assert cost == pytest.approx(100.0)
class TestSquareRootSlippage:
"""方根市场冲击模型。"""
def test_zero_size(self) -> None:
"""size=0 时无冲击。"""
model = SquareRootSlippage(impact_coeff=0.1)
cost = model.compute(price=10.0, size=0, volume=10000, volatility=0.3, direction="BUY")
assert cost == 0.0
def test_small_participation_rate(self) -> None:
"""低参与率(小单),冲击成本低。"""
model = SquareRootSlippage(impact_coeff=0.1)
# size=100, volume=1000000, participation_rate=0.0001
cost = model.compute(
price=10.0, size=100, volume=1_000_000, volatility=0.3, direction="BUY"
)
# σ=0.3, √(0.0001)=0.01, impact = 0.3 × 0.01 × 10 × 100 × 0.1 = 0.3
assert cost == pytest.approx(0.3)
def test_high_participation_rate(self) -> None:
"""高参与率(大单),冲击成本高。"""
model = SquareRootSlippage(impact_coeff=0.1)
cost = model.compute(
price=10.0, size=100_000, volume=200_000, volatility=0.3, direction="BUY"
)
small_cost = model.compute(
price=10.0, size=100, volume=1_000_000, volatility=0.3, direction="BUY"
)
assert cost > small_cost * 10
def test_zero_volume_fallback(self) -> None:
"""volume=0 时退化为 PercentSlippage(rate=0.001)。"""
model = SquareRootSlippage(impact_coeff=0.1)
cost = model.compute(price=10.0, size=100, volume=0, volatility=0.3, direction="BUY")
assert cost == pytest.approx(1.0)
def test_zero_volatility_fallback(self) -> None:
"""volatility=0 时退化为 PercentSlippage(rate=0.001)。"""
model = SquareRootSlippage(impact_coeff=0.1)
cost = model.compute(price=10.0, size=100, volume=10000, volatility=0.0, direction="BUY")
assert cost == pytest.approx(1.0)
class TestVolumeSlippage:
"""成交量比例滑点。"""
def test_zero_size(self) -> None:
model = VolumeSlippage(base_bps=10.0)
cost = model.compute(price=10.0, size=0, volume=10000, volatility=0.3, direction="BUY")
assert cost == 0.0
def test_basic(self) -> None:
model = VolumeSlippage(base_bps=10.0)
cost = model.compute(price=10.0, size=100, volume=10000, volatility=0.3, direction="BUY")
assert cost == pytest.approx(0.01)
def test_high_participation(self) -> None:
model = VolumeSlippage(base_bps=10.0)
cost_high = model.compute(
price=10.0, size=5000, volume=10000, volatility=0.3, direction="BUY"
)
cost_low = model.compute(
price=10.0, size=100, volume=10000, volatility=0.3, direction="BUY"
)
assert cost_high > cost_low
def test_zero_volume_fallback(self) -> None:
model = VolumeSlippage(base_bps=10.0)
cost = model.compute(price=10.0, size=100, volume=0, volatility=0.3, direction="BUY")
assert cost == pytest.approx(1.0)