mirror of
https://ghfast.top/https://github.com/aeroxw/easy-tdx.git
synced 2026-09-12 23:54:17 +08:00
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 全绿
120 lines
3.0 KiB
Python
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
|