133 lines
4.0 KiB
Python
133 lines
4.0 KiB
Python
"""市场概览 API"""
|
||
|
||
from fastapi import APIRouter
|
||
|
||
from app.data.tushare_client import tushare_client
|
||
from app.data import tencent_client
|
||
from app.engine.recommender import get_latest_recommendations
|
||
from app.config import is_trading_hours, is_market_session
|
||
|
||
router = APIRouter(prefix="/api/market", tags=["market"])
|
||
|
||
|
||
@router.get("/temperature")
|
||
async def get_temperature():
|
||
"""获取市场温度(只读缓存,不触发扫描)"""
|
||
result = await get_latest_recommendations()
|
||
mt = result.get("market_temp")
|
||
if mt:
|
||
return {
|
||
"trade_date": mt.trade_date,
|
||
"temperature": mt.temperature,
|
||
"up_count": mt.up_count,
|
||
"down_count": mt.down_count,
|
||
"limit_up_count": mt.limit_up_count,
|
||
"limit_down_count": mt.limit_down_count,
|
||
"max_streak": mt.max_streak,
|
||
"broken_rate": mt.broken_rate,
|
||
"index_above_ma20": getattr(mt, "index_above_ma20", False),
|
||
"is_trading": is_trading_hours(),
|
||
}
|
||
return {
|
||
"trade_date": "",
|
||
"temperature": 0,
|
||
"up_count": 0,
|
||
"down_count": 0,
|
||
"limit_up_count": 0,
|
||
"limit_down_count": 0,
|
||
"max_streak": 0,
|
||
"broken_rate": 0,
|
||
"index_above_ma20": False,
|
||
"is_trading": is_trading_hours(),
|
||
}
|
||
|
||
|
||
@router.get("/overview")
|
||
async def get_overview():
|
||
"""市场概况:上证、深证、创业板指数
|
||
|
||
盘中用腾讯实时行情,盘后用 Tushare 日线(有缓存)。
|
||
"""
|
||
if is_market_session():
|
||
return await _overview_realtime()
|
||
return _overview_daily()
|
||
|
||
|
||
@router.get("/daily-review")
|
||
async def get_daily_review():
|
||
"""获取每日复盘报告"""
|
||
from sqlalchemy import text
|
||
from app.db.database import get_db
|
||
async with get_db() as db:
|
||
result = await db.execute(
|
||
text("SELECT * FROM daily_reviews ORDER BY trade_date DESC LIMIT 5")
|
||
)
|
||
reviews = []
|
||
for row in result.fetchall():
|
||
r = row._mapping
|
||
reviews.append({
|
||
"trade_date": r["trade_date"],
|
||
"content": r["content"] or "",
|
||
"created_at": str(r["created_at"]) if r["created_at"] else "",
|
||
})
|
||
return {"reviews": reviews}
|
||
|
||
|
||
@router.post("/generate-review")
|
||
async def generate_daily_review():
|
||
"""手动触发生成每日复盘"""
|
||
from app.llm.daily_review import generate_review
|
||
result = await generate_review()
|
||
return result
|
||
|
||
|
||
async def _overview_realtime():
|
||
"""盘中:腾讯实时指数行情"""
|
||
index_data = await tencent_client.get_index_realtime()
|
||
result = []
|
||
name_map = {
|
||
"000001.SH": "上证指数",
|
||
"399001.SZ": "深证成指",
|
||
"399006.SZ": "创业板指",
|
||
}
|
||
for code in ["000001.SH", "399001.SZ", "399006.SZ"]:
|
||
data = index_data.get(code)
|
||
if not data:
|
||
continue
|
||
result.append({
|
||
"name": name_map.get(code, data.get("name", code)),
|
||
"code": code,
|
||
"close": round(data["price"], 2),
|
||
"pct_chg": round(data["pct_chg"], 2),
|
||
"volume": round(data["volume"], 2),
|
||
"realtime": True,
|
||
})
|
||
return result
|
||
|
||
|
||
def _overview_daily():
|
||
"""盘后:Tushare 日线数据"""
|
||
indices = {
|
||
"上证指数": "000001.SH",
|
||
"深证成指": "399001.SZ",
|
||
"创业板指": "399006.SZ",
|
||
}
|
||
result = []
|
||
for name, code in indices.items():
|
||
df = tushare_client.get_index_daily(code, days=5)
|
||
if df.empty:
|
||
continue
|
||
df = df.sort_values("trade_date")
|
||
latest = df.iloc[-1]
|
||
prev = df.iloc[-2] if len(df) > 1 else latest
|
||
pct = (latest["close"] - prev["close"]) / prev["close"] * 100
|
||
result.append({
|
||
"name": name,
|
||
"code": code,
|
||
"close": round(float(latest["close"]), 2),
|
||
"pct_chg": round(pct, 2),
|
||
"volume": round(float(latest["vol"]), 2),
|
||
"realtime": False,
|
||
})
|
||
return result
|