JOURNAL / AI

个人回测平台的搭建指南(二)

个人回测平台的搭建指南(二) 封面
本文目录
  1. 搭建自己的 K 线回测平台:从数据源、K 线组件到策略信号
    1. 一、先确定回测平台第一阶段要做什么
    2. 二、K 线图组件:KLineCharts
    3. 三、数据源选择:AData + Tushare
      1. 1. AData
      2. 2. Tushare
    4. 四、为什么第一阶段只做日 K
    5. 五、回测平台的核心模块
      1. 1. 标的管理
      2. 2. 日 K 数据管理
      3. 3. 技术指标计算
      4. 4. 技术评分
      5. 5. 技术信号
      6. 6. 模拟回测
    6. 六、一个完整回测链路长什么样
    7. 七、为什么还要接入 LLM 系统
    8. 八、为什么把 LLM 系统作为外部分析服务
    9. 九、K 线系统和 LLM 系统的数据流
    10. 十、为什么不直接使用 LLM 系统原始接口
    11. 十一、在回测系统中封装 LlmAnalysisClient
    12. 十二、缓存规则:同一天同一标的不重复调用
    13. 十三、失败降级:LLM 挂了也不能影响 K 线系统
    14. 十四、最终决策由 K 线系统生成
    15. 十五、最终集成架构
    16. 十六、当前阶段的成果
    17. 十七、总结

搭建自己的 K 线回测平台:从数据源、K 线组件到策略信号

上一篇文章里,我们理清了整体的架构思路,和我们需要完成的模块设计,这一篇我们将着重分析二者系统之间的数据交互方案。

LLM 系统很适合做投研、新闻分析、市场情绪判断和风险解释,但如果我要真正测试一个交易策略,第一步还是要回到最基础的问题:

  • 我的数据从哪里来?
  • 我的 K 线图怎么展示?
  • 我的技术指标怎么算?
  • 我的买入信号如何生成?
  • 我的卖出规则是什么?
  • 如果放到历史数据里执行,结果会怎么样?

所以这一篇文章主要进行说明如何搭建自己的第一版 K 线回测平台。

这个系统的目标不是一上来就实现自动交易,而是先跑通一条最基础的链路:

ETF 标的管理 → 日 K 数据获取 → K 线展示 → 技术指标计算 → 技术信号生成 → 模拟买入 / 卖出 → 回测结果统计。


一、先确定回测平台第一阶段要做什么

在第一阶段,我们不需要把系统做得太复杂。

因为对个人开发者,尤其是假如自己是金融新手来说,一开始最容易犯的错误就是:还没把一个简单策略测清楚,就开始堆很多复杂功能。

比如一上来就做:

  • 自动交易
  • 券商 API
  • 高频行情
  • 分钟级 K 线
  • 多策略组合
  • 多 Agent 决策
  • 持仓管理
  • 实盘资金同步

这些东西听起来很完整,但对第一阶段来说反而容易分散注意力。

所以我们的第一版回测平台只需要完成下面几个核心功能:

  1. 添加 ETF 标的
  2. 获取 / 导入日 K 数据
  3. 展示 K 线图
  4. 计算技术指标
  5. 生成技术评分和信号
  6. 用信号做模拟买入 / 卖出
  7. 查看持仓、交易明细、资金曲线、收益率统计

有了这条链路之后,就可以测试一些最基础的策略,比如:

  • MA 趋势策略
  • RSI 过滤策略
  • 成交量确认策略
  • 均线突破策略
  • 简单止损止盈策略

先把简单策略测清楚,再考虑复杂策略。


二、K 线图组件:KLineCharts

既然是 K 线回测系统,首先我们需要有一个 K 线图组件。

这里我选择的是 KLineCharts

GitHub 地址:

https://github.com/klinecharts/KLineChart

这是一个我很早之前就开始关注和使用的 K 线图组件,GitHub 上目前有着3.7K🌟。

我选择它的原因主要有几个:

  1. 它专门面向 K 线场景,而不是普通图表库硬凑出来的 K 线图。
  2. 支持蜡烛图、成交量、指标线等常见金融图表能力。
  3. 前端集成相对清晰,适合和 Vue 项目结合。
  4. 对个人回测系统来说,功能已经足够。

上面都是扯淡的,主要是免费。
在我们的系统里,KLineCharts 主要负责展示:

  • 日 K 蜡烛图
  • 成交量
  • MA5 / MA10 / MA20 / MA60
  • 买入信号点
  • 卖出信号点
  • 回测交易标记

也就是说,它不是单纯为了“好看”,而是为了让我能直观看到策略信号到底出现在什么位置。

比如系统给出一个 BUY_CANDIDATE,我希望在图上能看到:

  • 当时价格是不是在 MA20 上方?
  • MA5 和 MA10 是否形成短期向上趋势?
  • RSI 是否已经过热?
  • 成交量有没有配合?
  • 后续几天价格有没有继续上涨?
  • 是否很快触发止损?

这些都需要通过 K 线图来辅助判断。


三、数据源选择:AData + Tushare

有了图表组件之后,第二个问题就是数据源。

不管是股票还是 ETF,实时行情数据都不是一个特别容易免费获取的东西。尤其如果要稳定、长期、实时、分钟级数据,通常都会涉及付费或限制。

但我们当前阶段并不需要高频实时行情。

我的第一阶段目标是做日 K 回测,所以日 K 数据就够了。

因此我选择了两个数据源作为基础:

1. AData

GitHub 地址:

https://github.com/1nchaos/adata

AData 是一个面向 A 股数据获取的开源项目,可以用于获取一些常见的股票、指数、ETF 等数据。

对个人测试来说,它的优势是:

  • 使用成本低
  • 接入相对简单
  • 适合本地测试
  • 对 A 股数据比较友好

2. Tushare

官网地址:

https://tushare.pro/

Tushare 在国内量化数据圈里比较常见,很多人应该都知道。

它的优势是:

  • 数据类型比较丰富
  • 文档相对完善
  • 生态成熟
  • 适合做历史数据获取和策略研究

当然,Tushare 的部分能力会受到积分或权限影响,但如果只是个人学习、测试和搭建第一版系统,很多免费能力已经够用了。

所以在第一阶段,我们的数据源策略是:

先保证能稳定拿到日 K 数据,不追求一开始就拿到最完整、最实时、最高频的数据。


四、为什么第一阶段只做日 K

很多人一做交易系统,就想直接做分钟级 K 线,甚至实时行情。

但我现在不打算这么做。

原因很简单:日 K 更适合第一阶段策略测试。

日 K 的好处是:

  • 数据量相对小
  • 计算简单
  • 回测速度快
  • 信号更稳定
  • 不容易陷入高频噪音
  • 更适合新手理解趋势

而分钟级数据会带来很多额外复杂度:

  • 数据量巨大
  • 清洗成本高
  • 缺失和异常更多
  • 回测速度变慢
  • 更容易受到噪音影响
  • 更接近短线交易,对新手并不友好

所以我们现在只做:

ETF 日 K 回测。

这个范围已经足够完成第一版系统验证。


五、回测平台的核心模块

我们的回测平台暂时命名为:

personal-kline-assistant

技术栈大致是:

后端:Spring Boot + MySQL
前端:Vue + KLineCharts
数据源:AData / Tushare / CSV 导入
后续 LLM:daily_stock_analysis FastAPI

第一阶段核心模块如下。

1. 标的管理

用于维护我们需要测试的 ETF 或股票。

比如:

510300  沪深300ETF
159915 创业板ETF
SPY 标普500ETF
QQQ 纳斯达克100ETF

不过第一阶段不建议标的太多。

建议倾向于先测试 1 到 2 个 ETF,把流程跑通之后再扩展。


2. 日 K 数据管理

日 K 数据包括:

  • 日期
  • 开盘价
  • 最高价
  • 最低价
  • 收盘价
  • 成交量
  • 成交额

系统需要支持两种方式:

  1. 从数据源拉取
  2. 从 CSV 导入

为什么还要保留 CSV 导入?

因为在开发和测试阶段,CSV 是最稳定、最容易调试的方式。
如果数据源接口挂了,或者字段变了,我仍然可以用 CSV 继续跑系统逻辑。


3. 技术指标计算

第一版指标不需要太多,我主要保留几个基础指标:

  • MA5
  • MA10
  • MA20
  • MA60
  • RSI14
  • ATR14
  • 成交量比率

这些指标足够支撑第一阶段的趋势策略测试。

比如:

close > MA20
MA5 > MA10
RSI 在 45 到 70 之间
成交量不低于 20 日均量

这些条件组合起来,就可以生成一个基础的 BUY_CANDIDATE 信号。


4. 技术评分

为了让系统有统一标准,我们需要设计一个技术评分。

