三源合议工作包二(一):基本面/择时/合议三个纯逻辑模块
方案第四节的三个纯逻辑判定,零外部依赖好单测: - fund_rules 基本面立场:质地好看多/质地中两好零差看多/差或硬疑点看空/缺或超120天无读数/ 十视角好≥8且估值贵打共识已定价标记 - timing_rules 择时立场:昨夜 BUY/WATCH/SELL 映射 + 盘中转多升级 - consensus 合议与路由:多数决方向、强弱、附录丙路由表逐条 batch28 二十六例。装配服务 consensus_service 与 advise_v2、扫描接入、增持门、页面合议行随后。 Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
5fa68817dc
commit
84207d53b8
|
|
@ -0,0 +1,72 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""三源合议与路由 (2026-09-11, 纯逻辑, 零外部依赖)。
|
||||||
|
|
||||||
|
三票 (基本面 / 技术面 / 择时系统) 各是看多/看空/中性/无读数之一, 合成方向再定路由。
|
||||||
|
方案第四节的合议规则 (台账 009):
|
||||||
|
三票多数决定方向; 无读数弃权不计; 平局中性; 只有一方表态算弱; 三方一致算强。
|
||||||
|
路由表见方案附录丙。方向词与合议词一律中文原文, 取值只有看多/看空/中性/无读数。
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
_BULL, _BEAR, _NEUT, _NA = "看多", "看空", "中性", "无读数"
|
||||||
|
|
||||||
|
|
||||||
|
def _reason(fund, tech, timing, direction, strength):
|
||||||
|
head = "、".join([f"基本面{fund}", f"技术面{tech}", f"择时{timing}"])
|
||||||
|
return head + " → " + direction + (("·" + strength) if strength else "")
|
||||||
|
|
||||||
|
|
||||||
|
def _route(fund, tech, timing, direction, tech_phase, fund_required):
|
||||||
|
"""附录丙路由表, 从上到下第一个命中。返回 (路由, 原因)。路由取值: 放行/观察/交人/跳过。"""
|
||||||
|
if fund_required and fund == _NA:
|
||||||
|
return "跳过", "没有买方评析"
|
||||||
|
if direction == _BEAR:
|
||||||
|
return "跳过", f"方向看空(基本面{fund}、技术面{tech}、择时{timing})"
|
||||||
|
if direction == _NEUT and tech_phase == "收口等待" and fund != _BEAR:
|
||||||
|
return "观察", "等技术面开口"
|
||||||
|
if direction == _NEUT and fund == _BULL and tech == _BEAR:
|
||||||
|
return "观察", "等技术面转向"
|
||||||
|
if fund == _BEAR and tech == _BULL:
|
||||||
|
return "交人", "基本面看空、技术面看多,试探仓"
|
||||||
|
if direction == _BULL:
|
||||||
|
voiced = [v for v in (fund, tech, timing) if v in (_BULL, _BEAR)]
|
||||||
|
if timing == _BEAR: # 异议在择时系统
|
||||||
|
return "交人", "方向看多但择时系统有异议"
|
||||||
|
if len(voiced) <= 1: # 只有一方表态
|
||||||
|
return "交人", "方向看多但只有一方表态"
|
||||||
|
return "放行", "方向看多" # 看多强, 或异议只在技术面
|
||||||
|
if fund == _NA and tech == _NA and timing == _NA:
|
||||||
|
return "放行", "三方都无读数,机械方案"
|
||||||
|
return "观察", "方向中性"
|
||||||
|
|
||||||
|
|
||||||
|
def decide(fund, tech, timing, *, tech_phase=None, fund_required=True) -> dict:
|
||||||
|
"""fund/tech/timing 是三个立场字符串 (看多/看空/中性/无读数)。tech_phase 是技术面相位
|
||||||
|
(收口等待 等), 路由用。fund_required: 无买方评析是否拦 (开关 PMS_FUND_REQUIRED)。"""
|
||||||
|
votes = {"fund": fund, "tech": tech, "timing": timing}
|
||||||
|
voiced = [v for v in (fund, tech, timing) if v in (_BULL, _BEAR)]
|
||||||
|
bulls = sum(1 for v in voiced if v == _BULL)
|
||||||
|
bears = sum(1 for v in voiced if v == _BEAR)
|
||||||
|
|
||||||
|
# ---- 方向: 多数决, 无读数弃权不计, 平局中性 ----
|
||||||
|
if bulls > bears:
|
||||||
|
direction = _BULL
|
||||||
|
elif bears > bulls:
|
||||||
|
direction = _BEAR
|
||||||
|
else:
|
||||||
|
direction = _NEUT
|
||||||
|
|
||||||
|
# ---- 强弱: 只有一方表态弱, 三方一致强, 其余 (两方表态) 为中 ----
|
||||||
|
if direction == _NEUT:
|
||||||
|
strength = None
|
||||||
|
elif len(voiced) <= 1:
|
||||||
|
strength = "弱"
|
||||||
|
elif bulls == 3 or bears == 3:
|
||||||
|
strength = "强"
|
||||||
|
else:
|
||||||
|
strength = "中"
|
||||||
|
|
||||||
|
route, route_reason = _route(fund, tech, timing, direction, tech_phase, fund_required)
|
||||||
|
return {"direction": direction, "votes": votes, "strength": strength,
|
||||||
|
"reason": _reason(fund, tech, timing, direction, strength),
|
||||||
|
"route": route, "route_reason": route_reason}
|
||||||
|
|
@ -0,0 +1,67 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""基本面立场合成 (2026-09-11 三源合议, 纯逻辑, 零外部依赖)。
|
||||||
|
|
||||||
|
输入是选股系统的公司深度评析 (plan_feed._company_review_or_none 的结构: overall 质地档、
|
||||||
|
valuation 估值、grade_counts 十视角计数、groups 三类、doubt_hard 硬疑点、age_days 报告日龄)。
|
||||||
|
规则见《技术面接入与三源合议方案》第四节与台账 007:
|
||||||
|
质地好为看多; 质地中且三类里两好零差、十视角差不超过一个也算看多 (整体方向为好就够);
|
||||||
|
质地差或有硬疑点为看空; 其余中性; 缺报告或超 120 天为无读数。
|
||||||
|
十视角好达八个且估值贵时打「共识已定价」标记, 不改立场, 只让首批推迟。
|
||||||
|
立场取值只有: 看多 / 看空 / 中性 / 无读数。无读数是弃权, 绝不折成看空 (设计原则二)。
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
_GROUPS = ("回报与护城河", "盈余质量与财务安全", "成长与含金量")
|
||||||
|
|
||||||
|
DEFAULTS = {
|
||||||
|
"stale_days": 120, # 评析报告超这么多天算无读数 (PMS_FUND_STALE_DAYS)
|
||||||
|
"consensus_good_min": 8, # 十视角好达这么多个且估值贵 → 共识已定价 (PMS_FUND_CONSENSUS_GOOD_MIN)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _out(stance, *, fact=None, why=None, mark=None, priced_in=False, valuation=None, overall=None):
|
||||||
|
return {"stance": stance, "fact": fact, "mark": mark, "priced_in": priced_in,
|
||||||
|
"valuation": valuation, "overall": overall, "no_read_why": why}
|
||||||
|
|
||||||
|
|
||||||
|
def synthesize(company_review, *, params=None) -> dict:
|
||||||
|
p = dict(DEFAULTS)
|
||||||
|
if params:
|
||||||
|
p.update({k: params[k] for k in DEFAULTS if params.get(k) is not None})
|
||||||
|
cr = company_review if isinstance(company_review, dict) else None
|
||||||
|
|
||||||
|
# ---- 无读数: 缺报告或超期 (无读数是弃权) ----
|
||||||
|
if not cr or not cr.get("overall"):
|
||||||
|
return _out("无读数", why="没有买方评析")
|
||||||
|
age = cr.get("age_days")
|
||||||
|
if age is not None and int(age) > int(p["stale_days"]):
|
||||||
|
return _out("无读数", why=f"评析报告已 {age} 天, 超过 {p['stale_days']} 天")
|
||||||
|
|
||||||
|
overall = str(cr.get("overall") or "")
|
||||||
|
gc = cr.get("grade_counts") if isinstance(cr.get("grade_counts"), dict) else {}
|
||||||
|
groups = cr.get("groups") if isinstance(cr.get("groups"), dict) else {}
|
||||||
|
good_views = int(gc.get("好") or 0)
|
||||||
|
bad_views = int(gc.get("差") or 0)
|
||||||
|
doubt = bool(cr.get("doubt_hard"))
|
||||||
|
|
||||||
|
# 共识已定价: 十视角好够多且估值贵 (不改立场, 只让首批推迟)
|
||||||
|
priced_in = good_views >= int(p["consensus_good_min"]) and "贵" in str(cr.get("valuation") or "")
|
||||||
|
|
||||||
|
def out(stance, fact):
|
||||||
|
return _out(stance, fact=fact, mark=("共识已定价" if priced_in else None),
|
||||||
|
priced_in=priced_in, valuation=cr.get("valuation"), overall=overall)
|
||||||
|
|
||||||
|
# ---- 看空: 质地差或硬疑点 ----
|
||||||
|
if overall == "差" or doubt:
|
||||||
|
return out("看空", "质地差" if overall == "差" else "有硬疑点")
|
||||||
|
# ---- 看多: 质地好 ----
|
||||||
|
if overall == "好":
|
||||||
|
return out("看多", "质地好")
|
||||||
|
# ---- 看多: 质地中但「整体方向为好就够」(三类两好零差, 十视角差 ≤ 1) ----
|
||||||
|
if overall == "中":
|
||||||
|
group_good = sum(1 for k in _GROUPS if str(groups.get(k) or "") == "好")
|
||||||
|
group_bad = sum(1 for k in _GROUPS if str(groups.get(k) or "") == "差")
|
||||||
|
if group_good >= 2 and group_bad == 0 and bad_views <= 1:
|
||||||
|
return out("看多", "质地中但三类两好零差、十视角差不过一个")
|
||||||
|
# ---- 其余中性 ----
|
||||||
|
return out("中性", f"质地{overall}")
|
||||||
|
|
@ -0,0 +1,31 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""择时系统立场合成 (2026-09-11 三源合议, 纯逻辑, 零外部依赖)。
|
||||||
|
|
||||||
|
输入是决策系统的昨夜定性 (BUY/WATCH/SELL/AVOID/DROPPED, 或超期/缺) 与当天盘中转多留痕。
|
||||||
|
规则见《技术面接入与三源合议方案》第四节:
|
||||||
|
昨夜 BUY 看多, WATCH 中性, SELL/AVOID/DROPPED 看空, 超期或缺无读数。
|
||||||
|
当天盘中转多留痕把无读数与中性升为看多, 与看空相遇归中性。
|
||||||
|
立场取值只有: 看多 / 看空 / 中性 / 无读数。无读数是弃权, 绝不折成看空 (设计原则二)。
|
||||||
|
|
||||||
|
研判闸不当选票 (设计原则三): 这一票只用昨夜定性与盘中转多留痕, 不重复计入研判结论。
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
_NIGHTLY = {"BUY": "看多", "WATCH": "中性", "SELL": "看空", "AVOID": "看空", "DROPPED": "看空"}
|
||||||
|
|
||||||
|
|
||||||
|
def synthesize(nightly, *, intraday_flip_at=None, fresh=True) -> dict:
|
||||||
|
"""nightly: 昨夜定性字符串 (BUY 等) 或 None/空; fresh: 昨夜结论是否在有效期内 (超期传 False);
|
||||||
|
intraday_flip_at: 当天盘中转多留痕的时刻字符串, 没有传 None。"""
|
||||||
|
n = str(nightly or "").strip().upper()
|
||||||
|
base = _NIGHTLY.get(n, "中性") if (n and fresh) else "无读数"
|
||||||
|
stance = base
|
||||||
|
if intraday_flip_at: # 当天盘中转多留痕
|
||||||
|
if base in ("无读数", "中性"):
|
||||||
|
stance = "看多"
|
||||||
|
elif base == "看空":
|
||||||
|
stance = "中性" # 盘中转多与昨夜看空相遇, 归中性
|
||||||
|
# base 已是看多则维持看多
|
||||||
|
return {"stance": stance, "nightly": (n or None), "fresh": bool(fresh),
|
||||||
|
"intraday_flip_at": intraday_flip_at,
|
||||||
|
"no_read_why": ("没有昨夜结论或已超期" if stance == "无读数" else None)}
|
||||||
|
|
@ -72,14 +72,16 @@
|
||||||
提议卡重排要紧的在前证据收进展开/建议档位与实下股数不一致标出 (17 例)
|
提议卡重排要紧的在前证据收进展开/建议档位与实下股数不一致标出 (17 例)
|
||||||
test_batch27_units.py 技术面接入·工作包一 (2026-09-11): 相位合成九格与四种无读数/震荡市抑制翻向/
|
test_batch27_units.py 技术面接入·工作包一 (2026-09-11): 相位合成九格与四种无读数/震荡市抑制翻向/
|
||||||
取数解析与代码归一/分页拼接与状态非 OK 不落表/映射只覆盖有当天读数的相关票/
|
取数解析与代码归一/分页拼接与状态非 OK 不落表/映射只覆盖有当天读数的相关票/
|
||||||
读数陈旧按无读数/参数登记与交易日差 (27 例)
|
读数陈旧按无读数/参数登记与交易日差/upsert 用 VALUES 与按 matched 分页两个真机哨兵 (29 例)
|
||||||
|
test_batch28_units.py 三源合议·工作包二纯逻辑 (2026-09-11): 基本面质地档映射与共识已定价/择时定性
|
||||||
|
映射与盘中转多升级/合议多数决与强弱/附录丙路由表逐条 (26 例)
|
||||||
test_page_enum_guard.py 页面文案守卫 (静态扫描, 不连库不起浏览器): 枚举字段不许
|
test_page_enum_guard.py 页面文案守卫 (静态扫描, 不连库不起浏览器): 枚举字段不许
|
||||||
直接印到页面上 / 判据码显示前必须剥前缀 / 不许把整个对象
|
直接印到页面上 / 判据码显示前必须剥前缀 / 不许把整个对象
|
||||||
打给交易员看 / 翻译兜底不许让英文码单独当句子 (1 例)
|
打给交易员看 / 翻译兜底不许让英文码单独当句子 (1 例)
|
||||||
test_wiring.py 装配自检: 服务层→核心→落表 全链路 (内存桩) +
|
test_wiring.py 装配自检: 服务层→核心→落表 全链路 (内存桩) +
|
||||||
目标价到价必定入队 (档位 full 也不自动卖) +
|
目标价到价必定入队 (档位 full 也不自动卖) +
|
||||||
用户设的止损价与目标价单独成列显示 (70 例)
|
用户设的止损价与目标价单独成列显示 (70 例)
|
||||||
共 724 例
|
共 752 例
|
||||||
(总数按实跑逐批相加校正过两次: 曾写 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)。
|
||||||
|
|
@ -131,6 +133,7 @@ SUITES = ["test_core_units.py", "test_batch2_units.py", "test_batch3_units.py",
|
||||||
"test_batch24_units.py", "test_batch25_units.py",
|
"test_batch24_units.py", "test_batch25_units.py",
|
||||||
"test_batch26_units.py",
|
"test_batch26_units.py",
|
||||||
"test_batch27_units.py",
|
"test_batch27_units.py",
|
||||||
|
"test_batch28_units.py",
|
||||||
"test_page_enum_guard.py",
|
"test_page_enum_guard.py",
|
||||||
"test_page_wiring_guard.py",
|
"test_page_wiring_guard.py",
|
||||||
"test_wiring.py"]
|
"test_wiring.py"]
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,214 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
"""三源合议 · 工作包二纯逻辑 (2026-09-11 方案第四节, 台账 007/009)。全部离线, 不连库。
|
||||||
|
A 基本面 fund_rules: 质地档映射、质地中整体为好、共识已定价、缺与超期无读数。
|
||||||
|
B 择时 timing_rules: 昨夜定性映射、盘中转多升级、超期无读数。
|
||||||
|
C 合议 consensus: 多数决方向、平局中性、强弱、附录丙路由表逐条。
|
||||||
|
"""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
|
||||||
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||||||
|
|
||||||
|
from app.core import fund_rules as fr # noqa: E402
|
||||||
|
from app.core import timing_rules as tm # noqa: E402
|
||||||
|
from app.core import consensus as cons # noqa: E402
|
||||||
|
|
||||||
|
RESULTS = []
|
||||||
|
|
||||||
|
|
||||||
|
def case(name):
|
||||||
|
def deco(fn):
|
||||||
|
RESULTS.append((name, fn))
|
||||||
|
return fn
|
||||||
|
return deco
|
||||||
|
|
||||||
|
|
||||||
|
def _cr(overall="好", valuation="便宜", good=8, mid=2, bad=0, groups=None, doubt=False, age=0):
|
||||||
|
return {"overall": overall, "valuation": valuation,
|
||||||
|
"grade_counts": {"好": good, "中": mid, "差": bad, "证据不足": 0},
|
||||||
|
"groups": groups or {"回报与护城河": "好", "盈余质量与财务安全": "好", "成长与含金量": "中"},
|
||||||
|
"doubt_hard": doubt, "age_days": age}
|
||||||
|
|
||||||
|
|
||||||
|
# ================================================================ A 基本面
|
||||||
|
@case("A 基本面: 质地好为看多")
|
||||||
|
def _():
|
||||||
|
assert fr.synthesize(_cr(overall="好"))["stance"] == "看多"
|
||||||
|
|
||||||
|
|
||||||
|
@case("A 基本面: 质地中但三类两好零差、十视角差≤1 → 看多(整体方向为好就够)")
|
||||||
|
def _():
|
||||||
|
r = fr.synthesize(_cr(overall="中", bad=1,
|
||||||
|
groups={"回报与护城河": "好", "盈余质量与财务安全": "好", "成长与含金量": "中"}))
|
||||||
|
assert r["stance"] == "看多", r
|
||||||
|
|
||||||
|
|
||||||
|
@case("A 基本面: 质地中只有一类好 → 中性")
|
||||||
|
def _():
|
||||||
|
r = fr.synthesize(_cr(overall="中", bad=0,
|
||||||
|
groups={"回报与护城河": "好", "盈余质量与财务安全": "中", "成长与含金量": "中"}))
|
||||||
|
assert r["stance"] == "中性", r
|
||||||
|
|
||||||
|
|
||||||
|
@case("A 基本面: 质地中两好但十视角差>1 → 中性")
|
||||||
|
def _():
|
||||||
|
r = fr.synthesize(_cr(overall="中", bad=2,
|
||||||
|
groups={"回报与护城河": "好", "盈余质量与财务安全": "好", "成长与含金量": "中"}))
|
||||||
|
assert r["stance"] == "中性", r
|
||||||
|
|
||||||
|
|
||||||
|
@case("A 基本面: 质地差看空")
|
||||||
|
def _():
|
||||||
|
assert fr.synthesize(_cr(overall="差"))["stance"] == "看空"
|
||||||
|
|
||||||
|
|
||||||
|
@case("A 基本面: 有硬疑点看空(哪怕质地不差)")
|
||||||
|
def _():
|
||||||
|
assert fr.synthesize(_cr(overall="中", doubt=True))["stance"] == "看空"
|
||||||
|
|
||||||
|
|
||||||
|
@case("A 基本面: 缺评析无读数, 绝不折看空")
|
||||||
|
def _():
|
||||||
|
assert fr.synthesize(None)["stance"] == "无读数"
|
||||||
|
assert fr.synthesize({})["stance"] == "无读数"
|
||||||
|
|
||||||
|
|
||||||
|
@case("A 基本面: 报告超 120 天无读数")
|
||||||
|
def _():
|
||||||
|
assert fr.synthesize(_cr(overall="好", age=121))["stance"] == "无读数"
|
||||||
|
assert fr.synthesize(_cr(overall="好", age=120))["stance"] == "看多"
|
||||||
|
|
||||||
|
|
||||||
|
@case("A 基本面: 十视角好≥8且估值贵 → 共识已定价标记, 立场不变")
|
||||||
|
def _():
|
||||||
|
r = fr.synthesize(_cr(overall="好", good=8, valuation="贵"))
|
||||||
|
assert r["stance"] == "看多" and r["mark"] == "共识已定价" and r["priced_in"] is True
|
||||||
|
|
||||||
|
|
||||||
|
@case("A 基本面: 好视角够但估值不贵 → 不打共识已定价")
|
||||||
|
def _():
|
||||||
|
assert fr.synthesize(_cr(overall="好", good=9, valuation="便宜"))["mark"] is None
|
||||||
|
|
||||||
|
|
||||||
|
# ================================================================ B 择时
|
||||||
|
@case("B 择时: BUY看多 / WATCH中性 / SELL·AVOID·DROPPED看空")
|
||||||
|
def _():
|
||||||
|
assert tm.synthesize("BUY")["stance"] == "看多"
|
||||||
|
assert tm.synthesize("WATCH")["stance"] == "中性"
|
||||||
|
for v in ("SELL", "AVOID", "DROPPED"):
|
||||||
|
assert tm.synthesize(v)["stance"] == "看空", v
|
||||||
|
|
||||||
|
|
||||||
|
@case("B 择时: 缺或超期无读数")
|
||||||
|
def _():
|
||||||
|
assert tm.synthesize(None)["stance"] == "无读数"
|
||||||
|
assert tm.synthesize("BUY", fresh=False)["stance"] == "无读数"
|
||||||
|
|
||||||
|
|
||||||
|
@case("B 择时: 盘中转多把无读数与中性升看多")
|
||||||
|
def _():
|
||||||
|
assert tm.synthesize(None, intraday_flip_at="10:05")["stance"] == "看多"
|
||||||
|
assert tm.synthesize("WATCH", intraday_flip_at="10:05")["stance"] == "看多"
|
||||||
|
|
||||||
|
|
||||||
|
@case("B 择时: 盘中转多与昨夜看空相遇归中性; 昨夜看多维持看多")
|
||||||
|
def _():
|
||||||
|
assert tm.synthesize("SELL", intraday_flip_at="10:05")["stance"] == "中性"
|
||||||
|
assert tm.synthesize("BUY", intraday_flip_at="10:05")["stance"] == "看多"
|
||||||
|
|
||||||
|
|
||||||
|
# ================================================================ C 合议
|
||||||
|
@case("C 合议·方向: 多数决, 无读数弃权, 平局中性")
|
||||||
|
def _():
|
||||||
|
assert cons.decide("看多", "看多", "看空")["direction"] == "看多"
|
||||||
|
assert cons.decide("看空", "看空", "看多")["direction"] == "看空"
|
||||||
|
assert cons.decide("看多", "看空", "无读数")["direction"] == "中性"
|
||||||
|
assert cons.decide("无读数", "无读数", "无读数", fund_required=False)["direction"] == "中性"
|
||||||
|
|
||||||
|
|
||||||
|
@case("C 合议·强弱: 一方表态弱, 三方一致强, 两方为中")
|
||||||
|
def _():
|
||||||
|
assert cons.decide("看多", "无读数", "无读数")["strength"] == "弱"
|
||||||
|
assert cons.decide("看多", "看多", "看多")["strength"] == "强"
|
||||||
|
assert cons.decide("看多", "看多", "无读数")["strength"] == "中"
|
||||||
|
|
||||||
|
|
||||||
|
@case("C 路由: 基本面无读数且开关开 → 跳过没有买方评析")
|
||||||
|
def _():
|
||||||
|
r = cons.decide("无读数", "看多", "看多", fund_required=True)
|
||||||
|
assert r["route"] == "跳过" and "买方评析" in r["route_reason"]
|
||||||
|
|
||||||
|
|
||||||
|
@case("C 路由: 关掉 fund_required 后无评析不因此跳过")
|
||||||
|
def _():
|
||||||
|
assert cons.decide("无读数", "看多", "看多", fund_required=False)["route"] != "跳过"
|
||||||
|
|
||||||
|
|
||||||
|
@case("C 路由: 方向看空 → 跳过")
|
||||||
|
def _():
|
||||||
|
assert cons.decide("看空", "看空", "中性")["route"] == "跳过"
|
||||||
|
|
||||||
|
|
||||||
|
@case("C 路由: 中性且技术面收口等待且基本面非看空 → 观察等开口")
|
||||||
|
def _():
|
||||||
|
r = cons.decide("中性", "中性", "中性", tech_phase="收口等待")
|
||||||
|
assert r["route"] == "观察" and "开口" in r["route_reason"]
|
||||||
|
|
||||||
|
|
||||||
|
@case("C 路由: 中性且基本面看多技术面看空 → 观察等技术面转向")
|
||||||
|
def _():
|
||||||
|
r = cons.decide("看多", "看空", "无读数")
|
||||||
|
assert r["direction"] == "中性" and r["route"] == "观察" and "转向" in r["route_reason"]
|
||||||
|
|
||||||
|
|
||||||
|
@case("C 路由: 基本面看空技术面看多 → 交人试探仓")
|
||||||
|
def _():
|
||||||
|
r = cons.decide("看空", "看多", "中性")
|
||||||
|
assert r["route"] == "交人" and "试探" in r["route_reason"]
|
||||||
|
|
||||||
|
|
||||||
|
@case("C 路由: 看多但异议在择时系统 → 交人")
|
||||||
|
def _():
|
||||||
|
r = cons.decide("看多", "看多", "看空")
|
||||||
|
assert r["direction"] == "看多" and r["route"] == "交人" and "择时" in r["route_reason"]
|
||||||
|
|
||||||
|
|
||||||
|
@case("C 路由: 看多但只有一方表态 → 交人")
|
||||||
|
def _():
|
||||||
|
r = cons.decide("看多", "无读数", "无读数")
|
||||||
|
assert r["route"] == "交人" and "一方" in r["route_reason"]
|
||||||
|
|
||||||
|
|
||||||
|
@case("C 路由: 看多且多方表态、异议只在技术面 → 放行")
|
||||||
|
def _():
|
||||||
|
r = cons.decide("看多", "看空", "看多")
|
||||||
|
assert r["direction"] == "看多" and r["route"] == "放行"
|
||||||
|
|
||||||
|
|
||||||
|
@case("C 路由: 三方都无读数 → 放行机械方案")
|
||||||
|
def _():
|
||||||
|
r = cons.decide("无读数", "无读数", "无读数", fund_required=False)
|
||||||
|
assert r["route"] == "放行" and "机械" in r["route_reason"]
|
||||||
|
|
||||||
|
|
||||||
|
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())
|
||||||
Loading…
Reference in New Issue