From 86429f43c2924698c1d652942eb16a4cb6e74f19 Mon Sep 17 00:00:00 2001 From: Sakurasan <26715255+Sakurasan@users.noreply.github.com> Date: Tue, 1 Sep 2026 17:04:38 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=9A=B4=E9=9C=B2fuyao=20SDK=E5=85=A8?= =?UTF-8?q?=E9=83=A8A=E8=82=A1=E6=8E=A5=E5=8F=A3=20+=20AI=E5=88=86?= =?UTF-8?q?=E6=9E=90=E8=BD=AE=E6=AC=A1=E4=B8=8A=E9=99=90=E6=8F=90=E8=87=B3?= =?UTF-8?q?30?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/routes/fuyao.py | 120 ++++++++++++++++++++++++++++++- backend/services/ai_service.py | 5 +- backend/services/fuyao_client.py | 24 +++++++ 3 files changed, 145 insertions(+), 4 deletions(-) diff --git a/backend/routes/fuyao.py b/backend/routes/fuyao.py index 095bc59..34ad640 100644 --- a/backend/routes/fuyao.py +++ b/backend/routes/fuyao.py @@ -7,6 +7,7 @@ 上游错误转 HTTPException,密钥永不出现在响应中。 """ +from datetime import datetime from fastapi import APIRouter, Query, HTTPException from fastapi.responses import JSONResponse from services import fuyao_client @@ -80,6 +81,16 @@ async def v2_prices_historical( return JSONResponse({"data": items, "count": len(items)}, headers=_NO_CACHE_HEADERS) +@router.get("/corporate-actions/adjustment-factors", summary="v2 复权因子事件流") +async def v2_adjustment_factors( + thscode: str = Query(..., description="标的 thscode,单只"), + from_date: str = Query(None, description="起始日 yyyy-MM-dd"), + to_date: str = Query(None, description="截止日 yyyy-MM-dd"), +): + data = await _guard(fuyao_client.corp_actions_adjustment_factors(thscode, from_date, to_date)) + return JSONResponse({"data": data}, headers=_NO_CACHE_HEADERS) + + @router.get("/calendar/trading-days", summary="v2 近一年交易日序列") async def v2_calendar(): items = await _guard(fuyao_client.calendar_trading_days()) @@ -162,4 +173,111 @@ async def v2_index_prices_historical( end: int = Query(..., description="结束时间,毫秒 Unix 时间戳"), ): items = await _guard(fuyao_client.index_prices_historical(thscode, start, end)) - return JSONResponse({"data": items, "count": len(items)}, headers=_NO_CACHE_HEADERS) \ No newline at end of file + return JSONResponse({"data": items, "count": len(items)}, headers=_NO_CACHE_HEADERS) + + +# --------------------------------------------------------------- +# 特殊数据(涨停/跌停/炸板/连板/热股/龙虎/异动/飙升) +# --------------------------------------------------------------- + +@router.get("/special/limit-up-pool", summary="v2 涨停池") +async def v2_limit_up_pool( + date: str = Query(None, description="日期 yyyy-MM-dd"), + page: int = Query(1, ge=1), + size: int = Query(50, ge=1, le=200), + sort_field: str = Query("seal_money", description="排序字段"), + sort_dir: str = Query("desc", description="asc/desc"), +): + date_ms = int(datetime.strptime(date, "%Y-%m-%d").timestamp() * 1000) if date else None + data = await _guard(fuyao_client.limit_up_pool(date_ms, page, size, sort_field, sort_dir)) + return JSONResponse({"data": data}, headers=_NO_CACHE_HEADERS) + + +@router.get("/special/limit-down-pool", summary="v2 跌停池") +async def v2_limit_down_pool( + date: str = Query(None, description="日期 yyyy-MM-dd"), + page: int = Query(1, ge=1), + size: int = Query(50, ge=1, le=200), + sort_field: str = Query("last_limit_time"), + sort_dir: str = Query("desc"), +): + date_ms = int(datetime.strptime(date, "%Y-%m-%d").timestamp() * 1000) if date else None + data = await _guard(fuyao_client.limit_down_pool(date_ms, page, size, sort_field, sort_dir)) + return JSONResponse({"data": data}, headers=_NO_CACHE_HEADERS) + + +@router.get("/special/limit-break-pool", summary="v2 炸板池") +async def v2_limit_break_pool( + date: str = Query(None, description="日期 yyyy-MM-dd"), + page: int = Query(1, ge=1), + size: int = Query(50, ge=1, le=200), + sort_field: str = Query("price_change_ratio_pct"), + sort_dir: str = Query("desc"), +): + date_ms = int(datetime.strptime(date, "%Y-%m-%d").timestamp() * 1000) if date else None + data = await _guard(fuyao_client.limit_break_pool(date_ms, page, size, sort_field, sort_dir)) + return JSONResponse({"data": data}, headers=_NO_CACHE_HEADERS) + + +@router.get("/special/limit-up-ladder", summary="v2 连板梯队") +async def v2_limit_up_ladder(): + data = await _guard(fuyao_client.limit_up_ladder()) + return JSONResponse({"data": data}, headers=_NO_CACHE_HEADERS) + + +@router.get("/special/hot-stock-list", summary="v2 热股榜") +async def v2_hot_stock_list( + period: str = Query("day", description="day/week/month"), +): + data = await _guard(fuyao_client.hot_stock_list(period)) + return JSONResponse({"data": data}, headers=_NO_CACHE_HEADERS) + + +@router.get("/special/hot-stock-history", summary="v2 热股榜历史") +async def v2_hot_stock_history(date: str = Query(..., description="日期 yyyy-MM-dd")): + data = await _guard(fuyao_client.hot_stock_list_history(date)) + return JSONResponse({"data": data}, headers=_NO_CACHE_HEADERS) + + +@router.get("/special/hot-stock-rank-trend", summary="v2 热股排名趋势") +async def v2_hot_stock_rank_trend( + thscode: str = Query(..., description="标的 thscode"), + start_date: str = Query(..., description="起始日期 yyyy-MM-dd"), + end_date: str = Query(..., description="结束日期 yyyy-MM-dd"), +): + data = await _guard(fuyao_client.hot_stock_rank_trend(thscode, start_date, end_date)) + return JSONResponse({"data": data}, headers=_NO_CACHE_HEADERS) + + +@router.get("/special/dragon-tiger-list", summary="v2 龙虎榜") +async def v2_dragon_tiger_list( + board_type: str = Query("all", description="all/sh/sz"), + date: str = Query(None, description="日期 yyyy-MM-dd"), +): + data = await _guard(fuyao_client.dragon_tiger_list(board_type, date)) + return JSONResponse({"data": data}, headers=_NO_CACHE_HEADERS) + + +@router.get("/special/anomaly-list", summary="v2 异动分析列表") +async def v2_anomaly_list( + tag_codes: str = Query(None, description="逗号分隔异动类型编码"), +): + codes = [c.strip() for c in tag_codes.split(",") if c.strip()] if tag_codes else None + data = await _guard(fuyao_client.anomaly_analysis_list(codes)) + return JSONResponse({"data": data}, headers=_NO_CACHE_HEADERS) + + +@router.get("/special/anomaly-stock", summary="v2 个股异动明细") +async def v2_anomaly_stock( + thscodes: str = Query(..., description="逗号分隔 thscode,最多50只"), +): + data = await _guard(fuyao_client.anomaly_analysis_stock(thscodes)) + return JSONResponse({"data": data}, headers=_NO_CACHE_HEADERS) + + +@router.get("/special/skyrocket-list", summary="v2 飙升榜") +async def v2_skyrocket_list( + period: str = Query("day", description="day/week/month"), +): + data = await _guard(fuyao_client.skyrocket_list(period)) + return JSONResponse({"data": data}, headers=_NO_CACHE_HEADERS) \ No newline at end of file diff --git a/backend/services/ai_service.py b/backend/services/ai_service.py index 7e6162d..d93eeda 100644 --- a/backend/services/ai_service.py +++ b/backend/services/ai_service.py @@ -122,8 +122,9 @@ async def collect_ai_analysis(trade_date: str) -> dict: total_tokens = 0 final_content = "" was_truncated = False + max_rounds = 30 # 安全上限,正常分析约 3-8 轮 - for i in range(8): + for i in range(max_rounds): response = await call_llm(messages, tools=TOOLS) total_tokens += response.get("usage", {}).get("total_tokens", 0) @@ -138,11 +139,9 @@ async def collect_ai_analysis(trade_date: str) -> dict: break if finish_reason == "length": - # 响应被截断,保存已生成的内容 was_truncated = True final_content = message.get("content") or "" if not final_content: - # 如果当前消息没有内容,尝试从之前的消息中提取 for msg in reversed(messages): if msg.get("role") == "assistant" and msg.get("content"): final_content = msg["content"] diff --git a/backend/services/fuyao_client.py b/backend/services/fuyao_client.py index d68898d..61a425f 100644 --- a/backend/services/fuyao_client.py +++ b/backend/services/fuyao_client.py @@ -135,6 +135,14 @@ async def prices_historical(thscode: str, start_ms: int, end_ms: int, interval="1d", adjust=adjust) +async def corp_actions_adjustment_factors(thscode: str, from_date: str = None, + to_date: str = None) -> dict: + data = await _run(_sdk.corp_actions_adjustment_factors, thscode, + **({"from": from_date} if from_date else {}), + **({"to": to_date} if to_date else {})) + return data or {} + + async def calendar_trading_days() -> list[dict]: return await _run(_sdk.calendar_trading_days) @@ -285,3 +293,19 @@ async def limit_break_pool(date_ms=None, page=1, size=50, data = await _run(_sdk.special_data_limit_break_pool, date_ms=date_ms, page=page, size=size, sort_field=sort_field, sort_dir=sort_dir) return data or {} + + +async def hot_stock_list_history(date: str) -> dict: + data = await _run(_sdk.special_data_hot_stock_list_history, date) + return data or {} + + +async def hot_stock_rank_trend(thscode: str, start_date: str, end_date: str) -> dict: + data = await _run(_sdk.special_data_hot_stock_rank_trend, thscode, start_date, end_date) + return data or {} + + +async def anomaly_analysis_stock(thscodes: str) -> dict: + codes = [c.strip() for c in thscodes.split(",") if c.strip()] + data = await _run(_sdk.special_data_anomaly_analysis_stock, codes) + return data or {}