feat: 暴露fuyao SDK全部A股接口 + AI分析轮次上限提至30
This commit is contained in:
@@ -7,6 +7,7 @@
|
|||||||
上游错误转 HTTPException,密钥永不出现在响应中。
|
上游错误转 HTTPException,密钥永不出现在响应中。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
from datetime import datetime
|
||||||
from fastapi import APIRouter, Query, HTTPException
|
from fastapi import APIRouter, Query, HTTPException
|
||||||
from fastapi.responses import JSONResponse
|
from fastapi.responses import JSONResponse
|
||||||
from services import fuyao_client
|
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)
|
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 近一年交易日序列")
|
@router.get("/calendar/trading-days", summary="v2 近一年交易日序列")
|
||||||
async def v2_calendar():
|
async def v2_calendar():
|
||||||
items = await _guard(fuyao_client.calendar_trading_days())
|
items = await _guard(fuyao_client.calendar_trading_days())
|
||||||
@@ -163,3 +174,110 @@ async def v2_index_prices_historical(
|
|||||||
):
|
):
|
||||||
items = await _guard(fuyao_client.index_prices_historical(thscode, start, end))
|
items = await _guard(fuyao_client.index_prices_historical(thscode, start, end))
|
||||||
return JSONResponse({"data": items, "count": len(items)}, headers=_NO_CACHE_HEADERS)
|
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)
|
||||||
@@ -122,8 +122,9 @@ async def collect_ai_analysis(trade_date: str) -> dict:
|
|||||||
total_tokens = 0
|
total_tokens = 0
|
||||||
final_content = ""
|
final_content = ""
|
||||||
was_truncated = False
|
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)
|
response = await call_llm(messages, tools=TOOLS)
|
||||||
total_tokens += response.get("usage", {}).get("total_tokens", 0)
|
total_tokens += response.get("usage", {}).get("total_tokens", 0)
|
||||||
|
|
||||||
@@ -138,11 +139,9 @@ async def collect_ai_analysis(trade_date: str) -> dict:
|
|||||||
break
|
break
|
||||||
|
|
||||||
if finish_reason == "length":
|
if finish_reason == "length":
|
||||||
# 响应被截断,保存已生成的内容
|
|
||||||
was_truncated = True
|
was_truncated = True
|
||||||
final_content = message.get("content") or ""
|
final_content = message.get("content") or ""
|
||||||
if not final_content:
|
if not final_content:
|
||||||
# 如果当前消息没有内容,尝试从之前的消息中提取
|
|
||||||
for msg in reversed(messages):
|
for msg in reversed(messages):
|
||||||
if msg.get("role") == "assistant" and msg.get("content"):
|
if msg.get("role") == "assistant" and msg.get("content"):
|
||||||
final_content = msg["content"]
|
final_content = msg["content"]
|
||||||
|
|||||||
@@ -135,6 +135,14 @@ async def prices_historical(thscode: str, start_ms: int, end_ms: int,
|
|||||||
interval="1d", adjust=adjust)
|
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]:
|
async def calendar_trading_days() -> list[dict]:
|
||||||
return await _run(_sdk.calendar_trading_days)
|
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,
|
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)
|
page=page, size=size, sort_field=sort_field, sort_dir=sort_dir)
|
||||||
return data or {}
|
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 {}
|
||||||
|
|||||||
Reference in New Issue
Block a user