#!/usr/bin/env python3 """market_data.py — 通用行情数据获取库(2026-08-11 从 mr_scanner 抽取) 背景:fetch_tx_klines/get_stock_pool 原定义在 mr_scanner.py, 被 s2_scanner.py import 复用 —— 策略扫描器互相 import 数据函数是坏味道。 抽取到公共模块,供所有策略扫描器(mr/s2/accumulation/p_oversold)共用。 """ import json import sqlite3 import urllib.request from pathlib import Path DB_PATH = Path("/home/hmo/MoFin/data/mofin.db") UA = "Mozilla/5.0" def fetch_tx_klines(code, datalen=120): """腾讯前复权日K(qfq),与 stock_daily 数据零偏差,返回 [{date,open,close,high,low,volume}]""" raw = str(code).strip() if raw.startswith(("6", "9")): prefix = "sh" elif raw.startswith(("0", "3")): prefix = "sz" else: return None url = f"http://ifzq.gtimg.cn/appstock/app/fqkline/get?param={prefix}{raw},day,,,{datalen},qfq" try: req = urllib.request.Request(url, headers={"User-Agent": UA}) opener = urllib.request.build_opener(urllib.request.ProxyHandler({})) with opener.open(req, timeout=8) as r: text = r.read().decode("utf-8", errors="replace").strip() data = json.loads(text) node = data.get("data", {}).get(f"{prefix}{raw}", {}) bars = node.get("qfqday") or node.get("day") or [] if not bars or len(bars) < 70: return None result = [] for b in bars: if len(b) < 6: continue result.append({ "date": b[0][:10], "open": float(b[1]), "close": float(b[2]), "high": float(b[3]), "low": float(b[4]), "volume": float(b[5]), # 手 }) return result except Exception: return None # 兼容别名(供外部引用) fetch_sina_klines = fetch_tx_klines def get_stock_pool(): """待扫描股票池:stock_daily 的 distinct code(与回测 run_mr_backtest 完全同口径) 回测股票池 = SELECT DISTINCT sd.code FROM stock_daily(4266只,含300/688, 不含301新创业板——数据源未收录)。实盘扫描用同一口径,保证信号 覆盖的股票都是回测验证过的。 """ conn = sqlite3.connect(str(DB_PATH), timeout=5) try: existing = set() for r in conn.execute("SELECT code FROM holding_strategies WHERE status='active'"): existing.add(str(r[0])) for r in conn.execute("SELECT code FROM holdings WHERE is_active=1"): existing.add(str(r[0])) # 与回测完全一致:stock_daily 有K线的股票(回测 universe='a' 排除5位港股) all_stocks = [str(r[0]) for r in conn.execute("SELECT DISTINCT code FROM stock_daily").fetchall()] finally: conn.close() # 只留 A 股(6位数字),排除港股(5位0开头)—— 与回测 is_hk_code 逻辑一致 a_stocks = [c for c in all_stocks if len(c) == 6 and c.isdigit()] return a_stocks, existing