三源合议工作包二(三):advise_v2 基本面×技术面仓位矩阵

方案附录乙的仓位矩阵, 叠在现有 advise 的基本面维度上加技术面维度, 旧 advise 原样保留:
- 基本面看多×技术面看多/中性 → 标准仓 5/2.5/2.5; 估值贵或共识已定价 → 减半, 共识已定价还首批推迟
- 基本面看多/中性×技术面看空 → 观察(route), 已持仓只停增持
- 基本面中性×技术面看多/中性 → 标准仓 3/3.5/3.5
- 基本面看空×技术面看多 → 试探仓1%一批交人, 配紧止盈与SAR线(tight_trail)
- 基本面看空技术面非多 / 无读数 → 不建
- 保留既有: 逻辑存疑不建、赔率低降试探、高位兑现首批推迟
batch28 加矩阵八例, 现 37 例。扫描接入路由、增持门、页面合议行随后。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
zlt 2026-09-11 14:50:45 +08:00
parent be7884da46
commit 0dd5b68e11
3 changed files with 141 additions and 2 deletions

View File

@ -91,6 +91,85 @@ def advise(c: dict, params: dict) -> dict[str, Any]:
return adv return adv
def advise_v2(c: dict, params: dict, *, fund: dict, tech: dict) -> dict:
"""基本面 × 技术面仓位矩阵 (2026-09-11 方案附录乙, 台账 006-011)。旧 advise 原样保留, 这是
叠了技术面维度的新函数fund fund_rules 结果块 (stance/priced_in/valuation), tech 是技术面
立场块 (stance/phase)返回 adv, 多带 route (放行/观察/交人/跳过) tight_trail (弱基本面
试探仓要配紧止盈)保留既有调整: 逻辑存疑不建定价高位兑现首批推迟赔率低降试探仓;
研判把握度低那一档由 apply_judge 在落账时再降"""
fs = fund.get("stance")
ts = (tech or {}).get("stance") or "无读数"
base_pct = _f(params.get("stock_target_default"), 0.06)
ls = (c.get("logic_state") or {}).get("state") if isinstance(c.get("logic_state"), dict) else None
ps = (c.get("pricing_state") or {}).get("state") if isinstance(c.get("pricing_state"), dict) else None
odds = odds_of(c.get("valuation"))
adv: dict[str, Any] = {"tier": TIER_STD, "factor": 1.0, "splits": DEFAULT_SPLIT, "conditions": [],
"why": [], "hold_type": "待定", "delay_first": False, "route": "放行",
"tight_trail": False, "fund_stance": fs, "tech_stance": ts,
"priced_in": bool(fund.get("priced_in")),
"mechanical": {"target_pct": base_pct, "splits": DEFAULT_SPLIT}, "version": "2"}
def finish(why_extra=None):
if why_extra:
adv["why"].append(why_extra)
adv["target_pct"] = round(base_pct * adv["factor"], 4)
adv["text"] = text(adv)
return adv
# 既有硬拦: 逻辑存疑 → 不建
if ls == "逻辑存疑":
adv.update(tier=TIER_NONE, factor=0.0, splits=(), route="跳过")
return finish("逻辑存疑:判决与分流已拦,不建")
# 基本面无读数 → 不建 (没有买方评析)
if fs == "无读数":
adv.update(tier=TIER_NONE, factor=0.0, splits=(), route="跳过")
return finish("没有买方评析(质地无读数或超期)")
# 基本面看空
if fs == "看空":
if ts == "看多": # 试探仓一批, 交人, 配紧止盈与 SAR 线
adv.update(tier=TIER_TRIAL, factor=1.0 / 6.0, splits=(1.0,), route="交人",
tight_trail=True, hold_type="试探仓")
adv["conditions"] += ["基本面看空、技术面看多:只给试探仓一批,人工确认后下",
"配回撤 3%、硬目标 8% 的紧止盈与 SAR 线"]
adv["target_pct"] = 0.01
adv["why"].append("基本面看空但技术面看多")
adv["text"] = text(adv)
return adv
adv.update(tier=TIER_NONE, factor=0.0, splits=(), route="跳过")
return finish("基本面看空、技术面未看多")
# 基本面看多 / 中性 × 技术面看空 → 观察 (等技术面转向; 已持仓只停增持)
if ts == "看空":
adv.update(route="观察", hold_type="观察")
adv["conditions"].append("技术面看空:进观察,转向后再分批建;已持仓只停增持")
return finish("基本面偏多但技术面看空,等技术面转向")
# 基本面看多 / 中性 × 技术面看多 / 中性 → 按矩阵定档
if fs == "看多":
adv.update(splits=(0.5, 0.25, 0.25), hold_type="长期持有类")
adv["conditions"].append("第二批要求技术面不看空")
if "" in str(fund.get("valuation") or "") or fund.get("priced_in"):
adv.update(tier=TIER_HALF, factor=0.5, splits=(0.5, 0.5))
if fund.get("priced_in"):
adv["delay_first"] = True
adv["conditions"].append("共识已定价:减半仓且首批推迟")
else:
adv["conditions"].append("估值贵:减半仓")
adv["why"].append(f"基本面看多、技术面{ts}")
else: # 基本面中性
adv.update(splits=(0.3, 0.35, 0.35), hold_type="事件驱动类")
adv["conditions"].append("后两批要求技术面看多且非高位兑现")
adv["why"].append(f"基本面中性、技术面{ts}")
# 既有叠加: 赔率低 → 试探; 高位兑现 → 首批推迟
if odds is not None and odds < 1.0:
adv.update(tier=TIER_TRIAL, factor=1.0 / 6.0, splits=(1.0,))
adv["conditions"].append("只在人工确认后下")
adv["why"].append(f"赔率 {odds:.2f} 低于 1")
if ps == "高位兑现":
adv["delay_first"] = True
adv["conditions"].append("定价状态高位兑现:首批推迟")
return finish()
def apply_judge(adv: Optional[dict], judge: Optional[dict], conf_min: int = 60) -> Optional[dict]: def apply_judge(adv: Optional[dict], judge: Optional[dict], conf_min: int = 60) -> Optional[dict]:
"""提议落账前补研判一档: 把握度低于 conf_min 或结论不可用 → 试探仓, 只在人工确认后下。 """提议落账前补研判一档: 把握度低于 conf_min 或结论不可用 → 试探仓, 只在人工确认后下。

View File

@ -74,14 +74,15 @@
取数解析与代码归一/分页拼接与状态非 OK 不落表/映射只覆盖有当天读数的相关票/ 取数解析与代码归一/分页拼接与状态非 OK 不落表/映射只覆盖有当天读数的相关票/
读数陈旧按无读数/参数登记与交易日差/upsert VALUES 与按 matched 分页两个真机哨兵 (29 ) 读数陈旧按无读数/参数登记与交易日差/upsert VALUES 与按 matched 分页两个真机哨兵 (29 )
test_batch28_units.py 三源合议·工作包二纯逻辑 (2026-09-11): 基本面质地档映射与共识已定价/择时定性 test_batch28_units.py 三源合议·工作包二纯逻辑 (2026-09-11): 基本面质地档映射与共识已定价/择时定性
映射与盘中转多升级/合议多数决与强弱/附录丙路由表逐条/装配服务凑齐四块与六键 (29 ) 映射与盘中转多升级/合议多数决与强弱/附录丙路由表逐条/装配服务凑齐四块与六键/
advise_v2 基本面×技术面仓位矩阵逐格 (37 )
test_page_enum_guard.py 页面文案守卫 (静态扫描, 不连库不起浏览器): 枚举字段不许 test_page_enum_guard.py 页面文案守卫 (静态扫描, 不连库不起浏览器): 枚举字段不许
直接印到页面上 / 判据码显示前必须剥前缀 / 不许把整个对象 直接印到页面上 / 判据码显示前必须剥前缀 / 不许把整个对象
打给交易员看 / 翻译兜底不许让英文码单独当句子 (1 ) 打给交易员看 / 翻译兜底不许让英文码单独当句子 (1 )
test_wiring.py 装配自检: 服务层核心落表 全链路 (内存桩) + test_wiring.py 装配自检: 服务层核心落表 全链路 (内存桩) +
目标价到价必定入队 (档位 full 也不自动卖) + 目标价到价必定入队 (档位 full 也不自动卖) +
用户设的止损价与目标价单独成列显示 (70 ) 用户设的止损价与目标价单独成列显示 (70 )
755 763
(总数按实跑逐批相加校正过两次: 曾写 649 是笔误, 实为 650; 09-03 先后加了同轮只发一条 (总数按实跑逐批相加校正过两次: 曾写 649 是笔误, 实为 650; 09-03 先后加了同轮只发一条
减持与研究理由两键各一例, 652; 09-04 加了仅展示跳过原因与空候选说明各一例, 654; 又加了页面文案守卫一例, 655; 09-07 审查修复加了跨轮减持等五例, 660; 第二件低把握驳回交人一例, 661; 第三件逻辑状态接入第二十二批十五例, 676; 第四件安全边际整句透传一例, 677; 参考目标价一例, 678; 催化事件与定价状态透传一例, 679; 两个期限的头一例, 现为 680; 09-10 建议档位对齐第二十六批十七例到 697; 09-11 技术面接入工作包一第二十七批二十七例到 724) 减持与研究理由两键各一例, 652; 09-04 加了仅展示跳过原因与空候选说明各一例, 654; 又加了页面文案守卫一例, 655; 09-07 审查修复加了跨轮减持等五例, 660; 第二件低把握驳回交人一例, 661; 第三件逻辑状态接入第二十二批十五例, 676; 第四件安全边际整句透传一例, 677; 参考目标价一例, 678; 催化事件与定价状态透传一例, 679; 两个期限的头一例, 现为 680; 09-10 建议档位对齐第二十六批十七例到 697; 09-11 技术面接入工作包一第二十七批二十七例到 724)
任一子集失败即整体失败 (退出码 1) 任一子集失败即整体失败 (退出码 1)

