Files
stock/README.md
2026-09-09 11:35:02 +08:00

350 lines
17 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 股市回测平台
专业的 **历史回测 + 回放式模拟** 平台A 股为主,**不做实盘**)。
输入技术指标参数MACD/RSI/KDJ…→ 后端回测 → 前端可视化 K 线、指标叠加、买卖点、净值曲线与绩效。
技术选型与架构决策详见 [TECH_STACK.md](./TECH_STACK.md)。
---
## 快速启动
两个终端分别启动后端与前端(命令均从仓库根 `stock/` 执行):
```bash
# 终端 1 —— 后端 APIhttp://localhost:8000
bash backend/restart_backend.sh
# 终端 2 —— 前端开发服务器http://localhost:5173
cd frontend
pnpm dev
```
> 后端统一走 `backend/restart_backend.sh`:自动杀旧进程树 + 剔除会崩 asyncpg 的 `SSLKEYLOGFILE` + 带热重载重启 + 日志写 `backend_run.log`。Windows cmd 下等价命令:`backend\restart_backend.cmd`。详见下文「快速开始(开发模式)」。
**首次运行前**
1. 装依赖:后端 `cd backend && uv sync`、前端 `cd frontend && pnpm install`
2. 后端依赖 **PostgreSQL16/17+ Redis 已启动**,并配好 `backend/.env`(复制 `.env.example`)。
3. 建库表:`cd backend && uv run alembic upgrade head`
浏览器打开 http://localhost:5173 即可使用。详细说明见下文「快速开始(开发模式)」。
---
## 功能特性
**已实现Phase 0 MVP**
- 自定义指标参数回测(当前内置 MACD 金叉死叉策略)
- K 线主图 + 成交量 + MACD 副图DIF/DEA/柱)
- **买卖点标注**(信号回放到 K 线,↑买 ↓卖)
- **周期切换**:日线 / 周线 / 月线 / 年线(日线为基底,应用层 pandas 聚合)
- **悬停弹框**:鼠标移到任意 K 线,显示当日 开/高/低/收、涨跌幅、振幅、成交量(手)、MACD 值(同花顺式)
- 净值曲线 + 绩效指标(总收益、最大回撤、夏普、年化波动、胜率、交易次数)
- A 股真实成本建模:印花税 0.05%(单边卖出)、过户费 0.001%沪深双边、佣金万1最低5元、T+1、100 股整手
- 回测运行注册表(每次回测落库,可复现/审计的基础)
**规划中(见 TECH_STACK.md 路线图)**
- 防过拟合体检walk-forward / 样本外 / FDR 多重比较修正)
- 基准归因对比沪深300/中证500超额、信息比率、beta/alpha
- 复权因子管道、回放式模拟盘
- 切 TimescaleDBhypertable + Continuous Aggregates
---
## 技术栈
| 层 | 选型 |
|---|---|
| 后端 | Python 3.12+ · FastAPI · Pydantic v2 · SQLAlchemy 2.0 (async) · uv包管理 |
| 回测引擎 | 自研单一引擎fast/strict 两档),指标纯 numpy/pandas生产可换 TA-Lib |
| 数据库 | PostgreSQL 16/17后端强制要求+ Redis 缓存TimescaleDB 规划中 |
| 前端 | Vue 3.5 · Vite · TypeScript · Pinia · PrimeVue 5 · pnpm |
| 图表 | lightweight-charts 5K 线)· ECharts 6净值 |
---
## 目录结构
```
stock/
├── TECH_STACK.md # 技术选型与架构决策(必读)
├── README.md # 本文件
├── backend/ # FastAPI + 回测引擎
│ ├── pyproject.toml # uv 依赖声明pytest 在 dev 组)
│ ├── .env.example # 配置示例(数据库 / 费率)
│ ├── smoke_test.py # 后端全链路自检脚本(线上库鉴权+核心 API
│ ├── conftest.py # pytest 根(使 tests/ 可直接 import app.*
│ ├── tests/ # pytest 单元测试(交割单解析/复权换算/缓存/限速/事件回测纯函数)
│ ├── scripts/ # 一次性运维脚本TDX 导入 / 复权因子回补…)
│ └── app/
│ ├── main.py # FastAPI 入口lifespan 拉起夜间调度)
│ ├── config.py # 配置pydantic-settings
│ ├── db.py # async SQLAlchemy 引擎/会话
│ ├── domain.py # 领域契约Bar/Signal/Fill/Position…
│ ├── models.py # ORMCandle / StockBasic / 用户数据表…)
│ ├── schemas.py # Pydantic DTO= OpenAPI 契约)
│ ├── auth.py # Argon2 密码 + 会话SHA-256 摘要)
│ ├── auth_api.py # 登录/登出(含按 IP 限速)
│ ├── scheduler.py # 夜间定时任务(收盘后自动同步 + 会话清理)
│ ├── cache.py # Redis 读缓存(本地层 + 版本号失效 + 熔断冷却恢复)
│ ├── indicators.py # 指标MACD/RSI/KDJ/布林/均线(单一事实源)
│ ├── api/ # 路由包stocks / etfs / market / backtest / screener / user + _deps 共享件
│ ├── data/ # 数据管道tushare 适配 + 同步 + 懒加载缓存)
│ ├── screener/ # 智能选股LLM 解析 + 全市场引擎 + 夜间同步)
│ └── backtest/ # engine / events(全市场事件回测) / strategies
└── frontend/ # Vue SPA
├── src/
│ ├── main.ts # Vue + Pinia
│ ├── api/ # 类型化客户端 + DTO 镜像
│ ├── stores/ # Pinia鉴权/设置/同步状态)
│ ├── composables/ # 组合式工具(防抖 ref / 路由 query 同步)
│ ├── components/ # StockDetailOverlay / DetailKLine / LimitBoard / MarketOverview…
│ └── views/ # Home / Stocks / ETF / Concepts / Indexes / Screener / Backtest
└── vite.config.ts # /api 代理到 :8000
```
---
## 环境要求
| 工具 | 版本 | 说明 |
|---|---|---|
| Node.js | ≥ 20实测 24 | 前端 |
| pnpm | ≥ 9实测 11 | 前端包管理 |
| Python | ≥ 3.12(实测 3.14 | 后端 |
| uv | 任意(实测 0.12 | 后端包管理,[安装](https://docs.astral.sh/uv/) |
| PostgreSQL | 16/17 | 数据库(必装,后端已不再支持 SQLite |
**安装 uv**(若未装):
```bash
pip install uv
```
---
## 快速开始(开发模式)
需要两个终端,分别跑后端与前端。
### 1) 后端
实际启动命令(一键脚本,自动杀旧进程树 + 剔除 SSLKEYLOGFILE + 热重载 + 日志写仓库根 `backend_run.log`
```bash
uv sync # 创建 .venv 并安装依赖(仅首次)
bash backend/restart_backend.sh # 从仓库根执行Git BashWindows cmd 下等价backend\restart_backend.cmd
```
看日志:`tail -f backend_run.log`;确认起没起:`curl http://localhost:8000/api/health`
等价的 `uv run` 写法(不杀旧进程、日志直接打到当前终端):
```bash
cd backend
env -u SSLKEYLOGFILE uv run uvicorn app.main:app --reload --port 8000
```
- **`env -u SSLKEYLOGFILE`(本机 Windows 必需)**:用户环境变量 `SSLKEYLOGFILE` 的值开头混有 U+202A 不可见控制符asyncpg 建连时执行 `ssl.keylog_filename` 直接抛 `OSError: [Errno 22]`uvicorn 启动即崩。Git Bash 下用 `env -u` 剔除即可;根治可 `setx SSLKEYLOGFILE "C:\Users\cirry\Desktop\fhzg.log"`(重开终端后不再需要前缀)。验证:`python -c "import os; print(repr(os.environ.get('SSLKEYLOGFILE')))"`
- **Windows `--reload` 僵死**watcher 偶尔改文件不重载且日志无 Reloading 行,此时只能杀进程树重启——一键脚本 `bash backend/restart_backend.sh`Git Bash`backend\restart_backend.cmd`cmd自动杀旧进程树`--reload` 起 launcher→reloader→worker 三层进程)+ 带 reload 重启 + 日志写 `backend_run.log`。手动:`netstat -ano | grep :8000` 找 PID`taskkill //PID <pid> //T //F`
- 数据库结构由 Alembic 管理:首次部署/更新代码后先执行 `uv run alembic upgrade head`(见「初始化登录系统」)。
- 交互式 API 文档http://localhost:8000/docs
- **夜间自动同步**:后端启动即拉起调度(`app/scheduler.py`),每日 18:05 本地时间自动跑全市场 A 股 + ETF 同步并清理过期会话;进程启动时若已过点且当日数据未落库会补跑。关闭:`.env``NIGHTLY_SYNC_ENABLED=false`,时间改 `NIGHTLY_SYNC_HOUR`
**测试**
```bash
uv run --directory backend pytest -q # 单元测试(交割单解析/复权换算/缓存/限速等纯函数,不碰库)
uv run --with httpx --directory backend python smoke_test.py # 全链路自检(线上库鉴权+核心 API
```
### 2) 前端
```bash
cd frontend
pnpm install # 首次
pnpm dev # http://localhost:5173
```
- **必须用 pnpm不要用 npm/yarn**:项目锁定 `pnpm-lock.yaml``node_modules` 是 pnpm 的硬链接结构,`npm install` 会写坏依赖,导致装包/构建崩溃。以后装包一律 `pnpm add <pkg>`(不要 `npm install <pkg>`)。
- 前端 `/api` 请求由 Vite 代理到后端 `:8000`(见 `vite.config.ts`),无需处理跨域。
打开 http://localhost:5173 → 选周期、改参数 → 点「开始回测」。
鼠标悬停 K 线可看当日详情弹框;切日线/周线/月线/年线勾「fast 模式」可对比关闭费用/T+1 的差异。
---
## 配置说明
后端配置通过 `backend/.env`(复制 `.env.example`)或环境变量:
```bash
# 数据库(开发、测试、生产统一使用 PostgreSQL
DATABASE_URL=postgresql+asyncpg://user:password@host:5432/stock
# 鉴权:本地 HTTP 为 false线上 HTTPS 必须为 true
AUTH_COOKIE_SECURE=false
AUTH_SESSION_HOURS=12
CORS_ORIGINS=http://localhost:5173
# 生产建议关闭接口文档
EXPOSE_API_DOCS=true
# 真实数据源Tushare Pro免费版即可
TUSHARE_TOKEN=你的token
DATA_ADJUST=qfq # 复权qfq 前复权 / hfq 后复权 / 留空不复权
DATA_DEFAULT_START=20200101
# A 股交易成本(基准日 2026-08可覆盖默认值见 app/config.py
# STAMP_DUTY_RATE=0.0005 # 印花税 0.05%,单边卖出
# TRANSFER_FEE_RATE=0.00001 # 过户费 0.001%,沪深双边
# COMMISSION_RATE=0.0001 # 佣金 万1
# COMMISSION_MIN=5.0 # 最低 5 元
```
### 初始化登录系统
数据库结构由 Alembic 管理。首次部署或更新代码后执行:
```bash
cd backend
uv run alembic upgrade head
```
系统不提供注册接口。使用服务器交互式命令创建唯一用户;再次执行会重置密码并吊销该用户的所有旧会话:
```bash
uv run python -m app.cli.create_user --username admin
```
密码使用 Argon2id 保存。浏览器只接收 `HttpOnly` 会话 Cookie数据库只保存随机会话 Token 的 SHA-256 摘要。默认会话有效期 12 小时,连续 5 次登录失败后锁定 15 分钟。
生产环境必须启用 HTTPS并设置
```bash
AUTH_COOKIE_SECURE=true
CORS_ORIGINS=https://你的域名
EXPOSE_API_DOCS=false
```
### 切到 PostgreSQL + TimescaleDB
1. 目标库执行 `CREATE EXTENSION IF NOT EXISTS timescaledb;`
2. `DATABASE_URL=postgresql+asyncpg://...`
3. TODO`candles` 表升级为 hypertable 并配置 Continuous Aggregates 多周期预聚合schema 已兼容,无需改表结构):
```sql
SELECT create_hypertable('candles', 'ts');
```
> 即便能装在现有 Postgres 上,建议为交易系统单独起一个 PG16/17 实例,便于调优/备份/隔离回测扫描负载。
---
## API
| 方法 | 路径 | 说明 |
|---|---|---|
| GET | `/api/health` | 健康检查 |
| POST | `/api/data/sync` | 拉取并缓存某标的日线Tushare 主 → AKShare 兜底。body: `{symbol, source?, force?}` |
| POST | `/api/backtest` | 跑回测(真实标的首次自动拉取并缓存;前端已改用 `/api/backtest/event` 事件回测,此为旧策略回测 API |
个股 K 线统一走 `GET /api/screener/preview/{ts_code}`(含复权/指标预热/翻页,两级缓存)。
**回测请求示例**
```json
POST /api/backtest
{
"symbol": "000001",
"timeframe": "1d",
"strategy": "macd_cross",
"params": { "fast": 12, "slow": 26, "signal": 9 },
"initial_cash": 100000,
"fast_mode": false
}
```
返回:`candles`K线、`indicators`MACD 三线)、`signals`(买卖点)、`equity`(净值序列)、`metrics`(绩效)、`final_cash`/`final_position`。
> **关于「标的」**:标的为真实 A 股代码(如 `000001`、`600519`),首次回测时**自动经 Tushare 拉取并本地缓存**(需配置 `TUSHARE_TOKEN` + 联网),二次回测秒出。默认初始资金 100 万,足够交易高价股(如茅台)。
---
## 生产构建与部署
### 前端构建
```bash
cd frontend
pnpm build # 产物在 frontend/dist
pnpm preview # 本地预览构建产物
```
部署时把 `frontend/dist` 用任意静态服务器nginx / caddy / 对象存储)托管,并把 `/api` 反向代理到后端。
### 后端生产运行
```bash
cd backend
uv run alembic upgrade head
uv run uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4
# 或 gunicornLinuxuv run gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker
```
后端强制要求 `DATABASE_URL` 使用 PostgreSQL`postgresql+asyncpg://`),已不再支持 SQLite。
### 参考docker-compose具备 Docker 后使用)
```yaml
services:
db:
image: timescale/timescaledb:latest-pg17
environment: { POSTGRES_PASSWORD: stock, POSTGRES_DB: stock }
ports: ["5432:5432"]
volumes: ["pgdata:/var/lib/postgresql/data"]
api:
build: ./backend
environment: { DATABASE_URL: postgresql+asyncpg://postgres:stock@db:5432/stock }
ports: ["8000:8000"]
depends_on: [db]
web:
image: nginx:alpine
volumes: ["./frontend/dist:/usr/share/nginx/html:ro", "./nginx.conf:/etc/nginx/conf.d/default.conf:ro"]
ports: ["80:80"]
depends_on: [api]
volumes: { pgdata: {} }
```
---
## 扩展指南
### 加一个指标
在 `backend/app/indicators.py` 增加纯函数(输入 close/high/low Series输出 DataFrame保持函数签名风格即可被策略复用。例已内置 `macd / rsi / kdj / bollinger / ma`。
### 加一个策略
内置策略:`macd_cross`MACD 金叉死叉)、`ma_cross`(双均线交叉)、`single_ma`(单均线,价格穿越均线)。前端下拉自动出现新策略、参数自适应。
在 `backend/app/backtest/strategies/` 新增继承 `Strategy``base.py`)的类,实现 `compute()`(预算指标)与 `on_bar(i, row, broker)`(决策并向 `broker` 下单),再在 `__init__.py` 的 `STRATEGIES` 注册 `{name: Class}`。策略构造参数由请求 `params(dict)` 以 `**kwargs` 传入。策略只产生买卖意图,撮合/费用交给 `PaperBroker`。
### 数据源(已集成)
真实 A 股行情已集成(`backend/app/data/`
- `tushare_provider.py` —— 主数据源,需 `TUSHARE_TOKEN`,默认前复权(`pro_bar`,积分不足时自动退化为不复权日线)
- `akshare_provider.py` —— 兜底,免费免 token默认**未安装**`uv add akshare` 后自动启用
- `fetcher.py` —— 编排Tushare 失败自动切 AKShare拉取后写入 `candles` 表本地缓存(解耦上游停运/限频)
换复权方式改 `.env` 的 `DATA_ADJUST`;换默认拉取起点改 `DATA_DEFAULT_START`。
---
## 常见问题
- **`pnpm install` 报 `Ignored build scripts: esbuild`**:新版 pnpm 默认不运行依赖的安装脚本。已通过 `frontend/pnpm-workspace.yaml` 的 `allowBuilds: { esbuild: true, vue-demi: true }` 放行;若仍提示,运行 `pnpm approve-builds` 选择允许。
- **前端图表不显示**:确认后端已起(`/api/health` 返回 ok浏览器控制台看是否 404/跨域dev 应走 Vite 代理无跨域)。
- **lightweight-charts 报 `addCandlestickSeries is not a function`**:那是 v4 API。本项目用 v5 的 `chart.addSeries(CandlestickSeries, ...)`,请勿混用旧教程。
- **真实标的首次回测慢/拉取失败**:首次需联网请求 Tushare数秒。Tushare 对 `adj_factor` 限频(每小时 1 次),触发时自动退化为不复权日线,不影响回测。数据已本地缓存,二次回测秒出。若 Tushare 积分不足,`uv add akshare` 后将自动用 AKShare 兜底。
- **高价股(如茅台)回测 0 信号**:一手太贵买不起。默认初始资金已设为 100 万;如仍不够,在表单调高「初始资金」。
- **Python 3.14 兼容**MVP 依赖fastapi/sqlalchemy/pandas/numpy均已支持指标用纯 Python 实现,不依赖 TA-Lib 的 C 库Windows 免编译。
- **后端启动即崩,栈在 asyncpg 深处(`OSError: [Errno 22] Invalid argument`**:是本机环境变量 `SSLKEYLOGFILE` 值里混入的 U+202A 不可见控制符所致不是数据库问题。Git Bash 下启动命令前加 `env -u SSLKEYLOGFILE`;或 `setx SSLKEYLOGFILE "C:\Users\cirry\Desktop\fhzg.log"` 重写该变量后重开终端。
- **uvicorn `--reload` 改代码不生效**Windows 下 watcher 会僵死(日志无 Reloading 行touch 无效)。`netstat -ano | grep :8000` 找到 PID 后 `taskkill //PID <pid> //T //F` 杀进程树重启。
---
## 路线图
详见 [TECH_STACK.md](./TECH_STACK.md)「分阶段落地路线」。下一步优先级:
1. 接入 Tushare 真实数据
2. 更多指标/策略RSI/KDJ/布林策略)
3. 防过拟合体检 + 基准归因
4. 切 TimescaleDB、回测结果缓存与对比