diff --git a/__pycache__/strategy_lifecycle.cpython-312.pyc b/__pycache__/strategy_lifecycle.cpython-312.pyc index 1e3d6ae..eeddeb7 100644 Binary files a/__pycache__/strategy_lifecycle.cpython-312.pyc and b/__pycache__/strategy_lifecycle.cpython-312.pyc differ diff --git a/data/mofin.db-shm b/data/mofin.db-shm index 3fc6c55..92a8eb7 100644 Binary files a/data/mofin.db-shm and b/data/mofin.db-shm differ diff --git a/data/mofin.db-wal b/data/mofin.db-wal index 154b8b5..6fe03e0 100644 Binary files a/data/mofin.db-wal and b/data/mofin.db-wal differ diff --git a/data/portfolio.json b/data/portfolio.json index 7b6032f..c4ef611 100644 --- a/data/portfolio.json +++ b/data/portfolio.json @@ -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" } \ No newline at end of file diff --git a/scripts/review_needed_watchdog.py b/scripts/review_needed_watchdog.py new file mode 100644 index 0000000..f8a2afa --- /dev/null +++ b/scripts/review_needed_watchdog.py @@ -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()