feat: predictive_oversold_scanner完善——真实全市场PE/市值分位(get_market_percentile)+行业20日动量(fetch_sector_momentum),替代简化值
This commit is contained in:
@@ -228,12 +228,14 @@ def main():
|
||||
fund = fetch_fundamentals(code)
|
||||
if not fund:
|
||||
continue
|
||||
# 简化分位(部署时可从全市场分位表读取)
|
||||
mcap_q = 0.1 if fund.get("mcap_total") and fund["mcap_total"] < 50 else 0.5
|
||||
pe_q = 0.1 if fund.get("pe") and fund["pe"] < 20 else 0.5
|
||||
# 简化 news3/sec_ret20(部署时可从 stock_news/sector 数据读取)
|
||||
news3 = 1 if fetch_news_count(code) > 0 else 0
|
||||
sec_ret20 = -5 # 简化,部署时用真实行业动量
|
||||
# 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日动量
|
||||
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)
|
||||
if ok_s:
|
||||
@@ -250,6 +252,57 @@ def main():
|
||||
print(f" ✅ 完成: 新增{hits}只 p_oversold 候选", flush=True)
|
||||
|
||||
|
||||
def get_market_percentile(code, field):
|
||||
"""计算个股在全市场的分位(0-1,越小越优)。
|
||||
field: mcap_total 或 pe。分位 = (比它小的数量 / 总数)。
|
||||
返回 None 表示数据不可用。"""
|
||||
try:
|
||||
conn = sqlite3.connect(str(DB_PATH), timeout=5)
|
||||
# 全市场分布
|
||||
total = conn.execute(f"SELECT COUNT(*) FROM stock_fundamentals WHERE {field} > 0").fetchone()[0]
|
||||
if not total:
|
||||
conn.close()
|
||||
return None
|
||||
mine = conn.execute(f"SELECT {field} FROM stock_fundamentals WHERE code=?", (code,)).fetchone()
|
||||
if not mine or not mine[0] or mine[0] <= 0:
|
||||
conn.close()
|
||||
return None
|
||||
val = mine[0]
|
||||
# 分位:比我小的占比
|
||||
cnt = conn.execute(f"SELECT COUNT(*) FROM stock_fundamentals WHERE {field} > 0 AND {field} < ?", (val,)).fetchone()[0]
|
||||
conn.close()
|
||||
return cnt / total
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def fetch_sector_momentum(code):
|
||||
"""计算行业20日动量(%)。从 stock_sectors 拿行业,再算行业指数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()
|
||||
if not sector or not sector[0]:
|
||||
conn.close()
|
||||
return None
|
||||
sector_name = sector[0]
|
||||
# 行业20日动量:查 sector_index_daily 该行业20日前 vs 最新
|
||||
rows = conn.execute(
|
||||
"SELECT close FROM sector_index_daily WHERE sector=? ORDER BY date DESC LIMIT 21",
|
||||
(sector_name,)).fetchall()
|
||||
conn.close()
|
||||
if len(rows) < 20:
|
||||
return None
|
||||
latest = rows[0][0]
|
||||
past = rows[19][0]
|
||||
if past <= 0:
|
||||
return None
|
||||
return round((latest - past) / past * 100, 2)
|
||||
except Exception:
|
||||
return None
|
||||
|
||||
|
||||
def fetch_news_count(code):
|
||||
"""简化:查 stock_news 表近3日新闻数(部署时可完善)"""
|
||||
try:
|
||||
|
||||
Reference in New Issue
Block a user