From 6eab2148eba1cc8696f954aef631bf29a6558a2d Mon Sep 17 00:00:00 2001 From: hmo Date: Thu, 23 Jul 2026 14:23:06 +0800 Subject: [PATCH] =?UTF-8?q?fix(audit):=20candidates=20UPSERT=E4=BF=9D?= =?UTF-8?q?=E8=AE=A1=E7=AE=97=E5=88=97+market=5Fscanner=20INSERT=E5=AF=B9?= =?UTF-8?q?=E9=BD=90=E8=A1=A8=E7=BB=93=E6=9E=84+=E6=8B=86=E9=99=A4sync=5Fd?= =?UTF-8?q?ecisions=5Fto=5Fdb=E5=9C=B0=E9=9B=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 举一反三审计结果: - accumulation_scanner: INSERT OR REPLACE整行替换,promoted=1的候选被重置score/promoted/log → ON CONFLICT DO UPDATE只更新扫描器自有列(cron活跃,今日必修) - market_scanner: INSERT引用表中不存在的列(price/score/entry_low等),实测必失败 → 对齐真实schema+同款UPSERT(孤儿脚本顺手修) - sync_decisions_to_db.py: 全表DELETE再从已不存在的decisions.json重建,地雷→归档 --- .../sync_decisions_to_db.py | 0 .../profile-scripts/accumulation_scanner.py | 11 ++++++++--- deploy/profile-scripts/market_scanner.py | 19 ++++++++++++------- 3 files changed, 20 insertions(+), 10 deletions(-) rename {deploy/profile-scripts => archive/20260723-landmine}/sync_decisions_to_db.py (100%) diff --git a/deploy/profile-scripts/sync_decisions_to_db.py b/archive/20260723-landmine/sync_decisions_to_db.py similarity index 100% rename from deploy/profile-scripts/sync_decisions_to_db.py rename to archive/20260723-landmine/sync_decisions_to_db.py diff --git a/deploy/profile-scripts/accumulation_scanner.py b/deploy/profile-scripts/accumulation_scanner.py index 0600e9c5..93606cf4 100644 --- a/deploy/profile-scripts/accumulation_scanner.py +++ b/deploy/profile-scripts/accumulation_scanner.py @@ -227,11 +227,16 @@ def main(): ).fetchone() if exists: continue - + + # UPSERT:只更新扫描器自有列,保留 score_*/pass_*/promoted/log/zhiwei_* 等计算列 + # (原 INSERT OR REPLACE 会把 promoted=1 的行整行替换,计算列全部清零——2026-07-23 审计发现) conn.execute( - "INSERT OR REPLACE INTO candidates (code, name, sector, reason, " + "INSERT INTO candidates (code, name, sector, reason, " "entry_range, stop_loss, target, created_at) " - "VALUES (?,?,?,?,?,?,?,datetime('now','localtime'))", + "VALUES (?,?,?,?,?,?,?,datetime('now','localtime')) " + "ON CONFLICT(code) DO UPDATE SET " + "name=excluded.name, sector=excluded.sector, reason=excluded.reason, " + "entry_range=excluded.entry_range, stop_loss=excluded.stop_loss, target=excluded.target", (code, name, "accumulation", f"主力建仓特征({reasons}) 评分{score}/7", f"{entry_low}~{entry_high}", sl, tp) diff --git a/deploy/profile-scripts/market_scanner.py b/deploy/profile-scripts/market_scanner.py index 2bdb6b9d..62f8935d 100644 --- a/deploy/profile-scripts/market_scanner.py +++ b/deploy/profile-scripts/market_scanner.py @@ -159,17 +159,22 @@ def scan_candidates(): new_candidates.append(candidate) print(f" ✅ {code} {name} 价{price} (+{chg:.1f}%) 评分{score}", flush=True) - # 4. 写入candidates表 + # 4. 写入candidates表(对齐真实表结构:表无 price/change_pct/score/entry_low/entry_high/ + # take_profit/source 列,原 INSERT 必失败——2026-07-23 审计实测确认。 + # UPSERT 只更新扫描器自有列,保留 score_*/pass_*/promoted/log 等计算列) if new_candidates: conn = get_conn() for c in new_candidates: conn.execute( - "INSERT OR REPLACE INTO candidates (code, name, price, change_pct, score, " - "entry_low, entry_high, stop_loss, take_profit, source, sector, reason, created_at) " - "VALUES (?,?,?,?,?,?,?,?,?,?,?,?,datetime('now','localtime'))", - (c["code"], c["name"], c["price"], c["change_pct"], c["score"], - c["entry_low"], c["entry_high"], c["stop_loss"], c["take_profit"], - c["source"], c["sector"], c["reason"]) + "INSERT INTO candidates (code, name, sector, reason, " + "entry_range, stop_loss, target, created_at) " + "VALUES (?,?,?,?,?,?,?,datetime('now','localtime')) " + "ON CONFLICT(code) DO UPDATE SET " + "name=excluded.name, sector=excluded.sector, reason=excluded.reason, " + "entry_range=excluded.entry_range, stop_loss=excluded.stop_loss, target=excluded.target", + (c["code"], c["name"], c.get("sector") or c.get("source", "market_scanner"), + f"{c['reason']} 价{c['price']}(+{c['change_pct']:.1f}%) 评分{c['score']}", + f"{c['entry_low']}~{c['entry_high']}", c["stop_loss"], c["take_profit"]) ) conn.commit() conn.close()