dsh-tradewatcher(盯盘)
一个 DeepSeek Harness Web 插件:通过宿主客户端 Slots 服务注册一个全局「盯盘」面板(左侧栏图标 sidebar.panellist + 主面板 main,两者 id/key 同为 tradewatcher),提供 A股/国际/商品行情条 + 悬浮分时图、行业热力图、分组自选、流水驱动的分组持仓账本(市值/浮动盈亏/当日盈亏/已实现),并把持仓记录以只读模型工具 + 明文 JSON 双通道开放给 dsh 会话分析。
界面与数据组织参考浏览器插件「爱盯盘」(52etf.site);行情为免费公开接口的延迟行情(非 L2),仅作盯盘参考。
- 安装与使用 → 本文档
- 历次变更、口径修订与实测依据 →
CHANGELOG.md - 待开发清单(含「动手前必须探活的源」与「明确不做」) →
docs/ROADMAP.md
功能
面板由八个页签组成:自选 / 持仓 / 大盘(含护盘信号与底部位置分析)/ 云图 / 日历 / 工具箱 / 复盘 / 棱镜。下面按模块说明。
行情条与详情图
- 顶部行情条(四行,默认每 10s 自动刷新 + 手动刷新,页签隐藏时自动暂停)
- A股指数:上证指数、深证成指、创业板指、科创50、沪深300、中证500
- 国际市场:韩国KOSPI200、法国CAC40、英国富时100、欧洲斯托克50、标普500、纳斯达克、德国DAX30、日经225、恒生指数
- 国债收益率:美债10Y、日债10Y、德债10Y、英债10Y
- 大宗商品:生猪主连、焦煤主连、豆粕主连、螺纹钢主连、布伦特原油当月、COMEX铜、COMEX白银、伦敦金现
- 每卡片:名称 / 现价 / 当日涨跌值 / 涨跌幅(红涨绿跌;浅色/深色/跟随系统主题可切);右上角五态色点(实时/延迟/定稿/未取到/无行情源,见「数据出处与状态」)
- hover 悬浮卡:当天分时图(源缺失自动回退近 5 日收盘走势),附昨收基准虚线、最后行情时间与成交额
- 点击卡片打开右侧详情抽屉(键盘 Enter/Space 亦可)
- 自选/持仓每行带分时缩略线,hover 叠加均价线与分时首笔 / 分时高 / 分时低 / 分时振幅(口径:取自分时抽样点,不是交易所口径的今开/最高/最低 —— 官方开高低在详情抽屉;图上不加常驻文字:密集布局里加字就是噪音);均价线用与价格同一套 y 映射,缺口不连线
- 详情抽屉:固定信息头(名称/代码/现价/涨跌幅/所属行业板块及板块涨跌幅/今开/昨收/换手率/最高/最低/成交量/成交额/市盈率/总市值)+ 三窗格专业图:主图 + 成交量(红绿柱、最大量标注)+ MACD(HIST 柱 + DIF/DEA 线,12/26/9,HIST=(DIF−DEA)×2),主图与子图均带纵轴刻度线(1/2/5×10ⁿ 步长);K 线叠加 MA5/10/30/60(橙/蓝/紫/灰,附当前值图例,均线在全量序列上计算后再截取可视区间)
- 周期:分时 / 五日 / 日K / 周K / 月K / 年K(记住上次选中)
- 分时主图叠加均价线;五日图为 5 个交易日连续拼接(午休/隔夜已折叠,交易日之间画分隔线)
- 东财
trends2的ndays参数会被上游忽略(只回当日),因此五日视图改用 5 分钟 K 线拼接:新浪为主源(A股/ETF/指数,一次可取 6–8 个交易日)、腾讯 5 分钟 K 线兜底 - 本地按日归档 + 拼接(v0.31.0 起):每次成功取到某一日的分时(任何来源,含 LKG 兜底命中那一次)都会按
secid + 交易日归档;港/美股、国际指数、外盘商品、期货这些市场,五日档改用本地归档拼接——覆盖多少天、缺哪天,写在口径条上(我们的两条多日渠道(新浪 5 分钟线、腾讯 5 分钟线)只覆盖沪/深,其余市场靠本地归档逐日累积;这是「我们没覆盖」,不等于「上游没有」。2026-10-11 实测:东财 trends2 忽略 ndays(ndays=1 与 5 回包逐点相同),腾讯 5 分钟线对港股未产出 ≥2 个交易日)(本地拼接 3/5 天 · 缺 2026-10-07),缺的日子只列不补 - ⚠ 不得期待历史归档:归档从本版起累积,装好当天最多只有 1 天(界面上会写「仅当日,从本版起累积」),之后每过一天多一天,最多拼到 5 天。过去的日子没有归档过,追不回来——这不是 bug,也不需要"补数"
- 缩放:日期宽度双滑块 + 近1月/3月/6月/1年/全部 快捷键,图表内滚轮以光标为中心缩放(最少 8 根);键盘:
←/→平移(Shift 加速 10 根)、+/-缩放、Home/End到两端,滑块手柄是标准 slider 语义 - 买卖点:图上按持仓流水标注 B/S(悬停显示「数量 @ 价格 · 分组/持仓」);周/月/年 K 把同周期多笔归并为
B2/S3;同一根 bar 的多笔合并为一个标记,悬停给当日小结(笔数 · 合计股数 · 均价);图下有区间图例(区间内 B/S 笔数、净买卖股数、买卖均价),缩放把交易移出可视范围时补一句(区间外另有 N 笔) - 读屏摘要:
.tw-sr文本与图同源同一次计算(区间、首末收盘、区间涨跌幅、最高最低、买卖点笔数与净买卖),不会出现"摘要说涨、图上在跌" - 顶部口径条:
日K · klt 101 · 前复权 · 截至 10-09 14:31(当根未收盘,MA/MACD 已含未收盘当根)—— 复权用宿主回包的实际生效口径;「截至」用本地最近一次成功取数时刻,不拿"现在"顶替
自选
- 分组的新建/改名/备注/删除(删除=归档,可恢复)+ 组内添加(名称/代码搜索补全)/移除/移动分组;搜索池覆盖 A股/港股/美股/ETF/基金/指数/期货主连(如 688825、02155)
- A股行展示所属行业板块名、板块当日涨幅、个股相对板块 alpha(个股涨跌幅 − 板块涨跌幅)(行业归属 10 分钟缓存,板块涨幅随行情轮询同步刷新);板块涨跌缺失时显示
—并给出原因,禁用 0 代替(0 会被读成"没涨没跌",那是错的信息) - 排序:默认顺序 / 当日涨跌 / 总市值(组内生效,记忆在
prefs.json,同键再点切换升降序);排序沿用面板头部的段控(v0.36.0 起不再有列头排序 —— 列表上方那排列头与段控功能重复,已按用户要求删除),任何宽度下都只有这一个入口 - 年初至今(YTD)列:口径见「口径规范 → 年初至今」;显示
—时可在 tooltip 看到具体原因 - 异动提示:放量/价格异动/量价共振的行带左侧竖条 + 缓慢呼吸背景(不是整行变红——整行红会与"跌了"的语义冲突;
prefers-reduced-motion下退化为静态底色),面板头部「异动 N」徽标可展开队列;同一条目 30 分钟静默(记在 localStorage)
持仓
- 分组 CRUD(同上归档语义)+ 组内证券的「买入/卖出/调整/编辑/移动/明细/移除」
- 每分组与总览显示总市值、浮动盈亏、当日盈亏、累计已实现;每行含浮动盈亏率与当日盈亏率、可用(可卖)数量、累计费用(含占累计成交额百分比)、标的年初至今(YTD)
- 排序:默认顺序 / 市值 / 持仓盈亏(随所选成本口径)/ 当日盈亏 / 仓位占比
- 总额口径:默认只含 A股,港/美股逐项列在「不含 N 项 ▼」里并可下钻看原因;也可切到固定汇率折算(见「折算口径」)
- 除权提示:复用已同步的日历事件,顶部列出涉及持仓的公司行为与日期,持仓卡片标「N 天后除权」;不自动改账(真实的送转/派息以券商为准,正确动作是在「调整」里按实际到账录入)
- 成本口径可切换(右上角分段开关,偏好存
prefs.json),切档弹 toast 报差值而不是只改数字
A股大盘
- 12 栏网格的紧凑布局(v0.33.0 重排:单行 25px 指标条 + 45px 指数卡矩阵,取代原先每个数值一张两行卡):B1 市场宽度(家数/两市成交额/上涨占比 + 历史分位分布条)+ B2 六大指数卡矩阵 + B3 护盘信号(收起态,无数据时塌成一行)+ B4 板块/资金/ETF + B5 搜索与详情
- 涨跌家数:优先用上游直接给的字段;拿不到时由本插件自行按沪深A股全量统计(口径见「口径规范 → 涨跌家数」)。取不到就显示
—并把每个尝试过的源与各自的原因写在 tooltip 里,不用 0 顶替 - 涨跌家数历史分位(样本 ≥5 个交易日后显示;样本不足给原因不给 0)
- 行业/概念板块涨跌排行与主力资金排行(口径行常显:数据时刻 / 来源;15:05 后标「定稿」;金额与占比两种排序可切,占比只在本页 40 条内重排并写明)
- ETF 排行;个股/基金搜索 + 详情卡(分时大图、换手/量比/市盈率/市值)
- 缺失时收缩而不是留空:家数缺失时三格合并成一格且原因只出现一次;其余块缺失只影响一格/一行(块高变化 ≤±8px)
状态可见性(侧栏徽标与降级态)
- 侧栏徽标:左侧栏「盯盘」图标上带一个小徽标,不打开面板也知道发生了什么 —— 内容只有持仓当日盈亏率(带符号短标记,如
-2.3%;0.0%是真的零;算不出时徽标为空、悬停里给原因,不用0冒充);颜色跟随prefs.redUp(红涨绿跌 ↔ 绿涨红跌);隐身视图下只显示沪深300 点位,不出现任何金额。侧栏标签恒为「盯盘」(不带· 数据 HH:mm:ss、也不带· 已收盘—— 用户明确不要后缀);数据时刻与「已收盘」状态在侧栏条目的悬停title(当日盈亏 -1.6% · 数据 15:05:56 · 已收盘)与面板内,信息没有丢。图标只有 18px、字形最多 4 个字符。徽标数据来自GET /tradewatcher/badge,只读同一份 rescue 快照与持仓汇总,不触发新的采样 —— 徽标是常驻的,若它自己打一遍上游,等于把"看一眼侧栏"变成取数压力。 ⚠ 护盘等级不进这个徽标:v0.25.0 曾有一条"护盘 ≥「疑似护盘」就显示「护」"的优先分支,它顶掉当日涨跌幅;2026-10-16 按用户要求删除(同一个 18px 的位置放两种语义,用户既分不清这个字是"我的"还是"市场的",也分不清它为什么变)。护盘等级有自己的位置(护盘面板 +tradewatcher_rescue工具);要放回侧栏必须另立入口。回归防线在src/client/badgeView.test.ts(徽标改成「护」或任何非涨跌幅文本 ⇒ 断言必败) - 卡片五态色点与列表头汇总:见「数据出处与状态」
- 降级态一律显式:护盘暂停时分数位显示
—而非上次的分数;数据来自备用源/上次成功结果时行上标source与「上次数据」;上游熔断时状态条简述 + tooltip 明细(逐主机剩余时间、最后错误),并说明哪种因子"等几分钟能补齐"、哪种"该源根本没有,不是等的问题" - 本会话未采样 ≠ 采样缺口:收盘后重启用持久化的上次快照并红标「上次数据」,
gap只反映当日真实失败
备份与迁移
持仓页「备份」按钮:导出把 watch/positions/ledger/prefs 打包成一份带 schemaVersion 与校验和的 JSON;导入是两段式 —— 选文件 → 预览(校验 + 覆盖前后差异)→ 确认覆盖。覆盖前宿主把原文件写成 .bak,导入后立即重读内存(不需要重启)。
校验不通过一律拒绝并逐条给原因:非对象、缺 schemaVersion、版本高于插件支持、缺文件、流水条目无法解析、引用不存在的持仓/分组 id、重放出现负持仓、校验和不匹配。最后一条是硬约束:宁可不导入,也不导入一份算不平的账 —— 一旦写进去,之后所有成本、盈亏、已实现都建立在一个错的账本上,而用户没有任何手段看出"从哪一天开始不对"。
大盘云图
只保留第三方站点(自绘云图已于 v0.62.0 删除,见 CHANGELOG.md 的对应条目):
- 第三方站点:内嵌 iframe 到第三方热力图站点(地址见
src/client/CloudMap.tsx的CLOUD_MAP_URL;可重新加载/新窗口打开)。信息量更全(钻取、K 线、复盘),但面积与颜色的口径由站点决定,本插件无法保证
本插件不抓取该站内容,iframe 只做展示与降级说明。
iframe 有三态降级:加载中 / 已加载 / 超时(8 秒)。超时显示占位(域名 + 最后成功加载时间 + 一句"本插件无法区分是本机网络还是该站点的问题")并给一次「重试」(重建 iframe 并重新计时);最后成功时间记在 localStorage,跨会话可见;状态徽标常驻显示当前处于哪一态。
护盘信号(A股大盘页内)
识别「符合国家队历史行为模式的宽基 ETF 护盘行为」。常驻状态条(等级/评分/归因)+ 六通道标的卡 + 因子明细表(含贡献度列)+ 分时量能图 + 今日信号时间线与近 60 日历史回看。信号达「疑似护盘」时面板自动展开。
- 监测通道:默认核心 6 只宽基(沪深300ETF华泰柏瑞 510300 / 上证50ETF华夏 510050 / 中证500ETF南方 510500 / 中证1000ETF华夏 512100 / 科创50ETF华夏 588000 / 创业板ETF易方达 159915),可在设置里追加 510310 / 510330 / 159919 / 588080;共振按指数去重
- 六因子加权:量能 0.22 / 超大单 0.26 / 脉冲 0.18 / 持续性 0.12 / 量价背离 0.14 / 共振 0.08,乘时点系数(早盘 0.4 → 尾盘 1.1)得 0–100 评分;等级
≥75 强护盘信号/55–74 疑似护盘/35–54 资金异动/<35 平静 - 因子语义:F2 以核心通道(沪深300/上证50)为主,仅外围通道净流入时打 0.7 折,核心通道超大单净流出达成交额 −15% 时封顶为「资金异动」;F3 区分盘中脉冲(14:30 前,打 0.6 折)与尾盘突袭(满分权重),命中线为该时段 P90;F4 用「时间 + 幅度」(最近 5 分钟超大单净增 ÷ 该窗口成交额,锚点 5%/12%/25%);F6 共振列出标的名并分层,核心 0 只时不得给「强护盘信号」
- 阈值来源全部标注:量能倍数用历史分位数标定(腾讯日K 500 根 × 6 只 = 2886 样本,P75/P90/P95 = 1.19/1.57/1.92x,对应 40/70/100 分);日内进度曲线用新浪 5 分钟线 126 个交易日标定(10:00 已占全天 29.6%、11:30 60.9%、14:30 86.0%),因此「同时点量能倍数」天然按时间进度归一;超大单强度因免费源已无日频资金流历史暂用经验锚点,host 采样器自建样本满 20 个交易日后自动改用自建分位数(面板显示当前来源与样本天数)
- 防误报约束(每条都写进归因,不做静默降级):指数明显上涨时封顶(
+1.0%以上最多「资金异动」;+0.3%以上不得为「强护盘」,因为放量上涨更可能是追涨);「强护盘」要求超大单强度达高门槛;无有效超大单且无脉冲时最多「资金异动」 - 采样:host 侧常驻采样器,仅工作日
09:25–11:35 / 12:55–15:05活跃,常态 30s、尾盘(默认 14:30 起)自动切 15s(可在面板设置里改),每次采样 1 个批量请求(约 2 请求/分钟),页面关闭也持续记录 - 非活跃时段:分数位显示
—(不把上次的分数留在那里假装是当前分),状态行采样暂停 · 下次 10-09 09:25(已收盘)(跨周末指向下周一);暂停时段一律不出现「采样缺口」字样——那时的缺口不是正在发生的事 - 数据:
rescue-log.json(60 天滚动)保存每日信号事件、5 分钟抽样、20 日均额基准、自建样本与出分日志(各因子原始值 + 当次生效阈值,粒度 5 分钟刻度 + 等级变化);⚠ 免费源无 ETF 资金流历史,历史回看只能从本模块上线日起算
交易日历维护(每月一次,5 分钟)
护盘模块按交易日历判断"今天是否交易":休市日不采样、不出等级、不写当日分(否则假日的"量能平稳"会被 算成一份平静的当日分 —— 那就是假信号)。判定优先级:外部日历文件 > 内置假日表 > 周末规则。
- 文件位置(用户自己的状态目录,升级不会覆盖):
~/.dsh/dsh-tradewatcher/trading-calendar.json - 格式:按月列出休市日(只列公告里的法定休市,周末不用写 —— 周末由星期判定)。
没有法定休市的月份写
"2026-11": []即可 —— 这条空数组同时表示"本月已覆盖"(缺键 = 未覆盖 ⇒ 会告警并退回内置表)。 为什么列休市日而不是列交易日:一个月里法定休市只有 0–5 天,交易日有 18–23 天,前者写得更少、更不容易抄错。 - 样例(2026 年 10 月,来源:上交所《关于上海证券交易所2026年部分节假日休市安排的通知》上证公告〔2025〕45号):
{
"v": 1,
"source": "上交所 2026 年部分节假日休市安排",
"updatedAt": "2026-10-08",
"months": {
"2026-10": ["2026-10-01", "2026-10-02", "2026-10-05", "2026-10-06", "2026-10-07"],
"2026-11": []
}
}
- 数据从哪来:交易所每年 12 月发布次年安排(上交所「休市安排」页 https://www.sse.com.cn/disclosure/dealinstruc/closed/), 每月初扫一眼该页有没有新的节假日公告即可(年中通常只有"中秋/国庆"这类补充公告)。
- 改完自查:
npm run calendar:check(只读)—— 打印文件路径、是否覆盖当月、本月休市日、被忽略的周末条目、 损坏文件的隔离去向,以及未来 10 天的交易日判定。也可在护盘面板上看:未覆盖当月时面板与 agent 工具输出 都会显示⚠ 交易日历未更新到本月(YYYY-MM)…。 - 文件写坏了怎么办:schema 不合法(版本不对、日期格式错、日期不在所属月份、重复)或 JSON 解析失败时,
文件会被改名隔离成
trading-calendar.json.invalid-<时间戳>(旁边留一份.reason.txt说明原因), 并退回内置假日表;面板与工具输出都会显示这次隔离。不会静默按空表跑。 - 失败方向:日历过期的后果只是多采一天(把假期当交易日),不会把真交易日误判成休市 —— 后者会漏掉 真实信号,是更糟的失败。所以判定顺序把"确定性的日历"放在"上游行情推断"之前。
底部位置 / 形态 / 概率(与护盘并列,不进入护盘评分)
回答"当前位置是否具备底部特征",避免把两件事混成一个数:
- 位置:距 60/250 日最高回撤、近 60 日收盘分位、距 60 日最低、连续下跌天数、是否创 60 日新低 → 位置分(0–100,低位高分)
- 日内形态:现价相对当日最低的回升幅度、下影线占振幅比例、当日最低点出现的时刻、是否"跌破前 20 日低后收回"、是否"创 60 日新低后收回" → 形态分
- 底部概率(历史频率):把当前状态与历史同类日类比(
近 60 日收盘分位 ≤ 25% 且 当日量能 ≥ 前 20 日均量 × 1),统计其后 5 日内最高价达到当日收盘 ×(1+目标) 的频率,同时给出无条件基线与样本量,并附 Wilson 95% 区间;样本N < 30标「样本少」 - 诚实边界:① 概率是历史同类情形的频率,不是预测,必须与 N 和基线一起读;② 日内形态不进入概率(没有可回算的历史分钟数据,混进去就是伪精确),它只作实时修正参考;③ 位置低 ≠ 见底,护盘也可能发生在下跌半程
日历
财经日历模块。月历网格按重要性三档语义色标记(高 / 中 / 低):颜色是独立语义色阶(--tw-imp-1/2/3),不复用涨跌色、不随红涨绿跌翻转,隐身视图下统一灰化。格子里只显示事件名,点日期弹出当天事件详情卡(公布时刻、类别、重要度、来源、关联标的、备注),支持手动事件的增/删/改与自动事件的隐藏。
- 自动同步(东财数据中心,6 小时缓存):新股申购/上市日历(全市场);自选 + 持仓标的的财报预约披露日期、分红除权除息/股权登记日;国际/国内宏观(东财 H5 财经日历,覆盖窗口过去 7 天 ~ 未来 92 天,分页 50 条/页、4 并发,约 1–2 秒),含美联储议息会议、联邦基金利率目标、各国央行利率决议、非农、失业率、PCE、PPI、GDP、ISM、零售、初请失业金,以及中国 CPI/PPI/社融/进出口等;每条带北京时间公布时刻(00:00 视为未定时刻不显示)
- 归类与重要度由名称启发式推导(数据源本身不含重要度字段):国家前缀 → 中国/中国香港/中国台湾/中国澳门 归国内宏观,其余核心经济体归国际宏观,无前缀的会议/行业事件归其他;非核心经济体(泰国、土耳其)不入库。重要度:议息/利率决议/联邦基金利率/LPR/非农/CPI/PCE/GDP/两会/关税 → 高;PPI/PMI/零售/社融/失业金/库存等 → 中;其余 → 低(港口/保税区高频库存分项降为低)
- 改期留痕与勾稽:上游会悄悄改期,因此按
autoKey对比上一条,日期/结束日/标题变化即记from → to(最多 5 条),事件卡标可能变更 ×N,悬停给出历次变更的时刻与前后值(手动事件不受影响);格点与事件卡上 实心 ●=有持仓 / 空心 ○=仅自选 / 无标记=无关(用字形而非颜色——颜色已被重要度占用),当天事件列表默认按勾稽排序(持仓 → 自选 → 其他,组内按重要度与时刻) - 手动维护:源里没有的事件(美国 CPI(BLS)不在东财免费源内)、未上市公司 IPO 与上市日期、自定义提醒——可在页面新增,也可以直接让会话里的 agent 用
tradewatcher_calendar_add写入
视图与隐私
- 三档视图(
Alt+M轮换,持久化):完整(默认)/ 紧凑(隐藏说明文字与脚注)/ 隐身(金额模糊为¥••••、盈亏保留正负号、涨跌色转灰阶) - 金额遮罩做在格式化出口内部,因此"新增一处显示金额忘了判断"不会漏出真实数字;只影响显示,取数、告警、agent 工具返回完全不受影响。价格与点位不属于"金额",照常显示
- 动效纪律(v0.87.0,参考竞品做法后自定):常驻/循环动画只动
transform/opacity;关闭态display:none(不留透明层吃点击);面板不可见或页面切后台 ⇒ 根节点data-idle=true暂停全部动画;尊重prefers-reduced-motion。细则与阈值见docs/DESIGN-UIUX-ADOPT.md - 截图 / 录屏保护(行情条右侧「▣」):面板不透明度 35%–100%(只作用于本插件面板);数字模糊(把价格/涨跌/盈亏/成交额糊住,鼠标移到某一行或某张卡上时该行临时显形)。模糊目标是"所有使用
--tw-mono的数字面"清单,界面上写明边界:只覆盖本插件渲染的数字,整屏不可读请配合隐身视图
宽屏布局
面板在宽屏下用宽度装更多内容而不是把内容拉长:行情条是自动填充网格(宽屏多列、窄屏换行,没有内容被藏进横向滚动);自选与持仓列表在 ≥1080px 时铺成多列(按列竖向填充,名次自上而下读是 1/2/3,同高度约两倍条目;窗口变窄自动退回单列);提示文案限宽 96ch。
安装
前置:插件按 dsh 0.2.x 的客户端 Slots 契约注册面板(ctx.slots.inject('sidebar.panellist' | 'main', …)),不依赖任何第三方侧边栏插件;宿主侧只需 webServer + webRuntime 两个服务(由 dsh web 提供)。旧版 ctx.betterSidebar 已在 0.2 移除。
# 1) 构建(lib/index.js + lib/client.js)
cd dsh-tradewatcher && npm install && npm run build # 或 npm run check(含类型检查/测试/自检)
# 2) 注册进 web profile(默认 ~/.dsh/profiles/web,可用 DSH_PROFILE_DIR 覆盖)
bash scripts/install.sh
# 3) 重启 dsh web 进程(必须:宿主半区改动只有重启后才生效)
# 重启后点左侧栏「盯盘」图标(走势线)打开主面板
卸载:bash scripts/uninstall.sh,然后同样重启 web。
插件为「双面」结构:主机半区(quote 中继、文件存储、
/tradewatcher/*路由、agent 工具)+ 浏览器半区(Tab UI),通过dsh.bundle.patch(cordis.patch.yml的 insert 行)随 bundle 自动挂载,无需手改 profile 的 cordis 配置。
快速上手
- 左侧栏点「盯盘」图标打开主面板;顶部三行行情条是 A股指数 / 国际市场 / 大宗商品,点任意卡片打开右侧详情抽屉(分时/五日/日K/周K/月K/年K)。
- 「自选」里新建分组 →
添加证券(可搜代码或名称)→ 行上可移除/移动分组;排序开关在面板右上。 - 「持仓」里新建分组 →
+持仓添加标的 → 行上「买/卖」录入流水(数量/价格/费用)→ 分组与总览自动核算。数量与成本不落盘,全部由流水推导,所以请始终用买/卖/调整录入,而不是直接改数字。 - 想离线复盘或让会话分析:数据都在
~/.dsh/dsh-tradewatcher/*.json(明文),也可以直接用tradewatcher_portfolio等模型工具问。
数据与接口
数据文件(DSH_HOME 默认 ~/.dsh)
| 文件 | 内容 |
|---|---|
~/.dsh/dsh-tradewatcher/watch.json |
自选:{ groups: [{id,name,order,archived?,note?}], items: [{id,groupId,secid,name,note?,createdAt}] } |
~/.dsh/dsh-tradewatcher/positions.json |
持仓:{ groups:[…], items:[{id,groupId,secid,name,note?,createdAt}] } —— 数量/成本不落盘,全部由流水推导 |
~/.dsh/dsh-tradewatcher/ledger.json |
逐笔流水/审计(append-only):{v, entries:[{id,ts,actor,verb,groupId?,posId?,secid?,qty?,price?,fee?,note?,meta?}]} |
~/.dsh/dsh-tradewatcher/prefs.json |
主题/刷新间隔/配色/排序/视图/折算口径/截图保护等偏好 |
~/.dsh/dsh-tradewatcher/quotes-lkg.json |
行情 last-known-good 快照(每只证券最近一次有效读数,进程重启后仍可兜底,非账本数据) |
~/.dsh/dsh-tradewatcher/trends-lkg.json |
分时序列 last-known-good(上游失败时返回上一份好图并标 staleAt) |
~/.dsh/dsh-tradewatcher/trends/<secid>/<YYYY-MM-DD>.json |
按日归档的分时(本地拼接五日的原料)。保留最近 12 个交易日;单日 >512KB 或单标的总量 >8MB 时不写并在界面提示「本次未归档」;清理只删本目录里的日期文件。可在设置里用 trendArchive 关闭 |
~/.dsh/dsh-tradewatcher/board-lkg.json |
板块/资金/ETF 排行的 last-known-good(全部上游不可用时显示上次成功结果并标注时间) |
~/.dsh/dsh-tradewatcher/klines/<secid>_<周期>_<口径>.json |
K 线本地缓存:首次查看一次性拉全量(日 800 / 周 400 / 月 240 根),之后每次只增量拉最新几根并合并;上游不可用时直接回退缓存。复权口径进文件名(_0/_1/_2),不同口径互不顶替 |
~/.dsh/dsh-tradewatcher/breadth.json |
涨跌家数每日快照:收盘后(15:05 起,15:30 后定稿)记录当日涨/跌/平家数与两市成交额,保留 60 日窗口 + 30 天余量 |
~/.dsh/dsh-tradewatcher/calendar.json |
财经日历事件库(自动同步 + 手动维护,含改期留痕) |
~/.dsh/dsh-tradewatcher/trading-calendar.json |
交易日历缓存(npm run calendar:check 与跨市场休市判断用;按年缓存,缺失时按规则推算并标注) |
~/.dsh/dsh-tradewatcher/prism-settings.json |
棱镜(第 9 行):研判时点(HH:MM,默认 09:35 / 11:20 / 13:10 / 14:40,可增删)+ 当日已触发水位;写入走 updateJsonLocked,读不到不覆写 |
~/.dsh/dsh-tradewatcher/prism-skeleton.md |
棱镜的研判骨架(页面可编辑的多行文本,≤20 000 字);首次打开写默认值 |
~/.dsh/dsh-tradewatcher/prism-news.json |
金十快讯(24h):{status, items};首次回填 ≤10 页/批、页间 ≥3s,之后按 lastSeenId 增量;只保留 24 小时 |
~/.dsh/dsh-tradewatcher/prism-runs.json |
棱镜研判结果(4 路分析 + 1 次汇总,保留最近 20 次);失败留痕、不自动重试 |
~/.dsh/dsh-tradewatcher/prism-account.json |
棱镜模拟盘账户:资金上限(初始 150 万,可入金)、现金、持仓与成本、已实现盈亏、复制留痕;复制按真实成本价(只读真实持仓) |
~/.dsh/dsh-tradewatcher/prism-orders.json |
棱镜挂单/成交:{orders, fills};挂单当日有效,跨日失效;撤销/不可成交(T+1/涨跌停/停牌/整手)都留原因 |
~/.dsh/dsh-tradewatcher/prism-equity.json |
棱镜每日净值曲线(一天一点):{points:[{day,equity,cash,positionsValue,realized,unrealized,at}]} |
~/.dsh/dsh-tradewatcher/fupan.json |
复盘笔记(第 8 行 fupan):{ v:1, notes:[{id,date,text,pinnedTo?,source,at}] },at = 最后一次写入时刻。有界:单条正文 ≤4000 字、≤2000 条、序列化后 ≤512 KB;条数/体积超限时按「最久未更新」丢弃并在回包 dropped 段逐条留痕(置顶的最后才丢),单条超长直接 400 不截断。读不到(权限/磁盘)≠ 没有笔记 ⇒ 本次启动拒绝写回并如实披露;坏 JSON 先隔离保留原字节再从容开始 |
~/.dsh/dsh-tradewatcher/global-flow.json |
工具箱「全球资金流蛋糕模型」的每日份额快照:一天一条、保留 90 天(一天 ≈ 0.9KB);成分一变,旧快照整份作废并留痕,不与新成分混用 |
~/.dsh/dsh-tradewatcher/macro-log.json |
工具箱「全球资金流蛋糕模型」的宏观两档自采样:每个交易日一条(广义美元指数 / 美国 10Y 收益率)+ 每源诊断留痕(diag:本次各源是否可用、值、原因 —— 真机排障的现场证据),保留 120 天。用途:两个源都没有可无歧义解读的"昨收"字段 ⇒ 「较上日」与「5 日变化」只由本插件自采样算,绝不用来源不明的字段充当变化量;不足 ⇒ 界面显示「未取到」+ 原因,不写 0 |
~/.dsh/dsh-tradewatcher/market-extras.json |
大宗商品/债券等扩展行情缓存(last-known-good;上游失败时回退上次成功结果并标注时间) |
~/.dsh/dsh-tradewatcher/leverage.json |
工具箱「杠杆温度计」的两融历史:{ v:1, lastFetchDay, lastAttemptAt, cross, rows:[{d,rzye,rqye,ltsz,rzyezb,rzjme}] }(单位:元 / %)。每天最多取一次上游(两融 T+1,当天数据不会中途变);首次 6 页回填 4018 个交易日(2010-03-31 起,≈610 KB),之后每天追加一行;上限 4500 行 / 1 MB(丢最旧的并留痕)。读不到 ≠ 没有(坏 JSON 先隔离保留原字节,不覆写);cross 只内部记录深交所单市与合计的量级校验,不进回包 |
~/.dsh/dsh-tradewatcher/rescue-log.json |
护盘信号:每日信号事件、5 分钟抽样、20 日均额基准、自建样本、出分日志(60 天滚动) |
~/.dsh/dsh-tradewatcher/macro-archive.json |
宏观后台归档(npm run macro:archive):BIS 等官方长期序列(单次约 25 秒 ⇒ 只允许后台跑,不进交互路径),带抓取时间与来源;同样走 robots.txt 门禁与「每主机 ≤5 次/小时」预算 |
~/.dsh/dsh-tradewatcher/write-log.json |
写操作日志(目前只覆盖插件代写的日历事件新增:tradewatcher_calendar_add 这类;持仓/流水的写入由侧边栏自己维护、不经这里):每条记 by(会话 id)/反向操作/是否已撤销。撤销不删历史(撤销本身也留痕),保留最近 500 条。真机上若从未用工具写过日历,这个文件不会存在(不是漏了) |
~/.dsh/dsh-tradewatcher/*.json.bak |
导入备份前自动留下的原文件副本,用于人工回滚 |
verb:buy买入 /sell卖出 /adjust调整 /add新建持仓 /remove移除持仓 /gcreate新建分组 /grename改名 /gdelete归档 /grestore还原 /gmove移动或编辑 /pnote备注secid为东财代码,格式市场.代码:1.600519(沪A)、1.688825(科创板)、0.300750(深A)、1.510300(沪ETF)、116.02155/116.00700(港股)、100.KOSPI200(全球指数)、114.lhm(生猪主连)等
会话内分析(双通道)
- 模型工具(只读,重启后对所有会话可见)
tradewatcher_portfolio— 分组汇总 + 每只持仓(数量/成本/现价/市值/浮盈/当日/已实现/可用数量/费用)tradewatcher_ledger— 逐笔流水,可按posId/groupId过滤(追溯完整交易过程)tradewatcher_watchlist/tradewatcher_quotes/tradewatcher_search— 自选、实时行情、证券搜索(cn/intl/commodity/bond/all预设组)tradewatcher_calendar/tradewatcher_calendar_add— 财经日历读取与手动事件写入(唯一的写工具,带节流与反向操作登记)tradewatcher_undo— 撤销本插件的写入(按id或last=N;listOnly=true只列出可撤销项)tradewatcher_rescue— 护盘信号(六因子 + 贡献度 + 出分日志 + 采样窗口)tradewatcher_ytd— 年初至今(前复权口径,含基准说明与缺失明细)- 每个读数都带数据出处:
asOf/stale/source/missing[](见「数据出处与状态」) - 系统提示词已注册引导段落(插件在会话工具列表即显形,如出现工具名即说明挂载成功)
- 明文 JSON 文件:上述数据文件路径固定、格式如上,任何会话可用文件工具直接读取做离线分析/回溯校验
HTTP 路由(同源,浏览器信任围栏保护,POST 校验 Origin)
GET /tradewatcher/health|badge|quotes?ids=|trend?secid=|kline?secid=|detail?secid=|auction?secid=|suggest?q=|board?scope=&sort=&pn=&pz=|anomaly?ids=|ytd?ids=|breadth、GET|POST /tradewatcher/watch|portfolio|prefs|calendar、GET /tradewatcher/ledger、GET /tradewatcher/trades?secid=、GET /tradewatcher/industry|industries、GET|POST /tradewatcher/backup(导出/导入备份;POST body {mode:'preview'|'apply', bundle})
/tradewatcher/kline的fqt:0=不复权1=前复权(默认)2=后复权;回包kline.fqt是实际生效口径(指数/期货恒为 0 并置fqSupported:false),kline.asOf/barOpen供口径条显示"数据截至几点、当根是否已收盘"/tradewatcher/auction?secid=:竞价四项(价/量/占比/溢价)+ 09:15–09:25 采样序列。唯一一条不依赖东财的详情类出口:只读本地auction/<secid>.json与腾讯 m1(同包qt里的昨收),东财熔断也照给;永远 200,不可用时auction.state='unavailable'+missing[](沪深transient/ 非沪深no-source)/tradewatcher/detail除detail外同时返回顶层auction(与/auction同一份);detail.auction保留只为兼容旧客户端/tradewatcher/badge只读同一份 rescue 快照与持仓汇总,不触发新采样/tradewatcher/anomaly用 K 线本地缓存 + 行情涨跌幅判定自选异动,不额外打上游
口径规范
以下为当前生效的规则。每条改动的来龙去脉与实测依据见 CHANGELOG.md。
时间口径
全插件的时间标签只有一套口径:Asia/Shanghai(src/host/time.ts)。hhmmOf / dayOf / weekdayOf / calToday / shanghaiDayStart 全部来自同一实现,并在 4 个时区(Asia/Shanghai、UTC、America/New_York、Australia/Sydney)下断言一致;npm test 会把这套断言跑两遍(宿主 TZ 与 TZ=UTC),因为只有显式切 TZ 才锁得住时区回归。
注意 inTradingWindow 里的星期也必须按北京时间——北京时间周一 10:00 在 TZ=America/New_York 下是周日 22:00,用宿主星期会把真实交易日判成休市。收盘后是否"定稿"同样按北京时间 15:05 判定(不用浏览器本地时区,否则容器里的 UTC 浏览器会把收盘后判成盘中)。
排序
选择存进 prefs.json,同键再点一次切换升降序。两条硬约定(src/client/sort.ts,8 组断言锁死):
- 无效值恒沉底:无行情、无市值、无盈亏、
NaN/Infinity一律排在最后 —— 升序也不许跑到最前(null参与数值比较会退化成 0,正是这类功能最常见的错误)。 - 稳定且可预期:同值条目保持原顺序,排序不会在每次行情轮询后重新洗牌。
持仓的盈亏键跟随当前成本口径(摊薄口径排「持仓盈亏」,均价口径排「浮动盈亏」)——排序用的数与表格里显示的那一列必须是同一个。
市值来源:东财 f20(全部标的)→ 腾讯 f45(沪/深/港股,原始单位亿元,已 ×1e8 转元);新浪备用源不提供市值(int_/nf_/hf_ 行情里没有该字段),因此国际指数、商品、期货经新浪兜底时没有市值 → 排序时沉底,界面上也不显示这一栏(不编造)。
watchSort / portSort 走主机侧白名单校验(非法键、非布尔方向抛错,界面能提示出来);明文文件被手改成非法值时装载即回退默认(启动不因一个坏偏好失败)。
secid 与大小写
规则:secid 在存储、路由、工具入参、上游请求里一律保留原样;只有"判断是否同一标的"时才做大小写无关比较(secidKey())。
为什么必须如此:113.rbm / 114.lhm 这类商品/期货后缀区分大小写,而三家的映射关系完全不同 —— 东财用 f13.f12 回包(商品是小写)、腾讯映射沪/深/港(数字代码)、新浪 nf_*/hf_* 以小写 secid 为键。任何一处把 secid 改写成大写,都会让"上游请求"与"备用源映射"双双落空。上游映射与回包均大小写不敏感,因此历史写入的大写数据自动可用,不需要迁移脚本。
配套的诚实性:/quotes 返回 missing[](请求了但没有任何源给出价格的标的,界面显示「暂无可用行情源」,与「暂无行情」区分)与 requested/truncated(超过 160 项上限时如实回报,不静默截断);搜索结果标注 hasFallback(仅东财源的标的必然更易空行,提前说清而不是让用户以为是 bug)。
行情取数、缓存与新鲜度
- 逐标的缓存槽:
/quotes(行情条 + 自选 + 持仓)与/portfolio(仅持仓)标的互相包含,逐标的槽让持仓那几只不再重复请求(实测一拍 12 次上游:东财 8 + 腾讯 1 + 新浪 3;东财熔断后稳态 0 次,持仓页子集请求 0 次) - 新鲜度窗口 =
refreshSec(夹在 5–60 秒),改动设置立即生效。代价如实说:东财健康时上游请求约 6 次/分(每次一个批量请求覆盖全部标的);想更省就把刷新间隔调大,界面文案与数据新鲜度会一致 - last-known-good:接口瞬时缺价/漏返时按字段级回填,快照持久化到
quotes-lkg.json跨重启生效;单批缺码先换备机补拉;客户端保留已拉取旧值、空样本不回写——价格不会在"数字 / —"间闪烁;停牌等真实无价标的仍显示 — - 休市定稿零回源(
isSettledOffline):休市且本地缓存更新时间晚于最近一个工作日 15:05 时,K 线与分时直接复用本地并标cached: true(定稿是完整数据,只是不必再问上游;与stale语义区分)。此时客户端 TTL 也放长(分时 5min / 五日 10min / K 线 1h),收盘后反复开关面板一个请求都不发。盘中永不成立 - 客户端缓存(
chartCache.ts):同标的同周期在 TTL 内零 HTTP,同键并发请求合并为一个(悬浮卡与抽屉同时要同一份数据时只发一次),失败保留上次成功值并如实标注。TTL:分时 30s / 五日 120s / K 线 600s
数据出处与状态
读工具统一契约:每个读数返回
asOf(数据时刻)/stale(降级复用)/source(em·tencent·sina·lkg·local·mixed·none)/missing[](缺失项 + 原因)。asOf是数据被观测到的时刻,不是响应生成时刻缺失分两类:
no-source(上游根本没有这份数据,重试无用)与transient(本次失败,稍后重试可能恢复)—— 两者对应的下一步动作完全不同,因此不许含糊卡片五态(6px 色点 + tooltip 给出源与观测时刻):
色点 状态 判据 绿 实时 有价、非兜底、观测时刻在刷新间隔内 黄 延迟 兜底值( source='lkg')或观测时刻超过刷新间隔 3 倍灰 定稿复用 整批 cached(非交易时段 + 无兜底行)灰 未取到 本轮没拿到这一行(该标的本次没请求,或首拍还没回来)——等下一拍,不是"三源都没有" 红 无行情源 三源都没有可用价格(服务端给出的结论) 列表头汇总
12/23 实时 · 9 延迟 · 2 未取到,五个数字与逐卡判定共用同一个函数,因此必然加得上;分母是屏幕上的卡片数(用户隐藏的卡不请求、也不计入,隐藏张数写在徽标悬停里)。未开盘(定稿)时不显示绿色;灰色与黄色语义不同:定稿是"数据已确定、不需要回源",延迟是"上游这次没给,退回旧值"错误码分级:400 请求本身有问题 · 413 请求体过大 · 415 非
application/json· 503 上游不可用(熔断中/连接被关闭/超时/上游 4xx-5xx/非 JSON,附retry-after)· 500 未归类(本插件 bug)。不把"上游被限流"报成"你的请求写错了"熔断按主机隔离:行情主机被限流不影响搜索/日历(不同主机)。
/rescue与/health返回breakerSummary(逐主机明细 +minutesLeft最早可重试 + 最近失败原因);展示的剩余时间取最早恢复的那台(收盘后改为"下次开盘自动重试")缺失不许用 0 代替:板块涨幅、费用占比、涨跌幅等任何"取不到"的值显示
—并给出原因,不显示 0 —— 0 会被读成"没涨没跌"/"没有手续费",那是错的信息降级不许假装实时:降级复用的数据标
stale;"上次成功结果"与"本次计算"分开标注;定稿缓存标明"未回源";失败时刻与数据时刻分列(失败不改写ts)
成本与盈亏口径
- 成本口径:买入按移动加权(费用计入摊薄成本);卖出确认已实现盈亏(费用于卖出时扣除);当日盈亏 = 隔夜
(现价−昨收)×数量+ 当日每笔买卖差额 − 当日费用;昨收取行情接口昨收(期货主连按接口口径的昨结基准,见「已知边界」) - 成本口径可切换(偏好存
prefs.json):- 摊薄口径(默认,与多数券商 App 一致):摊薄成本 = (累计买入含费 − 累计卖出净额) ÷ 剩余数量;持仓盈亏 = (现价 − 摊薄成本) × 数量,已实现盈亏已折进剩余持仓成本;卖出亏损会抬高摊薄成本,卖出盈利会压低(可能为负)
- 均价口径:买入移动加权均价(卖出不改成本);浮动盈亏单独显示,已实现盈亏另列
- 两者恒等关系:摊薄持仓盈亏 = 均价浮动盈亏 + 累计已实现(
selftest断言"逐项差值之和 == 总额差值")
- 盈亏率:浮动盈亏率 = 浮动盈亏 ÷(摊薄成本×数量);当日盈亏率 = 当日盈亏 ÷(期初数量×昨收 + 当日买入金额);无法估值(无昨收)时显示 —
- 可用(可卖)数量:A股 T+1(今日买入部分当日不可卖),ETF/LOF、港股、美股 T+0;判定集中在
isT0Secid()一处(沪深场内基金代码段5xxxxx/15–19xxxx),拿不准的一律按 T+1 —— 保守方向是"绝不说今天买的能卖" - 累计费用给绝对金额同时给占累计成交额的百分比(没有参照物的手续费数看不出贵不贵);成交额为 0 时占比是
—而不是0 - 价格与成本保留 4 位小数(ETF/基金等低价标的 0.948 不会被四舍五入成 0.95)
折算口径(跨市场)
港/美股以原币种计价,两种档位:
- 不折算(默认):总额只含 A股,其余逐项列在
unpriced[](why:no-fx口径问题 /no-quote行情问题),界面给不含 N 项 ▼可下钻看原因 - 固定汇率:填
1 港元 = ? 人民币/1 美元 = ? 人民币(填了哪个币种就折哪个),折算后计入总额,fxRates回读实际生效汇率并显示在总额旁 —— "这个人民币数字按什么汇率算的"必须答得上来
两档之外没有第三种:按 1:1 悄悄加进去会让总额看起来完整、其实错了(错的口径比缺的口径更危险 —— 用户不会去质疑一个看起来正常的数)。汇率只接受 0.01–100,越界一律拒绝(把 7.15 填成 715 会得到差 100 倍的人民币市值)。
实时汇率暂不可用:汇率源尚未验证连通性与字段口径(中间价还是即期、符号怎么写都未确认)。宿主对 fxMode='live' 显式报错说明原因,而不是接受一个什么都不做的档位。
涨跌家数
优先用上游直接给的(指数的 f104/f105/f106),只有在它拿不到时才由本插件自行统计 —— 回包带 source 区分两者(em-index = 上游给的;em-clist = 本插件按沪深A股全量自行统计)。
自行统计的口径(回包 caliber 里写明,界面上也能读到):
- 范围:沪深A股全量,含科创板/创业板,不含北交所(
fs = m:0+t:6,m:0+t:80,m:1+t:2,m:1+t:23); - 判据:涨跌幅
> 0记上涨、= 0记平盘、< 0记下跌; - 数字时刻是"统计完成时刻",与上方行情条的数据时刻不是同一个时刻 —— 两处各自标注,不混用;
- 合理性护栏:三类之和必须等于总数、扫过的行数不得少于总数、三者全 0 而总数 > 0 一律按失败处理(宁可不给,也不给一个看起来正常但错的数);
- 不做涨停/跌停家数:它需要每个标的的涨跌停价(主板 ±10%、创业板/科创板 ±20%、ST ±5%、新股首日不设限……)并处理停牌与无昨收,按涨跌幅近似会在 ST/新股/科创上稳定地算错。
- 第三条链路(按涨跌幅降序的分页边界搜索)已实现但未启用:需要"按涨跌幅降序返回分页"的取数实现(
deps.sortedPctPage已留注入位)。
每天的快照(分位样本)只在家数与成交额都真实时记一条;缺的那天就缺,不补。
年初至今(YTD)
YTD =(现价 − 本年内第一个交易日收盘价)÷ 该收盘价 × 100%,序列用前复权(不复权序列在除权除息日会跳空下跌,那不是真实收益)。口径常量是单一来源(shared/model.ts 的 YTD_CALIBER),界面 tooltip 与 agent 工具引用同一句。
三处必须随结果说明的边界:
- 指数 / 期货 / 板块(
fqSupported=false):按原始价格(点位、合约价)计算,tooltip 写明"该标的不适用复权"。 - 本年内上市:序列里没有更早的交易日 → 基准是上市首日,标注为「上市首日至今,不是年初至今」(按年初读会高估)。
- 年内第一个交易日就是今天:口径要求的是收盘价,此时不给数(
missing[].why='no-source',今天重试无用),不拿未收盘价或上一年收盘顶替。
取数:宿主按日 memo 复用 em.fetchKline(磁盘缓存 + 增量 + 单飞),并发 ≤4、失败有冷却、不重试轰炸;单次上限 60 个标的,超出如实回报 truncated;缺失一律 — 并给原因,不用 0 顶替。
K 线与复权
- 默认前复权:前复权序列连续、最适合判断当前位置与相对成本(不复权会把除权跳空伪装成暴跌,所以不能当默认)。段控
前复权 / 后复权 / 不复权,口径常显在脚注 - 显示的是实际生效口径(用宿主回包的
fqt,不是用户请求的那个值)—— 错误的口径标注比没有标注更糟 - 按标的记忆:全局默认 + 每只标的一份选择,切周期不重置
- 适用性由宿主判定(
fqSupported):股票 / ETF / 基金 / 港美股可复权;指数(沪市1.000*、深市0.399*、100.*国际指数)、期货(113/114/115.*、101/112.*外盘商品)、板块(90.*)一律回落不复权,段控禁用并标「不适用」,tooltip 说明"按点位/合约价,没有除权除息"——禁用必须给理由,不能只灰掉 - 口径进缓存键:
klines/<secid>_<klt>_<fqt>.json,三个口径各自一份、各自增量。请求口径无缓存时宁可回源失败也不拿另一口径顶替 —— 否则图上会出现两套价格拼出来的假连续序列,而stale标注救不了它 - 指标与图同源:MA5/10/30/60、MACD 与 K 线共用同一份序列(不存在"画线复权、指标不复权");B/S 买卖点按当前口径对齐坐标系,tooltip 里仍是真实成交价
- 详情头不被改写:今开/昨收/最高/最低/成交量始终是真实成交价,脚注写明「(详情头为真实成交价)」,避免拿复权后的历史价去核对今日昨收
涨跌家数分位
大盘页在涨跌家数旁给出 上涨占比 62.1% · 近 60 日分位 78%。三条口径写死并随结果返回:
- 指标 = 上涨家数占比(
up ÷ up+down+even),不是涨跌比、也不是家数之差 - 基准 = 之前 N 个交易日(不含今天)—— 把今天算进基准是自我参照
N < 5不给分位(返回null,不是 0):5 天的"分位"是噪音
快照只在收盘后(15:05 起;15:30 后标记定稿、不再被后续读数的抖动覆盖)写入 breadth.json。盘中家数一直在变,记进去等于把某一时刻的横截面混进"每日定稿",几天后分位就不可解释了。数据源与大盘页同一处(上证/深证行情里的 f104/f105/f106),不另开链路。
自选异动
- 判据:量能倍数(同时点口径:今日量 ÷ (20 日均量 × 日内进度),阈值 2.5x,复用护盘标定的进度曲线)、价格异动(|涨跌幅| ≥ 5%)、量价共振(|涨跌幅| ≥ 3% 且量能 ≥ 2x —— 单条都不越线、组合越线)
- 为什么用同时点:早盘 10:00 的进度才 33%,拿全天均量直接比会得出"每天都缩量",反过来用全天口径比又会把早盘的正常量报成放量
- 样本 < 5 个交易日不判定,"没判定"(样本不足/非交易时段/日线里还没有今天这一根)与"判定过且无异常"严格分开显示 —— 前者不是结论,说成"正常"是错的信息
- 提醒策略:同一条目 30 分钟静默(记在 localStorage,刷新页面后不会把刚看过的再提醒一遍)。静默只压制提醒,不抹掉"它正在异动"这个事实 —— 队列里仍以"静默中"列出,行高亮也不消失。否则会出现"明明在放量、徽标却是 0",然后没人再信这个徽标
统计护栏
概率类结论(底部概率)一律给 Wilson 95% 区间:20 个样本的 80% 与 500 个样本的 80% 完全不是一回事,只给点估计会被读成"准确率"。样本 N < 30 加「样本少」标记。用 Wilson 而不是朴素正态近似——后者在 p̂ 接近 0/1 或 n 小时会给出越界区间(概率 100%、n=5 时上界 >100%)。注意 Wilson 是 score 区间,围绕调整后的估计对称,因此极端比例下 p̂ 可以落在区间外。
护盘信号的边界
识别的只是「符合国家队护盘行为模式的放量 + 超大单净流入」,不能证明买入方是汇金/国新/诚通(它们不披露日内成交),回答与展示都按概率性信号处理。超大单为东财按单笔金额的分类口径(非席位数据);ETF 天量含做市双边报价与套利盘,故始终用超大单净额与量价背离交叉验证。免费源已无 ETF 资金流历史(东财 fflow 仅当日 1 行、push2his 常限流),故 F2 初期为经验锚点、历史回看自上线日起。
子组件与开关
本插件在一个 bundle 内拆成九行(core / calendar / cloudmap / market / watchlist / portfolio / tools / fupan / prism),各自可独立开关:
停用某一行 = 该行的路由 404 + 工具从列表消失 + 侧栏 tab 消失(不灰显、不留空壳)。
行清单的唯一权威处是本包的 cordis.patch.yml(该文件的 - insert: 块,共九行:tradewatcher-core/-calendar/-watchlist/-portfolio/-market/-cloudmap/-tools(v0.71.0)/-fupan(v0.111.0)/-prism(v0.130.0),全部启用、每行 config: { row: … });
行归属与设计见 docs/DESIGN-SUBPLUGINS.md,落地与验证记录见 docs/AUDIT-IMPL35.md。
本节的行数与页签数、
cordis.patch.yml的行、src/index.ts的ROW_ROUTES、src/client/index.tsx的tabs五处由src/readmeCounts.test.ts锁死(改一处不改另一处 ⇒ 测试红)。
关闭某一行:不改本包,在 profile 层(~/.dsh/profiles/<profile>/cordis.patch.yml)按 id 覆盖即可 —— 只关日历就写:
- id: tradewatcher-calendar
disabled: true
改完 cordis.patch.yml(或 profile 层行开关)必须重启 dsh web:宿主行只在启动时装载,刷新页面不够。
core 行不要关 —— 行情条/详情图//tradewatcher/rows 等共享路由都挂在它上面。
agent 工具索引
本表由
src/host/tools.ts的defs决定;src/host/toolIndex.test.ts会锁住名字与数量(加了工具不改表就红)。 计数:12 个工具。
| 分类 | 名称 | 一句话用途 | 计数 |
|---|---|---|---|
| 行情 | quotes |
批量实时行情(预设 cn/intl/commodity/bond/all 或任意 secid) | 1 |
| 行情 | search |
在证券池里搜代码/名称 | 2 |
| 分析 | ytd |
年初至今涨跌幅(前复权序列算,带口径与样本) | 3 |
| 分析 | position |
52 周区间位置% / 距高低点%(前复权,窗口随结果返回) | 4 |
| 分析 | group_rank |
自选分组一屏排序 + 统计药丸 + 分档汇总 + 文本分位 | 5 |
| 分析 | rescue |
护盘信号(六因子)+信号的前瞻表现(forward,带基准与持有期) |
6 |
| 分析 | calendar |
财经日历(宏观/IPO/财报/分红) | 7 |
| 分析 | calendar_add |
往日历写一条手动事件 | 8 |
| 分析 | undo |
撤销本插件做过的写入 | 9 |
| 持仓 | portfolio |
持仓总览与盈亏(含组合构成、做T 配对、每股费用) | 10 |
| 持仓 | ledger |
逐笔流水(默认 500,超限如实报 truncated) | 11 |
| 持仓 | watchlist |
自选列表(分组结构) | 12 |
开发
逐轮审计与验收报告(含历史版本的过程记录)见
docs/archive/(AUDIT-*、COMPETITOR-NOTES.md、 各批实现自述AUDIT-IMPL*.md),口径以本 README 的「口径规范」为准; 未落地的设计稿(如DESIGN-DASHBOARD.md)顶部会写明"未落地",不要照它改代码。
开发原则:不冗余解释、不啰嗦(可执行版本)
写界面文案与提示时按这七条做,评审与验收也按这七条查:
- 常驻可见的说明性文本只留一行,且只写"影响什么 + 原因";长解释、复权口径、边界条件一律进
title与aria-label(读屏要读得到 —— 只挂在title上等于键盘/触屏拿不到)。 - 同一事实全局只留一处,优先级固定:行级 tooltip > 面板级 hint > 正文。同一个缺口不要在正文、面板、tooltip 各写一遍。
- 恒真 / 恒假句不渲染;空值不占位:缺失一律
—+ 原因,不用 0(0 会被读成真实值)。 - 状态词、时间格式、单位用词表统一:
未取到(本次失败)/上游无此数据(结构性)/数据时刻(不用"观测时刻/统计于/判定于")/HH:mm:ss(页面级)/MM-DD HH:mm(跨日历史)。
不可算:输入不足、算不出来(例如成本未录入、样本不足、账不平)。它与未取到(取数失败)是两种状态,不得合并。加载中:界面瞬时态(正在加载的转圈提示),不属于 R19 的那 10 个状态词;不要改成「进行中」。例外:窄容器(侧栏徽标的悬停
title,宽度只够一行)允许简写成数据 HH:mm:ss—— 那里放不下「数据时刻」四个字,规则与实现必须一致,故在此写明。(侧栏标签恒为「盯盘」,不带任何时刻后缀,见上文「状态可见性」。)
- 结构性限制不进红色告警 —— 红色只留给故障("该市场只有当日分时"是限制,不是故障)。
- 口径标签只写通用词(如
涨跌幅),具体口径进title/aria-label(如"相对当日开盘价 / 相对序列首点");可见文本里不堆括号解释。 - 新数字面必须登记进
styles.ts的两条data-blur=1清单(隐身档要糊上、悬停显形);涨跌着色一律走toneClass(tone, redUp)/dirClass(...),不许写死红绿。
来由:
docs/archive/AUDIT-COPY2.md的用词表 + v0.34–v0.37 的执行经验(同一缺口三处重复、口径写进正文导致标签变长、 结构性限制染红)。完整决策记录见docs/DECISIONS.md。
src/
index.ts # 主机半区入口(路由 + 工具注册)
host/em.ts # 东财行情中继(push2delay/push2/push2his 多主机回退 + TTL 缓存)
host/store.ts # 文件存储 + 校验 + append-only 流水(纯函数可测)
host/portfolio.ts # 流水重放 → 持仓/分组/总览核算(纯函数)
host/anomaly.ts # 自选异动判定(纯函数 judgeAnomaly + 批量 detectAnomalies)
host/breadth.ts # 涨跌家数快照与历史分位(纯函数 percentileOf)
host/backup.ts # 备份导出/校验(纯函数 makeBundle/verifyBundle/describeConflicts)
host/writeLog.ts # 写操作日志与撤销登记
host/rescue.ts # 护盘采样与评分(六因子 + 时点系数 + 出分日志)
host/routes.ts # /tradewatcher/* HTTP
host/tools.ts # tradewatcher_* agent 工具 + 提示词段落
host/selftest.ts # npm run selftest(核算断言 + 实时接口冒烟,网络项 soft)
client/index.tsx # 浏览器半区:ctx.slots 注册(sidebar.panellist 图标 + main 面板)+ 装配
client/*.tsx # TopBar/自选/持仓/大盘/云图 + SVG 分时图 + 悬浮卡
host/ytd.ts # 年初至今(前复权基准 + 按日 memo + 并发有界的批量计算)
client/ytdView.ts # YTD 展示纯函数(文案/口径 tooltip/缺失摘要)
shared/model.ts # 两端共享类型 + 固定代码
shared/stats.ts # Wilson 区间(纯函数)
- 构建:
npm run build(esbuild →lib/index.jsESM、lib/client.jsCJS +__ModuleLoader__工厂) - 门禁:
npm run check=typecheck→test→selftest→buildtypecheck:tsc --noEmit;build.mjs构建前会先跑它,失败即中断(可用TW_SKIP_TYPECHECK=1临时跳过);类型错误退出码 1、tsc未能启动(依赖没装)退出码 2、被信号杀死退出码 3 —— 不把"没装 typescript"报成"类型检查不通过"test:node --test --experimental-strip-types,覆盖客户端纯函数(指标/格式化/排序/状态判定/时区/区间估计/布局)与路由层(src/host/routes.test.ts:请求项全覆盖断言、大小写回归、400/413/415/503 语义、信任围栏、截断回报;该文件把DSH_HOME指向临时目录,不读不写用户真实缓存)selftest:host 侧集成自测(纯函数断言为硬失败,真实网络探针为 soft);同样把DSH_HOME指向临时目录build:产物校验 —— 客户端必须包含一组必须存在的 UI 文案片段(含 esbuild 的\xHH/\uXXXX两种转义形式,避免假阴性),数量与清单在build.mjs的REQUIRED_CLIENT_SNIPPETS
- 版本号单源:
package.json.version经 esbuilddefine注入客户端(__TW_VERSION__),构建期校验产物确实含该版本号 - 客户端在浏览器中的
react/react-dom/cordis由 web shell 模块表提供(externals);其余全部内联 - 纯逻辑与 React 分离:可断言的逻辑(状态判定、排序、会计、布局、区间估计)放在不 import react 的模块里,因此
node --test可以直接跑;hook 单独成文件 - 新增数字展示面时,需同时把选择器加进
styles.ts里数字模糊的两条规则(否则截图保护会漏数)
明确不做的项(附理由,避免反复评估)
外部代码评审(v0.15.1 基线)还提出若干结构性优化,经实测后明确不做或推迟,理由如下:
- 账务单遍分组(O(P×N) → O(N)):实测本机账本为 103 条流水 × 10 只持仓 = 1030 次遍历,
/portfolio响应 0.28s 且主要耗时在上游行情等待。此规模下优化遍历无收益。 - 流水改 JSONL 追加写、模块级缓存加 LRU、上游加令牌桶限流:单用户单进程、标的不满百;上游全挂的主要风险已由按主机熔断覆盖。
- 拆分 em.ts / rescue.ts / tools.ts 为多文件、把模块级单例改工厂注入:功能仍在演进期,拆分与并行开发的冲突成本大于收益;新增模块按
bottom.ts的方式办(纯函数、单主题、有断言)。 - 引入 vitest / husky / CI 供应商:改用零依赖的
node --test;本机单机项目不引入 CI 服务。 - CSRF token 等完整加固:实测服务只监听
127.0.0.1,局域网不可达,故不引入 token 体系;但请求 Content-Type 断言已实装(readBody要求application/json,否则 415)—— 浏览器对<form>只能发 urlencoded/text-plain/multipart,因此这一条把跨站表单整类挡在路由之外,与 Origin /sec-fetch-site围栏互补。
已知边界
- K 线数据源:腾讯历史 K 线为主(A股/港股/美股,稳定且快),东财
push2his兜底(指数/期货等腾讯不覆盖的标的),两源统一不复权口径避免混源污染缓存;东财klt=104实际是季K,本插件的「年K」由月K本地重采样;历史接口的「200 + 空数组」按失败处理并切换数据源 - 行情来自东方财富免费公开接口(
push2delay主、push2/push2his备;历史接口偶发限流会自动重试/降级);延迟数据,盘中偶有缺口;国际指数与商品按源时区展示 - 待复核:
100.KOSPI200的数值水平。本机quotes-lkg.json里东财返回的100.KOSPI200(名为「韩国KOSPI200」)为 1088.15,而同一批其它国际指数的水平与公开行情一致(CAC40 8078 / FTSE 9284 / 恒生 24523 / 日经 44946 / 标普 6643)。KOSPI200 长期在数百点区间,1088 与之不符 —— 怀疑东财该 secid 指向的不是 KOSPI200 指数本身(或其口径不同)。新浪无int_kospi200、腾讯无对应映射,因此两个备用源都无法交叉验证;东财行情主机对本机不可达时也无法复核。在验证清楚之前,不要把该数值当作决策依据;东财可达时可用tradewatcher_quotes 100.KOSPI200与其它公开源对照 - 国际指数/商品的备用源缺口:东财不可达时的兜底覆盖为 20/23(腾讯 6 个 A股指数 + 新浪 14 个:7 国际指数、4 国内期货、3 外盘商品)。缺 韩国KOSPI200 / 法国CAC40 / 欧洲斯托克50 —— 三家免费源(
hq.sinajs.cn、qt.gtimg.cn、smartbox 搜索)都没有对应符号,只能等东财恢复;此时它们会带source: lkg与滞后标记,而不是显示假数据 - 待复核:国内期货主连的昨收字段。本机缓存里 4 个国内期货主连(生猪/焦煤/豆粕/螺纹钢)来自新浪源时出现"昨收与最新价完全相同 → 涨跌幅 0.00%",而同一条记录的当日区间明显有波动(如生猪
open 11200 / high 11230 / low 10970 / 最新 11105)。怀疑新浪nf_*布局对该类合约的昨收字段取错;在拿到真实返回原文之前不改解析器(按错的下标改只会把"错得很明显"换成"错得看不出来")。看到 0.00% 时请以行情软件为准 - 上游连接抖动:本机到东财主机的连接会随机被立刻关闭(实测瞬时失败率 20–75%,与响应大小、IPv4/IPv6 选择无关,表现为
UND_ERR_SOCKET,几十毫秒即返回)。因此上游请求统一走fetchAny:「2 轮 × 每主机 2 次」(最多 8 次尝试)+ 抖动退避 + 9 秒总截止;实测分时接口冷启动成功率从 3/12 提升到 12/12、板块接口从 1/3 提升到 6/6,且正常时首次尝试即成功(240–500ms)。行情主路径已对齐同一骨架并补上失败日志与熔断 - 分时序列兜底:
trends-lkg.json保存上一份好图并在staleAt标记快照时间,命中后写 20s 短缓存(显式grace: 0,不被 peek 的 45s 宽限吞掉)避免断网期间按行重试形成风暴;客户端同样不因一次失败抹掉缩略图(失败只短缓存 15s) - 分时图对没有当日分时的标的回退近 5 日日线;
push2his限流时悬停图可能短暂显示占位 - 期货主连「昨收/昨结」等口径字段随接口语义展示,若做精确期货结算对账请以结算单为准
- 跨市场标的盈亏按接口币种金额展示;未开折算时不计入总额(见「折算口径」)
- 删除分组 = 归档(保留持仓与全部流水);「移除持仓」要求数量为 0(先卖后移),保证流水完整可追溯
- 财经日历:宏观自动源为东财 H5 财经日历(免费公开接口),它不含美国 CPI/BLS 数据(该源有 PCE、PPI、非农、失业率,但没有 CPI),也不含未上市公司 IPO 传闻;这类事件请用页面
+ 事件或tradewatcher_calendar_add手动补录。宏观重要度为名称启发式推导,非官方评级;公布值/预期值该源未提供
变更记录
历次变更、口径修订与实测依据见 CHANGELOG.md。
License
MIT
No comments yet. Be the first to write one.