股市回测平台
专业的 历史回测 + 回放式模拟 平台(A 股为主,不做实盘)。 输入技术指标参数(MACD/RSI/KDJ…)→ 后端回测 → 前端可视化 K 线、指标叠加、买卖点、净值曲线与绩效。
技术选型与架构决策详见 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) | 后端包管理,安装 |
| (可选)PostgreSQL | 16/17 | 生产数据库;MVP 用 SQLite 无需安装 |
安装 uv(若未装):
pip install uv
快速开始(开发模式)
需要两个终端,分别跑后端与前端。
1) 后端
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
自检(无需起服务器,验证全链路):
uv run --with httpx --directory backend python smoke_test.py
2) 前端
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)或环境变量:
# 数据库(开发、测试、生产统一使用 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 管理。首次部署或更新代码后执行:
cd backend
uv run alembic upgrade head
系统不提供注册接口。使用服务器交互式命令创建唯一用户;再次执行会重置密码并吊销该用户的所有旧会话:
uv run python -m app.cli.create_user --username admin
密码使用 Argon2id 保存。浏览器只接收 HttpOnly 会话 Cookie,数据库只保存随机会话 Token 的 SHA-256 摘要。默认会话有效期 12 小时,连续 5 次登录失败后锁定 15 分钟。
生产环境必须启用 HTTPS,并设置:
AUTH_COOKIE_SECURE=true
CORS_ORIGINS=https://你的域名
EXPOSE_API_DOCS=false
切到 PostgreSQL + TimescaleDB
- 目标库执行
CREATE EXTENSION IF NOT EXISTS timescaledb; DATABASE_URL=postgresql+asyncpg://...- (TODO)把
candles表升级为 hypertable 并配置 Continuous Aggregates 多周期预聚合(schema 已兼容,无需改表结构):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 |
跑回测(真实标的首次自动拉取并缓存) |
回测请求示例:
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 万,足够交易高价股(如茅台)。
生产构建与部署
前端构建
cd frontend
pnpm build # 产物在 frontend/dist
pnpm preview # 本地预览构建产物
部署时把 frontend/dist 用任意静态服务器(nginx / caddy / 对象存储)托管,并把 /api 反向代理到后端。
后端生产运行
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 后使用)
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「分阶段落地路线」。下一步优先级:
- 接入 Tushare 真实数据
- 更多指标/策略(RSI/KDJ/布林策略)
- 防过拟合体检 + 基准归因
- 切 TimescaleDB、回测结果缓存与对比