Compare commits

..

55 Commits

Author SHA1 Message Date
fish 2f2b53e03e hjg 账号默认使用 DeepSeek Pro AI 配置 2026-07-07 22:17:00 +08:00
fish 490fb7ac0a 移除 serve 看板涨停梯队降级标签 2026-07-07 21:47:56 +08:00
fish 566a8be912 服务端同步改为按行合并去重 2026-07-07 21:30:54 +08:00
fish 476201abed 整理项目说明与许可文件 2026-07-07 21:15:01 +08:00
fish e21d2f69ee 更新项目说明文档 2026-07-07 21:13:55 +08:00
fish 0ad4b1f030 财务分析清理同步相关代码
移除能力检查、同步按钮、进度追踪等未用代码,
仅靠 status.available 判断数据是否存在。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 16:09:39 +08:00
fish 7a96d51f8a 财务分析移除 Expert 套餐能力门控
serve 只读模式,财务数据仅来自 local 同步,
不再依赖 TickFlow Expert 能力。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 16:04:47 +08:00
fish f408362c91 移除引导配置页面
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 15:53:00 +08:00
fish 008f92ffe3 登录页账号输入框默认改为空
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 15:45:29 +08:00
fish eae13737ff 登录页移除底部文案
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 15:45:13 +08:00
fish fcebba0a32 Docker容器改名
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 15:19:07 +08:00
fish e7cd30b30d 侧边栏增加 admin 用户管理入口
admin 用户可在侧边栏底部进入用户管理页,
支持新增/删除用户。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 15:05:35 +08:00
fish c1fa0461c1 复盘页添加日期选择功能
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 15:04:01 +08:00
fish 267b8557e8 概念/行业分析添加日期选择功能
- backend: market_snapshot 支持可选 as_of 参数
- frontend: 两页加 DatePicker,按选定日期查询数据

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 14:58:31 +08:00
fish 4cd74b06f1 指数页移除日K来源文案
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 14:54:37 +08:00
fish ef6e308ad1 指数页移除分时数据面板,K线图占满
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 14:51:50 +08:00
fish 26a7c02887 指数页隐藏副标题文案
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 14:49:57 +08:00
fish 5c4ae36adc 指数页移除同步按钮及相关代码
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 14:48:21 +08:00
fish 22415d3f25 概念/行业分析移除配置图标及相关代码
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 14:42:22 +08:00
fish 6c770f828e 连板梯队移除 SealedBadge 降级标签
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 14:35:16 +08:00
fish 833e4e90b9 连板梯队/概念/行业分析移除右上角刷新按钮
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 14:31:41 +08:00
fish 3a963a130f AI 分析报告按用户隔离存储
个股/财务/复盘报告改为 data/user_data/reports/{username}/ 目录下,
API 端从 request.state.username 读取对应用户的报告。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 14:29:10 +08:00
fish efdfe29ccc 概念/行业分析移除数据获取功能
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 14:26:06 +08:00
fish a415762a77 财务面板移除同步时间信息
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 14:21:31 +08:00
fish c08e7f2c54 AI 配置改为按用户独立存储
每个用户 AI 配置存 data/user_data/ai_settings/{username}.json,
读配置时用户级优先,无则回退到全局 secrets.json。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 14:10:32 +08:00
fish 9ecfc2bb71 侧边栏底部移除设置图标
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 13:00:00 +08:00
fish d2f428ec86 侧边栏底部改为登录用户名 + 暗夜模式开关
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 12:57:31 +08:00
fish fc69f51e23 serve 多用户认证体系
单密码→多用户:auth.json 格式迁移,login 支持用户名密码,
新增用户管理 API 和前端页面,会话关联 username,
auth middleware 将当前用户注入 request.state。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 12:49:20 +08:00
fish 22049582df AI 设置隐藏 Codex CLI 预设入口
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 12:42:10 +08:00
fish b2a4e90e74 AI 设置隐藏炸鸡中转站预设入口
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 12:40:07 +08:00
fish 6dfce96bb3 AI 设置独立为单独页面 /settings/ai
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 12:37:08 +08:00
fish 4384f5fa00 复盘隐藏定时入口并清理相关代码
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 12:35:03 +08:00
fish 331ff3067f 个股分析隐藏点位提醒入口
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 12:32:45 +08:00
fish 11dc81a189 个股分析移除顶部 Beta 标签和历史报告入口
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 12:30:04 +08:00
fish 431aae0882 看板跌停梯队改为真实跌停数据展示
移除监控中心告警组件,新增 DownLadderMini 组件调用
limit-ladder API(direction=down)获取真实跌停梯队数据,
配色用 red/bear 色系,与涨停梯队布局一致。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 12:25:33 +08:00
fish 0861e68fe4 看板监控中心标题改为跌停梯队
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 12:17:27 +08:00
fish a83da2b8b4 serve 侧边栏移除个股分析和复盘的 Beta 标签
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 12:12:42 +08:00
fish c71f45e147 serve 侧边栏品牌名改为 A股工作台
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 12:11:21 +08:00
fish fa89b382df serve 侧边栏移除 Quant · Terminal 副标题
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 12:09:14 +08:00
fish e843d7556d serve 品牌名改为「A股工作台」
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 12:08:53 +08:00
fish 580743853a serve 设为只读模式,禁用外部数据拉取
serve 端不再启动 pull_scheduler 和 financial_scheduler,
阻塞 ext_data 和 financials 的外部获取/同步 API 端点,
数据仅通过 local→serve 同步推送。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 12:04:46 +08:00
fish 817d130fd3 数据同步页面新增扩展数据(概念/行业)同步选项
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 12:01:14 +08:00
fish 8031f27f85 新增同步 ext_data(概念/行业) 和本地化财务数据展示
- 将 ext_data 加入 SYNCABLE_PARTS,概念/行业分类数据可从 local 同步到 serve
- 去掉 serve 财务分析接口的 Cap.FINANCIAL 能力门控,依赖本地 parquet 文件来展示数据
- serve 财务分析前端在无 API Key 但有同步数据时正常展示,隐藏同步按钮

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-06 10:53:00 +08:00
fish 274b90eef6 新增 local→serve 数据同步功能
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 10:27:00 +08:00
fish 1ae2102b4c 隐藏策略与 API 配置菜单入口
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-06 10:01:56 +08:00
fish db978240da 将 serve/data 加入忽略文件
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 18:06:18 +08:00
fish 0be67be799 修复未使用变量的构建错误
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 17:58:54 +08:00
fish aa76c19aff 隐藏数据与监控中心菜单入口
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 17:57:06 +08:00
fish f8c946e8a9 移除交易功能
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-07-04 17:54:41 +08:00
fish a45aa5b033 移除自选和回测功能
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-04 17:41:08 +08:00
fish ab6282b94c 修复 gitignore 匹配 package-lock.json 路径
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-04 17:33:53 +08:00
fish 9904854cc1 删除实时行情、盘口深度和监控规则功能
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-04 17:32:39 +08:00
fish 0474e5fb46 复制 local 代码到 serve 2026-07-04 16:59:25 +08:00
fish cee04c1b46 同步 gitignore 数据目录路径
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-04 16:33:55 +08:00
fish aad34202f1 项目目录从 refer 迁移到 local
Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-04 16:32:48 +08:00
577 changed files with 62885 additions and 355 deletions
+4 -2
View File
@@ -44,7 +44,7 @@ backend/static/
backend/app/static/ backend/app/static/
# ===== npm lockfile (不纳入版本控制) ===== # ===== npm lockfile (不纳入版本控制) =====
frontend/package-lock.json **/package-lock.json
# ===== IDE ===== # ===== IDE =====
.idea/ .idea/
@@ -58,6 +58,8 @@ backend/data/**
!backend/data/.gitkeep !backend/data/.gitkeep
data/** data/**
!data/.gitkeep !data/.gitkeep
serve/data/**
!serve/data/.gitkeep
# 扩展数据: 全部纳入版本控制(含 config.json 表结构 + part.parquet 数据文件) # 扩展数据: 全部纳入版本控制(含 config.json 表结构 + part.parquet 数据文件)
# 注意 gitignore 否定规则特性: 需先放行目录才能放行内部文件 # 注意 gitignore 否定规则特性: 需先放行目录才能放行内部文件
!data/ext_data/ !data/ext_data/
@@ -102,5 +104,5 @@ data/strategies/custom/
# ===== 临时 / 调试产物 ===== # ===== 临时 / 调试产物 =====
backend._recheck*.py backend._recheck*.py
backend/_verify*.py backend/_verify*.py
refer/data local/data
.claude/ .claude/
+49 -1
View File
@@ -1 +1,49 @@
# README # A股智能量化工作台
本项目是一个面向个人散户与量化爱好者的 **A 股「选股 + 监控 + 回测」量化工作台**,采用前后端分离架构,支持 Docker 一键部署。
## 目录结构
```
stock/
├── local/ # 本地完整版(满血功能)
└── serve/ # 服务端轻量版(备份 + 基础查看)
```
### local/ — 本地完整版
自托管的满血工作站,所有功能本地运行:
- **选股**:20+ 内置策略,支持自定义信号与 AI 生成策略
- **回测**:基于 vectorbt 的向量化回测,支持 T+1、手续费、止损等真实约束
- **实时监控**:策略 / 个股信号 / 价格 / 异动四类规则,SSE 实时告警 + 飞书推送
- **个股分析**:日 K 图表、9 类关键价位、AI 四维分析
- **数据扩展**TickFlow 多源数据 + 第三方 HTTP / CSV / Excel 接入
详细说明与部署指南见 [`local/README.md`](./local/README.md)。
### serve/ — 服务端轻量版
`local/` 的云端伴侣,用于**数据备份**与**远程基础查看**:
- 接收 `local/` 推送的选股结果、复盘报告、历史 K 线等基础数据
- 支持多用户(admin / viewer)角色隔离
- 不包含回测、实时监控、实时行情等需要本地运行的能力
详细说明与部署指南见 [`serve/README.md`](./serve/README.md)。
## 快速选择
| 场景 | 使用目录 |
| :--- | :--- |
| 在自己的电脑 / 服务器上跑完整功能 | [`local/`](./local/README.md) |
| 只需要远程备份和查看基础数据 | [`serve/`](./serve/README.md) |
| 既要本地分析,又要远程查看 | 同时部署 `local/` + `serve/`,并通过 `SYNC_SERVE_URL` 同步 |
## 数据安全提示
两个目录下的 `data/` 文件夹均为运行时生成的用户数据,**不纳入 git 管理**。更新代码时 `git pull` 不会影响已有数据,但请勿使用 `git clean -fdx``git reset --hard`,以免误删数据。
## 开源协议
[MIT](./LICENSE)
+334
View File
@@ -0,0 +1,334 @@
<div align="center">
# 📈 A股智能量化工作台(本地完整版)
**自托管、满血功能的 A 股「选股 + 监控 + 回测」量化工作台**
**面向个人散户与量化爱好者,所有功能本地运行**
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](./LICENSE)
[![Python](https://img.shields.io/badge/Python-≥3.11-blue.svg)](https://www.python.org/)
[![React](https://img.shields.io/badge/React-18-61dafb.svg)](https://react.dev/)
[![Data: TickFlow](https://img.shields.io/badge/Data-TickFlow-00b386.svg)](https://tickflow.org/auth/register?ref=V3KDKGXPEA)
[![Deploy: Docker](https://img.shields.io/badge/Deploy-Docker-2496ed.svg)](./Dockerfile)
[![GitHub stars](https://img.shields.io/github/stars/shy3130/tickflow-stock-panel?style=social)](https://github.com/shy3130/tickflow-stock-panel/stargazers)
</div>
<div align="center">
**[快速开始](#-快速开始)** · **[核心功能](#-核心功能)** · **[配置](#-配置)** · **[同步到服务端](#-同步到服务端)**
</div>
- 🆓 **开箱即用** — 留空 Key 即进 None 模式,历史日 K 免费体验,**无需付费**
- 🏠 **自托管零运维** — Docker 单容器部署,数据完全掌握在自己手里
- 🔍 **三位一体** — 选股(20 内置策略)+ 实时监控 + 向量化回测,Polars 毫秒级扫描全 A 股
- 🤖 **AI 加持** — 一句话生成策略代码,任意 OpenAI 兼容接口均可接入(留空即关闭)
- 📡 **实时行情与监控** — 自选股实时行情、五档盘口、监控规则命中后 SSE 弹窗 + 飞书推送
- 🔌 **自由扩展** — 自有量化项目数据,与内置数据同台分析
- 🇨🇳 **A 股专用** — 盘后自动 AI 复盘并推送至飞书等;连板梯队、涨停动量、内置 ths 概念 / 行业
基于 [TickFlow](https://tickflow.org/auth/register?ref=V3KDKGXPEA) 数据源。**明确不做**:不对标同花顺 / 通达信,不内置「AI 荐股 / 涨停预测」。
> ⚠️ 考虑到 tickflow 数据源没有人气/资金流向等个性化数据,我将开放自有的第三方数据以供大佬们研究使用,包括但不限于当前内置的 ths 概念/ths 行业(后续更新在这里)
>
> 有更多稳定免费数据源推荐,或者提交建议/意见的大佬可以邮件到 415333856@qq.comq群 109338242
觉得有用可以点个 Star,蟹蟹 🌹
---
## 🎯 项目定位
`local/` 目录下的版本是**本地完整版**,面向希望在自己的电脑或局域网中运行全部功能的用户。
它包含完整的选股、回测、实时监控、个股分析、财务分析、连板梯队、概念/行业分析等全部能力,是项目功能的「满血」形态。同时支持把数据同步到远程 `serve/` 服务端,作为云端备份和基础数据查看入口。
---
## 📸 界面预览
<table>
<tr>
<td width="50%" align="center"><b>看板 Dashboard</b></td>
<td width="50%" align="center"><b>策略 Screener</b></td>
</tr>
<tr>
<td width="50%"><img src="./screenshots/dashboard.png" alt="看板页面"></td>
<td width="50%"><img src="./screenshots/screener.png" alt="策略页"></td>
</tr>
<tr>
<td width="50%" align="center"><b>回测 Backtest</b></td>
<td width="50%" align="center"><b>监控中心 Monitor</b></td>
</tr>
<tr>
<td width="50%"><img src="./screenshots/backtest.png" alt="回测页"></td>
<td width="50%"><img src="./screenshots/monitor.png" alt="监控中心"></td>
</tr>
<tr>
<td width="50%" align="center"><b>连板梯队 Limit Ladder</b></td>
<td width="50%" align="center"><b>概念分析 Concept</b></td>
</tr>
<tr>
<td width="50%"><img src="./screenshots/limit-ladder.png" alt="连板梯队页"></td>
<td width="50%"><img src="./screenshots/concept-analysis.png" alt="概念分析"></td>
</tr>
</table>
<div align="center">
### 📸 [查看更多界面截图 »](./screenshots/README.md)
</div>
---
## 🚀 快速开始
### 前置依赖
| 工具 | 版本 | 安装 |
| :--------------------------------- | :----- | :------------------------------------------------- |
| Python | ≥ 3.11 | [python.org](https://www.python.org/) |
| Node | ≥ 20 | [nodejs.org](https://nodejs.org/) |
| [`uv`](https://docs.astral.sh/uv/) | latest | `curl -LsSf https://astral.sh/uv/install.sh \| sh` |
| `pnpm` | 9 | `npm i -g pnpm` |
### 方式 A:Dev 模式(二次开发推荐)
```bash
cp .env.example .env # 按需填 TICKFLOW_API_KEY(留空 = None 模式)
./dev.sh # Windows: .\dev.ps1
```
自动检查 / 下载依赖、释放端口、同时起前后端,Ctrl-C 一并关闭。默认:
- 后端 → <http://localhost:3018> · 前端 → <http://localhost:3011>
- 自定义端口:`BACKEND_PORT=8000 FRONTEND_PORT=5173 ./dev.sh`
### 方式 BDocker(部署最省心)
```bash
cp .env.example .env
docker compose up --build
# 打开 http://localhost:3018
```
容器名:`TickFlow_Local`。默认端口 `3018`,数据持久化到 `./data/`
<details>
<summary><b>环境适配与高级选项(老 CPU · 手动启动 · 回测依赖)</b></summary>
**老 CPU 兼容(avx2/fma 缺失报错或 exit 132**:桌面客户端安装包已内置兼容内核(新老 CPU 通吃)。Docker / 源码用户在 `.env` 打开 `BACKEND_EXTRAS=legacy-cpu` 后重建,会给 Polars 切到 `rtcompat` 运行时;需回测则 `BACKEND_EXTRAS=legacy-cpu backtest`
**手动分别启动:**
```bash
# 后端
cd backend && uv sync --extra backtest # 含回测依赖
uv run uvicorn app.main:app --reload --port 3018
# 前端
cd frontend && pnpm install && pnpm dev # http://localhost:3011
```
**回测依赖**vectorbt → numba 体积较大,作为可选 extras(`uv sync --extra backtest`)。macOS / Intel 无预构建 wheel 时需 `brew install cmake` 现场编译。
</details>
### 🔄 更新代码(已部署用户必读)
拉取新版本只需一条命令:
```bash
git pull
```
**整个 `data/` 目录都不纳入 git**——行情 K线、财务、自选、回测、监控记录,乃至概念/行业扩展数据,全部是程序运行时生成/拉取的用户数据,`git pull` 物理上无法影响它们。新用户首次启动时,概念/行业两份扩展数据会自动从远程接口拉取,无需任何手动操作。
> ⚠️ **切勿使用以下命令"解决冲突"或"清理",它们会一次性删光 `data/` 下所有未被 git 跟踪的数据:**
> - `git clean -fdx`(最危险,会删掉所有 `.gitignore` 忽略的文件)
> - `git reset --hard`
> - 直接删除整个项目文件夹重新 `git clone`
>
> 若 `git pull` 报冲突,通常是本地误改了被跟踪的文件,请先 `git stash` 暂存再 pull,或单独联系作者,不要直接执行上面的命令。
### 🧭 跑起来后的第一次使用
1. **设置 → 凭据与能力** → 点 **重新检测**,确认档位标签
2. **设置****立即跑盘后管道**:拉日 K + 计算 enriched 表(None / Free 走 free-api,当日数据盘后 1-2 小时可用)
3. **自选**页加标的 → **选股**页点策略卡片扫描 / 配自定义信号
4. **回测**页选策略 + 区间 → 看净值 / 夏普 / 交易明细(SSE 实时进度)
5. **监控中心**配规则(策略 / 个股信号 / 价格 / 异动),盘中实时弹窗 + 持久化记录
---
## ✨ 核心功能
### 🔍 选股引擎(Screener
**20 个内置策略**,每个策略一个独立 Python 文件,基于 Polars 表达式向量化实现(`backend/app/strategy/builtin/`):
| 类型 | 代表策略 |
| :---------- | :------------------------------------------------------- |
| 趋势 / 形态 | 趋势突破 · 均线多头 · MA 金叉 · MACD 金叉放量 · 布林突破 |
| 量价 / 涨停 | 量价齐升 · 高换手强势 · 连板股 · 断板反包 · 涨停动量 |
| 反转 / 波动 | 超跌反弹 · 超卖反转 · 新低反转 · 低波动龙头 · 回踩 MA20 |
**扩展策略的三种方式:**
| 方式 | 说明 |
| :---------------- | :---------------------------------------------------------------------------------------------------- |
| **🎛️ 自定义信号** | 不写代码,UI 上 `字段 + 操作符 + 阈值` 组合编译成 Polars 表达式热加载 |
| **🤖 AI 生成** | 一句话描述思路,LLM 读 `strategy-guide.md` 生成完整策略文件(经 `ast` 校验)→ 落入 `data/strategies/ai/` |
| **📝 代码迁移** | 参照开发指南把已有策略改写为 Polars 文件放入 `data/strategies/custom/`,引擎自动发现 |
### 📊 指标流水线(Indicators
原生 Polars 向量化,全 A 股一次扫表落盘 enriched Parquet
- **均线 / 趋势**MA5-60)· EMA · MACD · 动量 · 布林带
- **震荡 / 波动**RSI · KDJ · ATR · 年化波动率 · 振幅
- **量能 / 涨跌停**:量比 · 量均线 · 涨停信号 · 连板数
- **原子信号**MA / MACD 金叉死叉 · N 日新高新低 · 布林突破
- **复权**:基于除权因子自动前复权,回测与指标口径一致
### 🧪 回测引擎(Backtest
基于 vectorbt:**三种模式**(个股 / 策略组合 / 自由信号组合),真实约束(T+1 · 手续费 · 滑点 · 止损 · 最大持仓天数),组合管理(最大持仓 · 敞口 · 等权 / 自定义仓位)。SSE 流式进度支持切页重连,输出净值曲线 · 夏普 · 最大回撤 · 胜率 · 交易明细。
### 📡 监控中心(Monitor
统一规则引擎,一个页面管理**四类监控**(策略 · 个股信号 · 价格涨跌 · 全市场异动):
- 多条件 AND/OR + 冷却期去重 + 严重级别(info/warn/critical
- 多入口配置:监控中心新建 / 个股详情页「加监控」/ 策略卡片一键开启
- 命中后右下角弹窗(可配声效)+ 持久化到 `alerts.jsonl`,菜单未读徽标
- **触发记录详情**:每条记录展示命中的具体条件(如 `RSI>80`)与当前价位,一眼看清为何触发
- **飞书 Webhook 推送**:全局一处配置飞书群机器人地址,启用推送的规则命中即推送到飞书群(支持签名校验);可在设置页设「默认推送渠道」,新建规则自动预填
- **SSE 实时推送**:本地运行时,命中告警通过 Server-Sent Events 实时推送到前端
### 📈 个股分析(Beta
以「行情 + 关键价位」为主体的单标的决策页:
- **专用日 K 图表**:主图 + 成交量 + 滑块,默认近 6 个月
- **9 类关键价位**(纯函数实时计算,毫秒级):压力支撑 · 成交密集区 · 枢轴点 · 前高前低 · Keltner 通道 · ATR 止损 · 缺口位 · 斐波那契 · 整数关口
- **AI 四维分析**:技术 / 基本面 / 财务 / 消息面流式生成,实战派交易员视角
### 🧰 数据与扩展
- **TickFlow 多源数据**:日 K / 分钟 K / 指数 / 财务 / 实时行情
- **🔌 第三方接入(重点)**:Tushare 等 HTTP 定时拉取 · CSV / Excel 上传 · JSON 写入,自动 schema 发现 + 符号归一,页面可视化配置,**可与自有量化项目数据并入 DuckDB 同台分析**
- **盘后定时管道**APScheduler 15:30 CST 自动拉日 K + 重算 enriched + 跑监控
- **令牌桶限流**:适配各档位 rpm / batch,批量合并 + 增量拉取
---
## ⚙️ 配置
所有配置从根目录 `.env` 读取(复制 `.env.example` 开始),也可在面板 **设置** 页修改。
### 数据源:TickFlow
```ini
TICKFLOW_API_KEY= # 留空 = None 模式(历史日K免费);填 Key = 按订阅档位解锁
```
留空即 None 模式,通过 free-api 使用历史日 K(当日数据盘后 1-2 小时可用);免费注册 Key 后进 Free 模式,开启自选股实时监控。**实时行情按档位**:
| 档位 | 实时能力 |
| :------- | :--------------------------------------- |
| None | 无实时行情,仅历史日 K |
| Free | 自选页前 5 个标的实时监控(最低 6 秒刷新) |
| Starter+ | 全市场实时行情 |
| Pro | 分钟 K + 盘口 |
| Expert | WebSocket + 财务数据 |
> 完整能力矩阵见 [tickflow.org/pricing](https://tickflow.org/pricing/),高等档位含较低档全部权益。
### AI(可选)
用于自然语言生成策略。**所有配置留空即跳过**,不影响核心功能。支持任意 OpenAI 兼容接口:
```ini
AI_PROVIDER=openai_compat # openai_compat | ollama
AI_BASE_URL=https://api.deepseek.com/v1
AI_API_KEY= # 留空 = 关闭 AI
AI_MODEL=deepseek-chat
AI_DAILY_TOKEN_BUDGET=500000 # 每日 token 预算上限
```
### 服务与数据
```ini
HOST=0.0.0.0 # 监听地址
PORT=3018 # 服务端口
LOG_LEVEL=INFO # DEBUG | INFO | WARNING | ERROR
DATA_DIR=./data # Parquet / DuckDB 数据存储目录
```
### 访问密码
面板首次设置访问密码时,出于安全考虑**仅允许本机或内网访问**(防公网陌生人抢先设置锁死面板)。公网服务器部署有两种方式设首个密码:
1. **环境变量预置(推荐)** — 在 `.env` 填入 `AUTH_PASSWORD`,首次启动自动初始化(哈希后写入 `auth.json`,之后不再读取):
```ini
AUTH_PASSWORD=你的密码 # 至少 6 位;仅首次生效,已设过则不覆盖
```
2. **SSH 端口转发** — 本机执行 `ssh -L 3018:127.0.0.1:3018 用户@服务器IP`,浏览器开 `http://127.0.0.1:3018` 设密码
> 详细步骤与重置密码见 [docs/deploy-password.md](./docs/deploy-password.md)。设完密码后改密码走页面 UI(`设置 → 修改密码`)。
---
## 🔄 同步到服务端
本地完整版支持把核心数据同步到远程 `serve/` 服务端,作为:
- **异地备份**:防止本地磁盘损坏导致历史数据丢失
- **远程查看**:在手机或其他设备上通过浏览器查看基础行情与选股结果
配置方式:
```ini
SYNC_SERVE_URL=https://your-serve.example.com
SYNC_KEY=your-shared-secret
```
> 同步是**单向**的:本地 → 服务端。服务端只接收并展示基础数据,不包含本地版的实时行情、回测、监控等复杂能力。详细说明见 `../serve/README.md`。
---
## 🏗️ 技术栈
| 层 | 选型 |
| :----------- | :------------------------------------------------------------------------------------------------ |
| **后端** | FastAPI · Pydantic v2 · APScheduler · sse-starlette |
| **数据** | Polars(计算)· DuckDB(查询)· Parquet(存储) |
| **回测** | vectorbt(全项目唯一 pandas 边界) |
| **数据源** | [TickFlow](https://tickflow.org/auth/register?ref=V3KDKGXPEA) 官方 SDK |
| **AI**(可选) | OpenAI 兼容接口(DeepSeek / 通义 / Ollama 等) |
| **前端** | React 18 · Vite · TypeScript · Tailwind · Tanstack Query · Lightweight Charts · ECharts · dnd-kit |
| **部署** | Docker 两阶段构建,前端 dist 拷进后端镜像,**单容器** |
| **桌面端** | PyInstaller + Inno SetupWindows 安装包) |
---
## 🗺️ 路线图
| Phase | 内容 | 状态 |
| :----- | :----------------------------------------------------------------- | :--- |
| 0-1 | 仓库骨架 · FastAPI 壳 · 能力探测 · K 线同步与分析页 | ✅ |
| 2-3 | Polars enriched 流水线 · Screener · vectorbt 回测(T+1/手续费/止损) | ✅ |
| 4-5 | 监控引擎 · 四类监控规则 · 实时 SSE 推送 · 持久化记录 | ✅ |
| 6 | 个股分析(专用日 K + 9 类关键价位 + AI 四维分析) | ✅ |
| **v2** | Webhook 推送(QMT/掘金下单)· 板块异动 · 早晚报 · 更多扩展 | 🚧 |
---
## 📚 文档与贡献
- [docs/strategy-guide.md](./docs/strategy-guide.md) —— 策略开发指南(AI 生成与手写规范)
- [docs/](./docs) —— 策略构建步骤、示例
View File
+201
View File
@@ -0,0 +1,201 @@
"""数据同步 — 将本地 data/ 打包推送到 serve 端。"""
from __future__ import annotations
import json
import logging
import tarfile
import tempfile
from datetime import datetime
from io import BytesIO
from pathlib import Path
import httpx
from fastapi import APIRouter, HTTPException, Request
from pydantic import BaseModel
from app.config import settings
logger = logging.getLogger(__name__)
router = APIRouter(prefix="/api/data/sync", tags=["data-sync"])
# 允许同步的子目录 — 覆盖 serve 功能面板所需的数据
SYNCABLE_PARTS = [
"kline_daily",
"kline_daily_enriched",
"kline_index_daily",
"kline_index_enriched",
"kline_etf_daily",
"kline_etf_enriched",
"kline_etf_minute",
"kline_minute",
"adj_factor",
"adj_factor_etf",
"instruments",
"instruments_index",
"instruments_etf",
"financials",
"pools",
"ext_data",
]
def _part_size_info(data_dir: Path, part: str) -> dict:
"""获取指定子目录的文件数和大小。"""
part_dir = data_dir / part
if not part_dir.exists():
return {"file_count": 0, "size_bytes": 0, "last_modified": None}
files = list(part_dir.rglob("*.parquet"))
if not files:
return {"file_count": 0, "size_bytes": 0, "last_modified": None}
total = sum(f.stat().st_size for f in files)
mtimes = [f.stat().st_mtime for f in files]
last_ts = datetime.fromtimestamp(max(mtimes)).isoformat() if mtimes else None
return {"file_count": len(files), "size_bytes": total, "last_modified": last_ts}
def _pack_parts(data_dir: Path, parts: list[str]) -> bytes:
"""将指定子目录打包为 tar.gz 字节流。"""
buf = BytesIO()
with tarfile.open(fileobj=buf, mode="w:gz") as tar:
for part in parts:
part_dir = data_dir / part
if not part_dir.exists():
continue
for fpath in sorted(part_dir.rglob("*.parquet")):
rel = fpath.relative_to(data_dir)
tar.add(fpath, arcname=str(rel))
buf.seek(0)
return buf.getvalue()
class SyncConfig(BaseModel):
serve_url: str = ""
sync_key: str = ""
enable_auto: bool = False
interval_minutes: int = 0
# ── 存储配置用的 JSON 文件 ──────────────────────────────────
CONFIG_FILE = "sync_config.json"
def _config_path(data_dir: Path) -> Path:
return data_dir / "user_data" / CONFIG_FILE
def _load_config(data_dir: Path) -> SyncConfig:
cp = _config_path(data_dir)
if cp.exists():
try:
return SyncConfig(**json.loads(cp.read_text()))
except Exception:
pass
return SyncConfig()
def _save_config(data_dir: Path, cfg: SyncConfig) -> None:
cp = _config_path(data_dir)
cp.parent.mkdir(parents=True, exist_ok=True)
cp.write_text(cfg.model_dump_json(indent=2))
# ── 路由 ──────────────────────────────────────────────────
@router.get("/status")
def sync_status(request: Request):
"""各数据目录的本地状态 + 同步配置。"""
data_dir: Path = request.app.state.datastore.data_dir
cfg = _load_config(data_dir)
parts = {}
for p in SYNCABLE_PARTS:
parts[p] = _part_size_info(data_dir, p)
return {
"parts": parts,
"config": cfg.model_dump(),
"last_sync": cfg.model_dump().get("_last_sync_time"),
}
class ConfigReq(BaseModel):
serve_url: str
sync_key: str
enable_auto: bool = False
interval_minutes: int = 0
@router.post("/config")
def update_sync_config(request: Request, body: ConfigReq):
"""更新同步配置。"""
data_dir: Path = request.app.state.datastore.data_dir
cfg = SyncConfig(
serve_url=body.serve_url.rstrip("/"),
sync_key=body.sync_key,
enable_auto=body.enable_auto,
interval_minutes=body.interval_minutes,
)
_save_config(data_dir, cfg)
return {"ok": True}
class PushReq(BaseModel):
parts: list[str]
@router.post("/push")
async def push_sync(request: Request, body: PushReq):
"""打包指定 parts 推送到 serve 端。"""
data_dir: Path = request.app.state.datastore.data_dir
cfg = _load_config(data_dir)
if not cfg.serve_url:
raise HTTPException(status_code=400, detail="未配置 serve_url,请先在同步设置中配置")
requested = [p for p in body.parts if p in SYNCABLE_PARTS]
if not requested:
raise HTTPException(status_code=400, detail="未指定有效的同步目录")
# 检查有没有数据可同步
available = [p for p in requested if (data_dir / p).exists()]
if not available:
raise HTTPException(status_code=400, detail="所选目录均无数据可同步")
# 打包
tarball = _pack_parts(data_dir, available)
size_bytes = len(tarball)
logger.info("packed %d parts -> %d bytes", len(available), size_bytes)
# 推送
upload_url = f"{cfg.serve_url}/api/data/sync/upload"
headers = {"X-Sync-Key": cfg.sync_key} if cfg.sync_key else {}
try:
async with httpx.AsyncClient(timeout=600) as client:
resp = await client.post(
upload_url,
files={"file": ("sync.tar.gz", tarball, "application/gzip")},
data={"parts": ",".join(available)},
headers=headers,
)
if resp.status_code != 200:
detail = resp.text[:500]
raise HTTPException(
status_code=502,
detail=f"serve 返回异常 ({resp.status_code}): {detail}",
)
result = resp.json()
except httpx.RequestError as exc:
raise HTTPException(status_code=502, detail=f"连接 serve 失败: {exc}") from exc
# 记录上次同步时间
cfg_dict = cfg.model_dump()
cfg_dict["_last_sync_time"] = datetime.now().isoformat()
cfg = SyncConfig(**cfg_dict)
_save_config(data_dir, cfg)
logger.info("sync pushed: parts=%s files=%d", available, result.get("file_count", 0))
return {
"ok": True,
"parts_sent": available,
"size_bytes": size_bytes,
"serve_response": result,
}
@@ -95,6 +95,10 @@ class Settings(BaseSettings):
log_level: str = "INFO" log_level: str = "INFO"
backtest_range_guard: bool = False backtest_range_guard: bool = False
# Sync — 数据同步到 serve
sync_serve_url: str = ""
sync_key: str = ""
# Auth — 首次启动时预置访问密码(明文, 仅用于初始化, 详见 services/auth.bootstrap_from_env) # Auth — 首次启动时预置访问密码(明文, 仅用于初始化, 详见 services/auth.bootstrap_from_env)
# 公网服务器部署时免去 SSH 端口转发设密码的麻烦。写入 auth.json(哈希)后即不再读取。 # 公网服务器部署时免去 SSH 端口转发设密码的麻烦。写入 auth.json(哈希)后即不再读取。
auth_password: str = "" auth_password: str = ""
@@ -11,7 +11,7 @@ from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles from fastapi.staticfiles import StaticFiles
from app import __version__ from app import __version__
from app.api import analysis, auth as auth_api, backtest, data, ext_data, financials, indices, intraday, kline, market_recap, monitor_rules, alerts, overview, pipeline, rps, screener, settings as settings_api, signals, stock_analysis, strategy, watchlist from app.api import analysis, auth as auth_api, backtest, data, data_sync, ext_data, financials, indices, intraday, kline, market_recap, monitor_rules, alerts, overview, pipeline, rps, screener, settings as settings_api, signals, stock_analysis, strategy, watchlist
from app.api.routes import router as core_router from app.api.routes import router as core_router
from app.config import settings from app.config import settings
from app.jobs import daily_pipeline from app.jobs import daily_pipeline
@@ -207,7 +207,7 @@ app.add_middleware(
# 2. 未设密码 + 公网 → 拒绝(403, 防裸奔也防抢占; 引导本机设密码) # 2. 未设密码 + 公网 → 拒绝(403, 防裸奔也防抢占; 引导本机设密码)
# 3. 已设密码 → 检查 session, 无效则 401(前端跳登录) # 3. 已设密码 → 检查 session, 无效则 401(前端跳登录)
# 白名单: /api/auth/* (设密码/登录本身)、/health 等探活。 # 白名单: /api/auth/* (设密码/登录本身)、/health 等探活。
_AUTH_WHITELIST_PREFIX = ("/api/auth/",) _AUTH_WHITELIST_PREFIX = ("/api/auth/", "/api/data/sync/")
_AUTH_WHITELIST_EXACT = ("/health", "/api/health", "/openapi.json", "/docs", "/redoc") _AUTH_WHITELIST_EXACT = ("/health", "/api/health", "/openapi.json", "/docs", "/redoc")
@@ -257,6 +257,7 @@ app.include_router(overview.router)
app.include_router(analysis.router) app.include_router(analysis.router)
app.include_router(pipeline.router) app.include_router(pipeline.router)
app.include_router(data.router) app.include_router(data.router)
app.include_router(data_sync.router)
app.include_router(ext_data.router) app.include_router(ext_data.router)
app.include_router(financials.router) app.include_router(financials.router)
app.include_router(stock_analysis.router) app.include_router(stock_analysis.router)

Some files were not shown because too many files have changed in this diff Show More