claude强化功能
This commit is contained in:
537
backend/main.py
537
backend/main.py
@@ -1,4 +1,4 @@
|
||||
"""智策股票终端 — FastAPI 后端入口。
|
||||
"""Blackdata股票终端 — FastAPI 后端入口。
|
||||
|
||||
- /api/* : 数据接口(基于 AkShare,带缓存与降级)
|
||||
- / : 托管前端原型(prototype 目录)
|
||||
@@ -7,6 +7,7 @@ import os
|
||||
import json
|
||||
import datetime as dt
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import List, Dict, Any, Optional
|
||||
|
||||
from fastapi import FastAPI, Query
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
@@ -19,6 +20,7 @@ import akshare_service as svc
|
||||
import config
|
||||
import scheduler
|
||||
import backtest as bt
|
||||
import backtest_advanced as bta
|
||||
import ai
|
||||
import signals as sig
|
||||
import report as rpt
|
||||
@@ -26,10 +28,19 @@ import portfolio as pf
|
||||
import llm
|
||||
import alerts as al
|
||||
import notifier
|
||||
import intraday_radar as radar
|
||||
import sector_rotation as sector
|
||||
import smart_selector as selector
|
||||
import attribution_analysis as attrib
|
||||
import ai_chat
|
||||
import sentiment_monitor as sentiment
|
||||
import event_driven as events
|
||||
import financial_analysis as fin
|
||||
import limit_analysis as limit_up
|
||||
from db import init_db, get_session
|
||||
from models import (DailyQuote, IndexDaily, SectorDaily, FundFlowDaily,
|
||||
SentimentDaily, DragonTiger, Security, JobRun, StockMetric, Trade,
|
||||
AlertRule, AlertEvent)
|
||||
AlertRule, AlertEvent, SelectorStrategy, SelectorAlert)
|
||||
|
||||
|
||||
@asynccontextmanager
|
||||
@@ -43,7 +54,7 @@ async def lifespan(app: FastAPI):
|
||||
yield
|
||||
|
||||
|
||||
app = FastAPI(title="智策股票终端 API", version="0.2.0", lifespan=lifespan)
|
||||
app = FastAPI(title="Blackdata股票终端 API", version="0.2.0", lifespan=lifespan)
|
||||
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
@@ -253,6 +264,98 @@ def backtest_api(symbol: str = Query("600519"), fast: int = Query(5, ge=2, le=60
|
||||
return bt.run_backtest(symbol, fast, slow)
|
||||
|
||||
|
||||
# ============ 增强回测 ============
|
||||
class BacktestParams(BaseModel):
|
||||
symbol: str
|
||||
strategy: str = "ma" # ma, multi_factor
|
||||
fast: int = 5
|
||||
slow: int = 20
|
||||
position_size: float = 1.0
|
||||
stop_loss: float = 0.0
|
||||
take_profit: float = 0.0
|
||||
initial_capital: float = 100000.0
|
||||
commission: float = 0.0005
|
||||
|
||||
|
||||
@app.post("/api/backtest/advanced")
|
||||
def backtest_advanced(params: BacktestParams):
|
||||
"""增强回测"""
|
||||
if params.strategy == "ma":
|
||||
strategy = bta.MAStrategy(
|
||||
fast=params.fast,
|
||||
slow=params.slow,
|
||||
position_size=params.position_size,
|
||||
stop_loss=params.stop_loss,
|
||||
take_profit=params.take_profit
|
||||
)
|
||||
elif params.strategy == "multi_factor":
|
||||
strategy = bta.MultiFactorStrategy(position_size=params.position_size)
|
||||
else:
|
||||
return {"ok": False, "msg": "不支持的策略类型"}
|
||||
|
||||
return bta.run_advanced_backtest(
|
||||
symbol=params.symbol,
|
||||
strategy=strategy,
|
||||
initial_capital=params.initial_capital,
|
||||
commission=params.commission
|
||||
)
|
||||
|
||||
|
||||
class OptimizeParams(BaseModel):
|
||||
symbol: str
|
||||
strategy: str = "ma"
|
||||
fast_range: List[int] = [3, 5, 10, 15]
|
||||
slow_range: List[int] = [20, 30, 60]
|
||||
metric: str = "sharpe_ratio"
|
||||
|
||||
|
||||
@app.post("/api/backtest/optimize")
|
||||
def backtest_optimize(params: OptimizeParams):
|
||||
"""参数优化"""
|
||||
param_grid = {
|
||||
"fast": params.fast_range,
|
||||
"slow": params.slow_range
|
||||
}
|
||||
|
||||
results = bta.optimize_parameters(
|
||||
symbol=params.symbol,
|
||||
param_grid=param_grid,
|
||||
strategy_class=bta.MAStrategy,
|
||||
metric=params.metric
|
||||
)
|
||||
|
||||
return {
|
||||
"ok": True,
|
||||
"symbol": params.symbol,
|
||||
"metric": params.metric,
|
||||
"results": results[:20] # 返回前20个最优结果
|
||||
}
|
||||
|
||||
|
||||
class CompareParams(BaseModel):
|
||||
symbol: str
|
||||
strategies: List[Dict[str, Any]]
|
||||
|
||||
|
||||
@app.post("/api/backtest/compare")
|
||||
def backtest_compare(params: CompareParams):
|
||||
"""策略对比"""
|
||||
strategies = []
|
||||
|
||||
for s in params.strategies:
|
||||
if s["type"] == "ma":
|
||||
strategies.append(bta.MAStrategy(
|
||||
fast=s.get("fast", 5),
|
||||
slow=s.get("slow", 20),
|
||||
stop_loss=s.get("stop_loss", 0),
|
||||
take_profit=s.get("take_profit", 0)
|
||||
))
|
||||
elif s["type"] == "multi_factor":
|
||||
strategies.append(bta.MultiFactorStrategy())
|
||||
|
||||
return bta.compare_strategies(params.symbol, strategies)
|
||||
|
||||
|
||||
# ============ 全市场选股 ============
|
||||
STRATEGIES = {
|
||||
"surge": "最近暴涨(5日涨幅≥20%)",
|
||||
@@ -479,6 +582,186 @@ def portfolio_equity():
|
||||
return pf.equity_curve()
|
||||
|
||||
|
||||
@app.get("/api/portfolio/attribution")
|
||||
def portfolio_attribution():
|
||||
"""持仓归因分析"""
|
||||
return attrib.analyze_attribution()
|
||||
|
||||
|
||||
# ============ AI 对话式分析 ============
|
||||
class ChatRequest(BaseModel):
|
||||
session_id: str
|
||||
message: str
|
||||
|
||||
|
||||
@app.post("/api/chat")
|
||||
def chat(req: ChatRequest):
|
||||
"""AI对话"""
|
||||
return ai_chat.chat(req.session_id, req.message)
|
||||
|
||||
|
||||
@app.delete("/api/chat/{session_id}")
|
||||
def clear_chat(session_id: str):
|
||||
"""清空会话"""
|
||||
ai_chat.clear_session(session_id)
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@app.get("/api/chat/{session_id}/history")
|
||||
def chat_history(session_id: str):
|
||||
"""获取会话历史"""
|
||||
return {"ok": True, "messages": ai_chat.get_session_history(session_id)}
|
||||
|
||||
|
||||
# ============ 社区情绪监控 ============
|
||||
@app.post("/api/sentiment/collect")
|
||||
def sentiment_collect(limit: int = Query(50, ge=10, le=200)):
|
||||
"""采集社区帖子"""
|
||||
return sentiment.collect_posts(limit)
|
||||
|
||||
|
||||
@app.get("/api/sentiment/index")
|
||||
def sentiment_index(date: Optional[str] = None):
|
||||
"""获取情绪指数"""
|
||||
d = dt.date.fromisoformat(date) if date else None
|
||||
return sentiment.calculate_sentiment_index(d)
|
||||
|
||||
|
||||
@app.get("/api/sentiment/hot_stocks")
|
||||
def sentiment_hot_stocks(days: int = Query(1, ge=1, le=7), limit: int = Query(20, le=50)):
|
||||
"""热议股票排行"""
|
||||
return sentiment.get_hot_stocks(days, limit)
|
||||
|
||||
|
||||
@app.get("/api/sentiment/history")
|
||||
def sentiment_history(days: int = Query(30, ge=7, le=90)):
|
||||
"""情绪指数历史"""
|
||||
return sentiment.get_sentiment_history(days)
|
||||
|
||||
|
||||
@app.get("/api/sentiment/correlation")
|
||||
def sentiment_correlation(code: str = Query(...), days: int = Query(60, ge=20, le=180)):
|
||||
"""情绪与股价相关性"""
|
||||
return sentiment.analyze_sentiment_correlation(code, days)
|
||||
|
||||
|
||||
@app.get("/api/sentiment/wordcloud")
|
||||
def sentiment_wordcloud(days: int = Query(7, ge=1, le=30), top_n: int = Query(50, le=100)):
|
||||
"""关键词云"""
|
||||
return sentiment.get_keyword_cloud(days, top_n)
|
||||
|
||||
|
||||
# ============ 事件驱动策略 ============
|
||||
@app.post("/api/events/seed")
|
||||
def events_seed():
|
||||
"""生成示例事件数据"""
|
||||
return events.seed_sample_events()
|
||||
|
||||
|
||||
@app.get("/api/events/earnings/pattern")
|
||||
def earnings_pattern(days_before: int = Query(5, ge=1, le=10), days_after: int = Query(10, ge=5, le=30)):
|
||||
"""财报发布前后统计规律"""
|
||||
return events.analyze_earnings_pattern(days_before, days_after)
|
||||
|
||||
|
||||
@app.get("/api/events/insider")
|
||||
def insider_trading(code: Optional[str] = None, days: int = Query(180, ge=30, le=365)):
|
||||
"""高管增减持跟踪"""
|
||||
return events.track_insider_trading(code, days)
|
||||
|
||||
|
||||
@app.get("/api/events/unlock")
|
||||
def unlock_impact(days: int = Query(90, ge=30, le=180)):
|
||||
"""限售解禁影响分析"""
|
||||
return events.analyze_unlock_impact(days)
|
||||
|
||||
|
||||
@app.get("/api/events/policy")
|
||||
def policy_events(sector: Optional[str] = None, days: int = Query(180, ge=30, le=365)):
|
||||
"""行业政策事件"""
|
||||
return events.get_policy_events(sector, days)
|
||||
|
||||
|
||||
class EventSelectorRequest(BaseModel):
|
||||
event_types: List[str]
|
||||
days: int = 30
|
||||
|
||||
|
||||
@app.post("/api/events/selector")
|
||||
def event_selector(req: EventSelectorRequest):
|
||||
"""事件驱动选股"""
|
||||
return events.event_driven_selector(req.event_types, req.days)
|
||||
|
||||
|
||||
# ============ 财报深度解读 ============
|
||||
@app.post("/api/financial/seed")
|
||||
def financial_seed():
|
||||
"""生成示例财报数据"""
|
||||
return fin.seed_sample_reports()
|
||||
|
||||
|
||||
@app.get("/api/financial/trend")
|
||||
def financial_trend(code: str = Query(...), periods: int = Query(8, ge=4, le=16)):
|
||||
"""财报关键指标趋势"""
|
||||
return fin.get_report_trend(code, periods)
|
||||
|
||||
|
||||
@app.get("/api/financial/summary")
|
||||
def financial_summary(code: str = Query(...)):
|
||||
"""AI财报摘要"""
|
||||
return fin.generate_ai_summary(code)
|
||||
|
||||
|
||||
@app.get("/api/financial/compare")
|
||||
def financial_compare(code: str = Query(...), sector: Optional[str] = None):
|
||||
"""同行对比"""
|
||||
return fin.compare_with_peers(code, sector)
|
||||
|
||||
|
||||
@app.get("/api/financial/warnings")
|
||||
def financial_warnings(code: str = Query(...)):
|
||||
"""财报异常预警"""
|
||||
return fin.detect_abnormalities(code)
|
||||
|
||||
|
||||
@app.get("/api/financial/calendar")
|
||||
def financial_calendar(days: int = Query(30, ge=7, le=90)):
|
||||
"""财报发布日历"""
|
||||
return fin.get_report_calendar(days)
|
||||
|
||||
|
||||
@app.get("/api/financial/rankings")
|
||||
def financial_rankings(metric: str = Query("roe"), limit: int = Query(20, le=50)):
|
||||
"""财报排行榜"""
|
||||
return fin.get_top_reports(metric, limit)
|
||||
|
||||
|
||||
# ============ 涨跌停分析 ============
|
||||
@app.get("/api/limit/stocks")
|
||||
def limit_stocks(date: Optional[str] = None, limit_type: str = Query("up")):
|
||||
"""获取涨停/跌停股票"""
|
||||
d = dt.date.fromisoformat(date) if date else None
|
||||
return limit_up.get_limit_stocks(d, limit_type)
|
||||
|
||||
|
||||
@app.get("/api/limit/consecutive")
|
||||
def consecutive_limits(days: int = Query(10, ge=5, le=30)):
|
||||
"""连板股追踪"""
|
||||
return limit_up.track_consecutive_limits(days)
|
||||
|
||||
|
||||
@app.get("/api/limit/break_rate")
|
||||
def limit_break_rate(days: int = Query(60, ge=30, le=180)):
|
||||
"""炸板率统计"""
|
||||
return limit_up.analyze_limit_break_rate(days)
|
||||
|
||||
|
||||
@app.get("/api/limit/squad")
|
||||
def limit_squad(days: int = Query(30, ge=10, le=90), min_limits: int = Query(5, ge=3, le=10)):
|
||||
"""涨停敢死队排行"""
|
||||
return limit_up.get_limit_squad_rankings(days, min_limits)
|
||||
|
||||
|
||||
# ============ 推送通知 ============
|
||||
@app.get("/api/notify/status")
|
||||
def notify_status():
|
||||
@@ -489,7 +772,7 @@ def notify_status():
|
||||
def notify_test():
|
||||
if not notifier.any_enabled():
|
||||
return {"ok": False, "msg": "未配置任何推送渠道,请在 backend/.env 配置后重启"}
|
||||
res = notifier.notify("【智策】推送测试", "这是一条来自智策股票终端的测试通知,收到即表示推送通道正常。")
|
||||
res = notifier.notify("【Blackdata】推送测试", "这是一条来自Blackdata股票终端的测试通知,收到即表示推送通道正常。")
|
||||
return {"ok": True, "result": res}
|
||||
|
||||
|
||||
@@ -613,6 +896,252 @@ def news_ai(n: NewsAI):
|
||||
"text": f"判断:{senti}(关键词:{'、'.join(kw) or '无'})。摘要:{text_in[:80]}…\n(配置大模型后可获得更深入的关联分析)"}
|
||||
|
||||
|
||||
# ============ 盘中实时监控雷达 ============
|
||||
@app.get("/api/radar/status")
|
||||
def radar_status():
|
||||
"""雷达状态。"""
|
||||
return {"trading_time": radar._is_trading_time()}
|
||||
|
||||
|
||||
@app.post("/api/radar/scan")
|
||||
def radar_scan():
|
||||
"""手动触发异动扫描。"""
|
||||
return radar.scan_all()
|
||||
|
||||
|
||||
@app.get("/api/radar/events")
|
||||
def radar_events(hours: int = Query(2, ge=1, le=24), limit: int = Query(50, le=200)):
|
||||
"""获取最近的异动事件。"""
|
||||
return {"list": radar.get_recent_events(hours, limit)}
|
||||
|
||||
|
||||
@app.post("/api/radar/notify")
|
||||
def radar_notify():
|
||||
"""推送未通知的异动。"""
|
||||
return radar.notify_events()
|
||||
|
||||
|
||||
@app.get("/api/radar/stats")
|
||||
def radar_stats(date: str = Query(None)):
|
||||
"""异动统计。"""
|
||||
d = dt.date.fromisoformat(date) if date else None
|
||||
return radar.get_statistics(d)
|
||||
|
||||
|
||||
# ============ 板块轮动分析 ============
|
||||
@app.get("/api/sector/trend")
|
||||
def sector_trend(days: int = Query(20, ge=5, le=60), top_n: int = Query(15, le=30)):
|
||||
"""板块强弱趋势"""
|
||||
return sector.get_sector_trend(days, top_n)
|
||||
|
||||
|
||||
@app.get("/api/sector/flow")
|
||||
def sector_flow(days: int = Query(5, ge=1, le=20)):
|
||||
"""资金流向分析"""
|
||||
return sector.analyze_fund_flow(days)
|
||||
|
||||
|
||||
@app.get("/api/sector/lifecycle")
|
||||
def sector_lifecycle(name: str = Query(...), days: int = Query(60, ge=20, le=120)):
|
||||
"""板块生命周期"""
|
||||
return sector.analyze_lifecycle(name, days)
|
||||
|
||||
|
||||
@app.get("/api/sector/leaders")
|
||||
def sector_leaders(name: str = Query(...), days: int = Query(20, ge=5, le=60), limit: int = Query(10, le=30)):
|
||||
"""龙头股识别"""
|
||||
return sector.identify_leaders(name, days, limit)
|
||||
|
||||
|
||||
@app.get("/api/sector/correlation")
|
||||
def sector_correlation(days: int = Query(60, ge=20, le=120), top_n: int = Query(20, le=30)):
|
||||
"""板块联动性分析"""
|
||||
return sector.analyze_correlation(days, top_n)
|
||||
|
||||
|
||||
@app.get("/api/sector/summary")
|
||||
def sector_summary():
|
||||
"""板块轮动摘要"""
|
||||
return sector.get_rotation_summary()
|
||||
|
||||
|
||||
# ============ 智能选股增强 ============
|
||||
@app.get("/api/selector/fields")
|
||||
def selector_fields():
|
||||
"""获取可用字段"""
|
||||
return {"ok": True, "fields": selector.get_available_fields()}
|
||||
|
||||
|
||||
@app.get("/api/selector/presets")
|
||||
def selector_presets():
|
||||
"""获取预设策略"""
|
||||
return {"ok": True, "presets": selector.get_preset_strategies()}
|
||||
|
||||
|
||||
class SelectorRequest(BaseModel):
|
||||
strategy: Dict[str, Any]
|
||||
date: Optional[str] = None
|
||||
|
||||
|
||||
@app.post("/api/selector/run")
|
||||
def selector_run(req: SelectorRequest):
|
||||
"""执行选股"""
|
||||
try:
|
||||
strategy = selector.Strategy.from_dict(req.strategy)
|
||||
date = dt.date.fromisoformat(req.date) if req.date else None
|
||||
return selector.run_selector(strategy, date)
|
||||
except Exception as e:
|
||||
return {"ok": False, "msg": str(e)}
|
||||
|
||||
|
||||
@app.post("/api/selector/backtest")
|
||||
def selector_backtest(req: SelectorRequest, days: int = Query(60, ge=20, le=250)):
|
||||
"""选股策略回测"""
|
||||
try:
|
||||
strategy = selector.Strategy.from_dict(req.strategy)
|
||||
return selector.backtest_selector(strategy, days)
|
||||
except Exception as e:
|
||||
return {"ok": False, "msg": str(e)}
|
||||
|
||||
|
||||
class CompareRequest(BaseModel):
|
||||
strategy: Dict[str, Any]
|
||||
date1: str
|
||||
date2: str
|
||||
|
||||
|
||||
@app.post("/api/selector/compare")
|
||||
def selector_compare(req: CompareRequest):
|
||||
"""对比选股结果"""
|
||||
try:
|
||||
strategy = selector.Strategy.from_dict(req.strategy)
|
||||
date1 = dt.date.fromisoformat(req.date1)
|
||||
date2 = dt.date.fromisoformat(req.date2)
|
||||
return selector.compare_results(date1, date2, strategy)
|
||||
except Exception as e:
|
||||
return {"ok": False, "msg": str(e)}
|
||||
|
||||
|
||||
@app.get("/api/selector/strategies")
|
||||
def list_strategies():
|
||||
"""获取保存的策略列表"""
|
||||
with get_session() as s:
|
||||
rows = s.execute(
|
||||
select(SelectorStrategy).order_by(SelectorStrategy.updated_at.desc())
|
||||
).scalars().all()
|
||||
return {
|
||||
"ok": True,
|
||||
"strategies": [{
|
||||
"id": r.id,
|
||||
"name": r.name,
|
||||
"description": r.description,
|
||||
"is_preset": r.is_preset,
|
||||
"created_at": r.created_at.strftime("%Y-%m-%d %H:%M:%S"),
|
||||
"updated_at": r.updated_at.strftime("%Y-%m-%d %H:%M:%S")
|
||||
} for r in rows]
|
||||
}
|
||||
|
||||
|
||||
class SaveStrategyRequest(BaseModel):
|
||||
name: str
|
||||
description: str = ""
|
||||
strategy: Dict[str, Any]
|
||||
|
||||
|
||||
@app.post("/api/selector/strategies")
|
||||
def save_strategy(req: SaveStrategyRequest):
|
||||
"""保存策略"""
|
||||
try:
|
||||
strategy = selector.Strategy.from_dict(req.strategy)
|
||||
with get_session() as s:
|
||||
record = SelectorStrategy(
|
||||
name=req.name,
|
||||
description=req.description,
|
||||
strategy_json=strategy.to_json()
|
||||
)
|
||||
s.add(record)
|
||||
s.commit()
|
||||
return {"ok": True, "id": record.id}
|
||||
except Exception as e:
|
||||
return {"ok": False, "msg": str(e)}
|
||||
|
||||
|
||||
@app.get("/api/selector/strategies/{sid}")
|
||||
def get_strategy(sid: int):
|
||||
"""获取策略详情"""
|
||||
with get_session() as s:
|
||||
record = s.get(SelectorStrategy, sid)
|
||||
if not record:
|
||||
return {"ok": False, "msg": "策略不存在"}
|
||||
return {
|
||||
"ok": True,
|
||||
"id": record.id,
|
||||
"name": record.name,
|
||||
"description": record.description,
|
||||
"strategy": json.loads(record.strategy_json)
|
||||
}
|
||||
|
||||
|
||||
@app.delete("/api/selector/strategies/{sid}")
|
||||
def delete_strategy(sid: int):
|
||||
"""删除策略"""
|
||||
with get_session() as s:
|
||||
record = s.get(SelectorStrategy, sid)
|
||||
if record:
|
||||
s.delete(record)
|
||||
s.commit()
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
@app.get("/api/selector/alerts")
|
||||
def list_selector_alerts():
|
||||
"""获取选股预警列表"""
|
||||
with get_session() as s:
|
||||
rows = s.execute(
|
||||
select(SelectorAlert).order_by(SelectorAlert.id.desc())
|
||||
).scalars().all()
|
||||
return {
|
||||
"ok": True,
|
||||
"alerts": [{
|
||||
"id": r.id,
|
||||
"strategy_id": r.strategy_id,
|
||||
"strategy_name": r.strategy_name,
|
||||
"status": r.status,
|
||||
"last_checked": r.last_checked.strftime("%m-%d %H:%M") if r.last_checked else "",
|
||||
"last_count": r.last_count
|
||||
} for r in rows]
|
||||
}
|
||||
|
||||
|
||||
class CreateAlertRequest(BaseModel):
|
||||
strategy_id: int
|
||||
strategy_name: str
|
||||
|
||||
|
||||
@app.post("/api/selector/alerts")
|
||||
def create_selector_alert(req: CreateAlertRequest):
|
||||
"""创建选股预警"""
|
||||
with get_session() as s:
|
||||
record = SelectorAlert(
|
||||
strategy_id=req.strategy_id,
|
||||
strategy_name=req.strategy_name
|
||||
)
|
||||
s.add(record)
|
||||
s.commit()
|
||||
return {"ok": True, "id": record.id}
|
||||
|
||||
|
||||
@app.delete("/api/selector/alerts/{aid}")
|
||||
def delete_selector_alert(aid: int):
|
||||
"""删除选股预警"""
|
||||
with get_session() as s:
|
||||
record = s.get(SelectorAlert, aid)
|
||||
if record:
|
||||
s.delete(record)
|
||||
s.commit()
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
# ============ 静态前端 ============
|
||||
FRONTEND_DIR = os.path.join(os.path.dirname(BASE_DIR), "prototype")
|
||||
if os.path.isdir(FRONTEND_DIR):
|
||||
|
||||
Reference in New Issue
Block a user