236 lines
9.6 KiB
Python
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())
|