npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

dsh-astock

v0.9.0

Published

A股 / 港股量化工作台:自选股、K线图、公司数据、策略配置与回测。 · A-share & Hong Kong stock quant workbench for DeepSeek Harness.

Readme

dsh-astock

A股港股量化工作台 —— DeepSeek Harness 插件。

当前版本 0.9.0 · npm · 版本记录 · 提交历史

在侧边栏底部提供独立的「A股港股」整页:A 股与港股的自选股管理、K线图、公司财务数据、策略配置与回测。

包名 dsh-astock 里的 a 是历史遗留(最初只做 A 股),安装命令与 id 都依赖它,因此保持不变;界面与文档里显示的是 A股港股。

⚠️ 免责声明

本插件仅用于技术学习与量化研究,不构成任何投资建议、要约或收益承诺。 不提供选股推荐与买卖时机提示,也不代客理财。任何据此做出的投资决策由使用者自行判断并承担全部后果。

  • 数据:行情、K线、财务数据均来自第三方公开接口,可能存在错误、延迟、缺失或因接口变更而中断,不对准确性、完整性、及时性作任何保证。港股数据来自腾讯且未经复权,除权除息日会出现价格跳空。
  • 回测:基于历史数据,不能代表未来表现。结果受策略过拟合、幸存者偏差、参数与区间选择影响;交易成本、滑点、涨跌停与流动性均为近似模型,与真实成交存在差异。历史收益不预示未来收益。
  • AI 生成:生成的策略代码仅供参考、未经审核,可能有逻辑错误或隐含风险,请自行阅读并验证后再使用。
  • 责任:使用者应自行核实数据与结果,并自行承担使用本插件所产生的任何直接或间接损失。插件作者不对任何投资损失承担责任。
  • 合规:请遵守所在地区法律法规及所使用数据源的服务条款,禁止用于内幕交易、市场操纵等违法用途。

界面里有两处对应措施:首次打开会要求确认完整条款(确认后落盘,条款有实质修改时会重新提示),底部常驻一条精简声明(即使确认弹窗因接口故障没能出现,这条也始终可见)。回测结果与 AI 生成处也各有对应提示。

功能

| 模块 | 内容 | | --- | --- | | 自选股 | 支持 A 股与港股(可混排):按代码 / 名称 / 拼音首字母搜索(600519、茅台、GZMT、00700),增删、切换;落盘保存 | | K线 | 手写 SVG 蜡烛图(阳线空心红 / 阴线实心绿)、MA5/10/20/60、成交量副图、十字光标逐根读数;日/周/月 × 前/后/不复权 × 1/3/5/10 年;60–500 根缩放 + 平移 | | 公司数据 | 实时快照(现价/涨跌/开高低/成交额/换手/PE/PB/总市值/流通市值/每手股数/币种)+ 16 期财务(EPS/BPS/ROE/毛利率,仅 A 股)+ 公司动态(说明会 / 财报预约披露 / 除权除息 / 自定义事件)+ AI 查动态并自动填入(可接自定义 HTTP 数据源) | | 策略 | 9 个参数化模板(双均线、MACD、RSI、布林带、唐奇安突破、均线多头、相对强弱 RS、大盘趋势+相对强弱、说明会前后)+ 表达式编辑器 + JavaScript 编辑器 + 和 AI 多轮对话改策略(可出表达式或 JS、可联网查证) | | 回测 | 按市场自动切换规则(A 股 T+1/涨跌停/100 股一手;港股 T+0/无涨跌停/每手逐股不同/印花税双边);佣金(5元起)/印花税/其他费率/滑点;总收益/年化/最大回撤/夏普/卡玛/胜率/盈亏比;资金曲线 + 沪深300 对比;支持按日期区间回测与年份快捷档位 | | 交易明细 | 点击买入日期向下展开决策证据:逐条条件的 ✓/✗ 与两侧实际数值、实际触发项加重显示、交叉给出前一根、策略自述 why;拿不到证据时如实说明并提供一键 AI 改写 | | 免责 | 首次强制确认完整条款(落盘,版本变更时重新提示)+ 底部常驻精简声明;回测结果与 AI 生成处各有提示 |

策略:三种入口

策略页顶部可切换 表达式 / JavaScript 两种模式;页面最上方还有一个 用文字描述让 AI 生成 的入口。三者共用同一套指标实现与同一个回测引擎——JS 侧的指标函数只是把值包装成 AST 节点交给表达式求值器,所以同一个策略在两种模式下必然得到完全相同的回测结果(测试里有交叉验证断言)。

入口一:和 AI 多轮对话(推荐)

策略页底部是对话区:写一句话点「发送」,然后一直追问下去。

我:20 日均线上穿 60 日均线时买入,跌破 20 日均线时卖出。 AI:先用双均线金叉做了一版。已更新为表达式策略,已通过试运行校验 我:再加一个放量过滤,成交量要高于 20 日均量。 AI:加上了。已更新为表达式策略,已通过试运行校验 我:如果持有不到 5 天就别卖。 AI:这个需求表达式表达不了,需要 JavaScript 模式,因为要记住持有了几天。

每一轮 AI 都会给出完整的新策略并直接填进编辑器,所以看完就能点回测。要点:

  • 按当前模式产出:你在表达式模式就让它写表达式,在 JS 模式就写 JS。它做不到时会自己换模式并说明原因(比如「需要记住持有天数」只能写 JS)。
  • 对话上下文一起带上:每一轮都把之前的对话与当前策略发给模型,所以可以用「再加个」「改成」「把止损换成」这种自然的追问,不用重复描述。
  • 想改哪一版都能退回去:表达式与 JS 各留一版,「↺ 换回上一版」一键切回。
  • 只回答、不给代码也是合法的一轮:需求不明确时它会反问,编辑器不动,你接着答就行。
  • 发送后立刻试运行。拿到策略就地跑一遍当前 K 线(表达式按表达式解析、JS 按 JS 执行),语法错、运行错、信号长度不对当场写在气泡下面。试运行失败时策略仍会填进去,方便你手动修。
  • 失败不丢输入:请求失败时你那句话会留在输入框里,直接重试即可。

表达式模式还有个额外好处:表达式里的比较(C > MA(20))会被系统逐条拆出真假与两侧数值, 交易明细里的证据比 JS 模式更细。所以简单逻辑优先用表达式。

  • 把真实事件日期一并交给模型,必要时还会自己联网查。你自己算不出「哪天开会」「那天是不是交易日」,模型同样算不出,所以宿主会先把该股的真实事件列表(见因子)塞进提示词,并明确要求:只能用这些日期、禁止自己推算日历、禁止写死日期字符串。列表里没有的(比如产品发布会),它可以自己用 web_search 去查,查到的日期要经你确认才会生效。比如直接写:

    公司开业绩说明会的前一个交易日卖出,会后的第二个交易日买入。

    它会生成 sell: EVMEET(-1), buy: EVMEET(2),用哪场会、哪天、隔几个交易日,全由真实数据决定。 生成完成后提示里会写「已把该股 N 条真实事件日期交给模型」——这句话就是「有没有真实依据」的凭证。

模型调用走宿主的 llm 服务,用 agentDefaultModel 的当前选型,因此不需要在插件里配置任何 API Key。若宿主未挂载 llm 服务或没有默认模型,会给出明确提示。 对话只保存在当前页面状态里(不落盘),切换股票时会清空;点「清空对话」可以随时重新开始。

入口二:表达式

买入 / 卖出条件各写一行,例如:

买入:CROSS(MA(5),MA(20)) AND RSI(14)<70
卖出:CROSS(MA(20),MA(5)) OR C>BOLL_UP(20,2)
  • 变量:C O H L V(收/开/高/低/量),别名 CLOSE OPEN HIGH LOW VOL
  • 函数:MA(n) EMA(n) SUM(n) STD(n)、RSI(n)、DIF() DEA() MACD()、BOLL_UP(n,k) BOLL_MID(n) BOLL_LOW(n,k)、HHV(n) LLV(n) REF(x,n) CROSS(a,b) ABS MAX MIN
  • 运算符:+ - * / > < >= <= == != AND OR NOT(或 && || !)、括号
  • 函数名与 AND/OR/NOT 大小写不敏感;MA(5) 等价于 MA(C,5)

表达式由本包自带的词法 / 语法分析器求值,不使用 eval。每个节点求值为整段区间的序列,一次算完全程。

入口三:JavaScript

写任意 JS——循环、变量、状态机、多分支都可以。必须 return { buy, sell },两个数组长度都要等于 C.length:

const fast = MA(C, 5)
const slow = MA(C, 20)
const volUp = GT(V, MA(V, 20))
const buy = new Array(C.length).fill(0)
const sell = new Array(C.length).fill(0)
let inPosition = false
let holdDays = 0
for (let i = 0; i < C.length; i++) {
  if (!inPosition && CROSS(fast, slow)[i] && volUp[i]) { inPosition = true; holdDays = 0; buy[i] = 1; continue }
  if (inPosition) {
    holdDays++
    if (holdDays >= 5 && LT(C, slow)[i]) { inPosition = false; sell[i] = 1 }  // 至少持有 5 日
  }
}
// 可选:自己说明第 i 根为什么买卖,会显示在交易明细最前面
function why(i, side) {
  return side === 'buy' ? '5 日线上穿 20 日线且放量' : '持有满 5 日后收盘跌破 20 日线'
}
return { buy, sell, why }

可用(都是与 K 线等长的数组,索引可直接用):

  • 序列:C O H L V,以及总根数 N
  • 指标:MA(x,n) EMA(x,n) SUM(x,n) STD(x,n) HHV(x,n) LLV(x,n) REF(x,n)、RSI(n)、DIF() DEA() MACD()、BOLL_UP(p,k) BOLL_MID(p) BOLL_LOW(p,k)、ABS/MAX/MIN
  • 布尔组合(返回 1/0 序列,null 当假):CROSS(a,b) GT LT GTE LTE AND OR NOT
  • 也支持 MA(5) 这类省略序列的写法(默认用收盘价;HHV 默认最高价、LLV 默认最低价)

想让交易明细给出证据,买卖条件就用上面这组布尔函数组合(AND(CROSS(fast, slow), volUp)), 系统才能逐条还原「信号日哪一条成立、两侧数值是多少、这次到底踩中了哪几条」; 如果直接写 C[i] > slow[i] 这类原生比较,运行期不留下任何可读的中间结构,界面只能给指标快照 + 一条一键 AI 改写的出路。也可以额外 return 一个 why(i, side),用自己的话补一句原因。

错误会分类提示:语法错误、运行异常、返回值类型不对、信号数组长度不匹配——四种都是可读的具体信息,不是一句「失败」。

⚠️ 策略代码在浏览器里执行。避免死循环(如 while(true)):主线程被占满会让界面卡住,刷新页面即可恢复。 模板按钮在两个模式下都会填入对应写法——JS 模式下填的是等价 JS 代码,可直接改。

因子

策略不只能用 K 线。因子分两类,界面上也明确分区——因为有一类数据只存在于「当下」,拿来回测就是造假。

已接入:市场情绪(可回测)

全部由「个股 K 线 + 沪深300 指数」算出,不依赖任何不稳定的第三方源:

| 因子 | 含义 | | --- | --- | | BENCH | 沪深300 收盘价序列(已按日期对齐到当前标的) | | RS(n) | 相对强弱 = 个股 n 日涨幅 ÷ 指数同期涨幅。>1 表示跑赢大盘 | | IDXRET(n) | 指数 n 日涨幅 | | IDXMA(n) | 指数 n 日均线 | | IDXDEV(n) | 指数偏离 n 日均线的比例,正数表示在均线上方 | | IDXVOL(n) | 指数年化波动率(越高越不安全) | | BETA(n) | 个股对指数的滚动 Beta,>1 表示比大盘波动更大 |

表达式模式与 JS 模式都能用(两者共用同一套实现,不会漂移)。策略页会显示这些因子的当前读数。

内置两个模板演示:相对强弱 RS 择时、大盘趋势 + 相对强弱(要求个股与大盘同时处于上升趋势、且个股跑赢大盘才买)。

前视偏差的处理:指数序列只做前向填充,开头没有对应交易日的位置保持 null,刻意不用未来值回填开头——否则早期的因子值会偷偷用上后面的数据。指数取不到时策略会明确报错,而不是静默算出一堆 null。

已接入:公司事件(可回测)

「财报披露后第二个交易日买入」「说明会前一个交易日卖出」这类想法,缺的从来不是写代码的 能力,而是真实日期——「哪天开会」「哪天披露」「那天是不是交易日」,模型凭空算不出来。 所以这三类日期直接从公开数据里取,交易日换算按真实 K 线做:

| 因子 | 含义 | 日期来源 | | --- | --- | --- | | EV(n) | 全部事件 | — | | EVMEET(n) | 业绩说明会 / 股东大会 / 路演 | 公告正文里的「会议召开时间」(公告日在前) | | EVREP(n) | 财报预约披露 | 交易所预约披露时间表 | | EVDIV(n) | 除权除息 | 分红实施公告(公告日在前) | | EVCUS(n) | 自定义事件(AI 联网检索 / 手工添加) | 用户逐个确认,标为「未核实」 |

n 是相对该事件的第 n 个交易日:-1 = 事件前一个交易日,0 = 事件当日, 2 = 事件后第二个交易日。事件当天不是交易日(公告常在周末或盘后发)时按之后第一个 交易日算。策略只写 n,日历由引擎算。

港股同样可用(EVMEET / EVREP):

| 港股事件 | 来源 | 因子 | | --- | --- | --- | | 董事会会议召开日期 | 港交所公告(提前公布哪天开会审批业绩) | EVREP(n) | | 股东周年大会 / 股东特别大会通告 | 港交所公告 | EVMEET(n) | | 业绩公告 | 公告日即业绩日,只能用于 n >= 0 | EVREP(n) |

港股公告是繁体、日期还常写成中文数字(「謹訂於二零二六年五月十三日」),所以日期解析 同时支持阿拉伯数字与中文数字,并覆盖「將於…召開董事會會議」「謹訂於…舉行股東週年大會」 这些写法。

三类事件都是事先公开过的日期,所以「提前于事件」的条件是合规的:

  • 说明会、股东大会、除权除息都有公告日在前;
  • 财报用预约披露日而不是实际披露日——预约时间表是交易所期初公布的,用它做「财报前 卖出」没问题;用实际披露日等于提前知道了财报哪天出。
  • 引擎还有一道硬保护:如果那一根 K 线当天事件还没公告,该条件直接不触发。所以 「当天才公告的说明会」不会被提前埋伏——这一条有测试专门守着。

「公司数据」页有公司动态表,列出该股全部真实事件日期(日期 / 类型 / 内容 / 何时公开), 你能一眼看到策略手里有什么牌。内置模板 说明会前后 演示这套因子。

AI 生成会把该股的真实事件列表直接塞进提示词,并明确要求:只能用这些日期、禁止自己 推算日历、禁止写死日期字符串;生成结果里也会告诉你「已把该股 N 条真实事件日期交给模型」。

数据源里没有的日期:让 AI 自己联网查

产品发布会这类日期,交易所数据里就是没有。这时 AI 会自己联网查证:宿主给它挂了 web_search / web_fetch 两个工具,它可以搜多轮、也可以打开公告页确认,最多四轮 (最后一轮不再给工具,逼它落笔写代码)。

查到之后它不能直接把日期写进策略,而是输出一个 events 区块:

[{"date":"2026-09-09","title":"秋季新品发布会","announcedAt":"2026-08-01","source":"https://…","evidence":"原文写明 9 月 9 日召开"}]

插件解析出来后,在策略页列成候选事件,必须由你点一下「确认加入」才会生效:

  • 自定义事件单独一类,只由 EVCUS(n) 引用,不会悄悄改变 EV / EVMEET 的语义;
  • 表里标成未核实并给出出处链接——交易所数据和模型检索结果永远分得清;
  • 日期不合法(不是 YYYY-MM-DD、或不是真实存在的日期)的条目直接丢掉——宁可少一条,也不让编的日期进来;
  • 公告日未知时按「事先已知」处理,界面会明确写出来:那是个建模假设,不是事实。

两条检索路径(宿主挂了就用宿主的,坏了用内置的)

宿主 web 服务的搜索 provider 可能是坏的——实测本机部署每次调用都报 WEB_PROVIDER_ERROR: DeepSeek returned no web_search_tool_result blocks。所以检索是两级的:

| 顺序 | 来源 | 覆盖范围 | | --- | --- | --- | | 1 | 宿主 web.search(部署配置的搜索提供方) | 通用网页 | | 2 | 内置财经资讯检索 | 东方财富站内搜索:财经新闻 + 公告 |

宿主第一次失败后,本轮不再重试它,直接走内置检索;两条路都不通时如实报错,并且 不再继续给模型工具(省掉后面几轮空转)。界面上会写明这次用的是哪条路、宿主报了什么错、 以及去设置 → 插件 → 插件配置 → Web search 改 Endpoint 就能修好宿主搜索。

宿主还会替模型先查一遍:需求里出现「发布会 / 说明会 / 股东大会 / 业绩 / 财报 / 除权」这类 事件词时,宿主先拿股票名 + 需求去检索一次(约 0.5 秒,10 分钟内同查询走缓存), 把结果原样摆进提示词,并要求模型只能从中取真实日期、找不到就说找不到。 这样做是因为「等模型自己决定去搜」不可靠——实测它会直接声称「列表里已经有这个事件」, 然后写出一个指向不存在日期的策略。

内置检索的结果会标注来源(内置财经资讯检索·东方财富站内搜索:媒体新闻与公告), 不冒充通用搜索。对选股策略来说这部分内容恰好最相关——会议、业绩、发布会都在公告与新闻里。 抓网页(web_fetch)同样先走宿主服务,不可用时退回插件直连取正文。

宿主没挂 web 服务时不会假装查过:返回值里写明「未挂载 web 服务,本次没有联网查证」, 界面照原样显示。/astock/api/health 里的 webSearch 字段就是这项能力的状态。

取不到时不会装没事

会议日期只能从公告正文里读,而正文接口(np-cnotice-stock)在密集请求下会直接 ECONNRESET(实测过)。所以:

  • 正文按 art_code 把解析结果落盘缓存(announcements.json):一条公告里的会议日期 不会变,抓过一次就不再打扰上游,上游抖动也不会让已有的事件凭空消失;
  • 拉取改为小批量并发 + 重试 + 批间隔,降低触发限流的概率;
  • 仍然失败的部分会写进 errors 并在界面上说明「部分事件没取到:…」,绝不悄悄变少。 财报预约披露与除权除息走另一个域名,不受影响,所以限流时事件列表不会全空;
  • 失败后 60 秒内不再请求正文(退避):被限流时反复切股票/刷新只会把封禁越敲越久;
  • 界面上有 ↻ 重新加载事件日期 与 手动添加 两条出路,不用干等。

实测该正文接口在密集请求后会按 IP 限流,返回 ECONNRESET,通常几十分钟到几小时恢复; 恢复后解析结果会永久落盘,之后不再请求。

兜底一:自己填

上游读不到、或数据源里根本没有的日期(自家公司发布会、行业展会),「公司数据」页有 手动添加:选日期、写名称,立刻成为自定义事件,策略里用 EVCUS(n) 引用。 它和 AI 检索的结果一起标成「未核实」,来源分列显示。

一只股票如果没有可用事件(例如港股最近 200 条公告里没有会议类文件),界面会说明原因, 用到事件因子的策略会直接报错而不是静默算出「没有信号」。给 AI 的提示词里也会明确写上 「这只股票没有事件数据,禁止使用 EV/EVMEET/EVREP/EVDIV」,免得它生成一个一跑就报错的策略。

公司数据页:让 AI 去查动态,查到就填表

「公司数据」页有独立的 AI 对话区,和策略页那个是两回事:这里不写策略,只查这家公司的动态 (发布会、业绩说明会、股东大会、财报披露、分红除权……),查到的日期直接填进事件表。

我:小米近三年的产品发布会都有哪些? AI:查到 2026 年 9 月 7 日有一场秋季旗舰新品发布会…… 已自动填入 1 条事件(标为未核实,可删除)

  • 自动填入:宿主直接从 AI 的输出里解析 events 区块并写进自定义事件(同一天同标题只留一条), 客户端只管把最新表铺上去。表里标未核实、带来源链接、随时可删;策略里用 EVCUS(n) 引用。
  • 只问情况不会乱写:AI 没给出日期时,表里不会多出任何东西。
  • 换了股票就重开对话:对话是跟着股票的。

外部数据源:接你自己的接口

