#!/usr/bin/env python3 """market_watch.py — 行業熱點 + 市場洞察數據採集,寫入 dashboard data/market.json""" import json import urllib.request from datetime import datetime from pathlib import Path DATA_DIR = Path(__file__).parent / "data" # ── 後端A:東方財富 push2 API(首選) ── def _fetch_em(url): """通用 EM API 請求""" req = urllib.request.Request(url, headers={"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"}) resp = urllib.request.urlopen(req, timeout=10) return json.loads(resp.read().decode("utf-8")) def fetch_sector_em(): """東方財富行業板塊""" try: data = _fetch_em("https://push2.eastmoney.com/api/qt/clist/get?pn=1&pz=15&po=1&np=1&fields=f2,f3,f4,f12,f14&fs=m:90+t:2") return [{"name": i["f14"], "code": i["f12"], "price": i.get("f2", 0), "change": i.get("f3", 0)} for i in data.get("data", {}).get("diff", [])] except Exception as e: return None def fetch_concept_em(): """東方財富概念板塊""" try: data = _fetch_em("https://push2.eastmoney.com/api/qt/clist/get?pn=1&pz=10&po=1&np=1&fields=f2,f3,f4,f12,f14&fs=m:90+t:3") return [{"name": i["f14"], "code": i["f12"], "change": i.get("f3", 0)} for i in data.get("data", {}).get("diff", [])] except Exception as e: return None # ── 後端B:同花順 THS(降級) ── def fetch_sector_ths(): """THS 行業板塊(含漲跌家數、資金流向)""" try: import akshare as ak df = ak.stock_board_industry_summary_ths() return [{ "name": r["板块"], "code": "", "price": 0, "change": float(r.get("涨跌幅", 0)), "up_count": int(r.get("上涨家数", 0)), "down_count": int(r.get("下跌家数", 0)), "net_inflow": float(r.get("净流入", 0)), } for _, r in df.iterrows()] except Exception as e: print(f"⚠️ THS行業失敗: {e}") return [] def fetch_concept_ths(): """THS 概念板塊(僅名稱,無實時漲跌)""" try: import akshare as ak df = ak.stock_board_concept_name_ths() return [{"name": r["name"], "code": str(r["code"]), "change": 0} for _, r in df.head(15).iterrows()] except Exception as e: print(f"⚠️ THS概念失敗: {e}") return [] # ── 主流程 ── def get_market_mood(sectors): if not sectors: return "unknown" ratio = sum(1 for s in sectors if s.get("change", 0) > 0) / len(sectors) return "bullish" if ratio > 0.7 else "neutral" if ratio > 0.4 else "bearish" def main(): sectors = fetch_sector_em() if sectors is None: sectors = fetch_sector_ths() concepts = fetch_concept_em() if concepts is None: concepts = fetch_concept_ths() sorted_sectors = sorted(sectors, key=lambda s: s.get("change", 0), reverse=True) top_gainers = [s for s in sorted_sectors if s.get("change", 0) > 0][:5] top_losers = [s for s in reversed(sorted_sectors) if s.get("change", 0) < 0][:3] market_data = { "timestamp": datetime.now().strftime("%Y-%m-%d %H:%M"), "total_sectors": len(sectors), "up_ratio": round(sum(1 for s in sectors if s.get("change", 0) > 0) / max(len(sectors), 1) * 100, 1), "mood": get_market_mood(sectors), "top_gainers": top_gainers, "top_losers": top_losers, "sectors": sectors, "concepts": concepts, } DATA_DIR.mkdir(parents=True, exist_ok=True) with open(DATA_DIR / "market.json", "w", encoding="utf-8") as f: json.dump(market_data, f, ensure_ascii=False, indent=2) # 安静:数据只写文件,不打印到stdout,避免cron输出被推送 if __name__ == "__main__": main()