Files

109 lines
4.5 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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
from market_config import kline_symbol, market_for_code
DB_PATH = Path("/home/hmo/MoFin/data/mofin.db")
UA = "Mozilla/5.0"
def fetch_tx_klines(code, datalen=120):
"""腾讯前复权日Kqfq),与 stock_daily 数据零偏差,返回 [{date,open,close,high,low,volume}]"""
raw = str(code).strip()
sym = kline_symbol(raw)
if sym is None:
return None
url = f"http://ifzq.gtimg.cn/appstock/app/fqkline/get?param={sym},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(sym, {})
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(market='a'):
"""待扫描股票池:按市场参数化(阶段2B 港股接入)
返回 (pool, existing)
- pool: 待扫描股票池
- existing: 已有策略/持仓代码(供扫描器跳过)
market
- 'a'(默认): A股池 = stock_daily distinct 6位 code(与回测 run_mr_backtest 完全同口径,
含300/688,不含301新创业板——数据源未收录)。实盘扫描用同一口径,保证信号
覆盖的股票都是回测验证过的。
- 'hk' : 港股池 = hk_connect_stocks 表 is_active=1 的 code5位)
+ 港股持仓/自选(holdings/holding_strategies 里 5 位代码)
- 'all' : A股池 + 港股池 合并
降级:hk_connect_stocks 表可能不存在(阶段2A 并行开发中)→ try/except
优雅降级,market='hk' 只返回港股持仓/自选,不报错。
"""
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()]
# 港股通名单(阶段2A hk_connect_list 采集器写入;表可能尚不存在 → 优雅降级)
hk_connect = set()
try:
for r in conn.execute("SELECT code FROM hk_connect_stocks WHERE is_active=1"):
c = str(r[0])
if len(c) == 5 and c.isdigit():
hk_connect.add(c)
except sqlite3.OperationalError as e:
if "no such table" not in str(e).lower():
raise
# 表不存在(阶段2A 并行开发中)→ 仅港股持仓/自选,不报错
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()]
# 港股持仓/自选(5位代码,用 market_for_code 判断)
hk_existing = sorted(c for c in existing if market_for_code(c) == 'hk')
hk_pool = sorted(set(hk_connect) | set(hk_existing))
if market == 'hk':
return hk_pool, set(hk_existing)
if market == 'all':
return sorted(set(a_stocks) | set(hk_pool)), existing
return a_stocks, existing # market='a'(默认):与阶段2B 前完全一致