Commit Graph
312 Commits
Author SHA1 Message Date
wshy 5a483501e8 Merge pull request #113 from shy3130/fix/intraday-refresh-and-voice-pct
fix: 分时刷新开关不生效 + Pro+默认开启 + 语音涨跌幅漏乘100
2026-07-13 22:29:13 +08:00
shy3130 4df5cc3b27 fix(voice): 涨跌幅播报漏乘 100, 几乎所有股票都念成「涨0.0%」
fmtPctText 直接 pct.toFixed(1), 但后端 change_pct 是小数制
(0.0366 = 3.66%, 见 quote_service.py「不乘 100」注释)。屏幕显示的
fmtPct (format.ts) 正确 ×100, 但语音的 fmtPctText 单独写漏了。

  0.0366 → 旧「涨0.0%」/ 新「涨3.7%」
  0.10   → 旧「涨0.1%」/ 新「涨10.0%」

×100 后再 toFixed, 与 fmtPct 单位约定一致。tsc 通过。
2026-07-13 22:28:16 +08:00
shy3130 590de5d694 fix(monitor): 分时图刷新开关不生效 + Pro+ 默认开启 + 刷新条件统一
三处问题:

1. 后端 API 漏接线 (settings.py):
   - 入参模型 RealtimeMonitorConfigIn 缺 minute_intraday_refresh →
     PUT 时 Pydantic 静默丢弃, 开关拨了也不存
   - GET /preferences 响应没返回该字段 → 前端永远读 undefined→false,
     开关拨完弹回 OFF
   服务层 (preferences.py) 本就有 get/set, 纯 API 层漏接。

2. 默认值 (preferences.py):
   get_minute_intraday_refresh 原默认 False。改为三态: 未设置过的
   key 按 Pro+ 权限定默认 (有权限=开, 无=关); 用户主动设置过的
   (含 False) 以用户选择为准。用 'in data' 区分 未设置 vs 设为 False。

3. 刷新条件不一致 (Watchlist.tsx):
   Watchlist 用 || (开关或实时行情任一即刷新), Screener 用 && (两者都要)。
   与设置卡片描述「需 Pro+ 权限 + 实时行情运行」对齐, 统一成 &&。

后端 216 测试通过; 前端 tsc 通过。
2026-07-13 22:28:04 +08:00
wshy 933d216c83 Merge pull request #111 from shy3130/fix/dev-ps1-utf8
fix(dev): dev.ps1 Start-Job 子进程强制 UTF-8, 修后端中文日志乱码
2026-07-13 20:51:59 +08:00
shy3130 40c197149e fix(dev): dev.ps1 Start-Job 子进程强制 UTF-8, 修后端中文日志乱码
Start-Job 开的是全新 powershell.exe 子进程, 不继承主进程第 25-28 行
设的 UTF-8, 默认用系统 ANSI (中文 Windows = GBK/cp936) 解码后端的
UTF-8 输出 → 中文变 '瀹氫箟缂哄皯' 式乱码 (UTF-8 被当 GBK 解)。

第 25 行注释 'so child process logs aren't garbled' 的意图因 Start-Job
开新进程而落空。在 backend/frontend 两个 scriptBlock 开头各设 UTF-8,
与 app/__init__.py 的 stdout/stderr 编码对齐。

PowerShell 语法校验通过。
2026-07-13 20:51:23 +08:00
wshy a0fec8a754 Merge pull request #110 from shy3130/fix/wf-nan-rendering
fix(walkforward): 汇总数值 NaN/Infinity 渲染兜底
2026-07-13 18:54:46 +08:00
shy3130 95c918c841 fix(walkforward): 汇总数值 NaN/Infinity 渲染兜底
#104 审查发现 StrategyWalkForward 的汇总卡和净值曲线在后端返回
NaN/Infinity (零成交折等边界) 时会渲染出 "NaN" 字样或空白 SVG:
- OosEquityChart: vals 含 NaN → lo/hi/span 全 NaN → SVG 路径空白,
  页脚 last.toFixed(4) 显示 "NaN"
- IS→OOS 退化 / 目标均值: degradation != null 对 NaN 为 true,
  toFixed(3) 显示 "NaN"; 颜色判断 (NaN > 0 = false) 方向也错

修复: 加 fin() 把非有限值归一为 null, JSX 外用 const 取出归一值
(TS 据此收窄类型), 下游统一走 '—' 降级。复利收益/一致性/OOS 收益
本就走 NaN-safe 的 fmtPct, 无需改。

前端 tsc -b + vite build 通过。后端未改动。
2026-07-13 18:54:13 +08:00
wshy 79b86b26a0 Merge pull request #105 from im47cn/fix/task-reconnect-cleanup
fix(optimizer): SSE 放弃时清 localStorage + JSON 解析失败告警
2026-07-13 18:49:24 +08:00
wshy b869f3a552 Merge pull request #104 from im47cn/feat/wf-optimization
feat(walkforward): 滚动窗口 walk-forward 优化 — IS 优化 + OOS 验证
2026-07-13 18:48:47 +08:00
wshy 31596824d7 Merge pull request #109 from shy3130/fix/ext-concepts-cast-and-meta-normalize
fix: 扩展数据 List cast 报错 + AI 策略 META 规范化失败
2026-07-13 18:40:49 +08:00
shy3130 7062dcd94d fix(strategy): AI 生成 META: dict = {...} 时规范化报错「找不到 META 字典」
LLM 常给 META 加类型注解 (ast.AnnAssign 节点), 旧版 _find_meta_dict
只遍历 ast.Assign 漏掉注解形式 → 抛「找不到 META 字典」→ 上层包成
「规范化 META 失败」。校验 (_extract_meta) 与规范化 (_find_meta_dict)
用两套不一致逻辑, 导致「校验通过、规范化失败」。

两个函数统一增加 ast.AnnAssign 分支, 消除不一致。补充 4 个回归测试
覆盖注解形式 (含端到端 _normalize_build_result 路径)。

修复后无论哪台机器、哪个模型生成都不再触发该错误。
2026-07-13 18:39:17 +08:00
shy3130 570bcba8b9 fix(ext-data): List→string 字段直接 cast 报错 — 先 join 再 cast
手动创建的扩展配置拉取 THS 概念接口时, concepts 列为 List(String),
声明为 string 后 cast_df_to_schema 直接 cast(pl.Utf8) 抛
「cannot cast List type (inner: String, to: String)」。

cast_df_to_schema 检测到源列是 List 且目标为 Utf8 时, 先把元素
转字符串再以分号拼接 (与 _flatten_concept_rows 一致), 然后再 cast。
保护任意返回数组字段的配置, 不再依赖预设 id 走 flatten 分支。
2026-07-13 18:39:10 +08:00
shy3130 ede22336fa docs: clarify Docker deployment behavior 2026-07-13 00:05:07 +08:00
wshyandshy3130 697ff2a181 fix(compliance): reinforce research-only boundaries (#108)
Co-authored-by: shy3130 <shy3130@users.noreply.github.com>
2026-07-12 22:42:38 +08:00
im47cn b4fa9d6eb4 fix(optimizer): SSE 放弃时清 localStorage + JSON 解析失败告警
对抗式审查发现的两处健壮性问题 (与 walk-forward 同源, 此为优化器侧):
- stopOptimize 延迟分支 + MAX_RECONNECT 超限退出漏清 localStorage, 导致刷新页面
  tryReconnect 会重连到已放弃/未取消的任务。两处补 removeItem(RECONNECT_KEY/JOB_KEY_KEY)。
- optimize_stream 的 params/overrides JSON 解析失败静默降级为 {}/None, 会让用户配置
  丢失变成无声 bug。升级为 logger.warning (仍降级不崩流, 但可诊断)。
2026-07-12 22:22:36 +08:00
im47cn 5ea58f6a8d fix(walkforward): SSE 放弃时清 localStorage + JSON 解析失败告警
跨组件后续修复的 WF 侧 (优化器侧同款问题另起 PR):
- stopWalkForward 延迟分支 + MAX_RECONNECT 超限退出漏清 localStorage, 导致刷新页面
  tryReconnect 会重连到已放弃/未取消的任务。两处补 removeItem(RECONNECT_KEY/JOB_KEY_KEY)。
- walkforward_stream 的 params/overrides JSON 解析失败静默降级为 {}/None, 会让用户配置
  丢失变成无声 bug。升级为 logger.warning (仍降级不崩流, 但可诊断)。
2026-07-12 22:18:11 +08:00
im47cn e4262d9951 fix(walkforward): IS 训练折强制 position 模式, 堵前视泄漏
对抗式审查发现: WF UI 暴露 full 模式, 选中时训练折(end=train_end)未平仓的持仓
会用 train_end 之后(即 OOS 区间)的真实 K 线平仓 -> IS 分数被未来数据污染, 优化选出
的最优参数乐观偏移, 使过拟合被掩盖 (对专门检测过拟合的 WF 工具危害尤重)。

修复: IS 训练区间优化强制 mode="position" (只看正式区间表现), OOS 回测保留用户
所选 mode。新增测试: 用户选 full 时断言每折 IS 得到 position、OOS 保留 full。
2026-07-12 21:48:13 +08:00
shy3130 97e1cdcc78 feat(backtest): 交易记录显示具体触发信号 (不再只显示「信号」类别)
原问题: 多个信号在 _build_signal_mask 里 OR 成一个 mask 后, 只记录
exit_reason="信号", 具体哪个信号触发的信息丢失。回测里买入/卖出原因
只能看到笼统的「信号」, 不知道是 MA金叉还是 MACD死叉。

实现:
- 后端 TradeRecord 加 entry_signal_id/exit_signal_id 字段
- 新增 _resolve_signal_id() 在触发行回查 panel 列, 找出为 True 的具体信号
- 三个 simulate 方法加 entry_signal_ids/exit_signal_ids 参数
- strategy.py 把信号列表传给 engine, _trade_to_dict 透传新字段
- 前端 useSignalNames() hook 合并内置 (BUILTIN_SIGNAL_DEFINITIONS) + 自定义
  信号名称; TradeLegCell/ExitReasonBadge 显示具体信号名
- 信号名称前端解析 (复用 cnSignal), 后端只返回 signal_id, 不读磁盘

效果: 买入腿显示「MA5上穿MA20」, 卖出腿显示「MACD死叉」, 原因列从
通用「信号」变成具体信号名。止损/止盈/到期等非信号退出不受影响。

验证: 191 passed, 前端类型检查无错误
2026-07-12 18:31:36 +08:00
shy3130 ff059a0cda fix(backtest): 分钟K回测爆内存 — 按触发日精确读取 + 紧凑数组缓存
上一提交 (365f1cc) 修 index bug 时把 cache 从紧凑 numpy 数组改成臃肿的
polars DataFrame, 分钟K回测内存翻几倍导致 OOM 死机。且 _load_minute_for_fills
用 get_minute_range 扫描整个触发区间 (start~end), 触发日稀疏时读了大量无关
日期, 全市场长周期回测直接爆内存。

修复:
1. repository 新增 get_minute_by_dates — 按日期列表精确读 date=YYYY-MM-DD
   分区文件, 不扫描区间。内存与回测区间长度解耦, 只随触发日数量增长。
2. _load_minute_for_fills 改用 get_minute_by_dates, 每批 50 天分批读取。
3. cache 改回 float64 紧凑 2D 数组 (6 列: open/high/low/close/volume/amount),
   .cast(Float64) 保证 to_numpy 不退化回 object, 原来的 index bug 不复发。
4. _resolve_minute_fill 改用整数列索引 (arr[:,0]) 替代列名索引。

内存对比 (全市场 1 年):
  之前: get_minute_range 扫全年 ~365 文件 + DataFrame 缓存 → 5-10GB
  现在: 只读触发日文件 + float64 数组 → 几十 MB

验证: 191 passed (含 7 个分钟K回归测试)
2026-07-12 18:01:19 +08:00
shy3130 365f1ccbfb feat(minute-k): 分钟K同步流式落盘 + 段大小可配 + 回测成交修复 + 卡死超时调整
1. 分钟K流式落盘 (修复1年全市场卡死)
   - sync_minute_batch 加 on_segment 回调,每段拉完立即写盘
   - 抽出 _write_minute_partition 公共函数,消除重复
   - 内存峰值从全量(~25GB)降到单段,避免 OOM

2. 段大小可配 (默认20交易日,范围[5,30])
   - 新增 minute_sync_segment_days 偏好项
   - 「单次获取」与「获取1年」共用此分段设置
   - 前端设置弹窗新增分段大小步进器

3. 回测分钟K精确成交修复 (从未成功跑通过)
   - _resolve_minute_fill 改接受 DataFrame,避免 to_numpy() 退化为
     object 数组后字段名索引抛 IndexError
   - _load_minute_for_fills 用 partition_by 向量化分组替换逐行循环
   - 新增 test_minute_fill.py 回归测试 (6 用例)

4. 卡死 watchdog 超时阈值按任务类型区分
   - 普通任务 600s → 1200s;分钟K长任务 → 1800s
   - create(timeout_s=) 支持按 job 存阈值,reap_stale 读 job 自身值
   - 修复分钟K正常拉取被误杀导致僵尸线程继续写盘的问题

5. UI 优化
   - 设置弹窗按 自动同步/手动获取/清空 三区块分组
   - 「单次获取」改为按分段大小拉一段 (天数=分段设置)
   - 状态文案: 盘后自动同步 → 自动同步已开启/已关闭
2026-07-12 14:23:22 +08:00
im47cn 8d93b8fe98 fix(walkforward): 前端对齐优化器已有防护 (切策略停任务 + 坏网格拦截)
对抗式审查发现 WF 页面偏离了 StrategyOptimizer 已修好的两处防护:
- 切策略时直接传 clearWalkForward (只清前端状态), 不停后端任务 —— 对齐优化器
  改为 if(pending) stopWalkForward() else clearWalkForward(), 真正 cancel+关SSE+清storage。
- canRun 缺 !sweep.gridError, 且 CombosHint 未传 gridError prop —— 坏网格(步长越界等)
  既不显示错误也能提交到后端。补齐, 与优化器一致。
2026-07-12 14:10:15 +08:00
shy3130 a6e9987be6 fix(packaging): support legacy Polars CPUs 2026-07-12 14:08:24 +08:00
im47cn 482904b591 test(panelcache): 子代理审查加固 — 去 flaky + 补遥测差值覆盖
三处审查驱动的测试改进:
- 并发计数测试放宽 reuse_count==4 为 reuse+hit==4 (保留"只扫盘1次"核心不变量),
  消除慢调度下 leader 已写缓存致某线程走 hit 分支的 flaky。
- _FakeEngine 零值 dict 改从 PanelCache().stats() 取键 —— 字段重命名时桩自动跟随,
  杜绝 test 绿而生产 cache_stats KeyError 的契约漂移。
- 新增 test_walkforward_cache_telemetry_computes_deltas: 递增桩验证 scans/hits/
  reuses/秒数 = after-before, 覆盖此前 _FakeEngine 恒返 0 掩盖的差值/顺序逻辑。
2026-07-12 13:52:49 +08:00
shy3130 2014da57a9 Merge PR #103: Codex CLI 增加 GPT-5.6 支持 + 安全隔离
冲突解决: 保留自定义预设 + GPT-5.6 模型选择 + reasoning_effort 配置
2026-07-12 11:07:16 +08:00
shy3130 f9a632ad54 feat(ui): 自定义信号字段选择改居中对话框 + AI配置新增自定义预设
- CustomSignalDialog: FieldPicker 从浮动小面板改为居中对话框(Portal)
  条件行 flex-wrap 防截断; 字段按钮弹性宽度
- AI.tsx: 新增"自定义"预设(默认选中, 不自动填充); 新用户未配置AI时字段留空
2026-07-12 11:04:49 +08:00
shy3130 20ef070099 feat(backtest): 分钟K精确回测 — 穿越价/VWAP 成交 + Pro+ 门控
- engine.py: MatcherConfig 加 minute_fill; _resolve_minute_fill (穿越价/VWAP/降级)
  + _load_minute_for_fills; simulate_portfolio/independent_candidates 接入
- strategy.py: StrategyBacktestConfig 加 minute_fill
- backtest.py: strategy_stream 加 minute_fill 参数 + Pro+ 门控 + 数据范围检查
- repository.py: 新增 get_minute_range (多symbol x 日期范围)
- backtestTask.ts/StrategyBacktest.tsx: 激活 highGranularity 开关 + Pro+ 门控
2026-07-12 11:04:43 +08:00
shy3130 fc85d53bee feat(minute-k): 分钟K重做 — 前复权 + 向前扩展 + 清空 + 单独拉取 + 进度
- kline_sync: adjust="forward" 前复权; sync_minute_batch 按月分段(57天/段)
  突破 count=10000 上限; on_chunk_done 回调带日期段标签
- kline.py: 删除 extend_minute_history; 新增 sync_minute_single(单股拉取落库)
  + clear_minute(仅清分钟K二次确认) + sync_minute 可选 days/extend 参数
- preferences/settings: 默认 5 天上限 30 (恢复原始); 超时强制释放 _heavy_run_lock
- MinuteSyncConfig: 自动同步(5天/关闭) + 往前获取(单次拉满) + 获取1年(分段)
  + 清空按钮 + 进度回调 onJobStart
- StockIntradayChart: 单股获取改用 syncMinuteSingle (落库)
- daily_pipeline: _minute_chunk_progress 兼容 seg_label 参数
2026-07-12 11:04:34 +08:00
im47cn db535c8004 feat(panelcache): IO 遥测量化跨折重复扫盘占比
walk-forward 各折 train 窗口日历重叠 ~75%, 但 PanelCache key 精确到区间,
跨折永不命中 -> 每折重复扫盘。为判断是否值得做"整区间加载一次+切片"优化,
先埋轻量遥测再决策 (避免盲优化)。

- PanelCache 累加真实扫盘耗时/次数 + 命中/single-flight 复用次数, 暴露 stats();
  BacktestEngine.cache_stats() 透传 (不让上层伸手进私有 _cache)。
- WalkForwardService.run 首尾快照, 返回 cache_telemetry.load_panel_pct
  (扫盘耗时/WF总耗时) + scans/hits/reuses, 并 logger.info 一行。
- 测试: PanelCache 计数 (miss扫盘/命中/并发复用); WF 测试补最小 engine 桩
  (生产路径不加 getattr 兜底, 避免静默吞掉真缺 engine 的 bug)。
2026-07-12 00:52:32 +08:00
im47cn 72697707a4 fix(walkforward): 优化器吃用户当前策略配置 + EmptyState 构建修复
WF 前端 onRun 只传了 grid/objective/窗口, 没传当前策略的 params/overrides:
- 未扫描参数不固定为用户当前值
- basic_filter/entry_signals/exit_signals/止损止盈/评分等覆盖不参与优化
结果 walk-forward 优化的策略与用户实际回测的策略不一致 (与 PR #82 优化器
阻塞项同类问题)。修复: onRun 补 params=selected.params_defaults +
overrides=buildDefaultOverrides(selected), 复用 optimizer 同一工具。

附带:
- StrategyWalkForward.tsx 用了 EmptyState description= (该组件只接受 title/hint),
  导致 tsc -b 构建失败, 2 处改为 hint。
- _make_wf_job_key 已含 params/overrides, 补测试断言二者不同产出不同 job_key
  (stream 与 cancel 对齐前提) 且相同入参稳定一致。
2026-07-12 00:52:11 +08:00
Lzwaang 646d143d58 feat: 完善 Codex CLI 模型配置与安全隔离 2026-07-12 00:31:03 +08:00
wshyandshy3130 a7acc5e49d fix(settings): 实时监控设置页左右两列等宽 (#102)
grid-cols-[1fr_1fr] 的 1fr 等价于 minmax(auto,1fr),左列较长内容
撑出最小宽度挤压右列;改用 minmax(0,1fr) 强制严格等分。

Co-authored-by: shy3130 <shy3130@users.noreply.github.com>
2026-07-11 20:31:33 +08:00
im47cn 32d27a0a6e fix(walkforward): 子代理审查修复 — 折有效性/前视泄漏/方向退化/NaN序列化/净值曲线
三份子代理审查 (核心/API/前端) 发现的真实 bug:

核心正确性 (walkforward.py):
- [高] 折有效性抽象: best_params=None (训练全组失败) 或 OOS error 的折原会用默认
  参数硬跑/按0收益混入复利, 伪装成有效折污染 OOS 净值与退化指标。改为分流 ——
  有效折进 folds, 无效折进 skipped(带原因), 聚合只看有效折。一次修掉 H1/H2/M1。
- [高] 前视泄漏: train_end==test_start 使该日 K 线同时进训练与 OOS 首日。test_start
  后移一天隔断。
- [中] degradation 方向感知: 原 avg_is-avg_oos 对 min 类目标 (avg_holding_days) 符号
  反了。归一到越大越好空间再相减; 每折加 oos_degraded 方向感知标志。
- consistency 改为 OOS 盈利折占比 (与目标方向无关, 更直观)。

API (backtest.py):
- [高] 单折 guard: WF 缺 guard, start=None 默认拉 3 年, 每折训练窗口可能 OOM。按
  单折窗口 (train/test) 而非总区间 guard —— 总区间长本是 WF 正常形态, 按总区间拦会误杀。
- [中] NaN/inf 序列化: json.dumps(default=str) 处理不了 nan/inf, 输出非法 JSON 崩前端。
  加 _json_safe 递归清洗, 优化器与 WF 两处 done 分支都套上。

前端 (StrategyWalkForward.tsx):
- n_folds=0 门控: 全跳过时不再渲染误导性全0卡, 改显示'未产生有效折'+跳过原因。
- 渲染 OOS 拼接净值曲线 (walk-forward 核心产出, 原后端算了前端没画)。
- 每折退化标红改用后端方向感知的 oos_degraded (min 类目标 oos<is 未必退化)。

测试新增: 前视泄漏隔断 / 方向感知退化(min目标) / best_params=None跳过 /
OOS error跳过。后端 173 测试通过; 前端 tsc 无新增错误。
2026-07-11 19:20:31 +08:00
im47cn 4c40c6b1bf feat(walkforward): 前端 walk-forward 面板 + 抽出共享参数扫描组件
PR2b 前端 (完成 PR2b):

抽共享组件 (DRY, 优化器与 walk-forward 复用):
- components/paramSweep.tsx: Sweep 类型/helper + useParamSweep hook (策略选择 +
  各参数扫描配置 + 组合数 + buildGrid) + StrategySelect/SweepParamList/CombosHint
  组件 + OBJECTIVES/INPUT_CLS 常量。
- StrategyOptimizer.tsx 重构复用该组件 (删除本地重复的 sweep 逻辑与 JSX)。

Walk-forward:
- lib/walkforwardTask.ts: SSE 客户端 (镜像 optimizerTask + job_key 回吐 + 重连)。
- StrategyWalkForward.tsx: 配置 (策略/目标/日期/训练-测试-步进窗口/可扫参数) +
  结果 (汇总卡: OOS复利收益/IS→OOS退化/一致性/折数; 每折表: 测试区间/最优参数/
  IS目标/OOS目标(退化标红)/OOS收益; 过拟合告警条)。
- Backtest.tsx: 新增 'Walk-forward' 第四 tab。

前端 tsc 无新增类型错误。
2026-07-11 19:20:31 +08:00
im47cn 586d9a4699 feat(walkforward): 真·walk-forward 优化后端 — 滚动窗口 IS 优化 + OOS 验证
PR2b 后端 (叠在 PR2a 优化器上):

app/backtest/walkforward.py:
- generate_folds: 滚动窗口切分 (训练固定长度 + 紧邻测试窗口 + step 前移),
  测试超出 end 即停; 放不下一折抛错; 非正窗口拒绝。
- aggregate_oos: 从各折 OOS 结果聚合 — 复利净值曲线 / IS-vs-OOS 退化
  (样本内目标均值 - 样本外目标均值, 正值=过拟合信号) / 一致性 (OOS 目标为正的折占比)。
- WalkForwardService.run: 每折在训练区间调 PR2a 优化器选最优参数, 再在测试区间
  用该参数做 OOS 回测。核心产出是纯 OOS 拼接 + 每折 IS/OOS 对比 — 单次样本内
  回测的过拟合一眼看穿。支持进度回调 (fold i/N) 与 cancel。

API app/api/backtest.py:
- GET /walkforward/stream (SSE, 复用 _BacktestJob + job_key 回吐) + POST
  /walkforward/cancel (按回吐 key 查表)。

测试 13 例: fold 切分 (滚动/边界/不足/非正) + OOS 聚合 (复利/退化/一致性/空) +
编排 (训练优化→测试OOS/退化上报/取消) + API (job_key 区分窗口/cancel按key)。
全量 169 测试通过。
2026-07-11 19:20:30 +08:00
shy3130 7e6ea3afcd feat(custom-signals): 自定义信号条件支持日期偏移 + 字段弹出选择器
- custom_signals.py: 新增 _col(name, days) helper (days>0 时 .shift().over('symbol'));
  validate 加 leftDays/rightDays 校验 (0-60); build_expressions 加 allow_shift 参数
- signals.py: ConditionModel 加 leftDays/rightDays; options 端点加 maxDays + 字段分组 groups
- pipeline.py: 盘中路径 build_expressions(allow_shift=False), 带偏移的信号优雅跳过
- api.ts: CustomSignalCondition 加 leftDays/rightDays; options 加 groups/maxDays 类型
- CustomSignalDialog.tsx: 字段选择改为 Portal 弹出式 (搜索+分组), 解决下拉框过长;
  日期偏移控件 (最新/前N日 两种态); 宽松布局
- CustomSignals.tsx: 只读卡片用 fieldWithDays 显示偏移标注

向后兼容: 旧 JSON 无 leftDays/rightDays 时当 0 处理, 行为不变
2026-07-11 19:15:54 +08:00
wshy b53aa5c8d3 Update README.md 2026-07-11 18:58:10 +08:00
wshyandshy3130 fcae7f5446 feat(dashboard): 看板间距收紧、卡片美化、板块标识与连板个股名称 (#101)
- 收紧看板全局间距(卡片间 gap/外层 padding 降至 6px),信息密度提升
- 卡片统一加微投影/毛玻璃/hover 浮起效果,标题加渐变指示条
- 四榜与概念/行业热度龙头个股加创/科/北板块标识
- 涨停梯队(后端 tiers 带 stocks)及最高连板卡片:≤3 只时显示个股名称

Co-authored-by: shy3130 <shy3130@users.noreply.github.com>
2026-07-11 16:57:23 +08:00
shy3130 d788183f01 feat(screener): 策略页新增分时列 + 按套餐截断 + 手动/自动刷新
- screener-columns.ts: 注册 builtin:intraday 列, 加入 signal 分组
- ScreenerTable.tsx: intraday 单元格渲染(MiniIntraday) + 表头眼睛开关
  + 手动刷新按钮(自动轮询关闭时显示) + 列间 border-l 分隔
- Screener.tsx: 分时数据全量加载, 按套餐 batch 上限截断(Pro=100/Expert=200);
  截断提示内联工具栏可关闭; 复用 minute_intraday_refresh 偏好控制
  盘中轮询(开关+实时行情运行双条件)
- Monitoring.tsx: 分时刷新开关描述补上策略页
- storage.ts: 新增 screenerIntraday 持久化键
2026-07-11 16:55:22 +08:00
shy3130 76fbd70e13 fix(ext-data): 行业/概念页拉取兼容字典信封, 修复新用户「接口返回不是数组」
_fetch_json 原硬性要求上游返回裸数组 [{...}], 部分用户网络环境
(CDN/WAF/网关) 会把数组包成 {data:[...]} 信封或返回错误 dict,
导致新用户首次「获取数据」必现 HTTP 400 弹窗。

- 兼容 {data/list/rows/result/results} 等常见信封键, 兜底遍历取首个数组
- 真不是数组时, 错误信息附带响应预览(截断200字)便于排查
- 通用拉取引擎已有 response_path 解包, 预设路径此前绕过了它
2026-07-11 13:17:56 +08:00
shy3130 56ee0e6c0f feat(signals): 新增价格上穿/下穿 MA5 MA10 信号
- pipeline.py: compute_signals() 与 compute_enriched_today() 双路径
  新增 signal_ma5_breakout/breakdown、signal_ma10_breakout/breakdown
  (盘中路径同步补 _prev_ma10 到 sig_prev)
- ENRICHED_COLUMNS 登记新列
- signals.ts: BUILTIN_SIGNAL_DEFINITIONS 加 4 条定义
- monitor.py: _SIGNAL_CN 加 4 条中文标签

注: 价格上穿/下穿 MA20 已有 (signal_ma20_breakout/breakdown), 本次补齐 MA5/MA10
2026-07-11 13:17:45 +08:00
wshy 33212a7d95 Merge pull request #100 from shy3130/feat/voice-broadcast
feat(voice): 监控告警语音播报 v0.1.84
2026-07-11 11:22:44 +08:00
shy3130 17994445e2 feat(voice): 监控告警语音播报 (播报个股名称与信号) v0.1.84
- 新增 voiceBroadcast.ts: 浏览器原生 speechSynthesis, 零依赖/零后端改动
- 按 source 分类拼接文案, 必念个股名称不念代码, 含涨跌幅
  · strategy: [名称] 进入/移出 策略「名」 [涨跌幅]
  · signal:   [名称] 买入/卖出信号触发 [涨跌幅]
  · price/market: [名称] [条件摘要] [涨跌幅]
- 默认偏好 Google 中国大陆语音 (音质最佳), 异步注入自动同步回显
- 整批合并一句 + 正在播报时丢弃新批次 (防快行情叠噪音)
- AlertToast 加 dispatchSideEffects 聚合点: 弹窗逻辑与副作用职责分离
- System 设置页加语音播报区: 开关(默认关)/音色/语速/试听
- 版本号同步升至 0.1.84 (VERSION / __version__ / package.json)
- README 监控中心加语音播报特性说明
2026-07-11 11:21:57 +08:00
wshy f1cc948eb2 Merge pull request #99 from shy3130/fix/datepicker-portal-overflow
fix(datepicker): 弹层改用 Portal+fixed 定位, 修复被布局遮挡
2026-07-11 10:58:31 +08:00
shy3130 26b7f552f0 fix(datepicker): 弹层改用 Portal+fixed 定位, 修复被布局遮挡
日期选择器弹层在参数优化页等含 overflow:hidden / transform 的容器内会被
裁剪或遮挡。改用 createPortal 渲染到 body, 配合 fixed 定位 + 视口坐标计算:

- 打开时按按钮位置计算坐标, 下方不足自动向上翻转, 水平溢出回拉
- 滚动/resize 时关闭弹层 (fixed 不跟随滚动, 关闭比重算更可靠)
- 外部点击检测适配 Portal (分别判断按钮与弹层 ref)
2026-07-11 10:57:56 +08:00
wshy 49a259d94d Merge pull request #95 from im47cn/perf/panelcache-single-flight
perf(panelcache): single-flight 消除并发同 key 缓存踩踏
2026-07-11 10:54:39 +08:00
wshy d1a229b7ab Merge pull request #98 from shy3130/feat/ext-data-default-enabled
feat(ext-data): 概念/行业定时拉取默认开启 + 修复启动顺序
2026-07-11 10:44:59 +08:00
shy3130 bdbce71217 feat(ext-data): 概念/行业定时拉取默认开启 + 修复启动顺序
- 概念/行业预设 enabled 由 False 改为 True, 全新部署首次启动即自动调度
- 修复 main.py 启动顺序: ensure_builtin_presets 移到 pull_scheduler.refresh
  之前, 否则全新部署时 scheduler 读不到刚创建的预设, 定时任务不启动
- 已有用户遵循"已存在则跳过"原则, 不受影响
2026-07-11 10:44:37 +08:00
wshy 8c71e954de Merge pull request #97 from shy3130/fix/ai-kimi-reasoning-temperature
fix(ai): 兼容 reasoning 模型 temperature 限制 + 透出上游真实错误
2026-07-11 10:22:17 +08:00
wshyandshy3130 773cd2fe12 chore(polling): 调整各档位轮询间隔范围 (#96)
* fix(ai): 兼容 reasoning 模型 temperature 限制 + 透出上游真实错误

Kimi kimi-k2.7-code 等 reasoning 模型拒绝非约定 temperature (Moonshot
报 "only 1 is allowed for this model"), 之前无条件下发导致 400 配置失败。

- 捕获 temperature 相关 400 后自动去掉 temperature 重试一次 (非流式 + 流式),
  不再依赖模型名猜测, 对任意 reasoning 模型稳健
- _format_openai_error 优先透出上游真实 detail, 仅无可读 detail 时回落到
  状态码通用文案, 避免吞掉 "model not found" 等排障关键信息
- 前端 Kimi 预设 model 更正为 kimi-k2.7-code

* chore(polling): 调整各档位轮询间隔范围

实时行情 (quote_service):
- pro 最小间隔 2s → 3s, starter 3s → 6s (expert/free 不变)
- DEFAULT_INTERVAL 10s → 6s

五档盘口 (depth_service):
- expert 区间上限 300s → 120s
- 默认值 20s → 10s

前端兜底默认值同步 (Data/Monitoring/DepthConfigCard):
- quote interval fallback 10→6, min 5→6
- depth interval fallback 20→10, expert hi 300→120

---------

Co-authored-by: shy3130 <shy3130@users.noreply.github.com>
2026-07-11 10:22:11 +08:00
shy3130 55c1984bf9 fix(ai): 兼容 reasoning 模型 temperature 限制 + 透出上游真实错误
Kimi kimi-k2.7-code 等 reasoning 模型拒绝非约定 temperature (Moonshot
报 "only 1 is allowed for this model"), 之前无条件下发导致 400 配置失败。

- 捕获 temperature 相关 400 后自动去掉 temperature 重试一次 (非流式 + 流式),
  不再依赖模型名猜测, 对任意 reasoning 模型稳健
- _format_openai_error 优先透出上游真实 detail, 仅无可读 detail 时回落到
  状态码通用文案, 避免吞掉 "model not found" 等排障关键信息
- 前端 Kimi 预设 model 更正为 kimi-k2.7-code
2026-07-11 10:20:03 +08:00