feat(universe): B1-1 指数历史成分(index_weight)+ Universe 按 as_of 成分过滤

- index_weight 表(migration f5e0d1c2b3a4,MySQL 已应用;index_code+date+symbol 唯一)
  + IndexWeight 实体 + IndexConstituentRepository(members_at:取 <=as_of 最近一期快照,
  Survivorship-free / 无未来成分;latest_date)
- UniverseSpec.index_code + universe.filter_stocks members 交集 + resolve_members;
  Research/Selection/Signal/Replay 服务注入 index repo(历史成分过滤,选股/回测共用)
- tests/test_index_universe.py:快照历史成分(成分变更不入早期结果)、幂等、
  空快照期空集、index_code 过滤下 as_of 一致性;全量 pytest 通过
This commit is contained in:
Simon
2026-09-09 07:27:13 +08:00
parent 03fb463216
commit 9cc4bfccac
13 changed files with 379 additions and 16 deletions
+16 -5
View File
@@ -16,6 +16,7 @@ from app.application.services.selection_service import SelectionService
from app.application.services.signal_service import SignalService
from app.domain.repositories.composite import CompositeRepository
from app.domain.repositories.factor import FactorRepository
from app.domain.repositories.index import IndexConstituentRepository
from app.domain.repositories.jobs import ExperimentRepository, JobRepository
from app.domain.repositories.market import (
AdjustFactorRepository,
@@ -32,6 +33,9 @@ from app.infrastructure.persistence.sqlalchemy.repositories.composite_impl impor
from app.infrastructure.persistence.sqlalchemy.repositories.factor_impl import (
SqlAlchemyFactorRepository,
)
from app.infrastructure.persistence.sqlalchemy.repositories.index_impl import (
SqlAlchemyIndexConstituentRepository,
)
from app.infrastructure.persistence.sqlalchemy.repositories.market_impl import (
SqlAlchemyAdjustFactorRepository,
SqlAlchemyDailyBarRepository,
@@ -78,6 +82,10 @@ def _chart_service_factory(
return ChartService(stock_repo, daily_repo, adj_repo)
def _index_repo_factory(session: DbSession) -> IndexConstituentRepository:
return SqlAlchemyIndexConstituentRepository(session)
def _engine_factory() -> QuantEngine:
return LocalEngine()
@@ -86,31 +94,34 @@ def _service_factory(
stock_repo: Annotated[StockRepository, Depends(_stock_repo_factory)],
daily_repo: Annotated[DailyBarRepository, Depends(_daily_repo_factory)],
engine: Annotated[QuantEngine, Depends(_engine_factory)],
index_repo: Annotated[IndexConstituentRepository, Depends(_index_repo_factory)],
) -> ResearchService:
return ResearchService(stock_repo, daily_repo, engine)
return ResearchService(stock_repo, daily_repo, engine, index_repo)
def _replay_service_factory(
stock_repo: Annotated[StockRepository, Depends(_stock_repo_factory)],
daily_repo: Annotated[DailyBarRepository, Depends(_daily_repo_factory)],
index_repo: Annotated[IndexConstituentRepository, Depends(_index_repo_factory)],
) -> ReplayService:
return ReplayService(stock_repo, daily_repo)
return ReplayService(stock_repo, daily_repo, index_repo)
def _signal_service_factory(
stock_repo: Annotated[StockRepository, Depends(_stock_repo_factory)],
daily_repo: Annotated[DailyBarRepository, Depends(_daily_repo_factory)],
index_repo: Annotated[IndexConstituentRepository, Depends(_index_repo_factory)],
) -> SignalService:
return SignalService(stock_repo, daily_repo)
return SignalService(stock_repo, daily_repo, index_repo)
def _selection_service_factory(
stock_repo: Annotated[StockRepository, Depends(_stock_repo_factory)],
daily_repo: Annotated[DailyBarRepository, Depends(_daily_repo_factory)],
financial_repo: Annotated[FinancialRepository, Depends(_financial_repo_factory)],
index_repo: Annotated[IndexConstituentRepository, Depends(_index_repo_factory)],
) -> SelectionService:
return SelectionService(stock_repo, daily_repo, financial_repo)
return SelectionService(stock_repo, daily_repo, financial_repo, index_repo)
def _selection_repo_factory(session: DbSession) -> SelectionRepository: