feat: complete trading system with FastAPI backend, web frontend, and auto-analysis

This commit is contained in:
Stock Buddy Bot
2026-03-22 21:59:14 +08:00
parent 17c124009f
commit 3d8e59cc0e
16 changed files with 2923 additions and 0 deletions

250
backend/main.py Normal file
View File

@@ -0,0 +1,250 @@
"""
Stock Buddy 交易系统 - 主入口
FastAPI + SQLite + APScheduler
"""
from fastapi import FastAPI, HTTPException, BackgroundTasks
from fastapi.middleware.cors import CORSMiddleware
from fastapi.staticfiles import StaticFiles
from contextlib import asynccontextmanager
from apscheduler.schedulers.asyncio import AsyncIOScheduler
from apscheduler.triggers.cron import CronTrigger
from datetime import datetime, timedelta
import uvicorn
from database import init_db, SessionLocal
from models import PositionCreate, PositionResponse, StockAnalysisRequest, AnalysisResult
from services.stock_service import StockService
from services.sentiment_service import SentimentService
from services.strategy_service import StrategyService
from services.llm_service import LLMService
# 初始化数据库
init_db()
# 定时任务调度器
scheduler = AsyncIOScheduler()
async def daily_analysis_task():
"""每日定时任务:分析所有持仓股票"""
print(f"[{datetime.now()}] 开始每日自动分析...")
db = SessionLocal()
try:
stock_service = StockService(db)
sentiment_service = SentimentService(db)
strategy_service = StrategyService(db)
llm_service = LLMService()
# 获取所有持仓
positions = stock_service.get_all_positions()
for pos in positions:
try:
# 1. 更新股票数据
stock_data = stock_service.update_stock_data(pos.ticker)
# 2. 生成舆情分析
sentiment = await llm_service.analyze_sentiment(pos.name, pos.ticker)
sentiment_service.save_sentiment(pos.ticker, sentiment)
# 3. 计算策略信号
signal = strategy_service.calculate_signal(
pos.ticker,
stock_data,
sentiment['score']
)
# 4. 保存分析结果
stock_service.save_analysis_result(pos.ticker, {
'signal': signal,
'sentiment': sentiment,
'updated_at': datetime.now().isoformat()
})
print(f"{pos.name}: {signal['action']} (评分:{signal['score']:.2f})")
except Exception as e:
print(f"{pos.name}: {e}")
print(f"[{datetime.now()}] 每日分析完成")
finally:
db.close()
@asynccontextmanager
async def lifespan(app: FastAPI):
"""应用生命周期管理"""
# 启动时
print("🚀 Stock Buddy 交易系统启动")
# 启动定时任务每天9:00运行
scheduler.add_job(
daily_analysis_task,
CronTrigger(hour=9, minute=0),
id='daily_analysis',
replace_existing=True
)
scheduler.start()
print("⏰ 定时任务已启动每天9:00")
yield
# 关闭时
scheduler.shutdown()
print("🛑 系统关闭")
app = FastAPI(
title="Stock Buddy API",
description="港股AI交易分析系统",
version="1.0.0",
lifespan=lifespan
)
# CORS配置
app.add_middleware(
CORSMiddleware,
allow_origins=["*"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# ═════════════════════════════════════════════════════════════════════
# API路由
# ═════════════════════════════════════════════════════════════════════
@app.get("/")
async def root():
return {"message": "Stock Buddy API", "version": "1.0.0"}
# ═════════════════════════════════════════════════════════════════════
# 持仓管理
# ═════════════════════════════════════════════════════════════════════
@app.get("/api/positions", response_model=list[PositionResponse])
async def get_positions():
"""获取所有持仓"""
db = SessionLocal()
try:
service = StockService(db)
return service.get_all_positions()
finally:
db.close()
@app.post("/api/positions", response_model=PositionResponse)
async def create_position(position: PositionCreate):
"""添加持仓"""
db = SessionLocal()
try:
service = StockService(db)
return service.create_position(position)
finally:
db.close()
@app.delete("/api/positions/{position_id}")
async def delete_position(position_id: int):
"""删除持仓"""
db = SessionLocal()
try:
service = StockService(db)
service.delete_position(position_id)
return {"message": "删除成功"}
finally:
db.close()
@app.put("/api/positions/{position_id}", response_model=PositionResponse)
async def update_position(position_id: int, position: PositionCreate):
"""更新持仓"""
db = SessionLocal()
try:
service = StockService(db)
return service.update_position(position_id, position)
finally:
db.close()
# ═════════════════════════════════════════════════════════════════════
# 分析功能
# ═════════════════════════════════════════════════════════════════════
@app.post("/api/analyze", response_model=AnalysisResult)
async def analyze_stock(request: StockAnalysisRequest):
"""手动分析股票(支持新增股票)"""
db = SessionLocal()
try:
stock_service = StockService(db)
sentiment_service = SentimentService(db)
strategy_service = StrategyService(db)
llm_service = LLMService()
# 1. 获取/更新股票数据
ticker = request.ticker if request.ticker else stock_service.search_ticker(request.stock_name)
stock_data = stock_service.update_stock_data(ticker)
# 2. 生成舆情分析(异步)
sentiment = await llm_service.analyze_sentiment(request.stock_name, ticker)
sentiment_service.save_sentiment(ticker, sentiment)
# 3. 计算策略信号
signal = strategy_service.calculate_signal(ticker, stock_data, sentiment['score'])
# 4. 技术分析详情
tech_analysis = strategy_service.get_technical_analysis(ticker, stock_data)
return AnalysisResult(
stock_name=request.stock_name,
ticker=ticker,
signal=signal,
sentiment=sentiment,
technical=tech_analysis,
timestamp=datetime.now().isoformat()
)
except Exception as e:
raise HTTPException(status_code=500, detail=str(e))
finally:
db.close()
@app.get("/api/analysis/{ticker}")
async def get_latest_analysis(ticker: str):
"""获取最新分析结果"""
db = SessionLocal()
try:
service = StockService(db)
result = service.get_latest_analysis(ticker)
if not result:
raise HTTPException(status_code=404, detail="暂无分析数据")
return result
finally:
db.close()
# ═════════════════════════════════════════════════════════════════════
# 实时行情
# ═════════════════════════════════════════════════════════════════════
@app.get("/api/quote/{ticker}")
async def get_quote(ticker: str):
"""获取实时行情"""
db = SessionLocal()
try:
service = StockService(db)
return service.get_realtime_quote(ticker)
finally:
db.close()
# ═════════════════════════════════════════════════════════════════════
# 手动触发任务
# ═════════════════════════════════════════════════════════════════════
@app.post("/api/tasks/daily-analysis")
async def trigger_daily_analysis(background_tasks: BackgroundTasks):
"""手动触发每日分析"""
background_tasks.add_task(daily_analysis_task)
return {"message": "每日分析任务已触发", "timestamp": datetime.now().isoformat()}
# ═════════════════════════════════════════════════════════════════════
# 前端静态文件
# ═════════════════════════════════════════════════════════════════════
app.mount("/app", StaticFiles(directory="../frontend", html=True), name="frontend")
if __name__ == "__main__":
uvicorn.run("main:app", host="0.0.0.0", port=8000, reload=True)