refactor: 工具函数抽取公共模块——indicators.py(指标)+market_data.py(数据),mr/s2扫描器改为公共模块import(消除策略扫描器互import)
This commit is contained in:
@@ -0,0 +1,80 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user