From 0dc27af2d1a2bc5b97482fd2227a31bf117438a6 Mon Sep 17 00:00:00 2001 From: aaron <> Date: Mon, 13 Apr 2026 14:32:41 +0800 Subject: [PATCH] 1 --- .gitignore | 4 + README.md | 146 ++++++++++++++++++ backend/astock.db | Bin 2744320 -> 2756608 bytes frontend/.next/app-build-manifest.json | 36 ++--- frontend/.next/build-manifest.json | 4 +- frontend/.next/cache/.tsbuildinfo | 2 +- frontend/.next/server/app-paths-manifest.json | 6 +- .../server/server-reference-manifest.json | 2 +- frontend/.next/trace | 68 ++++---- frontend/src/app/chat/page.tsx | 6 +- frontend/src/app/recommendations/page.tsx | 24 +-- frontend/src/components/stock-card.tsx | 69 +++++---- 12 files changed, 260 insertions(+), 107 deletions(-) create mode 100644 README.md diff --git a/.gitignore b/.gitignore index 82ea9525..e9345578 100644 --- a/.gitignore +++ b/.gitignore @@ -76,3 +76,7 @@ htmlcov/ *.bak *.tmp *.temp + + +#cluade +.claude/ \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 00000000..a8c87d4c --- /dev/null +++ b/README.md @@ -0,0 +1,146 @@ +# A股分析推荐 Agent + +基于资金驱动的四层漏斗模型,盘中实时分析推荐 A 股。支持趋势突破/缩量回踩/启动型三种策略,结合 LLM (DeepSeek) 生成个股分析。 + +## 技术栈 + +- **Backend**: Python 3.10+ / FastAPI / SQLAlchemy / Tushare / APScheduler +- **Frontend**: Next.js 14 / React 18 / TypeScript / Tailwind CSS / ECharts +- **Database**: SQLite (aiosqlite) +- **LLM**: DeepSeek API(可选,用于 AI 分析) + +## 前置条件 + +- Python 3.10+ +- Node.js 18+ +- [Tushare Pro](https://tushare.pro/) Token(用于获取 A 股行情数据) +- DeepSeek API Key(可选,不配置则跳过 AI 分析功能) + +## 快速开始 + +### 1. 克隆项目 + +```bash +git clone +cd astock-agent +``` + +### 2. 启动 Backend + +```bash +cd backend + +# 创建虚拟环境 +python -m venv venv +source venv/bin/activate # Windows: venv\Scripts\activate + +# 安装依赖 +pip install -r requirements.txt + +# 配置环境变量 +cp .env.example .env +``` + +编辑 `backend/.env`,填入你的 Token: + +```env +ASTOCK_TUSHARE_TOKEN=你的Tushare Token +ASTOCK_DEBUG=true + +# 可选:配置后启用 AI 分析功能 +ASTOCK_DEEPSEEK_API_KEY=你的DeepSeek API Key +``` + +启动服务: + +```bash +uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload +``` + +首次启动会自动创建 SQLite 数据库和默认管理员账户: +- 用户名:`admin` +- 密码:`admin123` + +### 3. 启动 Frontend + +```bash +cd frontend + +# 安装依赖 +npm install + +# 启动开发服务器 +npm run dev +``` + +前端默认运行在 `http://localhost:3002`,通过 Next.js rewrites 代理 API 请求到后端 `localhost:8000`。 + +### 4. 访问 + +浏览器打开 http://localhost:3002 ,使用默认管理员账户登录。 + +## 环境变量说明 + +| 变量 | 必填 | 默认值 | 说明 | +|------|------|--------|------| +| `ASTOCK_TUSHARE_TOKEN` | 是 | - | Tushare Pro 数据接口 Token | +| `ASTOCK_DEBUG` | 否 | `false` | 开启调试日志 | +| `ASTOCK_DEEPSEEK_API_KEY` | 否 | - | DeepSeek API Key,配置后启用 AI 分析 | +| `ASTOCK_DEEPSEEK_BASE_URL` | 否 | `https://api.deepseek.com/v1` | DeepSeek API 地址 | +| `ASTOCK_FRONTEND_URL` | 否 | `http://localhost:3002` | 前端地址(CORS 白名单) | +| `ASTOCK_JWT_SECRET` | 否 | `change-me-in-production` | JWT 签名密钥,生产环境务必修改 | +| `ASTOCK_ADMIN_USERNAME` | 否 | `admin` | 默认管理员用户名 | +| `ASTOCK_ADMIN_PASSWORD` | 否 | `admin123` | 默认管理员密码 | + +完整配置项见 `backend/app/config.py`。 + +## 项目结构 + +``` +astock-agent/ +├── backend/ +│ ├── app/ +│ │ ├── main.py # FastAPI 入口 +│ │ ├── config.py # 配置管理 +│ │ ├── api/ # API 路由(行情、板块、推荐、个股、聊天、认证) +│ │ ├── analysis/ # 分析引擎(信号检测、板块扫描、选股) +│ │ ├── core/ # 认证、缓存等核心模块 +│ │ ├── data/ # 数据层(Tushare、腾讯行情客户端) +│ │ ├── db/ # 数据库表定义与初始化 +│ │ ├── engine/ # 调度器(定时扫描任务) +│ │ └── llm/ # LLM 集成(DeepSeek) +│ ├── requirements.txt +│ ├── .env.example +│ └── Dockerfile +├── frontend/ +│ ├── src/ +│ │ ├── app/ # Next.js 页面(总览、推荐、板块、个股、对话) +│ │ ├── components/ # 组件(图表、卡片、导航等) +│ │ ├── hooks/ # 自定义 Hooks +│ │ └── lib/ # 工具函数、API 客户端 +│ ├── tailwind.config.ts +│ ├── next.config.js +│ └── package.json +└── .gitignore +``` + +## 构建生产版本 + +```bash +# Backend +cd backend +pip install -r requirements.txt +uvicorn app.main:app --host 0.0.0.0 --port 8000 + +# Frontend +cd frontend +npm run build +npm run start +``` + +## 注意事项 + +- 首次启动后,系统会在 A 股交易时段(9:30-11:30, 13:00-15:00)自动执行扫描任务 +- 也可以在推荐列表页手动点击「立即扫描」触发 +- 数据库为 SQLite,数据文件 `astock.db` 自动生成在 `backend/` 目录下 +- Tushare API 有请求频率限制,请勿过于频繁地手动触发扫描 diff --git a/backend/astock.db b/backend/astock.db index c92b9b83203b054c12a1677136012acea8c7dbfe..473af050cb7a4c4e2490741c4e2d703ff4bd3ff1 100644 GIT binary patch delta 23487 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 +
{/* Header */} -
+
@@ -114,7 +114,7 @@ export default function ChatPage() {
{/* Messages */} -
+
{messages.length === 0 ? (
diff --git a/frontend/src/app/recommendations/page.tsx b/frontend/src/app/recommendations/page.tsx index d07e4566..883c086d 100644 --- a/frontend/src/app/recommendations/page.tsx +++ b/frontend/src/app/recommendations/page.tsx @@ -106,9 +106,9 @@ export default function RecommendationsPage() { return (
{/* Header */} -
+
-

推荐列表

+

推荐列表

{totalCount} 只 · {dayGroups.length} 天记录 @@ -117,7 +117,7 @@ export default function RecommendationsPage() { {aiExpanded && ( -

) : rec.llm_analysis === "AI 分析暂时不可用" ? ( -
+
AI 分析暂时不可用
) : showLLMLoading ? ( -
+
AI 分析中...
@@ -135,13 +138,13 @@ export default function StockCard({ rec, showLLMLoading = false }: { rec: Recomm {/* Risk note */} {rec.risk_note && ( -
+
{rec.risk_note}
)} - {/* Hover indicator */} -
+ {/* Hover indicator - hidden on mobile */} +
查看详情 @@ -236,11 +239,11 @@ function ScoreBar({ label, value }: { label: string; value: number }) { const gradientClass = value >= 70 ? "score-bar-gradient-high" : value >= 50 ? "score-bar-gradient-mid" : "score-bar-gradient-low"; return (
-
+
{label} {value.toFixed(0)}
-
+