Commit Graph
71 Commits
Author SHA1 Message Date
Simon 9aaca12751 fix(web): 控件对齐与输入友好整改(控件高度令牌化 + 对齐自检脚本)
问题不是"不好看",而是**可测量的错位**:同一行里原生 date 输入 38.8px、数字输入
36.8px、按钮 34px;16px 的勾选框与 36.8px 的下拉同排;因子行的下拉与权重框没有
可见标签;列表里的勾选框点不中;参数非法时「运行回测」直接置灰且不说原因。

根因:控件高度靠「上下 padding + 行高」拼出来,而 input / select / button 的原生行高
各不相同(Chrome 的 date 还会多 2px),必然参差;加上各处内联像素宽度
(style={{width:220}}、flex:1)与自搓布局,列自然对不齐。

改动:
- 新增控件高度令牌 --ctl-h-sm/md/lg(28/34/38px)与 --ctl-px,.input/select/.btn/
  .icon-btn/date 统一显式 height(不再拼 padding);原生 checkbox/radio 统一 16px,
  点击热区交给外层 label(.check/.radio-row/.check--cell),表格整格可点
- 因子行、选股条件行改为「表头 + CSS 栅格」成列对齐,列宽由样式决定,
  去掉内联像素宽度与 flex 拉伸,配 aria-label 供读屏分辨重复行
- 表单友好化:错误提示改为**失焦或提交后**才出现(清空重填的瞬间不再标红);
  提交被拦下时一次展开全部行内错误 + 自动聚焦并滚动到第一个问题字段;
  运行/保存按钮不再因参数非法而置灰(灰按钮不说原因 = 看起来不可点却无响应),
  改为可点击并讲清原因;补齐 topN/costs 两处「产生了却没人显示」的行内错误落点
- 数值字段补 inputMode/step/min/max 与单位、取值范围提示;工具条检索/筛选用
  .input--search/.input--filter/.input--picker 类,不再写内联宽度
- /experiments 筛选无结果的空态与「暂无实验」区分开(原文案会让人以为归档丢了)
- 同一页面可能挂两份表单:radio name 与 label/for 加表单实例前缀(useId),
  否则两边单选互相取消、label 指错控件
- 新增 scripts/verify_ui_alignment.py:系统 Chrome + 原生 CDP(仅标准库,
  独占随机端口与临时 profile),按 7 个页面 × 1500/375px 检查同排等高、
  高度取值归一、点击目标、标签与无障碍名、字号圆角一致、尺寸匹配内容、
  横向溢出、提示裁切;本次基线 108/32 → 现 140/140

验证:pytest 388 passed、ruff 全绿(顺带清掉 qlib_verify.py 一处死代码)、
tsc 0 错误、图表单测 7 passed、next build 成功、契约自检 59/59、
对齐自检 140/140(含 375px 小屏)。
2026-09-27 09:01:55 +08:00
Simon 82240e383d docs: 同步操作说明/架构/路线图(归档操作、图表基座、数据库目标硬约束)
- USAGE 新增 §6.3.1「归档的日常操作」:查看 / 筛选(回写 URL)/ 导出完整 JSON /
  以此参数再跑 / 删除(写明「删除即失去结果,结果只存归档一份」)/
  历史归档用 restore_experiment_from_job 按原 id 重建;并说明归档完整度如何标注
- USAGE/README 更正技术栈与图表基座:TradingView Lightweight Charts 4.2.3 为唯一
  图表基座(ECharts 已从 package.json、pnpm-lock.yaml、node_modules、文档与
  架构图标注中全部清除),并记录实测证据(个股页图表根节点为
  div.tv-lightweight-charts,页面 canvas 无一来自其它图表库)
- ARCHITECTURE 更正「当前数据库」(原写 SQLite/未来 MySQL):现为本机 MariaDB 10.11,
  §6 补目标库硬约束;USAGE 补服务器身份与实测连接证据
- AGENT.md 新增 §0.1:数据库目标只允许本机 MariaDB,禁止 192.168.1.10,
  由 config.py::assert_db_target_allowed 硬拦截(命中直接抛错,不静默降级)
- DEV_PLAN_DIVIDEND_BACKTEST 记录三轮实施与验证、归档恢复边界(§12.5)、已知限制
- DEV_PLAN v2/v3 标注为历史记录(避免把当时的远端库地址当现状照抄);
  ROADMAP 更正 M3 图表选型
2026-09-20 07:31:20 +08:00
Simon 23972e7063 feat: 股息率案例口径 + 策略库与图表统一 + 回测存档完整化
汇总三轮未提交的开发(每轮均在本机 MariaDB + 真实浏览器上验证):

1) 股息率案例(全市场股息率最高 n 只,默认 20,每 m 月择股)
   - 新增日频估值表 daily_basic + 迁移;股息率因子(dv_ratio / dividend_yield / TTM)
   - 名称历史表 stock_name_history:剔除 ST 按**择股日当时名称**判定,消除
     「曾高股息后 ST」的股息陷阱(实测 3.70pp 偏差)
   - 区间择股/调仓双周期(m 择股 / y 调仓)、指数成分与白名单、停牌近似剔除
   - 复权因子口径核对(4,164,742 行、缺失 0.0%)、收盘价成交与涨跌停拦单
   - 案例实测:2020-01-01~2026-09-04 总收益 +24.86%(年化 3.52%、回撤 -28.58%)

