Files
easy_tdx_max/tests/unit/test_factor_base.py
T
Justin Gu 5fc398255d fix(types): 修复 CI mypy strict + ruff format 失败
mypy (13 errors → 0):
- portfolio/optimizer: register_optimizer 返回类型改为 Callable 装饰器签名
  (原标注 type[WeightOptimizer] 导致 4 个子类 Too many arguments)
- factor/engine: _datetime_to_int 用 isinstance 收窄替代 object→int 强转
- factor/analysis: 删多余 type:ignore(改由 mypy override 统一处理 scipy)
- backtest/orders, execution: np.sqrt 表达式用 float() 包裹消除 no-any-return
- MyTT.pyi: MACD 签名删除错误的 LOW/HIGH 参数(与 MyTT.py 实际签名对齐)
- pyproject: 新增 scipy mypy override (ignore_missing_imports)

ruff format: 8 个 test 文件格式化

验证: 564 passed, mypy 192 文件零错误, ruff check/format 全绿
2026-06-13 21:21:33 +08:00

120 lines
3.0 KiB
Python

# tests/unit/test_factor_base.py
"""Test Factor base class and registry."""
from __future__ import annotations
import pandas as pd
import pytest
from easy_tdx.factor.base import (
FACTORY_REGISTRY,
Factor,
register_factor,
)
class _StubFactor(Factor):
"""测试用因子。"""
name = "test_stub"
category = "test"
description = "stub for testing"
inputs = ("close",)
def compute(self, df: pd.DataFrame) -> pd.Series:
return df["close"].pct_change(1)
class TestFactorABC:
def test_cannot_instantiate_abc(self):
with pytest.raises(TypeError):
Factor() # type: ignore[abstract]
def test_subclass_must_define_name(self):
class NoName(Factor):
category = "test"
description = "x"
inputs = ("close",)
def compute(self, df):
return df["close"]
with pytest.raises(TypeError):
NoName()
def test_subclass_must_define_category(self):
class NoCategory(Factor):
name = "x"
description = "x"
inputs = ("close",)
def compute(self, df):
return df["close"]
with pytest.raises(TypeError):
NoCategory()
def test_subclass_must_implement_compute(self):
class NoCompute(Factor):
name = "x"
category = "test"
description = "x"
inputs = ("close",)
with pytest.raises(TypeError):
NoCompute()
def test_concrete_subclass_works(self):
f = _StubFactor()
assert f.name == "test_stub"
assert f.category == "test"
assert f.inputs == ("close",)
class TestRegistry:
def test_register_factor_decorator(self):
@register_factor
class RegFactor(Factor):
name = "reg_test_factor"
category = "test"
description = "registered factor"
inputs = ("close",)
def compute(self, df):
return df["close"]
assert "reg_test_factor" in FACTORY_REGISTRY
assert FACTORY_REGISTRY["reg_test_factor"] is RegFactor
def test_duplicate_name_raises(self):
@register_factor
class Dup(Factor):
name = "dup_test_factor"
category = "test"
description = "dup"
inputs = ("close",)
def compute(self, df):
return df["close"]
with pytest.raises(ValueError, match="已注册"):
@register_factor
class Dup2(Factor):
name = "dup_test_factor"
category = "test"
description = "dup2"
inputs = ("close",)
def compute(self, df):
return df["close"]
class TestFactorCompute:
def test_compute_returns_series(self):
f = _StubFactor()
df = pd.DataFrame({"close": [10.0, 11.0, 10.5, 12.0]})
result = f.compute(df)
assert isinstance(result, pd.Series)
assert len(result) == 4