Files
crypto_monitor/manual_trading_hub/hub_ai/routes.py
T
dekun bfbd6879d6 Add AI trading supervisor with WeChat push and daily session
Proactive monitoring for manual/hub closes and new opens prevents overtrading via in-app alerts, configurable WeChat links, and supervisor chat.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-23 19:25:01 +08:00

201 lines
6.6 KiB
Python

"""中控 AI FastAPI 路由。"""
from __future__ import annotations
import asyncio
from typing import Callable
from fastapi import APIRouter, Body, File, Form, HTTPException, UploadFile
from pydantic import BaseModel, Field
from hub_ai.archive_quote import send_archive_quote_review
from hub_ai.chat import (
get_chat_state,
remove_chat_session,
send_chat_message,
start_new_chat,
switch_chat_session,
)
from hub_ai.client import model_label
from hub_ai.config import trading_day_reset_hour
from hub_ai.context import build_daily_context
from hub_ai.store import get_latest_summary, list_summaries
from hub_ai.supervisor import send_supervisor_chat
from hub_ai.supervisor_store import get_supervisor_session_state
from hub_ai.summary import generate_daily_summary
from hub_trades_lib import current_trading_day
from settings_store import normalize_supervisor_settings
class ChatSendBody(BaseModel):
message: str = ""
trading_day: str = ""
class SummaryGenerateBody(BaseModel):
trading_day: str = ""
force: bool = False
class ChatNewBody(BaseModel):
trading_day: str = ""
bot_mode: str = "trading"
class ChatSwitchBody(BaseModel):
session_id: str = Field(..., min_length=1)
class ArchiveQuoteChatBody(BaseModel):
quote_date: str = ""
content: str = ""
class SupervisorChatBody(BaseModel):
message: str = ""
trading_day: str = ""
def create_hub_ai_router(*, load_all_exchanges: Callable[[], list]) -> APIRouter:
router = APIRouter(prefix="/api/ai", tags=["hub-ai"])
def _day(raw: str = "") -> str:
d = (raw or "").strip()[:10]
return d or current_trading_day(reset_hour=trading_day_reset_hour())
@router.get("/meta")
def api_ai_meta():
return {
"ok": True,
"model": model_label(),
"trading_day_reset_hour": trading_day_reset_hour(),
"trading_day": current_trading_day(reset_hour=trading_day_reset_hour()),
"storage": {
"summaries": "hub_ai_summaries.json",
"chat": "hub_ai_chat.json",
},
}
@router.get("/context")
def api_ai_context(trading_day: str = ""):
exchanges = load_all_exchanges()
ctx = build_daily_context(exchanges, trading_day=_day(trading_day))
return {"ok": True, **ctx}
@router.get("/summary")
def api_ai_summary_list(trading_day: str = ""):
day = _day(trading_day) if trading_day.strip() else ""
items = list_summaries(trading_day=day or None, limit=20)
latest = get_latest_summary(_day(trading_day)) if trading_day.strip() else (
items[0] if items else None
)
return {
"ok": True,
"trading_day": _day(trading_day) if trading_day.strip() else None,
"summaries": items,
"latest": latest,
"model": model_label(),
}
@router.post("/summary/generate")
def api_ai_summary_generate(body: SummaryGenerateBody = SummaryGenerateBody()):
exchanges = load_all_exchanges()
result = generate_daily_summary(
exchanges,
trading_day=_day(body.trading_day) if body.trading_day.strip() else None,
force=bool(body.force),
)
if not result.get("ok"):
raise HTTPException(status_code=502, detail=result.get("msg") or "生成失败")
result.pop("context", None)
return result
@router.get("/chat/session")
def api_ai_chat_session():
state = get_chat_state()
return {"ok": True, **state, "model": model_label()}
@router.post("/chat/new")
def api_ai_chat_new(body: ChatNewBody = ChatNewBody()):
day = _day(body.trading_day)
return start_new_chat(trading_day=day, bot_mode=body.bot_mode or "trading")
@router.post("/chat/switch")
def api_ai_chat_switch(body: ChatSwitchBody):
try:
return switch_chat_session(body.session_id.strip())
except KeyError:
raise HTTPException(status_code=404, detail="会话不存在")
@router.delete("/chat/session/{session_id}")
def api_ai_chat_delete(session_id: str):
result = remove_chat_session(session_id.strip())
if not result.get("ok"):
raise HTTPException(status_code=404, detail="会话不存在")
return result
@router.post("/chat/archive-quote")
def api_ai_chat_archive_quote(body: ArchiveQuoteChatBody = Body(...)):
result = send_archive_quote_review(
quote_date=body.quote_date,
content=body.content,
)
if not result.get("ok"):
raise HTTPException(status_code=502, detail=result.get("msg") or "发送失败")
return result
@router.post("/chat/send")
async def api_ai_chat_send(
message: str = Form(""),
trading_day: str = Form(""),
files: list[UploadFile] = File(default=[]),
):
exchanges = load_all_exchanges()
raw_attachments = []
for f in files or []:
if not f or not f.filename:
continue
data = await f.read()
raw_attachments.append(
{
"filename": f.filename,
"content_type": f.content_type or "",
"data": data,
}
)
result = await asyncio.to_thread(
send_chat_message,
exchanges,
message,
trading_day=_day(trading_day) if trading_day.strip() else None,
raw_attachments=raw_attachments,
)
if not result.get("ok"):
raise HTTPException(status_code=502, detail=result.get("msg") or "发送失败")
return result
@router.get("/supervisor/session")
def api_ai_supervisor_session(trading_day: str = ""):
day = _day(trading_day)
return get_supervisor_session_state(day)
@router.get("/supervisor/rules")
def api_ai_supervisor_rules():
from settings_store import load_settings
cfg = normalize_supervisor_settings(load_settings().get("supervisor"))
return {"ok": True, "supervisor": cfg}
@router.post("/supervisor/chat/send")
def api_ai_supervisor_chat_send(body: SupervisorChatBody = SupervisorChatBody()):
exchanges = load_all_exchanges()
result = send_supervisor_chat(
exchanges,
body.message,
trading_day=_day(body.trading_day) if body.trading_day.strip() else None,
)
if not result.get("ok"):
raise HTTPException(status_code=502, detail=result.get("msg") or "发送失败")
return result
return router