diff --git a/backend/routes/stock.py b/backend/routes/stock.py index 93c4924..470954b 100644 --- a/backend/routes/stock.py +++ b/backend/routes/stock.py @@ -245,3 +245,19 @@ async def stock_fund_flow( "negativeDays": negative, }, } + + +@router.get("/mx-tool", summary="MX 通用金融数据查询") +async def mx_tool( + query: str = Query(..., description="自然语言问句,如:「格力电器2024年净利润」「沪深300最新收盘价」「市盈率最低的50只股票」"), +): + """通过 MX 妙想 API 查询任意金融数据(A股/港股/美股/基金/债券/指数/板块/宏观/新闻/公告/选股等) + + 单次查询最多支持 20 只证券。返回原始结构化表格(sheetName + columns + items)。 + """ + if not query or not query.strip(): + raise HTTPException(status_code=400, detail="查询内容不能为空") + data = await eastmoney.fetch_mx_tool(query.strip()) + if not data: + raise HTTPException(status_code=404, detail="MX 查询无结果或所有 API Key 已耗尽") + return {"data": data} diff --git a/backend/services/eastmoney.py b/backend/services/eastmoney.py index edafd56..4cc623d 100644 --- a/backend/services/eastmoney.py +++ b/backend/services/eastmoney.py @@ -206,6 +206,75 @@ async def fetch_mx_api(name: str, days: int) -> Optional[List[dict]]: return None +async def fetch_mx_tool(tool_query: str) -> Optional[dict]: + """通用 MX 妙想 API 查询(自然语言 → 结构化数据,缓存 6 小时) + + 支持东方财富数据库的全品类查询,包括但不限于: + - A 股/港股/美股行情、财务、估值、股本、事件 + - 基金净值、收益、持仓、排名 + - 债券基本信息、久期凸性、信用评级 + - 指数与板块行情、技术指标 + - 宏观经济/行业经济/大宗商品数据 + - 新闻研报、公告搜索 + - 多条件选股/选基/选债 + + Args: + tool_query: 自然语言问句,如 "格力电器2024年营业收入和净利润" + "沪深300最新收盘价" "市盈率最低的50只股票" + + Returns: + MX API 原始 JSON(status=0 时成功,data 中含 sheetName/columns/items) + 失败返回 None + """ + cache_key = f"mx_tool:{tool_query}" + cached = get_cache(cache_key) + if cached is not None: + return json.loads(cached) + + if not _ensure_keys(): + return None + + url = "https://mkapi2.dfcfs.com/finskillshub/api/claw/query" + payload = {"toolQuery": tool_query} + + for attempt in range(len(_api_keys)): + key = _get_key() + async with httpx.AsyncClient() as client: + try: + resp = await client.post( + url, + json=payload, + headers={"Content-Type": "application/json", "apikey": key}, + timeout=15, + ) + if resp.status_code != 200: + _rotate_key() + continue + result = resp.json() + except Exception as e: + print(f"[eastmoney] MX tool error: {e}") + _rotate_key() + continue + + status = result.get("status", -1) + if status == 113: + print(f"[eastmoney] key 已达每日上限,切换到下一个") + _rotate_key() + continue + if status == 114: + _rotate_key() + continue + if status != 0: + _rotate_key() + continue + + set_cache(cache_key, json.dumps(result, ensure_ascii=False), ttl_hours=6) + return result + + print(f"[eastmoney] 所有 MX API key 均已耗尽") + return None + + def get_eastmoney_market(code: str) -> str: """获取东方财富格式的市场标识""" if code.startswith("688"):