Commit Graph
472 Commits
Author SHA1 Message Date
shy3130 a292f7bc58 feat(migrate): 内置策略按 ID 种入 custom — 修复升级丢失 25 个策略
启动时按文件名补缺, 不覆盖已有, 标记防复活; 桌面打包 datas 落盘,
前端迁移 toast 同步提示。
2026-10-15 12:27:59 +08:00
shy3130 db300cad45 refactor(ui): 页面包更名为页面插件
消除断句歧义, 与数据源插件命名对齐; 仅改文案, 代码层标识不动。
2026-10-15 12:27:59 +08:00
shy3130 ae93849d55 test(contract): 同步 Tier A 开放契约快照
b14f367 模拟盘重构新增 DELETE /api/paper/account 与 GET /api/paper/rounds,
按网关 paper:trade 前缀规则自动进入开放面, 补进快照。
2026-10-14 17:01:03 +08:00
shy3130 b22a970097 fix(strategy): 堵住策略安全校验绕过 GH #475
别名赋值与字符串拼接可绕过 AST 名单, 在主进程执行任意命令。
改为拦截危险引用、dunder、模块句柄与文件原语; 加载层 fail-closed。
2026-10-14 16:26:33 +08:00
shy3130 b14f3679d5 feat(paper): 模拟盘重构 — 多账户对比与策略自动执行
- 对比榜单 14 列可排序, 盘中实时估值; 行展开完整账户操作台
- 创建即绑策略自动执行: 入场信号买入 · 出场信号全平, 支持最大持仓
  (对齐回测口径)、仓位、订单类型、冷却; 批量创建同享
- 账户删除 (两步强确认)、冻结; 回合明细; 基础配置 (新账户默认参数)
- 策略联想下拉重设计: 全量候选、两行式选项、portal 防裁剪
2026-10-14 15:11:54 +08:00
shy3130 506d66f1ef fix(desktop): TdxQuant Key 探测打包版失效 — 哨兵重执行 + tdxaidata 入包
- probe: worker 移入主模块, frozen 以「重执行自身 + TDX_PROBE_WORKER 哨兵」
  拉起子进程 (sys.executable -c 被 PyInstaller bootloader 无视, 子进程会
  重启整个桌面应用 → Key 永远验证失败); dev 保持 python -c, 共用 run_worker
- spec: tdxaidata 显式入包 — 后端全部懒导入 Analysis 不跟随; lib/ 跨平台
  原生库按 datas 整目录复制 (collect_all 会让 macholib 解析 Linux ELF 崩)
- desktop: 单实例 _pid_alive Windows 改 OpenProcess+GetExitCodeProcess
  (os.kill(pid,0) 在 Windows 是 TerminateProcess, 会误杀在跑实例);
  main() 哨兵分发探测 worker, 先于单实例/后端/窗口
- 测试: 新增 test_tdxquant_probe.py (8 例); 全量 2692 passed;
  打包产物端到端实测探测 API ok, tdxaidata 真实加载连服
2026-10-13 18:37:29 +08:00
shy3130 25fd5a0fad fix(desktop): macOS 数据目录移出 .app 包 — 覆盖安装不再清空用户数据 + 包内旧数据自动迁移 2026-10-13 17:23:13 +08:00
shy3130 68368ac8c2 Update 2026-10-13 14:40:32 +08:00
shy3130 ecb49cbaca release: v0.3.6 — 核心+扩展架构完善版
合并 0.3.6 分支全部内容 (36 提交, 详细历史见 0.3.6 分支):

■ 数据与插件
- TdxQuant 数据源: 通达信量化接入 (实时/五档/日K/复权/分钟), tdxaidata 收进默认依赖
- 扩展表两级拉取配方: 概念/行业出厂即带完整可拉配置, 可作自定义接入参考示例
- 能力门禁日志带生效源; 标的池解析只走 instruments 维表

