mirror of
https://ghfast.top/https://github.com/aeroxw/easy-tdx.git
synced 2026-09-12 14:34:15 +08:00
feat(backtest): add SquareRootSlippage + VolumeSlippage
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user