tradingSystem/scripts/test_batch5_units.py

142 lines
5.9 KiB
Python
Raw Normal View History

# -*- 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()