■ 页面扩展化
- 11 个官方页面全量迁移为单文件扩展 (custom/*/extension.tsx), 删目录即卸载
- 运行时页面包系统: 打包产物上传即挂载, 免重建宿主
- 新装用户扩展页默认隐藏, 老用户升级菜单无感

■ AI
- Responses 协议接入; 会话历史与长任务跨设备同步 (服务端注册表)
- 助手头部快捷入口 + 悬浮球拖拽 zoom 坐标系修复 + 系统设置开关

■ 界面
- 设置「关于」子菜单: 版权/更新/打赏 + 开源许可弹窗与合规声明双保险
- 侧栏两态收起、数据/AI 助手快捷行 (同步呼吸光晕)、核心/扩展 ext 标记
- 界面缩放默认 90%; 菜单设置徽标 Swiss 化; 策略池「叠加」分组
- 情绪评分口径可配置; 财务画像 5 张子表手动拉取

■ 文档与其他
- README 重构「核心+扩展」主线 + 架构图重画; 开放端点数核实为 65
- 版本号 bump 0.3.6; tdx 物化库测试夹具补 macOS dylib
2026-10-13 13:40:17 +08:00
shy3130 53ed6755ff fix(strategy): 信号种子迁移改为按 id 合并种入 — 修复升级用户丢失内置涨跌停信号
- seed_if_empty 原逻辑「目录存在任一 json 即整体跳过播种」令升级前已有
  自定义信号的用户丢失 20 个默认信号定义 (signal_limit_up /
  signal_limit_down / 跌停翘板 / 炸板), 表现为看板跌停恒 0、连板梯队
  无数据; 现改为仅种入缺失的 id, 绝不覆盖任何已有文件。
- 历史遗留的 skipped: user signals present 标记视为未种子化, 升级后自动
  补种缺失项。
- 播种提前到 repo.refresh_cache 之前: enriched 缓存构建时编译自定义信号
  表达式, 晚种会让本次启动的看板/梯队引用的 signal_limit_* 列缺失,
  直到下一次缓存刷新才自愈。
- 测试: 跳过断言重写为合并断言 + skipped 标记补救 + 同 id 不覆盖 3 个用例。
2026-10-12 22:50:50 +08:00
shy3130 c5ffe38c75 fix(test): ETF 分钟回填测试注入迁移信号定义 — 干净环境缺 signal_limit_* 定义导致回测解析报错 2026-10-11 15:51:50 +08:00
shy3130 350ffbf612 chore(build): 精简 Dockerfile 可选构建阶段, 清理文档与引导页冗余引用 2026-10-11 15:42:55 +08:00
shy3130 6a2a9bb2a7 chore(build): 清理打包 spec 冗余双声明与文档参考段落 2026-10-11 15:03:58 +08:00
shy3130 da51ff5711 feat(migrate): 旧数据目录一次性自动迁移 — 信号自动种入 + 失效策略自动归档
老环境升级后进入策略页逐文件报错刷屏、信号库为空, 需要用户手动搬运。
改为启动时自动完成, 前端只提示一次:

- 信号种子: data/user_data/custom_signals 为空且从未种过(.seeded_v1
  标记防重)时写入 20 个默认定义(原内置信号, 带 signal_* 列名), 写入
  即用户数据可编辑可删; 用户已有自定义信号时不掺入只落标记; 全删后
  重启不复活
- 失效策略归档: 引擎加载后把「内置残留」类失败(composite 子策略断链/
  import 已删内置模块/引用不存在的策略)移入同目录 _archive_unloadable/
  (引擎只扫顶层 *.py), 归档后 reload; 语法错误等普通失败不归档仍走
  原提示; 同名碰撞追加序号不覆盖
- 提示一次: /api/strategies 透出 legacy_migration 摘要, 前端按迁移
  指纹 localStorage 防重, 一条 toast 说明种入/归档结果

单一事实源: app/strategy/signal_seeds.py (tests/fixtures JSON 同内容,
tests/signal_seeds.py 改为从此读取)。回归: 6 个新迁移测试 + 后端 66
passed + 前端 vitest 125 + tsc 0; 本机实启验证 66 策略/0 错误/不重复
种入(标记落盘)
2026-10-10 22:15:17 +08:00
shy3130 9fdf7266a3 fix(screener): 旧数据目录升级的宽容降级 — 兼容垫片 + run_all 跳过失效 ID + 报错聚合
内置策略移除后, 老环境(策略池残留内置 ID / 自定义文件 import 旧模块 /
composite 断链)进入策略页会刷屏报错。三层修复:

- 兼容垫片: app/strategy/builtin/factor_rank_research 透明转发到
  app.strategy.research —— 旧策略文件 import 零改动继续可用
- /run_all 宽容: 混合未知 ID 跳过继续跑并回 skipped_unknown(全部未知
  才 404); 前端据此自动清策略池并提示一次, 空库场景 prune 的兜底
- 前端 load_errors toast 聚合: 多个失败合并为一条带文件名与修复指引,
  不再逐条刷屏

配套回归: tests/test_legacy_upgrade_degrade.py(垫片转发/混合跳过/全未知
404); 后端 34 passed + 前端 vitest 125 passed + tsc 0 错
2026-10-10 21:47:46 +08:00
shy3130 e077fc2729 Merge branch 'refactor/de-builtin-strategies-signals' 2026-10-10 21:30:28 +08:00
shy3130 918c7ade7e refactor(core): 策略与信号全面去内置化, 转为用户自定义定义
- 策略: 引擎只加载 data/strategies/{custom,ai,composite} 与研究模板目录,
  原 25 个内置策略一次性迁移为用户自定义文件(ID 不变, 引用零迁移);
  研究模板移至 app/strategy/research, 打包 spec 同步; 新装环境策略库为空
- 信号: 管线不再硬编码任何信号表达式; 涨跌停判定价成为运行时列
  limit_up_price/limit_down_price(生效价∓0.005 容差), 20 个信号转为
  data/user_data/custom_signals 定义(显式 column 沿用 signal_* 原列名);
  依赖解析/注入顺序/当日路径 prev-join 全部定义驱动; 语义统一为历史路径
  口径(顺带修正当日路径 BOLL 比较符与翘板/炸板空值两处既有偏差)
- 前端: 信号库单列表(全部可编辑), SignalPicker/监控选项/信号标签全走
  定义接口; 回测页新增免责声明(模拟性质/数据源准确性不作保证/不构成
  投资建议)
- 测试: 25 个策略夹具与 20 个信号种子入库 tests/fixtures, 受影响测试
  全部自包含化(不依赖运行目录用户数据); 77 万行 MA 死叉新旧表达式对拍
  逐行一致; 回测实跑 45 笔卖点信号出场验证矩阵链路
- 同批包含工作区既有进度: 策略结果 ext 列同日投影修复(含测试)、数据源插件、fuyao/auth/api_gateway/data_integrity 调整、README 信号库
  描述与 strategy.md 导入路径补充

验证: 后端 2592 passed(空数据目录 CI 模拟 2588 passed, 4 例为模拟环境
伪影复验排除); 前端 vitest 125 passed + tsc 0 错 + vite build 通过
2026-10-10 21:10:44 +08:00
shy3130 ae9feb869c fix(sources): 自定义源全量分钟/分钟K amount 改为可选 + 保存先验后存
amount 按契约可空, 缺成交额的源此前被整体拒绝且保存静默失败;
现在校验不过返回 400 带原因, 不落盘。
2026-10-09 16:21:29 +08:00
shy3130 b65d2691d9 feat(strategy): 每策略「叠加条件」overlay_filter — 零代码硬过滤叠加
策略设置新增「叠加条件」分区: 字段+运算符+值 多行 AND 硬过滤, 直接叠加在
策略自身规则之上, 选股/回测/监控三处一致生效, 不改策略代码、无需另建
信号或叠加策略 (扩展数据因子如 ext_ext_fuyao_hot_rank 可直接选)。

- 条件结构与自定义信号同源 (allowed_fields 白名单含注册表因子/字符串扩展
  字段), 禁用日期偏移, 最多 8 条
- 只挡入场/候选: exit_signal_hits 在过滤前收集, 已持仓卖出不受影响
  (与 basic_filter 既有口径一致)
- null/缺数据 ≡ 不命中 (fail-closed); 引用列缺失时报可读错误
- 仅日线 polars_expr/matrix_native 支持, composite/minute 保存期+运行期双拦;
  matrix 路径拒绝字符串字段 (矩阵 fields 为 float 数组)
- 保存时条件实际变化才清策略结果缓存, 普通保存不受波及
- 回测: polars 路径在裁剪前完整 panel 上评估掩码 AND 入场; matrix 路径条件
  字段并入 feature_plan 进矩阵 fields, NaN 一律不命中
- 前端提取 ConditionEditor 共享组件, 自定义信号弹窗复用 (净减 ~240 行)

测试: 后端新增 16 项 + 回测/关联模块回归全绿; ruff 对比基线零新增;
前端 6 项组件测试 + tsc/vite build 通过。
2026-10-09 00:19:59 +08:00
9f065dd3dd test(quote): 补 boot_check() 启动决策与可观测性回归测试(4 分支 + 1 xfail) (#457)
背景(本机实测事故 2026-09-16):面板 08:58 起,直到用户 09:35:33 手动开启才有行情
⇒ 开盘后 6 分钟实时评估全空。根因 = 旧版 `stop()` 把 `realtime_quotes_enabled` 写成 False
(lifespan shutdown 也调 `stop`)⇒ 次日 `boot_check()` 读到 False 就**静默不启动**,
且**一行日志都不打** ⇒ 排查只能靠排除法。

⭐ 为什么只补 `boot_check`(而不是把本机整套测试原样搬来):
  · 根因那一半上游**已有**测试:`tests/test_realtime_preference_persistence.py` 断言
    「`stop()` 不写偏好 / `disable()` 才写 False」—— 与本机那份同向 ⇒ ⛔ 本提交**不重复造**
  · `boot_check()` 本身上游**没有任何测试**(`test_data_integrity.py:537` 那个同名 `boot_check`
    是**数据修复**用的,⛔ 不是本函数)⇒ 缺口正是「**启动决策** + **有没有留下痕迹**」

新增 `tests/test_quote_service_boot_check.py`(**4 例 + 1 xfail**):
  1. 偏好开 + 档位允许 ⇒ **必须** `start()`(⭐ 反向对照:防守卫写成"永远不启动")
  2. 偏好关 + 档位允许 ⇒ **不得** `start()`
  3. 档位 none ⇒ 不得 `start()` **且必须留日志**(含「未启动」)
  4. 档位 none + 偏好为开 ⇒ 必须把偏好**同步写 False**(防 UI 误显示"已开启")
  5. ⚠️ `xfail(strict=True)`:偏好关 + 档位允许时**期望**留日志;上游该分支当前**仍静默**
     ⇒ 用它编码**期望行为**:上游修好那天会 XPASS ⇒ strict 让它 FAIL 以提示更新测试
     (⛔ 本提交**不改被测代码**;若维护者不接受这种用法,删掉这一例即可,其余 4 例完全独立)

验证(隔离 worktree,基于 `origin/main` 30fcade):
  · **P1** 新文件 ⇒ `4 passed, 1 xfailed`
  · ⭐ **P2 判别力**(4 个**单变量**变异,每个都**只**让对应用例 FAIL,且源码**逐字节还原**):
      M1 删 tier-none 的 `logger.info` ⇒ `test_logs_and_does_not_start_when_tier_disallows` FAIL
      M2 删 tier-none 的 `_save_enabled(False)` ⇒ `test_demotes_preference_when_tier_disallows` FAIL
      M3 `boot_check` 永不 `start` ⇒ `test_starts_when_preference_enabled` FAIL
      M4 `boot_check` 无条件 `start` ⇒ `test_does_not_start_when_preference_disabled` FAIL
  · **P3** 零回归:`tests/test_realtime_preference_persistence.py` + `tests/test_realtime_mode.py`
    ⇒ **5 passed**(与改动前一致)
  · **P4** 改动面:分支相对 `origin/main` **只新增 1 个文件**;工作树干净;`git diff --check` rc=0
  · **P5** `ruff check tests/test_quote_service_boot_check.py` ⇒ **All checks passed**

⚠️ **自曝(当轮修,留痕)**:初版把**中文全角标点**写进 docstring ⇒ 上游当前 ruff 配置
  (未忽略 RUF00x)报 **43 条 RUF002**。实测上游 `tests/` 的惯例是 **ASCII 标点**
  (288 个文件里 **271 个**无 RUF00x 告警)⇒ 已把文档串/注释改为 ASCII 标点、强调引号改用「」
  ⇒ 复测 **0 告警**。⚠️ 这与「上游应否忽略 RUF00x」是**两个问题**:本文件按**现状惯例**写,
  ⛔ 不依赖任何"改 lint 配置"的提交。

⚠️ 边界(⛔ 不含糊):`start` 被 monkeypatch 成**记录器** ⇒ 验的是「**决策**」,
  ⛔ 不能据此说"实时行情真能跑";日志断言用**子串**匹配(含「未启动」)⇒ 上游改文案会 FAIL
  —— 这是**故意**的:那行日志正是本事故的**可观测性承诺**。
⛔ 未推送。回滚 = `worktree remove` + `branch -D`。
(由 DSH agent 在用户授权下完成。)

Co-authored-by: JackChan83 <JackChan83@users.noreply.github.com>
2026-10-08 13:08:47 +08:00
e15fff13c1 fix(regime): _score 对非有限输入取中性 50(上游缺此防护,移植本机 f67d9ed 的防护) (#455)
问题(本机 2026-09-15 实测事故;上游同名表达式逐字相同):
  停牌股补零 ⇒ change_pct = close / prev_close - 1 = inf ⇒ 当日 avg_pct 被单个 inf 污染
  ⇒ _score 里 round((value - low) / (high - low) * 100) 抛
    OverflowError: cannot convert float infinity to integer
  —— round 是**最内层**调用,在 min/max 钳制**之前**求值,钳制救不了它
  ⇒ compute_regime 整段中止、regime_history 停更。上游原先没有覆盖该路径的测试。

改动(三处逐字副本,+15 行,无 schema 变更、无数据迁移):
  - app/services/regime_builder.py:新增 `import math` + `_score` 非有限值取中性 50.0
  - app/services/market_overview_builder.py:同款防护(math 已在 import 中)
  - app/api/overview.py:同款防护(math 已在 import 中)
  - tests/test_regime_score_non_finite.py:新增 3 例(_score 单元 / classify_state / _aggregate_daily 端到端)
  取 50 = 「该维度不可计算」:既不崩,也不把 inf 钳成 100(那等于伪造「极度看多」)。

验证(P1 先红后绿;全部在 origin/main 隔离 worktree 内执行):
  - P1 红:**未改代码**上 pytest tests/test_regime_score_non_finite.py → 3 failed,
    报错与生产逐字一致(OverflowError: cannot convert float infinity to integer)
  - P2 绿:修后同命令 → 3 passed
  - P3 行为:三处 `_score(nan / inf / -inf, -1.2, 1.3)` 全部 = 50;
    常规值 `_score(0.5, -1.2, 1.3)` 三处仍 = 68.0 ⇒ 正常口径未漂移
  - P4 基线:tests/test_regime_builder.py → 22 passed(与改动前一致)
  - P5 lint:ruff 三个改动文件 + 新测试 → 有改动 24 / stash 回上游原状 24 ⇒ **0 新增**;
    `git diff --check` rc=0
  - P6 相关回归:引用这三个模块的既有 5 个测试文件 → 35 passed
  - P7 逐字一致:三处 hunk 与本机 f67d9ed 的同名 hunk 完全相同(仅行号因上游位移);
    新增测试与本机 f67d9ed 版仅差 docstring 中一句(见下「边界」)

边界(本移植**未**包含):
  本机同批修复里「上游 `change_pct` 对非正前收取 NULL」(`ratio_over_positive`,
  app/indicators/pipeline.py)及 tests/test_pipeline_price_ratio.py 未移植
  ⇒ 本件只堵「坏值已产生后不崩」,**不堵「坏值的产生」**。
  ⚠️ 自动测试通过 ≠ 上游真实数据上不会再出现非有限值 ⇒ 仍需**上游真实运行**验证;
  本提交是把本机已验证的防护**预置**过去,不是对上游线上事故的结论。

影响与回滚:
  仅改计算口径(非有限输入的去向),无 API 形状 / schema / 数据迁移变更,历史 Parquet 不受影响。
  `git revert <sha>` 即可回滚。
  本提交在基于 origin/main(30fcade) 的隔离 worktree 内完成,本机主线工作树未被改动(status 为空)。

Co-authored-by: JackChan83 <JackChan83@users.noreply.github.com>
2026-10-08 13:08:41 +08:00
shy3130 f17260587b fix(watchlist): 分时缩略图续画按资产类型行情日期守卫, ETF 旧价不再拼尾K
enriched 响应新增 dates; 未开实时拉取的资产类型 (enriched close 为旧日K回退值)
不续画, 修复自选页 ETF 分时尾部错价长刺。
2026-10-08 12:04:41 +08:00
shy3130 0929253620 fix(packaging): app/custom 扩展双声明随桌面包发布, 修复 AI 对话助手 405 (#459)
loader 运行时 importlib+pkgutil 发现 app/custom/*, 静态分析不可见;
未声明时冻结包里扩展静默消失, 助手路由未注册 → SPA 兜底返回 405。
datas 落盘 _internal/app/custom + hiddenimports 进 PYZ, 仿 fuyao 双声明;
新增 test_packaging_spec.py 锁定打包不变量。
2026-10-08 10:36:26 +08:00
shy3130 badab07619 feat(adj): 除权因子独立同步 + 事件明细扩列 + 按需窗口优化
- 扩列: adj_factor 表扩至 8 列(dividend/bonus/allot/allot_price/prev_close 明细, 可空), fuyao 从事件 dump 原始成分回填; merge 变更检测按 ex_factor diff>1e-9, 仅明细回填不计入受影响列表; 字段说明补齐
- 独立同步: /api/pipeline/adj-factor/run 端点(共用任务机制: 单飞+重任务槽+行情暂停), Data 页除权因子卡片设置齿轮弹窗(生效数据源/能力徽标/TickFlow 无明细提示), 因子变更个股自动 enriched 局部重算
- 按需优化: 独立同步事件窗口钳到本地日K起点(更早事件在前复权 cum/total 两侧抵消, 对已存价格零影响); fuyao 配价由全有或全无改为逐只按需(远古事件只拖累自己, 不再整轮回退单标的接口); 实测全市场因子阶段 15min+ → ~11s, 配价 3676/3676 走本地 dump 零回退
- 盘后管道步骤条显示各能力路由徽标(矩阵驱动); README 补桌面端 macOS 安装注意事项
- 测试: 明细列 merge 语义 4 例 / 独立同步联动 5 例 / 远古事件不连累全场回归; 全量回归通过(2 例历史遗留失败与本改动无关, 基线同样失败)
2026-10-07 21:45:40 +08:00
shy3130 4ba0c9bcc0 修复部分已知Bug 2026-10-06 12:40:16 +08:00
kevinandGitHub 4cb4ae1a7d fix(assistant): 工具调用回传 Gemini 3 的 thought_signature, 同 index 的并行调用按 id 拆开, 不再 400 / 拼接工具名 (#453) 2026-10-05 00:21:02 +08:00
shy3130 379b7d283a feat(paper): 对比视图二期 — 全面去擂台化措辞 + 行内嵌完整账户面板替代跳转
- 措辞: 擂台/参赛/比拼全部改为对比定位 (标题/按钮/卡片/测试/端点文案), 移除 Trophy/Swords 图标
- 交互: 删除「进入」按钮, 点行展开内嵌 AccountPanel (下单/跟单/费用/冻结/持仓/台账/净值/策略对比), 单账户视图仅保留新建向导
- compare 增量字段: created_at/last_nav_date/day_change_pct/auto_rules/auto_enabled/holdings_count (加法契约)

验证: pytest 全量 2485 passed 6 skipped; ruff 通过; pnpm build 通过; 浏览器实测展开面板无擂台字样、无跳转、布局正常
2026-10-04 22:11:55 +08:00
shy3130 ca30bdaa84 feat(paper): 模拟盘擂台主视图 — 多账户收益对比榜单 + 同规格批量创建
参考擂台模式重构模拟盘默认视图, 对比从弹窗提升为页面主形态;
数据层零改动, 老账户自动上榜, 原单账户记账功能全量保留为下钻视图。

后端 (加法契约):
- POST /api/paper/arena/batch_create: 同本金/同费率一次开 N 个账户
  (≤20, 服务端生成 arena_ id), 各绑一条自动跟单规则; 规则整体先校验
  再落盘, 任一非法 400 不留半创建状态
- GET /api/paper/compare 增量字段: holdings_count / created_at /
  last_nav_date / day_change_pct(末两定版净值) / auto_rules / auto_enabled,
  旧前端可忽略
- 开放契约快照 +1 端点 (test_openapi_contract)

前端:
- Paper.tsx 拆为 ArenaView(默认: 摘要卡 + 归一化净值叠加 + 收益降序
  榜单[排名/徽章/迷你走势/跟单规则chip] + 行展开持仓与近期成交) 与
  AccountView(原视图全量保留, 头部加返回擂台); compare 查询 60s 轻轮询
- ArenaCreateModal: 策略/监控规则联想添加 + 全部策略一键加入(≤20),
  方向/仓位/订单类型/冷却为全表共享参数保证同规格
- api.ts: PaperCompareRow 扩展可选擂台字段 + PaperArenaSource 类型
  与 paperArenaCreate; 查询键复用 paperCompare/paperAll

验证: 全量 pytest 2485 passed (含新增 test_paper_arena 4 用例);
ruff 通过; pnpm build 通过; 浏览器实测擂台渲染/行展开/下钻返回/
批量创建弹窗交互均正常 (未在真实数据目录实际建账户)
2026-10-04 22:01:59 +08:00
shy3130 16ece46861 fix(backtest): close_t 卖出遇收盘封死跌停拦截+递延 — 涨停拒买的镜像问题(严重)
回测体检确认的严重项: _can_sell 只拦「一字跌停」, exit_fill=close_t 时尾盘
封板股(盘中正常交易、收盘=跌停价)按跌停收盘价"卖出" — 现实中收盘封死的
卖队排队排不出去, 带止损策略的亏损被系统性低估, 收益虚高。与已修的买入侧
涨停拒买(a64f573e)同构。

- engine.py 四处同构 _can_sell(矩阵/数组 × 独立/组合): exit_fill=close_t 且
  无 override(计划出场: 信号/max_hold/期末)时, 凡收盘跌停(limit_down_locked,
  含一字)一律拦截, 计数键沿用 sell_limit_down(前端已有「跌停阻塞」标签);
  日内风控线(止损/移动止损/止盈, override 盘中价)是封板前穿越成交, 不拦 —
  避免把盘中已触发的保护性离场误递延
- 被拦出场经既有 pending_exit 机制递延到下一可卖日成交, TradeRecord 的
  blocked_exit_days 保留受阻天数
- 修复递延机制的尾部漏洞: 独立候选两路径原会在窗口末尾静默丢弃仍被递延的
  持仓(封板/停牌卡死时 trade 凭空消失) — 改为期末按末日价格强制落账
  (_try_close 新增 force 参数跳过可卖性闸门), reason 取 pending 原因,
  blocked_exit_days>0 标记; 组合路径本就保留持仓并计入 pending_exit_positions,
  无需改
- open_t+1 按次日开盘卖出, 行为不变; signal_next_minute 分钟触发价照旧

测试: test_limit_down_close_exit.py 五场景 — 封板日出场递延到次日/正常日
当日出场不变/日内止损线价当日成交不被误拦/open_t+1 不受影响/期末卡死持仓
强制落账不再丢弃。本地与 TZ=UTC 双跑全量 2481 passed 6 skipped
2026-10-04 21:51:31 +08:00
shy3130 a64f573ef2 fix(backtest): 用户报告两处修复 — close_t 收盘封涨停拦截 + 移动止盈回撤不再静默钳制
报告①(严重, 收益虚高): _can_buy 只拦「一字板」(收盘涨停且四价合一),
entry_fill=close_t 时尾盘封板股(盘中正常交易、收盘=涨停价)以涨停收盘价
"成交" — 现实中收盘封板的买队排队排不进, 动量类策略信号集中在涨停日,
回测收益系统性虚高(报告实测 55.7% 买单落在涨停收盘日, close_t +586% 对比
open_t+1 -71%)。修复: close_t 口径下凡收盘涨停(limit_up_locked, 含一字)
一律拦截, 拦截原因沿用 buy_limit_up; open_t+1 按次日开盘成交保持只拦次日
一字板。engine.py 四处同构 _can_buy(_simulate_independent_matrix /
simulate_independent_candidates_legacy / _simulate_portfolio_matrix /
simulate_portfolio_legacy)同步修改。旧 simple 路径 simulate() 无任何涨停
守卫但生产代码无调用方, 未动。

报告②(严重, 配置不生效): strategy.py 移动止盈的 min(drawdown, activate)
把用户填的回撤值静默改写成激活值, 无任何提示。改为显式校验报错
「移动止盈回撤(x%)不能大于激活涨幅(y%)」, 与同函数内其他参数组合错误的
 _err 风格一致。

测试: test_limit_up_close_fill.py 四场景(封板拦截/正常日成交/open_t+1
信号日封板不影响次日开盘买入/次日一字仍拦截) + test_trailing_drawdown_
validation.py 两场景(超限报错/未超限放行)。本地与 TZ=UTC 双跑全量
2476 passed 6 skipped
2026-10-04 18:01:52 +08:00
shy3130 eefd9521bb fix(jobs): 分钟K同步「卡死空转」修复 — 排队等槽有上界 + pending 纳入 reap + SDK 批量调用守护 deadline
用户反馈(升级后数据页分钟K同步无限停在「等待其他计算任务完成…」): 被取消/
已终止的残留线程挂死在单次 SDK 网络读里(SDK timeout=30s×3 只保护其覆盖的
阶段), 协作式取消要到分块边界才生效 → 它持有的独占槽(capacity=2, exclusive
权重=2)永不释放; 排队任务等槽无超时且 status=pending 不在 reap_stale 视野
(旧实现只回收 running), 无限空转只能重启进程。机制为 0.3.1 统一重任务并发
控制(b16957a8)引入, 非 0.3.2 回归。

- pipeline_jobs.run_with_capacity: 槽位等待加 timeout(取 job 自身 timeout_s,
  与停滞阈值同源、用户可配), 超时以可读错误失败并按已取消上报 — fn 未执行,
  零数据副作用, 四个调用方(kline/pipeline/daily/integrity)的异常分支免改
- pipeline_jobs.reap_stale: pending 排队停滞纳入回收(pending 无 started_at,
  总时长硬上限跳过); create() 锚定 last_progress_at, executor 排队中从未
  上报进度的任务也有兜底
- kline_sync: SDK 批量调用(日K区间/日K条数/复权因子/分钟K区间/分钟K条数共
  5 处)统一 _sdk_call_with_deadline 守护(600s, daemon 线程执行 + join 超时
  放弃); 超时走既有分块失败路径, 且补发 on_chunk_done 心跳保留进度上报与
  协作取消检查点 — 持有侧线程可退出并释放槽位, 被放弃线程为 daemon 不阻
  塞进程退出

测试: 新增排队超时可读失败/槽空闲行为不变/pending 回收与不误杀/硬上限跳过/
SDK deadline 单元与分块循环心跳(分钟+日K); test_queued_pipeline_is_
cancellable_and_not_reaped 按新契约改写为 cancellable_and_reaped_when_stalled
(其旧断言「排队不被 reap」即本 bug 的排队侧契约)。本地与 TZ=UTC 双跑全量
2470 passed 6 skipped
2026-10-04 16:50:58 +08:00
shy3130 146bc2418f fix(custom): config_from_dict 白名单补上 full_minute — loader 清洗白名单含它而配置模型构建阶段漏了, 自定义源的「全量分钟」数据集被静默丢弃, 表现为设置页试拉报 does not configure dataset 'full_minute'、provider_has_dataset 为 False、分钟全量批量同步链路静默失效; 回归测试覆盖 清洗→YAML加载→load_all 注册→provider_has_dataset 全链路 (此前能力授予层测试 mock 掉了该函数, 真实路径无覆盖)
Fixes #451
2026-10-04 00:47:17 +08:00
shy3130 88e4c19bc2 test(abnormal): 盘中基准动量测试的「今天」改用 cn_today() — 实现按北京日切今日, 测试脏行用宿主 date.today() 在 UTC 16-24 点窗口错开一天不再被排除, CI(UTC runner) 每晚必红 (#446 合并触发的既有时间炸弹, 与其改动无关) 2026-10-04 00:29:59 +08:00
wshyandGitHub 0b4c410dbb Merge pull request #443 from KenNeo730/fix/integrity-scan-trading-calendar
fix(integrity): 完整性扫描候选日接入 fuyao 交易日历
2026-09-29 18:35:46 +08:00
wshyandGitHub 77e7088e54 Merge pull request #440 from kevin9327/fix/assistant-coverage-weekend-lag
fix(assistant): 数据完整性检查按最新日之后至今天的工作日计落后天数, 周末不再误报日线落后
2026-09-29 10:49:36 +08:00
wshyandGitHub 03f43d6790 Merge pull request #438 from kevin9327/fix/assistant-confirm-timeout
fix(assistant): 确认卡超时未作答返回 expired, 不再当成「用户已拒绝」回填给模型
2026-09-29 10:49:22 +08:00
wshyandGitHub 3667dec842 Merge pull request #436 from kevin9327/fix/sse-event-id-unique
fix(events): SSE 事件 id 由总线分配为严格递增的毫秒时间戳, 同一毫秒的一批告警不再共用 id
2026-09-29 10:49:14 +08:00
KenNeo730 6e401ab086 fix(integrity): 完整性扫描候选日接入 fuyao 交易日历
「周一~周五」近似把休市日当成缺失日: 2026 中秋 09-25(周五) 休市被报
kline_etf_daily missing, realtime_gate 409 后修复任务对休市日永远拉不到
数据, 门禁死锁, 实时行情自长假起打不开 (issue #442)。

- 新增 _trading_calendar(): fuyao 近一年交易日集合, TTL 30 分钟缓存,
  取数失败沿用上次成功结果 (stale-while-error), 从未取到才回退周几近似
- _candidate_days 按日历过滤候选日
- 测试的「最近工作日」探针同步升级为「最近交易日」
  (_recent_trading_day_before), 避免节假日环境误造休市日分区

本地验证: 37 passed (原 33 + 新增 4); 真实环境 boot 自检通过, 实时行情
门禁恢复放行。
2026-09-28 19:18:13 +08:00
kevin9327andClaude Opus 5.5 0181ab6656 fix(assistant): 数据完整性检查按最新日之后至今天的工作日计落后天数, 周末不再误报日线落后
_biz_days_behind 数的是 [latest, today) 的工作日, 最新日本身算作落后、
今天不算; 周六/周日对截至周五的数据报「落后约 1 天, 建议补全」。
改为数 (latest, today], 两端都是工作日时结果不变。

Closes #439

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 19:17:46 +09:00
kevin9327andClaude Opus 5.5 ef12b6dd60 fix(assistant): 确认卡超时未作答返回 expired, 不再当成「用户已拒绝」回填给模型
await_decision 超时也返回 denied, chat_service 为超时准备的文案分支
走不到, 模型被告知用户拒绝且不要重试。超时与溢出回收改为 expired,
同样不执行; 用户点拒绝的行为不变。

Closes #437

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 19:16:00 +09:00
kevin9327andClaude Opus 5.5 21bdffd237 fix(events): SSE 事件 id 由总线分配为严格递增的毫秒时间戳, 同一毫秒的一批告警不再共用 id
id 原本在格式化时按 time.time() 现算, append_many 一批告警落在同一毫秒
共用一个 id, 按 lastEventId 去重的订阅方只收到第一条; 时钟回拨时 id 也
会变小。发布时持锁分配 max(当前毫秒, 上一个 id + 1)。

Closes #435

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 19:14:01 +09:00
kevin9327andClaude Opus 5.5 18327c6519 fix(minute-adjust): 部分分区缺日K锚点时不建 .raw_basis 标记, 补齐后可续跑
只在全部分区都缺锚点时才不建标记, 有分区被跳过仍会建; 标记存在后迁移
短路, 被跳过的前复权分区永不换算, 读取时再被复权一次。任一分区缺锚点
即不建标记, 与注释、接口 docstring 和既有测试 docstring 一致。

Closes #433

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 19:12:08 +09:00
shy3130 3d03bf8dba test(fuyao): test_dataset 预览用例改用相对日期 — 硬编码日期滑出近30天窗口导致必失败 2026-09-27 23:03:07 +08:00
shy3130 44e8b7ebbd feat(dashboard): 布局后端持久化(preferences链路) + 外部链接组件(sandbox iframe+新窗口兜底) + 添加组件面板 + useDashboardLayout持久化hook(7用例) 2026-09-26 20:52:04 +08:00
shy3130 f5d9d6af71 feat(assistant): 工具轮次检查点支持在设置→AI 手动配置 (默认100, 0=不检查, 最小5) 2026-09-26 19:04:54 +08:00
shy3130 c6c4fd2819 feat(assistant): 工具轮次 6 轮硬限改为 100 轮检查点(继续/停止选择卡)
- 到达 _TOOL_ROUND_CHECKPOINT(100) 时复用动作确认闸门(PendingRegistry+决策端点)
  弹 rounds_confirm 事件: 点「继续」重置计数再跑一个周期, 点「停止」或 120 秒
  未选则优雅收尾(notice + done, 已生成内容保留, 不再是 error)
- 前端 rounds_confirm 以合成足迹记录承载选择卡(继续/停止按钮), 复用 decideAction;
  流结束时仍悬挂的 awaiting 确认统一收敛为已取消
- 旧 test_chat_stream_stops_at_rounds_limit 改写为新契约; 新增 3 项检查点测试
  (批准继续/拒绝停止/超时停止)
2026-09-26 18:04:37 +08:00
shy3130 24c1d10f7f fix(assistant): 策略 id 不存在的报错附带可用策略清单, 模型一次自纠
- run_strategy(助手本地) 增加 engine.has 预检; 与 run_backtest(核心目录) 的
  「策略 xxx 不存在」统一升级为附带前 12 个可用策略 id 的提示
- 起因: 模型用中文名/自造 id 直接调 run_strategy 时足迹卡报失败, 需再花一轮
  list_strategies 才能修正; 现在错误信息本身即可自纠, 用户看到的失败原因也可读
- 测试 3 项: 两工具的未知 id 提示文案 + 同步路径
2026-09-26 17:44:47 +08:00
shy3130 86a01a66e3 feat(assistant): 快捷建议 12→18 条 + 芯片按文字自适应排布
- 新增 6 条建议: 主要指数表现 / 横向对比几只股票 / 个股财务速览 /
  对比两个策略(两次确认卡回测后横向对比) / 跑一个选股策略 / 有没有任务在跑
- 建议芯片由固定两列网格改为 flex-wrap 自适应宽度, 芯片随文字长短伸缩, 每组自然成行
- README 交互设计条目同步(18 条 · 文字自适应); 测试补对比类建议与 id 唯一性断言
2026-09-26 17:24:58 +08:00
shy3130 6d810d138d feat(assistant): 空会话建议重设计 + 扩展数据表问答工具
- 快捷建议 8→12 条并按 4 组分区(行情与大盘/我的与个股/策略与信号/数据与扩展),
  新增板块轮动、盘中异动、批量行情、扩展数据表 4 条; suggests 契约新增 group 字段
- 空会话页重设计: 欢迎卡(图标+主副标题) + 分组图标头 + 两列建议芯片 +
  虚线提示卡「扩展内容同样可以问」(扩展数据表/自定义策略与信号可问答互动)
- 新增只读工具 list_ext_tables / query_ext_table(复用 ext_data 同一条读取链):
  列表含模式/字段/最新分区日期; 读行支持 等值过滤/排序/日期范围, limit 封顶 100
- prompt 工具策略补扩展内容问答指引; 足迹卡/前端工具标签同步
- 测试: 扩展表工具回路 7 项(快照/时序/过滤排序/范围/错误契约/摘要/分组断言)
2026-09-26 16:48:27 +08:00
shy3130 1d855de03b feat(assistant): 数据完整性与补全 — check_data_coverage 只读检查 + sync_data 确认闸门 + get_sync_status 进度
- check_data_coverage: 分区目录轻量扫描(与 /api/data/status 同口径, 不读
  parquet)输出日线/enriched/分钟K覆盖区间、财务表覆盖、进行中任务与 issues
  问题清单(数据停更/指标落后/财务缺失); 可选 symbol 参数查单标的日线+财务
- sync_data(动作, 须确认): 复用数据页同一条触发路径 — pipeline 盘后管道 /
  financials 财务表调度器 / minute_extend 分钟K向前扩展(days 钳制 30-1095);
  后台执行立即返回, 单飞去重, async 工具经事件循环调度后台任务
- 核心层最小抽取: api/pipeline.trigger_pipeline_job 与
  api/kline.trigger_minute_sync, HTTP 端点与助手共用(端点行为不变)
- get_sync_status: 活跃任务阶段/进度 + 最近 5 任务 + 财务同步状态
- 提示词/快捷指令(数据是最新的吗)/前端足迹标签同步补齐
- 测试: +7 项(覆盖检查缺口与新鲜数据/单标的滞后/共用触发路径与钳制/
  财务同步与参数校验/闸门拒绝不触发/摘要), 助手套件 21+34 项通过;
  全套 2428 passed(1 项 backtest spawn 时序 flake 单独重跑通过);
  线上 mock 验收: 真实数据发现缺口 → 建议补全 → 确认卡 → 拒绝不触发
2026-09-26 15:09:10 +08:00