akg-factor-bridge/test_plan_logic_state.py

93 lines
4.6 KiB
Python
Raw Normal View History

逻辑状态四态接进计划装配,结论随每张卡发给下游 此前四态只是一个纯函数模块,结论躺在库里没人读。这次接上: 计划装配时每只票算一次四态,三路输入分别是研报论断(已有)、产业研判(读行业观点 快照,按环节名对上今日被指向的环节)、券商行动(同财年同预测期的每股收益预测中位数 与覆盖机构数,两个等长窗口比较)。公司事件那一路无数据源,恒出缺失,但照样记名—— 缺失要让人看得见系统缺的是什么,不能让人以为系统判过了。 两处取数搬进公用的地方,免得读数脚本和计划各写一套:行业观点快照的当日读取进 judgement.py,券商行动进 sources.py。读数脚本改调它们,自己那两份删掉。 修了一处日期口径错误:行业观点快照按计划日落库,行情与论断按数据日取,两者在生产里 差一天。原先用同一个日期取三样东西,结果是快照首日读到空。 另修一处:读数脚本原先只读含已启动成员的窄传导视图,而候选卡认的是完整那张, 算出来的产业研判覆盖比卡上真实看到的低。两边现在同一口径。 接口每行新增逻辑状态:状态、子因、每路的来龙去脉与截止日。不发权重、不发判决改动—— 四态怎么作用于建仓通道是 PMS 那边的事,这里只提供状态与出处。内部中间量不外泄。 测试十七例,重点钉住四态不改判决:四个状态乘三个判决十二种组合逐个扫过,判决一次 都没被动过。这是收敛规则单调性的守卫。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-04 11:46:01 +08:00
"""计划装配接入逻辑状态四态的离线单测(不连库)。
钉住三件事
四态随每张卡一起产出并按约定的形状发给下游每路带截止日与一句话说明
四态**不改判决**候选卡的三门槛判决与逻辑四态是正交的两维收敛规则是单调的
逻辑强化只能提前卡内序永远不能把判决往上升一档逻辑存疑只能改分流通道
不能把仅展示变成可执行
取数任一路读不到都不让计划断产那一路记缺失
跑法python3 test_plan_logic_state.py pytest test_plan_logic_state.py
"""
import logic_state as ls
import plan
DS = "2026-09-03"
def t(name, cond):
assert cond, name
print(" ok", name)
def claim(date, direction="利好"):
return {"disclosure_date": date, "direction": direction, "mechanism": "机制",
"doc_title": "研报", "claim_id": "c" + date.replace("-", "")}
def jrow(leaning="偏多", verified=1, migrated=0, stale=3, seg="固态电解质"):
return {"leaning": leaning, "leaning_prev": None, "migrated": migrated,
"stale_days": stale, "verified": verified, "review_date": "2026-09-03",
"n_materials": 43, "subject_name": seg, "segment_name": seg,
"n_bull": 3, "n_bear": 3}
def main():
print("四态随卡产出")
evd = {"logic": [claim("2026-08-25")]}
seg_of = {"SH600000": ["固态电解质"]}
seg_view = {"固态电解质": jrow()}
broker = {"SH600000": ls.signal(ls.PATH_BROKER, ls.SIG_FLAT, as_of=DS,
coverage=6, why="预测中位数变化 +1%,在阈值之内")}
st = plan._logic_state_of("SH600000", evd, seg_of, seg_view, broker, DS) # noqa: SLF001
t("三路都在且无负面 -> 逻辑成立", st["state"] == ls.STATE_HOLD)
t("四路都记了名,缺的那一路写明是公司事件",
len(st["paths"]) == 4 and ls.PATH_EVENT in st["missing"])
print("取数缺席时不断产")
st = plan._logic_state_of("SH600000", {}, {}, {}, {}, DS) # noqa: SLF001
t("四路全缺 -> 无法判断加证据不足,不抛异常",
st["state"] == ls.STATE_UNKNOWN and st["why"] == ls.WHY_THIN)
t("缺失名单写全了四路", len(st["missing"]) == 4)
st = plan._logic_state_of("SH999999", evd, seg_of, seg_view, broker, DS) # noqa: SLF001
t("这只票不在任何被指向环节上 -> 产业研判缺席,其余照算",
ls.PATH_JUDGE in st["missing"] and ls.PATH_CLAIM in st["usable"])
print("一票挂多个环节")
st = plan._logic_state_of( # noqa: SLF001
"SH600000", evd, {"SH600000": ["没评过的环节", "固态电解质"]}, seg_view, broker, DS)
t("取第一个有行业观点的那个环节", ls.PATH_JUDGE in st["usable"])
print("发给下游的形状")
out = plan._state_out(st) # noqa: SLF001
t("状态、子因、截止日都在", set(out) >= {"state", "why", "as_of", "usable", "missing",
"reasons", "paths"})
t("每一路都带自己的截止日与一句话说明",
all(set(p) == {"path", "signal", "as_of", "why"} for p in out["paths"]))
t("不发内部中间量(硬触发标记、出处原值不外泄)",
all("hard" not in p and "refs" not in p for p in out["paths"]))
t("没有状态时发 None不硬拼一个空壳", plan._state_out(None) is None) # noqa: SLF001
print("四态不改判决(收敛规则单调)")
for state in (ls.STATE_STRONG, ls.STATE_HOLD, ls.STATE_UNKNOWN, ls.STATE_DOUBT):
for verdict in ("候选", "关注", "仅展示"):
r = ls.apply_to_card(verdict, state)
assert r["verdict"] == verdict, (verdict, state, r)
t("十二种组合逐个扫过,判决一次都没被四态改动", True)
t("逻辑强化最多提前卡内序", ls.apply_to_card("关注", ls.STATE_STRONG)["rank_bonus"] == 1)
t("逻辑存疑不把仅展示变成可执行",
not ls.apply_to_card("仅展示", ls.STATE_DOUBT)["force_confirm"])
t("候选加逻辑存疑是唯一需要新语义的一格:强制人工确认",
ls.apply_to_card("候选", ls.STATE_DOUBT)["force_confirm"])
print("计划日与数据日差一天")
t("次日推算正确", plan._next_day("2026-09-03") == "2026-09-04") # noqa: SLF001
t("认不出的日期原样返回,不抛异常", plan._next_day("不是日期") == "不是日期") # noqa: SLF001
print("ALL OK — 四态随卡产出 / 缺席不断产 / 下游形状 / 判决不被改动 / 计划日推算 全部通过")
if __name__ == "__main__":
main()