# 股市回测平台 专业的 **历史回测 + 回放式模拟** 平台(A 股为主,**不做实盘**)。 输入技术指标参数(MACD/RSI/KDJ…)→ 后端回测 → 前端可视化 K 线、指标叠加、买卖点、净值曲线与绩效。 技术选型与架构决策详见 [TECH_STACK.md](./TECH_STACK.md)。 --- ## 功能特性 **已实现(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 路线图)** - 接入 Tushare/AKShare 真实 A 股数据(替换 DEMO 合成数据) - 防过拟合体检(walk-forward / 样本外 / FDR 多重比较修正) - 基准归因(对比沪深300/中证500:超额、信息比率、beta/alpha) - 复权因子管道、回放式模拟盘 - 切 TimescaleDB(hypertable + Continuous Aggregates) --- ## 技术栈 | 层 | 选型 | |---|---| | 后端 | Python 3.12+ · FastAPI · Pydantic v2 · SQLAlchemy 2.0 (async) · uv(包管理) | | 回测引擎 | 自研单一引擎(fast/strict 两档),指标纯 numpy/pandas(生产可换 TA-Lib) | | 数据库 | MVP 用 SQLite 零配置;生产切 PostgreSQL 16/17 + TimescaleDB | | 前端 | Vue 3.5 · Vite · TypeScript · Pinia · PrimeVue 5 · pnpm | | 图表 | lightweight-charts 5(K 线)· ECharts 6(净值) | --- ## 目录结构 ``` stock/ ├── TECH_STACK.md # 技术选型与架构决策(必读) ├── README.md # 本文件 ├── backend/ # FastAPI + 回测引擎 │ ├── pyproject.toml # uv 依赖声明 │ ├── .env.example # 配置示例(数据库 / 费率) │ ├── smoke_test.py # 后端全链路自检脚本 │ └── app/ │ ├── main.py # FastAPI 入口(启动建表) │ ├── config.py # 配置(pydantic-settings) │ ├── db.py # async SQLAlchemy 引擎/会话 │ ├── domain.py # 领域契约(Bar/Signal/Fill/Position…) │ ├── models.py # ORM(Candle / BacktestRun) │ ├── schemas.py # Pydantic DTO(= OpenAPI 契约) │ ├── commission.py # A 股交易成本(已修正、可配置) │ ├── indicators.py # 指标:MACD/RSI/KDJ/布林/均线(单一事实源) │ ├── api.py # 路由:/health /candles /backtest │ ├── data/ # DataProvider 适配器 + 合成数据 + 周期聚合 │ └── backtest/ # engine / broker(PaperBroker) / metrics / strategies └── frontend/ # Vue SPA ├── src/ │ ├── main.ts # PrimeVue(Aura 深色) + Pinia │ ├── api/ # 类型化客户端 + DTO 镜像 │ ├── stores/ # Pinia 回测状态 │ ├── components/ # KLineChart / EquityChart / MetricsPanel / BacktestForm │ └── views/ # BacktestView └── 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 | 生产数据库;MVP 用 SQLite 无需安装 | **安装 uv**(若未装): ```bash pip install uv ``` --- ## 快速开始(开发模式) 需要两个终端,分别跑后端与前端。 ### 1) 后端 ```bash cd backend uv sync # 创建 .venv 并安装依赖(首次) uv run uvicorn app.main:app --reload --port 8000 ``` - 首次启动自动建表(SQLite:`backend/stock.db`)并播种约 500 个交易日的合成 K 线(symbol=`DEMO`)。 - 交互式 API 文档:http://localhost:8000/docs **自检**(无需起服务器,验证全链路): ```bash uv run --with httpx --directory backend python smoke_test.py ``` ### 2) 前端 ```bash cd frontend pnpm install # 首次 pnpm dev # http://localhost:5173 ``` 前端 `/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;免费版即可。留空则仅 DEMO 合成数据可用) 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` | 健康检查 | | GET | `/api/candles/{symbol}?timeframe=1d&limit=1000` | 取 K 线(`timeframe`: `1d`/`1w`/`1M`/`1y`) | | POST | `/api/data/sync` | 拉取并缓存某标的日线(Tushare 主 → AKShare 兜底)。body: `{symbol, source?, force?}` | | POST | `/api/backtest` | 跑回测(真实标的首次自动拉取并缓存) | **回测请求示例**: ```json POST /api/backtest { "symbol": "DEMO", "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`。 > **关于「标的」**:`DEMO` 为内置合成数据;其余为真实 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 # 或 gunicorn(Linux):uv run gunicorn app.main:app -w 4 -k uvicorn.workers.UvicornWorker ``` 生产务必设置 `DATABASE_URL` 指向 PostgreSQL,不要用 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 免编译。 --- ## 路线图 详见 [TECH_STACK.md](./TECH_STACK.md)「分阶段落地路线」。下一步优先级: 1. 接入 Tushare 真实数据 2. 更多指标/策略(RSI/KDJ/布林策略) 3. 防过拟合体检 + 基准归因 4. 切 TimescaleDB、回测结果缓存与对比