A 股每天有 5000+ 只股票需要被审视,每只股票要计算数十个技术指标,传统人工筛选在这种规模下完全失效。程序化扫描不是「能不能做」的问题,而是「怎么把数据管道、指标计算、信号扫描和结果呈现组织清楚」的工程问题。
本文基于 stock-analyzer 的真实源码,梳理它从数据获取到 AI 选股的整体架构与关键取舍。
💡 这套工具集最值得看的两点:
- 用自然语言直接问全市场 5000 只股票——系统自动把问题翻成 SQL,在真实行情数据上只读执行并出图作答(详见第七节);
- 51 指标 × 5000 股票的全市场扫描靠增量 ETL + 三库分离撑住,而非靠换计算内核。
一、项目定位:一个分析工具集,而不是量化平台
stock-analyzer 的边界很明确:它是一套面向 A 股日线数据的开源分析工具集,而不是一个面向实盘的量化交易平台。它包含几条相对独立的能力线:
- ETL 数据管道:从免费数据源摄取日线 K 线,计算 51 个技术指标;
- 信号扫描器:全市场技术信号检测,支持可配置的分数阈值;
- 自定义规则选股:基于 70+ 个技术与资产字段做 AND 组合筛选;
- 策略回测与优化:内置多种策略,含风险指标与参数扫描;
- AI 选股助手:用自然语言提问,系统对真实数据运行只读 Text2SQL 管线作答;
- Web 仪表盘:React + FastAPI,共 10 个 Tab。
把这几条线都收在同一个工程里,好处是数据只进一次、各模块复用同一套分析库;代价是工程边界必须划清,否则指标计算、扫描逻辑和 Web 服务会很快耦合成一团。
二、整体架构:按职责分层的 src/
真实的项目结构(节选自仓库根 src/)是按职责分层的,而不是一个大脚本:
src/
├── agent/ # AI 选股(只读 Text2SQL):llm / sqlsafety / schema / pipeline / portfolio / fallback
├── data/ # 数据获取与清洗:fetcher(开源行情数据源) / stock_info / asset / turnover / sync_env
├── etl/ # ETL 数据管道:摄取日线 K 线 → 计算 51 指标
├── scanner/ # 信号扫描:signals / screener / monitor / accuracy / risk_alert / visualization
├── scorer/ # 评分排序:ranking
├── strategy/ # 策略回测/优化/行业轮动/组合/风控/择时
├── web/ # FastAPI 服务 + 模拟交易:api / api_cache / paper_trading
├── utils/ # 通用工具
└── main.py # CLI 入口
数据层由三个 SQLite 库组成(均位于 data/ 目录):
| 数据库 | 角色 |
|---|---|
stock_klines.db |
原始日线 K 线(源库),由 data/fetcher.py 从开源行情数据源获取 |
stock_analysis.db |
分析库,约 700 万行、51 个指标,由 make etl 从源库生成 |
asset_snapshot.db |
全市场市值 / 股本 / 估值快照,支撑基于资产的筛选 |
源库与分析库分离是这个设计里很重要的一点:原始行情和衍生指标解耦,清洗逻辑或指标口径变化时可以安全地重算分析库,而不必回头改源数据。
分层的工程价值体现在三处:
- 指标与数据解耦:指标计算只读 DataFrame,数据来源(本地库还是在线接口)由
data/统一管理,换源不碰指标代码。 - 扫描逻辑独立:信号扫描器通过统一接口消费指标结果,新增或替换指标不需要改扫描器。
- 可测试性:ETL、扫描、策略各自可独立单测。
三、指标体系:51 个指标的计算与存储
51 个技术指标的计算集中在 src/etl/pipeline.py,用 pandas 实现(例如 _calculate_macd / _calculate_rsi / _calculate_boll / _calculate_kdj / _calculate_obv 等),并不是某个独立的「指标库」模块。指标覆盖趋势、动量、波动、量价等维度,典型实现包括 MACD、RSI、KDJ、BOLL、OBV 等。
全市场重算的代价很高,因此 ETL 采用真正的增量窗口:只重算近期数据,写入量比全量重算减少约 6 倍。计算产物落进 stock_analysis.db,单库约 700 万行,供扫描、回测和 Web 层统一读取。
说明:指标的「分类数量」在不同的划分口径下会有出入(同一指标可能同时服务于趋势与动量判断)。本文不强行给出一份互斥的四大类计数,以免数字失真;工程上更关键的是指标可配置、结果可复用。
四、信号扫描:从指标到交易信号
信号扫描在 src/scanner/signals.py 中以 SignalType 枚举显式定义,真实存在的信号类型包括:
| 类别 | 信号示例 |
|---|---|
| 金叉 / 死叉 | MACD 金叉/死叉、KDJ 金叉/死叉、MA5 上穿/下穿 MA20 |
| 超买 / 超卖 | RSI 超买/超卖、Williams %R 超买/超卖、CCI 超买/超卖 |
| 突破 | 突破布林上轨、跌破布林下轨、价格突破 |
| 量价 | OBV 背离、成交量异动 |
| 趋势 | 上升趋势、下降趋势、MA 排列、动量 |
| K 线形态 | 锤子线、倒锤线、看涨/看跌吞没、启明星/黄昏星 |
扫描器支持可配置的分数阈值:每只命中股票给出信号类型与评分,可按分数过滤和排序。这与「自定义规则选股」形成互补——规则引擎负责「明确的条件组合」(如市值 + 市盈率 + 量比),评分扫描负责「指标触发的复合信号」。
五、自定义规则选股:70+ 字段的 AND 组合
除了指标信号,系统还提供基于资产字段的筛选:覆盖 70+ 个技术与资产字段(市值、流通市值、市盈率、市净率、量比等),支持多条件 AND 组合对全市场做筛选。这条线解决的是「我知道要什么硬性条件,但全市场手动翻不动」的问题,和第四节的评分信号是两套不同思路。
六、策略回测与优化
src/strategy/ 是策略相关能力的集合:backtest.py 提供内置策略回测,optimization.py 与 scripts/param_sweep.py 做参数扫描,portfolio.py / risk_control.py / benchmark.py 负责组合与风控,market_timing.py / sector_rotation.py 做大盘择时与行业轮动。
回测模块可以统计信号/策略的胜率、盈亏比与各指标贡献度,并以报告形式输出。需要强调的是:回测结果用于方法验证,不代表未来收益;信号有效性高度依赖市场环境,这也是系统把「择时」「行业轮动」单独做成模块的原因。
七、AI 选股:只读 Text2SQL 智能体
想象一下:直接问「最近 20 天 MACD 金叉、且 RSI 低于 40 的股票有哪些」,系统自己把这句话翻成 SQL,在真实行情数据上只读执行、返回结果并出图——你不用写一行 SQL。
src/agent/ 实现的就是这套能力:一个只读的 Text2SQL 管线(pipeline.py):自然语言问题 → 生成 SQL → 只读校验 → 执行 → 基于结果作答/出图,失败时自纠错(生成与执行最多各重试 3 轮)。它复刻了 work/harness/datapulse 的方法——SQL 生成与最终作答各自有独立的重试预算。关于这套 Text2SQL 智能体的设计细节与取舍,可延伸阅读 datapulse 专文。
安全边界由三层保证(见 agent/sqlsafety.py 与 README):
- 数据库只读:分析库以
mode=ro打开; - SQL 白名单校验:拦截写操作、危险函数、多条语句、过大的
LIMIT; - 结果上限:单次返回最多 200 行。
也就是说,模型即使出错,也只能在「只读、受限、小结果集」的沙箱里出错,不会触碰源数据或执行写操作。未配置 LLM 凭据时,其余 9 个 Tab 仍可正常使用(fallback 模块兜底)。
模型接入走 OpenAI 兼容的大模型 API(通过环境变量配置 LLM_BASE_URL / LLM_API_KEY / LLM_MODEL),运行时不绑定任何特定厂商。公开托管的演示环境(https://stock-analyzer-demo.onrender.com)与本地正式库完全隔离——演示库使用一组占位股票(DemoXX)模拟行情,不承载任何真实持仓数据。
八、技术栈与工程取舍
| 维度 | 选型 |
|---|---|
| 运行时 | Python 3.11+,用 uv 管理依赖 |
| 指标计算 | pandas + numpy(etl/pipeline.py 内联实现) |
| Web 后端 | FastAPI(本地 :8001) |
| Web 前端 | React + Vite + TypeScript(本地 :3000),图表用 ECharts,红涨绿跌配色 |
| 存储 | SQLite(源库 / 分析库 / 资产快照三库分离) |
关于「为什么是 pandas 而不是 polars / Rust」:这个项目选择 pandas,核心是指标实现与迭代速度的取舍。51 个指标的计算逻辑分散在各 _calculate_* 方法里,pandas 的生态(TA-Lib、numpy)和可读性让这些逻辑容易写、容易测;全市场扫描的时效则通过增量 ETL + SQLite 缓存来满足,而不是靠换计算内核。对一套以「研究 + 可维护」为目标的工具集来说,这套组合比纯 Rust 更务实。
九、数据质量与边界
数据质量的上限决定了指标的可信度。系统把「获取」和「分析」分到两个库:开源行情数据源返回的日线先落 stock_klines.db,再由 ETL 清洗、复权处理后生成 stock_analysis.db。双库分离让清洗口径的变更可以安全重放,也避免源数据被衍生计算污染。
资产维度的快照(asset_snapshot.db)单独存储,使得「按市值/估值筛选」这类需求不依赖实时拉取,扫描可以稳定可重复地运行。
十、现状与后续方向
当前已具备的能力:ETL + 51 指标、信号扫描(可配置阈值)、自定义规则选股(70+ 字段)、资产快照、策略回测与优化、市场分析(择时 / 广度 / 行业轮动)、模拟交易、AI 选股助手、Web 仪表盘(10 个 Tab)。
后续可继续打磨的方向:
- 市场环境识别:自动判断牛 / 熊 / 震荡,调整信号阈值;
- 信号衰减模型:不同信号有效性随时间衰减,引入衰减因子;
- 板块联动分析:引入板块维度,过滤板块弱势股;
- 实时化改造:从日级扫描走向盘中监控。
源码导航
src/etl/pipeline.py— ETL 管道与 51 个指标计算(pandas)src/scanner/signals.py— 信号类型定义与扫描逻辑src/strategy/backtest.py— 回测模块src/agent/pipeline.py— 只读 Text2SQL AI 选股管线src/web/api.py— FastAPI 服务入口src/data/fetcher.py— 行情数据获取
51 个技术指标是否过多,会导致信号相互矛盾?
指标覆盖趋势、动量、波动、量价等维度,按信号类型分组。系统支持可配置的分数阈值,可按需过滤弱信号,工程上更关注「指标可配置、结果可复用」,而非强行互斥计数。
全市场扫描的性能瓶颈在哪里,如何优化?
瓶颈在指标计算的重复写入。ETL 采用真正的增量窗口,只重算近期数据,写入量比全量重算减少约 6 倍;计算产物落 SQLite 分析库,供扫描、回测与 Web 层复用,避免重复计算。
AI 选股安全吗,会不会改到我的数据?
管线是只读的:分析库以 mode=ro 打开,SQL 经过只读白名单校验(拦截写操作、危险函数、多语句、过大 LIMIT),结果最多返回 200 行。模型即使出错也只能在受限沙箱内出错,不会触碰源数据。
信号和回测结果的有效性如何?
内置回测模块可统计胜率、盈亏比与各指标贡献度用于方法验证,但结果不代表未来收益,且高度依赖市场环境。所有信号与 AI 回答仅供参考,不构成投资建议。
数据从哪里来,需要付费或 API Key 吗?
行情来自开源行情数据源,免费、无需 API Key。原始日线落源库,再由 ETL 生成分析库,源库与分析库分离。
未来系统的优化方向是什么?
主要包括市场环境识别、信号衰减模型、板块联动分析和实时化改造,提升信号的适用性与时效性。
项目地址
- GitHub 仓库:https://github.com/erishen/stock-analyzer
- 在线 Demo(Render):https://stock-analyzer-demo.onrender.com
⚠️ 免责声明:本项目仅供技术学习和研究使用,不构成任何投资建议。所有技术指标、信号和 AI 回答仅供参考,不保证准确性;股市有风险,请勿将本工具用于实际投资决策。
发表回复