78 lines
3.6 KiB
Python
78 lines
3.6 KiB
Python
"""card.judge() 纯逻辑单测(无需 DB、无需 pandas)。
|
||
|
||
覆盖:候选 / 关注(缺覆盖)/ 关注(缺确认)/ 仅展示(未启动)/ 仅展示(硬风险否决)
|
||
/ 缺失文案 / 评分陈旧 / 旋钮 / 卡内序 / 坏信号集合与 pool 同源。
|
||
|
||
跑法:python3 test_card.py 或 pytest test_card.py
|
||
"""
|
||
import card
|
||
|
||
BASE = dict(pointed=True, theme="散热器件", n_sources=2, pct0=4.1,
|
||
covered=True, upside=0.25, risk_name=False,
|
||
accum_state="明确吸筹·量在价先", accum_score=78, accum_age=3,
|
||
y_signal="WATCH", stale_snapshot=False)
|
||
|
||
|
||
def t(name, cond):
|
||
assert cond, name
|
||
print(" ok", name)
|
||
|
||
|
||
def main():
|
||
r = card.judge(dict(BASE))
|
||
t("三门槛全过且明确吸筹 -> 候选", r["verdict"] == "候选" and r["confirm"] and not r["risk"])
|
||
t("候选理由四条齐", len(r["reasons"]) == 4 and "已启动" in r["reasons"][1])
|
||
|
||
r = card.judge({**BASE, "covered": False, "upside": None})
|
||
t("只差覆盖 -> 关注", r["verdict"] == "关注" and "无券商覆盖" in r["missing"])
|
||
|
||
r = card.judge({**BASE, "accum_state": "潜在吸筹·低位企稳"})
|
||
t("门槛全过但潜在吸筹不算确认 -> 关注", r["verdict"] == "关注" and not r["confirm"])
|
||
|
||
r = card.judge({**BASE, "accum_state": ""})
|
||
t("无评分 -> 关注且缺失文案", r["verdict"] == "关注" and "无吸筹评分(未入池)" in r["missing"])
|
||
|
||
r = card.judge({**BASE, "pct0": 1.2})
|
||
t("未启动 -> 仅展示", r["verdict"] == "仅展示" and "started" in r["failed_gates"])
|
||
|
||
r = card.judge({**BASE, "pointed": False})
|
||
t("未被指向 -> 仅展示且标无传导", r["verdict"] == "仅展示" and "无传导" in r["missing"])
|
||
|
||
r = card.judge({**BASE, "y_signal": "sell"})
|
||
t("决策系统 SELL -> 硬风险否决为仅展示", r["verdict"] == "仅展示" and r["risk"])
|
||
|
||
r = card.judge({**BASE, "stale_snapshot": True})
|
||
t("快照日不符 -> 硬风险", r["verdict"] == "仅展示" and "传导快照日与计划日不符" in r["risk"])
|
||
|
||
r = card.judge({**BASE, "accum_state": "⚠️ 高位派发"})
|
||
t("高位派发 -> 硬风险", r["verdict"] == "仅展示" and "高位派发" in r["risk"])
|
||
|
||
r = card.judge({**BASE, "accum_age": 45})
|
||
t("评分陈旧 -> 不确认、关注、缺失文案", r["verdict"] == "关注" and not r["confirm"]
|
||
and any("陈旧 45" in m for m in r["missing"]))
|
||
|
||
r = card.judge({**BASE, "pct0": 2.5}, start_pct=2.0)
|
||
t("启动阈值旋钮生效", r["verdict"] == "候选")
|
||
r = card.judge({**BASE, "accum_age": 45}, accum_max_age=60)
|
||
t("评分日龄旋钮生效", r["verdict"] == "候选")
|
||
r = card.judge({**BASE, "upside": -0.05}, neg_tol=0.10)
|
||
t("负容忍线传入生效", r["verdict"] == "候选")
|
||
r = card.judge({**BASE, "upside": -0.05})
|
||
t("负容忍默认 0:轻微为负不过覆盖门槛 -> 关注", r["verdict"] == "关注")
|
||
|
||
r = card.judge({**BASE, "risk_name": True})
|
||
t("ST 族 -> 仅展示", r["verdict"] == "仅展示" and "clean_name" in r["failed_gates"])
|
||
|
||
rows = [{"verdict": "关注", "pct0": 9.0}, {"verdict": "候选", "pct0": 3.5},
|
||
{"verdict": "候选", "pct0": 7.2}, {"verdict": "仅展示", "pct0": None}]
|
||
rows.sort(key=card.sort_key)
|
||
t("卡内序:候选在前、同判决涨幅降序、缺行情垫底",
|
||
[x["pct0"] for x in rows] == [7.2, 3.5, 9.0, None])
|
||
|
||
t("坏信号集合口径", card.BAD_SIGNALS == {"SELL", "AVOID", "DROPPED"})
|
||
print("ALL OK — 候选卡判决 / 关注两种 / 硬风险三种 / 缺失文案 / 旋钮 / 卡内序 全部通过")
|
||
|
||
|
||
if __name__ == "__main__":
|
||
main()
|