tradingSystem/scripts/test_batch32_units.py

236 lines
9.6 KiB
Python

# -*- coding: utf-8 -*-
"""盘中确认包 (2026-09-14 方案第三节, 台账 012)。全部离线, 不连库。
给技术面这一票加盘中确认, 只用于入场。时段与量比与解锁重问同源 (台账 003)。
A intraday_confirm 纯函数: 相位无关原样/收口突破要价与量同时满足/突破开关关不升/取不到按等待/
开口确认要均价与量/时段外开口进观察。
B 同源哨兵与映射带上轨: in_window 是 reask_rules 那一个; synthesize base 与 _compact 带 boll_upper。
C 接线 (proposal_service._apply_intraday): 突破改立场重合议为放行、硬数字带 intraday;
开口未确认路由改观察带 wait_confirm; 总闸关掉 _intraday_cfg 为 None (逐字回旧)。
E 参数登记与范围。
"""
import os
import sys
import traceback
from datetime import datetime
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
from app.core import tech_rules as tr # noqa: E402
from app.core import reask_rules as rr # noqa: E402
from app.services import param_store as ps # noqa: E402
RESULTS = []
def case(name):
def deco(fn):
RESULTS.append((name, fn))
return fn
return deco
def _st(phase, **kw):
"""一个技术面立场块 (synthesize 的紧凑态)。"""
d = {"stance": "中性", "strength": None, "phase": phase, "sar_side": "",
"boll_upper": 10.0, "reason": "x"}
d.update(kw)
return d
NOW = datetime(2026, 9, 15, 10, 0) # 在 0945-1430 时段内
# ================================================================ A intraday_confirm 纯函数
@case("A 相位无关 (趋势多) → 原样返回, kind 空, 不改")
def _():
r = tr.intraday_confirm(_st("趋势多"), price=11, vwap=10, upper_prev=10,
vol_ratio=3, now=NOW)
assert r["kind"] is None and r["changed"] is False
@case("A 收口等待·价站上昨日上轨且量足 → breakout (看多/强/盘中开口向上)")
def _():
r = tr.intraday_confirm(_st("收口等待"), price=10.5, vwap=10.0, upper_prev=10.2,
vol_ratio=2.0, now=NOW, vol_min=1.5)
assert r["kind"] == "breakout" and r["changed"] is True
assert r["state"]["stance"] == "看多" and r["state"]["strength"] == ""
assert r["state"]["phase"] == "盘中开口向上"
@case("A 收口等待·价没过上轨 → 不升 (原样, kind 空)")
def _():
r = tr.intraday_confirm(_st("收口等待"), price=10.1, vwap=10.0, upper_prev=10.2,
vol_ratio=2.0, now=NOW, vol_min=1.5)
assert r["kind"] is None and r["changed"] is False
@case("A 收口等待·量不足 → 不升")
def _():
r = tr.intraday_confirm(_st("收口等待"), price=10.5, vwap=10.0, upper_prev=10.2,
vol_ratio=1.2, now=NOW, vol_min=1.5)
assert r["kind"] is None
@case("A 收口等待·突破开关关掉 → 不升 (breakout_on 假)")
def _():
r = tr.intraday_confirm(_st("收口等待"), price=10.5, vwap=10.0, upper_prev=10.2,
vol_ratio=2.0, now=NOW, vol_min=1.5, breakout_on=False)
assert r["kind"] is None and r["changed"] is False
@case("A 收口等待·昨日上轨取不到 → 不升 (取不到不放行)")
def _():
r = tr.intraday_confirm(_st("收口等待"), price=10.5, vwap=10.0, upper_prev=None,
vol_ratio=2.0, now=NOW, vol_min=1.5)
assert r["kind"] is None
@case("A 开口向上·价≥均价且量足 → opened_ok, 块不变")
def _():
r = tr.intraday_confirm(_st("开口向上", stance="看多"), price=10.5, vwap=10.0,
upper_prev=10.2, vol_ratio=2.0, now=NOW, vol_min=1.5)
assert r["kind"] == "opened_ok" and r["changed"] is False
assert r["state"]["phase"] == "开口向上" and r["state"]["stance"] == "看多"
@case("A 开口向上·价低于均价 → opened_wait")
def _():
r = tr.intraday_confirm(_st("开口向上", stance="看多"), price=9.8, vwap=10.0,
upper_prev=10.2, vol_ratio=2.0, now=NOW, vol_min=1.5)
assert r["kind"] == "opened_wait" and r["changed"] is False
@case("A 开口向上·量不足 → opened_wait")
def _():
r = tr.intraday_confirm(_st("开口向上", stance="看多"), price=10.5, vwap=10.0,
upper_prev=10.2, vol_ratio=1.0, now=NOW, vol_min=1.5)
assert r["kind"] == "opened_wait"
@case("A 开口向上·读数取不到 (现价 None) → opened_wait (取不到按等待)")
def _():
r = tr.intraday_confirm(_st("开口向上", stance="看多"), price=None, vwap=10.0,
upper_prev=10.2, vol_ratio=2.0, now=NOW, vol_min=1.5)
assert r["kind"] == "opened_wait"
@case("A 时段外·开口向上 → outside_window (进观察)")
def _():
early = datetime(2026, 9, 15, 9, 40) # 早于 09:45
r = tr.intraday_confirm(_st("开口向上", stance="看多"), price=10.5, vwap=10.0,
upper_prev=10.2, vol_ratio=2.0, now=early, window="0945-1430")
assert r["kind"] == "outside_window" and r["changed"] is False
@case("A 时段外·收口等待 → 原样 (kind 空, 不动)")
def _():
early = datetime(2026, 9, 15, 9, 40)
r = tr.intraday_confirm(_st("收口等待"), price=10.5, vwap=10.0, upper_prev=10.2,
vol_ratio=2.0, now=early, window="0945-1430")
assert r["kind"] is None and r["changed"] is False
# ================================================================ B 同源哨兵与映射带上轨
@case("B 同源哨兵·tech_rules 的 in_window 就是 reask_rules 那一个函数对象")
def _():
assert tr.in_window is rr.in_window
@case("B 映射带上轨·synthesize base 与 _compact 都带 boll_upper")
def _():
from app.services import tech_service as ts
rows = [{"data_date": 20260914, "reanchored": 1, "quality": "OK", "sar_side": "",
"boll_pos": 0.6, "boll_upper": 12.34, "boll_squeeze": 1, "sar_flip_days": 5,
"bbi_state": "多头区", "sar_value": 11.0, "boll_bw_pct": 3.0}]
st = tr.synthesize(rows)
assert st.get("boll_upper") == 12.34
assert "boll_upper" in ts._compact(st) and ts._compact(st)["boll_upper"] == 12.34
# ================================================================ C 接线 (proposal_service._apply_intraday)
def _cfg(**kw):
c = {"breakout_on": True, "vol_min": 1.5, "window": "0945-1430",
"cparams": {"fund_required": True, "consensus_route": True, "weak_confirm": True}}
c.update(kw)
return c
@case("C 突破·改立场重合议为放行, 硬数字 tech 带 intraday")
def _():
from app.services import proposal_service as pp
blocks = {"fund": {"stance": "看多"}, "timing": {"stance": "中性"},
"tech": {"phase": "收口等待", "boll_upper": 10.2, "stance": "中性"},
"consensus": {"route": "观察", "direction": "中性", "route_reason": "等技术面开口"}}
cache = {"600000.SH": (10.5, 10.0, 2.0)} # 价站上上轨、量足
kind = pp._apply_intraday("600000.SH", blocks, _cfg(), NOW, cache)
assert kind == "breakout"
assert blocks["tech"]["stance"] == "看多" and blocks["tech"]["phase"] == "盘中开口向上"
assert blocks["tech"]["intraday"]["kind"] == "breakout"
assert blocks["consensus"]["direction"] == "看多" and blocks["consensus"]["route"] == "放行"
@case("C 开口未确认·路由改观察带 wait_confirm, 硬数字带 intraday")
def _():
from app.services import proposal_service as pp
from app.core import action_engine as ae
blocks = {"fund": {"stance": "看多"}, "timing": {"stance": "看多"},
"tech": {"phase": "开口向上", "boll_upper": 10.2, "stance": "看多"},
"consensus": {"route": "放行", "direction": "看多"}}
cache = {"600000.SH": (9.5, 10.0, 2.0)} # 价低于均价 → 未确认
kind = pp._apply_intraday("600000.SH", blocks, _cfg(), NOW, cache)
assert kind == "opened_wait"
assert blocks["consensus"]["route"] == "观察"
assert blocks["consensus"]["disp"] == ae.DISP_WAIT_CONFIRM
assert blocks["tech"]["intraday"]["kind"] == "opened_wait"
@case("C 总闸关掉·_intraday_cfg 返回 None (逐字回旧)")
def _():
from app.services import proposal_service as pp
o = pp.param_store.get_bool
pp.param_store.get_bool = lambda k, d=False: (False if k == "PMS_TECH_INTRADAY_CONFIRM"
else o(k, d))
try:
assert pp._intraday_cfg() is None
finally:
pp.param_store.get_bool = o
# ================================================================ E 参数
@case("E 参数·盘中确认四键登记, 默认值对")
def _():
assert ps.RUNTIME_EXTRA["PMS_TECH_INTRADAY_CONFIRM"][0] is True
assert ps.RUNTIME_EXTRA["PMS_TECH_INTRADAY_BREAKOUT"][0] is True
assert abs(ps.RUNTIME_EXTRA["PMS_TECH_VOL_CONFIRM"][0] - 1.5) < 1e-9
assert ps.RUNTIME_EXTRA["PMS_TECH_INTRADAY_WINDOW"][0] == "0945-1430"
@case("E 参数·量比阈值范围校验 0.5 到 10")
def _():
assert ps._range_check("PMS_TECH_VOL_CONFIRM", 0.3) # 低于下限 → 报错串
assert ps._range_check("PMS_TECH_VOL_CONFIRM", 20) # 高于上限 → 报错串
assert ps._range_check("PMS_TECH_VOL_CONFIRM", 1.5) is None
def main():
ok = 0
for name, fn in RESULTS:
try:
fn()
ok += 1
print(" ok " + name)
except Exception:
print(" FAIL " + name)
traceback.print_exc()
print("-" * 60)
if ok == len(RESULTS):
print("ALL PASS (%d cases)" % ok)
return 0
print("FAILED %d/%d" % (len(RESULTS) - ok, len(RESULTS)))
return 1
if __name__ == "__main__":
sys.exit(main())