# -*- coding: utf-8 -*- """ 第五批模块单测 (实机运行, 零外部依赖) ====================================== 运行: 在 tradingSystem 仓库根目录执行 python scripts/test_batch5_units.py 覆盖: signal_rules 两条信号流的解析 (含两条流置信度尺度不同这个坑) 与消化口径。 """ import json import os import sys import traceback sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) from app.core import signal_rules as sr # noqa: E402 RESULTS = [] def case(name): def deco(fn): RESULTS.append((name, fn)) return fn return deco PRM = {"sell_conf_min": 0.75, "auto_exit_conf": 0.85, "trim_ratio": 1 / 3} POS = {"ts_code": "600000.SH", "total_qty": 6000, "avail_qty": 6000, "price": 10.0} # ================================================================ 解析 @case("解析·盘中流 (db2): 扁平字段 + component_scores 内嵌 JSON, 置信度本就是 0~1") def _(): s = sr.parse_intraday({"ts_code": "600000.SH", "action": "buy", "confidence": "0.83", "component_scores": json.dumps({"minute_qrs": 2.4}), "suggested_price": "10.25"}, msg_id="1-1") assert s["source"] == sr.SRC_INTRADAY and s["ts_code"] == "600000.SH" assert s["action"] == "BUY" and abs(s["confidence"] - 0.83) < 1e-9 assert abs(s["minute_qrs"] - 2.4) < 1e-9 and s["suggested_price"] == 10.25 # component_scores 给成 dict 或坏 JSON 都不能炸 assert sr.parse_intraday({"component_scores": {"minute_qrs": 3}})["minute_qrs"] == 3.0 assert sr.parse_intraday({"component_scores": "{坏"})["minute_qrs"] == 0.0 @case("解析·风控流 (db3): 外层套 data JSON, 置信度是 0~100 要归一") def _(): inner = {"ts_code": "600000.SH", "action": "SELL", "confidence": 88, "dominant_signal": "破位", "llm_reason": "跌破关键支撑且量能背离"} s = sr.parse_risk_sell({"data": json.dumps(inner, ensure_ascii=False)}, msg_id="2-1") assert s["source"] == sr.SRC_RISK_SELL and s["action"] == "SELL" assert abs(s["confidence"] - 0.88) < 1e-9, s # 88 → 0.88, 两条流尺度不同 assert s["dominant_signal"] == "破位" and "支撑" in s["reason"] # 已经是 0~1 的也不会被再除一次 assert abs(sr.parse_risk_sell({"data": json.dumps({"ts_code": "x", "action": "SELL", "confidence": 0.9})})["confidence"] - 0.9) < 1e-9 # 坏 JSON → 明确标记, 不抛异常 bad = sr.parse_risk_sell({"data": "{不是JSON"}) assert bad["ts_code"] == "" and "解析失败" in bad["parse_error"] # ================================================================ 消化 @case("消化·高置信风控卖出 → 直接清仓 (减持不设确认门槛)") def _(): s = sr.parse_risk_sell({"data": json.dumps({"ts_code": "600000.SH", "action": "SELL", "confidence": 90, "llm_reason": "逻辑走坏"})}) d = sr.digest(s, POS, PRM) assert d["action"] == sr.ACT_EXIT and d["qty"] == 6000, d assert "清仓" in d["reason"] and "逻辑走坏" in d["reason"], d assert d["hard_numbers"]["confidence"] == 0.9 @case("消化·中等置信 → 落提议待确认 (按比例减)") def _(): s = sr.parse_risk_sell({"data": json.dumps({"ts_code": "600000.SH", "action": "SELL", "confidence": 80})}) d = sr.digest(s, POS, PRM) assert d["action"] == sr.ACT_PROPOSE and d["qty"] == 2000, d # 6000 的 1/3, 整百 assert "待确认" in d["reason"] @case("消化·低置信 / 未持有 / 缺代码 一律不动") def _(): low = sr.parse_risk_sell({"data": json.dumps({"ts_code": "600000.SH", "action": "SELL", "confidence": 60})}) assert sr.digest(low, POS, PRM)["action"] == sr.ACT_IGNORE hi = sr.parse_risk_sell({"data": json.dumps({"ts_code": "600000.SH", "action": "SELL", "confidence": 95})}) assert sr.digest(hi, {"total_qty": 0}, PRM)["action"] == sr.ACT_IGNORE assert sr.digest(hi, None, PRM)["action"] == sr.ACT_IGNORE assert sr.digest({"ts_code": "", "action": "SELL", "confidence": 0.95}, POS, PRM)["action"] == sr.ACT_IGNORE @case("消化·BUY/HOLD 信号只留痕不买 (买什么买多少由 PMS 自己决定)") def _(): b = sr.parse_intraday({"ts_code": "600000.SH", "action": "BUY", "confidence": "0.95"}) d = sr.digest(b, POS, PRM) assert d["action"] == sr.ACT_RECORD and d["qty"] == 0, d assert "不据此买入" in d["reason"], d h = sr.parse_intraday({"ts_code": "600000.SH", "action": "HOLD", "confidence": "0.99"}) assert sr.digest(h, POS, PRM)["action"] == sr.ACT_RECORD @case("消化·盘中流里的 SELL 也照样消化 (两条流同一套口径)") def _(): s = sr.parse_intraday({"ts_code": "600000.SH", "action": "SELL", "confidence": "0.92"}) d = sr.digest(s, POS, PRM) assert d["action"] == sr.ACT_EXIT and d["qty"] == 6000, d @case("去重键·同票同源同动作当日只算一次") def _(): s = {"source": sr.SRC_RISK_SELL, "ts_code": "600000.SH", "action": "SELL"} k1 = sr.dedup_key(s, 20260728) assert k1 == sr.dedup_key(dict(s), 20260728) assert k1 != sr.dedup_key(s, 20260729) assert k1 != sr.dedup_key({**s, "source": sr.SRC_INTRADAY}, 20260728) # ---------------------------------------------------------------- runner def main(): passed, failed = 0, 0 for name, fn in RESULTS: try: fn() print(f" PASS {name}") passed += 1 except Exception: print(f" FAIL {name}") traceback.print_exc() failed += 1 print("-" * 60) if failed: print(f"FAILED: {failed} / {passed + failed}") sys.exit(1) print(f"ALL PASS ({passed} cases)") if __name__ == "__main__": main()