stock-ai-agent/backend/app/api/astock.py
2026-03-11 00:01:18 +08:00

213 lines
6.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"""
A股相关 API 路由
"""
from fastapi import APIRouter, HTTPException, BackgroundTasks
from typing import Dict, Any
from app.utils.logger import logger
from app.config import get_settings
router = APIRouter()
# 全局变量,用于访问智能体实例
_astock_agent_instance = None
def set_astock_agent(agent):
"""设置智能体实例(由 main.py 调用)"""
global _astock_agent_instance
_astock_agent_instance = agent
@router.get("/status")
async def get_astock_status() -> Dict[str, Any]:
"""
获取A股智能体状态
Returns:
智能体状态信息
"""
try:
if _astock_agent_instance is None:
return {
"enabled": False,
"message": "A股智能体未启用"
}
settings = get_settings()
return {
"enabled": True,
"running": _astock_agent_instance.running,
"selector_type": "short_term_thematic",
"description": "短期题材选股器(题材轮动 + 技术面确认 + 风险控制)",
"config": {
"min_market_cap": settings.astock_min_market_cap if hasattr(settings, 'astock_min_market_cap') else 50,
"max_market_cap": settings.astock_max_market_cap if hasattr(settings, 'astock_max_market_cap') else 500,
"change_threshold": settings.astock_change_threshold,
"top_n": settings.astock_top_n
}
}
except Exception as e:
logger.error(f"获取A股状态失败: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/select")
async def trigger_selection(background_tasks: BackgroundTasks) -> Dict[str, Any]:
"""
手动触发选股
Returns:
选股任务状态
"""
try:
if _astock_agent_instance is None:
raise HTTPException(status_code=400, detail="A股智能体未启用")
# 在后台执行选股任务
background_tasks.add_task(_astock_agent_instance.run_once)
return {
"success": True,
"message": "选股任务已提交,正在后台执行",
"note": "请查看通知或日志获取结果"
}
except HTTPException:
raise
except Exception as e:
logger.error(f"触发选股失败: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.post("/select/sync")
async def trigger_selection_sync() -> Dict[str, Any]:
"""
手动触发选股(同步执行)
Returns:
选股结果
"""
try:
if _astock_agent_instance is None:
raise HTTPException(status_code=400, detail="A股智能体未启用")
# 同步执行选股
result = await _astock_agent_instance.run_once()
return {
"success": True,
"result": result
}
except HTTPException:
raise
except Exception as e:
logger.error(f"触发选股失败: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/config")
async def get_astock_config() -> Dict[str, Any]:
"""
获取A股选股配置
Returns:
配置信息
"""
try:
settings = get_settings()
return {
"selector": {
"type": "short_term_thematic",
"description": "短期题材选股器",
"strategy": "题材轮动 + 技术面确认 + 风险控制"
},
"screening": {
"min_market_cap": 50, # 最小市值(亿)
"max_market_cap": 500, # 最大市值(亿)
"min_turnover": 3.0, # 最小换手率(%
"max_turnover": 15.0, # 最大换手率(%
"sector_change_threshold": 2.0, # 板块涨幅阈值(%
"volume_ratio_threshold": 1.2 # 量比阈值
},
"risk_control": {
"max_drawdown": 10.0, # 最大回撤(%
"hard_stop_loss": -7.0, # 硬止损(%
"max_single_position": 20, # 单票最大仓位(%
"max_sector_position": 40, # 单行业最大仓位(%
"max_total_position": 80 # 总仓位最大值(%
},
"schedule": {
"enabled": settings.astock_monitor_enabled,
"time": "15:30", # 盘后运行
"timezone": "Asia/Shanghai"
},
"notifications": {
"dingtalk": settings.dingtalk_enabled,
"telegram": settings.telegram_enabled
}
}
except Exception as e:
logger.error(f"获取配置失败: {e}")
raise HTTPException(status_code=500, detail=str(e))
@router.get("/sectors")
async def get_hot_sectors(limit: int = 10) -> Dict[str, Any]:
"""
获取当前异动板块
Args:
limit: 返回板块数量
Returns:
异动板块列表
"""
try:
from app.astock_agent.tushare_client import get_tushare_client
from app.config import get_settings
settings = get_settings()
ts_client = get_tushare_client(settings.tushare_token)
if not ts_client:
raise HTTPException(status_code=400, detail="Tushare客户端未初始化")
# 获取异动板块
sectors_df = ts_client.get_hot_sectors(threshold=2.0)
if sectors_df.empty:
return {
"success": True,
"count": 0,
"sectors": []
}
# 转换为列表格式
sectors = []
for _, row in sectors_df.head(limit).iterrows():
sectors.append({
"code": row['ts_code'],
"name": row['name'],
"change_pct": float(row['change_pct']),
"amount": float(row['amount']),
"amount_yi": float(row['amount']) / 100000000, # 转换为亿
"close": float(row['close'])
})
return {
"success": True,
"count": len(sectors),
"sectors": sectors
}
except HTTPException:
raise
except Exception as e:
logger.error(f"获取异动板块失败: {e}")
raise HTTPException(status_code=500, detail=str(e))