From 5ee934593be0d88d81d97760803e6e346e8592a7 Mon Sep 17 00:00:00 2001 From: hmo Date: Tue, 11 Aug 2026 06:52:53 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20P0=E6=B8=85=E7=90=86=E2=80=94?= =?UTF-8?q?=E2=80=94=E5=BD=92=E6=A1=A3=E6=A0=B9=E7=9B=AE=E5=BD=95market=5F?= =?UTF-8?q?watch=E6=97=A7=E7=89=88=E5=88=86=E5=8F=89/get=5Frealtime=5Fpric?= =?UTF-8?q?es=E6=AD=BB=E4=BB=A3=E7=A0=81,=E5=88=A0=E9=99=A4promote=5Fcandi?= =?UTF-8?q?dates=E5=A4=87=E4=BB=BD=E6=AE=8B=E7=95=99(=E6=B6=88=E9=99=A4?= =?UTF-8?q?=E4=BB=A3=E7=A0=81=E5=A4=9A=E7=89=88=E6=9C=AC)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../get_realtime_prices.py | 0 .../20260811-cron-cleanup/market_watch.py | 0 .../promote_candidates.py | 192 ------------------ 3 files changed, 192 deletions(-) rename {deploy/profile-scripts => archive/20260811-cron-cleanup}/get_realtime_prices.py (100%) rename market_watch.py => archive/20260811-cron-cleanup/market_watch.py (100%) delete mode 100644 docs/backup/pipeline_fix_20260810_133227/promote_candidates.py diff --git a/deploy/profile-scripts/get_realtime_prices.py b/archive/20260811-cron-cleanup/get_realtime_prices.py similarity index 100% rename from deploy/profile-scripts/get_realtime_prices.py rename to archive/20260811-cron-cleanup/get_realtime_prices.py diff --git a/market_watch.py b/archive/20260811-cron-cleanup/market_watch.py similarity index 100% rename from market_watch.py rename to archive/20260811-cron-cleanup/market_watch.py diff --git a/docs/backup/pipeline_fix_20260810_133227/promote_candidates.py b/docs/backup/pipeline_fix_20260810_133227/promote_candidates.py deleted file mode 100644 index 1c8aa32a..00000000 --- a/docs/backup/pipeline_fix_20260810_133227/promote_candidates.py +++ /dev/null @@ -1,192 +0,0 @@ -#!/usr/bin/env python3 -"""promote_candidates.py — 自动提拔候选股入自选 - -从 candidates 表读未提拔的候选,评估后自动加入 holding_strategies。 -""" -import sys, json, sqlite3 -from pathlib import Path -from datetime import datetime - -DB_PATH = Path("/home/hmo/MoFin/data/mofin.db") - -def main(): - conn = sqlite3.connect(str(DB_PATH), timeout=30) - conn.execute("PRAGMA busy_timeout=30000") - conn.row_factory = sqlite3.Row - - # 读未提拔候选(按评分降序) - # 2026-07-24 老爸"优中选优":score>=7 才可入候选评估(原 4 = 91%通过率等于没门槛) - rows = conn.execute(""" - SELECT c.code, c.name, c.score_final, c.entry_range, c.stop_loss, c.target - FROM candidates c - WHERE (c.promoted IS NULL OR c.promoted = 0) - AND (c.dropped IS NULL OR c.dropped = 0) - AND c.score_final >= 7 - ORDER BY c.score_final DESC - """).fetchall() - - if not rows: - print("[PROMOTE] 无待提拔候选") - conn.close() - return - - promoted = 0 - for r in rows: - code = str(r[0]) - name = r[1] or code - score = r[2] or 0 - entry_range = r[3] or "" - sl = r[4] or 0 - tp = r[5] or 0 - - # 解析 entry_range - el, eh = 0, 0 - if "~" in entry_range: - parts = entry_range.split("~") - try: - el = float(parts[0]) - eh = float(parts[1]) - except: pass - - # 查是否已在 holding_strategies - exists = conn.execute( - "SELECT id FROM holding_strategies WHERE code=? AND status='active'", - (code,) - ).fetchone() - if exists: - conn.execute("UPDATE candidates SET promoted=1 WHERE code=?", (code,)) - print(f" ⏭ {code} {name} 已在自选中,标记promoted") - continue - - # 验证实时价格:无有效价格的候选股不入自选(防假数据污染) - _price = 0.0 - try: - import subprocess, json as _jj - _r = subprocess.run(["python3", "/home/hmo/.hermes/profiles/position-analyst/scripts/stock_quote.py", code], - capture_output=True, text=True, timeout=10) - _q = _jj.loads(_r.stdout) - _price = float(_q.get("price", 0)) - if _price <= 0: - print(f" ⏭ {code} {name} 无实时价格,跳过") - continue - except Exception as _e: - print(f" ⏭ {code} {name} 价格获取失败({_e}),跳过") - continue - - # ── 优中选优闸(2026-07-24 老爸):ST排除 + 技术位锚定参数 + RR>=2.0 ── - if "ST" in (name or "").upper(): - print(f" ⏭ {code} {name} ST股,不入自选") - continue - # 技术位锚定:不信扫描器拍的 entry/sl/tp,用 ta.full_analysis 的确定性技术位重定 - try: - import sys as _s - if '/home/hmo/MoFin/deploy/profile-scripts' not in _s.path: - _s.path.insert(0, '/home/hmo/MoFin/deploy/profile-scripts') - import technical_analysis as _ta - _ta_r = _ta.full_analysis(code) - _sr = (_ta_r or {}).get("support_resistance", {}) or {} - _ws, _ss = _sr.get("weak_support"), _sr.get("strong_support") - _wr, _sr2 = _sr.get("weak_resist"), _sr.get("strong_resist") - if _ws and _wr and _price > 0: - el = round(_ws * 0.995, 2) # 区下沿贴弱撑 - eh = round(min(_wr, _price * 1.05), 2) # 区上沿取弱压(且不超现价5%) - sl = round((_ss or _ws) * 0.985, 2) # 止损=强撑下1.5%(无强撑用弱撑) - tp = round(_sr2 or _wr * 1.15, 2) # 止盈=强压(无强压则弱压+15%) - except Exception as _te: - print(f" ⚠️ {code} 技术位锚定失败({_te}),用扫描器参数", flush=True) - if el > 0 and eh > el and sl > 0 and tp > 0: - _mid = (el + eh) / 2 - _rr = (tp - _mid) / (_mid - sl) if (_mid - sl) > 0 else 0 - if _rr < 2.0: - print(f" ⏭ {code} {name} RR={_rr:.2f}<2.0,不入自选") - continue - else: - print(f" ⏭ {code} {name} 锚定后参数无效(区{el}~{eh} 损{sl} 盈{tp}),跳过") - continue - - # 构建策略(2026-07-24 老爸:提拔不直接给"买入"——先入观察,12维确认后再升) - now = datetime.now().strftime("%Y-%m-%d %H:%M:%S") - timing_signal = "关注" - price_est = (el + eh) / 2 if el > 0 and eh > 0 else 0 - reason_text = [] - if el > 0: reason_text.append(f"买{el}~{eh}") - if sl > 0: reason_text.append(f"损{sl}") - if tp > 0: reason_text.append(f"盈{tp}") - if sl > 0 and tp > 0 and price_est > 0: - rr = (tp - price_est) / (price_est - sl) if (price_est - sl) > 0 else 0 - reason_text.append(f"RR{rr:.1f}") - reason_text.append(f"评分{score}") - action = " | ".join(reason_text) if reason_text else f"市场扫描发现(评分{score})" - - # ── 容量闸:自选上限60只,超出时删综合分最弱的(RR低优先)── - MAX_WATCH = 60 - wl_count = conn.execute( - "SELECT COUNT(*) FROM holding_strategies WHERE status='active' AND decision_type='自选策略'").fetchone()[0] - if wl_count >= MAX_WATCH: - weakest = conn.execute(""" - SELECT code, name, COALESCE(rr_ratio,0) as rr FROM holding_strategies - WHERE status='active' AND decision_type='自选策略' - ORDER BY COALESCE(rr_ratio,0) ASC, updated_at ASC LIMIT 1""").fetchone() - if weakest and (weakest[2] or 0) < 2.0: - conn.execute( - "INSERT INTO watchlist_log (code, name, event, reason, old_signal, new_signal, price) " - "VALUES (?,?,?,?,?,?,?)", - (weakest[0], weakest[1] or "", "exit", f"容量{MAX_WATCH}淘汰为新标的{code}腾位", "", "已删除", 0)) - conn.execute( - "DELETE FROM holding_strategies WHERE code=? AND status='active' AND decision_type='自选策略'", - (weakest[0],)) - print(f" 🔴 容量淘汰: {weakest[0]} {weakest[1]} (RR={weakest[2]})", flush=True) - else: - print(f" ⏭ 自选已满{MAX_WATCH}且现有标的均RR>=2.0,{code}暂缓提拔", flush=True) - continue - - cur = conn.execute(""" - INSERT OR IGNORE INTO holding_strategies - (code, name, price, entry_low, entry_high, stop_loss, take_profit, - timing_signal, action, decision_type, strategy_type, status, - rr_ratio, stock_category, created_at, updated_at, - sector_context, quality_check) - VALUES (?,?,?,?,?,?,?,?,?,'自选策略','scan', - 'active',0,'关注',?,?,'', 'pending') - """, (code, name, 0, el, eh, sl, tp, timing_signal, action, now, now)) - newly_added = cur.rowcount > 0 - - conn.execute("UPDATE candidates SET promoted=1 WHERE code=?", (code,)) - if newly_added: - promoted += 1 - print(f" ✅ {code} {name} 评分{score} → 已加入自选({timing_signal})", flush=True) - else: - print(f" ⏭ {code} {name} 已在自选策略中,标记promoted", flush=True) - - # 触发全量重评(生成完整9维策略)——仅新插入的股票需要 - if newly_added: - try: - import subprocess as _sp - r = _sp.run(["python3", "/home/hmo/MoFin/deploy/profile-scripts/per_stock_reassess.py", code], - capture_output=True, text=True, timeout=480) - if r.returncode == 0: - print(f" 重评完成", flush=True) - else: - print(f" 重评失败: {r.stderr.strip()[:100]}", flush=True) - except Exception as e: - print(f" 重评异常: {e}", flush=True) - - conn.commit() - print(f"\n[PROMOTE] 本次提拔{promoted}只", flush=True) - - # 推XMPP - if promoted > 0: - try: - import urllib.request - msg = f"📈 自动提拔{promoted}只候选入自选" - payload = json.dumps({"to": "hmo@yoin.fun", "body": msg, "type": "chat"}).encode() - req = urllib.request.Request("http://127.0.0.1:5805/", data=payload, - headers={"Content-Type": "application/json"}) - urllib.request.urlopen(req, timeout=5) - except Exception: - pass - - conn.close() - -if __name__ == "__main__": - main()