比如:

  • 收盘价在 MA20 上方,加分
  • MA5 > MA10,加分
  • MA10 > MA20,加分
  • RSI 在合理区间,加分
  • 成交量确认,加分
  • 价格没有远离 MA20,加分

最终得到一个 0 到 100 的技术评分。

这个分数不是为了预测未来,而是为了让系统稳定判断:

当前这个标的是否满足我的技术观察条件。


5. 技术信号

根据技术评分和指标条件,系统生成几个信号:

信号 含义
BUY_CANDIDATE 可以进入买入候选
WATCH 可以观察,但条件还不够强
NEUTRAL 没有明显机会
AVOID 不适合买入
SELL_WARNING 如果已有持仓,需要警惕

这里我特别强调:

BUY_CANDIDATE 不是买入命令,只是买入候选。

它只是说明技术条件比较好,可以进入下一步观察。

真正是否模拟交易,还需要结合止损、止盈、仓位、市场环境和后续 LLM 风险过滤。


6. 模拟回测

有了信号之后,就可以做最简单的模拟回测。

比如规则可以是:

买入:
出现 BUY_CANDIDATE 后,下一个交易日开盘买入

卖出:
亏损 -2% 卖出
盈利 +4% 卖出
跌破 MA10 卖出
持有超过 5 个交易日卖出

回测系统需要输出:

  • 每笔交易记录
  • 买入日期
  • 卖出日期
  • 买入价格
  • 卖出价格
  • 单笔收益率
  • 总收益率
  • 胜率
  • 最大回撤
  • 权益曲线
  • 当前持仓

这些指标能帮助我判断:

这个信号到底有没有测试价值。


六、一个完整回测链路长什么样

从系统角度看,完整链路大概是:

添加 ETF 标的

获取 / 导入日 K 数据

计算 MA / RSI / ATR / 成交量指标

生成技术评分

生成 BUY_CANDIDATE / WATCH / AVOID 等信号

按照买卖规则做模拟回测

输出交易明细、资金曲线、收益率、回撤、胜率

这就是第一阶段回测系统的核心。

它不复杂,但很关键。

因为只有这条链路跑通之后,我才知道:

  • 数据是否可用
  • 指标是否正常
  • 信号是否合理
  • 回测是否可复现
  • 策略是否值得继续观察

七、为什么还要接入 LLM 系统

做到这里,其实已经有了一个完整的 K 线回测系统。

但它还有一个问题:

K 线只能告诉我价格发生了什么,不能告诉我为什么发生。

比如某个 ETF 出现了 BUY_CANDIDATE,技术上看起来不错。

但我还想知道:

  • 当前大盘情绪怎么样?
  • 有没有政策风险?
  • 有没有重大新闻?
  • 外围市场是否大跌?
  • 资金面是否支持?
  • 这次上涨是不是只是短期反弹?
  • 这个信号最可能失败在哪里?

这些问题就更适合交给 LLM 情绪分析系统。

所以我们后续会把 daily_stock_analysis 接进来。

不过它不是用来直接替我买卖,而是做风险过滤。


八、为什么把 LLM 系统作为外部分析服务

daily_stock_analysis 本身已经有:

  • FastAPI 服务
  • 分析接口
  • 任务状态接口
  • 历史报告接口
  • Agent 接口
  • 行情 / 回测 / 配置接口

所以它天然适合作为一个外部分析服务。

而我的回测系统是 Spring Boot,LLM 系统是 FastAPI,那么最自然的通信方式就是:

Spring Boot 通过 HTTP 调用 FastAPI。

这样做有几个好处:

  1. 两个系统保持独立。
  2. 不需要互相侵入源码。
  3. LLM 系统更新时影响较小。
  4. Java 系统只关心输入和输出。
  5. 调试方便,可以直接用 Postman / curl 测试。
  6. 以后换模型或换 LLM 项目,不影响 K 线系统核心逻辑。

这点对我很重要。

因为 daily_stock_analysis 是一个 GitHub 开源项目,我希望后续还能持续拉取原作者更新。
如果我把大量自己的逻辑写进它的核心代码,以后更新会很麻烦。

所以第一阶段我选择:

尽量不修改 LLM 项目核心代码,而是通过 HTTP API 进行通信。


九、K 线系统和 LLM 系统的数据流

