Commit Graph
192 Commits
Author SHA1 Message Date
shy3130 b43b177899 feat(monitor): add strategy signal event controls 2026-07-26 16:08:42 +08:00
shy3130 6f1c4032bb feat(backtest): toggle auxiliary nav series 2026-07-26 00:31:38 +08:00
shy3130 6d7092089c fix(data-source): harden custom source testing 2026-07-25 22:51:27 +08:00
shy3130 96fcbe4fff fix(data-source): validate custom request settings 2026-07-25 22:13:38 +08:00
wshy fffd89fce0 Merge pull request #138 from intfoo/feat/custom-source-timeout-param-ui
自定义数据源超时与请求参数名支持前端配置
2026-07-25 22:01:11 +08:00
HelloByeAll 852cc2f319 新增: 在回测净值图叠加仓位曲线 2026-07-24 21:12:26 +08:00
intfoo a15d0468cc feat(data-source): expose custom source timeout & request param names in UI
- backend: pass timeout through DatasetConfigIn / _config_to_dict / _sanitize_dataset
  (previously a hand-set YAML timeout was silently wiped on UI save, resetting to 30s)
- frontend: add per-dataset timeout input; collapsible 请求参数字段映射 with
  symbols/start/end_param (non-realtime) + asset_type/freq_param (minute);
  rename 字段映射 -> 响应参数字段映射; chevron at title end; realtime empty-state hint
- test: timeout config round-trip (custom value persists, default 30 not emitted)
- docs: document timeout in custom-data-source.md
2026-07-23 16:22:18 +08:00
shy3130 60fe9e6fa6 修复回测指标数据缺失提示问题 2026-07-22 13:30:02 +08:00
shy3130 a12e49d66d 修复实时行情下策略图表闪烁问题 2026-07-22 11:42:57 +08:00
shy3130 2c14fde788 完善个股点位提醒并发布0.1.87 2026-07-21 22:14:07 +08:00
shy3130 b4dae4bca1 修复股票与ETF切换误清策略池 2026-07-21 21:39:05 +08:00
shy3130 0ac6d6f56c 修复分析报告复制按钮兼容性 2026-07-21 20:50:34 +08:00
shy3130 0cb0b38437 修复看板监控通知个股弹窗 2026-07-20 17:10:15 +08:00
shy3130 72918fe602 优化策略卡片结果加载性能 2026-07-20 13:01:03 +08:00
shy3130 ed41f7b950 修复:个股详情量比开关显示异常 2026-07-20 09:59:14 +08:00
shy3130 4df3062002 fix: 完善自定义分钟数据源配置与异常处理
- 补齐资产类型和周期参数在 API、YAML 与前端编辑器中的保存回读

- 权限判断和分时监控统一复用安全的数据源解析边界

- 增加配置往返及解析异常回归测试
2026-07-19 17:01:33 +08:00
shy3130 b1b2494db9 merge: 合并 PR #128 自定义分钟数据源支持 2026-07-19 16:52:52 +08:00
shy3130 b17c882e27 feat: 完善历史换手率重算入口
- 财务数据统计纳入历史股本,并在数据页提供分批重算入口

- 缺少股本历史时阻止重算,并接入现有任务进度

- 财务分析卡片使用更新图标,明确数据拉取语义
2026-07-19 16:42:03 +08:00
intfoo 91e16db547 fix(layout): 修复 None 档+自定义实时源用户侧边栏指数行情不显示
承接 #121 review: Layout.tsx:631 的 !isNoneTier 一刀切判定与同文件
546 行实时行情开关判据不一致, 导致 None 档+自定义实时源用户监控页
放行、实时开关可用, 但侧边栏指数行情卡片不渲染。后端
/api/intraday/indices 从缓存读取不调 TickFlow, 对该用户有数据,
纯前端拦截导致 UI 体验不一致。

将 !isNoneTier 改为 (!isNoneTier || !!realtimeProviderName),
与实时行情开关判据对齐。
2026-07-19 13:35:36 +08:00
intfooandshy3130 e2ffde99d5 fix(minute,monitoring): 自定义数据源用户单股补拉/监控页打通 (#121)
承接 #126: 能力探测已补 KLINE_MINUTE_BATCH, 但两处取数/UI 仍按 TickFlow
档位拦截, 配了自定义分钟/实时源的 None 档用户依旧被挡。

- kline_sync.fetch_minute_single: 与 sync_minute_batch 一致, 先查
  preferences.get_minute_data_provider(), 非 tickflow 且自定义源有
  minute dataset 时走 custom provider, 避免分时图首次打开(本地无数据)
  补拉返回空。
- Monitoring.tsx: None 档但配了自定义实时源时, 后端
  is_realtime_allowed(realtime_mode=full_market) 仍返回 True。
  以 quoteStatus.realtime_allowed 作为最终判据, 不再用 isNoneTier
  一刀切拦截实时监控页。

Co-authored-by: shy3130 <shy3130@users.noreply.github.com>
2026-07-19 13:26:52 +08:00
shy3130 8e4258cf20 feat: support historical price limits and share capital 2026-07-18 23:37:54 +08:00
shy3130 ff12790042 feat: add trading-day volume comparison 2026-07-18 17:46:58 +08:00
shy3130 44ab51e65d feat: improve strategy scoring and backtest execution 2026-07-18 16:59:53 +08:00
shy3130 a1e8be58db feat: release v0.1.86 2026-07-18 00:49:28 +08:00
shy3130 34eaba3010 fix: handle uncovered walk-forward folds 2026-07-17 14:00:16 +08:00
shy3130 1e38c8cebb fix: reset strategy builder identity 2026-07-16 16:12:06 +08:00
shy3130 c763a6970f fix: restore strategy and data compatibility 2026-07-16 14:34:07 +08:00
lytem28 b6cf0495e1 feat: complete matrix-native backtest engine
Unify strategy execution across backtest, screener, and monitoring; isolate backtest workloads in spawn workers; and add shared matrix caching plus valid-bar indicator acceleration.
2026-07-16 12:17:27 +08:00
ChenJunheng 6f084e1b29 feat(watchlist): 支持从券商自选截图批量导入
基于 Tesseract OCR 识别六位代码并校验证券主数据,前端提供导入确认弹窗;
修复批量添加后 enriched 缓存未刷新导致需手动刷新页面的问题。
2026-07-15 23:25:46 +08:00
shy3130 0b3b769ca8 fix(strategy): clear saved strategy drafts 2026-07-15 20:51:07 +08:00
wshyandshy3130 e456d018fc feat(strategy): AI生成/自定义编写 tab 加策略开发指南链接 (#125)
两个 tab 的描述行都加了指向 GitHub strategy-guide.md 的链接,
方便用户参考指标列/信号列/filter_history 写法/参数类型等。

Co-authored-by: shy3130 <shy3130@users.noreply.github.com>
2026-07-14 21:59:56 +08:00
wshyandshy3130 ee17261feb fix(strategy): parsePyValue 对非 JSON 值兜底, 修复 AI 修改 date 参数崩溃 (#124)
get() 对字符串值去外层引号后传给 parsePyValue, 字符串型 default
(如 date 类型的 "2024-01-01") 去引号变成 2024-01-01, JSON.parse
在 position 4 报错 → 整个 StrategyBuilderDialog 白屏。

parsePyValue 加 try/catch: JSON.parse 失败时返回原始字符串。
数字/布尔不受影响, 字符串值兜底为字符串。

Co-authored-by: shy3130 <shy3130@users.noreply.github.com>
2026-07-14 21:52:24 +08:00
wshyandshy3130 f470e46f2b fix(security): 策略代码 RCE 漏洞三层修复 (#122)
漏洞链 (安全研究员 Aeon 报告):
1. StrategyCodeSaveRequest.strict 由客户端控制, 传 false 完全跳过安全校验
2. AST 名单只拦 ast.Name 直接调用, dunder 遍历可绕过
   ((lambda:0).__globals__["__builtins__"]["__import__"]("os"))
3. _load_file 用 exec_module 执行策略文件, 执行侧零校验
   => 未认证局域网用户可写入任意代码并立即执行 (RCE)

修复:
- strategy.py: 移除 strict 字段, 安全校验无条件执行 (第1层)
- ai_generator.py: _validate_safety 加固, 拦截 dunder 属性访问
  (__globals__/__builtins__/__class__/__subclasses__ 等) 和字符串下标
  访问 (第2层)
- engine.py: _load_file 在 exec_module 前读文件内容跑一次 _validate_safety,
  防止策略文件被直接篡改绕过 API 校验 (第3层 纵深防御)

前端: api.ts 移除 strict 参数, StrategyBuilderDialog/StrategyPoolDialog
移除 strict:true 传参 (前端本就全部传 true, 行为不变)

验证: PoC 三种攻击 payload 全部拦截, 正常策略(只import polars)无误杀

Co-authored-by: shy3130 <shy3130@users.noreply.github.com>
2026-07-14 21:32:39 +08:00
wshyandshy3130 70a4aad010 feat(monitor): 个股通知带行业/概念 ext 标签 + 全局配置 (#118)
监控中心顶部加全局配置(齿轮), 选择要在个股通知里展示的 ext 字段
(默认概念 ext_gn_ths + 行业 ext_hy_ths)。开启后触发记录卡片和推送
toast 统一显示行业(蓝)/概念(橙)标签。

架构: 源头富化 — 后端 SSE 推送前 (_enrich_alerts_ext) 用
_load_ext_value_maps (带 mtime 缓存) 按 symbol 富化; GET /api/alerts
读取时同样富化。前端零额外请求。

配置支持细粒度裁剪:
- 字段下拉按扩展表分组 (optgroup)
- 显示前N个 (maxTags), 留空=全部
- 隐藏指定位置 (hiddenIndices), 点数字划掉

数据结构 {field, maxTags, hiddenIndices} 向后兼容旧字符串格式。

后端:
- preferences.py: get_monitor_ext_fields + _normalize_ext_field
- settings.py: RealtimeMonitorConfigIn + get_preferences 加字段
- quote_service.py: _enrich_alerts_ext SSE 推送前富化
- alerts.py: list_alerts 加 ext_columns 参数

前端:
- api.ts: MonitorExtFieldItem 类型 + alertsList extColumns 参数
- Monitor.tsx: 齿轮入口 + 配置弹窗 + 卡片标签行
- AlertToast.tsx: toast 底行加标签

Co-authored-by: shy3130 <shy3130@users.noreply.github.com>
2026-07-14 18:49:23 +08:00
wshyandshy3130 d99112c160 分时图实时刷新: 日期修复 + 间隔可配 + 对话框实时 + 自选刷新图标 (#114)
* fix(kline): 分时图盘中显示昨天而非今天

get_minute_batch 的日期选择存在死循环: trade_date=date.today() 后立即
if trade_date==date.today() (恒真), 无条件用 latest_minute_date_global()
覆盖今天; 但批量实时补拉不落库, 该值盘中恒返回昨天 -> 永远回退到昨天 ->
expected=240 判定昨日完整 -> 不触发今天补拉。

改为仅在真正非交易日回退 (周末 + 收盘后无今日日K 的节假日), 盘中保持
今天让 completeness 检查触发实时补拉。同时全量 date.today()/datetime.now()
改用北京时间 cn_today()/cn_now(), 与 quote_service/monitor 等一致。

影响: 自选列表分时列 + 单股详情分时图。

* feat(settings): 分时图刷新间隔可配置 (默认6s, 范围3-60s)

原写死 15s 轮询, 现可在 实时监控设置 -> 分时图刷新 卡片用滑块调节。
复用 minute_intraday_refresh 偏好链路, 新增 minute_intraday_refresh_interval
字段 (preferences + settings API + 前端 Preferences 接口)。

- preferences.py: getter 默认6s clamp[3,60] + set/get config 接入
- settings.py: RealtimeMonitorConfigIn 字段 + get_preferences 返回
- Monitoring.tsx: 滑块控件 (参考行情轮询滑块, 2s 防抖保存)
- Watchlist/Screener: 15_000 -> interval*1000, fallback ?? 6

* feat(dialog): 个股对话框日K/分时实时刷新

日K走 SSE 精准刷新: 对话框打开时注册焦点股票, quotes_updated 推送时
invalidate ['kline', symbol], 后端 _maybe_inject_live_candle 只读内存
不调 TickFlow, 秒级零成本。关闭/切股自动注销焦点。

分时走轮询: 复用分时刷新开关 + 间隔偏好, 经 StockPanel.refetchIntervalMs
透传到 StockIntradayChart。分时端点数据不完整会调 TickFlow, 不接 SSE
避免打爆限流。

不影响回测弹窗 (不注册焦点、不传间隔)。

* feat(watchlist): 分时列表头加刷新图标 (与策略列表一致)

分时列可见 + 未自动刷新时显示手动刷新按钮 (点击 refetch, 加载旋转);
自动刷新中显示持续旋转小图标提示。逻辑/样式对齐 ScreenerTable。

* fix(badge): 真假涨停修正弹层被父级裁剪遮挡

弹出层原用 absolute 定位, 被看板卡片父级的 overflow-hidden +
backdrop-blur containing block 裁剪/遮挡。改用 createPortal 渲染到 body,
基于徽章 getBoundingClientRect 计算弹层坐标, 脱离父级裁剪。

---------

Co-authored-by: shy3130 <shy3130@users.noreply.github.com>
2026-07-14 15:46:18 +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
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
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
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 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 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