Commit Graph
112 Commits
Author SHA1 Message Date
Arepeater fec53bb46f fix(ai): 修复提供商切换与 Codex 自定义端点
- 分离 OpenAI-compatible 与 Codex CLI 的模型和推理配置
- 复用本机 Codex provider、认证与自定义端点,并适配 Docker 回环地址
- 隔离 OpenAI 专属 reasoning_effort,修复自定义预设切换与表单保留
2026-08-05 17:57:34 +08:00
Arepeater 7dad95c4f1 feat(settings): 支持配置数据任务超时
- 在数据源设置页配置普通任务与分钟 K 长任务超时
- 支持秒、分钟、小时输入,持久化时统一换算为秒
- 保留 1200/1800 秒默认值,配置仅影响新创建的任务
2026-08-05 17:57:05 +08:00
shy3130 c9f01ba288 feat: 行业轮动分析(参数化复用概念版) + 扩展数据列数字格式化
行业轮动分析(完全对齐概念版)
- 概念轮动逻辑参数化支持 industry 维度, 一套代码服务两个页面(复用 _dimension_field)
- 后端 build_rps_rotation/analyze_rotation_stream 加 kind 参数; 缓存按 kind+level 隔离
- 行业层级: 1/2/3 级可切换, 默认 2 级(与 _dimension_rank 口径一致)
  · 原始 257 个 3 级行业 → level=2 合并成 90 个
- AI 分析 prompt 动态化(概念/行业措辞), _SYSTEM_PROMPT 改为 _build_system_prompt(kind)
- 前端 RpsRotationDialog 加 kind prop; industry 显示层级选择器(1/2/3级默认2)
- IndustryAnalysis 加「涨幅RPS轮动分析」按钮 + dialog(kind=industry)
- ConceptAnalysis 不动(默认 kind=concept 向后兼容)

扩展数据列数字格式化(自选/策略列表)
- 列配置新增 3 项(仅数字类型字段): 千分位逗号 / 单位换算(万/亿/自动) / 小数位
- 新增 formatExtNumber 共享格式化函数(千分位+换算+小数位, 去尾零)
- Watchlist + ScreenerTable 的 number 渲染接 formatExtNumber
- ext source 加 fieldType; 旧列无 fieldType 时默认显示数字配置(放宽判断)
- ExtColumnDisplayConfig 加 thousandSeparator/unitConvert/unitDecimals 字段

验证
- 后端 583 passed; 行业映射实测 257→90(level=2)
- 数字格式化实测: 千分位 1,234,567 / 万换算 123.46万 / 自动 等均正确
- tsc + pnpm build 通过
2026-08-02 19:18:07 +08:00
shy3130 93558cf3b3 feat(regime): 市场环境页全面增强 — 内存可控/开关/画像/美化/分批可配
后端
- 修复 /recompute 全量分支误调增量函数致 computed 恒 0: 改为 earliest~today
  走 run_regime_batch + upsert 强制覆盖
- 修复 /history 日期范围模式被 limit 默认 120 截断: limit 仅在未传 start/end 时生效
- _scan_enriched_fallback:
  · 修复 compute_limit_signals 漏传 instruments 参数致慢路径必抛异常
  · needed 白名单: 只算 regime 用到的列, 全量峰值 6.8GB→3.2GB
  · 分批 + warmup: 范围超 batch_days 时切片, 每批带 warmup 前缀算后 concat,
    全量峰值再降至 ~1.9GB; batch/warmup 参数从用户偏好读取
- _aggregate_daily: 逐日 filter 扫全表(O(N²))改为 polars 向量化 group_by
- 新增 enriched_date_set / earliest_enriched_date 辅助函数
- 盘后管道 regime 计算加开关(preferences.get_pipeline_regime_enabled), 默认关闭
- 新增偏好: regime_batch_days(默认60, 25~500) / regime_warmup_days(默认40, 35~90)
- 新增 API: PUT /preferences/pipeline-regime-enabled, PUT /preferences/regime-batch-params

前端
- 市场环境页时间范围控件: select 三档 → 4 档按钮组(1年/2年/自定义/全部)
  · 自定义弹窗手动输入天数(1~1000 钳制) + 快捷预设
  · 全部走 regimeCoverage 拿实际日期范围用 start/end 请求
- 市场环境页美化(对齐 Dashboard 设计语言): 渐变条头部/半透明卡片/SectionTitle/
  指标卡进度条/语义色 bull-bear
- 新建 RegimeConfigCard: 盘后自动计算开关 + 全量回填分批参数(每批天数/预热天数/
  快捷预设省内存·默认·更快)
- 数据页画像加市场环境卡片(独立 regimeCoverage 查询, 同步后刷新, 齿轮设置弹窗)
- 菜单排序设置 + 侧边栏 nav 加入市场环境, 默认排在复盘上面
- Layout 市场环境菜单 badge 改用琥珀胶囊(对齐 Settings 数据源标签)

验证
- 后端 582 passed; 真实数据全量回填产出 986 天, 内存峰值 ~1.9GB(原 6.8GB)
- 前端 tsc + pnpm build 通过
- 分批参数实测生效: batch=30 峰值 1896MB / batch=500 峰值 3772MB
2026-08-02 16:25:21 +08:00
shy3130 46ebcd89c8 fix(regime): 修复重算无产出并补全 beta 标签
后端
- /recompute 全量分支原本误调 compute_regime_incremental(增量补差),
  无缺口/stale 时直接返回 0 行; 改为用 earliest_enriched_date ~ 今天
  走 run_regime_batch + upsert 强制覆盖, 真正实现"全量重算"
- _scan_enriched_fallback 原本漏调 compute_limit_signals 的 instruments
  参数致慢路径必抛异常 → enriched 缓存未预热时拿不到含信号列数据 → 聚合
  返回空; 改用主管道同款 compute_all(df, instruments, historical_shares)
  一站式补算, 与 indicators/pipeline.py 对齐
- 新增 enriched_date_set / earliest_enriched_date 辅助函数(抽出复用)

前端
- Regime 重算按钮: 加"重算中…"文字反馈 + 完成 toast(区分有/无新数据) +
  catch 捕获错误(原异常被静默吞掉)
- 市场环境菜单加 beta 胶囊标签(对齐 Settings 数据源标签样式)

验证
- 后端 582 passed; 真实数据全量重算产出 986 行(2022-07-08 ~ 2026-07-31)
- 前端 tsc + pnpm build 通过
2026-08-02 12:22:48 +08:00
shy3130 4ee55e40c6 feat(regime): 新增市场状态识别系统并接入叠加策略回测过滤器
后端
- 新增 services/regime_builder.py:5 档日级市场状态分级(strong/lean_strong/range/lean_weak/weak)
- 新增 api/regime.py:5 个 regime 查询接口
- daily_pipeline 接入 regime 构建(step 2.6,soft-fail + 双检测自愈)
- 回测 strategy.py 新增 regime_filter 过滤:采用 T-1 防未来函数,仅作用于入场信号
  在 composite / matrix_native / prepared 三处注入 entry_time_mask
- backtest API 透传 regime_filter(REST + SSE);_make_job_key 纳入 regime_filter 以隔离缓存
- 兼容 _RepoStub 测试夹具(store 属性缺失场景)

前端
- 新增 Regime 页面、路由、导航入口、api 类型与 queryKeys
- 叠加策略回测页新增 regime 过滤器控件

测试
- 新增 tests/test_regime_builder.py(18 项),覆盖分级逻辑、T-1 防未来函数、空值降级、三处 mask 注入
- 全量后端测试 582 passed;前端 pnpm build 通过;git diff --check 无空白错误
2026-08-02 12:05:20 +08:00
shy3130 4696fef959 feat(composite): 新增叠加策略(composite)支持选股与回测
引入第四种执行后端 composite: 声明式引用多个子策略, 复用现有注册表/缓存/撮合全链路。

核心设计:
- 退出采用来源投影(每个子策略 exit 仅在自己持仓窗口生效, 不串平其他子策略仓位)
- 评分用标准化排名加权(跨子策略可比, 不依赖 per-strategy 的 min-max 量纲)
- 来源归因(entry_signal_code 标记来源子策略)

后端:
- engine.py: CompositeSpec 数据模型, 两阶段引用校验(孤儿移除/嵌套禁止/asset_types 子集/≤8 上限), _run_composite_strategy 选股执行, find_dependents 删除防护, override_loader 注入保证子策略与单独跑同口径
- composite.py(新): 选股 merge_results + 回测 merge_signal_matrices 合并器(退出投影/排名融合/归因)
- backtest/strategy.py: composite 回测分支(特征计划合并/逐子信号/统一风控/basic_filter/归因)
- api/strategy.py: POST /composite/save 端点, _strategy_detail 返回 composite_children(含 name/source), 删除依赖 409 防护
- monitor.py: 实时监控 fail-closed(回退盘后缓存)
- walkforward.py: 对 composite 显式中文报错
- worker.py: _strategy_dirs 补 composite 目录(修复回测子进程找不到 composite 的 bug)

前端:
- CompositeStrategyDialog(新): 创建/编辑弹窗(自动生成 composite_ ID, 权重归一, 类型标签, 增删子策略)
- StrategySettingsDialog: composite 设置面板(子策略增删改/归一/类型标签)
- StrategyBacktest: 回测页 composite 适配(分组/参数区/结果区展示子策略构成)
- api.ts: 类型扩展(source/backend 加 composite, composite_children 含 name/source)

测试: 36 个 composite 专项测试(加载/选股/回测退出投影/排名/归因/override 透传/API/删除防护), 端到端验证真实内置策略回测通过
2026-08-02 10:56:03 +08:00
shy3130 a4e580e314 fix(security): 修复 ext_columns DuckDB SQL 注入 (closes #150)
Issue #150 报告了 ext_columns 功能的 DuckDB SQL 注入, 经核查属实:
field_name 经裸双引号拼进 SQL, 攻击者可借 COPY TO 写文件 (RCE) 或
UNION 读数据。已修复, 详见 PR。

根因: 3 个 SQL sink 用未转义的 f"{field_name}", 仅 screener:147 一处
用 _quote_ident 转义; 且 parser 校验不一致 (screener 校验 config_id,
kline/watchlist 零校验)。

修复方案:
1. 新增 backend/app/db_safe.py 集中定义 quote_ident (双引号转义, 对任意
   字符安全) + is_valid_ext_ident (config_id 白名单), 消除不一致根因
2. 3 个 sink 统一改用 quote_ident:
   - screener.py:785 (limit_ladder)
   - kline.py:376 (_attach_ext, GET /api/kline/daily)
   - watchlist.py:279 (watchlist_enriched)
3. kline + watchlist parser 加 config_id 白名单 (screener 已有)
4. screener 复用共享原语, 删除私有 _quote_ident/_EXT_IDENT_RE

关键约束:
- field_name 不能加白名单: FieldDef.name 无校验 + infer_fields_from_df
  直接采用原始 CSV/Parquet 列名, 合法可含中文/点。故只在 sink 转义。
- 不关闭 enable_external_access: 实测会同时禁用 read_parquet (项目核心
  数据访问方式), 不可行。

对 Issue #150 报告的澄清:
- sink #2/#3/#4 (field_name 注入) 属实, 已修
- sink #1 (screener.py:343 conditions[]/order_by) 不存在, 为误报
  (screener.py 无 conditions/order_by/execute 调用)

验证: 后端全量 527 passed (含新增 24 个安全测试, 含 2 个端到端注入
测试: COPY TO 不产生文件 / UNION 不泄漏数据 + 合法特殊字段名回归)
2026-07-31 19:56:33 +08:00
shy3130 192f6c4aa9 feat(search): 标的搜索支持拼音首字母 + 创业/科创/北交所徽标
## 1. 拼音首字母搜索 (同花顺式)

后端 search_instruments 新增拼音匹配层, 输入 payh 可命中「平安银行」。
- 辅助函数 _name_pinyin_keys 用 lru_cache 缓存「名称→首字母串」, 命中后
  近似 dict 查找, 全市场遍历 < 1ms
- 多音字用 heteronym 笛卡尔积展开, 「重庆」同时匹配 cq/zq 两种读音;
  并加载 A 股高频地名词典 (重庆/长安/长春/长沙/长城/长江)
- 拼音分支仅在纯 ASCII 字母输入时触发, 中文/数字搜索零开销跳过,
  完全向后兼容
- 搜索分层: ① code/symbol 前缀 → ② 拼音首字母前缀 → ③ 包含匹配

新增依赖: pypinyin>=0.50 (纯 Python, 无 C 扩展)
新增测试: tests/test_instrument_search.py (15 用例覆盖拼音/多音字/兼容/边界)

## 2. 搜索结果显示板块徽标

自选/财务搜索/监控规则/回测选标的 四个搜索入口的结果项, 现在会显示
创业板(橙「创」)/科创板(青「科」)/北交所(紫「北」)彩色徽标。
- 复用项目既有的 boardTag (@/components/stock-table/primitives),
  与自选/策略/Dashboard 表格徽标样式完全统一
- 沪深主板不显示徽标 (信息量低, 保持简洁)

## 验证
- 后端全量测试 503 passed (含新增 15)
- 前端 tsc --noEmit 零错误
2026-07-31 19:38:36 +08:00
wshy 84bc6cb875 Merge pull request #145 from intfoo/feat/index-support
自选/监控/个股分析支持指数(asset_type="index")
2026-07-31 19:19:17 +08:00
wshy b59be65107 Merge pull request #143 from CJ0Hn/feat/watchlist-screenshot-import
fix: 自选截图导入优化
2026-07-31 19:11:12 +08:00
intfoo 3e6f9bcfb7 fix(index): 后端分钟K隔离加固与规则资产类型纠正
- kline.sync_minute_single 对指数 symbol 显式 400 (防污染 kline_minute)
- kline.sync_minute 全市场 universe 剔除指数 symbol
- kline.get_minute 响应新增 asset_type 字段 (3 处 return)
- monitor_rules._reconcile_index_asset_type 纠正误存为 stock 的指数规则
  (应用于 save_rule/list_rules/_sync_engine, 混合池不动)
- 文案资产中立化: "个股信号→信号" "指定股票→指定标的" (options label + 校验报错)
- 测试: reconcile 5 断言 + sync_minute_single 拒指数 400 + 分时报错断言同步
2026-07-26 19:59:10 +08:00
intfoo ed4355e0ea feat: 指数(asset_type=index)后端接入 — 数据路由/自选enriched/监控指数轮/隔离防污染
- 数据路由: get_name_map 合并指数维表; get_enriched_latest_asset("index") 缓存+flush/merge 分支; daily-batch 按资产分组
- 自选: watchlist_enriched 指数分支 + 行级 asset_type 标注
- 监控: MonitorRuleEngine 第三轮指数评估 (signal/price); 指数实时焐热复刻 ETF flush; Free档自选实时资产分流; 规则校验 (禁 strategy/market/ladder/分时信号)
- 隔离: _resolve_universe 过滤指数防污染股票日K/分钟K; 指数轮 reset_strategy_results=False; 策略/回测/screener 零改动
- AI 分析: prompt 指数无财务文案
2026-07-26 18:35:31 +08:00
shy3130 b43b177899 feat(monitor): add strategy signal event controls 2026-07-26 16:08:42 +08:00
CJohn e526d25fd0 fix(ocr): 完善截图导入的安全限制与交互状态
- 在 Pillow 完整解码前检查图片像素数,并将 DecompressionBombError
  转为明确的参数错误,避免压缩大图在校验前占用过多内存
- 为 OCR 线程任务设置独立的 AnyIO CapacityLimiter(2),最多同时执行
  两次图片解码与 Tesseract 识别,其余请求排队等待
- 前端调用 ocr-status 检查 Tesseract 是否可用;不可用时禁用截图导入
  入口,并按 Windows、macOS 和 Linux 显示对应安装说明
- 关闭弹窗或重新选择图片时取消旧请求,并通过请求代次校验忽略迟到
  的异步结果,防止旧候选回写到新弹窗
- 禁用已存在于自选列表中的候选项,批量添加接口返回实际净新增数量,
  成功提示使用后端返回值
- 补充图片类型与大小校验、解压炸弹、OCR 状态、并发限制以及批量添加
  数量等测试
2026-07-26 12:19:55 +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
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 2d9fe4a4af 修复策略文件无法删除问题 2026-07-22 11:21:13 +08:00
shy3130 72918fe602 优化策略卡片结果加载性能 2026-07-20 13:01:03 +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 4c85f99633 fix(minute): 自定义分钟数据源用户单股补拉/监控页整改 (承接 #121 review) 2026-07-19 13:35:35 +08:00
shy3130 8e4258cf20 feat: support historical price limits and share capital 2026-07-18 23:37:54 +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 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 0d6b341945 fix(watchlist): 采纳 #94 审查 — 防大图 OOM、OCR 不阻塞事件循环、收紧图片类型
为截图预处理增加像素上限与长边降采样;import-image 将 OCR 放入线程池;
去掉任意 image/* 放行,仅允许白名单 MIME/扩展名。
2026-07-15 23:28:30 +08:00
ChenJunheng 6f084e1b29 feat(watchlist): 支持从券商自选截图批量导入
基于 Tesseract OCR 识别六位代码并校验证券主数据,前端提供导入确认弹窗;
修复批量添加后 enriched 缓存未刷新导致需手动刷新页面的问题。
2026-07-15 23:25:46 +08:00
shy3130 f317377bef fix(strategy): apply saved params to builtin screeners 2026-07-15 20:06:11 +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 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 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
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
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 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
shy3130 2014da57a9 Merge PR #103: Codex CLI 增加 GPT-5.6 支持 + 安全隔离
冲突解决: 保留自定义预设 + GPT-5.6 模型选择 + reasoning_effort 配置
2026-07-12 11:07:16 +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 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
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 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