120 lines
5.7 KiB
Python
120 lines
5.7 KiB
Python
|
|
# -*- coding: utf-8 -*-
|
||
|
|
"""
|
||
|
|
决策系统 (bionic) 两个 PMS 接口的实机探活 —— 只读, 不落任何表, 不产生任何指令
|
||
|
|
==============================================================================
|
||
|
|
运行 (factorevaluation, 需 .env 能连参数表; --base 显式给地址则连参数表也不用):
|
||
|
|
|
||
|
|
docker compose run --rm --no-deps pms-web python scripts/probe_bionic.py
|
||
|
|
docker compose run --rm --no-deps pms-web python scripts/probe_bionic.py \
|
||
|
|
--base http://192.168.16.178:38000 --code 600000.SH
|
||
|
|
|
||
|
|
探两件事 (待办 #9 / #10 的接通自证, 契约见 BIONIC_PMS_INTERFACE.md):
|
||
|
|
1. /api/intraday/pms_exec 择时研判: 送一份手造的现场快照, 看回不回 FIRE/WAIT/UNAVAILABLE
|
||
|
|
2. /api/intraday/pms_judge 研判闸: 送一份手造的 DCA 候选, 看回不回 PASS/REJECT
|
||
|
|
(研判走 LLM, 30~90 秒是正常的; --skip-judge 可只探择时)
|
||
|
|
|
||
|
|
判读:
|
||
|
|
* exec 回 UNAVAILABLE 时看原因——「无实时价」(收盘后正常)、「没有昨夜结论/已过期」
|
||
|
|
(该股不在决策系统每晚分析范围, 换一只探) 都是正常降级不是故障; 盘中探到 FIRE/WAIT 才算全通。
|
||
|
|
* judge 回 UNAVAILABLE —— 看 reason: 队列超时说明 brain_queue 拥堵 (考虑独立队列),
|
||
|
|
连接拒绝说明地址/网络不通。
|
||
|
|
* 两个都通了: 页面把 PMS_JUDGE_API_BASE 填上 (研判闸即生效),
|
||
|
|
要切择时A再把 PMS_EXEC_IMPL 改成 A。
|
||
|
|
"""
|
||
|
|
import argparse
|
||
|
|
import json
|
||
|
|
import os
|
||
|
|
import sys
|
||
|
|
import time
|
||
|
|
|
||
|
|
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
||
|
|
|
||
|
|
|
||
|
|
def main():
|
||
|
|
ap = argparse.ArgumentParser()
|
||
|
|
ap.add_argument("--base", default="", help="bionic 根地址; 缺省读参数 "
|
||
|
|
"PMS_EXEC_API_BASE / PMS_JUDGE_API_BASE")
|
||
|
|
ap.add_argument("--code", default="600000.SH", help="探活用股票代码 (点式)")
|
||
|
|
ap.add_argument("--skip-judge", action="store_true", help="只探择时, 不打 LLM")
|
||
|
|
ap.add_argument("--judge-timeout", type=int, default=120)
|
||
|
|
args = ap.parse_args()
|
||
|
|
|
||
|
|
import requests
|
||
|
|
|
||
|
|
base = args.base.rstrip("/")
|
||
|
|
if not base:
|
||
|
|
from app.services import exec_advisor, judge
|
||
|
|
base = exec_advisor.base_url() or judge.base_url()
|
||
|
|
if not base:
|
||
|
|
print("[FAIL] 没有可用地址: --base 未给, PMS_EXEC_API_BASE / "
|
||
|
|
"PMS_JUDGE_API_BASE 也都为空")
|
||
|
|
sys.exit(2)
|
||
|
|
print(f"目标: {base} 探活代码: {args.code}")
|
||
|
|
|
||
|
|
# ---- 1. 择时研判 (纯代码, 应当秒回) ----
|
||
|
|
exec_payload = {
|
||
|
|
"direction": "PMS_EXEC", "ts_code": args.code, "side": "buy", "action": "OPEN",
|
||
|
|
"qty_left": 500, "is_last_day": False, "tdays_left": 3,
|
||
|
|
"now": time.strftime("%H:%M"),
|
||
|
|
"day": {"price": None, "vwap": None, "open": None, "high": None, "low": None,
|
||
|
|
"day_chg_from_open": None, "bars": 0}, # 现场快照留空 → 由 bionic 自取实时价
|
||
|
|
"refs": {"support": None, "pressure": None, "stop": None, "source": None},
|
||
|
|
"position": {"total_qty": 0, "avail_qty": 0, "avg_cost": None, "cushion_pct": None},
|
||
|
|
}
|
||
|
|
t0 = time.time()
|
||
|
|
try:
|
||
|
|
r = requests.post(f"{base}/api/intraday/pms_exec", json=exec_payload, timeout=15)
|
||
|
|
body = r.json()
|
||
|
|
ms = (time.time() - t0) * 1000
|
||
|
|
verdict = str(body.get("verdict") or "?")
|
||
|
|
ok = verdict in ("FIRE", "WAIT", "UNAVAILABLE")
|
||
|
|
print(f"[{'OK' if ok else 'FAIL'}] pms_exec {r.status_code} {ms:.0f}ms → {verdict}: "
|
||
|
|
f"{body.get('reason')}")
|
||
|
|
print(" " + json.dumps({k: body.get(k) for k in
|
||
|
|
("limit_price", "valid_min", "confidence", "missing",
|
||
|
|
"advisor")}, ensure_ascii=False))
|
||
|
|
exec_ok = ok
|
||
|
|
except Exception as e:
|
||
|
|
print(f"[FAIL] pms_exec 打不通: {type(e).__name__}: {e}")
|
||
|
|
exec_ok = False
|
||
|
|
|
||
|
|
# ---- 2. 研判闸 (LLM, 慢是正常) ----
|
||
|
|
judge_ok = None
|
||
|
|
if not args.skip_judge:
|
||
|
|
judge_payload = {
|
||
|
|
"direction": "PMS_JUDGE", "action": "DCA", "ts_code": args.code, "qty": 200,
|
||
|
|
"reason": "[探活] 浮亏 -8.5% 触发第一档补仓评估",
|
||
|
|
"hard_numbers": {"cushion_pct": -0.085, "stage": 1, "price": None},
|
||
|
|
"context": {"position": {"ts_code": args.code, "avg_cost": None,
|
||
|
|
"total_qty": 1000, "cushion_pct": -0.085},
|
||
|
|
"recent_ledger": [], "probe": True},
|
||
|
|
"must_answer": ["下跌是杀逻辑还是杀情绪"],
|
||
|
|
}
|
||
|
|
print(f"研判闸探活中 (走 LLM, 最多等 {args.judge_timeout}s)...")
|
||
|
|
t0 = time.time()
|
||
|
|
try:
|
||
|
|
r = requests.post(f"{base}/api/intraday/pms_judge", json=judge_payload,
|
||
|
|
timeout=args.judge_timeout)
|
||
|
|
body = r.json()
|
||
|
|
sec = time.time() - t0
|
||
|
|
verdict = str(body.get("verdict") or "?")
|
||
|
|
judge_ok = verdict in ("PASS", "REJECT")
|
||
|
|
tag = "OK" if judge_ok else ("WARN" if verdict == "UNAVAILABLE" else "FAIL")
|
||
|
|
print(f"[{tag}] pms_judge {r.status_code} {sec:.1f}s → {verdict} "
|
||
|
|
f"(confidence={body.get('confidence')}): {body.get('reason')}")
|
||
|
|
except Exception as e:
|
||
|
|
print(f"[FAIL] pms_judge 打不通: {type(e).__name__}: {e}")
|
||
|
|
judge_ok = False
|
||
|
|
|
||
|
|
print("-" * 60)
|
||
|
|
if exec_ok and judge_ok:
|
||
|
|
print("两个接口都通。下一步: 页面填 PMS_JUDGE_API_BASE=" + base
|
||
|
|
+ " (研判闸即生效); 要切择时A再把 PMS_EXEC_IMPL 改成 A")
|
||
|
|
elif exec_ok and judge_ok is None:
|
||
|
|
print("择时接口通 (--skip-judge 未探研判闸)")
|
||
|
|
sys.exit(0 if exec_ok and judge_ok is not False else 1)
|
||
|
|
|
||
|
|
|
||
|
|
if __name__ == "__main__":
|
||
|
|
main()
|