refactor: p_oversold扫描器改读加工层——compute_market_filters读market_indicators+bias60/mcap_q/pe_q读stock_indicators+fetch_sector_momentum改查stock_sectors_em,不再fetch_tx_klines采集(使用层只读不采集,口径与回测一致)

This commit is contained in:
hmo
2026-08-12 10:31:56 +08:00
parent c3687aa4da
commit 52b808381c
@@ -96,25 +96,19 @@ def load_market_state():
def compute_market_filters():
"""计算大盘门控:mkt_rsi / mkt_dd60用上证指数K线)"""
# 000001 = 上证指数(fetch_tx_klines 自动加 sh 前缀;不能用带前缀的 sh000001)
klines = fetch_tx_klines("000001", datalen=80)
if not klines or len(klines) < 70:
"""计算大盘门控:mkt_rsi / mkt_dd602026-08-12 改读 market_indicators 加工层预算值,
不再 fetch_tx_klines 自己拉指数K线算——使用层只读数据不采集,口径与回测一致)"""
try:
conn = sqlite3.connect(str(DB_PATH), timeout=5)
row = conn.execute(
"SELECT mkt_rsi, mkt_dd60, mkt_down_days FROM market_indicators ORDER BY date DESC LIMIT 1"
).fetchone()
conn.close()
if not row:
return None
return {"mkt_rsi": row[0], "mkt_dd60": row[1], "down_days": row[2] or 0}
except Exception:
return None
closes = [k["close"] for k in klines]
rsi_all = calc_rsi(closes)
mkt_rsi = rsi_all[-1] if rsi_all and rsi_all[-1] is not None else None
# 60日高点回撤
hi60 = max(closes[-60:]) if len(closes) >= 60 else max(closes)
mkt_dd60 = (closes[-1] - hi60) / hi60 * 100 if hi60 > 0 else 0
# 阴跌判定:连跌天数
down_days = 0
for i in range(len(closes) - 1, 0, -1):
if closes[i] < closes[i - 1]:
down_days += 1
else:
break
return {"mkt_rsi": mkt_rsi, "mkt_dd60": mkt_dd60, "down_days": down_days}
def check_gate(mkt):
@@ -133,17 +127,12 @@ def check_gate(mkt):
return True, "门控通过"
def check_stock(code, name, mcap_q, pe_q, news3, sec_ret20, klines):
"""个股条件检查(v5):bias60 + 综合过滤"""
if not klines or len(klines) < 70:
return False, "K线不足"
closes = [k["close"] for k in klines]
ma60 = calc_ma(closes, 60)
m60 = ma60[-1]
close = closes[-1]
if not m60 or m60 <= 0:
return False, "MA60不可用"
bias60 = (close - m60) / m60 * 100
def check_stock(code, name, mcap_q, pe_q, news3, sec_ret20, bias60):
"""个股条件检查(v5):bias60 + 综合过滤
2026-08-12 改:bias60 由调用方从 stock_indicators 加工层预算值传入,
不再用 klines 现算 ma60——使用层只读数据不采集,口径与回测一致。"""
if bias60 is None:
return False, "bias60不可用"
# 核心:深度超跌
if bias60 >= OVERSOLD_CFG["bias60_max"]:
return False, f"bias60={bias60:.1f}>-20,不够超跌"
@@ -220,27 +209,29 @@ def main():
(code, SECTOR, f"{today}%")).fetchone()
if r:
continue
# 拉K线
klines = fetch_tx_klines(code)
if not klines:
# 2026-08-12 改:一次性读 stock_indicators 加工层预算值(bias60/mcap_q/pe_q),
# 替代 fetch_tx_klines + fetch_fundamentals + get_market_percentile——
# 使用层只读数据不采集,口径与回测完全一致(factor_engine 收盘后加工)
ind = conn.execute(
"SELECT bias60, mcap_q, pe_q FROM stock_indicators WHERE code=? ORDER BY date DESC LIMIT 1",
(code,)).fetchone()
if not ind:
continue
# 基本面(PE/市值)
fund = fetch_fundamentals(code)
if not fund:
bias60, mcap_q, pe_q = ind
if bias60 is None or mcap_q is None or pe_q is None:
continue
# 2026-08-11:真实分位(全市场 PE/市值分位,替代简化值
mcap_q = get_market_percentile(code, "mcap_total")
pe_q = get_market_percentile(code, "pe")
if mcap_q is None or pe_q is None:
continue
# 2026-08-11:真实新闻数(3日)+ 行业20日动量
# 新闻3日 + 行业20日动量(读采集/加工层数据
news3 = fetch_news_count(code)
sec_ret20 = fetch_sector_momentum(code)
ok_s, msg_s = check_stock(code, code, mcap_q, pe_q, news3, sec_ret20, klines)
ok_s, msg_s = check_stock(code, code, mcap_q, pe_q, news3, sec_ret20, bias60)
if ok_s:
name = code
write_candidate(conn, code, name, msg_s, klines[-1]["close"])
# 入场价:stock_daily 最新收盘价
pr = conn.execute("SELECT close FROM stock_daily WHERE code=? ORDER BY date DESC LIMIT 1", (code,)).fetchone()
price = pr[0] if pr and pr[0] else 0
if price <= 0:
continue
write_candidate(conn, code, code, msg_s, price)
hits += 1
print(f" 🟢 {code} {msg_s}", flush=True)
if hits >= 5: # 单日限5step39
@@ -277,12 +268,13 @@ def get_market_percentile(code, field):
def fetch_sector_momentum(code):
"""计算行业20日动量(%)。从 stock_sectors 拿行业,再算行业指数20日涨跌。
返回 None 表示无行业数据。"""
"""计算行业20日动量(%)。2026-08-12 改:从 stock_sectors_emEM体系权威映射,
5061只/307行业,与回测 prepare_sector_context 对齐)拿行业,查 sector_index_daily
sector_index_builder 加工层产物)算20日涨跌。返回 None 表示无行业数据。"""
try:
conn = sqlite3.connect(str(DB_PATH), timeout=5)
sector = conn.execute(
"SELECT sector_name FROM stock_sectors WHERE code=? LIMIT 1", (code,)).fetchone()
"SELECT sector FROM stock_sectors_em WHERE code=? LIMIT 1", (code,)).fetchone()
if not sector or not sector[0]:
conn.close()
return None