fix: stale_detector零标记漏洞(买入区上沿~+20%未覆盖)
This commit is contained in:
+28
-27
@@ -1,7 +1,7 @@
|
||||
#!/usr/bin/env python3
|
||||
"""stale_detector.py — 检查所有策略,标记价格偏离/过期的策略
|
||||
|
||||
读取 holding_strategies + watchlist_stocks 的DB双源数据。
|
||||
读取 holding_strategies + 自选策略的DB双源数据。
|
||||
可被 cron no_agent 模式调用:stdout 注入到后续 LLM 分析。
|
||||
|
||||
输出格式:
|
||||
@@ -73,14 +73,15 @@ def main():
|
||||
EXCLUDED_STATUSES = ("closed", "inactive")
|
||||
to_check = [d for d in decisions_list if (d.get("entry_low") is not None or d.get("entry_high") is not None) and d.get("status") not in EXCLUDED_STATUSES]
|
||||
|
||||
# ----- 合并 DB watchlist_stocks 自选股 -----
|
||||
# ----- 补充自选(从 holding_strategies 读取,watchlist_stocks 已废弃) -----
|
||||
try:
|
||||
import sqlite3
|
||||
db = sqlite3.connect('/home/hmo/MoFin/data/mofin.db')
|
||||
db.row_factory = sqlite3.Row
|
||||
wl_rows = db.execute(
|
||||
"SELECT code, name, price, entry_low, entry_high, stop_loss, analysis_json "
|
||||
"FROM watchlist_stocks WHERE is_active=1 AND entry_low IS NOT NULL AND entry_high IS NOT NULL"
|
||||
"SELECT code, name, entry_low, entry_high, stop_loss, take_profit, rr_ratio, timing_signal, action "
|
||||
"FROM holding_strategies WHERE status='active' AND decision_type='自选策略' "
|
||||
"AND entry_low IS NOT NULL AND entry_high IS NOT NULL"
|
||||
).fetchall()
|
||||
db.close()
|
||||
existing_codes = {d["code"] for d in to_check}
|
||||
@@ -92,15 +93,8 @@ def main():
|
||||
entry_high = row["entry_high"]
|
||||
if not entry_low or not entry_high or entry_low <= 0:
|
||||
continue
|
||||
analysis = {}
|
||||
aj = row["analysis_json"]
|
||||
if aj:
|
||||
try:
|
||||
analysis = json.loads(aj)
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
action = analysis.get("action", "") if isinstance(analysis, dict) else ""
|
||||
timing_signal = analysis.get("timing_signal", "买入") if isinstance(analysis, dict) else "买入"
|
||||
action = row["action"] or ""
|
||||
timing_signal = row["timing_signal"] or "买入"
|
||||
wl_entry = {
|
||||
"code": code,
|
||||
"name": row["name"] or code,
|
||||
@@ -119,19 +113,20 @@ def main():
|
||||
print("[SILENT] 无需要检查的策略")
|
||||
return 0
|
||||
|
||||
# ----- 自选股买入区偏离自动重评 (2026-07-07 fix: 不只标记, 直接触发) -----
|
||||
# ----- 自选股买入区偏离自动重评 (从 holding_strategies 读,watchlist_stocks 已废弃) -----
|
||||
try:
|
||||
import subprocess, sqlite3
|
||||
db = sqlite3.connect('/home/hmo/MoFin/data/mofin.db')
|
||||
db.row_factory = sqlite3.Row
|
||||
wl_stocks = db.execute(
|
||||
"SELECT code, name, price, entry_low, entry_high, analysis_json "
|
||||
"FROM watchlist_stocks WHERE is_active=1 AND entry_low IS NOT NULL"
|
||||
"SELECT code, name, entry_low, entry_high "
|
||||
"FROM holding_strategies WHERE status='active' AND decision_type='自选策略' "
|
||||
"AND entry_low IS NOT NULL AND entry_high IS NOT NULL AND entry_low > 0"
|
||||
).fetchall()
|
||||
db.close()
|
||||
reassess_scripts = []
|
||||
for ws in wl_stocks:
|
||||
code, name, wl_price, wl_el, wl_eh, wl_aj = ws
|
||||
code, name, wl_el, wl_eh = ws
|
||||
if not wl_el or not wl_el or wl_el <= 0:
|
||||
continue
|
||||
center = (wl_el + wl_eh) / 2
|
||||
@@ -168,12 +163,13 @@ def main():
|
||||
if not isinstance(decisions_list, list):
|
||||
decisions_list = decisions_list.get("decisions", []) if isinstance(decisions_list, dict) else []
|
||||
to_check = [d for d in decisions_list if (d.get("entry_low") is not None or d.get("entry_high") is not None) and d.get("status") not in EXCLUDED_STATUSES]
|
||||
# 重新合并watchlist_stocks
|
||||
# 重新合并自选(从 holding_strategies 读)
|
||||
db2 = sqlite3.connect('/home/hmo/MoFin/data/mofin.db')
|
||||
db2.row_factory = sqlite3.Row
|
||||
wl_rows2 = db2.execute(
|
||||
"SELECT code, name, price, entry_low, entry_high, stop_loss, analysis_json "
|
||||
"FROM watchlist_stocks WHERE is_active=1 AND entry_low IS NOT NULL AND entry_high IS NOT NULL"
|
||||
"SELECT code, name, entry_low, entry_high, stop_loss, take_profit, rr_ratio, timing_signal, action "
|
||||
"FROM holding_strategies WHERE status='active' AND decision_type='自选策略' "
|
||||
"AND entry_low IS NOT NULL AND entry_high IS NOT NULL AND entry_low > 0"
|
||||
).fetchall()
|
||||
db2.close()
|
||||
existing_codes2 = {d["code"] for d in to_check}
|
||||
@@ -185,13 +181,8 @@ def main():
|
||||
entry_high = row["entry_high"]
|
||||
if not entry_low or not entry_high or entry_low <= 0:
|
||||
continue
|
||||
analysis = {}
|
||||
try:
|
||||
analysis = json.loads(row["analysis_json"]) if row["analysis_json"] and row["analysis_json"] != "null" else {}
|
||||
except (json.JSONDecodeError, TypeError):
|
||||
pass
|
||||
action = analysis.get("action", "") if isinstance(analysis, dict) else ""
|
||||
timing_signal = analysis.get("timing_signal", "买入") if isinstance(analysis, dict) else "买入"
|
||||
action = row["action"] or ""
|
||||
timing_signal = row["timing_signal"] or "买入"
|
||||
wl_entry = {
|
||||
"code": code,
|
||||
"name": row["name"] or code,
|
||||
@@ -253,6 +244,12 @@ def main():
|
||||
tag = "[自选]" if is_wl else "[持仓]"
|
||||
|
||||
# -- 偏离 --
|
||||
if is_wl and not issues and not flags:
|
||||
# 自选在买入区上沿与20%之间(零标记漏洞):标记为小幅偏离
|
||||
if el and eh and price > eh:
|
||||
flags.append("[WL_DRIFT]")
|
||||
flags.append("[STRATEGY_STALE]")
|
||||
issues.append(f"[STRATEGY_STALE] 价{price:.2f}超买入区上沿+{((price/eh)-1)*100:.1f}%,买入区需重评")
|
||||
if is_wl and el and eh:
|
||||
# 读取 timing_signal 判断策略有效性(timing_signal 字段优先,fallback to action)
|
||||
current_str = d.get("current", "") or ""
|
||||
@@ -293,6 +290,10 @@ def main():
|
||||
flags.append("[WL_DRIFT]")
|
||||
flags.append("[STRATEGY_STALE]")
|
||||
issues.append(f"[STRATEGY_STALE] 价{price:.2f}高出买入区+{((price/eh)-1)*100:.0f}%,买入区需重评")
|
||||
elif price > eh:
|
||||
flags.append("[WL_DRIFT]")
|
||||
flags.append("[STRATEGY_STALE]")
|
||||
issues.append(f"[STRATEGY_STALE] 价{price:.2f}超买入区上沿+{((price/eh)-1)*100:.1f}%,买入区需重评")
|
||||
elif not is_wl and eh:
|
||||
dp = (price / eh - 1) * 100
|
||||
if dp > 35:
|
||||
|
||||
Reference in New Issue
Block a user