2) 策略库与前端统一
   - strategy 表 + CRUD/PUT 原地更新 + `describe_strategy` 按 spec 真实推导
     「一句话说明 + 计算公式 + 执行步骤 + 注意事项」(与引擎实执行规则同源)
   - 任何出现股票代码处都成对显示名称且可点击进个股页
   - 全站图表基座统一 TradingView Lightweight Charts(ECharts 依赖、
     锁文件、组件与文档标注一并清除),买卖点标记只落在真实交易日上

3) 回测存档完整化(可往复查看)
   - 同步端点(POST /api/backtests、/api/factor-tests)此前完全不落库 → 现在同样归档,
     归档 id 经响应头 X-Experiment-Id 返回(不破坏 response_model)
   - data_version 首次真实写入(数据快照指纹:最新交易日 + 各表规模)
   - 个股收益曲线默认**全量保存**(此前硬截断 60 只);超出体积预算才裁剪,
     并写 archive_meta(机器可读)+ unimplemented(人可读)如实标注
   - 列表 kind/q 过滤 + X-Total-Count(此前 limit=50 静默截断)、DELETE 归档
   - 只读归档页 /experiments/{id}(Server Component,SSR 直出**选股条件**与
     **交易执行依据**);结果视图按 kind 分发(backtest/factor_test/selection),
     非回测归档不套用回测口径
   - 新增 CLI:prune_experiments(保留策略,默认 dry-run)、
     restore_experiment_from_job(从 Job 副本按原 id 重建被删的历史归档,默认 dry-run)

门禁:pytest 388 passed、ruff All checks passed、tsc 0 错误、图表单测 7 passed、
next build 成功、契约脚本 verify_strategy_workspace 59/59(含按 kind 逐类验证归档页)。
2026-09-20 07:31:04 +08:00
Simon 7e15b7251e docs: README 同步 v3(Chart/个股页/Job/Agent14 等) 2026-09-09 07:44:41 +08:00
Simon adaf97f6d7 docs: 记录 v3 开发执行状态(M9 + 支撑线 + 延后项) 2026-09-09 07:43:53 +08:00
Simon 861a4051ca feat(job): D2 全市场选股 Job 化(kind=selection 异步)
- Job kind=selection:executor 分支用 SelectionService(spec=SelectionQuery),结果
  落 result_json、自动归档 Experiment(summary:as_of + 选出 N);/api/jobs 与
  /api/experiments 的 result 解码支持 SelectionResult
- POST /api/selections/jobs:提交 SelectionQuery 为异步 Job(BackgroundTasks/subprocess)
- Web 选股页新增「异步(全市场)」按钮:提交 Job → waitJob 轮询结果(解决同步 60s+)
- tests/test_selection_job.py:executor 执行归档(SelectionResult/Experiment kind)、
  API 提交→轮询→结果与实验列表;全量 pytest + tsc 通过
2026-09-09 07:43:35 +08:00
Simon ed54096331 feat(agent): D4 Agent 补齐至 v3 §25(10 → 14 工具)
- inspect_factor:因子目录元数据(公式/方向/lookback/输入列)
- create_composite_factor:解析 name:weight 组件并保存(方向由注册表填充,未注册 400 语义)
- get_backtest_result:回测 Experiment 详细结果(收益/回撤/交易/意图与成交统计)
- create_experiment:成功 Job 兜底归档为 Experiment(幂等提示)
- 白名单工具总数 14(= v3 §25 清单 + get_market_data);tests/test_agent_v3_tools.py
  5 例(数量/各工具行为/幂等);全量 pytest 通过
2026-09-09 07:40:45 +08:00
Simon 7c268e43df feat(job): D1 Job 阶段上报(v3 §23)+ 列表/取消端点
- ResearchService.run_backtest/run_factor_test 支持 on_stage 回调(data_loading →
  factor_calculation/backtesting → analysis);job_executor 经独立短会话把 stage 写库
  (子进程模式同样走 DB),收尾回读最后阶段避免覆盖
- /api/jobs:GET 列表(kind/limit)、POST /{id}/cancel(queued/running → CANCELLED +
  终止子进程 terminate_active;父进程兜底已跳过 CANCELLED)
- SSE /jobs/{id}/events 现会携带 stage
- tests/test_job_stages.py:成功 job 终态 stage=analysis;executor 取消 queued/幂等/
  已完成不可取消;API 列表+终态不可取消+404;全量 pytest 通过
2026-09-09 07:37:25 +08:00
Simon 67d3aa1349 feat(portfolio): C2 单股上限约束真实执行(Portfolio v1.1)
- portfolio.allocate_with_max_position:无上限=等权(与原实现一致);有上限=迭代
  把超过 cap×当日组合市值的标的封顶并把剩余现金在其余标的中再分配,超出留现金
- TopKBacktestRunner 买入按约束分流(默认等权路径位级不变,回归数值保持)
- unimplemented 只保留行业上限(依赖行业元数据注入,如实标注)
- tests/test_portfolio_constraints.py(分配数值/封顶留现金/回测持仓权重≤上限+容差、
  config_snapshot)+ 旧断言更新(单股不再标注);全量 pytest 通过
2026-09-09 07:33:52 +08:00
Simon 0d05bfd187 feat(factor): C1 因子相关性分析(横截面 Spearman 矩阵 + API)
- evaluation.factor_correlation_report:多因子共同日期 ∩ 后逐日横截面 Spearman 相关
  取均值 → FactorCorrelationReport(冗余剔除前置,v3 §12 Correlation→Redundancy)
- ResearchService.run_factor_correlation + POST /api/factor-correlations
  (universe/factors/period;与其它研究同装配口径)
- tests/test_factor_correlation.py:矩阵对角=1/近线性±相关符号/对称/无共同日期补零、
  API 冒烟;全量 pytest 通过
2026-09-09 07:31:49 +08:00
Simon 93e32f4e63 feat(data): B1-2 指数成分同步(Provider + CLI sync index_weight)
- MarketDataProvider.get_index_weight(协议);Tushare 实现 normalize_index_weight +
  get_index_weight(ts_code=... 全历史成分权重);Sina 抛 DataSourceNotSupported;
  FailoverProvider 代理并审计每次尝试
- CLI:sync index_weight --code 000300.SH(拉取→幂等落库 index_weight→打印最新快照;
  失败走 sync_log 审计并返回非零)
- tests:Tushare 映射与调用(FakePro)、Failover 主源单源语义(新浪不支持被审计);
  全量 pytest 通过
2026-09-09 07:29:26 +08:00
Simon 9cc4bfccac feat(universe): B1-1 指数历史成分(index_weight)+ Universe 按 as_of 成分过滤
- index_weight 表(migration f5e0d1c2b3a4,MySQL 已应用;index_code+date+symbol 唯一)
  + IndexWeight 实体 + IndexConstituentRepository(members_at:取 <=as_of 最近一期快照,
  Survivorship-free / 无未来成分;latest_date)
- UniverseSpec.index_code + universe.filter_stocks members 交集 + resolve_members;
  Research/Selection/Signal/Replay 服务注入 index repo(历史成分过滤,选股/回测共用)
- tests/test_index_universe.py:快照历史成分(成分变更不入早期结果)、幂等、
  空快照期空集、index_code 过滤下 as_of 一致性;全量 pytest 通过
2026-09-09 07:27:13 +08:00
Simon 03fb463216 feat(web): K线图库对比 —— TradingView Lightweight Charts 实现 + 双库切换
- 按确认方案「两套实现并存对比」:
  - npm 依赖 lightweight-charts@4.2.3(v3 §21 推荐库)
  - components/StockChart/chartTypes.ts:共享 props 接口
  - CandleChart.tsx(ECharts 版,重构复用共享类型)+ CandleChartLW.tsx
    (Lightweight 版:蜡烛+成交量副刻度+MA+成交实心箭头/未成交信号圆点 markers)
  - StockChart.tsx wrapper;个股页顶部「ECharts / Lightweight」切换(默认 Lightweight)
- /stocks/600519.SH 冒烟 200;tsc --noEmit 通过
2026-09-09 07:21:07 +08:00
Simon 37510c1b89 feat(replay): M9-6 Bar Replay 线性重放(as_of 逐日仅用当时数据)
- domain/entities/replay.py:ReplayDay{top/events/counts}/ReplayResult 时间线
- ReplayService:universe.symbols 白名单必填(≤40)且重放交易日 ≤90(防全市场长任务);
  每个交易日以 <=as_of 数据经同一 signal/score 引擎生成帧
- POST /api/replays(边界校验)→ 时间线;供前端 Bar Replay 控件(v3 §20.6 阶段二)
- tests/test_replays.py:重放帧 == 回测 selection_history 逐调仓日一致;范围约束;
  后段暴涨股不泄漏进早段帧(未来函数);API 400/200;全量 pytest 通过
2026-09-09 07:18:36 +08:00
Simon 5bde8f9f5f feat(chart): M9-5 复权口径坐标换算落地(qfq 基准=最新因子 + marker 贴图换算)
- ChartService qfq 基准改为该股最新因子(截至今天)归一:历史区间随最新除权
  平移正确(v3 §20.5 Chart Display vs Execution basis 分离)
- 显示口径与执行 basis 不一致时,成交/信号 marker 价格按当日因子换算到 K 线坐标系
  (fill 早段价格在 qfq 下折算验证 100→50)
- tests:qfq 回测 marker 折算 + selection 标记保留;全量 pytest 通过
2026-09-09 07:15:41 +08:00
Simon e1a0a8aa38 feat(web): M9-4 Stock Research Page /stocks/[symbol](v3 §20.4)
- 个股统一研究页:K 线(chart API,none/qfq/hfq 显示切换)+ 量 + MA +
  成交 fills/未成交信号标记 + 该股历史选股命中与信号记录(点击看原因/价格)
- 股票池每行代码可点击进入本页
- 数据全部来自 Chart API(前端不重算);类型补齐 Stock.market/exchange
- 冒烟:/stocks/600519.SH 200,MySQL 上 qfq K 线返回正常;tsc --noEmit 通过
2026-09-09 07:14:44 +08:00
Simon 63c61ded37 feat(web): M9-3 K线 Chart 组件(candlestick + 量 + MA + Signal↔Fill 标记)
- components/StockChart/CandleChart.tsx:ECharts candlestick(红涨绿跌)+ 成交量副网格 +
  MA20/MA60 叠加 + 事件标记:成交 fills 实心三角、未成交信号空心三角(v3 §20.3);
  组件只展示不计算;层隔离便于替换 TradingView Lightweight Charts
- lib/types.ts:ChartResult/OHLC/Volume/Series/Marker DTO
- tsc --noEmit 通过
2026-09-09 07:13:43 +08:00
Simon bfeac7aa4c feat(backtest): M9-2 回测补 selection_history/signal_history/fills(Signal↔Fill 区分)
- BacktestResult 新增:RankedPick(调仓意图,与 select(as_of) 同源排序)、
  ActionRecord(BUY/SELL 意图 + filled + reject_reason/price)字段 selection_history /
  signal_history / fills(fills=signal_history 中 filled 子集)(v3 §20.3/§22.3)
- TopKBacktestRunner:调仓记录卖出/买入逐动作与是否成交;涨停/停牌导致的
  「BUY 信号未成交」保留原因;意图 picks 与执行 targets 分离(不因涨停悄悄改选股视图)
- ChartService.backtest_stock_chart 改用 history 生成三类标记(selection/signal/fill),
  未成交意图在图上可见(v3 §20.4)
- tests/test_backtest_history.py:意图=select 一致、fills 推导、涨停拒绝可见(构造 +10%
  涨停日)、序列化 roundtrip;相关回归(quant/consistency/charts)全过;全量 pytest 通过
2026-09-09 07:12:36 +08:00
Simon 995ed08548 feat(chart): M9-1 Chart DTO + Chart Service + Chart API(v3 §20)
- domain/entities/chart.py:ChartResult/OHLC/Volume/Series/EventMarker/ChartMetadata
  (adjust_mode + execution_price_basis 口径元数据)+ SelectionHit
- application/services/chart_service.py:个股 K线/量/MA 指标;显示层 qfq/hfq 折算
  (基于主口径 none 行情 × adjust_factor,绝回写研究数据);回测个股视图把实际成交
  转 fills 标记并在显示口径不同时做坐标换算(v3 §20.3/§20.5)
- by-symbol 历史查询:SignalRepository/SelectionRepository.list_by_symbol(含溯源 id)
- api/charts.py:/stocks/{symbol}/chart|signals|selections、/backtests/{id}/stocks/{symbol}/chart
  |trades|positions
- tests/test_charts.py(指标/qfq-hfq 折算断言/回测 fills/API 集成+404);全量 pytest 通过
2026-09-09 07:09:52 +08:00
Simon 8abfd6538c docs: 制定架构 v3 下阶段开发计划(DEV_PLAN_v3,Chart/Signal↔Fill/个股页主线) 2026-09-09 07:01:10 +08:00
Simon 2836efc607 fix(web): 调大 dev 同源代理超时,防止长选股请求被截断为 500
根因: next dev 的 rewrites 代理默认 30s 超时(http-proxy),而全市场
同步选股实测需 ~50s; 超时后代理断开连接, 前端收到
"POST /selections → 500: Internal Server Error"。
配置 experimental.proxyTimeout=300s 后, 经前端代理的选股请求
(52.9s) 验证返回 200。
2026-09-09 06:44:50 +08:00
Simon 314bfc159f docs: 同步 README/USAGE 至 M6–M8(选股系统主线 + MySQL + 新 API/Agent 工具/限制)
- README:架构分层示意(Selection/Signal/Portfolio/Composite Engine)、MySQL 默认配置、
  核心能力表、里程碑 M0–M8
- USAGE:概览模块与页面清单、引擎分层 §6.0、API 表新增 /api/selections /api/signals
  /api/strategies /api/composites、Agent 10 工具、已知限制(M8.4 延后/全市场选股同步耗时)
2026-09-09 06:29:51 +08:00
Simon db147c4232 docs: 记录 M6–M8 执行状态(ROADMAP 里程碑行 + DEV_PLAN 章节) 2026-09-09 00:42:17 +08:00
Simon e5a23f176d feat(web): M8.6b 回测页做实 + 首页入口更新(零新后端 API)
- backtest 页:成本参数表单(手续费/印花税/滑点 %)+ 初始资金 → ResearchSpec.costs/
  initial_capital;结果新增年度收益卡与成交明细表(买/卖日、买/卖价、收益、累计换手)
- Dashboard 入口卡加入「股票筛选」「交易信号」(6 张)
- lib/types.ts:ResearchSpec 增加 price_adjustment/costs/portfolio/initial_capital,
  Trade 补 entry_price/exit_price
- 页面 200 冒烟 + tsc --noEmit 通过
2026-09-09 00:41:49 +08:00
Simon e1ac23fa25 feat(web): M8.6a 交易信号页(/signals)+ 导航与类型
- app/signals/page.tsx:因子+买卖排名阈值+时点+白名单 → BUY/WATCH/SELL 表格
  (类型 Pill 着色、得分/价格/触发理由)+ 最近信号记录回看
- app-shell 导航加入「交易信号」;lib/types.ts 增加 Signal* 类型与 universe.symbols
- 真实 MySQL 端到端:/api/signals 返回可解释信号(跌破 MA60 的 SELL 警示等);
  /signals 页面 200;tsc --noEmit 通过
2026-09-09 00:40:22 +08:00
Simon 8b2f8ac35c feat(agent): M8.5 Agent 工具补齐(screen_stocks/explain_selection/generate_signals/create_strategy)
- tools_impl 新增 4 工具(Agent 共 10 个):
  screen_stocks(因子评分选股,symbols 白名单防全市场长任务)、
  explain_selection(读回选股结果并解释理由)、
  generate_signals(BUY/WATCH/SELL + 规则)、create_strategy(命名策略入库)
- 全部经白名单 Tool + Repository/Session,无 shell/写删权限扩张
- tests/test_agent_selection_tools.py 5 例(选股/策略保存+重名/解释 404/注册表);全量 pytest 通过
2026-09-09 00:39:42 +08:00
Simon 9d25d466e5 feat(strategy): M8.3 策略模型 + /api/strategies(命名配置资产,可展开为 ResearchSpec)
- StrategyDefinition:universe/factors/selection/rebalance/costs/portfolio +
  price_adjustment(除 period 外完整策略定义);to_research_spec(period) 展开为标准 Spec
- strategy 表(migration e1f2a3b4c5d6,MySQL 已应用;name 唯一)+ StrategyRepository
- /api/strategies:POST/GET/DELETE + POST /{id}/expand(period+initial_capital → ResearchSpec)
- tests/test_strategies.py(repo CRUD/同名/expand、API CRUD/400/404);全量 pytest 通过
2026-09-09 00:38:09 +08:00
Simon 692bdb3be5 feat(portfolio): M8.2 Portfolio Engine 模块化(等权收敛 + 约束显式标注)
- research.PortfolioSpec(weighting=equal;max_position_pct/max_industry_weight_pct 预留)
  + ResearchSpec.portfolio;config_snapshot 自动记录组合配置
- quant/portfolio.py:equal_weight_budget(与既有等权回测语义一致,行为收敛到本模块)+
  unimplemented_notes(设置约束即在结果中显式标注未建模,禁止假装支持)
- TopKBacktestRunner 预算与 unimplemented 改用 portfolio 模块;默认配置数值不变
  (一致性/quant 引擎回归通过);tests 补约束标注与 config_snapshot;全量 pytest 通过
2026-09-09 00:36:38 +08:00
Simon ba52edc2d6 feat(signal): M8.1 交易信号引擎(规则 + signal_event 落库 + /api/signals)
- SignalRules(买入 rank 阈值/趋势 MA/动量 + 卖出区间/破位警示)+ SignalEvent
  (BUY/WATCH/SELL,score/price/trigger_reason 可解释)+ SignalResult/Meta
- quant/signal.generate_signals:与选股同一评分引擎取全市场 rank,按规则分类输出
- signal_snapshot/signal_event 表(migration d8e0b2f3c4d5,MySQL 已应用)+ Repo
- SignalService + POST /api/signals(同步+落库)、GET 详情/列表
- tests/test_signals.py(引擎分类/排序/破位不 BUY、service、API 提交读回);全量 pytest 通过
2026-09-09 00:35:37 +08:00
Simon ef09d5b419 feat(quant): M7.3 研究行情口径显式化(默认不复权 none,可切 qfq)
- DailyBarRepository.get_range_many / stream_range_many_columns 增加 adjust 参数
  (默认 'none')→ SQL 层过滤口径,消除 stock_daily 混 source/adjust 污染因子的风险
- ResearchSpec / SelectionQuery 增加 price_adjustment(none|qfq),随 config_snapshot
  落库可溯源;ResearchService._load_daily 与 SelectionService 装配按口径取数
- tests/test_price_adjustment.py:repo 读取按 adjust 过滤(none/qfq 各自命中)、
  spec 默认与字段记录;全量 pytest 通过
2026-09-09 00:32:55 +08:00
Simon 4fa2bb748e feat(composite): M7.2b 因子组合落库 + /api/composites CRUD
- factor_composite 表(migration c3e9a0d1f4b5,MySQL 已应用;name 唯一)
- CompositeDefinition/Component 实体 + CompositeRepository Protocol + SQLAlchemy 实现
- /api/composites:POST(注册表自动填充组件 direction;未注册因子 400)、GET 列表/详情、DELETE
- tests/test_composites_api.py(repo CRUD/同名拒绝/删除、API 方向填充/404/400);全量 pytest 通过
2026-09-09 00:30:37 +08:00
Simon 273aee2772 refactor(quant): M7.2a Composite Engine 模块化(quant/composite.py)
- cross_sectional_zscore / composite_score / build_factor_panels 从 local_engine 迁入
  quant/composite.py;新增统一入口 build_score_panel(daily, factor_specs)
- local_engine re-export 保持旧引用兼容;selection/engine 的评分面板构建均指向
  composite —— 选股与回测的复合分实现收敛于一处
- 回归:quant/eval/research/selection 一致性/qlib 引擎测试全过;全量 pytest 通过
2026-09-09 00:29:23 +08:00
Simon 8f47b5b603 feat(factor): M7.1 因子定义入库 + /api/factors 读库(目录契约源)
- factor_definition 表(migration b7f2a5e81c33,MySQL 已应用):name 主键 + 元数据
  (formula/brief/frequency/lookback/direction/requires JSON/version)+ FactorDefinition
  entity(from_registry_def 由代码注册表构造)
- FactorRepository Protocol + SQLAlchemy 实现(幂等 upsert/list/get)
- /api/factors 改读 DB;目录为空自动 seed 注册表(幂等)—— 保留自定义因子登记能力
  (计算仍须代码注册,引用未注册因子照常 FactorError,防伪因子)
- tests/test_factor_catalog.py(repo 幂等/roundtrip/registry seed、API seed+字段齐全);
  test_api 的 client fixture 补 tmp sqlite session(factors 读库);全量 pytest 通过
2026-09-09 00:28:16 +08:00
Simon 0ab9038570 feat(web): M6.5 股票筛选页(/selection)—— 评分/条件双模式 + 当前/历史选股
- app/selection/page.tsx:因子评分(多因子+权重+TopN)与条件模式(结构化条件行
  编辑:static.*/因子/close>ma60/ROE 等,AND 语义);as_of 留空=最近交易日;
  结果表(rank/symbol/score/因子值/入选理由)+ 最近选股记录点击回看
- lib/types.ts:SelectionQuery/Condition/Candidate/Result/Meta/Run DTO
- app-shell 导航加入「股票筛选」;rail 脚注与 Dashboard 文案 SQLite→MySQL(M-DB 收尾)
- 端到端验证:真实 MySQL 上 POST /api/selections 返回候选并落库可读回;
  /selection 页面 200;后端全量 pytest + 前端 tsc --noEmit 通过
2026-09-09 00:25:27 +08:00
Simon 0d3e123de3 feat(selection): M6.4 回测与选股共用评分引擎(v2 §25 一致性锁定)
- quant/selection.score_panel_for_factors:复合分面板构建收敛为共享函数;
  LocalEngine.run_backtest 与 SelectionEngine.run_score_selection 均调它 ——
  消除「回测一套评分、选股另一套」的隐患
- tests/test_selection_backtest_consistency.py:对回测每个调仓日验证
  SelectionService.select(as_of=d, top_n) 候选 == 该日回测实际持仓(月调仓多时点),
  排序方向一致性亦验证;全量 pytest 通过
2026-09-09 00:22:08 +08:00
Simon c60dc78c88 feat(selection): M6.3 选股结果落库 + /api/selections(快照 + 逐候选行)
- domain/repositories/selection.py + SelectionMeta:选股 Repository Protocol
- models/selection.py + migration a6c91d4e7f20:selection_snapshot(查询/统计快照)+
  selection_result(逐候选:rank/score/factor_values/filter_status/reason JSON)
- repositories/selection_impl.py:save/get/list_recent(读回重建 SelectionResult)
- api/selections.py:POST /api/selections(同步执行+落库,返回 selection_id+result)、
  GET /{id}、GET 列表(as_of/method 过滤);deps 装配 SelectionService/Repo
- config._build_mysql_url:仅编码破坏 URL 结构的字符(修 Alembic configparser 遇 %21 崩)
- 迁移已在 MySQL 应用(alembic head a6c91d4e7f20);tests/test_selections_api.py 4 例
  提交→读回一致/404/列表过滤/condition;全量 pytest 通过
2026-09-09 00:20:42 +08:00
Simon 75c5472c31 feat(selection): M6.2 条件选股(method=condition + 财务可见性防护)
- quant/selection.run_condition_selection:结构化条件 AND 求值 —— 字段域
  static.*(行业/市场…)、技术列与派生量(close/volume/ma20/ma60)、已注册因子
  (momentum_60 等)、fundamental.*(announce_date<=as_of 的最新已公告财务值);
  条件支持 value 字面量与 ref 字段比较(如 close > ma60);结果带 filter_status/reason
- SelectionQuery 校验调整:condition 模式为纯过滤(不再强制 top_n/top_pct)
- FinancialRepository 新增 list_announced_many(批量防未来函数读取)+ SQLAlchemy 实现;
  SelectionService 注入 financial_repo 并按 announce_date 取每股最新一版
- tests/test_selection_condition.py:8 例(行业 in/ne、动量>0、close>ma60 ref、阈值、
  ROE 过滤且未来公告不可见、缺财务 repo 报错、更早 as_of 排除);全量 pytest 通过
2026-09-09 00:16:45 +08:00
Simon 25a1d9531a feat(selection): M6.1 Universe 选股范围执行器(规则化 + symbols 白名单 + 历史日语义)
- quant/universe.py:filter_stocks 从 quant/service 迁出并集中(ST/上市天数/退市过滤),
  as_of 当前/历史日语义由 delist/list_date 保证;exclude_suspended 依赖停牌表未建模,
  由上层显式标注(选股结果 unimplemented)
- research.UniverseSpec 增加 symbols 白名单(非空时仅白名单内参与,再叠加其余过滤)
- quant/service re-export filter_stocks(外部引用不变);SelectionService 已共用
- tests/test_universe.py:6 例覆盖当前/历史日、ST、上市天数、退市、白名单;全量 pytest 通过
2026-09-09 00:13:28 +08:00
Simon f3586adb25 feat(selection): M6.0 选股契约与评分引擎(SelectionQuery/Result + select(as_of))
- domain/entities/selection.py:SelectionQuery(universe+method+factors+top_n/top_pct/
  min_score+as_of+预热)与 SelectionResult/Candidate/Statistics(v2 §14.2/§21.1 DTO);
  ConditionSpec 字段就位供 M6.2 条件选股
- quant/selection.py:Selection Engine method=score —— 复合分(zscore×权重×方向)
  → TopN/Top% 截断;observation_date=<=as_of 最近交易日(防未来函数,v2 §9);
  候选带 factor_values 与 selection_reason(可解释)
- application/services/selection_service.py:选股用例(universe 过滤 → 装配 → 引擎)
- quant/service.py:抽取公共 load_daily_df 供研究/选股共用(行为不变)
- tests/test_selection.py:11 例 —— TopN/排序/理由、as_of 防未来函数、ST/上市天数/
  退市过滤、top_pct/min_score、空数据与查询校验;全量 pytest 通过
2026-09-09 00:12:28 +08:00
Simon 697ffc767b docs: DEV_PLAN_v2 以选股系统为下一阶段主线(用户定调)
- 新路线:M6 选股系统(Universe 选股范围 + Selection Engine A 条件/B 评分 +
  selection_result/snapshot 落库 + /api/selections + 回测共用引擎 + Web 选股页)
  → M7 因子层落地(原 M6 内容后移为支撑)→ M8 Signal/Portfolio/Strategy/Agent/模型选股
- 选股 MVP 复用现有数据与 9 因子/composite_score,不阻塞于因子入库
- 明确历史/当前 as_of 一致性、selection_reason 可解释、未来函数防护与范围控制
2026-09-09 00:06:31 +08:00
Simon 0ffd574f30 docs: 下阶段开发计划(架构 v2 落地 M6-M8)+ MySQL 迁移文档同步
- docs/DEV_PLAN_v2.md:基于 ARCHITECTURE_v2 与 M0-M5 现状的下一阶段计划
  (M-DB 迁移收尾 → M6 因子定义入库/复合因子/口径修复 → M7 Selection/Signal/
  Portfolio 引擎分层 → M8 Strategy 平台化/Web 做实/Agent 工具补齐;含本机 Redis
  127.0.0.1:6379 的接入触发点与执行顺序)
- ROADMAP.md:登记 M-DB 里程碑并指向 DEV_PLAN_v2
- USAGE.md / README.md:数据库描述由 SQLite 更新为 MySQL(config.yaml database.mysql)
2026-09-08 23:58:51 +08:00
Simon 6c2f198261 feat(db): SQLite 全量迁移至 MySQL(config.yaml 配置化 + 迁移脚本 + 一致性校验)
- config.yaml database.mysql:host/port/db/user/charset 明文可提交;密码经 password_env
  引用 .env 的 MYSQL_PASSWORD(AGENT.md §33 密钥不进 git)
- config.py _build_mysql_url:URL 优先级 DATABASE_URL env > database.mysql 段 > sqlite 兜底
- pyproject 引入 pymysql>=1.1
- tests/conftest.py 强制每进程 /tmp SQLite(测试绝不触 MySQL 开发库);test_config 覆盖
  mysql 组装/密码可选/sqlite 兜底分支
- scripts/migrate_sqlite_to_mysql.py:sqlite→mysql 一次性迁移工具(keyset 分页 + chunk
  多值 INSERT + 攒批 commit + 幂等续传 + 低配 MySQL 节流 --throttle-sec + --verify-only
  行数与抽样一致性校验);已用于 data/quant.db 约 1605 万行迁移并经校验一致
2026-09-08 23:58:48 +08:00
Simon a3fabf9ae9 docs: 新增架构文档 v2(ARCHITECTURE_v2) 2026-09-08 21:48:23 +08:00
Simon db520d0430 build(web): Noto Sans SC 本地自托管,移除 Google Fonts 在线依赖
- 经 3128 代理下载可变字体(400-700)101 个 unicode-range 子集到 public/fonts
- app/fonts.css 生成本地 @font-face 表,globals.css 改用 @import './fonts.css'
- 页面运行期不再请求 fonts.googleapis.com / fonts.gstatic.com
2026-09-08 21:48:23 +08:00
Simon 24f98e90c6 feat(web): 按 ui-ux-pro-max 设计系统重构前端(侧边导航 + 组件库 + 图标 + 可视化)
- 设计令牌与组件体系:语义色/间距/圆角/状态 + Card/Metric/Pill/Btn/Field/Empty/Skeleton/Progress 等
- 桌面左侧导航 + 窄屏抽屉(usePathname 高亮、ESC/遮罩关闭);修复抽屉/遮罩占据网格把 main 挤到第二行的问题
- 自绘内联 SVG 图标集替代 emoji;图标/表格/按钮等交互态齐全,空态/加载/错误/禁用态补齐
- 因子研究分层柱状、因子组合与回测净值/回撤双图 + 月度收益热力表、实验页任务状态徽标
- LineChart 主题化并按需引入 echarts(回测/组合首载 JS 448k→281k);next.config 放行局域网 dev 来源
2026-09-08 21:48:20 +08:00
Simon 442999f701 feat(data): 财务/日线同步增量 + 新浪「两边一致」校验兜底 + 逐只进度
- financial 默认增量:按 A 股披露节奏判断已最新并跳过;--full 强制全量重拉
- Tushare fina_indicator 增加报告期窗口与 100 条/请求自动分页(修复老报告期静默截断)
- 新浪兜底收紧为校验兜底:两源重叠历史一致才导入缺失键,行标记 source=sina;
  财务可比字段取 eps/销售毛利率(ROE 两端口径不同不作依据),日线只比较最近重叠交易日
- CLI 输出逐只进度与导入内容描述(来源/行数/报告期与公告区间),失败股票留待重跑
- financial_indicator 增 source 列(迁移 d3f6c9a21b04);新增一致性/分页/服务测试
2026-09-08 21:48:09 +08:00
Simon a77d3c13c3 feat(web): 因子研究升级 + 因子组合页
- 因子元数据增加 brief 使用简介(后端 FactorDef + /api/factors 返回,9 个内置因子补齐文案)
- 因子研究页:
  · 时间默认「今日 ~ 近 6 个月」
  · 因子多选,逐个跑 IC/RankIC Job 并分卡展示报告
  · 目录显示使用简介,行点击展开公式/方向/用法详解
  · 新增计分规则说明与「去因子组合」入口
- 因子组合页 /factors/compose:多因子勾选+权重、组合得分规则说明(z-score×权重求和→TopN),
  可配 topN/调仓/区间/ST,一键回测 Job → 净值/回撤/月度/未建模标注
- 回测页时间默认同样近 6 月
- 验证:tsc + next build 通过;组合回测(momentum_60×1.5 + volatility_60×1.0)Job 成功归档
  EXP-330CAAAE(-13.51%,交易 20);后端 factors/research/api 相关测试通过 / ruff clean
2026-09-07 21:36:16 +08:00
Simon 1cf7c84897 fix(web): 因子研究/回测改走异步 Job(修复全市场研究长请求被代理中断 500)
- 根因:全市场因子测试同步执行约 47s,经 Next 同源代理转发时长请求被 socket 中断(前端日志:Failed to proxy .../api/factor-tests [socket hang up])
- factors/backtest 页改为 POST /api/jobs 提交 → 轮询 GET /api/jobs/{id} → 取内嵌结果(AGENT §19 异步化)
- 新增 lib/jobs.ts(submitJob / waitJob,超时 10 分钟),运行中显示 Job 号
- 端到端验证:全市场 momentum_60 测试 JOB-93479087 → success(IC -0.0598,样本 323 日)+ Experiment 归档
- tsc + next build 通过;同步接口保留(适合小样本/脚本调用)
2026-09-07 20:38:56 +08:00
Simon 55b684fbe1 feat(scripts): dev.sh 前后端一键 start/stop/restart/status/logs
- 启动后端 uvicorn(:8000, --reload, 0.0.0.0) + 前端 next dev(:3000),setsid 独立进程组
- 停止按 pidfile + 监听端口双定位,整进程组清理(无子进程残留,实测残留 0)
- 端口探测 ss 优先(lsof 在该环境对 socket 不可靠)
- status/logs/restart;支持 API_PORT/WEB_PORT/BACKEND_API_URL 覆盖
- 日志落 logs/,pid 落 .run/(均已 gitignore);USAGE §5 增加用法
2026-09-07 20:13:39 +08:00
Simon 195f5d41f4 perf(backend): 内存优化三项——全市场研究不再占满 8G
1) 数据装配流式+列裁剪:Repository 新增 stream_range_many_columns(只 SELECT
   所需列、SQL 侧转 REAL、yield_per 分批),引擎按 required_columns 取数
   (LocalEngine 仅 close+因子字段),消除 ORM/Decimal 全量物化;
2) 研究 Job 独立子进程执行(job.mode=subprocess):python -m app.cli.run_job
   在子进程内设 RLIMIT_AS 上限,OOM 归档 failed 而非拖垮 API worker;
   子进程异常退出由父进程补记 failed;并发上限 2;
3) 服务启动清理:残留 queued/running Job 标记 failed(防永久 running)。

实测同款全市场回测:uvicorn worker RSS 稳定 ~220MB,任务峰值内存由 4.1GB+
降至 ~470MB,24s 完成并归档(此前 43s 未完成即 OOM)。
新增/更新测试 96 passed,ruff 干净。
2026-09-06 22:12:44 +08:00