Files
qlib/backend/tests/test_domain_market.py
T
Simon 2da234220a feat(backend): Phase 1 数据层 — Domain / Provider / Failover 审计 + 持久化 + 同步 CLI
- domain:市场数据实体(Stock / 交易日历 / 日线 / 复权 / 财务含 announce_date)+ Repository 与 MarketDataProvider Protocol
- 数据源:TushareProvider(归一化、重试、鉴权错误归类)、SinaProvider(备用,明确前复权口径与能力边界)、FailoverProvider + SyncLog 审计(禁止静默切换)
- 持久化:SQLAlchemy 2.x Models + Repository 实现(按业务键幂等 upsert、as_of_date 防未来函数过滤)+ Alembic 迁移
- CLI:uv run python -m app.cli.sync {basic|calendar|daily|financial|verify},支持 --resume 断点续传
- 真实 Tushare 验证:stock 5556 / 交易日历 366 / daily+factor 242 / 财务 55;sync_log 审计完整
- 测试:38 passed(domain / provider / failover / repository / 未来函数 / 迁移),ruff clean
2026-09-06 16:59:28 +08:00

65 lines
2.0 KiB
Python

"""领域实体测试:字段约束与「防未来函数」可见性判断。"""
from __future__ import annotations
from datetime import date
from decimal import Decimal
import pytest
from app.domain.entities.market import (
DailyBar,
FinancialIndicator,
Stock,
SyncLog,
)
from pydantic import ValidationError
class TestStock:
def test_symbol_pattern_enforced(self) -> None:
Stock(symbol="600519.SH", name="贵州茅台", list_date=date(2001, 8, 27))
with pytest.raises(ValidationError):
Stock(symbol="600519", name="x", list_date=date(2001, 1, 1))
with pytest.raises(ValidationError):
Stock(symbol="sh600519", name="x", list_date=date(2001, 1, 1))
class TestDailyBar:
def test_is_complete(self) -> None:
bar = DailyBar(
symbol="600519.SH",
trade_date=date(2024, 1, 2),
open=Decimal("100"),
high=Decimal("101"),
low=Decimal("99"),
close=Decimal("100.5"),
volume=Decimal("10000"),
amount=Decimal("1000000"),
)
assert bar.is_complete
assert not DailyBar(symbol="600519.SH", trade_date=date(2024, 1, 2)).is_complete
class TestFinancialIndicator:
def _fin(self, announce: date) -> FinancialIndicator:
return FinancialIndicator(
symbol="600519.SH",
report_date=date(2024, 6, 30),
announce_date=announce,
eps=Decimal("1.2"),
)
def test_announced_by_after_announce(self) -> None:
fin = self._fin(date(2024, 8, 31))
# 公告日当天已可见;公告前不可见
assert fin.announced_by(date(2024, 8, 31))
assert not fin.announced_by(date(2024, 8, 30))
assert not fin.announced_by(date(2024, 6, 30)) # 报告期不代表公开
class TestSyncLog:
def test_defaults(self) -> None:
log = SyncLog(source="tushare", api="daily", success=True, row_count=5)
assert log.request_time is not None
assert log.data_start is None