除了联网检索,「外部数据源」区可以配 HTTP 接口,配完 AI 就能把它当工具调用:

| 字段 | 说明 | | --- | --- | | 名称 | 工具名(模型看到的是 ds_<名称>) | | URL 模板 | 支持 {code} {name} {query} 占位符,例如 https://api.example.com/ann?code={code} | | 说明 | 原样注入系统提示词——把某个 MCP / skill 的用法写在这里,AI 就知道该怎么用你的数据源 |

启用的数据源会作为工具交给模型,调用结果(含失败原因)都会回灌给它;停用的不会出现。

为什么不是「直接调用宿主注册的 MCP 工具」

用运行时探针在真实 Host 进程里查过,结论是做不到:

插件上下文 ctx.get('tools')  →  只有 register / schemas / get,没有 execute
agent 上下文 ctx.get('tools') →  有 execute(但那是 agent 作用域,插件借不到)
ctx.get('skills').list()     →  0 条(skill 注册在 agent 作用域,插件侧看不到)

也就是说,宿主里注册的工具(包括 MCP 服务器提供的)在插件作用域内不可执行,skill 目录 也读不到。这是作用域设计,不是 bug。所以插件这边给的是自己能掌控的两条路: 把数据源配成 HTTP 接口,或把用法写进「说明」(MCP 若同时提供 HTTP 端点,直接填进来即可)。 界面上也照实写明了这一点,不假装支持。

安全:harness 自带的核心工具(bash / 读写文件 / web_* 之外的东西)不会被暴露给这个助手; 插件只把它自己配置的数据源交给模型。

规划中

| 阶段 | 因子 | 可回测 | | --- | --- | --- | | 行业 | 所属行业、成分股自算行业指数、个股 vs 行业相对强度 | ✅ | | 公司动态 | 公告密度序列;最新公告列表 | 密度✅ / 列表仅当下 | | 市场热度 | 人气榜、行业实时排名、当日资金流 | ❌ 仅展示 |

市场热度这类数据没有历史,所以只能做信息面板并标注「不参与回测」,绝不能当因子用。

港股

搜索、行情、K线、回测都支持港股(代码 5 位,如 00700 腾讯控股、00005 汇丰控股)。A 股与港股可混在同一个自选股列表里。

但港股不是「换个数据源」那么简单——A 股与港股的交易规则不同,直接套用会算错。回测引擎按标的自动切换:

| | A 股 | 港股 | | --- | --- | --- | | 交割 | T+1(当日买入不可卖) | T+0(当日可回转) | | 涨跌停 | ±10%(创业板/科创板 ±20%,北交所 ±30%) | 无涨跌停 | | 每手股数 | 恒为 100 | 逐股不同:腾讯 100、小米 200、汇丰 400、长和 500 | | 印花税 | 卖出单边 0.05% | 买卖双边各 0.1% | | 币种 | CNY | HKD |

每手股数从行情里逐股读取,不写死。策略页会明示当前适用哪套规则,并提供「套用港股默认费率」一键填入。

港股的三个限制

  1. 无复权数据。腾讯对港股不提供复权价(qfq/hfq/不复权返回的数据完全相同),本插件拿到的港股 K 线是不复权的。除权除息日会有价格跳空,长期收益会略被低估。回测结果页会明确提示这一点。
  2. 无财务数据。东方财富那份财务报只覆盖 A 股(港股 SECUCODE=00700.HK 返回「返回数据为空」)。切到港股的公司数据页会给出这句说明,而不是一张空表。
  3. 成交额单位不同。A 股行情接口给的是万元,港股给的是元,已在解析层归一化。同样,港股行情没有换手率与市净率,界面显示 — 而非 0。

回测区间

策略页最上方是一条常驻操作条(滚动时也贴在顶部):

[开始回测]   起始[2023-09-11] ~ 结束[        ]
快捷区间  近1年  近3年  近5年  近10年  全部
          已加载 780 根:2023-08-25 ~ 2026-09-11(选更长的档位会自动多取数据)
          所选区间命中 780 根 K 线。
  • 默认三年:起始日默认为三年前的今天,结束日留空(取到最新)。可以直接改日期,也可以点档位。
  • 年份快捷档位:原生日期选择器只能按月步进,翻到几年前要点很多下。这里的 近1/3/5/10年 是一键跳年。
  • 档位会自动补数据:选了比已加载范围更长的档位(比如只加载了 3 年却点「近10年」),会连带把 K 线年数调大并重新取数,而不是给你一个被静默裁剪的区间。
  • 区间是在已加载的 K 线上按日期裁剪,切换瞬时完成,不会重新取数。
  • 所选区间不足 30 根会直接报错并说明原因;起始日早于已加载数据时会提示需要更多数据,而不会静默算出一段不完整的区间。
  • 结果页会写明实际使用的区间(2026-01-05 ~ 2026-06-30(116 根)),以及是否发生过裁剪。

交易明细:为什么买 / 为什么卖

点击交易明细里的买入日期,会在下方展开这笔交易的决策证据:

▾ 2026-07-15   1204.86   2026-08-20   1298.50   +7.66%   26
    为什么买
      信号日 2026-07-14(收盘决定信号) → 成交日 2026-07-15(开盘 @1204.86)
      策略自述原因(why 钩子返回)
        第 486 根:5 日线上穿 20 日线,成交量高过 20 日均量
      按你代码里的条件函数自动拆解(✓ 成立、✗ 不成立;加重显示的是本次信号的实际触发项)
      ✓  全部条件成立(AND)        下列条件必须同时成立
        ✓  CROSS(MA(C, 5), MA(C, 20))   今 1202.47 vs 1197.29 / 前 1195.80 vs 1197.29
        ✓  V > MA(V, 20)                今 86321.00 vs 41120.00
        ✗  RSI(14) > 200                今 41.8833 vs 200

为什么信号日在成交日前一天:信号在收盘产生、次日开盘成交(无未来函数),所以要解释「为什么」,必须回到信号那一根去看。

证据不是猜的:条件函数会被逐个记录

JS 策略有循环和状态机,静态分析确实给不出可信归因——但它在计算层面是收敛的:每一个买卖条件都是策略库里 GT / LT / GTE / LTE / CROSS / AND / OR / NOT 这些函数的组合。所以库函数在返回时会记下自己的身份、操作数与结果, 信号日就能把整条链路反过来还原出来:

  • 逐条列出每条条件当时成立还是不成立(不成立的一样列出来,不会只挑成立的讲);
  • 每条都比较两侧的实际数值,交叉类条件额外给出前一根(只看当根证明不了上穿);
  • 加重显示本次信号实际触发的那几条:AND 的每个分支都是触发源,OR 里没成立的分支不会被算进去;
  • 缩进表示条件之间的嵌套关系。

两种模式能给出的东西不一样