整合后的数据流是:

  1. K 线系统生成 technical_signal
  2. 判断 signal_type 是否为 BUY_CANDIDATE
  3. 如果不是 BUY_CANDIDATE,不调用 LLM。
  4. 如果是 BUY_CANDIDATE,K 线系统调用 LLM 系统 API。
  5. LLM 系统返回:
    • sentimentScore
    • riskScore
    • riskLevel
    • summary
    • positiveFactors
    • negativeFactors
    • riskFactors
    • contrarianView
    • rawReport
  6. K 线系统保存到 ai_analysis_snapshot
  7. K 线系统根据规则生成 final_trade_decision
  8. 前端展示最终结果。

这条链路的重点是:

K 线系统负责产生候选,LLM 系统负责分析风险,最终决策仍然由 K 线系统生成。


十、为什么不直接使用 LLM 系统原始接口

虽然 daily_stock_analysis 有很多接口,但我不想让 K 线系统直接依赖它复杂的原始返回结构。

原因是:

  • 原始字段可能很多
  • 结构可能偏向 LLM 系统内部使用
  • 后续开源项目更新时字段可能变化
  • K 线系统并不需要所有信息

所以我们需要在 LLM 系统外面封装一个稳定的适配接口。

例如:

POST /api/quant/etf-risk-analysis

请求体可以设计成:

{
"symbolCode": "510300",
"assetName": "沪深300ETF",
"market": "CN",
"assetType": "ETF",
"trackingTarget": "沪深300指数",
"tradeDate": "2026-05-04",
"technicalSignal": "BUY_CANDIDATE",
"technicalScore": 82,
"closePrice": 4.82,
"ma20": 4.71,
"rsi14": 58.4,
"atr14": 0.08,
"analysisScope": [
"A股大盘",
"沪深300指数",
"权重板块",
"政策新闻",
"宏观经济",
"市场情绪",
"成交量变化",
"外围市场"
]
}

响应体可以设计成:

{
"symbolCode": "510300",
"analysisDate": "2026-05-04",
"sentimentScore": 61,
"riskScore": 42,
"riskLevel": "MEDIUM",
"marketState": "NEUTRAL",
"actionConstraint": "WATCH_OR_HALF_POSITION",
"summary": "A股市场情绪中性,沪深300短期企稳,但成交量仍需确认。",
"positiveFactors": [
"指数短期站上关键均线",
"部分权重板块修复"
],
"negativeFactors": [
"成交量未明显放大",
"上方存在压力区域"
],
"riskFactors": [
"外围市场波动可能影响风险偏好",
"政策预期尚未完全落地"
],
"contrarianView": "当前上涨可能只是短期技术反弹,还不能确认趋势反转。",
"rawReport": "完整 Markdown 报告内容"
}

这样我的回测系统只需要适配这个稳定 JSON 即可。


十一、在回测系统中封装 LlmAnalysisClient

接下来我们需要一个客户端来和llm系统进行交互,我们需要回测系统里封装一个客户端:

llm
├── client
│ └── LlmAnalysisClient
├── dto
│ ├── LlmRiskAnalysisRequest
│ └── LlmRiskAnalysisResponse
└── service
└── AiAnalysisService

这里的职责划分是:

LlmAnalysisClient 只负责 HTTP 调用 LLM 系统。

AiAnalysisService 负责判断是否需要调用、读取缓存、保存结果、生成最终决策。

也就是说,Controller 不直接调用 LLM。
业务逻辑统一放在 Service 层,HTTP 通信统一放在 Client 层。

这样以后如果 LLM 系统接口变了,只需要改 LlmAnalysisClient 和 DTO,不会影响整个回测系统。


十二、缓存规则:同一天同一标的不重复调用

LLM 调用有成本,也可能会比较慢。

所以我们还需要加一个缓存规则:

同一个 symbolCode + analysisDate 只分析一次。

也就是说,如果今天已经有:

510300 + 2026-05-04

的 AI 分析结果,就不需要重复调用 LLM。

直接从 ai_analysis_snapshot 表中读取即可。

流程如下:

  1. 检查 ai_analysis_snapshot 是否已有今日结果。
  2. 有则直接返回。
  3. 没有才调用 LLM API。
  4. 调用成功后保存。
  5. 调用失败则保存失败状态,并返回保守决策。

这可以避免频繁调用 LLM,也能让前端重复刷新时不会浪费资源。


十三、失败降级:LLM 挂了也不能影响 K 线系统

还有一个非常重要的问题:如果 LLM 系统挂了怎么办?

