feat: 历史K线(Sina)驱动S2多日确认+10只新候选入自选
This commit is contained in:
+63
-56
@@ -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
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user