mirror of
https://ghfast.top/https://github.com/aeroxw/easy-tdx.git
synced 2026-09-12 15:44:15 +08:00
feat(backtest): add SlippageModel ABC + FixedSlippage + PercentSlippage
This commit is contained in:
@@ -0,0 +1,69 @@
|
||||
"""可插拔滑点模型。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
|
||||
|
||||
class SlippageModel(ABC):
|
||||
"""滑点模型基类。
|
||||
|
||||
所有滑点模型必须实现 compute() 方法,返回总滑点成本(金额)。
|
||||
"""
|
||||
|
||||
@abstractmethod
|
||||
def compute(
|
||||
self,
|
||||
price: float,
|
||||
size: float,
|
||||
volume: float,
|
||||
volatility: float,
|
||||
direction: str,
|
||||
) -> float:
|
||||
"""计算滑点成本。
|
||||
|
||||
Args:
|
||||
price: 成交价格
|
||||
size: 订单数量(股)
|
||||
volume: 当日成交量(股),0 表示无数据
|
||||
volatility: 近期年化波动率,0 表示无数据
|
||||
direction: 交易方向 BUY / SELL
|
||||
|
||||
Returns:
|
||||
总滑点成本(金额,非比率)
|
||||
"""
|
||||
...
|
||||
|
||||
|
||||
class FixedSlippage(SlippageModel):
|
||||
"""固定每股滑点(向后兼容)。"""
|
||||
|
||||
def __init__(self, per_share: float = 0.01) -> None:
|
||||
self._per_share = per_share
|
||||
|
||||
def compute(
|
||||
self,
|
||||
price: float,
|
||||
size: float,
|
||||
volume: float,
|
||||
volatility: float,
|
||||
direction: str,
|
||||
) -> float:
|
||||
return size * self._per_share
|
||||
|
||||
|
||||
class PercentSlippage(SlippageModel):
|
||||
"""按成交金额百分比滑点。"""
|
||||
|
||||
def __init__(self, rate: float = 0.001) -> None:
|
||||
self._rate = rate
|
||||
|
||||
def compute(
|
||||
self,
|
||||
price: float,
|
||||
size: float,
|
||||
volume: float,
|
||||
volatility: float,
|
||||
direction: str,
|
||||
) -> float:
|
||||
return price * size * self._rate
|
||||
@@ -0,0 +1,87 @@
|
||||
"""滑点模型单元测试。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from easy_tdx.backtest.slippage import (
|
||||
FixedSlippage,
|
||||
PercentSlippage,
|
||||
SlippageModel,
|
||||
)
|
||||
|
||||
|
||||
class TestSlippageBase:
|
||||
"""基类验证。"""
|
||||
|
||||
def test_cannot_instantiate_abc(self) -> None:
|
||||
"""不能直接实例化 ABC。"""
|
||||
with pytest.raises(TypeError):
|
||||
SlippageModel() # type: ignore[abstract]
|
||||
|
||||
def test_subclass_must_implement_compute(self) -> None:
|
||||
"""子类必须实现 compute。"""
|
||||
|
||||
class BadModel(SlippageModel):
|
||||
pass
|
||||
|
||||
with pytest.raises(TypeError):
|
||||
BadModel() # type: ignore[abstract]
|
||||
|
||||
|
||||
class TestFixedSlippage:
|
||||
"""固定每股滑点。"""
|
||||
|
||||
def test_zero_per_share(self) -> None:
|
||||
"""per_share=0 时无滑点。"""
|
||||
model = FixedSlippage(per_share=0.0)
|
||||
cost = model.compute(price=10.0, size=100, volume=10000, volatility=0.3, direction="BUY")
|
||||
assert cost == 0.0
|
||||
|
||||
def test_basic(self) -> None:
|
||||
"""基本计算:100 股 × 0.01 元/股 = 1.0。"""
|
||||
model = FixedSlippage(per_share=0.01)
|
||||
cost = model.compute(price=10.0, size=100, volume=10000, volatility=0.3, direction="BUY")
|
||||
assert cost == pytest.approx(1.0)
|
||||
|
||||
def test_large_size(self) -> None:
|
||||
"""大单。"""
|
||||
model = FixedSlippage(per_share=0.05)
|
||||
cost = model.compute(
|
||||
price=50.0, size=10000, volume=500000, volatility=0.2, direction="SELL"
|
||||
)
|
||||
assert cost == pytest.approx(500.0)
|
||||
|
||||
def test_direction_irrelevant(self) -> None:
|
||||
"""方向不影响固定滑点。"""
|
||||
model = FixedSlippage(per_share=0.01)
|
||||
buy_cost = model.compute(
|
||||
price=10.0, size=100, volume=10000, volatility=0.3, direction="BUY"
|
||||
)
|
||||
sell_cost = model.compute(
|
||||
price=10.0, size=100, volume=10000, volatility=0.3, direction="SELL"
|
||||
)
|
||||
assert buy_cost == sell_cost
|
||||
|
||||
|
||||
class TestPercentSlippage:
|
||||
"""按成交金额百分比滑点。"""
|
||||
|
||||
def test_zero_rate(self) -> None:
|
||||
"""rate=0 时无滑点。"""
|
||||
model = PercentSlippage(rate=0.0)
|
||||
cost = model.compute(price=10.0, size=100, volume=10000, volatility=0.3, direction="BUY")
|
||||
assert cost == 0.0
|
||||
|
||||
def test_basic(self) -> None:
|
||||
"""10元 × 100股 × 0.001 = 1.0。"""
|
||||
model = PercentSlippage(rate=0.001)
|
||||
cost = model.compute(price=10.0, size=100, volume=10000, volatility=0.3, direction="BUY")
|
||||
assert cost == pytest.approx(1.0)
|
||||
|
||||
def test_high_price(self) -> None:
|
||||
"""高价股。"""
|
||||
model = PercentSlippage(rate=0.002)
|
||||
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)
|
||||
Reference in New Issue
Block a user