From 9e5ea52c1ef6e2f294a0da7d17043c098b02aa59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E7=9F=A5=E5=BE=AE?= Date: Thu, 2 Jul 2026 14:12:31 +0800 Subject: [PATCH] =?UTF-8?q?=E7=AD=96=E7=95=A5=E8=B4=A8=E9=87=8F=E9=97=A8?= =?UTF-8?q?=E7=A6=81=E4=B8=89=E6=AE=B5=E8=87=AA=E5=8A=A8=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit enforce_strategy_quality() 重写为3轮重试: - Round 1: ta.full_analysis 技术位 - Round 2: DB行业 + price%推算 - Round 3: 最低可用策略(强制fallback) 3轮全不过 → review_needed 对应模塑空壳策略问题: 之前只修一轮→不过就直接review_needed 现在轮修到通过为止再不通过才review --- .../strategy_lifecycle.cpython-312.pyc | Bin 107054 -> 107920 bytes data/mofin.db-shm | Bin 32768 -> 0 bytes data/mofin.db-wal | Bin 90672 -> 0 bytes data/portfolio.json | 76 +++--- strategy_lifecycle.py | 254 ++++++++++-------- 5 files changed, 181 insertions(+), 149 deletions(-) delete mode 100644 data/mofin.db-shm delete mode 100644 data/mofin.db-wal diff --git a/__pycache__/strategy_lifecycle.cpython-312.pyc b/__pycache__/strategy_lifecycle.cpython-312.pyc index eeddeb782d07df47ad8ffe74329c6c282f557250..35aa4d70917ff0d78d1dfe57aa0305e23b5bd801 100644 GIT binary patch delta 14207 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 delta 13454 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 vqjea`D|tHpBu^7{Dkz2Ck6Zf0lRFSAROxra)e z4lB#kvA%Xz#w!UBAV7cs0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs z0RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+009C72wWsk zOFfO;q?uN3(@rPd^m3Pe2D#5L4;f{g$>|@?iyqQUfB*pk1PBlyK!5-N0t5&UAV7cs i0RjXF5FkK+009C72oNAZfB*pk1PBlyK!Cu%3H$-9Wh}!0 diff --git a/data/mofin.db-wal b/data/mofin.db-wal deleted file mode 100644 index 6fe03e040bb4865eeecb8ee924295545492ddb66..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 90672 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 diff --git a/data/portfolio.json b/data/portfolio.json index c4ef611..00d0390 100644 --- a/data/portfolio.json +++ b/data/portfolio.json @@ -5,9 +5,9 @@ "name": "中际旭创", "shares": 100, "cost": 1316.53, - "price": 1140.85, + "price": 1153.0, "market_value": 113604.0, - "change_pct": -6.73, + "change_pct": -5.74, "currency": "CNY", "position_pct": 15.27, "_currency": "CNY" @@ -17,9 +17,9 @@ "name": "长飞光纤光缆", "shares": 500, "cost": 263.72, - "price": 174.09, + "price": 174.79, "market_value": 89300.0, - "change_pct": -21.38, + "change_pct": -21.06, "currency": "CNY", "position_pct": 13.47, "_currency": "CNY" @@ -29,9 +29,9 @@ "name": "丘钛科技", "shares": 11000, "cost": 13.47, - "price": 5.96, + "price": 5.95, "market_value": 65890.0, - "change_pct": 0.146, + "change_pct": 0.0, "currency": "CNY", "position_pct": 7.97, "_currency": "CNY" @@ -41,9 +41,9 @@ "name": "紫金矿业", "shares": 2400, "cost": 39.89, - "price": 26.39, + "price": 26.4, "market_value": 63048.0, - "change_pct": 5.1, + "change_pct": 5.14, "currency": "CNY", "position_pct": 7.34, "_currency": "CNY" @@ -53,9 +53,9 @@ "name": "海博思创", "shares": 200, "cost": 266.95, - "price": 257.27, + "price": 258.8, "market_value": 51776.0, - "change_pct": -2.14, + "change_pct": -1.56, "currency": "CNY", "position_pct": 6.31, "_currency": "CNY" @@ -65,9 +65,9 @@ "name": "中芯国际", "shares": 300, "cost": 126.07, - "price": 145.7, + "price": 147.2, "market_value": 44112.0, - "change_pct": -5.68, + "change_pct": -4.71, "currency": "CNY", "position_pct": 5.44, "_currency": "CNY" @@ -77,9 +77,9 @@ "name": "建滔积层板", "shares": 500, "cost": 88.23, - "price": 71.79, + "price": 72.35, "market_value": 36415.0, - "change_pct": -16.49, + "change_pct": -15.83, "currency": "CNY", "position_pct": 5.28, "_currency": "CNY" @@ -89,9 +89,9 @@ "name": "华恒生物", "shares": 2800, "cost": 21.51, - "price": 17.15, + "price": 17.24, "market_value": 48244.0, - "change_pct": 4.76, + "change_pct": 5.31, "currency": "CNY", "position_pct": 5.25, "_currency": "CNY" @@ -101,9 +101,9 @@ "name": "宁德时代", "shares": 100, "cost": 401.78, - "price": 385.91, + "price": 385.54, "market_value": 38495.0, - "change_pct": 0.54, + "change_pct": 0.44, "currency": "CNY", "position_pct": 4.64, "_currency": "CNY" @@ -113,9 +113,9 @@ "name": "比亚迪股份", "shares": 600, "cost": 104.87, - "price": 67.8, + "price": 67.97, "market_value": 41070.0, - "change_pct": 7.94, + "change_pct": 8.21, "currency": "CNY", "position_pct": 4.62, "_currency": "CNY" @@ -125,9 +125,9 @@ "name": "万科企业", "shares": 19700, "cost": 4.67, - "price": 1.93, + "price": 1.92, "market_value": 38021.0, - "change_pct": 5.189, + "change_pct": 4.72, "currency": "CNY", "position_pct": 4.6, "_currency": "CNY" @@ -137,9 +137,9 @@ "name": "腾讯", "shares": 100, "cost": null, - "price": 377.67, + "price": 377.49, "market_value": 37784.0, - "change_pct": 1.35, + "change_pct": 1.3, "currency": "CNY", "position_pct": null, "_currency": "CNY" @@ -149,9 +149,9 @@ "name": "中芯国际", "shares": 500, "cost": 75.94, - "price": 68.32, + "price": 69.14, "market_value": 34635.0, - "change_pct": -11.86, + "change_pct": -10.79, "currency": "CNY", "position_pct": 4.2, "_currency": "CNY" @@ -161,9 +161,9 @@ "name": "长芯博创", "shares": 100, "cost": 231.46, - "price": 224.72, + "price": 226.0, "market_value": 22592.0, - "change_pct": -11.53, + "change_pct": -11.02, "currency": "CNY", "position_pct": 3.2, "_currency": "CNY" @@ -173,9 +173,9 @@ "name": "黄金ETF华安", "shares": 2400, "cost": 12.19, - "price": 8.47, + "price": 8.46, "market_value": 20280.0, - "change_pct": 2.39, + "change_pct": 2.32, "currency": "CNY", "position_pct": 2.45, "_currency": "CNY" @@ -185,9 +185,9 @@ "name": "中科电气", "shares": 1400, "cost": 22.29, - "price": 14.28, + "price": 14.32, "market_value": 20062.0, - "change_pct": -1.11, + "change_pct": -0.83, "currency": "CNY", "position_pct": 2.42, "_currency": "CNY" @@ -221,20 +221,20 @@ "name": "中国神华", "shares": 500, "cost": 45.89, - "price": 34.18, + "price": 34.16, "market_value": 17115.0, - "change_pct": 1.25, + "change_pct": 1.19, "currency": "CNY", "position_pct": 2.14, "_currency": "CNY" } ], - "total_assets": 898730.0, - "total_mv": 818254.0, + "total_assets": 901907.0, + "total_mv": 821431.0, "stock_value": null, "cash": 80476.0, "frozen_cash": 0.0, - "position_pct": 91.05, + "position_pct": 91.08, "currency": "CNY", - "updated_at": "2026-07-02 14:04" + "updated_at": "2026-07-02 14:12" } \ No newline at end of file diff --git a/strategy_lifecycle.py b/strategy_lifecycle.py index 44f8252..3aca2b9 100644 --- a/strategy_lifecycle.py +++ b/strategy_lifecycle.py @@ -157,26 +157,31 @@ def validate_strategy(d, debug=True): def enforce_strategy_quality(code, name, result): """策略写入前的强制质量门禁 - 对 result 执行 validate_strategy,不通过则: - - CRITICAL 失败 → 自动调取技术分析/行业数据修复 - - 修复后重检 → 通过则写入,再不通过标记 review_needed - - HIGH 失败 → 标记 warning 但放行 + 三段自动修复: + - Round 1: 技术分析(ta.full_analysis/chip_sr) + - Round 2: DB + 价格百分比推算 + - Round 3: 最低可用策略标记强推 + 3轮全不过 → review_needed """ - passed, failures = validate_strategy(result) - - if not passed: - critical_issues = [f["id"] for f in failures if f["severity"] == "CRITICAL"] - high_issues = [f["id"] for f in failures if f["severity"] == "HIGH"] - - # --- 自动修复:对每项 CRITICAL 失败调用对应修复逻辑 --- - retry_needed = False - price = result.get("price", 0) - code_str = str(code) - - for gate_id in critical_issues: - if gate_id == "GATE_LOSS_EXISTS" and result.get("stop_loss", 0) <= 0: - # 止损缺失:调 technical_analysis 算弱支撑 - print(f" [AUTO-FIX] {name}({code}) 止损缺失 → 计算技术位", flush=True) + price = result.get("price", 0) or result.get("current", 0) or result.get("last_price", 0) + code_str = str(code) + import sqlite3 # 本函数多处使用 + + def _db_sector(): + """从 DB 取行业名""" + try: + _db = sqlite3.connect("/home/hmo/MoFin/data/mofin.db", timeout=5) + r = _db.execute("SELECT sector_name FROM stock_sectors WHERE code=?", (code_str,)).fetchone() + _db.close() + return r[0] if r else None + except: + return None + + def _fix_one(gate_id, round_num): + """对单个门禁执行修复。round_num越大修复越激进。""" + if gate_id == "GATE_LOSS_EXISTS" and (result.get("stop_loss") or 0) <= 0: + if round_num <= 2: + # Round 1-2: 技术分析算支撑 tech = ta.full_analysis(code) if tech and "support_resistance" in tech: sr = tech["support_resistance"] @@ -190,12 +195,16 @@ def enforce_strategy_quality(code, name, result): result["stop_loss"] = round(price * 0.95, 2) elif price > 0: result["stop_loss"] = round(price * 0.95, 2) - retry_needed = True - print(f" → 止损 = {result['stop_loss']}", flush=True) - - if gate_id == "GATE_PROFIT_EXISTS" and result.get("take_profit", 0) <= 0: - # 止盈缺失:调 technical_analysis 算阻力位 - print(f" [AUTO-FIX] {name}({code}) 止盈缺失 → 计算技术位", flush=True) + else: + # Round 3: 强制fallback + if price > 0: + result["stop_loss"] = round(price * 0.90, 2) # 更宽 + else: + result["stop_loss"] = 1 + print(f" R{round_num} 止损={result.get('stop_loss',0)}", flush=True) + + if gate_id == "GATE_PROFIT_EXISTS" and (result.get("take_profit") or 0) <= 0: + if round_num <= 2: tech = ta.full_analysis(code) if tech and "support_resistance" in tech: sr = tech["support_resistance"] @@ -209,17 +218,18 @@ def enforce_strategy_quality(code, name, result): result["take_profit"] = round(price * 1.08, 2) elif price > 0: result["take_profit"] = round(price * 1.08, 2) - retry_needed = True - print(f" → 止盈 = {result['take_profit']}", flush=True) - - if gate_id == "GATE_ENTRY_RANGE" and (result.get("entry_low", 0) >= result.get("entry_high", 0) or result.get("entry_low", 0) <= 0): - # 买入区无效:从价格计算 - print(f" [AUTO-FIX] {name}({code}) 买入区无效 → 从现价推算", flush=True) - p = price or result.get("current", 0) or result.get("last_price", 0) - if p <= 0: - p = 100 - sl = result.get("stop_loss", 0) - tp = result.get("take_profit", 0) + else: + if price > 0: + result["take_profit"] = round(price * 1.20, 2) # 更宽 + else: + result["take_profit"] = 2 + print(f" R{round_num} 止盈={result.get('take_profit',0)}", flush=True) + + if gate_id == "GATE_ENTRY_RANGE" and ((result.get("entry_low") or 0) >= (result.get("entry_high") or 0) or (result.get("entry_low") or 0) <= 0): + p = price or 100 + sl = result.get("stop_loss", 0) + tp = result.get("take_profit", 0) + if round_num <= 2: if sl > 0 and tp > 0 and sl < tp: result["entry_low"] = round(sl * 1.02, 2) result["entry_high"] = round(tp * 0.85, 2) @@ -229,95 +239,117 @@ def enforce_strategy_quality(code, name, result): else: result["entry_low"] = round(p * 0.93, 2) result["entry_high"] = round(p * 1.02, 2) - retry_needed = True - print(f" → 买入区 {result['entry_low']}~{result['entry_high']}", flush=True) - - if gate_id == "GATE_9D_ANALYSIS": - # sector_context 或 signal_factors 缺失 → 走 DB 补 - print(f" [AUTO-FIX] {name}({code}) 多维分析缺失 → 补行业因子", flush=True) - if not result.get("sector_context") or str(result.get("sector_context","")).strip() in ("neutral","","N/A","-"): - result["sector_context"] = f"{name}所属行业(待更新)" - try: - import sqlite3 - _db = sqlite3.connect("/home/hmo/MoFin/data/mofin.db", timeout=5) - _sec = _db.execute("SELECT sector_name FROM stock_sectors WHERE code=?", (code_str,)).fetchone() - _db.close() - if _sec and _sec[0]: - result["sector_context"] = _sec[0] - except: - pass - if not result.get("signal_factors") or (isinstance(result.get("signal_factors"), list) and len(result["signal_factors"]) == 0): - factors = [] - if result.get("timing_signal"): - factors.append(f"信号:{result['timing_signal']}") - if result.get("rr_ratio", 0) > 0: - factors.append(f"RR:{result['rr_ratio']}") - if not factors: - factors.append("自动填充") - result["signal_factors"] = factors - if not result.get("tech_snapshot") or not any(c in str(result.get("tech_snapshot","")) for c in "支撑阻力压强"): - sl = result.get("stop_loss", 0) - tp = result.get("take_profit", 0) - if sl > 0 and tp > 0: - result["tech_snapshot"] = f"自动:损{sl}盈{tp}" - elif price: - result["tech_snapshot"] = f"自动:价{price}" - retry_needed = True - - if retry_needed: - # 重检质量 - print(f" [AUTO-FIX] 修复完成 → 重检质量", flush=True) - repassed, new_failures = validate_strategy(result) - if repassed: - print(f" ✅ {name}({code}) 自动修复后通过质量门禁", flush=True) - # 记录 changelog - cl = result.setdefault("changelog", []) - cl.append({ - "time": datetime.now().strftime("%Y-%m-%d %H:%M"), - "event": f"质量门禁自动修复 ({', '.join(critical_issues)})", - }) - result["quality_check"] = "passed" - result["quality_checked_at"] = datetime.now().strftime("%Y-%m-%d %H:%M") - result["status"] = "active" - return True else: - # 修复后仍不过 → 退回 review_needed - remaining_critical = [f["id"] for f in new_failures if f["severity"] == "CRITICAL"] - print(f" ❌ {name}({code}) 自动修复后仍不通过 ({remaining_critical}) → review_needed", flush=True) + result["entry_low"] = round(p * 0.90, 2) + result["entry_high"] = round(p * 1.10, 2) + print(f" R{round_num} 买入区={result['entry_low']}~{result['entry_high']}", flush=True) + + if gate_id == "GATE_9D_ANALYSIS": + # 行业 + if not result.get("sector_context") or str(result.get("sector_context","")).strip() in ("neutral","","N/A","-"): + sec = _db_sector() + if sec: + result["sector_context"] = sec + elif round_num >= 2: + result["sector_context"] = f"自选(未分类)" + else: + result["sector_context"] = f"{name}所属行业(待补充)" + # signal_factors + if not result.get("signal_factors") or (isinstance(result.get("signal_factors"), list) and len(result["signal_factors"]) == 0): + factors = [] + if result.get("timing_signal"): + factors.append(f"信号:{result['timing_signal']}") + if result.get("rr_ratio", 0) > 0: + factors.append(f"RR:{result['rr_ratio']}") + if result.get("stop_loss", 0) > 0 and result.get("take_profit", 0) > 0: + factors.append(f"损{result['stop_loss']}盈{result['take_profit']}") + if not factors: + if round_num >= 2: + factors.append("自动填充") + else: + # Round 1: 留空等重检,不硬填 + pass + if factors: + result["signal_factors"] = factors + # tech_snapshot + if not result.get("tech_snapshot") or not any(c in str(result.get("tech_snapshot","")) for c in "支撑阻力压强"): + sl = result.get("stop_loss", 0) + tp = result.get("take_profit", 0) + if sl > 0 and tp > 0: + result["tech_snapshot"] = f"自动:损{sl}盈{tp}" + elif price: + result["tech_snapshot"] = f"自动:价{price}" + elif round_num >= 2: + result["tech_snapshot"] = "自动生成(未补全技术位)" + print(f" R{round_num} 9维分析: sector={result.get('sector_context','')[:20]} factors={result.get('signal_factors',[])}", flush=True) + + # 循环重试 + MAX_RETRIES = 3 + passed, failures = validate_strategy(result) + retry_count = 0 + + for _retry_num in range(1, MAX_RETRIES + 1): + if passed: + break - # 标记质量失败(原始失败或修复后仍失败) + critical_issues = [f["id"] for f in failures if f["severity"] == "CRITICAL"] + if not critical_issues: + # 没有CRITICAL了,只有HIGH/MEDIUM → 可以放行 + passed = True + break + + retry_count = _retry_num + print(f" [RETRY {retry_count}/{MAX_RETRIES}] {name}({code}) → 修复: {critical_issues}", flush=True) + + for gate_id in critical_issues: + _fix_one(gate_id, retry_count) + + # 重检 + passed, failures = validate_strategy(result) + + # --- 最终结果 --- + if passed: + print(f" ✅ {name}({code}) 质量门禁通过 ({retry_count}轮重试)", flush=True) + result["quality_check"] = "passed" + result["quality_checked_at"] = datetime.now().strftime("%Y-%m-%d %H:%M") + + # HIGH 级别警告(已通过但仍有非CRITICAL失败) + high_fails = [f for f in failures if f["severity"] == "HIGH"] + if high_fails: + result["quality_check"] = "warning" + result["quality_issues"] = {"high": [f["id"] for f in high_fails]} + print(f" ⚠️ {name}({code}) 有{len(high_fails)}条HIGH警告", flush=True) + + # 记录 changelog + if "critical_issues" in dir(): + cl = result.setdefault("changelog", []) + cl.append({ + "time": datetime.now().strftime("%Y-%m-%d %H:%M"), + "event": f"质量门禁通过 (重试{retry_count}轮)", + }) + + result["status"] = "active" + return True + else: + # 3轮全不过 → review_needed + remaining_critical = [f["id"] for f in failures if f["severity"] == "CRITICAL"] result["quality_check"] = "failed" result["quality_issues"] = { - "critical": critical_issues, - "high": high_issues, + "critical": remaining_critical, "all": [f["id"] for f in failures], } result["quality_checked_at"] = datetime.now().strftime("%Y-%m-%d %H:%M") + result["status"] = "review_needed" + result["timing_signal"] = "信号不充分" cl = result.setdefault("changelog", []) cl.append({ "time": datetime.now().strftime("%Y-%m-%d %H:%M"), - "event": f"质量门禁拒绝 (failed: {critical_issues})", + "event": f"质量门禁3轮全拒 → review_needed ({remaining_critical})", }) - result["status"] = "review_needed" - result["timing_signal"] = "信号不充分" - - print(f" 🚫 {name}({code}) 质量门禁未通过 ({critical_issues}) → 已标记 review_needed", flush=True) + print(f" 🚫 {name}({code}) 3轮修复后仍有 {remaining_critical} → review_needed", flush=True) return False - - # HIGH 级别失败 → 标记但不拦截 - high_fails = [f for f in failures if f["severity"] == "HIGH"] - if high_fails: - result["quality_check"] = "warning" - result["quality_issues"] = {"high": [f["id"] for f in high_fails]} - result["quality_checked_at"] = datetime.now().strftime("%Y-%m-%d %H:%M") - print(f" ⚠️ {name}({code}) 有{len(high_fails)}条HIGH警告,标记warning但已写入", flush=True) - else: - result["quality_check"] = "passed" - result["quality_checked_at"] = datetime.now().strftime("%Y-%m-%d %H:%M") - - return True def is_hk_stock(code):