diff --git a/deploy/profile-scripts/build_panel_hk.py b/deploy/profile-scripts/build_panel_hk.py index 85308ca2..b3645d77 100644 --- a/deploy/profile-scripts/build_panel_hk.py +++ b/deploy/profile-scripts/build_panel_hk.py @@ -36,6 +36,7 @@ def calc_rsi(closes, n=14): def main(): conn = sqlite3.connect(DB) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) hsi = pd.read_sql("SELECT date, close FROM stock_daily WHERE code='hkHSI' ORDER BY date", conn) codes = [r[0] for r in conn.execute( "SELECT code FROM hk_connect_stocks WHERE is_active=1 ORDER BY code").fetchall()] @@ -61,6 +62,7 @@ def main(): if not sec: continue conn = sqlite3.connect(DB) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) df = pd.read_sql("SELECT date, close FROM stock_daily WHERE code=? ORDER BY date", conn, params=(code,)) conn.close() @@ -83,6 +85,7 @@ def main(): # 估值分位(每日截面) print("加载历史估值...", flush=True) conn = sqlite3.connect(DB) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) hist = pd.read_sql( "SELECT code, date, pe_ttm, pb FROM stock_fundamentals_history WHERE length(code)=5", conn) conn.close() @@ -97,6 +100,7 @@ def main(): # 资金流(当日主力净流入,万元) print("加载资金流...", flush=True) conn = sqlite3.connect(DB) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) flow = pd.read_sql( "SELECT code, date, flow_in, flow_out FROM hk_flow_daily", conn) conn.close() @@ -110,6 +114,7 @@ def main(): # 市值(当前市值×价格比反推历史,总股本短期不变) print("加载市值...", flush=True) conn = sqlite3.connect(DB) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) fund_mc = pd.read_sql( "SELECT code, mcap_total FROM stock_fundamentals WHERE length(code)=5", conn) conn.close() @@ -121,6 +126,7 @@ def main(): for idx, code in enumerate(codes): sec = sector.get(code) conn = sqlite3.connect(DB) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) df = pd.read_sql( "SELECT date, open, close, high, low, volume FROM stock_daily WHERE code=? ORDER BY date", conn, params=(code,)) diff --git a/deploy/profile-scripts/capital_flow_collector.py b/deploy/profile-scripts/capital_flow_collector.py index 0564412c..c7334edf 100644 --- a/deploy/profile-scripts/capital_flow_collector.py +++ b/deploy/profile-scripts/capital_flow_collector.py @@ -155,6 +155,7 @@ def main(): try: import sqlite3 _db = sqlite3.connect("/home/hmo/MoFin/data/mofin.db") + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) for row in _db.execute("SELECT DISTINCT code FROM holdings WHERE is_active=1").fetchall(): if row[0]: codes.add(row[0]) for row in _db.execute("SELECT DISTINCT code FROM holding_strategies WHERE status='active' AND decision_type='自选策略'").fetchall(): diff --git a/deploy/profile-scripts/data_governance.py b/deploy/profile-scripts/data_governance.py index bf10f449..18e3e3e9 100644 --- a/deploy/profile-scripts/data_governance.py +++ b/deploy/profile-scripts/data_governance.py @@ -58,6 +58,7 @@ def check_missing_strategies(conn, decisions_list): def main(): conn = sqlite3.connect(str(DB_PATH)) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) # 1. 清理 holding_strategies archived = clean_holding_strategies(conn) diff --git a/deploy/profile-scripts/divergence_detector.py b/deploy/profile-scripts/divergence_detector.py index 77fd2006..246631fa 100644 --- a/deploy/profile-scripts/divergence_detector.py +++ b/deploy/profile-scripts/divergence_detector.py @@ -95,6 +95,7 @@ def load_history(): try: import sqlite3 conn = sqlite3.connect(str(DB_PATH)) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) rows = conn.execute( "SELECT indices, created_at FROM macro_context_log " "WHERE has_valid_data=1 ORDER BY created_at DESC LIMIT 5" @@ -277,6 +278,7 @@ def write_to_signal_news(signals): return import sqlite3 conn = sqlite3.connect(str(DB_PATH)) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) high_signals = [s for s in signals if s["level"] == "high"] if high_signals: diff --git a/deploy/profile-scripts/fundamentals_refresh.py b/deploy/profile-scripts/fundamentals_refresh.py index 824b70e0..fd12dba8 100644 --- a/deploy/profile-scripts/fundamentals_refresh.py +++ b/deploy/profile-scripts/fundamentals_refresh.py @@ -22,6 +22,7 @@ def prefix(code): def main(): conn = sqlite3.connect(DB) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) codes = [r[0] for r in conn.execute( "SELECT DISTINCT code FROM holding_strategies WHERE status='active'")] print(f"刷新 {len(codes)} 只基本面...") diff --git a/deploy/profile-scripts/generate_report.py b/deploy/profile-scripts/generate_report.py index a4caefe2..4ad028a2 100644 --- a/deploy/profile-scripts/generate_report.py +++ b/deploy/profile-scripts/generate_report.py @@ -98,6 +98,7 @@ def main(): try: import sqlite3 _conn = sqlite3.connect("/home/hmo/web-dashboard/data/mofin.db") + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) _actionable = _conn.execute( "SELECT hs.code, lp.price, hs.entry_low, hs.entry_high FROM holding_strategies hs " "LEFT JOIN live_prices lp ON hs.code = lp.code " diff --git a/deploy/profile-scripts/hk_backtest.py b/deploy/profile-scripts/hk_backtest.py index 59eae541..d19ce115 100644 --- a/deploy/profile-scripts/hk_backtest.py +++ b/deploy/profile-scripts/hk_backtest.py @@ -36,6 +36,7 @@ def load_regime_map(): global _REGIME_CACHE if _REGIME_CACHE is None: conn = sqlite3.connect(DB) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) _REGIME_CACHE = dict(conn.execute( "SELECT date, regime FROM market_regime WHERE market='hk'").fetchall()) conn.close() @@ -156,6 +157,7 @@ def portfolio_nav(trades, capital=1000000, slots=8): return {}, {} # 用真实日历(stock_daily 港股日K日期) conn = sqlite3.connect(DB) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) cal = [r[0] for r in conn.execute( "SELECT DISTINCT date FROM stock_daily WHERE date>=? AND date<=? AND length(code)=5 ORDER BY date", (dates[0], dates[-1])).fetchall()] diff --git a/deploy/profile-scripts/hk_rate.py b/deploy/profile-scripts/hk_rate.py index f0972d29..de617128 100644 --- a/deploy/profile-scripts/hk_rate.py +++ b/deploy/profile-scripts/hk_rate.py @@ -26,6 +26,7 @@ def _get_rate_from_db(): try: import sqlite3 conn = sqlite3.connect(DB_PATH) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) cursor = conn.execute( "SELECT rate FROM fx_rate WHERE currency_pair='HKD_CNY' AND is_active=1 ORDER BY created_at DESC LIMIT 1" ) @@ -42,6 +43,7 @@ def _set_rate_in_db(rate, source='api_refresh'): try: import sqlite3 conn = sqlite3.connect(DB_PATH) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) # 先将旧的设为 inactive conn.execute("UPDATE fx_rate SET is_active=0 WHERE currency_pair='HKD_CNY'") # 插入新汇率 diff --git a/deploy/profile-scripts/import_full_stocks.py b/deploy/profile-scripts/import_full_stocks.py index ef6c3e72..148dc066 100644 --- a/deploy/profile-scripts/import_full_stocks.py +++ b/deploy/profile-scripts/import_full_stocks.py @@ -40,6 +40,7 @@ def fetch_tencent_batch(codes): def main(): import sqlite3 conn = sqlite3.connect(str(DB_PATH)) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) # 获取已有代码 existing = set(r[0] for r in conn.execute("SELECT code FROM stocks").fetchall()) diff --git a/deploy/profile-scripts/import_holding_xls.py b/deploy/profile-scripts/import_holding_xls.py index def74b15..1ed52959 100644 --- a/deploy/profile-scripts/import_holding_xls.py +++ b/deploy/profile-scripts/import_holding_xls.py @@ -114,6 +114,7 @@ def main(): # Step 1: Update SQLite (regenerate_all reads from here) print("\n→ 更新 SQLite holdings 表...") conn = sqlite3.connect(DB_PATH) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) c = conn.cursor() c.execute('DELETE FROM holdings') c.execute('DELETE FROM portfolio_summary') diff --git a/deploy/profile-scripts/kanban_xmpp_bridge.py b/deploy/profile-scripts/kanban_xmpp_bridge.py index a4ac5a65..ed32b851 100644 --- a/deploy/profile-scripts/kanban_xmpp_bridge.py +++ b/deploy/profile-scripts/kanban_xmpp_bridge.py @@ -51,6 +51,7 @@ def main(): return notified = _load_state() db = sqlite3.connect(KANBAN_DB) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) rows = db.execute( "SELECT id, title, assignee, created_by FROM tasks " "WHERE status='ready' AND assignee IN (%s) ORDER BY created_at" diff --git a/deploy/profile-scripts/leader_scanner.py b/deploy/profile-scripts/leader_scanner.py index 9c96c0ef..fb04bce8 100644 --- a/deploy/profile-scripts/leader_scanner.py +++ b/deploy/profile-scripts/leader_scanner.py @@ -41,6 +41,7 @@ def scan_leaders(): return [] c = sqlite3.connect(DB) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) # 最新交易日 row = c.execute("SELECT MAX(date) FROM stock_indicators").fetchone() if not row or not row[0]: diff --git a/deploy/profile-scripts/live_data_collector.py b/deploy/profile-scripts/live_data_collector.py index 7ab46b38..7f6921af 100644 --- a/deploy/profile-scripts/live_data_collector.py +++ b/deploy/profile-scripts/live_data_collector.py @@ -25,6 +25,7 @@ os.makedirs(LIVE_DIR, exist_ok=True) def collect_daily_snapshot(date_str=None): """累积某日的实盘数据快照""" conn = sqlite3.connect(f"file:{DB}?mode=ro", uri=True) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) conn.execute("PRAGMA query_only=ON") if date_str is None: diff --git a/deploy/profile-scripts/macro_context_collector.py b/deploy/profile-scripts/macro_context_collector.py index 35899ee0..f23d03c7 100644 --- a/deploy/profile-scripts/macro_context_collector.py +++ b/deploy/profile-scripts/macro_context_collector.py @@ -217,6 +217,7 @@ def write_risk_signal(conn, level, matched, summary): def main(): conn = sqlite3.connect(str(DB_PATH)) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) ensure_tables(conn) # 去重基础 diff --git a/deploy/profile-scripts/mo_alphasift_bridge.py b/deploy/profile-scripts/mo_alphasift_bridge.py index 7cbc3343..57b663f5 100644 --- a/deploy/profile-scripts/mo_alphasift_bridge.py +++ b/deploy/profile-scripts/mo_alphasift_bridge.py @@ -59,6 +59,7 @@ def get_existing_codes(): try: import sqlite3 db = sqlite3.connect(str(MOFIN_DATA / "mofin.db")) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) for row in db.execute("SELECT code FROM watchlist_stocks WHERE is_active=1"): codes.add(str(row[0]).strip()) for row in db.execute("SELECT code FROM holdings"): diff --git a/deploy/profile-scripts/mo_data.py b/deploy/profile-scripts/mo_data.py index fee5e990..48cb39f2 100644 --- a/deploy/profile-scripts/mo_data.py +++ b/deploy/profile-scripts/mo_data.py @@ -23,6 +23,7 @@ SCRIPT_DIR = Path('/home/hmo/MoFin/scripts') def _get_db(): db = sqlite3.connect(DB_PATH) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) db.row_factory = sqlite3.Row return db @@ -287,6 +288,7 @@ def write_cash_log(cash_before, cash_after, frozen_before, frozen_after, """记录现金变更到 cash_log 表。""" change_amount = round(cash_after - cash_before, 2) if cash_after is not None and cash_before is not None else 0 db = sqlite3.connect(DB_PATH) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) try: cur = db.execute( """INSERT INTO cash_log diff --git a/deploy/profile-scripts/mofin_db.py b/deploy/profile-scripts/mofin_db.py index b6f851c4..dd1c6110 100644 --- a/deploy/profile-scripts/mofin_db.py +++ b/deploy/profile-scripts/mofin_db.py @@ -1119,6 +1119,7 @@ def get_price_from_db(code: str) -> tuple[float | None, float | None]: """ try: db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db') + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) db.row_factory = sqlite3.Row row = db.execute( "SELECT price, change_pct FROM holdings WHERE code=? AND is_active=1", (str(code),) @@ -1142,6 +1143,7 @@ def get_prices_batch_from_db(codes: list[str]) -> dict: return results try: db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db') + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) db.row_factory = sqlite3.Row for code in codes: row = db.execute( diff --git a/deploy/profile-scripts/mofin_news.py b/deploy/profile-scripts/mofin_news.py index 4da6ed79..38df2443 100644 --- a/deploy/profile-scripts/mofin_news.py +++ b/deploy/profile-scripts/mofin_news.py @@ -29,6 +29,7 @@ def clean_proxy(): def get_conn(): import sqlite3 conn = sqlite3.connect(str(DB_PATH)) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) conn.row_factory = sqlite3.Row return conn diff --git a/deploy/profile-scripts/morning_health_check.py b/deploy/profile-scripts/morning_health_check.py index f53af5b5..bdab5c2a 100644 --- a/deploy/profile-scripts/morning_health_check.py +++ b/deploy/profile-scripts/morning_health_check.py @@ -141,6 +141,7 @@ def write_todos_for_issues(): try: conn = sqlite3.connect(str(DB_PATH)) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) todo_priority = {"critical": "high", "error": "medium", "warn": "low"} new_count = 0 @@ -304,6 +305,7 @@ def check_db_table_count(table, field, value, op="today", threshold=0): """检查数据库中的记录数""" try: conn = sqlite3.connect(str(DB_PATH)) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) cur = conn.cursor() if op == "today": today = ctx["started_at"].strftime("%Y-%m-%d") @@ -478,6 +480,7 @@ def check_meta_health_check_yesterday(): """元检:昨天体检是否正常完成""" try: conn = sqlite3.connect(str(DB_PATH)) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) yesterday = (ctx["started_at"] - timedelta(days=1)).strftime("%Y-%m-%d") row = conn.execute( "SELECT ok_count, error_count, critical_count FROM health_check_log " @@ -804,6 +807,7 @@ def main(): # 保存历史到DB try: conn_hist = sqlite3.connect(str(DB_PATH)) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) details = json.dumps([e for e in ctx["report"] if e["level"] in ("critical", "error")]) conn_hist.execute( "INSERT INTO health_check_log (ok_count, warn_count, error_count, critical_count, duration_s, details) " @@ -834,6 +838,7 @@ def main(): # 检查是否有执行器升级来的TODO(通知失败挂起的) try: conn2 = sqlite3.connect(str(DB_PATH)) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) needs_llm = conn2.execute( "SELECT id, title, priority, created_at, note FROM todos " "WHERE status='needs_llm' " diff --git a/deploy/profile-scripts/news_collector.py b/deploy/profile-scripts/news_collector.py index ab8027bf..c8462095 100644 --- a/deploy/profile-scripts/news_collector.py +++ b/deploy/profile-scripts/news_collector.py @@ -69,6 +69,7 @@ def init_table(conn): if __name__ == '__main__': conn = sqlite3.connect(DB) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) init_table(conn) # 取策略用到的 58 只股票 diff --git a/deploy/profile-scripts/per_stock_reassess.py b/deploy/profile-scripts/per_stock_reassess.py index ef2f3c6f..d0ebd7e6 100644 --- a/deploy/profile-scripts/per_stock_reassess.py +++ b/deploy/profile-scripts/per_stock_reassess.py @@ -16,6 +16,7 @@ def _in_cooldown(code): try: import sqlite3 conn = sqlite3.connect("/home/hmo/MoFin/data/mofin.db") + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) r = conn.execute("SELECT reassessed_at FROM holding_strategies WHERE code=? AND status='active' ORDER BY id DESC LIMIT 1", (code,)).fetchone() conn.close() if not r or not r[0]: @@ -272,6 +273,7 @@ def main(): # 不在 decisions 中的自选股 → 从 holding_strategies 构建entry import sqlite3 _db = sqlite3.connect('/home/hmo/MoFin/data/mofin.db') + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) _db.row_factory = sqlite3.Row _wl = _db.execute("SELECT * FROM holding_strategies WHERE code=? AND status='active' AND decision_type='自选策略'", (code,)).fetchone() _db.close() @@ -306,6 +308,7 @@ def main(): price = 0 import sqlite3 db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db') + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) db.row_factory = sqlite3.Row row = db.execute("SELECT price FROM holdings WHERE code=? AND is_active=1", (code_raw,)).fetchone() if not row: @@ -559,6 +562,7 @@ def main(): from datetime import datetime as _dt import sqlite3 _db2 = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db') + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) for _code in codes: _entry = decisions_map.get(_code) if _entry and _entry.get("is_watchlist"): @@ -622,6 +626,7 @@ def scan_watchlist_stocks(): DB = '/home/hmo/web-dashboard/data/mofin.db' db = sqlite3.connect(DB) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) db.row_factory = sqlite3.Row rows = db.execute( diff --git a/deploy/profile-scripts/pool_news_collector.py b/deploy/profile-scripts/pool_news_collector.py index e0977208..8fea4943 100644 --- a/deploy/profile-scripts/pool_news_collector.py +++ b/deploy/profile-scripts/pool_news_collector.py @@ -58,6 +58,7 @@ def main(): return conn = sqlite3.connect(DB_PATH) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) init_table(conn) cur = conn.cursor() diff --git a/deploy/profile-scripts/pre-flight-check.py b/deploy/profile-scripts/pre-flight-check.py index e0750e0f..ecbd44c6 100644 --- a/deploy/profile-scripts/pre-flight-check.py +++ b/deploy/profile-scripts/pre-flight-check.py @@ -67,6 +67,7 @@ def check_data_freshness(): try: import sqlite3 c = sqlite3.connect(str(WEB_DATA / "mofin.db")) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) row = c.execute("SELECT MAX(updated_at) FROM holding_strategies WHERE status IN ('active','updated')").fetchone() c.close() if row and row[0]: diff --git a/deploy/profile-scripts/preflight_verify.py b/deploy/profile-scripts/preflight_verify.py index 9b749a44..e77759e6 100644 --- a/deploy/profile-scripts/preflight_verify.py +++ b/deploy/profile-scripts/preflight_verify.py @@ -71,6 +71,7 @@ def check_db(): return try: conn = sqlite3.connect(str(DB_PATH)) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) pragma = conn.execute("PRAGMA integrity_check").fetchone()[0] check("DB 完整性", pragma == "ok", pragma) @@ -245,6 +246,7 @@ if __name__ == "__main__": # 尝试写mofin.db todos try: conn = sqlite3.connect(str(DB_PATH)) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) conn.execute(""" INSERT OR REPLACE INTO todos (id, content, status, source, created_at, fix_action) VALUES (?, ?, 'pending', 'preflight', datetime('now'), 'check preflight_result.json and fix issues') diff --git a/deploy/profile-scripts/prepare_recommendation.py b/deploy/profile-scripts/prepare_recommendation.py index 239912ae..a0b60957 100644 --- a/deploy/profile-scripts/prepare_recommendation.py +++ b/deploy/profile-scripts/prepare_recommendation.py @@ -23,6 +23,7 @@ SCRIPTS_DIR = os.path.dirname(os.path.abspath(__file__)) def get_stock_info(code): """从数据库获取股票信息""" db = sqlite3.connect(DB_PATH) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) db.row_factory = sqlite3.Row try: # 检查持仓 diff --git a/deploy/profile-scripts/refresh_macro_context.py b/deploy/profile-scripts/refresh_macro_context.py index de91b7f5..33488525 100644 --- a/deploy/profile-scripts/refresh_macro_context.py +++ b/deploy/profile-scripts/refresh_macro_context.py @@ -92,6 +92,7 @@ def main(): ts = now.strftime("%Y-%m-%d %H:%M:%S") try: conn = sqlite3.connect(str(DB)) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) conn.execute(""" INSERT INTO macro_context_log (data_timestamp, session, has_valid_data, indices, structure, created_at) diff --git a/deploy/profile-scripts/resonance.py b/deploy/profile-scripts/resonance.py index b134df8d..04699b75 100644 --- a/deploy/profile-scripts/resonance.py +++ b/deploy/profile-scripts/resonance.py @@ -25,6 +25,7 @@ def _flow_state(code): """资金维度: flow_5d / flow_delta → negative/neutral/positive/strong_positive""" try: conn = sqlite3.connect(DB) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) rows = conn.execute(""" SELECT date, main_pct FROM stock_capital_flow WHERE code=? ORDER BY date DESC LIMIT 10 @@ -58,6 +59,7 @@ _NEG_KW = ['大跌','跌停','回落','下跌','下挫','走弱','资金流出', def sector_news_sentiment(sector, days=3): import sqlite3 conn = sqlite3.connect('/home/hmo/MoFin/data/mofin.db') + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) try: stocks = conn.execute("SELECT code FROM stock_sectors_em WHERE sector=?", (sector,)).fetchall() if not stocks: @@ -92,6 +94,7 @@ def _news_state(code, name=None, sector=None): since = (datetime.now() - timedelta(days=3)).strftime('%Y-%m-%d') try: conn = sqlite3.connect(DB) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) # 个股级优先(searched_stocks 或 summary 含代码/名称) rows = conn.execute(""" SELECT overall_sentiment, summary, sector, created_at FROM signal_news @@ -159,6 +162,7 @@ def evaluate_resonance(code, gate, name=None): def log_resonance(code, name, price, gate, res, signal_before, signal_after): """完整记录三维状态 → signal_veto_log(后续回测验证的数据资产)""" conn = sqlite3.connect(DB) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) conn.execute(""" CREATE TABLE IF NOT EXISTS signal_veto_log ( id INTEGER PRIMARY KEY AUTOINCREMENT, diff --git a/deploy/profile-scripts/run_all_tests.py b/deploy/profile-scripts/run_all_tests.py index 4e313b2f..3de4d64d 100644 --- a/deploy/profile-scripts/run_all_tests.py +++ b/deploy/profile-scripts/run_all_tests.py @@ -94,6 +94,7 @@ test("frozen_cash 已清零", pf.get('frozen_cash', 0) < 1, f"frozen_cash={pf.ge print("\n--- 6. P&L ---") import sqlite3 db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db') +conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) rows = db.execute("SELECT code, name, cost, price, shares, currency FROM holdings WHERE is_active=1 AND shares>0").fetchall() pnl_issues = [] for r in rows: @@ -111,6 +112,7 @@ db.close() # ── 7. DB 完整性 ── print("\n--- 7. DB 完整性 ---") db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db') +conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) n_holds = db.execute("SELECT COUNT(*) FROM holdings WHERE is_active=1").fetchone()[0] n_strat = db.execute("SELECT COUNT(*) FROM holding_strategies WHERE status IN ('active','updated')").fetchone()[0] n_wl = db.execute("SELECT COUNT(*) FROM watchlist_stocks WHERE is_active=1").fetchone()[0] diff --git a/deploy/profile-scripts/scan_external_api.py b/deploy/profile-scripts/scan_external_api.py new file mode 100644 index 00000000..5166a448 --- /dev/null +++ b/deploy/profile-scripts/scan_external_api.py @@ -0,0 +1,36 @@ +import os + +results = {} +for f in sorted(os.listdir(".")): + if not f.endswith(".py"): + continue + with open(f, errors="ignore") as fh: + content = fh.read() + + hits = [] + for i, line in enumerate(content.split("\n"), 1): + stripped = line.strip() + if stripped.startswith("#") or stripped.startswith('"""') or stripped.startswith("'''"): + continue + apis = [] + if "qt.gtimg.cn" in line: apis.append("腾讯quote") + if "push2.eastmoney" in line or "eastmoney.com" in line: apis.append("东财") + if "ifzq.gtimg.cn" in line: apis.append("腾讯K线") + if "exchangerate-api" in line: apis.append("汇率API") + if "hq.sinajs" in line: apis.append("新浪行情") + if "web.sqt.gtimg.cn" in line: apis.append("腾讯sqt") + if "proxy.finance.qq.com" in line: apis.append("腾讯proxy") + if "stock.xueqiu.com" in line or "xueqiu.com" in line: apis.append("雪球") + if "finance.pae.baidu.com" in line or "baidu.com" in line: apis.append("百度") + + if apis: + hits.append((i, "|".join(apis), stripped[:80])) + + if hits: + results[f] = hits + +print("=== 全部外部API调用点 ===") +for f, hits in sorted(results.items()): + print("\n%s (%d 处):" % (f, len(hits))) + for ln, api, code in hits: + print(" L%d [%s] %s" % (ln, api, code)) \ No newline at end of file diff --git a/deploy/profile-scripts/sector_enrich_cninfo.py b/deploy/profile-scripts/sector_enrich_cninfo.py index dab9a2d6..64740cf5 100644 --- a/deploy/profile-scripts/sector_enrich_cninfo.py +++ b/deploy/profile-scripts/sector_enrich_cninfo.py @@ -16,6 +16,7 @@ DB = "/home/hmo/MoFin/data/mofin.db" def main(): conn = sqlite3.connect(DB) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) codes = [r[0] for r in conn.execute( "SELECT DISTINCT code FROM holding_strategies WHERE status='active'")] have = {r[0] for r in conn.execute("SELECT code FROM stock_sectors")} diff --git a/deploy/profile-scripts/session_to_cron_bridge.py b/deploy/profile-scripts/session_to_cron_bridge.py index 76d76214..5a6b1db7 100644 --- a/deploy/profile-scripts/session_to_cron_bridge.py +++ b/deploy/profile-scripts/session_to_cron_bridge.py @@ -126,6 +126,7 @@ def scan(): return conn = sqlite3.connect(str(STATE_DB)) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) conn.row_factory = sqlite3.Row cur = conn.cursor() diff --git a/deploy/profile-scripts/stale_detector.py b/deploy/profile-scripts/stale_detector.py index ba0f96eb..813a223a 100644 --- a/deploy/profile-scripts/stale_detector.py +++ b/deploy/profile-scripts/stale_detector.py @@ -84,6 +84,7 @@ def main(): try: import sqlite3 db = sqlite3.connect('/home/hmo/MoFin/data/mofin.db') + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) db.row_factory = sqlite3.Row wl_rows = db.execute( "SELECT code, name, entry_low, entry_high, stop_loss, take_profit, rr_ratio, timing_signal, action " @@ -124,6 +125,7 @@ def main(): try: import subprocess, sqlite3 db = sqlite3.connect('/home/hmo/MoFin/data/mofin.db') + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) db.row_factory = sqlite3.Row wl_stocks = db.execute( "SELECT code, name, entry_low, entry_high " @@ -182,6 +184,7 @@ def main(): 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] # 重新合并自选(从 holding_strategies 读) db2 = sqlite3.connect('/home/hmo/MoFin/data/mofin.db') + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) db2.row_factory = sqlite3.Row wl_rows2 = db2.execute( "SELECT code, name, entry_low, entry_high, stop_loss, take_profit, rr_ratio, timing_signal, action " diff --git a/deploy/profile-scripts/stock_profile.py b/deploy/profile-scripts/stock_profile.py index 259ed456..a2161020 100644 --- a/deploy/profile-scripts/stock_profile.py +++ b/deploy/profile-scripts/stock_profile.py @@ -58,6 +58,7 @@ def load_macro() -> dict: try: import sqlite3 conn = sqlite3.connect(os.path.join(DATA_DIR, "mofin.db")) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) row = conn.execute( "SELECT indices, structure, key_sectors FROM macro_context_log " "WHERE has_valid_data=1 ORDER BY created_at DESC LIMIT 1" diff --git a/deploy/profile-scripts/stock_quote.py b/deploy/profile-scripts/stock_quote.py index 951e5dde..59fa2417 100644 --- a/deploy/profile-scripts/stock_quote.py +++ b/deploy/profile-scripts/stock_quote.py @@ -303,6 +303,7 @@ def _get_name_from_cache(code): try: import sqlite3 _db = sqlite3.connect(str(DATA_DIR / "mofin.db")) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) _db.row_factory = sqlite3.Row row = _db.execute("SELECT name FROM watchlist_stocks WHERE code=? AND is_active=1", (code,)).fetchone() _db.close() diff --git a/deploy/profile-scripts/strategy-staleness-check.py b/deploy/profile-scripts/strategy-staleness-check.py index 8b5c5031..3fa7e7eb 100644 --- a/deploy/profile-scripts/strategy-staleness-check.py +++ b/deploy/profile-scripts/strategy-staleness-check.py @@ -205,6 +205,7 @@ def main(): stale_items = [s for s in flagged if any("[STRATEGY_STALE]" in f for f in s.get("flags", []))] if stale_items: conn = sqlite3.connect(DB_PATH) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) for s in stale_items: code = s["code"] existing = conn.execute("SELECT id FROM todos WHERE title LIKE ? AND status IN ('pending','in_progress')", (f"%{code}%",)).fetchone() diff --git a/deploy/profile-scripts/strategy_alert.py b/deploy/profile-scripts/strategy_alert.py index 01694c3b..27048fdb 100644 --- a/deploy/profile-scripts/strategy_alert.py +++ b/deploy/profile-scripts/strategy_alert.py @@ -37,6 +37,7 @@ ORANGE_PNL_RATIO = 0.5 # 盈亏比 < 0.5(赚的越来越少亏的越来越 def rolling_stats(version, days=60): """从 strategy_research 提取该策略近期交易的滚动胜率/盈亏比""" c = sqlite3.connect(DB) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) rows = c.execute( "SELECT results_json FROM strategy_research WHERE version=? ORDER BY period_tag DESC LIMIT 1", (version,) diff --git a/deploy/profile-scripts/strategy_lifecycle.py b/deploy/profile-scripts/strategy_lifecycle.py index 35431fda..88241c64 100644 --- a/deploy/profile-scripts/strategy_lifecycle.py +++ b/deploy/profile-scripts/strategy_lifecycle.py @@ -707,6 +707,7 @@ def load_macro_context(): import sqlite3 from pathlib import Path conn = sqlite3.connect(str(Path(__file__).parent.parent / "data" / "mofin.db")) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) row = conn.execute( "SELECT indices, structure FROM macro_context_log " "WHERE has_valid_data=1 ORDER BY created_at DESC LIMIT 1" @@ -748,6 +749,7 @@ def batch_fetch_prices(codes): try: import sqlite3 db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db') + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) db.row_factory = sqlite3.Row for raw_code in codes: raw_code = str(raw_code).split('_')[0] @@ -817,6 +819,7 @@ def get_price_tencent(code): try: import sqlite3 db = sqlite3.connect('/home/hmo/web-dashboard/data/mofin.db') + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) db.row_factory = sqlite3.Row row = db.execute("SELECT price FROM holdings WHERE code=? AND is_active=1", (raw_code,)).fetchone() if not row: diff --git a/deploy/profile-scripts/strategy_review.py b/deploy/profile-scripts/strategy_review.py index 84bd8653..7d0ed669 100644 --- a/deploy/profile-scripts/strategy_review.py +++ b/deploy/profile-scripts/strategy_review.py @@ -208,6 +208,7 @@ def review(): strategies = decisions.get("decisions", []) conn = sqlite3.connect(str(DB_PATH)) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) stats = {"correct": 0, "wrong": 0, "mixed": 0, "pending": 0, "total": 0} signal_fails = Counter() diff --git a/deploy/profile-scripts/system_audit.py b/deploy/profile-scripts/system_audit.py index d9b79524..0b74628c 100644 --- a/deploy/profile-scripts/system_audit.py +++ b/deploy/profile-scripts/system_audit.py @@ -175,6 +175,7 @@ def audit_pipeline(): """遍历所有关键数据管道,检查生产者→存储→消费者链路是否完整""" today = datetime.now().strftime("%Y-%m-%d") conn = sqlite3.connect(str(DATA_DIR / "mofin.db")) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) pipelines = [ # 管道名, 生产者, 存储位置, 检查SQL/文件, 新鲜度阈值(天) @@ -269,6 +270,7 @@ def audit_services(): def main(): start = time.time() conn = sqlite3.connect(str(DATA_DIR / "mofin.db")) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) audit_signals(conn) audit_stocks(conn) diff --git a/deploy/profile-scripts/technical_analysis.py b/deploy/profile-scripts/technical_analysis.py index 9694cc76..d1525b34 100644 --- a/deploy/profile-scripts/technical_analysis.py +++ b/deploy/profile-scripts/technical_analysis.py @@ -497,6 +497,7 @@ def analyze_volume_deep(code): DATA_DIR = Path(__file__).parent / "data" try: conn = sqlite3.connect(str(DATA_DIR / "mofin.db")) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) row = conn.execute("SELECT cache_json FROM mtf_cache WHERE code=?", (code,)).fetchone() conn.close() if not row: @@ -623,6 +624,7 @@ def _calc_scores(code: str) -> dict: try: import sqlite3 conn = sqlite3.connect('/home/hmo/MoFin/data/mofin.db') + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) rows = conn.execute( "SELECT close, high, low, volume FROM stock_daily WHERE code=? ORDER BY date DESC LIMIT 30", (code,)).fetchall() diff --git a/deploy/profile-scripts/ths_news.py b/deploy/profile-scripts/ths_news.py index dc2af2d8..af550af7 100644 --- a/deploy/profile-scripts/ths_news.py +++ b/deploy/profile-scripts/ths_news.py @@ -25,6 +25,7 @@ def get_market_id(code): def get_codes(): """从 v8.1 5y 策略交易取 58 只股票""" conn = sqlite3.connect(DB) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) codes = set() for r in conn.execute("SELECT results_json FROM strategy_research WHERE version='v8.1' AND period_tag='5y'").fetchall(): for t in json.loads(r[0])['trades']: @@ -98,6 +99,7 @@ def run_backfill(): codes = get_codes() print(f"=== 同花顺新闻回填: {len(codes)} 只股票 ===", flush=True) conn = sqlite3.connect(DB) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) init_table(conn) total_new = 0 for idx, code in enumerate(codes, 1): @@ -123,6 +125,7 @@ def run_daily(): codes = get_codes() print(f"=== 同花顺每日更新: {len(codes)} 只股票 ===", flush=True) conn = sqlite3.connect(DB) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) init_table(conn) total_new = 0 for idx, code in enumerate(codes, 1): diff --git a/deploy/profile-scripts/trend_detector.py b/deploy/profile-scripts/trend_detector.py index 96ed51fb..1ceee050 100644 --- a/deploy/profile-scripts/trend_detector.py +++ b/deploy/profile-scripts/trend_detector.py @@ -24,6 +24,7 @@ DB_PATH = DATA_DIR / "mofin.db" def get_conn(): conn = sqlite3.connect(str(DB_PATH)) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) conn.row_factory = sqlite3.Row return conn diff --git a/deploy/profile-scripts/vacuum_state_db.py b/deploy/profile-scripts/vacuum_state_db.py index 9e0eb839..8cdf25eb 100644 --- a/deploy/profile-scripts/vacuum_state_db.py +++ b/deploy/profile-scripts/vacuum_state_db.py @@ -23,6 +23,7 @@ for db_path in DBS: size_before = os.path.getsize(db_path) / 1024 / 1024 try: c = sqlite3.connect(db_path) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) c.execute("PRAGMA auto_vacuum=2") # 只做incremental vacuum,不做full vacuum(耗时太长) c.execute("PRAGMA incremental_vacuum(50000)") diff --git a/deploy/profile-scripts/verify_reassess_pipeline.py b/deploy/profile-scripts/verify_reassess_pipeline.py index 04c8adfb..29e663c9 100644 --- a/deploy/profile-scripts/verify_reassess_pipeline.py +++ b/deploy/profile-scripts/verify_reassess_pipeline.py @@ -81,6 +81,7 @@ def check_cron_jobs(): continue try: c = sqlite3.connect(str(db_path)) + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) for row in c.execute(""" SELECT id, name, last_status, last_run_at, enabled FROM cron_jobs WHERE enabled=1 @@ -112,6 +113,7 @@ def run(): for _attempt in range(2): try: conn = sqlite3.connect("/home/hmo/MoFin/data/mofin.db") + conn.execute("PRAGMA busy_timeout=30000") # 2026-08-28 防并发写锁(DB损坏教训) conn.execute("SELECT 1 FROM live_prices LIMIT 1").fetchone() break except Exception as e: