feat: 历史K线(Sina)驱动S2多日确认+10只新候选入自选

This commit is contained in:
知微
2026-07-09 19:42:35 +08:00
parent a83c0ea11e
commit bd744c252c
4 changed files with 200 additions and 74 deletions
+63 -56
View File
@@ -40,7 +40,7 @@ def log_candidate(conn, code, stage, passed, detail):
# ── Stage 2: 多日K线确认 ──
def fetch_daily_klines(code):
"""拉取近N日K线(如API不可用则返回当日单日数据"""
"""拉取近10日日K线(Sina 240分钟线=日K"""
raw = str(code).strip()
if raw.startswith(("6", "9")):
prefix = "sh"
@@ -49,78 +49,85 @@ def fetch_daily_klines(code):
else:
return None
import subprocess as _sp
url = f"http://qt.gtimg.cn/q={prefix}{raw}"
import subprocess as _sp, json as _json
url = f"http://money.finance.sina.com.cn/quotes_service/api/json_v2.php/CN_MarketData.getKLineData?symbol={prefix}{raw}&scale=240&ma=5&datalen=10"
try:
r = _sp.run(["curl", "-s", url], capture_output=True, timeout=10)
raw_text = r.stdout.decode("gbk", errors="ignore")
for line in raw_text.strip().split("\n"):
if "~" not in line: continue
parts = line.split("~")
if len(parts) < 40: continue
price = float(parts[3]) if parts[3] else 0
prev_close = float(parts[4]) if parts[4] else 0
high = float(parts[33]) if parts[33] else 0
low = float(parts[34]) if parts[34] else 0
volume = int(float(parts[6])) if parts[6] else 0
change = float(parts[32]) if parts[32] else 0
if price > 0:
# 返回当日单条K线(后续扫描积累多日数据)
return [{
"date": "today", "open": prev_close, "close": price,
"high": high, "low": low, "volume": volume,
"change_pct": change, "price": price
}]
r = _sp.run(["curl", "-s", "--noproxy", "*", url], capture_output=True, timeout=10)
data = _json.loads(r.stdout)
if not data:
return None
result = []
for k in data:
result.append({
"date": k.get("day", "")[:10],
"open": float(k["open"]),
"close": float(k["close"]),
"high": float(k["high"]),
"low": float(k["low"]),
"volume": int(k["volume"]),
"price": float(k["close"]),
"change_pct": 0,
})
# 计算涨跌幅
for i in range(1, len(result)):
prev = result[i-1]["close"]
if prev > 0:
result[i]["change_pct"] = (result[i]["close"] / prev - 1) * 100
return result
except Exception as e:
return None
except:
return None
def stage2_confirm(code, name, klines):
"""第二关:多日K线确认
当日有量价配合信号即可通过初筛。
多日连续性需要多日扫描数据积累后验证。
检查:多日量价配合、建仓特征
"""
if not klines or len(klines) == 0:
return False, 0, "无行情数据"
today = klines[-1]
price = today.get("price", 0)
volume = today.get("volume", 0)
change = today.get("change_pct", 0)
high = today.get("high", 0)
low = today.get("low", 0)
if not klines or len(klines) < 3:
return False, 0, "K线不足3日"
recent = klines[-5:] # 最近5日
score = 0
checks = []
# 1. 成交量
if volume > 100000: # 至少10万股
# 1. 成交量连续递增
vols = [k["volume"] for k in recent]
vol_rising = sum(1 for i in range(len(vols)-1) if vols[i] < vols[i+1])
if vol_rising >= 3:
score += 2
checks.append(f"量增{vol_rising}/4日")
elif vol_rising >= 2:
score += 1
checks.append(f"{volume/10000:.0f}")
else:
checks.append("量太小")
checks.append(f"微增{vol_rising}/4日")
# 2. 跌幅不过大
if change >= -2:
# 2. 涨放量、跌缩量
up_vol = sum(k["volume"] for k in recent if k["change_pct"] >= 0)
down_vol = sum(k["volume"] for k in recent if k["change_pct"] < 0)
if down_vol > 0 and up_vol / down_vol > 1.5:
score += 2
checks.append(f"涨量/跌量={up_vol/down_vol:.1f}")
elif down_vol > 0 and up_vol / down_vol > 1:
score += 1
# 3. 价格趋势
closes = [k["close"] for k in recent]
up_days = sum(1 for i in range(1, len(closes)) if closes[i] > closes[i-1])
if up_days >= 3:
score += 2
checks.append(f"{up_days}/4日")
elif up_days >= 2:
score += 1
# 4. 无异常放量(单日>3倍均量=可能出货)
avg_vol = sum(vols) / len(vols) if vols else 1
max_ratio = max(v / avg_vol for v in vols) if avg_vol > 0 else 1
if max_ratio < 2.5:
score += 1
else:
checks.append(f"{change:.1f}%")
checks.append(f"异常量{max_ratio:.0f}")
# 3. 日内有波动空间
if high > low and price > low:
score += 1
# 4. 价格不为0
if price > 0:
score += 1
# 多日确认需要后续扫描积累(暂标记)
if len(klines) < 3:
checks.append("待多日确认")
passed = score >= 3
detail = f"评分{score}/4 | {'; '.join(checks)}"
passed = score >= 4
detail = f"评分{score}/7 | {'; '.join(checks)}"
return passed, score, detail