| 模式 | 能给出的解释 | | --- | --- | | 表达式 | 逐条件拆解:把顶层的 AND / OR 摊平成一条条条件,各自标注 ✓/✗ 并附上比较两侧的具体数值。不成立的条件也会如实列出。 | | JavaScript(用条件函数组合) | 与表达式模式同一套证据:逐条真假 + 两侧数值 + 实际触发项,还能额外显示策略自己写的 why(i, side) 说明。 | | JavaScript(直接写 C[i] > MA(C, 20)[i] 等原生比较) | 无法自动归因——系统拿不到条件函数留下的标记。界面会如实说明这一点并给出信号日的指标快照(C / MA5 / MA20 / MA60 / RSI14 / MACD / 有指数时的 RS、IDXDEV),同时提供一键让 AI 改写成可归因版本(保持买卖逻辑不变,只换成条件函数 + why),原代码留在「策略配置」页可随时换回。 |

这个区别是本质的:原生 JS 比较在运行期不留下任何可读取的中间结构,静态分析也给不出可信的归因。 与其编一个看起来像原因的理由,不如说清「这里只能给快照」并给出一条能真正拿到证据的路。

why(i, side):让策略自己说话

条件函数的证据链说明「哪些条件成立了」,但这条策略为什么要看这几个条件只有写代码的人说得准。 所以代码可以额外 return 一个 why 函数,它返回的一句话会显示在明细最前面:

function why(i, side) {
  if (side === 'buy') return '5 日线上穿 20 日线,成交量高过 20 日均量,且大盘在 60 日线上方'
  return '5 日线下穿 20 日线,或收盘跌破 20 日线'
}
return { buy, sell, why }

AI 生成的策略会被要求同时给出条件函数组合与 why 说明,所以生成的策略开箱就有完整证据。

回测的真实性约定

按 A 股口径(港股按上表切换):

  • 无未来函数:信号在收盘产生,在次日开盘成交。
  • T+1:买入当日不可卖出。因卖出也走次日开盘,实际最短持有为 2 个交易日,比真实 T+1 略保守。
  • 涨跌停:按板块推断限幅(主板 10% / 创业板·科创板 20% / 北交所 30%)。开盘处于涨停价则买不进,跌停价则卖不出。ST 股的 ±5% 无法从代码推断,未建模,会对 ST 偏乐观。港股无此限制。
  • 成本:佣金费率可调、5 元起收;印花税买卖费率分开设置(A 股卖出单边、港股双边);其他费率(过户费等)可调;滑点默认千分之一。
  • 整手:委托量按该股的每手股数取整(A 股 100,港股逐股不同)。资金不足 1 手时不会建仓,界面会明确提示原因。
  • 前复权口径:A 股默认使用前复权价,收益连续性正确,但绝对价位与历史成交价不一致。
  • 回测结束仍有持仓时不强制平仓,按最后一根收盘价计入权益(界面会提示)。

数据源

默认全部免密钥。K线与行情以腾讯为主源、新浪为备源,搜索与财务用东方财富。

  • K线:web.ifzq.gtimg.cn(按年区间分页)→ 备源 money.finance.sina.com.cn
  • 行情:qt.gtimg.cn(GBK 编码,按响应头 charset 解码)
  • 搜索:searchapi.eastmoney.com;失败时回退为纯代码录入
  • 财务:datacenter-web.eastmoney.com
  • 事件:港交所/交易所公告(正文日期)、预约披露时间表、分红实施公告
  • 资讯检索(AI 联网查证的兜底):search-api-web.eastmoney.com

同花顺官方数据(可选,需要自己的 API Key)

同花顺官方有一套面向 AI Agent 的 A 股数据服务:同一个 API Key,提供 MCP 与 REST 两套入口。

| 项 | 值 | | --- | --- | | 官网 / 文档 | https://fuyao.aicubes.cn/ · https://fuyao.aicubes.cn/docs/ | | API Key | https://fuyao.aicubes.cn/admin/ 免费签发 | | REST | https://fuyao.aicubes.cn/api/...,请求头 X-api-key | | 托管 MCP(HTTP) | https://fuyao.aicubes.cn/mcp/a-share、/mcp/a-share-index、/mcp/meta 等 6 个 | | 限流 | 不限制累计调用次数(异常并发会 429 / code=4001) |

在「公司数据」页填入 Key 之后:

  • AI 助手会多出 ths_corporate_actions(除权除息 / 分红送股)、ths_prices_snapshot(行情)、 ths_hot_stocks(热榜)、ths_dragon_tiger(龙虎榜)四个工具;
  • 除权除息事件在东方财富那份表为空时,自动改用同花顺官方复权事件兜底;
  • Key 只存在本地($DSH_ASTOCK_HOME/hithink.json),接口里只回掩码,不回明文。

为什么插件走 REST 而不是直接连 MCP:宿主里注册的 MCP 工具在插件作用域内不可执行 (插件拿到的 tools 服务只有 register/schemas/get,没有 execute,见下文实测)。 好在这套服务两套入口共用同一个 Key、同一批数据,走 REST 等价且更简单。 当前覆盖 A 股,港股不在它的范围内。

配置

| 环境变量 | 作用 | 默认 | | --- | --- | --- | | DSH_ASTOCK_HOME | 状态落盘目录 | $DSH_HOME/astock,再退回 ~/.dsh/astock |

目录里有四个 JSON:watchlist.json(自选股)、disclaimer.json(免责声明确认)、 events.json(自定义事件)、announcements.json(公告正文解析缓存,可随时删除,删了会重新抓)。

结构

package.json             dsh.bundle.patch + dsh.client.platform,exports["./client"]
cordis.patch.yml         插入 id=astock name=dsh-astock
lib/index.js             Host 半边:10 条 /astock/api/* 路由 + 自选股/免责/自定义事件落盘 + 事件抓取 + 联网查证工具循环
client/client.js         客户端 bundle:__ModuleLoader__ 工厂块,仅 require('react')
tests/
  engine.test.mjs        表达式引擎 + 回测引擎(纯离线,读 fixtures)
  host.test.mjs          Host 路由(真实上游 + mock llm)
  client.test.mjs        客户端 bundle(真实执行组件 + 数据流)
  fixtures/              两份真实日线数据,供离线测试使用

客户端与 Host 通过 HTTP 路由通信(host.call 是动态插件专有机制,正式 bundle 不可用)。

测试

pnpm test                 # 三套全跑
node tests/engine.test.mjs   # 策略与回测引擎(纯离线,43 项)
node tests/host.test.mjs     # Host 半边:真实上游 + mock llm + 港股 + 自选股混排 + 免责声明 + 事件路由 + 港股事件解析 + 联网查证工具循环 + 多轮对话与表达式输出 + 检索兜底与预查 + 公司数据对话 + 同花顺数据(280 项)
node tests/client.test.mjs   # 客户端 bundle:真实执行组件 + 数据流 + 策略模式 + AI 入口 + 港股规则 + 回测区间与档位 + 免责弹窗 + 情绪因子 + 事件因子与自定义事件 + 交易证据链 + AI 对话与表达式模式 + 公司数据对话与数据源管理 + 同花顺 Key(274 项)

host.test.mjs 与 client.test.mjs 需要网络(要打腾讯/东财的真实接口)。engine.test.mjs 完全离线,只读 tests/fixtures/。

client.test.mjs 用自建 hook 运行时而非 react-dom:宿主里 react 是 18、react-dom 是 19,版本不匹配;本插件只用到 createElement / useState / useEffect,足以模拟。

安装

dsh plugin --profile web add -w <本目录绝对路径>

-w 是必需的:profile 自身的 pnpm-workspace.yaml 把它标记为 workspace root。

首次安装后需重启 dsh web(bundle 列表只在启动时读取)。

改代码后要不要重启?

分两半,结论不同:

| 改动的文件 | 生效方式 | | --- | --- | | client/client.js | 热更新,不用重启。harness 的 @deepseek-ai/dsh-client-hmr 会轮询 bundle 文件,内容变了就通过 /plugins/events SSE 推送新版本,页面自动换掉旧 bundle(必要时刷新一次页面)。 | | lib/index.js(Host 半边) | 需要重启 dsh web。Host 插件在启动时装载。 |

判断某次改动是否已经被服务:从 /plugins/events 取当前图,找到本包条目的 url,抓下来 grep 新代码即可:

curl -s --max-time 5 http://127.0.0.1:3080/plugins/events \
  | sed -n 's/^data: //p' | head -1 \
  | node -e "let s='';process.stdin.on('data',d=>s+=d).on('end',()=>{const g=JSON.parse(s).graph;console.log(g.entries.find(e=>e.id==='dsh-astock').url)})"

版本记录

每个版本对应一次 GitHub 提交与一次 npm 发布。完整提交历史见 commits。

0.9.0

接入同花顺官方数据(REST API,凭一个 Key)

调研确认:同花顺官方(HiThink-Tech)发布了面向 AI Agent 的金融数据服务 HiThink-Tech/Financial-API,同一个 API Key 同时提供 MCP 与 REST 两套入口: 6 个托管 MCP 端点(https://fuyao.aicubes.cn/mcp/*,HTTP 传输)与一组 REST 接口 (https://fuyao.aicubes.cn/api/...,请求头 X-api-key),不限制累计调用次数。

由于插件作用域执行不了宿主注册的 MCP 工具(实测),插件改走同一份数据的 REST 入口:

  • 「公司数据」页新增「同花顺官方数据」:粘贴 Key → 保存 / 清除 / 测试连接;Key 本地落盘、只回掩码。
  • 配好后 AI 助手多出四个工具:ths_corporate_actions(除权除息/分红送股)、 ths_prices_snapshot、ths_hot_stocks、ths_dragon_tiger。
  • 除权除息事件在东方财富为空时用同花顺官方复权事件兜底(A 股)。
  • 代码 → thscode 的交易所后缀映射(沪/深/北),港股明确报「不在覆盖范围」。

测试:host 262 → 280 项(后缀映射、Key 掩码与清除、未配置不暴露工具、配置后注入工具、 假 Key 被如实拒绝、除权兜底),client 264 → 274 项(Key 表单、掩码、测试连接、清除)。 合计 597 项全通过。

0.8.0

公司数据页接入 AI 对话:查到动态直接填进表;并接自定义数据源

  • 「公司数据」页新增 AI 对话区:查发布会 / 说明会 / 股东大会 / 财报 / 分红等公司动态, 查到的事件由宿主直接写进事件表(标未核实、带来源、可删除,同日同标题去重), 策略里用 EVCUS(n) 引用。只问情况、没给出日期时不会往表里塞东西;换股票自动重开对话。
  • 新增「外部数据源」:可配 HTTP 接口(URL 模板支持 {code} {name} {query}), 启用的数据源会作为工具交给模型,调用结果(含失败原因)回灌;「说明」字段原样注入提示词。
  • 实测结论(运行时探针):插件上下文里的 tools 服务只有 register/schemas/get, 没有 execute;skills.list() 在插件侧返回 0 条——也就是说插件无法调用宿主注册的 工具(含 MCP 服务器)或读取 skill 目录,这是作用域设计。因此走「HTTP 数据源 + 说明文本」 这条自己能掌控的路,界面上也照实写明,不假装支持。
  • 安全:只把插件自己配置的数据源交给模型,harness 自带核心工具不暴露。

测试:host 238 → 262 项(数据源增删改查、模板替换、数据源当工具调用并回灌、说明注入、 自动填表与去重、停用不暴露、只问答不写表),client 241 → 264 项(对话区、自动填表提示、 数据源增删、注册表限制说明)。合计 569 项全通过。

0.7.2

修「小米产品发布会之前卖出」这类需求跑不通

拿线上真实请求复现过,两头都出了问题:

  1. 模型根本没搜(searched=false、suggestedEvents=[]),却在说明里声称 「列表里已经有 2026-09-07「小米产品发布会」,已登记为自定义事件」,然后写了 EVCUS(-1)——策略指向一个不存在的日期。原因是提示词只说「列表里没有的事件才需要联网查」, 没写「不许假设它已经存在」。
  2. 即使它真的写错了,也没有任何机制拦住。

三处修复:

  • 提示词加硬规则:EVCUS 只能引用用户已确认的自定义事件;事件列表里没有的, 不许假设存在、不许跳过检索;绝不允许声称「列表里已经有某个事件」,除非它真的在列表里。
  • 输出后校验 + 强制补一轮:策略里用到了 EVMEET/EVREP/EVDIV/EVCUS(或事件全空的 EV) 而对应事件不存在时,宿主不采信,补一轮带明确指令的对话(「先联网查证并输出 events 区块, 查不到就明说」)。只有既给出候选事件、又确实搜过才算合规——凭记忆写的日期一样不可信。
  • 宿主替模型先查一遍:事件类需求先自己做一次新闻检索(内置财经资讯检索), 把结果摆进提示词。不再把可靠性押在「模型会不会去调工具」上。
  • 兜底也给足出路:确实查不到时,界面上写明三条路——再追问一次 / 公司数据页手动添加 / 改成不依赖事件因子;候选事件区也标注「不点确认就直接回测会报错」。

测试:host 218 → 238 项(假设事件的检测、强制补一轮、预查块与命中缓存、非事件需求不预查), client 240 → 241 项(查不到事件时的三条出路)。合计 522 项全通过。

0.7.1

修「AI 的 web_search 用不了」:检索加了一层内置兜底

用运行时探针在真实 Host 进程里查过,结论很明确:

  • llm 的工具调用完全正常——模型会正确发出 web_search 的流式工具调用;
  • 坏的是宿主 web.search:每次调用都返回 WEB_PROVIDER_ERROR: DeepSeek returned no web_search_tool_result blocks, 它配的搜索 Endpoint(open.feedcoopapi.com/search_api/global_search/messages)没返回原生搜索结果。

插件这边能做的是不把整件事卡在宿主的配置上:

  • 宿主搜索失败后自动改用内置财经资讯检索(东方财富站内搜索,覆盖财经新闻与公告), 实测能搜到「贵州茅台召开半年度业绩说明会」这类带日期的新闻,正是策略需要的东西;
  • 宿主 provider 失败一次后本轮不再重试;两条路都不通就如实报错并停止再给工具;
  • 界面写明这次用的是哪条路、宿主的原始报错,以及修好宿主搜索的路径 (设置 → 插件 → 插件配置 → Web search → Endpoint);
  • web_fetch 同样加了插件直连的兜底。

测试:host 208 → 218 项(内置检索结构、宿主可用时走宿主、provider 报错时退回内置、 本轮不再重试、两条路都不通),client 237 → 240 项(检索来源与修复路径提示)。合计 501 项全通过。

0.7.0

AI 改成多轮对话,并且能产出表达式策略

原来只有「一句话 → 一次生成 → 填进编辑器」,想再改只能重说一遍;而且只出 JS。 现在策略页底部是对话区:

  • 多轮对话:每一轮都把之前的对话与当前策略一起发给模型,所以「再加个放量过滤」 「把止损换成跌破 30 日线」这样追问就行。AI 每轮给出完整的新策略并直接填进编辑器。
  • 支持表达式模式:按你当前的模式产出——表达式模式要求输出 {"buy":"…","sell":"…"},JS 模式要求输出函数体。需求确实表达不了时(要记持有天数、 多步状态),它会改用另一种模式并说明原因,返回值和界面都如实显示实际产出的是哪种。
  • 只回答不给代码也是合法一轮:需求不明确时它会反问,编辑器不动。
  • 两模式各留一版可回退:↺ 换回上一版表达式 / ↺ 换回上一版代码。
  • 失败不丢输入:请求失败时那句话回到输入框,并在对话里留一条失败气泡。
  • 表达式模式下的事件因子(EVCUS(-1))也能用;交易明细里同样写明命中的真实事件—— 这一条原来只对 JS 生效,现在表达式模式也补上了。

注意:AI 产出的策略每一轮都会覆盖编辑器(旧版可回退)。模型偶尔忘记加围栏代码块时, 只要整段看着就是函数体也会被认成代码。

测试:host 187 → 208 项(表达式块解析、对话历史传递与裁剪、反问轮、模型换模式时如实回报、 无围栏纯代码识别),client 211 → 237 项(多轮历史、表达式应用与试运行校验、反问不改编辑器、 清空对话、回退上一版、表达式模式的事件因子证据)。合计 488 项全通过。

0.6.3

限流退避 + 手动添加事件(上游靠不住时的两条出路)

东财的公告正文接口(np-cnotice-stock)在密集请求后会按 IP 限流,返回 ECONNRESET。 实测该接口没有可替代的镜像域名(np-anotice-stock 的 content 路径返回空 body),所以:

  • 失败后 60 秒内不再请求正文——被限流时反复切股票/刷新只会把封禁越敲越久;
  • 「公司数据」页新增手动添加事件:选日期、写名称,即时成为自定义事件(标为手工来源、 未核实),与 AI 检索走同一条保存路径;
  • 配合 0.6.2 的「取数出错不写缓存」与「↻ 重新加载」,限流期间不再被锁死。

测试:host 185 → 187 项,client 206 → 211 项。合计 441 项全通过。

0.6.2

修「限流后要等 30 分钟才恢复」

事件列表有 30 分钟进程内缓存,但取数出错时也写了缓存——于是上游限流一次, 「缺了会议事件」的结果就被锁住半小时,上游恢复了用户也看不到。改成只有成功才进缓存。

同时「公司数据」页新增 ↻ 重新加载事件日期 按钮,取数失败会直接显示原因, 不用切股票来触发重取。

测试:client 204 → 206 项。合计 434 项全通过。

0.6.1

港股也能用事件因子了(修「港股没事件数据」)

0.6.0 上线后实测:港股股票的事件列表是空的,AI 生成的事件策略一跑就报 「事件因子需要公司事件数据」。原因是港股公告的标题写法、日期写法都与 A 股不同, 原来的解析一条都没匹配上:

  • 新增港股来源:「董事会会议召开日期」公告=提前公布的业绩日(港股版的预约披露), 归到 EVREP(n);股东周年大会 / 股东特别大会通告归到 EVMEET(n); 业绩公告作为兜底(公告日即业绩日,只能用于 n >= 0,不会变成未来函数)。
  • 日期解析支持中文数字(「謹訂於二零二六年五月十三日」→ 2026-05-13), 并覆盖「將於…召開董事會會議」这类港式写法。
  • 同一天的财报事件去重时优先保留更早公布的那条(预告日), 这样 EVREP(-1) 这种提前量才是有依据的。

修「上游限流导致事件悄悄变少」

公告正文接口在密集请求下会直接 ECONNRESET。原来的代码把它 .catch(() => null) 吞掉了, 表现就是「事件列表莫名变少、界面还说一切正常」。现在:

  • 正文解析结果按 art_code 落盘缓存(announcements.json),抓过就不再请求;
  • 小批量并发 + 重试 + 批间隔,降低触发限流的概率;
  • 失败的部分写进 errors 并在界面说明「部分事件没取到:…」,绝不静默变少。

修「模型不知道没有事件数据」

原来没有事件时只回报一个 0,模型照样按常识写 EVMEET(-1)。现在这种情况会在提示词里 明确写上「这只股票没有事件数据,禁止使用 EV/EVMEET/EVREP/EVDIV」,并给出两条出路 (联网查到就用 EVCUS、否则生成不依赖事件的替代策略)。客户端的试运行也会把 待确认的候选事件一并算进去,不再出现「明明确认后能用、却先报一次错」。

测试:host 158 → 185 项(中文数字与繁体日期提取、港股真实事件、候选事件校验、无事件提示词), client 201 → 204 项(无事件时的原因显示与预警、试运行带上候选事件)。合计 432 项全通过。 新增测试钩子 __test(仅供离线测试调用纯函数,不是公开 API)。

0.6.0

AI 可以自己联网查资料了(发布会这类交易所数据里没有的日期)

0.5.0 给了模型真实的交易所事件日期,但那里面没有「产品发布会」。0.6.0 让模型自己上网查:

  • 宿主给模型挂 web_search / web_fetch 两个工具,最多四轮工具调用(最后一轮不给工具, 逼它落笔写代码)。工具调用是流式分片给出的,插件按 index 拼回完整参数。
  • 模型查到的日期不能直接写进策略,必须输出 events 区块;插件解析后列成候选事件, 由用户点「确认加入」才成为自定义事件。日期不合法的条目直接丢弃。
  • 自定义事件单独一类,只由 EVCUS(n) 引用,不会悄悄改变 EV/EVMEET 的语义; 表中标注未核实并附出处链接,与交易所数据严格分开。
  • 宿主没挂 web 服务时静默降级,但会如实回报「未挂载 web 服务,本次没有联网查证」; health.webSearch 暴露这项能力的状态。
  • 新增 /astock/api/custom-events(覆盖式保存)与 events.json 落盘;事件表支持逐条删除。

测试:host 127 → 158 项(多轮工具调用、分片参数拼接、候选事件解析与非法日期丢弃、搜索失败的降级、 没有 web 服务时的降级、自定义事件的去重与来源区分),client 181 → 201 项(自定义事件渲染与删除、 EVCUS 语义、自定义事件不混进 EVMEET、候选事件的确认流程)。合计 401 项全通过。

0.5.0

交易明细的「为什么买 / 为什么卖」改为真正的证据链(针对「还是没给出实际证明」的反馈)

之前 JS 模式的交易明细只能给一张指标快照,没有任何东西能证明「这笔交易到底踩中了哪一条条件」—— 信号日的 MA5 甚至可能是向下的,快照本身无法解释信号。0.5.0 把这件事做实:

  • JS 策略库现在会记录触发链路:每个条件函数(GT / LT / GTE / LTE / CROSS / AND / OR / NOT) 返回时都会记下自己的身份、操作数与结果(标记不可枚举,不影响任何计算结果,两种模式的指标仍然同源)。 信号日据此把整条链路反向还原:
    • 逐条列出每条条件当时成立还是不成立(不成立的照样列,不挑着说);
    • 每条附两侧实际数值,交叉类条件额外给出前一根(只看当根证明不了上穿);
    • 加重显示本次信号实际触发的那几条:AND 的每个分支都是触发源,OR 里没成立的分支不算;
    • 缩进表示嵌套关系。
  • 新增 why(i, side) 钩子:策略可以自己 return 一个函数说明第 i 根为什么买卖, 这段话显示在明细最前面——「为什么要看这几个条件」只有写代码的人说得准。
  • 原生比较(C[i] > slow[i])仍然无法归因,此时界面明确说「无法自动归因」并给出三条出路, 其中一条是新增的一键让 AI 改写成可归因版本(保持买卖逻辑不变,改成条件函数组合 + why)。 改写会覆盖编辑器内容,因此旧代码自动留档,「策略配置」页可一键换回。
  • AI 生成的策略被要求必须用条件函数组合并附 why 说明,开箱即有完整证据。
  • 策略编辑页与 README 都写清了「怎么写才有证据」。

新增:公司事件因子(真实日期,可回测)

  • 新增 /astock/api/events:抓三类事先公开的真实日期——说明会 / 股东大会(逐条去公告正文里 提取「会议召开时间」,提取不到或日期不合常理就宁可漏掉也不编)、财报预约披露日、 除权除息日(带公告日)。
  • 新增 EV(n) / EVMEET(n) / EVREP(n) / EVDIV(n):n 是相对该事件的第 n 个交易日, 交易日换算按真实 K 线做(事件日不是交易日就顺延到之后第一个交易日)。
  • 硬保护:那一根 K 线当天事件还没公告时,条件直接不触发——「当天才公告的说明会」不会被提前埋伏。
  • 「公司数据」页新增公司动态表;新增模板说明会前后;交易明细里会写明「命中 2026-08-21 说明会」。
  • AI 生成把该股真实事件列表塞进提示词,并要求只能用这些日期、禁止推算日历;返回值里带 events 条数,界面显示「已把该股 N 条真实事件日期交给模型」。

测试:client 139 → 181 项(证据链、AND/OR/NOT 节点、实际触发项、OR 假分支不误标、原生比较兜底、 一键改写与换回;事件日映射、非交易日顺延、当天公告不许提前埋伏、事件缺失时的可读错误), host 103 → 127 项(AI 提示词要求条件函数与 why、要求用事件因子且不许编日期、事件路由的真实性与日期合理性)。 合计 351 项全通过。

0.4.1

文档发布:让 npm 页面同步 README

  • 纯文档版本,无代码变更。npm 上已发布的版本不能修改,0.4.0 的 README 里没有版本记录, 这个版本只为把最新文档同步到 npm 页面。
  • 之所以单独发一版而不是等下次代码改动:npm 页面的 README 是给使用者看的第一手材料, 版本记录留在那里才有意义。

0.4.0

交易明细说明「为什么买 / 为什么卖」

  • 点击交易行向下展开决策证据。表达式模式把顶层 AND / OR 摊平成一条条条件,各自标注 ✓ / ✗ 并附上比较两侧的具体数值;不成立的条件也如实列出,不挑着说。
  • 引擎开始记录信号发生的 bar(成交在次日开盘,所以信号在前一根),解释回到信号那一根而不是成交那一根。
  • JS 模式无法定位到具体语句,界面如实说明并改为给出信号日的指标快照——与其编一个像原因的理由,不如说清这里只能给快照。

0.3.1

修复:AI 生成策略总是报「模型没有返回任何内容」

  • 根因:思考 token 与正文 token 共用 maxTokens。默认选型带 reasoningEffort: high,2048 的预算被思考全部吃掉,模型还没开始写代码就被截断(实测 reasoningTokens=2048、text-delta=0、finish=max-tokens)。
  • 不再透传 reasoningEffort;maxTokens 2048 → 16384。
  • 错误信息改为说清「为什么没有内容」,区分截断 / 其他 finish 原因 / 流意外结束,并给出可操作建议。

0.3.0

市场情绪因子(第一阶段)

  • 新增 BENCH、RS(n)、IDXRET(n)、IDXMA(n)、IDXDEV(n)、IDXVOL(n)、BETA(n),全部由「个股 K 线 + 沪深300」算出,完全可回测。
  • 表达式与 JS 两种模式共用同一套实现(只在求值器里写一次),不会漂移。
  • 指数序列只做前向填充,开头保持 null,不用未来值回填——避免前视偏差。
  • 策略页新增因子读数面板;新增「相对强弱 RS 择时」「大盘趋势 + 相对强弱」两个模板。

0.2.0

免责声明 + 改名 + 首次发布到 npm

  • 首次打开强制确认完整条款(落盘,条款版本变更时重新提示)+ 底部常驻声明;回测结果与 AI 生成处各有提示。
  • 显示名从「A股量化工作台」改为「A股港股量化工作台」,侧边栏标签改为「A股港股」——此前已支持港股,旧名字不再准确。
  • 移除 private,补 publishConfig(本机默认 registry 是只读镜像,不显式指定会发布失败)。

0.1.0

首个版本

  • A 股与港股:自选股(可混排、落盘)、手写 SVG K 线图、公司财务数据。
  • 三种策略入口:6 个参数化模板 / 表达式(手写词法 + 语法分析,不使用 eval)/ JavaScript(new Function,支持循环与状态机)。
  • 回测引擎:按市场自动切换规则(A 股 T+1、涨跌停、100 股一手、印花税卖出单边;港股 T+0、无涨跌停、每手逐股读取、印花税双边),支持按日期区间回测。
  • 无未来函数(信号收盘产生、次日开盘成交)、佣金/印花税/过户费/滑点、整手约束。

说明:0.1.0 发布时显示名仍是「A股量化工作台」,改名的提交(42f99d4)随 0.2.0 一起发布。