review_needed 跟进机制 + 质量门禁文档
新增 review_needed_watchdog.py: - 每30分钟扫描 review_needed 策略 - 自动调 per_stock_reassess 重评 - 重试3次仍失败 → 推Dad XMPP人工介入 - cron: 交易日 9:30~15:30 news-flow-analysis skill 文档同步更新: - 完整 review_needed 流程链 - 自动修复→重检→跟进→上报 闭环
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
+36
-36
@@ -5,9 +5,9 @@
|
||||
"name": "中际旭创",
|
||||
"shares": 100,
|
||||
"cost": 1316.53,
|
||||
"price": 1141.0,
|
||||
"price": 1140.85,
|
||||
"market_value": 113604.0,
|
||||
"change_pct": -6.72,
|
||||
"change_pct": -6.73,
|
||||
"currency": "CNY",
|
||||
"position_pct": 15.27,
|
||||
"_currency": "CNY"
|
||||
@@ -17,9 +17,9 @@
|
||||
"name": "长飞光纤光缆",
|
||||
"shares": 500,
|
||||
"cost": 263.72,
|
||||
"price": 178.08,
|
||||
"price": 174.09,
|
||||
"market_value": 89300.0,
|
||||
"change_pct": -19.577,
|
||||
"change_pct": -21.38,
|
||||
"currency": "CNY",
|
||||
"position_pct": 13.47,
|
||||
"_currency": "CNY"
|
||||
@@ -29,9 +29,9 @@
|
||||
"name": "丘钛科技",
|
||||
"shares": 11000,
|
||||
"cost": 13.47,
|
||||
"price": 6.02,
|
||||
"price": 5.96,
|
||||
"market_value": 65890.0,
|
||||
"change_pct": 0.0,
|
||||
"change_pct": 0.146,
|
||||
"currency": "CNY",
|
||||
"position_pct": 7.97,
|
||||
"_currency": "CNY"
|
||||
@@ -41,9 +41,9 @@
|
||||
"name": "紫金矿业",
|
||||
"shares": 2400,
|
||||
"cost": 39.89,
|
||||
"price": 26.35,
|
||||
"price": 26.39,
|
||||
"market_value": 63048.0,
|
||||
"change_pct": 4.94,
|
||||
"change_pct": 5.1,
|
||||
"currency": "CNY",
|
||||
"position_pct": 7.34,
|
||||
"_currency": "CNY"
|
||||
@@ -53,9 +53,9 @@
|
||||
"name": "海博思创",
|
||||
"shares": 200,
|
||||
"cost": 266.95,
|
||||
"price": 257.9,
|
||||
"price": 257.27,
|
||||
"market_value": 51776.0,
|
||||
"change_pct": -1.9,
|
||||
"change_pct": -2.14,
|
||||
"currency": "CNY",
|
||||
"position_pct": 6.31,
|
||||
"_currency": "CNY"
|
||||
@@ -65,9 +65,9 @@
|
||||
"name": "中芯国际",
|
||||
"shares": 300,
|
||||
"cost": 126.07,
|
||||
"price": 146.57,
|
||||
"price": 145.7,
|
||||
"market_value": 44112.0,
|
||||
"change_pct": -5.12,
|
||||
"change_pct": -5.68,
|
||||
"currency": "CNY",
|
||||
"position_pct": 5.44,
|
||||
"_currency": "CNY"
|
||||
@@ -77,9 +77,9 @@
|
||||
"name": "建滔积层板",
|
||||
"shares": 500,
|
||||
"cost": 88.23,
|
||||
"price": 73.35,
|
||||
"price": 71.79,
|
||||
"market_value": 36415.0,
|
||||
"change_pct": -14.675,
|
||||
"change_pct": -16.49,
|
||||
"currency": "CNY",
|
||||
"position_pct": 5.28,
|
||||
"_currency": "CNY"
|
||||
@@ -89,9 +89,9 @@
|
||||
"name": "华恒生物",
|
||||
"shares": 2800,
|
||||
"cost": 21.51,
|
||||
"price": 17.21,
|
||||
"price": 17.15,
|
||||
"market_value": 48244.0,
|
||||
"change_pct": 5.13,
|
||||
"change_pct": 4.76,
|
||||
"currency": "CNY",
|
||||
"position_pct": 5.25,
|
||||
"_currency": "CNY"
|
||||
@@ -101,9 +101,9 @@
|
||||
"name": "宁德时代",
|
||||
"shares": 100,
|
||||
"cost": 401.78,
|
||||
"price": 386.64,
|
||||
"price": 385.91,
|
||||
"market_value": 38495.0,
|
||||
"change_pct": 0.73,
|
||||
"change_pct": 0.54,
|
||||
"currency": "CNY",
|
||||
"position_pct": 4.64,
|
||||
"_currency": "CNY"
|
||||
@@ -113,9 +113,9 @@
|
||||
"name": "比亚迪股份",
|
||||
"shares": 600,
|
||||
"cost": 104.87,
|
||||
"price": 68.19,
|
||||
"price": 67.8,
|
||||
"market_value": 41070.0,
|
||||
"change_pct": 8.558,
|
||||
"change_pct": 7.94,
|
||||
"currency": "CNY",
|
||||
"position_pct": 4.62,
|
||||
"_currency": "CNY"
|
||||
@@ -137,9 +137,9 @@
|
||||
"name": "腾讯",
|
||||
"shares": 100,
|
||||
"cost": null,
|
||||
"price": 378.01,
|
||||
"price": 377.67,
|
||||
"market_value": 37784.0,
|
||||
"change_pct": 1.443,
|
||||
"change_pct": 1.35,
|
||||
"currency": "CNY",
|
||||
"position_pct": null,
|
||||
"_currency": "CNY"
|
||||
@@ -149,9 +149,9 @@
|
||||
"name": "中芯国际",
|
||||
"shares": 500,
|
||||
"cost": 75.94,
|
||||
"price": 69.06,
|
||||
"price": 68.32,
|
||||
"market_value": 34635.0,
|
||||
"change_pct": -10.906,
|
||||
"change_pct": -11.86,
|
||||
"currency": "CNY",
|
||||
"position_pct": 4.2,
|
||||
"_currency": "CNY"
|
||||
@@ -161,9 +161,9 @@
|
||||
"name": "长芯博创",
|
||||
"shares": 100,
|
||||
"cost": 231.46,
|
||||
"price": 227.05,
|
||||
"price": 224.72,
|
||||
"market_value": 22592.0,
|
||||
"change_pct": -10.61,
|
||||
"change_pct": -11.53,
|
||||
"currency": "CNY",
|
||||
"position_pct": 3.2,
|
||||
"_currency": "CNY"
|
||||
@@ -173,9 +173,9 @@
|
||||
"name": "黄金ETF华安",
|
||||
"shares": 2400,
|
||||
"cost": 12.19,
|
||||
"price": 8.45,
|
||||
"price": 8.47,
|
||||
"market_value": 20280.0,
|
||||
"change_pct": 2.18,
|
||||
"change_pct": 2.39,
|
||||
"currency": "CNY",
|
||||
"position_pct": 2.45,
|
||||
"_currency": "CNY"
|
||||
@@ -185,9 +185,9 @@
|
||||
"name": "中科电气",
|
||||
"shares": 1400,
|
||||
"cost": 22.29,
|
||||
"price": 14.32,
|
||||
"price": 14.28,
|
||||
"market_value": 20062.0,
|
||||
"change_pct": -0.83,
|
||||
"change_pct": -1.11,
|
||||
"currency": "CNY",
|
||||
"position_pct": 2.42,
|
||||
"_currency": "CNY"
|
||||
@@ -221,20 +221,20 @@
|
||||
"name": "中国神华",
|
||||
"shares": 500,
|
||||
"cost": 45.89,
|
||||
"price": 34.14,
|
||||
"price": 34.18,
|
||||
"market_value": 17115.0,
|
||||
"change_pct": 1.143,
|
||||
"change_pct": 1.25,
|
||||
"currency": "CNY",
|
||||
"position_pct": 2.14,
|
||||
"_currency": "CNY"
|
||||
}
|
||||
],
|
||||
"total_assets": 903571.0,
|
||||
"total_mv": 823095.0,
|
||||
"total_assets": 898730.0,
|
||||
"total_mv": 818254.0,
|
||||
"stock_value": null,
|
||||
"cash": 80476.0,
|
||||
"frozen_cash": 0.0,
|
||||
"position_pct": 91.09,
|
||||
"position_pct": 91.05,
|
||||
"currency": "CNY",
|
||||
"updated_at": "2026-07-02 13:56"
|
||||
"updated_at": "2026-07-02 14:04"
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
#!/usr/bin/env python3
|
||||
"""
|
||||
review_needed_watchdog.py — review_needed 策略自动跟进
|
||||
|
||||
每30分钟扫描DB中 status=review_needed 的策略:
|
||||
1. 对每只策略调用 per_stock_reassess 重评
|
||||
2. 重评后 status 变 active → 通过,写入 changelog
|
||||
3. 还是 review_needed → retry_count+=1
|
||||
4. retry_count>=3 → 推 Dad 人工介入
|
||||
"""
|
||||
|
||||
import sys, json, os, datetime
|
||||
|
||||
sys.path.insert(0, "/home/hmo/web-dashboard")
|
||||
os.chdir("/home/hmo/MoFin")
|
||||
|
||||
DEC_PATH = "/home/hmo/web-dashboard/data/decisions.json"
|
||||
RETRY_FILE = "/home/hmo/web-dashboard/data/review_needed_retry.json"
|
||||
XMPP_USER = "hmo@yoin.fun"
|
||||
XMPP_BRIDGE = "http://192.168.1.246:5805/xmpp/send"
|
||||
|
||||
def load_retry():
|
||||
try:
|
||||
return json.load(open(RETRY_FILE))
|
||||
except:
|
||||
return {}
|
||||
|
||||
def save_retry(data):
|
||||
json.dump(data, open(RETRY_FILE, "w"), indent=2)
|
||||
|
||||
def push_xmpp(text):
|
||||
try:
|
||||
from urllib.request import Request, urlopen
|
||||
payload = json.dumps({"to": XMPP_USER, "body": text.strip()}).encode()
|
||||
req = Request(XMPP_BRIDGE, data=payload, headers={"Content-Type": "application/json"})
|
||||
urlopen(req, timeout=5)
|
||||
print(f" [XMPP] 已推送")
|
||||
except Exception as e:
|
||||
print(f" [XMPP推送失败] {e}")
|
||||
|
||||
def main():
|
||||
dec = json.load(open(DEC_PATH))
|
||||
review_list = [d for d in dec.get("decisions", []) if d.get("status") == "review_needed"]
|
||||
retry_data = load_retry()
|
||||
today = datetime.date.today().isoformat()
|
||||
|
||||
if not review_list:
|
||||
print("[SILENT] 无待处理策略")
|
||||
return
|
||||
|
||||
print(f"发现 {len(review_list)} 只 review_needed 策略")
|
||||
changes = False
|
||||
for d in review_list:
|
||||
code = d["code"]
|
||||
name = d.get("name", code)
|
||||
retries = retry_data.get(code, {}).get("count", 0) + 1
|
||||
retry_data[code] = {"count": retries, "last_attempt": today}
|
||||
|
||||
if retries >= 3:
|
||||
print(f" ⛔ {name}({code}) 已重试{retries}次,跳过")
|
||||
continue
|
||||
|
||||
print(f" 🔄 {name}({code}) 第{retries}次重试...")
|
||||
import subprocess
|
||||
r = subprocess.run(
|
||||
[sys.executable, "/home/hmo/MoFin/scripts/per_stock_reassess.py", code],
|
||||
capture_output=True, text=True, timeout=30
|
||||
)
|
||||
out = (r.stdout or "") + (r.stderr or "")
|
||||
print(f" {out[:200]}")
|
||||
|
||||
# 重读决策
|
||||
dec2 = json.load(open(DEC_PATH))
|
||||
for d2 in dec2.get("decisions", []):
|
||||
if d2["code"] == code:
|
||||
if d2.get("status") == "active":
|
||||
print(f" ✅ {name}({code}) 重评通过!")
|
||||
retry_data[code] = {"count": 0, "last_attempt": today}
|
||||
changes = True
|
||||
elif d2.get("status") == "review_needed":
|
||||
issues = d2.get("quality_issues", {}).get("critical", [])
|
||||
print(f" ❌ {name}({code}) 仍 review_needed ({issues})")
|
||||
break
|
||||
|
||||
# 3次以上失败 → 推 Dad
|
||||
dead = [code for code, v in retry_data.items() if v.get("count", 0) >= 3]
|
||||
if dead:
|
||||
names = []
|
||||
for code in dead:
|
||||
for d in dec.get("decisions", []):
|
||||
if d["code"] == code:
|
||||
names.append(f"{d.get('name', code)}({code})")
|
||||
break
|
||||
msg = f"【知微】策略质量审核 {today}\n以下策略3次自动重评均失败,需人工介入:\n"
|
||||
for n in names:
|
||||
msg += f" - {n}\n"
|
||||
msg += "\n原因可能是:缺少技术面数据 / 行业信息不完整 / 利润保护目标无法确定。"
|
||||
push_xmpp(msg)
|
||||
|
||||
save_retry(retry_data)
|
||||
if not changes:
|
||||
print("[SILENT] 状态无变化")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user