View File

@ -224,6 +224,65 @@ def _():
assert r["timing"]["stance"] == "无读数" assert r["timing"]["stance"] == "无读数"
# ================================================================ E advise_v2 仓位矩阵 (附录乙)
_MP = {"stock_target_default": 0.06, "batch_split": (0.5, 0.25, 0.25)}
def _av2(fs, ts, **kw):
from app.services import advice_service as a
c = {"valuation": kw.get("val"), "logic_state": ({"state": kw["ls"]} if kw.get("ls") else None),
"pricing_state": ({"state": kw["ps"]} if kw.get("ps") else None)}
fund = {"stance": fs, "priced_in": kw.get("priced", False), "valuation": kw.get("vlabel")}
return a.advise_v2(c, _MP, fund=fund, tech={"stance": ts, "phase": kw.get("phase")})
@case("E 矩阵: 基本面看多×技术面看多 → 标准仓 5/2.5/2.5 放行")
def _():
r = _av2("看多", "看多")
assert r["tier"] == "标准仓" and r["splits"] == (0.5, 0.25, 0.25) and r["route"] == "放行"
@case("E 矩阵: 基本面看多×技术面看空 → 观察")
def _():
assert _av2("看多", "看空")["route"] == "观察"
@case("E 矩阵: 基本面看多估值贵 → 减半仓 5/5")
def _():
r = _av2("看多", "看多", vlabel="")
assert r["tier"] == "减半仓" and r["splits"] == (0.5, 0.5)
@case("E 矩阵: 共识已定价 → 减半且首批推迟")
def _():
r = _av2("看多", "看多", priced=True)
assert r["tier"] == "减半仓" and r["delay_first"] is True
@case("E 矩阵: 基本面中性×技术面看多 → 标准仓 3/3.5/3.5")
def _():
r = _av2("中性", "看多")
assert r["tier"] == "标准仓" and r["splits"] == (0.3, 0.35, 0.35)
@case("E 矩阵: 基本面看空×技术面看多 → 试探仓1%交人配紧止盈")
def _():
r = _av2("看空", "看多")
assert (r["tier"] == "试探仓" and abs(r["target_pct"] - 0.01) < 1e-9
and r["route"] == "交人" and r["tight_trail"] is True)
@case("E 矩阵: 基本面看空技术面非多 / 无读数 → 不建跳过")
def _():
assert _av2("看空", "看空")["tier"] == "不建"
assert _av2("无读数", "看多")["route"] == "跳过"
@case("E 矩阵: 逻辑存疑 → 不建(既有硬拦保留)")
def _():
assert _av2("看多", "看多", ls="逻辑存疑")["tier"] == "不建"
def main(): def main():
ok = 0 ok = 0
for name, fn in RESULTS: for name, fn in RESULTS: