dsh-quant-assistant
Verifieddsh-quant-assistant · v1.0.1 · MIT · Web UI
把本地 DSH 一键转化为 A 股量化助手:因子检索、策略构建、可交易级回测、选股、仓位与止损、盘后自动增量同步,全部本地、零额外运行时。
Install
dsh plugin add dsh-quant-assistant Confirm the layer applied with dsh --profile default --dump-config — see the install guide.
Source
Published to npm without a public repository. Inspect the package contents before installing.
Tags
Creators
Readme
dsh-quant-assistant
把本地 DSH 一键变成 A 股量化助手:因子检索 → 策略组合 → 可交易级回测 → 选股 → 仓位。
全部本地运行,零原生依赖(纯 Node ≥ 22 + node:sqlite),不联网也能用已同步的数据。
免责声明:本工具只做历史数据的统计分析与回测,不提供投资建议、不预测股价、不推荐个股。 回测结果是对历史的统计描述,不构成对未来收益的任何承诺。
安装
dsh plugin --profile web add dsh-quant-assistant
装完重启 DSH,然后:
- 工具自动就绪(31 个
quant_*) - 切到「量化助手」preset,获得量化工作流与四个技能
- 第一次使用时先同步数据(见下)
快速开始
在会话里直接说人话即可:
先同步沪深 300 成分股的近 5 年行情
用 20 日动量做个策略,每月调仓,持有 20 只,跑一下 2019 年至今的回测
把这次回测导出成报告
助手会按这个流程走:
quant_data_status → 看数据有没有、新不新
quant_factor_search → 把"20 日动量"落到具体公式并念给你确认
quant_strategy_compose → 组合并校验,返回口径提醒
quant_strategy_save → 确认后保存(新增版本,不覆盖)
quant_backtest_run → 物化面板 + 逐日模拟
quant_backtest_analyze → 分年度收益、回撤区间、费用占比
quant_export + present → 落成 Markdown/CSV 并交给你
保持数据新鲜
数据不会自己变新。两种方式:
手动:说一句"同步一下最新行情"。
自动(推荐):说「开启每天收盘后自动同步数据」。助手会先向你说明代价, 你同意后才会开启:
- 每天在指定时刻(默认 18:30)把本地已有的股票增量补到当天
- 只同步本地已有的股票,不会自动扩大到全市场;单次有硬上限(默认 3000 只)
- 开启后不再逐次询问 —— 所以开启是一次长期授权,助手不会替你决定
- DSH 没在运行时不会执行,但下次启动会补跑被错过的那一次
- 每天真正新增的只有 1 个交易日,所以走的是按股票各自算起点的增量, 不是每天重拉全市场
- 非交易日会先探 3 只确认"今天有没有新数据",没有就一次请求都不发
- 想关掉随时说「关闭自动同步」
开启后可以问「自动同步什么状态」,会看到开关、下次执行时间、上次结果与授权记录。
新鲜度按工作日算
quant_data_status 的 stale 用工作日判定(跳过周末)——
否则每个周一早上都会报"过期 3 天",那是噪声不是信号。
已知近似:不识别节假日(不维护交易所日历),长假仍会算成若干工作日。
界面
装好后不需要额外操作,六类结果会自动渲染成卡片(都在会话流里,不占额外面板):
| 卡片 | 内容 |
|---|---|
| 回测结果 | 手写 SVG 净值曲线 + 总收益/年化/夏普/回撤/波动/Calmar/胜率/换手 + 基准对比 + 可折叠的口径声明 |
| 数据状态 | 覆盖范围、新鲜度(工作日/自然日)、自动同步状态、面板落后提示、可折叠的面板清单 |
| 数据同步 | 耗时、入库根数、离线事件表生效条数、失败明细;需确认时显示规模与耗时 |
| 选股结果 | 候选名单 + 拒绝原因计数(ST/停牌/次新)+ 免责声明 |
| 自动同步 | 开关、时刻、下次执行、上次结果、授权记录;当前进程没有定时器时会如实说明 |
| 导出交付物 | 文件清单 + 字节数 + 落点(会话工作目录 / 数据目录) |
外加一个设置页(侧边栏设置 → 量化助手):数据路径、低内存选项、保持数据新鲜、 导出交付物、查看/清理指引、免责声明、已知限制。
卡片是工具结果的纯函数:数据经 presentationMeta 随工具结果一起送达,
不订阅、不轮询、不需要额外的服务。所有卡片按 320px 宽度设计,窄屏不溢出。
导出交付物
说「把这次回测导出成报告」,助手会调用 quant_export:
| kind | 要什么 | 产出 |
|---|---|---|
backtest |
backtestId |
Markdown 报告(指标 + 费用 + 分因子 + 口径声明 + 完整净值表),format:"both" 另给净值 CSV |
select |
与 quant_select_stocks 同参数 |
候选名单(带拒绝原因与"不是推荐"声明) |
holdings |
可选 cash |
持仓估值 |
panel |
factorId |
因子面板明细 CSV(带股票名) |
data_status |
— | 数据仓库状态 |
- 默认落在会话工作目录的
quant-exports/下 —— 你能在项目里找到,也能直接打开 - CSV 带 UTF-8 BOM,Excel 打开中文列名不乱码
- 报告自带口径声明与免责声明
- 导出后助手会调用
present声明为交付物(只把路径写在回复里不算交付)
为什么要落盘:长会话里工具结果会在 8KB 处被压缩裁剪,回测的完整净值曲线随时可能 从上下文里消失。要引用的结论先落盘,是唯一可靠的做法。
31 个工具
| 域 | 工具 |
|---|---|
| 因子(4) | quant_factor_list quant_factor_search quant_factor_preview quant_factor_create |
| 策略(6) | quant_strategy_compose quant_strategy_save quant_strategy_load quant_strategy_list quant_strategy_search quant_strategy_versions |
| 回测(3) | quant_backtest_run quant_backtest_analyze quant_backtest_get |
| 选股(1) | quant_select_stocks |
| 优化(1) | quant_optimize_parameters |
| 仓位(3) | quant_save_holdings quant_portfolio_status quant_save_position_rules |
| 日历(2) | quant_trading_calendar quant_resolve_date |
| 行情财务(6) | quant_quote quant_kline_history quant_financial_summary quant_quarterly_financials quant_industry_stocks quant_index_constituents |
| 仓库(3) | quant_data_status quant_data_sync quant_data_cleanup |
| 自动同步(1) | quant_autosync |
| 导出(1) | quant_export |
回测口径
这是本项目最花功夫的部分 —— 能算得出来的数字很多,算得对的不多。
- T 日收盘出信号、T+1 开盘成交
- 涨停不买、跌停不卖、停牌不交易,判定用不复权原始价(用复权价会算错涨跌停幅度)
- 涨跌停幅度分板块:主板 10%、创业板/科创板 20%、北交所 30%、ST 5%
- T+1 制度(当日买入不可卖出)
- 100 股整手(部分卖出按整手取整,清仓可卖零股)
- 佣金(最低 5 元)/ 印花税(仅卖出)/ 过户费 / 滑点
- 停牌持仓按最后已知收盘价估值(按 0 计会产生假跳变)
- 因子与收益算在复权价上,否则动量类因子会在除权日带上分红跳空
结果里一定会带口径声明
回测结果包含 caveats 字段,逐条说明这次计算建立在什么假设上:
- ST 状态使用当前快照,非历史逐日状态
- 上市天数用自然日近似(未按交易日历计数)
- 日频无法表达盘中路径;止损类规则使用显式假设
- 有 N 个持仓日在停牌中,按最后已知收盘价估值
- 基准是区间内等权买入持有代理(v1 未接入指数行情)
这些不是免责套话,是理解数字的前提。助手会主动转述它们。
数据来源
| 数据 | 来源 |
|---|---|
| 日线行情 | 东财 push2his 为主,新浪兜底(已与 BaoStock 独立源逐字节比对) |
| 复权事件 | 东财分红 + 腾讯事件元数据 + 随包发布的离线修正表 |
| 基本面 | 东财数据中心(报告期快照 + 公告日生效) |
已知覆盖边界(八条)
- 退市股的复权事件不全
- 同日多笔分配可能被来源合并(实测 300750 在 2024-04-30:东财记 2.011,实际 5.01)
- 存在所有源都漏记的分配(实测 300750 在 2025-01-24)
- 个别标的原始价序列存在一日错位风险(实测 601899 在 2018-06-25/26)
- 停牌与涨跌停按日线判定,无法表达盘中路径
- 基本面不做财报重述回溯
- 北交所代码的市场归属待复核
完整版见 DATA-SOURCES.md。
审批护栏
高风险操作会先被拒绝,助手必须向你说明规模与耗时、取得同意后才能继续:
| 触发条件 | 工具 |
|---|---|
| 全市场同步(> 200 只或未指定列表) | quant_data_sync |
| 回测区间 > 5 年 | quant_backtest_run |
| 参数组合 > 100 组 | quant_optimize_parameters |
| 删除全部行情 | quant_data_cleanup |
| 开启自动同步(长期授权) | quant_autosync {action:"enable"} |
护栏是单调否决:每次按当次参数判断,不会"记住上次批准"。 用户同意的是"这次全市场同步",不是"以后任何全市场同步"。
唯一的例外是"开启自动同步":它拦的不是"这一次很重",而是"以后每天都会发生、 而且没人再问"。自动任务没有弹窗通道,所以同意必须在配置时取得 —— 护栏就是那道门。
数据存放与清理
数据全部在 $DSH_HOME/quant/(可用 DSH_QUANT_DATA_DIR 或插件行的 dataDir 覆盖):
$DSH_HOME/quant/quant.db 仓库(行情 / 复权事件 / 面板 / 策略 / 回测留档 / 自动同步状态)
- 不上传任何内容。网络只用于同步,不用于计算。
- 内存受限的机器可以设
DSH_QUANT_TEMP_STORE=file: 全市场重因子的峰值 RSS 从约 1.6GB 降到约 0.42GB,代价是耗时 +18% (需要可写的临时目录,插件会自己在仓库同级建.sqlite-tmp/)。
卸载
# ① 卸载前先删掉本包投递的 preset(DSH 正在运行时做,工具可用)
# 也可以在会话里说「清理量化助手的 preset」
quant_data_cleanup {scope: "preset"}
# ② 移除插件
dsh plugin --profile web remove dsh-quant-assistant
| 会被清掉 | 被保留 |
|---|---|
| 31 个工具与护栏 | 数据($DSH_HOME/quant/)—— 卸载不删用户数据 |
| preset 目录(若你先做了 ①) | 自动同步的授权记录(撤销授权不该由清理数据代劳) |
为什么 preset 要手动(卸载前)清理 —— 平台限制,如实说明
dsh plugin remove 不启动 DSH,所以插件的清理代码在卸载时根本没有机会执行。
DSH 也不把 package.json 的变化纳入 HMR 观察范围(实测改了它也不重载成分)。
原先实现的"卸载时清理"其实是靠 任何 unload 时都触发 effect —— 包括 DSH 正常关闭。
于是出现一个更糟的 bug:向 dsh web 发 SIGTERM,preset 就被删了(下个启动会重新物化,自愈,但语义错了)。
现在的行为是区分这两件事:
| 动作 | preset |
|---|---|
| DSH 正常关闭(SIGTERM / 退出) | 保留 |
quant_data_cleanup {scope:"preset"} |
删除 |
dsh plugin remove |
删除前如果你没做①,会剩下一个目录 —— 见下面 |
卸载后若 preset 目录还在,手动删掉即可(它引用的是 DSH 自带的包,不会让 DSH 启不来,
但技能会引用已不存在的 quant_* 工具,会让人困惑):
rm -rf "$DSH_HOME/.agent-presets/quant-assistant"
只别把它当成卸载没做干净——这是平台的钩子缺失,本插件补不上这一层,README 只能告诉你怎么办。 它只删元数据标记为本包的那一个目录,绝不碰别人的 preset。
技能
preset 自带四个技能,助手会按需加载:
| 技能 | 内容 |
|---|---|
quant-sources |
数据来源、八条覆盖边界、复权口径、没有专用工具时如何用 web_fetch 兜底 |
quant-workflow |
标准流程,以及"数据过期/自动同步""用户拒绝保存""护栏拦截""结果导出"四条分支 |
quant-discipline |
合规边界与结果表述规范(怎么把数字讲对) |
quant-session |
会话机制:goal(长任务的判据与完成纪律)、subagent_fork(样本外独立复核,必须在看到答案前分叉)、压缩的代价与"先落盘再依赖" |
已知限制(v1 有意不做)
- 贝叶斯优化、IC/IR 与分层回测等研究套件
- 新闻 / 研报 / 资金流的专用抓取工具(改用
quant-sources技能 +web_fetch) - 指数行情:回测基准目前是等权买入持有代理,结果里会写明
- PTC 批量编排(
DSH_TOOLS_MODE是进程级的,第三方插件不应改动它) - 仓位与止损规则是声明性配置,不会自动下单,也不会在回测里隐式生效
- 自动同步只跳过周末,不识别节假日(不维护交易所日历); 非交易日会空跑一次,但已被探测压到 3 次请求
- 自动同步需要 DSH 在那个时刻正在运行;关机/休眠不会执行,只能靠下次启动补跑
- headless profile(
patchReload: startup)不会自动挂载timer服务, 此时定时不会触发;手动补跑与状态读写照常可用,插件会如实说明而不是假装会跑
开发
npm test # 122 个单测
node spike/dsl-smoke.mjs # 109 个内置因子全部可编译
node spike/verify-factors.mjs # 因子值 vs qlib/pandas 真值(106,263 点)
node spike/tools-smoke.mjs # 31 个工具逐个真调用
node spike/plugin-apply-smoke.mjs # 安装副作用与卸载清理
node spike/plugin-real-ctx-smoke.mjs # 真实 Cordis 上下文装载(不启动 DSH 进程)
node spike/p2-smoke.mjs # 回测端到端 + 不变式
spike/out/browser/verify-cards.mjs 是真实浏览器验证(隔离实例 + puppeteer + 真 React +
真主题 CSS),28 条断言,失败非 0 退出。它抓到过 4 个只有真浏览器/看图才能发现的缺陷:
chip('已开启', 'down')传字符串而非令牌 →color: down是非法 CSS,静默回退成继承色- 同步卡把"规模估算"渲染成"同步中…"(
estimateOnly没有ms字段,卡片却拿它当"运行中") - 回测卡 4 个指标在 320px 下挤成 3+1(不溢出,所以溢出断言全绿 —— 断言盲区)
- 验证脚本自己的截图文件名在撒谎(按数组下标取,插了新用例后整体错位)
现场与复现步骤见 spike/out/browser/README.md,里面也记了一条教训:
不要把一次工具拒绝推断成自己的能力结论 —— read_image 当初被拒绝,
原因是模型路由配置没声明 input: [text, image],不是模型不能读图。
plugin-real-ctx-smoke.mjs 值得单独说一句:它在一个裸 Cordis Context + 真实
TimerService 里用 ctx.plugin() 把插件装载一遍(这正是 DSH loader 装载 bundle 行的路径),
所以它抓得到手写假 ctx 永远抓不到的东西。它上线就抓到一个装不上级别的缺陷:
Cordis 把插件
apply的返回值当成 effect 解释 —— 返回普通对象直接抛Invalid effect。
原来的 apply 返回诊断报告,于是插件在真实 DSH 里根本装不上。
现在拆成 apply(返回 void)+ setup(返回报告),并在 tests/plugin.test.mjs 里加了回归守卫。
架构与设计依据见 ARCHITECTURE.md(P5 的定时同步与导出见 §6.5 / §6.6),
阶段计划见 PLAN.md,与 AIFUND5/qlib 的同参数对照见 spike/GATE-FINDINGS.md。
许可
MIT