|
|
|
|
|
"""
|
|
|
|
|
|
复盘计划接口 - 提供复盘与交易计划相关数据 (V1 + V2)
|
|
|
|
|
|
"""
|
|
|
|
|
|
import logging
|
|
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
from typing import Optional, List
|
|
|
|
|
|
|
|
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
|
|
|
|
from pydantic import BaseModel
|
|
|
|
|
|
from sqlalchemy.orm import Session
|
|
|
|
|
|
|
|
|
|
|
|
from app.database import get_db
|
|
|
|
|
|
from app.analysis_db import get_analysis_db
|
|
|
|
|
|
from app.analysis_models import (
|
|
|
|
|
|
ReviewDate, SymbolRanking, TradingPlan,
|
|
|
|
|
|
ReviewPlanV2, SymbolScoreV2, TradingPlanV2, SectorHeat,
|
|
|
|
|
|
)
|
|
|
|
|
|
from app.services.plan_generator import generate_plan, save_plan_to_db, get_plan_data
|
|
|
|
|
|
|
|
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
router = APIRouter(prefix="/review", tags=["复盘计划"])
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==================== 请求体模型 ====================
|
|
|
|
|
|
|
|
|
|
|
|
class GeneratePlanRequest(BaseModel):
|
|
|
|
|
|
review_date: str
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==================== V1 复盘日期管理 ====================
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/dates")
|
|
|
|
|
|
def get_review_dates(limit: int = 10, db: Session = Depends(get_analysis_db)):
|
|
|
|
|
|
"""获取复盘日期列表"""
|
|
|
|
|
|
dates = db.query(ReviewDate).order_by(
|
|
|
|
|
|
ReviewDate.review_date.desc()
|
|
|
|
|
|
).limit(limit).all()
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": True,
|
|
|
|
|
|
"data": [{
|
|
|
|
|
|
"id": d.id,
|
|
|
|
|
|
"review_date": d.review_date,
|
|
|
|
|
|
"week_day": d.week_day
|
|
|
|
|
|
} for d in dates]
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/dates")
|
|
|
|
|
|
def create_review_date(
|
|
|
|
|
|
review_date: str,
|
|
|
|
|
|
week_day: Optional[str] = None,
|
|
|
|
|
|
db: Session = Depends(get_analysis_db)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""创建复盘日期"""
|
|
|
|
|
|
existing = db.query(ReviewDate).filter(
|
|
|
|
|
|
ReviewDate.review_date == review_date
|
|
|
|
|
|
).first()
|
|
|
|
|
|
|
|
|
|
|
|
if existing:
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": False,
|
|
|
|
|
|
"message": f"日期 {review_date} 已存在"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if not week_day:
|
|
|
|
|
|
try:
|
|
|
|
|
|
dt = datetime.strptime(review_date, "%Y-%m-%d")
|
|
|
|
|
|
week_days = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
|
|
|
|
|
|
week_day = week_days[dt.weekday()]
|
|
|
|
|
|
except:
|
|
|
|
|
|
week_day = ""
|
|
|
|
|
|
|
|
|
|
|
|
new_date = ReviewDate(
|
|
|
|
|
|
review_date=review_date,
|
|
|
|
|
|
week_day=week_day
|
|
|
|
|
|
)
|
|
|
|
|
|
db.add(new_date)
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
db.refresh(new_date)
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": True,
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"id": new_date.id,
|
|
|
|
|
|
"review_date": new_date.review_date,
|
|
|
|
|
|
"week_day": new_date.week_day
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==================== V1 品种排名 ====================
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/rankings/{review_date_id}")
|
|
|
|
|
|
def get_rankings(review_date_id: int, db: Session = Depends(get_analysis_db)):
|
|
|
|
|
|
"""获取指定日期的品种排名"""
|
|
|
|
|
|
review_date = db.query(ReviewDate).filter(
|
|
|
|
|
|
ReviewDate.id == review_date_id
|
|
|
|
|
|
).first()
|
|
|
|
|
|
|
|
|
|
|
|
if not review_date:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="复盘日期不存在")
|
|
|
|
|
|
|
|
|
|
|
|
rankings = db.query(SymbolRanking).filter(
|
|
|
|
|
|
SymbolRanking.review_date_id == review_date_id
|
|
|
|
|
|
).order_by(
|
|
|
|
|
|
SymbolRanking.rank_type,
|
|
|
|
|
|
SymbolRanking.rank
|
|
|
|
|
|
).all()
|
|
|
|
|
|
|
|
|
|
|
|
rank_types = ["volume", "amplitude", "change", "open_interest"]
|
|
|
|
|
|
rank_type_names = {
|
|
|
|
|
|
"volume": "成交量排名",
|
|
|
|
|
|
"amplitude": "振幅排名",
|
|
|
|
|
|
"change": "涨跌幅排名",
|
|
|
|
|
|
"open_interest": "持仓量排名"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
result = {}
|
|
|
|
|
|
for rt in rank_types:
|
|
|
|
|
|
result[rt] = {
|
|
|
|
|
|
"title": rank_type_names.get(rt, rt),
|
|
|
|
|
|
"items": []
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for r in rankings:
|
|
|
|
|
|
if r.rank_type in result:
|
|
|
|
|
|
result[r.rank_type]["items"].append({
|
|
|
|
|
|
"id": r.id,
|
|
|
|
|
|
"symbol": r.symbol,
|
|
|
|
|
|
"name": r.name or r.symbol,
|
|
|
|
|
|
"rank": r.rank,
|
|
|
|
|
|
"value": r.value,
|
|
|
|
|
|
"price": r.price,
|
|
|
|
|
|
"change_pct": r.change_pct
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": True,
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"review_date": review_date.review_date,
|
|
|
|
|
|
"week_day": review_date.week_day,
|
|
|
|
|
|
"rankings": result
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/rankings")
|
|
|
|
|
|
def create_or_update_rankings(
|
|
|
|
|
|
review_date_id: int,
|
|
|
|
|
|
rankings: List[dict],
|
|
|
|
|
|
db: Session = Depends(get_analysis_db)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""创建或更新品种排名"""
|
|
|
|
|
|
review_date = db.query(ReviewDate).filter(
|
|
|
|
|
|
ReviewDate.id == review_date_id
|
|
|
|
|
|
).first()
|
|
|
|
|
|
|
|
|
|
|
|
if not review_date:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="复盘日期不存在")
|
|
|
|
|
|
|
|
|
|
|
|
db.query(SymbolRanking).filter(
|
|
|
|
|
|
SymbolRanking.review_date_id == review_date_id
|
|
|
|
|
|
).delete()
|
|
|
|
|
|
|
|
|
|
|
|
created = []
|
|
|
|
|
|
for r in rankings:
|
|
|
|
|
|
new_ranking = SymbolRanking(
|
|
|
|
|
|
review_date_id=review_date_id,
|
|
|
|
|
|
symbol=r.get("symbol"),
|
|
|
|
|
|
name=r.get("name"),
|
|
|
|
|
|
rank_type=r.get("rank_type"),
|
|
|
|
|
|
rank=r.get("rank", 1),
|
|
|
|
|
|
value=r.get("value"),
|
|
|
|
|
|
price=r.get("price"),
|
|
|
|
|
|
change_pct=r.get("change_pct")
|
|
|
|
|
|
)
|
|
|
|
|
|
db.add(new_ranking)
|
|
|
|
|
|
created.append({
|
|
|
|
|
|
"id": new_ranking.id,
|
|
|
|
|
|
"symbol": new_ranking.symbol,
|
|
|
|
|
|
"rank_type": new_ranking.rank_type,
|
|
|
|
|
|
"rank": new_ranking.rank
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": True,
|
|
|
|
|
|
"data": created
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==================== V1 交易计划 ====================
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/plans/{review_date_id}")
|
|
|
|
|
|
def get_trading_plans(review_date_id: int, db: Session = Depends(get_analysis_db)):
|
|
|
|
|
|
"""获取指定日期的交易计划"""
|
|
|
|
|
|
review_date = db.query(ReviewDate).filter(
|
|
|
|
|
|
ReviewDate.id == review_date_id
|
|
|
|
|
|
).first()
|
|
|
|
|
|
|
|
|
|
|
|
if not review_date:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="复盘日期不存在")
|
|
|
|
|
|
|
|
|
|
|
|
plans = db.query(TradingPlan).filter(
|
|
|
|
|
|
TradingPlan.review_date_id == review_date_id
|
|
|
|
|
|
).order_by(
|
|
|
|
|
|
TradingPlan.score.desc()
|
|
|
|
|
|
).all()
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": True,
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"review_date": review_date.review_date,
|
|
|
|
|
|
"week_day": review_date.week_day,
|
|
|
|
|
|
"plans": [{
|
|
|
|
|
|
"id": p.id,
|
|
|
|
|
|
"symbol": p.symbol,
|
|
|
|
|
|
"name": p.name or p.symbol,
|
|
|
|
|
|
"plan_type": p.plan_type,
|
|
|
|
|
|
"score": p.score,
|
|
|
|
|
|
"logic": p.logic,
|
|
|
|
|
|
"reason": p.reason,
|
|
|
|
|
|
"entry_price": p.entry_price,
|
|
|
|
|
|
"stop_loss": p.stop_loss,
|
|
|
|
|
|
"take_profit": p.take_profit,
|
|
|
|
|
|
"confidence": p.confidence,
|
|
|
|
|
|
"position_suggestion": p.position_suggestion,
|
|
|
|
|
|
"created_at": p.created_at.isoformat() if p.created_at else None
|
|
|
|
|
|
} for p in plans]
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/plans")
|
|
|
|
|
|
def create_or_update_plans(
|
|
|
|
|
|
review_date_id: int,
|
|
|
|
|
|
plans: List[dict],
|
|
|
|
|
|
db: Session = Depends(get_analysis_db)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""创建或更新交易计划"""
|
|
|
|
|
|
review_date = db.query(ReviewDate).filter(
|
|
|
|
|
|
ReviewDate.id == review_date_id
|
|
|
|
|
|
).first()
|
|
|
|
|
|
|
|
|
|
|
|
if not review_date:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="复盘日期不存在")
|
|
|
|
|
|
|
|
|
|
|
|
db.query(TradingPlan).filter(
|
|
|
|
|
|
TradingPlan.review_date_id == review_date_id
|
|
|
|
|
|
).delete()
|
|
|
|
|
|
|
|
|
|
|
|
created = []
|
|
|
|
|
|
for p in plans:
|
|
|
|
|
|
new_plan = TradingPlan(
|
|
|
|
|
|
review_date_id=review_date_id,
|
|
|
|
|
|
symbol=p.get("symbol"),
|
|
|
|
|
|
name=p.get("name"),
|
|
|
|
|
|
plan_type=p.get("plan_type"),
|
|
|
|
|
|
score=p.get("score", 0),
|
|
|
|
|
|
logic=p.get("logic"),
|
|
|
|
|
|
reason=p.get("reason"),
|
|
|
|
|
|
entry_price=p.get("entry_price"),
|
|
|
|
|
|
stop_loss=p.get("stop_loss"),
|
|
|
|
|
|
take_profit=p.get("take_profit"),
|
|
|
|
|
|
confidence=p.get("confidence"),
|
|
|
|
|
|
position_suggestion=p.get("position_suggestion")
|
|
|
|
|
|
)
|
|
|
|
|
|
db.add(new_plan)
|
|
|
|
|
|
created.append({
|
|
|
|
|
|
"id": new_plan.id,
|
|
|
|
|
|
"symbol": new_plan.symbol,
|
|
|
|
|
|
"plan_type": new_plan.plan_type,
|
|
|
|
|
|
"score": new_plan.score
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": True,
|
|
|
|
|
|
"data": created
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/plans/batch")
|
|
|
|
|
|
def batch_create_review_data(
|
|
|
|
|
|
review_data: dict,
|
|
|
|
|
|
db: Session = Depends(get_analysis_db)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""批量创建复盘数据(日期+排名+计划)"""
|
|
|
|
|
|
review_date_str = review_data.get("review_date")
|
|
|
|
|
|
week_day = review_data.get("week_day")
|
|
|
|
|
|
|
|
|
|
|
|
existing = db.query(ReviewDate).filter(
|
|
|
|
|
|
ReviewDate.review_date == review_date_str
|
|
|
|
|
|
).first()
|
|
|
|
|
|
|
|
|
|
|
|
if existing:
|
|
|
|
|
|
review_date_id = existing.id
|
|
|
|
|
|
else:
|
|
|
|
|
|
if not week_day:
|
|
|
|
|
|
try:
|
|
|
|
|
|
dt = datetime.strptime(review_date_str, "%Y-%m-%d")
|
|
|
|
|
|
week_days = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
|
|
|
|
|
|
week_day = week_days[dt.weekday()]
|
|
|
|
|
|
except:
|
|
|
|
|
|
week_day = ""
|
|
|
|
|
|
|
|
|
|
|
|
new_date = ReviewDate(
|
|
|
|
|
|
review_date=review_date_str,
|
|
|
|
|
|
week_day=week_day
|
|
|
|
|
|
)
|
|
|
|
|
|
db.add(new_date)
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
db.refresh(new_date)
|
|
|
|
|
|
review_date_id = new_date.id
|
|
|
|
|
|
|
|
|
|
|
|
rankings = review_data.get("rankings", [])
|
|
|
|
|
|
if rankings:
|
|
|
|
|
|
db.query(SymbolRanking).filter(
|
|
|
|
|
|
SymbolRanking.review_date_id == review_date_id
|
|
|
|
|
|
).delete()
|
|
|
|
|
|
|
|
|
|
|
|
for r in rankings:
|
|
|
|
|
|
new_ranking = SymbolRanking(
|
|
|
|
|
|
review_date_id=review_date_id,
|
|
|
|
|
|
symbol=r.get("symbol"),
|
|
|
|
|
|
name=r.get("name"),
|
|
|
|
|
|
rank_type=r.get("rank_type"),
|
|
|
|
|
|
rank=r.get("rank", 1),
|
|
|
|
|
|
value=r.get("value"),
|
|
|
|
|
|
price=r.get("price"),
|
|
|
|
|
|
change_pct=r.get("change_pct")
|
|
|
|
|
|
)
|
|
|
|
|
|
db.add(new_ranking)
|
|
|
|
|
|
|
|
|
|
|
|
plans = review_data.get("plans", [])
|
|
|
|
|
|
if plans:
|
|
|
|
|
|
db.query(TradingPlan).filter(
|
|
|
|
|
|
TradingPlan.review_date_id == review_date_id
|
|
|
|
|
|
).delete()
|
|
|
|
|
|
|
|
|
|
|
|
for p in plans:
|
|
|
|
|
|
new_plan = TradingPlan(
|
|
|
|
|
|
review_date_id=review_date_id,
|
|
|
|
|
|
symbol=p.get("symbol"),
|
|
|
|
|
|
name=p.get("name"),
|
|
|
|
|
|
plan_type=p.get("plan_type"),
|
|
|
|
|
|
score=p.get("score", 0),
|
|
|
|
|
|
logic=p.get("logic"),
|
|
|
|
|
|
reason=p.get("reason"),
|
|
|
|
|
|
entry_price=p.get("entry_price"),
|
|
|
|
|
|
stop_loss=p.get("stop_loss"),
|
|
|
|
|
|
take_profit=p.get("take_profit"),
|
|
|
|
|
|
confidence=p.get("confidence"),
|
|
|
|
|
|
position_suggestion=p.get("position_suggestion")
|
|
|
|
|
|
)
|
|
|
|
|
|
db.add(new_plan)
|
|
|
|
|
|
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": True,
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"review_date_id": review_date_id,
|
|
|
|
|
|
"review_date": review_date_str,
|
|
|
|
|
|
"rankings_count": len(rankings),
|
|
|
|
|
|
"plans_count": len(plans)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/summary/{review_date_id}")
|
|
|
|
|
|
def get_review_summary(review_date_id: int, db: Session = Depends(get_analysis_db)):
|
|
|
|
|
|
"""获取复盘摘要(包含排名和计划的完整数据)"""
|
|
|
|
|
|
review_date = db.query(ReviewDate).filter(
|
|
|
|
|
|
ReviewDate.id == review_date_id
|
|
|
|
|
|
).first()
|
|
|
|
|
|
|
|
|
|
|
|
if not review_date:
|
|
|
|
|
|
raise HTTPException(status_code=404, detail="复盘日期不存在")
|
|
|
|
|
|
|
|
|
|
|
|
rankings = db.query(SymbolRanking).filter(
|
|
|
|
|
|
SymbolRanking.review_date_id == review_date_id
|
|
|
|
|
|
).order_by(
|
|
|
|
|
|
SymbolRanking.rank_type,
|
|
|
|
|
|
SymbolRanking.rank
|
|
|
|
|
|
).all()
|
|
|
|
|
|
|
|
|
|
|
|
plans = db.query(TradingPlan).filter(
|
|
|
|
|
|
TradingPlan.review_date_id == review_date_id
|
|
|
|
|
|
).order_by(
|
|
|
|
|
|
TradingPlan.score.desc()
|
|
|
|
|
|
).all()
|
|
|
|
|
|
|
|
|
|
|
|
rank_types = ["volume", "amplitude", "change", "open_interest"]
|
|
|
|
|
|
rank_type_names = {
|
|
|
|
|
|
"volume": "成交量排名 Top 5",
|
|
|
|
|
|
"amplitude": "振幅排名 Top 5",
|
|
|
|
|
|
"change": "涨跌幅排名 Top 5",
|
|
|
|
|
|
"open_interest": "持仓量排名 Top 5"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
rankings_result = {}
|
|
|
|
|
|
for rt in rank_types:
|
|
|
|
|
|
rankings_result[rt] = {
|
|
|
|
|
|
"title": rank_type_names.get(rt, rt),
|
|
|
|
|
|
"items": []
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
for r in rankings:
|
|
|
|
|
|
if r.rank_type in rankings_result:
|
|
|
|
|
|
rankings_result[r.rank_type]["items"].append({
|
|
|
|
|
|
"symbol": r.symbol,
|
|
|
|
|
|
"name": r.name or r.symbol,
|
|
|
|
|
|
"rank": r.rank,
|
|
|
|
|
|
"value": r.value,
|
|
|
|
|
|
"price": r.price,
|
|
|
|
|
|
"change_pct": r.change_pct
|
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
|
|
plans_result = [{
|
|
|
|
|
|
"id": p.id,
|
|
|
|
|
|
"symbol": p.symbol,
|
|
|
|
|
|
"name": p.name or p.symbol,
|
|
|
|
|
|
"plan_type": p.plan_type,
|
|
|
|
|
|
"score": p.score,
|
|
|
|
|
|
"logic": p.logic,
|
|
|
|
|
|
"reason": p.reason,
|
|
|
|
|
|
"entry_price": p.entry_price,
|
|
|
|
|
|
"stop_loss": p.stop_loss,
|
|
|
|
|
|
"take_profit": p.take_profit,
|
|
|
|
|
|
"confidence": p.confidence,
|
|
|
|
|
|
"position_suggestion": p.position_suggestion,
|
|
|
|
|
|
"created_at": p.created_at.isoformat() if p.created_at else None
|
|
|
|
|
|
} for p in plans]
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": True,
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"review_date": review_date.review_date,
|
|
|
|
|
|
"week_day": review_date.week_day,
|
|
|
|
|
|
"rankings": rankings_result,
|
|
|
|
|
|
"plans": plans_result
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/clear")
|
|
|
|
|
|
def clear_review_data(db: Session = Depends(get_analysis_db)):
|
|
|
|
|
|
"""清除所有V1复盘与交易计划数据"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
db.query(TradingPlan).delete()
|
|
|
|
|
|
db.query(SymbolRanking).delete()
|
|
|
|
|
|
db.query(ReviewDate).delete()
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": True,
|
|
|
|
|
|
"message": "复盘数据已清除"
|
|
|
|
|
|
}
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
db.rollback()
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": False,
|
|
|
|
|
|
"message": f"清除失败: {str(e)}"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==================== V2 生成交易计划 ====================
|
|
|
|
|
|
|
|
|
|
|
|
@router.post("/generate")
|
|
|
|
|
|
def generate_review_plan(
|
|
|
|
|
|
req: GeneratePlanRequest,
|
|
|
|
|
|
buffer_db: Session = Depends(get_db),
|
|
|
|
|
|
analysis_db: Session = Depends(get_analysis_db)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""生成V2交易计划"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
review_date_str = req.review_date
|
|
|
|
|
|
dt = datetime.strptime(review_date_str, "%Y-%m-%d")
|
|
|
|
|
|
week_days = ["周一", "周二", "周三", "周四", "周五", "周六", "周日"]
|
|
|
|
|
|
week_day = week_days[dt.weekday()]
|
|
|
|
|
|
|
|
|
|
|
|
# buffer_db 用于获取K线数据,analysis_db 用于保存计划
|
|
|
|
|
|
plan_data = generate_plan(buffer_db, review_date_str, week_day)
|
|
|
|
|
|
plan_id = save_plan_to_db(analysis_db, plan_data)
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": True,
|
|
|
|
|
|
"data": {
|
|
|
|
|
|
"plan_id": plan_id,
|
|
|
|
|
|
"summary": {
|
|
|
|
|
|
"review_date": plan_data["review_date"],
|
|
|
|
|
|
"week_day": plan_data["week_day"],
|
|
|
|
|
|
"core_conclusion": plan_data.get("core_conclusion"),
|
|
|
|
|
|
"bull_count": plan_data.get("bull_count"),
|
|
|
|
|
|
"bear_count": plan_data.get("bear_count"),
|
|
|
|
|
|
"neutral_count": plan_data.get("neutral_count"),
|
|
|
|
|
|
"opportunity_count": plan_data.get("opportunity_count"),
|
|
|
|
|
|
"symbol_count": plan_data.get("symbol_count"),
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.exception("生成交易计划失败")
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": False,
|
|
|
|
|
|
"message": f"生成失败: {str(e)}"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ==================== V2 查询接口 ====================
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/v2/plans")
|
|
|
|
|
|
def list_v2_plans(
|
|
|
|
|
|
limit: int = 20,
|
|
|
|
|
|
db: Session = Depends(get_analysis_db)
|
|
|
|
|
|
):
|
|
|
|
|
|
"""获取V2复盘计划列表"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
plans = db.query(ReviewPlanV2).order_by(
|
|
|
|
|
|
ReviewPlanV2.review_date.desc()
|
|
|
|
|
|
).limit(limit).all()
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": True,
|
|
|
|
|
|
"data": [{
|
|
|
|
|
|
"id": p.id,
|
|
|
|
|
|
"review_date": p.review_date,
|
|
|
|
|
|
"week_day": p.week_day,
|
|
|
|
|
|
"core_conclusion": p.core_conclusion,
|
|
|
|
|
|
"opportunity_count": p.opportunity_count,
|
|
|
|
|
|
"created_at": p.created_at.isoformat() if p.created_at else None,
|
|
|
|
|
|
} for p in plans]
|
|
|
|
|
|
}
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.exception("获取V2计划列表失败")
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": False,
|
|
|
|
|
|
"message": f"查询失败: {str(e)}"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/v2/plan/{plan_id}")
|
|
|
|
|
|
def get_v2_plan(plan_id: int, db: Session = Depends(get_analysis_db)):
|
|
|
|
|
|
"""根据ID获取完整V2计划数据"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
result = get_plan_data(db, plan_id)
|
|
|
|
|
|
if not result:
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": False,
|
|
|
|
|
|
"message": f"计划 {plan_id} 不存在"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": True,
|
|
|
|
|
|
"data": result
|
|
|
|
|
|
}
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.exception(f"获取V2计划 {plan_id} 失败")
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": False,
|
|
|
|
|
|
"message": f"查询失败: {str(e)}"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.get("/v2/plan/by-date/{date}")
|
|
|
|
|
|
def get_v2_plan_by_date(date: str, db: Session = Depends(get_analysis_db)):
|
|
|
|
|
|
"""根据日期获取V2计划数据"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
plan_meta = db.query(ReviewPlanV2).filter(
|
|
|
|
|
|
ReviewPlanV2.review_date == date
|
|
|
|
|
|
).first()
|
|
|
|
|
|
|
|
|
|
|
|
if not plan_meta:
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": False,
|
|
|
|
|
|
"message": f"日期 {date} 的计划不存在"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
result = get_plan_data(db, plan_meta.id)
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": True,
|
|
|
|
|
|
"data": result
|
|
|
|
|
|
}
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
logger.exception(f"获取V2计划 by-date {date} 失败")
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": False,
|
|
|
|
|
|
"message": f"查询失败: {str(e)}"
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@router.delete("/v2/clear")
|
|
|
|
|
|
def clear_v2_data(db: Session = Depends(get_analysis_db)):
|
|
|
|
|
|
"""清除所有V2数据"""
|
|
|
|
|
|
try:
|
|
|
|
|
|
db.query(SectorHeat).delete()
|
|
|
|
|
|
db.query(TradingPlanV2).delete()
|
|
|
|
|
|
db.query(SymbolScoreV2).delete()
|
|
|
|
|
|
db.query(ReviewPlanV2).delete()
|
|
|
|
|
|
db.commit()
|
|
|
|
|
|
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": True,
|
|
|
|
|
|
"message": "V2复盘数据已清除"
|
|
|
|
|
|
}
|
|
|
|
|
|
except Exception as e:
|
|
|
|
|
|
db.rollback()
|
|
|
|
|
|
return {
|
|
|
|
|
|
"success": False,
|
|
|
|
|
|
"message": f"清除失败: {str(e)}"
|
|
|
|
|
|
}
|