质量门禁自动修复 — CRITICAL失败不再退回, 调技术分析补全
enforce_strategy_quality() 新增自动修复层: GATE_LOSS_EXISTS → 调 ta.full_analysis() 算弱支撑 GATE_PROFIT_EXISTS → 调 ta.full_analysis() 算阻力位 GATE_ENTRY_RANGE → 从止损/止盈或现价推算 GATE_9D_ANALYSIS → 从 DB 补行业、自动填因子 修复后重检质量, 通过则正常写入, 不进 review_needed Dad要求: 不是挡, 是打回重评直到有正确结果
This commit is contained in:
Binary file not shown.
Binary file not shown.
Binary file not shown.
+35
-35
@@ -5,9 +5,9 @@
|
|||||||
"name": "中际旭创",
|
"name": "中际旭创",
|
||||||
"shares": 100,
|
"shares": 100,
|
||||||
"cost": 1316.53,
|
"cost": 1316.53,
|
||||||
"price": 1136.12,
|
"price": 1141.0,
|
||||||
"market_value": 113604.0,
|
"market_value": 113604.0,
|
||||||
"change_pct": -7.12,
|
"change_pct": -6.72,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 15.27,
|
"position_pct": 15.27,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -17,9 +17,9 @@
|
|||||||
"name": "长飞光纤光缆",
|
"name": "长飞光纤光缆",
|
||||||
"shares": 500,
|
"shares": 500,
|
||||||
"cost": 263.72,
|
"cost": 263.72,
|
||||||
"price": 178.6,
|
"price": 178.08,
|
||||||
"market_value": 89300.0,
|
"market_value": 89300.0,
|
||||||
"change_pct": -19.342,
|
"change_pct": -19.577,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 13.47,
|
"position_pct": 13.47,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -29,9 +29,9 @@
|
|||||||
"name": "丘钛科技",
|
"name": "丘钛科技",
|
||||||
"shares": 11000,
|
"shares": 11000,
|
||||||
"cost": 13.47,
|
"cost": 13.47,
|
||||||
"price": 5.98,
|
"price": 6.02,
|
||||||
"market_value": 65890.0,
|
"market_value": 65890.0,
|
||||||
"change_pct": 0.583,
|
"change_pct": 0.0,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 7.97,
|
"position_pct": 7.97,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -41,9 +41,9 @@
|
|||||||
"name": "紫金矿业",
|
"name": "紫金矿业",
|
||||||
"shares": 2400,
|
"shares": 2400,
|
||||||
"cost": 39.89,
|
"cost": 39.89,
|
||||||
"price": 26.34,
|
"price": 26.35,
|
||||||
"market_value": 63048.0,
|
"market_value": 63048.0,
|
||||||
"change_pct": 4.9,
|
"change_pct": 4.94,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 7.34,
|
"position_pct": 7.34,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -53,9 +53,9 @@
|
|||||||
"name": "海博思创",
|
"name": "海博思创",
|
||||||
"shares": 200,
|
"shares": 200,
|
||||||
"cost": 266.95,
|
"cost": 266.95,
|
||||||
"price": 259.0,
|
"price": 257.9,
|
||||||
"market_value": 51776.0,
|
"market_value": 51776.0,
|
||||||
"change_pct": -1.48,
|
"change_pct": -1.9,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 6.31,
|
"position_pct": 6.31,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -65,9 +65,9 @@
|
|||||||
"name": "中芯国际",
|
"name": "中芯国际",
|
||||||
"shares": 300,
|
"shares": 300,
|
||||||
"cost": 126.07,
|
"cost": 126.07,
|
||||||
"price": 146.33,
|
"price": 146.57,
|
||||||
"market_value": 44112.0,
|
"market_value": 44112.0,
|
||||||
"change_pct": -5.28,
|
"change_pct": -5.12,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 5.44,
|
"position_pct": 5.44,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -77,9 +77,9 @@
|
|||||||
"name": "建滔积层板",
|
"name": "建滔积层板",
|
||||||
"shares": 500,
|
"shares": 500,
|
||||||
"cost": 88.23,
|
"cost": 88.23,
|
||||||
"price": 73.52,
|
"price": 73.35,
|
||||||
"market_value": 36415.0,
|
"market_value": 36415.0,
|
||||||
"change_pct": -14.473,
|
"change_pct": -14.675,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 5.28,
|
"position_pct": 5.28,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -89,9 +89,9 @@
|
|||||||
"name": "华恒生物",
|
"name": "华恒生物",
|
||||||
"shares": 2800,
|
"shares": 2800,
|
||||||
"cost": 21.51,
|
"cost": 21.51,
|
||||||
"price": 17.18,
|
"price": 17.21,
|
||||||
"market_value": 48244.0,
|
"market_value": 48244.0,
|
||||||
"change_pct": 4.95,
|
"change_pct": 5.13,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 5.25,
|
"position_pct": 5.25,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -101,9 +101,9 @@
|
|||||||
"name": "宁德时代",
|
"name": "宁德时代",
|
||||||
"shares": 100,
|
"shares": 100,
|
||||||
"cost": 401.78,
|
"cost": 401.78,
|
||||||
"price": 386.01,
|
"price": 386.64,
|
||||||
"market_value": 38495.0,
|
"market_value": 38495.0,
|
||||||
"change_pct": 0.57,
|
"change_pct": 0.73,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 4.64,
|
"position_pct": 4.64,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -113,9 +113,9 @@
|
|||||||
"name": "比亚迪股份",
|
"name": "比亚迪股份",
|
||||||
"shares": 600,
|
"shares": 600,
|
||||||
"cost": 104.87,
|
"cost": 104.87,
|
||||||
"price": 68.36,
|
"price": 68.19,
|
||||||
"market_value": 41070.0,
|
"market_value": 41070.0,
|
||||||
"change_pct": 8.834,
|
"change_pct": 8.558,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 4.62,
|
"position_pct": 4.62,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -127,7 +127,7 @@
|
|||||||
"cost": 4.67,
|
"cost": 4.67,
|
||||||
"price": 1.93,
|
"price": 1.93,
|
||||||
"market_value": 38021.0,
|
"market_value": 38021.0,
|
||||||
"change_pct": 5.19,
|
"change_pct": 5.189,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 4.6,
|
"position_pct": 4.6,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -137,9 +137,9 @@
|
|||||||
"name": "腾讯",
|
"name": "腾讯",
|
||||||
"shares": 100,
|
"shares": 100,
|
||||||
"cost": null,
|
"cost": null,
|
||||||
"price": 378.36,
|
"price": 378.01,
|
||||||
"market_value": 37784.0,
|
"market_value": 37784.0,
|
||||||
"change_pct": 1.536,
|
"change_pct": 1.443,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": null,
|
"position_pct": null,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -149,9 +149,9 @@
|
|||||||
"name": "中芯国际",
|
"name": "中芯国际",
|
||||||
"shares": 500,
|
"shares": 500,
|
||||||
"cost": 75.94,
|
"cost": 75.94,
|
||||||
"price": 69.62,
|
"price": 69.06,
|
||||||
"market_value": 34635.0,
|
"market_value": 34635.0,
|
||||||
"change_pct": -10.179,
|
"change_pct": -10.906,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 4.2,
|
"position_pct": 4.2,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -161,9 +161,9 @@
|
|||||||
"name": "长芯博创",
|
"name": "长芯博创",
|
||||||
"shares": 100,
|
"shares": 100,
|
||||||
"cost": 231.46,
|
"cost": 231.46,
|
||||||
"price": 226.07,
|
"price": 227.05,
|
||||||
"market_value": 22592.0,
|
"market_value": 22592.0,
|
||||||
"change_pct": -11.0,
|
"change_pct": -10.61,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 3.2,
|
"position_pct": 3.2,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -175,7 +175,7 @@
|
|||||||
"cost": 12.19,
|
"cost": 12.19,
|
||||||
"price": 8.45,
|
"price": 8.45,
|
||||||
"market_value": 20280.0,
|
"market_value": 20280.0,
|
||||||
"change_pct": 2.14,
|
"change_pct": 2.18,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 2.45,
|
"position_pct": 2.45,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -185,9 +185,9 @@
|
|||||||
"name": "中科电气",
|
"name": "中科电气",
|
||||||
"shares": 1400,
|
"shares": 1400,
|
||||||
"cost": 22.29,
|
"cost": 22.29,
|
||||||
"price": 14.31,
|
"price": 14.32,
|
||||||
"market_value": 20062.0,
|
"market_value": 20062.0,
|
||||||
"change_pct": -0.9,
|
"change_pct": -0.83,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 2.42,
|
"position_pct": 2.42,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
@@ -221,20 +221,20 @@
|
|||||||
"name": "中国神华",
|
"name": "中国神华",
|
||||||
"shares": 500,
|
"shares": 500,
|
||||||
"cost": 45.89,
|
"cost": 45.89,
|
||||||
"price": 34.13,
|
"price": 34.14,
|
||||||
"market_value": 17115.0,
|
"market_value": 17115.0,
|
||||||
"change_pct": 1.092,
|
"change_pct": 1.143,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"position_pct": 2.14,
|
"position_pct": 2.14,
|
||||||
"_currency": "CNY"
|
"_currency": "CNY"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"total_assets": 903265.0,
|
"total_assets": 903571.0,
|
||||||
"total_mv": 822789.0,
|
"total_mv": 823095.0,
|
||||||
"stock_value": null,
|
"stock_value": null,
|
||||||
"cash": 80476.0,
|
"cash": 80476.0,
|
||||||
"frozen_cash": 0.0,
|
"frozen_cash": 0.0,
|
||||||
"position_pct": 91.09,
|
"position_pct": 91.09,
|
||||||
"currency": "CNY",
|
"currency": "CNY",
|
||||||
"updated_at": "2026-07-02 13:50"
|
"updated_at": "2026-07-02 13:56"
|
||||||
}
|
}
|
||||||
+122
-8
@@ -158,10 +158,9 @@ def enforce_strategy_quality(code, name, result):
|
|||||||
"""策略写入前的强制质量门禁
|
"""策略写入前的强制质量门禁
|
||||||
|
|
||||||
对 result 执行 validate_strategy,不通过则:
|
对 result 执行 validate_strategy,不通过则:
|
||||||
- 设置 quality_check: failed + 失败原因列表
|
- CRITICAL 失败 → 自动调取技术分析/行业数据修复
|
||||||
- 自动在 changelog 记录
|
- 修复后重检 → 通过则写入,再不通过标记 review_needed
|
||||||
- 返回 False,调用方不写入
|
- HIGH 失败 → 标记 warning 但放行
|
||||||
- 返回 True 但 result 被修改(标记了 failed,未被写入)
|
|
||||||
"""
|
"""
|
||||||
passed, failures = validate_strategy(result)
|
passed, failures = validate_strategy(result)
|
||||||
|
|
||||||
@@ -169,7 +168,124 @@ def enforce_strategy_quality(code, name, result):
|
|||||||
critical_issues = [f["id"] for f in failures if f["severity"] == "CRITICAL"]
|
critical_issues = [f["id"] for f in failures if f["severity"] == "CRITICAL"]
|
||||||
high_issues = [f["id"] for f in failures if f["severity"] == "HIGH"]
|
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)
|
||||||
|
tech = ta.full_analysis(code)
|
||||||
|
if tech and "support_resistance" in tech:
|
||||||
|
sr = tech["support_resistance"]
|
||||||
|
ws = sr.get("weak_support")
|
||||||
|
ss = sr.get("strong_support")
|
||||||
|
if ws and ws > 0:
|
||||||
|
result["stop_loss"] = round(ws, 2)
|
||||||
|
elif ss and ss > 0:
|
||||||
|
result["stop_loss"] = round(ss, 2)
|
||||||
|
elif price > 0:
|
||||||
|
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)
|
||||||
|
tech = ta.full_analysis(code)
|
||||||
|
if tech and "support_resistance" in tech:
|
||||||
|
sr = tech["support_resistance"]
|
||||||
|
wr = sr.get("weak_resist")
|
||||||
|
sr_resist = sr.get("strong_resist")
|
||||||
|
if sr_resist and sr_resist > 0:
|
||||||
|
result["take_profit"] = round(sr_resist, 2)
|
||||||
|
elif wr and wr > 0:
|
||||||
|
result["take_profit"] = round(wr, 2)
|
||||||
|
elif price > 0:
|
||||||
|
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)
|
||||||
|
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)
|
||||||
|
if result["entry_low"] >= result["entry_high"]:
|
||||||
|
result["entry_low"] = round(p * 0.95, 2)
|
||||||
|
result["entry_high"] = round(p * 0.99, 2)
|
||||||
|
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["quality_check"] = "failed"
|
result["quality_check"] = "failed"
|
||||||
result["quality_issues"] = {
|
result["quality_issues"] = {
|
||||||
"critical": critical_issues,
|
"critical": critical_issues,
|
||||||
@@ -178,21 +294,19 @@ def enforce_strategy_quality(code, name, result):
|
|||||||
}
|
}
|
||||||
result["quality_checked_at"] = datetime.now().strftime("%Y-%m-%d %H:%M")
|
result["quality_checked_at"] = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||||
|
|
||||||
# 记录 changelog
|
|
||||||
cl = result.setdefault("changelog", [])
|
cl = result.setdefault("changelog", [])
|
||||||
cl.append({
|
cl.append({
|
||||||
"time": datetime.now().strftime("%Y-%m-%d %H:%M"),
|
"time": datetime.now().strftime("%Y-%m-%d %H:%M"),
|
||||||
"event": f"质量门禁拒绝 (failed: {critical_issues})",
|
"event": f"质量门禁拒绝 (failed: {critical_issues})",
|
||||||
})
|
})
|
||||||
|
|
||||||
# 信号降级
|
|
||||||
result["status"] = "review_needed"
|
result["status"] = "review_needed"
|
||||||
result["timing_signal"] = "信号不充分"
|
result["timing_signal"] = "信号不充分"
|
||||||
|
|
||||||
print(f" 🚫 {name}({code}) 质量门禁未通过 ({critical_issues}) → 已标记 review_needed", flush=True)
|
print(f" 🚫 {name}({code}) 质量门禁未通过 ({critical_issues}) → 已标记 review_needed", flush=True)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# 如果有 HIGH 级别失败 → 标记但不拦截写入
|
# HIGH 级别失败 → 标记但不拦截
|
||||||
high_fails = [f for f in failures if f["severity"] == "HIGH"]
|
high_fails = [f for f in failures if f["severity"] == "HIGH"]
|
||||||
if high_fails:
|
if high_fails:
|
||||||
result["quality_check"] = "warning"
|
result["quality_check"] = "warning"
|
||||||
|
|||||||
Reference in New Issue
Block a user