213 lines
6.3 KiB
Python
213 lines
6.3 KiB
Python
"""
|
||
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))
|