比如:

  • LLM 服务没启动
  • FastAPI 超时
  • 模型调用失败
  • 网络异常
  • 返回字段异常

这时候不能让整个 K 线系统崩掉。

所以要设计了一个保守降级规则:

如果 LLM 调用失败:
final_action = WATCH_ONLY
reason = 缺少 AI 风险分析,暂不允许交易

也就是说:

不因为 LLM 调用失败就默认允许交易。

这点很重要。

因为 LLM 在我的系统里是风险过滤器。
如果风险过滤器不可用,那么最保守的处理就是继续观察,而不是放行。


十四、最终决策由 K 线系统生成

整合之后,最终决策会保存在 final_trade_decision 中。

我不让 LLM 系统直接生成最终交易动作。

原因是:

  1. K 线系统掌握技术信号。
  2. K 线系统掌握策略规则。
  3. K 线系统掌握回测和模拟交易。
  4. LLM 系统只提供外部风险输入。

所以两个系统的边界是:

LLM 系统:
返回风险分析

K线系统:
合并 K线信号 + AI风险 + 风控规则
生成最终动作

这个边界越清晰,后续系统越容易维护。


十五、最终集成架构

最终得到的集成架构大概是:

personal-kline-assistant  Spring Boot + Vue + MySQL

├── technical_signal
├── backtest_result
├── ai_analysis_snapshot
├── final_trade_decision

└── LlmAnalysisClient
│ HTTP REST

daily_stock_analysis FastAPI + LLM 引擎

├── 新闻舆情
├── 大盘复盘
├── AI报告
├── Agent分析
└── 返回结构化风险结果

一句话总结:

用 HTTP API 保持系统边界,用 MySQL 保存分析结果,用 K 线系统做最终决策。

这就是我当前认为最适合第一阶段的整合方式。


十六、当前阶段的成果

到这里,我们其实已经完成了一个简单但完整的回测系统设计。

它具备:

  • ETF 标的管理
  • 日 K 数据获取 / 导入
  • K 线图展示
  • 技术指标计算
  • 技术评分
  • 技术信号生成
  • 模拟回测
  • 交易明细
  • 权益曲线
  • 收益率统计
  • 与 LLM 系统的数据通信设计
  • AI 风险分析结果缓存
  • 最终决策生成

这已经是一条比较完整的个人量化辅助链路。

当然,它还不是一个自动交易系统,也不是一个能保证盈利的系统。

它更像是一个交易测试实验台。

我可以用它不断测试:

  • 某个技术信号有没有价值
  • 某个 ETF 是否适合趋势策略
  • 止损止盈设置是否合理
  • LLM 风险过滤是否能减少错误信号
  • 回测结果和实时模拟是否一致

这比一开始直接问 AI “能不能买”要踏实得多。


十七、总结

这一篇主要记录了如何进行搭建和设计我们的第一个回测平台,并且是如何通过http交互来进行两个系统之间的数据调度。

整体思路是:

  1. 用 KLineCharts 做 K 线图展示。
  2. 用 AData / Tushare / CSV 解决日 K 数据来源。
  3. 用 Spring Boot + MySQL 管理数据和回测逻辑。
  4. 用 MA / RSI / ATR / 成交量生成基础技术信号。
  5. 用模拟买入 / 卖出验证策略效果。
  6. 用 HTTP API 接入 LLM 系统做风险过滤。
  7. 用 MySQL 保存 AI 分析结果和最终决策。

这一阶段最重要的不是“马上赚钱”,而是先把完整链路搭起来。

因为只有链路跑通之后,后面才能继续做:

  • 虚拟资金测试
  • ETF 策略验证
  • 交易计划模块
  • 仓位计算
  • 交易日志
  • K 线策略和 K 线 + LLM 策略的对比测试

最后展示一下当前平台的模样,和数据的获取方式是否达到了我们效果,我们来测试获取一个沪深300的ETF华泰柏瑞的数据来进行指标和信号的生成:

所有的技术指标和信号都获取正常,包括k线的数据。

下一篇,就是激动人心的时刻,我们会开始使用虚拟资金,对一个简单的 ETF 策略做模拟测试。

来测试:

如果系统给出 BUY_CANDIDATE,我按规则模拟买入,再按止损止盈规则卖出,结果到底会怎么样?

分享个人回测平台的搭建指南(二)

交流与讨论

评论由 Valine / LeanCloud 提供,点击后连接第三方服务。

搜索文章