diff --git a/data/mofin.db-shm b/data/mofin.db-shm new file mode 100644 index 0000000..d7bc782 Binary files /dev/null and b/data/mofin.db-shm differ diff --git a/data/mofin.db-wal b/data/mofin.db-wal new file mode 100644 index 0000000..d666b34 Binary files /dev/null and b/data/mofin.db-wal differ diff --git a/docs/xiaoguo-signal-pipeline.md b/docs/xiaoguo-signal-pipeline.md index 3cda335..f00b5a2 100644 --- a/docs/xiaoguo-signal-pipeline.md +++ b/docs/xiaoguo-signal-pipeline.md @@ -23,9 +23,15 @@ xiaoguo_scanner.py(每5分钟跑一轮) ## 二、数据源 -### 榜单来源 -- 同花顺技术面榜单(akshare),覆盖技术指标类信号 -- 东方财富热榜(akshare.stock_hot_rank_em)因502不可用,降级静默 +### 榜单来源(三路并行) +1. **同花顺技术面榜单**(akshare,6看多+5看空轮换)— 技术指标类信号 +2. **行业领涨股**(从 market.json 读取,每轮都跑)— 涨幅>2.5%板块的领涨龙头 +3. **东方财富热榜**(akshare.stock_hot_rank_em)— 因502不可用,降级静默 + +### 三路数据合并规则 +优先级:行业领涨 > 同花顺技术榜 > 东方财富热榜 +同只股票不重复处理,最多15只/轮。 +行业领涨股保证能被扫描到,不会被技术榜单的股票挤掉。 ### 新闻来源 - 东方财富个股新闻API(akshare.stock_news_em) diff --git a/xiaoguo_scanner.py b/xiaoguo_scanner.py index 53b93f3..bc158bb 100644 --- a/xiaoguo_scanner.py +++ b/xiaoguo_scanner.py @@ -45,6 +45,10 @@ BEARISH_BOARDS = [ ALL_BOARDS = BULLISH_BOARDS + BEARISH_BOARDS BULLISH_COUNT = len(BULLISH_BOARDS) +# 行业领涨股扫描(不轮换,每轮都跑) +# 从 market.json 读热门行业领涨股 +SECTOR_HOT_THRESHOLD = 2.5 # 板块涨幅>2.5%时捞它的领涨股 + def clean_proxy(): for k in ['http_proxy','https_proxy','HTTP_PROXY','HTTPS_PROXY']: @@ -112,6 +116,53 @@ def fetch_rotating_board(): return [], is_bullish +def fetch_sector_leaders(): + """从 market.json 读热门行业领涨股""" + mkt_path = DATA_DIR / "market.json" + if not mkt_path.exists(): + return [] + try: + mkt = json.loads(mkt_path.read_text()) + sectors = mkt.get("sectors", []) + + # 代码→名称映射(优先用本地缓存,避免每次跑都调akshare) + cache_path = DATA_DIR / "stock_name_code_cache.json" + name_to_code = {} + if cache_path.exists(): + name_to_code = json.loads(cache_path.read_text()) + if not name_to_code: + try: + import akshare as ak + df = ak.stock_info_a_code_name() + for _, r in df.iterrows(): + name_to_code[r["简称"]] = r["代码"] + cache_path.write_text(json.dumps(name_to_code, ensure_ascii=False)) + print(f" 名称代码映射: {len(name_to_code)}只已缓存", flush=True) + except Exception as e: + print(f" 名称代码映射加载失败: {e}", flush=True) + + leaders = [] + seen = set() + for s in sectors: + chg = s.get("change", 0) or 0 + lead_name = s.get("lead_stock", "") + if chg < SECTOR_HOT_THRESHOLD or not lead_name or lead_name in seen: + continue + seen.add(lead_name) + code = name_to_code.get(lead_name, "") + if not code: + continue + leaders.append({ + "code": code, + "name": lead_name, + "source": f"行业领涨-{s['name']}+{chg:+.1f}%", + }) + return leaders + except Exception as e: + print(f" 行业领涨获取失败: {e}", flush=True) + return [] + + def get_scanned_codes(conn): """取1小时内已扫描过的代码""" rows = conn.execute( @@ -192,10 +243,11 @@ def main(): # 1. 拉板 hot = fetch_hot_board() rotating, is_bullish = fetch_rotating_board() + leaders = fetch_sector_leaders() elapsed = time.time() - start_time - print(f"榜单: 东方财富{len(hot)}只, 同花顺{len(rotating)}只 ({elapsed:.0f}s)", flush=True) + print(f"榜单: 东方财富{len(hot)}只, 同花顺{len(rotating)}只, 行业领涨{len(leaders)}只 ({elapsed:.0f}s)", flush=True) - if not hot and not rotating: + if not hot and not rotating and not leaders: conn.close() return @@ -210,7 +262,8 @@ def main(): # 2. 合并去重 + 看空榜只保留持仓股 all_stocks = {} - for s in hot + rotating: + # 行业领涨优先(热门板块龙头) + for s in (leaders if is_bullish else []) + hot + rotating: code = s["code"] code_stripped = code.lstrip("0") if not is_bullish: