feat(selection): M6.2 条件选股(method=condition + 财务可见性防护)

- quant/selection.run_condition_selection:结构化条件 AND 求值 —— 字段域
  static.*(行业/市场…)、技术列与派生量(close/volume/ma20/ma60)、已注册因子
  (momentum_60 等)、fundamental.*(announce_date<=as_of 的最新已公告财务值);
  条件支持 value 字面量与 ref 字段比较(如 close > ma60);结果带 filter_status/reason
- SelectionQuery 校验调整:condition 模式为纯过滤(不再强制 top_n/top_pct)
- FinancialRepository 新增 list_announced_many(批量防未来函数读取)+ SQLAlchemy 实现;
  SelectionService 注入 financial_repo 并按 announce_date 取每股最新一版
- tests/test_selection_condition.py:8 例(行业 in/ne、动量>0、close>ma60 ref、阈值、
  ROE 过滤且未来公告不可见、缺财务 repo 报错、更早 as_of 排除);全量 pytest 通过
This commit is contained in:
Simon
2026-09-09 00:16:45 +08:00
parent 25a1d9531a
commit 75c5472c31
6 changed files with 530 additions and 13 deletions
@@ -280,6 +280,28 @@ class SqlAlchemyFinancialRepository:
rows = self._session.scalars(stmt).all()
return [FinancialIndicator.model_validate(r, from_attributes=True) for r in rows]
def list_announced_many(
self,
symbols: Sequence[str],
as_of_date: date,
) -> list[FinancialIndicator]:
"""批量:这些股票 announce_date <= as_of_date 的全部记录(防未来函数)。"""
if not symbols:
return []
rows = self._session.scalars(
select(FinancialIndicatorModel)
.where(
FinancialIndicatorModel.symbol.in_(list(symbols)),
FinancialIndicatorModel.announce_date <= as_of_date,
)
.order_by(
FinancialIndicatorModel.symbol,
FinancialIndicatorModel.announce_date,
FinancialIndicatorModel.report_date,
)
).all()
return [FinancialIndicator.model_validate(r, from_attributes=True) for r in rows]
class SqlAlchemySyncLogRepository:
def __init__(self, session: Session) -> None: