Commit Graph
395 Commits
Author SHA1 Message Date
shy3130 58b161b16d fix(enriched): 自愈孤儿 publishing 标记, 步进优化不再误报 publishing
清库半删除等异常会留下 owner 已死但状态仍为 publishing 的 generation
标记, 后续读取直接抛 EnrichedGenerationUnavailableError, 步进优化连锁失败。

- 读取侧仅在「owner 是其他进程且该进程已死」时判孤儿并在独占锁内
  自愈 (换新 uuid 重建 ready 标记); 同 pid 无活跃对象时保守不判,
  避免暴露清库半删除的数据 (fail-closed)
- 回测引擎改为 data_generation_await 轮询等待 (300s 上限), 遇短暂
  publishing 自动重试而非直接失败; worker 错误文案同步为可重试提示
2026-09-07 15:25:27 +08:00
kevin9327 6876db1a86 perf(ext-data): CSV 编码转换改为分块进行, 峰值内存不再随文件线性增长 2026-09-07 08:10:38 +09:00
kevin9327 f2fac2e8f0 fix(ext-data): CSV 编码转换保留原始换行, 避免最后一列被推断为字符串 2026-09-07 08:07:02 +09:00
shy3130 4a27dc45d5 fix(docker): 修复容器启动卡死 — uv 锁过期触发运行时重装 + 运行时无镜像源
47 服务器部署实测暴露两个连环问题:
1. main 上的 uv.lock 停留在 0.2.2 (pyproject 已 0.2.3), 容器 CMD 是
   'uv run uvicorn', 启动时 uv 校验锁不一致 → 在容器内重新锁+重装依赖;
2. uv 国内源 (UV_DEFAULT_INDEX) 只在构建期 RUN 内 export, 未持久化为
   ENV —— 运行时重装走默认 pypi.org, 国内网络直连极慢, 容器表现为
   零日志不监听。

修复: 重新生成 uv.lock (仅项目版本 0.2.2→0.2.3, 依赖版本不变, 源 URL
改用阿里云镜像); Dockerfile 将 UV_DEFAULT_INDEX/UV_EXTRA_INDEX_URL
持久化为运行时 ENV, 与构建期同源。

验证: 本地 uv run --locked 通过 (锁与 pyproject 一致, 0.2.3)。
2026-09-07 00:34:34 +08:00
shy3130 8131cfe853 feat(pipeline): 定时复盘默认 15:40 — 等盘后管道跑完再复盘
默认 15:10 的"即时复盘"走实时快照缓存, 不含盘后管道产出的最终口径
(含盘后量校正的日K/enriched)。默认改为 15:40: 盘后管道默认 15:35
启动后留 5 分钟缓冲, 复盘直接使用管道数据。

强制下限仍为 15:00, 偏好即时复盘的用户可自行调早; 存量已保存的
review_schedule 不受影响 (读取不迁移)。

验证: 无本地存储时默认生效 {'hour': 15, 'minute': 40}; prefer/pipeline/
review 相关测试 67 通过; ruff 与 HEAD 一致无新增。
2026-09-06 23:38:55 +08:00
shy3130 ddb265b0f8 feat(ext-data): 出站请求默认携带 tsp 标识头
扩展数据的三个出站点 (定时/手动拉取与历史回补、内置预设拉取、URL 探测
测试) 统一经 outbound_headers() 注入 User-Agent: tsp/<版本> 与
X-TSP-Client: tick-stock-panel, 服务端 (如 tickflow-hub) 可据此识别
本项目的请求来源。

用户在拉取配置里显式设置的同名请求头优先 (大小写不敏感匹配, 不重复
发送), 需要特定 UA 的数据源不受影响。

验证: pytest 22/22 (含 3 个新用例: 默认标识头/用户头优先/实际请求携带);
逐文件 ruff 与 HEAD 对比零新增。
2026-09-06 23:16:27 +08:00
shy3130 ecbb45b8ef feat(pipeline): 盘后管道默认 15:35, 与盘后固定价终点 (15:30) 拉开缓冲
实测 2026-09-04 数据: 836 只可比标的中 24 只日K量大于分钟K合计 (最大
5.4%), 差额产生于 15:00 收盘后的盘后时段 (盘后固定价/大宗类成交计入
日成交量但不产生分钟K) —— 盘中实时落盘的日K注定缺失该尾巴, 只有
15:30 之后的官方日线才完整。

- 默认调度 15:30 → 15:35: 避开供应商日线未定稿窗口, 也与 quote 定版
  重试窗口终点 (15:30) 不再精确重合
- set_pipeline_schedule 保存下限从 15:00 收紧到 15:35 (存量已存配置
  不迁移, 读取不钳制, 仅下次显式保存时生效)
- 定时复盘默认 15:10 (即时复盘走实时快照缓存, 不依赖管道) 刻意不动

验证: set 钳制冒烟 (15:30→15:35, 15:35/15:36 原样); ruff 与 HEAD 基线
一致无新增; pytest prefer/pipeline/daily 相关 128 通过。
2026-09-06 22:35:24 +08:00
shy3130 cd9e20610f feat(ext-data): 时序表历史回补 — date_param 按日拉取 + tickflow-hub 404 契约
人气排行等 timeseries 扩展表此前只能从开启拉取之日起逐日累积。拉取配置
新增日期参数名 (date_param), 接口支持按日查询时即可历史回补: 按本地交易日
逐日请求 ?{date_param}=YYYY-MM-DD 写入对应分区, 幂等 (已有分区跳过),
单次上限 120 天, 429 限流退避重试 + 连续限流中止可续补。

适配 tickflow-hub PR#19 (/exports、/fuyao-rank 按日历史截面):
- 该日无快照返回 404 → 计入 empty 跳过, 不进失败清单 (主循环与 429
  退避重试两条路径均覆盖)
- 防污染契约: 响应行 date 与请求日期不一致 (接口忽略日期参数) 时
  fail-closed 拒写该分区, 避免当日值污染整个历史时序
- 修复调度器 next_run 计算的 UTC 未导入 NameError

前端拉取面板新增日期参数名配置与回补入口; 回补落盘的历史数据立即进入
信号/因子/回测按日对齐通道 (PIT)。

验证: pytest tests/test_ext_backfill.py 16 通过 (含 404/429/幂等/防污染),
ext 全套 144 通过; pnpm build 通过。
2026-09-06 22:35:08 +08:00
wshy f275edafeb Merge pull request #256 from SummerCaptain/feat/recap-push-gate
feat(preferences): 复盘推送 review_push_mode 门控
2026-09-06 20:53:29 +08:00
wshy f0c0fd3bf8 Merge pull request #255 from SummerCaptain/feat/ai-strategy-draft-gate
feat(strategy): AI 策略保存为 research_only 草稿 + publish 端点
2026-09-06 20:53:25 +08:00
wshy c94fe2bf31 Merge pull request #251 from 0112020179/codex/fix-fuyao-history-oom
修复扶摇历史日K同步内存溢出
2026-09-06 20:53:22 +08:00
shy3130 e3a2fbce13 feat(ext-data): 扩展表字段接入信号与因子(数值因子/评分 + string 归属筛选)
数值字段(int/float) → 信号+因子双通道:
- ext_factors: 帧组装时 join 扩展列并注册 kind=base 因子(分组「扩展数据」),
  时序模式按 (symbol,交易日) 精确对齐无未来函数, 快照模式仅当日单日帧
  注入(历史帧跳过防未来函数)
- registry.all_factors 惰性同步(配置目录签名幂等, 以注册表为权威增删);
  custom_signals.allowed_fields 自动并入 → 信号下拉/因子库/AI提示词/检验
  同一份清单; factor 补算入口按需注入
- 失效链: 上传/拉取/配置变更自动清扩展帧缓存+策略缓存, API层补
  repo.clear_cache; 写入后下一次计算立即生效
- 列名保留中文(预设表字段名), 非ASCII数值字段只进信号不注册因子
  (DSL标识符ASCII-only)

string 字段(概念/行业归属) → 仅信号条件通道:
- 运算符 包含(contains,字面量匹配非正则)/等于/不等于, 右值为字符串字面量,
  可与数值条件混合(强势板块归属 AND 热度阈值)
- 前端信号编辑器按字段类型切换运算符与右值输入; /options 暴露
  stringFields; AI 提示词含字符串字段清单与 contains 用法
- string 不注册为因子(数值口径), 空值不误报

测试: test_ext_factors 18个(PIT对齐/跨日不泄露/快照门控/写入失效/
contains字面量/中文列名端到端等); 存量因子计数测试补 data/ 运行时隔离
夹具(黄金断言不依赖本机扩展表); 受影响回归148个全过; pnpm build 通过;
ruff 对齐 main 基线
2026-09-06 18:27:33 +08:00
shy3130 716897f41a feat(signals): 信号库新增盘中信号(分钟K特征)
- intraday_features: 会话对齐特征帧构造器(vwap/涨跌幅/1·3·5分钟放量比/
  日内与开盘30分钟高低点距离, 滚动窗口不跨午休, 只用已收盘bar防未来函数)
- custom_signals: timeframe=daily|intraday 双 schema, 盘中条件支持
  cross_up/cross_down 穿越算子; 输出为当日条件上升沿(首bar不触发,
  null特征判false绝不误报)
- 旧4个分时穿越信号列名零迁移(评估器回映射历史列名, 存量监控规则不动)
- 引擎单点注入 csgi_ 列: 监控/分钟策略/分钟回测共用同一构造器;
  日线策略引用盘中信号显式报错
- 回放验证 API /api/custom-signals/intraday/replay(本地历史分钟K重放,
  区间≤60天标的≤200, 先验证再配置监控)
- 能力门槛: 分钟K能力(订阅池)或全量分钟能力(本地分区); 回放仅需本地历史

验证: 新增16个测试(特征数值/边界/旧4信号黄金等价/引擎注入/回放端点),
受影响回归109个全过, ruff对齐基线, docs/features.md 同步
2026-09-06 18:26:29 +08:00
0112020179 3d6beb3c35 fix(fuyao): 根据审查完善流式同步 2026-09-06 17:39:43 +08:00
sc 9d55c237ed feat(preferences): review_push_mode 端点与前端开关配套
- settings: GET 返回 review_push_mode, PUT review-push 支持 mode 入参
- market_recap: push 移入 SaveReportRequest body(显式外发)
- Review 页: 推送触发方式 auto/manual 开关
- 门控测试: 手动/定时两条路径的 manual/auto 推送断言
2026-09-06 16:20:42 +08:00
sc e6c7fa4a43 feat(strategy): 策略池 AI 草稿分区与发布闸前端配套
- 后端 publish 失败由 ValueError 改为 HTTPException(500) 返回给前端
- 策略池 AI 标签新增「草稿」分区, 展示 research_only 策略并提供发布按钮
- 导入/保存 AI 策略为草稿时提示发布后可用, onSavedId 对草稿态跳过入池校验
2026-09-06 16:05:02 +08:00
sc 905fb8846c feat(preferences): 复盘推送 review_push_mode 门控 2026-09-06 13:41:52 +08:00
sc f6fc22fef1 feat(strategy): AI 策略保存为 research_only 草稿 + publish 端点 2026-09-06 13:41:51 +08:00
0112020179 1c022a1c7c fix(fuyao): 流式同步历史日K避免内存溢出 2026-09-06 12:35:26 +08:00
wshy 900dbea59c Merge pull request #246 from 0112020179/codex/custom-depth-provider-245
feat(data-source): support custom depth providers
2026-09-06 09:51:41 +08:00
wshy 2ebb9f0b11 Merge pull request #249 from kevin9327/fix/watchlist-upload-cap
fix(watchlist): 自选导入上传分块读取, 越限即拒绝而非读完再拒
2026-09-06 09:51:36 +08:00
wshy c08e26ddd1 Merge pull request #250 from kevin9327/fix/feishu-test-attempt-count
fix(notifications): 飞书推送按传入的 max_attempts 退避与记日志
2026-09-06 09:51:33 +08:00
0112020179 b5a005a8ac feat(data-source): support custom depth providers
Closes #245
2026-09-05 22:49:52 +08:00
shy3130 c289ac5767 fix(backtest): 步进优化支持 python_history_legacy/polars_expr 策略
问题: WalkForwardService._prepare_shared_matrix 对非 matrix_native 策略直接抛
"步进优化暂仅支持矩阵策略", filter_history 类自定义策略 (如 everbloom 系列)
无法做步进优化。

根因: 闸门把非矩阵后端一刀切, 但 run() 内部本就存在 shared_market_data=None
的通用路径 (每折独立优化 + 独立 OOS 回测), 只是永远不会被走到。

方案: 闸门改分流 —— matrix_native 仍走共享矩阵加速路径; python_history_legacy
与 polars_expr 返回 None 走通用每折回测路径 (正确但无矩阵加速); composite /
minute_filter 维持 fail-closed 拒绝, 错误文案同步更新。

兼容: matrix_native 行为不变; 结果字段 shared_market_data=false 标识通用路径。
性能: 非矩阵策略每组合×每折一次完整回测, 大网格耗时线性放大 (UI 已有耗时提示)。

验证: uv run --frozen pytest tests/backtest/test_walkforward.py -q (23 passed,
含新增 3 例: legacy 走通用路径/polars_expr 放行/minute_filter 拒绝);
tests/backtest 全套 282 passed; ruff 无新增告警;
真实 API 端到端: everbloom_tupengpan 2x2 网格 120/30 步进 8/8 折全部完成。
2026-09-05 20:33:31 +08:00
kevin9327 d46e601863 fix(notifications): 飞书推送按传入的 max_attempts 退避与记日志
_post_feishu 的退避判断与最终 WARNING 仍用模块常量 _FEISHU_MAX_ATTEMPTS(3),
设置页「发送测试消息」传 max_attempts=1 时: 唯一一次失败后仍 sleep 1 秒才返回,
日志写「已重试 3 次」而实际只试了 1 次。两处改用 max_attempts; 生产路径不变。

测试: 单次尝试不退避且日志计数为 1 (未修复时失败), 默认 3 次仍退避 1s、2s。
2026-09-05 21:14:53 +09:00
kevin9327 e12e0c7d02 fix(watchlist): 自选导入上传分块读取, 越限即拒绝而非读完再拒
import-csv / import-image 原先 `await file.read()` 之后才比较长度, 上限只在
整个文件进入内存之后生效, 与 issue #204 修复前的扩展数据上传同类。新增
_read_upload_capped 分块读取, 累计超过上限的那一块立即返回 400 并停止读取,
状态码与文案不变; 内存占用不超过上限 + 一块。

测试: 新增 5 例 (含「越限后不再继续读取」的字节计数断言); test_watchlist_csv
的上传 mock 改为像真实 UploadFile 一样读尽返回 b""。
2026-09-05 21:11:44 +09:00
shy3130 2ff909cd36 fix(notifications): #244 维护者补丁 — 移除预埋类型并修正全角标点
- 移除 api.ts 中与通知无关的 MainCostEstimate/StockLevels.main_cost 类型(对应后端字段在本仓库不存在, 属跨 PR 预埋, 全仓库零引用)
- settings.py 5 处 PR 新增字符串的全角逗号改半角, RUF001 恢复到 main 基线(28 ≤ 29)
- 验证: 通知测试 48 passed, 前端 build 通过
2026-09-05 17:16:58 +08:00
wshy c1cad36449 Merge pull request #244 from yushenglin01/feat/notification-channels
feat(notifications): add custom webhook and email channels
2026-09-05 17:14:25 +08:00
shy3130 e0cd625ef4 feat(platform): 因子平台与因子↔策略双向联动 v0.2.3
- 因子平台: /factors 一级页(检验/因子库/编辑器/组合/挖掘), DSL 公式因子(25 算子点选、双语字段、我的因子模板、脏公式守卫), 版本与生命周期, 自动挖掘 L1 统计筛选
- 因子↔策略四条桥: 触发器 Zap 快建因子条件信号、因子一键生成排名策略、自定义信号 AI 提示词接入因子分组、策略回测因子归因(胜/败单入场信号日因子均值, 独立 tab, 双语因子名)
- 回测: 统计卡新增盈亏比(≥1 红/<1 绿), 蒙卡回撤合并为中位/95% 双值卡(自适应字号), 高级设置基础过滤与策略编辑器参数对齐(5 组区间)
- 信号库独立页 /signals(原设置 tab 迁出), 持仓提醒入导航; 挖掘并入因子页第 5 tab, /mining 旧链接重定向
- 研究线配套: 因子目录 61→77(评分/矩阵双内核), stats_v2(Newey-West/BH-FDR/DSR), enriched 管道与异动/报价服务配套调整
- 文档: README 导航与特性表、features.md 因子平台章节、操作说明书 9.2、factor-platform-plan 执行状态与 §5、二开文档桥接说明; 交流与支持节改版
- 版本 0.2.2 → 0.2.3; 后端全量 1625 passed(1 例环境性跳过), 前端 build 通过
2026-09-05 15:41:15 +08:00
yushenglin 5289cde13e feat(notifications): add custom webhook and email channels 2026-09-04 16:37:06 +08:00
shy3130 bab609b2d4 fix(ai): DeepSeek thinking 禁用参数被 400 拒绝时去参重试 (#240 跟进)
_openai_kwargs 对 DeepSeek V4 官方域名注入的 thinking 禁用参数
(extra_body) 属外部 API 契约, 模型/接口版本差异可能报 400。照
temperature / reasoning_effort 既有模式补第三条定向回退: 400 且
错误信息指向 thinking 时移除 extra_body 重建请求 (回退默认思考
模式; 若正文因此被推理挤占, 由 _iter_openai_text 显式报错指引)。

与 thinking 无关的 400 不触发回退, 直接抛出。
2026-09-04 12:50:50 +08:00
wshy 3fee4ecba5 Merge pull request #240 from yushenglin01/fix/ai-analysis-focus
修复 AI 分析关注重点不突出及静默失效问题
2026-09-04 12:47:39 +08:00
wshy 831e2dc5db Merge pull request #243 from 0112020179/codex/stream-regime-batches
fix(regime): 逐批聚合以降低全量重算内存峰值
2026-09-04 12:47:35 +08:00
shy3130 a9af2bce1c fix(pipeline): enriched 全量重建流式化, 峰值内存与历史长度解耦 (#208/#174)
小内存机器 (2C2G) 延长历史后全量重建必然 OOM 的两个根因:
  1. 全量模式把所有批次结果累积在内存 date_buffers 直到统一写盘,
     5 年 x 5500 只 (~800 万行) 全表驻留 0.5-1GB;
  2. 批次大小按「每只 ~244 行」的年代设定, 历史延长后单批宽表
     (指标全列) 瞬时 ~600MB。

修复 (双核模拟实测: 275 万行峰值 RSS 958MB -> 415MB, 耗时 +37%):
  - 流式暂存发布: 每批结果立即写暂存文件 (enriched 树外隐藏目录,
    不被 **/*.parquet 业务 glob 扫到; polars/duckdb 的 ** 均匹配
    点目录), 最后按日期分块流式合并、逐分区原子替换; 日期覆盖
    校验与 EnrichedPublication 崩溃语义保持不变, finally 保证
    暂存清理, 历史残留按 mtime (24h) 启动清扫;
  - 自适应批次: 检测内存 <8GB 时按单批目标行数 15 万收缩,
    >=8GB 保持用户设置 (大机器行为不变, 20 核实测耗时持平);
  - 刷新路径同修: 启动/盘后的 300 天窗口计算 (repository) 改走
    compute_enriched_history_window 分批执行, 与整帧顺序等价
    (各步骤均 over(symbol) 分组), 峰值与标的总量解耦。

验证: 新增 5 项回归测试 (流式输出与整帧直算逐列相等/暂存清理/
残留清扫/自适应批次/窗口分批等价), 全套 backend 1498 项通过,
ruff 基线零新增。
2026-09-04 12:40:04 +08:00
0112020179 885e693118 fix(regime): aggregate fallback batches incrementally 2026-09-04 12:19:35 +08:00
yushenglin 00b3492414 fix(ai): 优化分析关注重点与正文输出 2026-09-04 12:03:06 +08:00
shy3130 8c28132361 fix(analysis): 概念/行业映射缓存命中返回类型与签名不一致 (#186)
_load_concept_map_df 正常路径返回 (map_df, count) 元组, 但缓存只存了
裸 map_df —— 600s TTL 内二次访问命中缓存返回 DataFrame, 调用方按元组
解包把两列拆成两个 Series, 概念/行业分析二次打开必报错 (issue #186
报告者定位)。

- 缓存与返回值同构: 存 (map_df, count) 元组, 类型注解同步
- 移除 market_mainline / rps_rotation 两处针对旧 bug 的防御性兼容层
- 删除失效的 bare-DataFrame 兼容测试, 新增缓存契约回归测试 (3 次连跑通过)
2026-09-04 11:59:00 +08:00
shy3130 2ce8b4b17d fix(ext-data): 内置概念/行业 preset 不再启动即自动联网拉取 (#199) 2026-09-04 11:53:15 +08:00
wshy 2140209506 Merge pull request #237 from 0112020179/codex/incremental-detail-kline
perf(kline): 个股详情仅增量更新最后一根日 K
2026-09-04 11:26:55 +08:00
0112020179 8721bd2e83 perf(kline): update detail chart with latest row 2026-09-03 17:35:16 +08:00
0112020179 2e8be527b6 fix(index): preserve realtime cache on provider failure 2026-09-03 14:42:32 +08:00
shy3130 4cb30e48aa fix: screener JIT 透传 turnover_rate (#187) + PullScheduler 线程安全 (#203)
- #187: _compute_enriched_full/_load_enriched_history 的 warmup 读取
  白名单补上存储列 turnover_rate —— instruments 不可用无从重算时,
  自定义 SQL 用该列做条件会 Binder Error 被吞成静默空结果
- #203: refresh 的 _tasks 增删 diff 从调用方线程移进主循环闭包,
  消除 TOCTOU 窗口; 行为测试覆盖增删/幂等

#187 测试已在修复前代码上验证会失败 (instruments 为空时列丢失)。
2026-09-03 13:45:12 +08:00
shy3130 91fef6d793 test: 加强 #201/#224 回归测试至可复现级别
- #201 warmup 测试改为与全历史计算基准对比数值 (ewm 指标从首值播种,
  仅断言非 NaN 无法区分新旧代码)
- #224 注入测试改用合法同构 parquet 做目标 (读 /etc/passwd 在未加固
  连接下同样报错, 区分不了攻击面是否真实闭合)

两个测试均已在修复前代码 (41205b1/e89ea9b) 上验证会失败。
2026-09-03 13:26:42 +08:00
shy3130 ef59df4a67 fix: 修复 6 个 issue (#225/#226/#201/#188/#200/#196)
后端:
- #225 自定义源分钟K字符串 datetime 不再被 cast 成 null:
  _normalize_minute 对 Utf8 列按常见格式链式解析 (参照 kline_sync 口径)
- #226 自定义源日K/除权因子单批失败只隔离该批 (重试 1 次 + 跳过 +
  warning 汇总), 不再丢弃已成功批次的全部进度
- #201 旧信号回测 _load_panel 加指标 warmup 窗口 (120 交易日保守日历日),
  计算后裁回 [start,end]; 数据不足时自然退化

前端:
- #188 因子回测单标的不再整面板空白: 外层条件改 !error, IC 卡片
  单独守卫并给出需 >=2 只的提示
- #200 因子回测支持调仓频率 (日/周/月) 与滑点 (bp) 配置
- #196 自选页板块筛选新增 ETF 分类, 旧偏好加载时补 ETF 键保持默认可见
2026-09-03 13:20:59 +08:00
shy3130 e89ea9becf fix: 修复 4 个 P1 issue (#224/#232/#223/#215)
- #224 screener 自定义 SQL 的内存连接关闭 enable_external_access,
  注入的 read_parquet/COPY 文件读写直接报错 (安全)
- #232 指数展示缓存百分数口径在消费边界显式 /100:
  pipeline._bench_rt_pct_of 与 abnormal_moves._bench_rt_pct 两处,
  修复 3/10/30 日偏离值被放大两个数量级
- #223 盘后管道按同日 daily/enriched 行数比较检测实时合并提前
  创建的部分分区, 删除后由增量重算全市场补齐
- #215 _basic_filter_for_asset 扩展中和股票专属键 (price_min/max/boards),
  并应用到回测/挖掘/策略扫描三个运行期入口, 修复 ETF 静默零信号
2026-09-03 13:01:43 +08:00
shy3130 41205b197c chore: follow-ups for PR #231/#233 reviews
- monitor_rules: raise from None in remind_date validation (B904)
- lots: log warning on asset-type fallback to stock (fail-open traceability)
- matrix: demote turnover NaN-column notice to debug
2026-09-03 12:45:24 +08:00
wshy 8b12fc7a65 Merge pull request #231 from thinkbuf/feat/lots-alerts
feat(lots): holdings-reminder page — per-buy-lot auto stop/profit & expiry monitoring
2026-09-03 12:39:58 +08:00
SeerGlaucus ba83e0241b fix(backtest): ETF矩阵回测不因缺失换手率字段而失败
非股票资产(etf/index)无股本数据(维表仅 symbol/name/code/asset_type),
数据源也不提供换手率; 但矩阵缓存档 common_filter 强制 turnover_min:0.0,
依赖解析仍为 ETF 请求 turnover_rate(需 float_shares 派生), 导致矩阵缓存
构建失败。实测内置 low_volatility_leader 也报
"matrix turnover_rate requires float_shares"。

- _basic_filter_for_asset 将 turnover_min/max 一并中和(与市值界同族, 上次修复遗漏)
- _populate_matrix_derived_arrays 对无股本的 etf/index 把缺失的 turnover_rate
  降级为全 NaN 列(与运行期 _optional_field 语义一致), 股票派生路径行为不变
- 新增回归测试: ETF 无股本时降级 NaN 不报错; 有 float_shares 时仍正常派生
2026-09-03 02:37:52 +08:00
richardandClaude adc7ab52a9 feat(lots): holdings-reminder registry with auto stop/profit & expiry monitoring
新增「持仓提醒」页(/lots),登记买入批次(个股/ETF),每批自动生成并同步两条
监控规则: lot_{id}_p(type=price 止盈止损)与 lot_{id}_d(type=date 到期提醒)。

- 核心监控引擎新增 date 规则类型: 纯日历窗口、每个交易日仅在首个轮询评估一次、
  按天 cooldown、跨天清理过期键; 消息按触发当天显示"N天后到期/今日到期"。
- 批次派生规则继承默认 webhook 渠道; 监控中心只读展示(批次/批次托管徽标),
  通用 monitor-rules 接口对带 lot_id 的规则写/删返回 409, 避免与批次页脱节。
- 通知正文统一在缺省时追加触发现价与涨跌幅, 并避免与引擎自带引语重复。
- 后端分层: strategy/lots.py 域(校验/存储/批次→规则纯映射) + api/lots.py 薄路由
  (写锁/校验先行/级联删规则/复用 _sync_engine 重载); services/fs_utils.py 原子写。
- 前端: api/queryKeys 契约、DateShortcuts、Lots 页(数值输入可留空、加载/出错态、
  文案打磨)、Monitor 日期提醒/批次徽标; RuleEditor 不手工建 date(由持仓页生成)。
- 记账/加仓减仓属"交易口径", 不在本改动(issue #230); 到期提醒按自然日窗口,
  休市/节假日顺延的交易日历口径待 issue 定夺。

验证: 新增 tests/test_date_rule.py + tests/test_lots.py 全绿(20), 监控族回归通过,
ruff 新文件干净, pnpm build 通过。

Co-Authored-By: Claude <noreply@anthropic.com>
2026-09-02 20:20:38 +08:00
wshy a28f9e852f Merge pull request #228 from thinkbuf/feat/watchlist-import-csv-groups
feat(watchlist): batch import CSV / pasted codes into groups
2026-09-02 15:55:18 +08:00