Commit Graph
1040 Commits
Author SHA1 Message Date
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
shy3130 fb69f382e7 fix(screener): 策略池草稿区仅列 AI 来源草稿,非 AI 研究模板不再显示发布按钮
内置研究模板 factor_rank_research 带 research_only=True,此前混入 AI 草稿
分区并渲染「发布」按钮,点击必被发布闸(仅接受 source=ai)以 400 拒绝。
草稿过滤补 source==='ai' 校验;新增 vitest 测试复现该问题(无修复时红)。
2026-10-08 22:02:13 +08:00
shy3130 17093a8c36 fix(front): 对齐 @tanstack/react-query 至 5.104.1, 与持久化配套包同版
三包此前后台实际为 react-query 5.100.11 + persist-client/persister 5.104.1,
peer 依赖形式上不满足且打包存在 query-core 双拷贝。统一到 5.104.1 后
peer 精确满足, query-core 收敛为单一版本。安装/构建/vitest 105 例/
node:test 5 例全部通过。
v0.3.5
2026-10-08 13:27:10 +08:00
3fd1f6f95a chore(lint): ruff 忽略中文全角标点 + 启用 BLE(移植本机 f67d9ed 的 lint 配置;⛔ 不含业务代码) (#456)
问题:
  本仓 [tool.ruff.lint] 的 select 含 "RUF" 但未忽略 RUF001/002/003,而注释/文档字符串通篇中文
  ⇒ `ruff check backend/app` 实测 **1420 条**,其中 RUF001/2/3(全角标点)占 **815 条(57.4%)**;
  另有 **271 处** `# noqa: BLE001`(禁止裸 except)因 `BLE` 未启用而**全是"无效指令"**(RUF100 293 条)
  ⇒ 「禁止裸 except」这条规则**从来没有被真正强制过**。
  ⚠️ 且本仓 CI 不跑 ruff(ci.yml 只有 `uv run pytest tests -q`)⇒ 这条门禁一直没人守。

改动(仅 backend/pyproject.toml 的 [tool.ruff.lint] 一段;⛔ 无任何业务代码改动):
  - ignore 增加 RUF001/002/003 —— 理由:全角标点是中文书写的正常形态,不是笔误
  - select 增加 BLE(flake8-blind-except)⇒ 271 处既有 noqa 立即生效
  - ⚠️ 文字与理由照抄本机 f67d9ed 的原注释,但**注释里的数字一律按本仓实测重写**
    (本机数字 1442/801/642/518/291 与本仓实测 1420/815/611/594/271 **不同**,⛔ 不照抄)
  - ⛔ 不跑 `--add-noqa` 批量承认(那会改上百个业务文件,与在途改动冲突)

验证(隔离 worktree,基于 origin/main 30fcade;ruff 0.15.14):
  - **P2/P3 逐规则对账 1420 → 594(净 −826)**:
      BLE001 0→236(+236) · RUF001 189→0(−189) · RUF002 403→0(−403) · RUF003 223→0(−223)
      · RUF100 293→46(−247)
      ⭐ **出现正增量的规则只有 BLE001**,其余规则全部不变或减少(⛔ 不是"看总数下降就算过")
  - **P5 配置语义(可 FAIL 的差集断言)**:用 tomllib 解析 `origin/main` 版与本版 ⇒
      line-length/target-version 逐字不变;select 差集**恰为** {BLE}、ignore 差集**恰为**
      {RUF001,RUF002,RUF003},两侧**无任何删除**。核验脚本:
      research/oneshot/_verify_ruff_config_port_20261005.py(判别力 = 差集不等即 FAIL)
  - P4 改动面:`git diff --stat` = 1 file;`git diff --check` rc=0
  - P6 辅助:`pytest tests/test_regime_builder.py -q` ⇒ **22 passed**(配置改动不影响测试)
  - ⚠️ 本件**没有回归测试可守**(纯配置改动):靠上面的数值/差集对账,⛔ 不假装有单测

影响与取舍(⛔ 不粉饰):
  `ruff check backend/app` 从 1420 条降到 594 条 ⇒ 噪声大减、门禁**变得可执行**;
  但启用 BLE 会**显形 236 条**既有「尚未表态的 `except Exception`」(本机对应 166 条)——
  这是**暴露既有沉默违规**,不是本次新引入的错误;是否进一步 `--add-noqa` 批量承认,
  留给维护者决定(本提交不替上游表态)。
  ⛔ CI 无 ruff 步骤 ⇒ 本改动**不会打断 CI**,同样也**不会自动守住** lint。

兼容/回滚:
  纯 lint 配置,无 schema/数据/接口影响;`git revert` 即可回滚(不影响任何历史产物)。

说明:本提交在基于 origin/main(30fcade) 的隔离 worktree 内完成,本机主线工作树未被改动;
      ⛔ 未推送;⛔ 与 port/regime-isfinite-guard(_score 防护)**是两个独立分支/提交**。

Co-authored-by: JackChan83 <JackChan83@users.noreply.github.com>
2026-10-08 13:08:52 +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 625f5c7b56 chore: bump version to 0.3.5 — 前后端版本同源(package.json 唯一权威, fallback/VERSION 同步) 2026-10-08 10:36:26 +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 85b8a3f930 chore: 补提交 .zcodeignore(AI 工具扫描忽略配置) 与 mcp-server/uv.lock(与 backend 锁文件入库惯例一致) 2026-10-08 00:00:56 +08:00
shy3130 a358e07b01 feat(paper): 模拟盘持仓表显示股票名称 (#454)
代码列内联灰色小字显示名称(股票/ETF/指数), 批量 instrumentNames 查询
(与监控/持仓提醒页同模式, 5min staleTime, 空持仓不发请求), 查不到名称优雅降级不显示。
v0.3.4
2026-10-07 22:24:33 +08:00
shy3130 39f2543ccc chore: bump version to 0.3.4 — 前后端版本同源(package.json 唯一权威, fallback/VERSION 同步) 2026-10-07 21:49:30 +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 a1ae815f4f docs: 更新交流群二维码 2026-10-06 15:25:37 +08:00
shy3130 597afbb5f4 fix: 图标缓存刷新改用 SHChangeNotify, 不再依赖 ie4uinit(无IE系统刷新不到) 2026-10-06 13:26:37 +08:00
shy3130 29b54e5dac fix: pywebview 依赖链显式 hiddenimports, 构建环境缺包时构建报错而非产出坏包 2026-10-06 13:12:01 +08:00
shy3130 11977ce6bf fix: 安装完成自动启动失败(错误码740)改用 shellexec 2026-10-06 12:52:29 +08:00
shy3130 4ba0c9bcc0 修复部分已知Bug 2026-10-06 12:40:16 +08:00
shy3130 2b73e89b81 feat(app): 桌面端体验优化 — 界面缩放(80%-150%, 修复缩放黑条)/每小时检查更新/侧栏橘黄色呼吸灯下载图标(点击定位并自动检查)/发现新版本徽章带版本号/官网入口/顶部工具栏不折行/页面标题渐变字效; build: 安装包文件名带版本号 + 安装后自动刷新图标缓存 v0.3.3 2026-10-06 12:10:32 +08:00
shy3130 30fcade330 docs(readme): 官网徽章移至标语下方, 配色改黑底+品牌紫 2026-10-05 22:39:30 +08:00
shy3130 fdb8a0f435 fix(packaging): 补齐 fuyao 插件 — plugin.yaml 落盘 + provider/client 入 hiddenimports, 打包后设置页恢复数据源 2026-10-05 19:41:17 +08:00
shy3130 b23db4ecd7 brand: 品牌统一为 TSP — 应用名/图标(浅色圆角卡, Windows/macOS 双平台)对齐 brand 资产, README 新增官网入口与标题行内嵌 logo 2026-10-05 18:45:09 +08:00
shy3130 af63614610 perf(front,back): 查询缓存持久化 + staleTime 分层 — 重开应用秒出上次数据, 不再全量重拉
- 新增 lib/queryPersist.ts: PersistQueryClientProvider 接入 localStorage 持久化,
  白名单 13 个慢变查询族 (设置/偏好/能力矩阵/自选清单与分组/策略池/regime 全家/自定义信号),
  键首段精确匹配不误伤 watchlist-enriched 等实时行情族; 24h maxAge + 版本 buster;
  快变数据 (实时价/分钟K/告警) 刻意不持久化, 避免陈旧价格误导
- staleTime 分层: settings/preferences 30s, capability-matrix 5min, 自选清单 30s,
  策略池 60s, 自定义信号 60s — 切页不再对慢变数据反复请求
- regime_builder.load_regime_history 加 (path,mtime,size) 签名缓存: Regime 页
  三个端点原先每请求各自全量重读同一 parquet, 现共享一份, 写入后 mtime 失效

验证: pnpm build 通过; 浏览器实测持久化恢复路径 (注入缓存刷新后守卫直接放行,
不再整屏 loading); regime 相关 pytest 22 passed; ruff 零新增; git diff --check 干净
2026-10-05 15:16:16 +08:00
shy3130 f3804215f9 perf(front,back): 首屏弹窗/设置页懒加载落地页 -1.5MB + 后端取数锁收窄/分钟K分区直读/缓存治理 + 错误态与轮询门控
前端 (bundle/渲染/体验):
- StockPreviewDialog/DimensionMembersDialog/ListColumnCustomizer 拆薄壳+lazy 内容 (关闭不挂载, 保留 300ms 退出动画), 落地页静态依赖不再含 echarts/lightweight-charts (~2.2MB→~540KB); Settings 八 tab 与 Onboarding/Auth 按需加载; 字体自托管 @fontsource 替换 CDN 外链; 删除零引用依赖与 buildQuoteMap 死代码
- Watchlist O(n²) symbol 派生链修复 + 批量查询 placeholderData; LimitUpLadder 回调稳定化让 memo 生效; AbnormalMoves 两行组件 memo 化
- 非交易时段轮询降频 (quote_status.is_trading_hours 门控, 字段缺失保持原频); StockPreviewDialog 尊重异动页主开关不再意外触发全市场计算
- 六处错误伪装空态补 isError+重试 (新增共享 ErrorState); Monitor 涨跌色统一 priceColorClass (修 0 值显红); AI 复盘取消生成 (修 reviewStore 中断空指针与半途归档); Regime 主线过滤面板配置晚到回填

后端 (热路径):
- quote_service 监控评估链移出 _fetch_lock, 独立 _evaluate_lock 保两轮不并发; depth_service 网络拉取出锁, parquet 写仍串行
- get_minute/get_minute_batch 优先直读 date= 分区 (缺失回退全树扫描); get_daily_batch 复用 300 天 enriched 历史缓存 (与单股 get_daily 同源判据)
- secrets_store mtime 缓存; 总览 ext 读取 (path,mtime) memo; screener 历史缓存 LRU 硬上限; final 同步状态按日清理; webhook 线程池挂入 lifespan shutdown

验证: pnpm build 通过; 后端 pytest 全量 2489 passed (test_worker_process 1 例时序 flaky 已隔离复跑确认与本次无关); ruff 修改文件零新增告警
2026-10-05 15:07:53 +08:00
kevinandGitHub 4cb4ae1a7d fix(assistant): 工具调用回传 Gemini 3 的 thought_signature, 同 index 的并行调用按 id 拆开, 不再 400 / 拼接工具名 (#453) 2026-10-05 00:21:02 +08:00
shy3130 490c62bca7 fix(paper): 对比榜单三处小修 — 空值恒垫底/副标题随排序可变/徽章高亮去歧义
- 可空列 (累计收益/最新日/盈亏比/最近结算) 无值行排序时恒垫底, 不再随升序翻到顶部
- 页头副标题去掉写死的「收益降序」, 改为「点表头排序」
- # 徽章仅在累计收益降序时高亮为收益名次, 其他排序弱化为列位

验证: pnpm build 通过; 浏览器实测默认高亮/名排序弱化/全空列升降序稳定
2026-10-05 00:13:27 +08:00
shy3130 3aa07d5ef5 feat(paper): 对比榜单加盈亏比列 (胜率旁, 可排序) — 无亏损回合显示 —, 排序空值垫底
后端 compare 已返回 profit_loss_ratio (round 2位), 纯前端改动:
表头 12 列 + 排序键 + 展开行 colSpan 同步, min-w 860→920

验证: pnpm build 通过; 浏览器实测列显示/排序切换/空值 — 正常
2026-10-04 22:19:01 +08:00
shy3130 35e21cf126 feat(paper): 对比榜单表头点击排序 — 8 列升降序切换, 统计卡与净值图固定收益口径
- 可排序列: 账户/总资产/累计收益/最新日/胜率/回合/持仓/最近结算; 默认累计收益降序, 同列再点翻转
- 空值垫底 (数值 -1e9 / 字符串空串), 账户名换列默认升序, 其余默认降序
- 「收益最高」统计卡与归一化净值叠加 (收益前 8) 不受表头排序影响, 始终按收益口径
- 排序表头样式随 Review._DtTh (活动列方向箭头, 非活动列 hover 淡箭头)

验证: pnpm build 通过; 浏览器实测点「账户」表头升降序切换正确、活动列指示正确、恢复默认排序正常、截图无布局错乱
2026-10-04 22:17:39 +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 ff91eca3c0 fix(jobs): JobStore 快照落盘原子化 + get() 取锁 — 旧实现在 succeed()/fail() 的 pop→写盘窗口内, 无锁的 get() 会读到写一半的 JSON 而静默返回 None, 轮询方瞬时看到任务凭空消失 (CI test_pipeline_capacity 偶发 TypeError 的根因; 压力复现: 旧代码 2000 轮 2340 次 None, 修复后 0 次) 2026-10-04 00:41:01 +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 4bbe571ec9 Merge pull request #446 from intfoo/pr/strategy-param-string-type
feat(strategy): 支持 string 类型策略参数
2026-10-04 00:24:53 +08:00
intfoo 3e105a475d feat(strategy): 支持 string 类型策略参数
META["params"] 新增 string 类型(自由文本),后端本就按非数值类型
原样透传,无需改动;本 PR 补齐前端渲染与扫描守卫:

- StrategyParamDef.type 联合类型加 'string'
- 回测页/策略设置弹窗参数面板:string 渲染为文本输入框
  (此前未知类型会掉进 number 分支,文本值被 Number() 转成 NaN)
- paramSweep:string 参数标记「文本参数不可扫描」,buildGrid
  防御性跳过(后端 _candidates_for 只支持 float/int 范围展开)
- strategy-guide / compact 指南与 AI 生成 prompt 同步参数类型说明
2026-10-01 21:07:56 +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
wshyandGitHub 3452c0577e Merge pull request #434 from kevin9327/fix/minute-raw-basis-marker
fix(minute-adjust): 部分分区缺日K锚点时不建 .raw_basis 标记, 补齐后可续跑
2026-09-29 10:48:04 +08:00
wshyandGitHub 8f6b2bf0fc Merge pull request #441 from kevin9327/docs/builtin-strategy-count
docs(features): 内置策略数改为 25 并同步代表策略表, 与策略指南口径一致
2026-09-29 10:47:54 +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