Commit Graph
100 Commits
Author SHA1 Message Date
Justin Gu e419f911b7 feat: 盘面洞察三栏目 — 热点滚动/大盘日历/涨停生态,移除龙头池
- 热点滚动 /hotspots:交易日×板块涨跌矩阵(行业/概念/风格FG、领涨领跌镜像、今日~30日窗口),
  每日名次徽标+连板/累计/首榜统计,统计卡可点击展开成员板块;后端 /board-mac/hotspot
  两段式数据(历史日K矩阵当日缓存 + 今日实时列,周末 pre_close 未滚动去重),
  单连接约束下后台构建+进度轮询;风格轮动 /styles 为同视图 FG 路由别名
- 大盘日历 /calendar:指数全年红绿热力图,方框大小编码成交额(年内四分位),
  悬停浮框显示收盘/涨跌幅/成交额
- 涨停生态 /limitup:本地 vipdoc 日线离线回算连板天梯/首板二板分布/炸板率/跌停;
  仅统计最后一根 bar 等于全市场最新交易日的股票(防停牌/退市/未下载陈旧文件污染),
  主板 5% ST 判定带低价护栏;名称前端经 symbol-info 懒加载补齐
- 移除龙头池栏目及 /market/core-leaders 端点(screen 模块 universe=core 保留)
- 设计文档:docs/hotspot-rolling-design.md、docs/market-insights-roadmap.md
2026-09-05 02:43:02 +08:00
Justin Gu d6478a1e1c ci: 排除 test_ai_llm.py — CI 无 API key 且 runner 卡顿时 LLM 异步轮询必然超时抖挂 2026-09-04 13:09:50 +08:00
Justin Gu d0b72e1bd1 release: v1.31.3 — 紧急修复盘中分时数据错乱(实时命令解析错位,全程改走历史分时) 2026-09-04 12:42:44 +08:00
Justin Gu a69fa4103a fix: 寻优指标缓存键改内容哈希 — 修复 v1.25 起两段式加速静默失效
缓存键此前用数组对象 id 做签名,而引擎每次 run 会重建数组对象,
id 逐网格点漂移导致跨点永不命中——ParamGridOptimizer 的指标缓存
全程 0 命中,两段式加速自 v1.25 引入起就没生效过。正确性不受影响
(缓存未命中即走重算路径,结果与无缓存逐位一致),属纯性能回归;
test_optimizer_cache_reuse_across_grid_points 因此一直在失败。

- IndicatorCache._atom 数组签名由 (id, shape) 改为
  (dtype, shape, blake2b 内容摘要 16 字节):同值不同对象视为同一
  数据;NaN 按字节参与哈希,位模式不同只多算一次、不会误命中
- 删除防 id 复用的 _array_refs 强引用表(内容寻址下不再需要,
  缓存不再延长数组生命周期)
- 新增缓存级回归测试:同内容不同对象必须命中;原失败测试转绿
- 实测 16 点网格命中率 38%(修复前恒为 0);全量 pytest 1613
  通过、ruff/mypy 全绿
2026-09-04 12:29:09 +08:00
Justin Gu c8535e2596 feat: CLI 对齐 WebUI/SDK 分析能力 — 一键参数寻优+策略列表+组合级WF/一条龙
CLI 此前缺失的三块补齐,引擎层复用现成实现(ParamGridOptimizer/
STRATEGY_PRESETS/evaluate_portfolio/PortfolioWalkForwardEngine),
CLI、Web API 与 Python SDK 三条通路能力对等:

- 新增 easy-tdx optimize 参数网格寻优命令:单策略网格搜索
  (--strategy 用预设网格或 --param 自定义),--all 一键寻优所有
  内置策略并按总收益率全局排名(对齐 WebUI /optimize 页与
  /backtest/optimize-all/run/async);--workers 进程级并行;
  策略名/参数名联网前前置校验快速失败
- optimizer 新增 optimize_all_strategies 规范实现(模块级 worker
  可 pickle、主进程解析 label、跨策略进程池并行、presets 可注入
  子集网格),CLI 与后续 Web 端共用
- 新增 easy-tdx strategies 内置策略列表命令(名称/参数默认值/
  预设网格/说明;--output json 与 GET /backtest/strategies 同构)
- portfolio 补 --evaluate(组合级一条龙)/ --wf(组合级 WF)/
  --auto-fees,输出与 WebUI /portfolio 页同构
- 测试新增 12 例(optimize 互斥/未知策略/未知参数校验、strategies
  表格与 JSON、portfolio 新旗标、optimize_all_strategies 排名序/
  skipped/JSON 原生类型);pytest 1611 通过、ruff/mypy 全绿
- 文档同步:README、docs/backtest_usage.md CLI 章节+目录、
  CHANGELOG 未发布小节、examples/20_cli/cli_examples.sh 补
  回测系列 §39-48(输出样例均为真实行情实测)
2026-09-04 12:17:36 +08:00
Justin Gu 9d2b0397bd release: v1.31.2 — 修复分时接口盘前/休市/指数返回空(最近交易日锚定+指数K线适配) 2026-09-04 04:40:50 +08:00
Justin Gu 9f260dfb09 release: v1.30.3 — 修复一键寻优所有策略漏掉新策略(STRATEGY_PRESETS 19→54 同步 + 一致性回归测试) 2026-09-03 03:31:51 +08:00
Justin Gu e866e82299 fix: 一键寻优所有策略补齐 35 个新策略预设网格(STRATEGY_PRESETS 19→54,网格点 174→328)+ 注册表↔预设一致性回归测试 2026-09-03 03:27:04 +08:00
Justin Gu 62e80d892e release: v1.30.2 — 无未来函数指标扩容16个(注册指标50个)+ 内置策略补齐54个:WebUI回测全覆盖,新增前缀一致性无未来函数回归测试 2026-09-03 03:07:57 +08:00
Justin Gu 1433f4eed0 release: v1.30.1 — 移除 ZIG 未来函数指标与 zig_breakout 策略:回测数字可信优先 2026-09-03 02:02:19 +08:00
Justin Gu 1e95bab476 release: v1.29.2 — 可编辑安装失效友好报错(#58):入口守卫 _editable_guard 经 force-include 存活于失效态 2026-09-03 00:46:28 +08:00
Justin Gu 0a00ca3f70 release: v1.28.0 — 深度风险报告+移动止损+黄金测试(借鉴 akquant):25 项绩效 / α·β·IR·TE 基准对比 / trail_stop OCO 2026-09-02 11:53:53 +08:00
Justin Gu 90f2595d3d release: v1.27.2 — 市场异动类型解析补齐(#62):盘中强弱/竞价尾盘/急速拉跌/竞价试盘方向 2026-09-02 10:44:20 +08:00
Justin Gu 4dde339f26 fix(mac): 异动 0x1237 补齐 0x15/0x16/0x1D/0x1E 解析并修正 0x13 试盘方向(#62)
- 0x16 盘中强势/弱势:09:25 样本 v2 与开盘涨幅 49/49 精确一致,v1 为带符号 ±1~3 级强弱等级
- 0x15 竞价/尾盘异动:09:25 与 15:00 双时刻触发,desc 按时刻区分竞价/尾盘前缀
- 0x1D/0x1E 急速拉升/急速下跌:阈值下限 ±0.6%
- 0x13 竞价试盘:v1=0x00 试买/0x01 试卖(552 条对照昨收 549 条一致),旧实现试卖方向描述错误
- 新增顶层常量 UNUSUAL_TYPE_NAMES(19 种类型码→名称)
- 协议探索结论与完整证据链文档化 docs/protocol-unknown-fields.md §3.4
- 测试 test_unusual.py 21 例(全部真实抓包字节 fixture)
2026-09-02 10:44:09 +08:00
毛利哥andGitHub 12d308e1a4 fix!: 扩展日线槽位语义——第6槽为 float32 成交额,移除误导字段 hk_stock_amount(#57) (#61)
read_ex_daily_bars 原把第7槽(成交量)同时赋给 amount 与 vol(amount=vol),
真实成交额藏在第6槽 float32 重解释值里、以 hk_stock_amount 之名暴露。
实测对照:47#IF300 2023-09-11 第6槽 float32=186871758848(元)、第7槽
uint32=105358016(手),与标准市场 sh000300.day 同日 amount/vol 完全一致。

- _EX_DAILY_FMT: <IffffIIf → <IfffffIf,第6槽正名为 float32 成交额
- ExDailyBar.amount: int → float;移除语义错误的 hk_stock_amount 字段
- encode 端沿新 fmt 自动正确:大成交额按 float32 编码,不再溢出 uint32
- CLI offline ex-daily 输出新增 amount 列
- 新增真实样本回归测试(47#IF300 原始记录)+ 补齐缺失的 amount 往返断言

Co-authored-by: GitHub <action@github.com>
2026-08-31 20:43:35 +08:00
毛利哥 0b349e67f4 Merge pull request #56 from Harveyliu007/main
fix(ex): ex tick --date 接受 YYYYMMDD 整数,修复 AttributeError
2026-08-28 22:38:31 +08:00
毛利哥 8eaafb93ee Merge pull request #51 from handsomejustin/fix/issue-39-param-order-constraints
fix(backtest): 策略参数加跨语义约束,杜绝寻优选出快慢倒挂组合(issue #39)
2026-08-19 19:05:42 +08:00
毛利哥 cc17ab650d Merge pull request #50 from handsomejustin/fix/issue-49-bars-min1-daily-plus
fix(web): /bars MIN_1 被误判为日线,datetime 归一化为 date 00:00:00(issue #49)
2026-08-19 18:26:49 +08:00
毛利哥 e533b705d4 Merge pull request #48 from handsomejustin/fix/dependabot-postcss-nanoid
fix(web): 升级 postcss/nanoid 修复 Dependabot 安全告警
2026-08-14 17:35:29 +08:00
毛利哥 513ee15c83 Merge pull request #46 from handsomejustin/feat/issue-43-bars-mac-adjust
feat(web): /bars 迁移到 MacClient + 支持复权(issue #43)
2026-08-05 16:44:00 +08:00
毛利哥 e5e8f75516 Merge pull request #45 from handsomejustin/fix/issue-41-fund-flow-failover
fix(client): get_history_fund_flow 加空数据故障转移(issue #41)
2026-08-05 16:02:53 +08:00
Justin Gu 3b7ce97f2a chore(release): v1.20.4 服务器健康分引擎 + K线空数据故障转移
版本包含(PR #37):
- 新增 _health.py 服务器健康分引擎(降权/冷却/恢复)
- get_index_bars/get_security_bars 空数据自动逐台换台
- 8 个 client _execute 统一健康分联动
- select_best_host_*/find_working_host_* 应用 rank_by_health
- security_bars 截断日志区分空响应与末尾截断
- 26 个新增测试,CI 8/8 通过
2026-07-13 16:36:11 +08:00
毛利哥 0be4277d03 Merge pull request #37 from handsomejustin/feat/health-score-and-bars-failover
feat(reconnect): 服务器健康分引擎 + K线空数据故障转移
2026-07-13 16:21:55 +08:00
Justin Gu 1f040afabf feat(reconnect): 引入服务器健康分引擎 + K线空数据故障转移
彻底解决通达信服务器"跳来跳去"问题:

1. 新增 _health.py 健康分引擎:失败乘性降权(×0.5)、连续失败3次进
   120s 冷却、成功加性恢复(+0.2)。rank_by_health 按 latency/score
   (有效延迟)重排,冷却中的剔除。全健康时恒等映射,对既有测试零影响。

2. get_index_bars/get_security_bars 空数据时自动逐台换台(此前直接
   返回空 DataFrame,是日志"指数K线响应在第1/800条处被截断"后用户拿
   不到数据的根因)。复用泛化后的 _find_host_returning_data。

3. select_best_host_*/find_working_host_* 应用 rank_by_health 重排;
   空数据验证失败/异常时调 record_failure,命中调 record_success。

4. 8 个 _execute(A股/MAC/EX/MAC-EX × sync/async)统一注入健康分记录:
   成功 record_success、连接失败 record_failure。

5. security_bars 截断日志区分"首条即空(服务器无数据)"与"末尾截断"。

测试:26 个新增(15 health + 7 failover + 4 ex-client 健康分追踪),
全量 reconnect/failover/decode 回归通过,ruff/mypy 通过。
2026-07-13 15:59:28 +08:00
毛利哥 5dd1b96818 Merge pull request #36 from handsomejustin/fix/issue-34-realtime-data-feed
feat(realtime): 新增 RealtimeDataFeed 轮询数据源(issue #34)
2026-07-13 03:58:36 +08:00
Justin Gu afcc100e35 feat(realtime): 新增 RealtimeDataFeed 轮询数据源(issue #34)
EventBus 原本是纯发布/订阅管道,库内没有任何代码调用 publish(),导致
RealtimeStrategy 订阅后无数据可收(issue #34 根因)。

新增 RealtimeDataFeed:轮询 get_stock_quotes 快照 → 封装 MarketEvent →
bus.publish,让 RealtimeStrategy 拿来就能跑。

- feed.py: async(run_async) + sync(run_sync, executor 线程避免卡 loop)
  支持 interval/dedup(去重)/sessions(交易时段过滤,默认 9:15-11:30/13:00-15:00)
  symbol key 自动拼 f"{market}{code}" 匹配 EventBus.publish
- test_realtime_feed.py: 20 个单测覆盖 publish 路径/symbol key 匹配/
  去重开关/空 df/客户端异常不崩溃/盘外不拉取/stop 退出
- engine.py + __init__.py docstring: 「API 骨架待实现」→「需配合 feed 使用」
- README: 新增实时行情轮询 Python API 段落(含 issue #34 三个坑说明),
  WebSocket 段落标注「当前未联动数据源」现状
- examples/11_realtime/: 可直接运行的异步示例

通达信协议无服务端推送,本模块是约 3 秒延迟的快照轮询近似,非逐笔 tick。

验证:ruff + mypy(strict) + pytest 全量 956 passed(+20 新增),无回归。
2026-07-12 16:56:41 +08:00
毛利哥 9e7d30f33e Merge pull request #35 from handsomejustin/fix/index-bars-empty-failover
fix(client): get_index_bars/get_security_bars 加空数据故障转移
2026-07-10 19:58:35 +08:00
Justin Gu 6b98715a14 fix(client): get_index_bars/get_security_bars 加空数据故障转移
指数/板块指数(880xxx 等)并非所有服务器都提供,延迟最低的不一定返回数据。
实测约 6/47 台可达主机对所有指数返回空 body 且不报错,from_best_host() 按纯
延迟选主会选中这种"快但空"的主机,导致 get_index_bars 返回空 DataFrame。

get_market_stat 早有 _find_host_returning_quotes 兜底,但 K 线查询缺失同类逻辑。
本提交补齐对称设计:

- 新增 _find_host_returning_bars(sync + async):照搬 _find_host_returning_quotes
  模式,复用 find_working_host_sync/_async(最多试 5 台、带持久化、异常吞掉、
  全失败回退原 host)
- get_security_bars / get_index_bars(sync + async 共 4 处)接入:空数据且
  auto_reconnect 开启时触发逐台实测,找首台返回数据的 host
- 新增 TestIndexBarsEmptyFailover(5 个测试):覆盖空数据切 host、全候选空、
  非空不触发、auto_reconnect=False 不触发、个股 K 线同链路

验证:23 个 failover 测试全绿,941 个单元测试无回归。
2026-07-10 19:37:31 +08:00
毛利哥 471c79d5e2 Merge pull request #29 from handsomejustin/fix/v1.20.2-mypy-ci
fix(types): v1.20.2 修复 v1.20.1 引入的 CI mypy 失败
2026-07-09 20:59:58 +08:00
毛利哥 eb51770608 Merge pull request #27 from handsomejustin/fix/v1.20.1-backtest-bugs
fix(backtest): v1.20.1 修复回测引擎 3 个 bug(issues #22 #23 #25)
2026-07-09 20:17:00 +08:00
毛利哥 e21c4d595c Merge pull request #26 from handsomejustin/feat/fsl-indicator-strategy
feat(backtest): v4.2 新增 FSL 分水岭指标策略(单标的/组合/寻优)
2026-07-09 20:10:42 +08:00
毛利哥 8bf4ffb322 Merge pull request #20 from handsomejustin/feat/auto-failover
feat: v1.20.0 服务器失败自动 ping 切换(无需手动 easy-tdx ping)
2026-07-08 18:13:03 +08:00
Justin Gu 14debdb446 feat(web-ui): v1.19.7 服务器设置页面(测速+切换通达信服务器)
新增 web UI 第六个页面「服务器设置」,让用户在浏览器上:
1. 列出全部 50+ 候选服务器
2. 一键并发测速(显示延迟和可达性)
3. 点选切换到最快或可用的服务器(热重连,无需重启)

解决不同地区/运营商对通达信各服务器连通性不同的问题。

新增:
- AsyncTdxClient.reconnect_to(host) 热切换方法(持锁,关旧建新)
- GET/POST /api/v1/server/hosts|test|switch 三个端点
- ServerSettingsView.vue 页面(列表+测速+切换)
- 导航栏第六个入口

设计: 先reconnect成功再save_best_host(避免污染config),
host校验只允许候选列表内IP,不自动测速避免首屏卡顿。
2026-07-07 23:08:32 +08:00
Justin Gu c2c3e7ffb4 fix(release): v1.19.6 release.yml 先构建前端再 pip install(修复EXE丢依赖)
v1.19.5 EXE 只有 11MB,双击报 ModuleNotFoundError: No module named 'pandas'。
根因:release.yml 先 pip install -e '.[web,packaging]' 再 Build frontend,
但 force-include 要求 web-ui/dist 在 pip install 时就存在——install 阶段
dist 不存在导致 editable install 静默降级,PyInstaller 收集不到第三方包。

修复:调换步骤顺序,Build frontend 在 Install Python deps 之前。
ci.yml 上一 commit 已同样修复。
2026-07-07 22:23:30 +08:00
Justin Gu e8b088bca1 fix(ci): ci.yml test/mypy job 先构建前端再 pip install
pyproject.toml 的 force-include 要求 web-ui/dist 存在,否则 pip install -e .
报 FileNotFoundError。ci.yml 的 test 和 mypy job 此前没有构建前端步骤,
导致全部 CI 失败。

修复:test(6 矩阵)和 mypy job 在 pip install 前加 setup-node + npm ci +
npm run build。frontend job 本来就有,不变。

同时清理 pyproject.toml:恢复 force-include(之前误改成 build-hook 但
hatchling 自定义 hook 需要插件注册,过于复杂)。
2026-07-07 22:14:03 +08:00
Justin Gu 9ab093a3c7 fix(build): hatchling artifacts 让 sdist 也含 web-ui/dist
python -m build 先构建 sdist 再从 sdist 构建 wheel。sdist 默认尊重 .gitignore
不含 web-ui/dist(构建产物),导致 wheel 阶段 force-include 找不到源报
FileNotFoundError。用 [tool.hatch.build] artifacts 强制 sdist 和 wheel
都包含 web-ui/dist。

本地验证:sdist 含 4 个 dist 文件,wheel 含 4 个 web/dist 文件。
2026-07-07 20:50:48 +08:00
Justin Gu d5309454bc fix(packaging): v1.19.5 PyPI wheel 含前端 dist(pip install 后不再 404)
根因:PyPI wheel 只有 Python 包,不含前端 dist。_resolve_web_dist_dir
三级探测(env/_MEIPASS/仓库根)在 PyPI 安装环境下全失败返回 None,
StaticFiles 不挂载,GET / 返回 404。

修复:
- pyproject.toml hatchling force-include 把 web-ui/dist 映射到包内
  easy_tdx/web/dist
- publish.yml 在 python -m build 前先 npm ci && npm run build
- _resolve_web_dist_dir 新增第 4 级探测:包内 easy_tdx/web/dist

验证:本地构建 wheel 含 4 个前端文件,模拟 PyPI 安装后第 4 级探测
成功找到 dist。907 测试全过。
2026-07-07 20:46:26 +08:00
Justin Gu 3bc15e620c fix(config): v1.19.4 MAC客户端不再污染标准best_host(取不到数据根因)
根因:mac/client.py 的 MacClient.from_best_host 和 AsyncMacClient.from_best_host
调用 save_best_host(best),把选中的 MAC 协议服务器(如 121.36.248.138)
写进了全局 best_host 字段——但这个字段是标准 TDX 协议用的。之后标准
AsyncTdxClient 用 get_best_host 读到这个 MAC host,用标准协议请求 MAC
服务器,返回空 body(偏移 2 剩余 0)。web app lifespan 启动 MAC 客户端
时触发污染。

修复:
- config.py 新增独立的 best_mac_host 字段 + get_best_mac_host / save_best_mac_host
- mac/client.py 4 处改用新字段(不再碰 save_best_host / get_best_host)
- get_best_host 加交叉污染校验:缓存 host 不在标准列表里自动重置
  (自动修复已被污染的 config.json,用户无需手动操作)

测试:新增 5 个回归测试(污染检测 + MAC host 隔离),907 全过。
2026-07-07 20:36:44 +08:00
Justin Gu 032f5a40c0 fix(web): v1.19.3 K线空body不再500 + SPA路由刷新404
两个独立问题(日志证据):

1. K线空body 500:SH600519 等正常股票偶发请求时,通达信返回
   ret_count>0 但 body 完全为空(偏移2 剩余0)。v1.18.3 的容错有
   'if bars:' 条件,bars 为空时 raise → 500。移除该条件,始终 return
   (空列表让前端分页重试比 500 友好)。GetIndexBarsCmd 同改。

2. SPA fallback 缺失:/optimize /portfolio 等前端路由刷新时 404。
   子类化 StaticFiles 为 SPAStaticFiles,404 时返回 index.html。
   API 路径不受影响。

测试:更新 test_security_bars_truncated_first_record(原断言 raise,
现断言返回空列表)+ 新增空 body 回归测试。902 全过。
2026-07-07 20:13:39 +08:00
Justin Gu 6b51487477 test: 修复 test_task_runner_lru_eviction 的竞态 flaky
根因:测试用 max_workers=1 串行执行 5 个任务,断言「前 2 个被淘汰」。
但 LRU 淘汰发生在 submit() 时,淘汰的是「当时最旧的非 running 任务」。
快机器上 t0 还在 running(被跳过),实际淘汰 t1/t2;慢机器(CI windows
3.12)上 t0 已完成,淘汰 t0/t1——取决于提交速度 vs 执行速度的竞态。

修复:不断言特定 task_id 被淘汰,改为验证不变量:
(1) 最后提交的任务一定存活(LRU 最近)
(2) 至少淘汰 2 个(5 - max_results 3)
(3) 存活任务数 ≤ max_results

本地跑 10 次全过,不再 flaky。
2026-07-07 15:47:13 +08:00
Justin Gu 27a8ab1c76 style: ruff format security_bars.py(修复 ci.yml format 检查)
合并 e3e8dd4 时采用远程版本,但其 _log.warning 字符串换行未跑
ruff format,导致 ci.yml 的 format 检查失败。纯格式调整,无逻辑变化。
2026-07-07 15:27:20 +08:00
Justin Gu ecd6e298f5 fix(packaging): v1.19.2 修复干净 Windows 上 EXE 页面纯黑(MIME 类型)
根因:干净 Windows(无开发环境)注册表里没有 .js 的 Content Type 映射,
mimetypes.guess_type('.js') 返回 None,StaticFiles 回退到 text/plain。
浏览器 <script type="module"> 启用严格 MIME 检查,拒绝执行 text/plain
的 JS → Vue 不挂载 → 页面纯黑(/docs 不受影响因为它是 API 路由)。

修复:mount 前用 mimetypes.add_type 强制注册 .js/.mjs/.css/.svg 的正确
MIME,无论机器装没装开发工具都生效。

同时改 easy_tdx.spec:datas 用 pathlib.rglob 逐文件展开,更可靠。
2026-07-07 15:18:53 +08:00
Justin Gu 957cb8eeb0 Merge commit 'e3e8dd4'
# Conflicts:
#	CHANGELOG.md
#	pyproject.toml
#	src/easy_tdx/commands/security_bars.py
2026-07-07 01:54:20 +08:00
Justin Gu 0b4ed9af62 feat(packaging): v1.19.1 支持 Windows 单 EXE 打包 + 系统托盘 + 自动发版
面向零基础老年用户,easy-tdx 可打包成单一 Windows EXE,双击即用。

新增:
- 后端同源托管前端 dist(app.py 三级探测:env → _MEIPASS → web-ui/dist)
- easy-tdx serve 默认 --open-browser,启动后自动开浏览器
- PyInstaller 打包入口(__main__.py)+ spec 配置(easy_tdx.spec)
- 系统托盘(tray.py):右下角图标,右键"打开浏览器/退出"
  解决老人不会用任务管理器关闭的问题
- GitHub Actions release.yml:打 v* tag 自动构建并发布 EXE 到 Releases
- docs/packaging.md 打包使用文档

修复:
- K 线残缺尾记录导致 500(security_bars.py):通达信服务器偶发
  ret_count 与 body 长度不匹配,改为 try/except 优雅降级丢弃残缺尾,
  返回已解析的完整记录。GetIndexBarsCmd 同改。加 4 个回归测试。
- PyInstaller frozen 模式三个坑:
  1. console=False 下 stdout/stderr 为 None → 重定向到日志文件
  2. multiprocessing spawn 子进程重新 import __main__ → freeze_support + 子进程检测
  3. 系统托盘需主线程消息泵 → uvicorn 挪到后台线程

文档:
- README/手册改为三档分流:EXE(零基础)/ Python(一条命令)/ 源码(打包)
- 删除 npm run dev / 5173 / 两个终端的过时说明
- 手册补虚拟环境配置 + EXE 打包附录 + EXE 排错 FAQ
2026-07-07 01:48:07 +08:00
Justin Gu c901d198ca chore: bump version to 1.18.2 + CHANGELOG
记录移除拼音声母搜索的回退发版
2026-07-06 02:55:58 +08:00
Justin Gu c9d80617e8 revert: 移除拼音声母搜索,回到 6 位代码输入
拼音搜索的底层依赖(首次需爬沪深 A 股 5000 条全名单,几十次 TDX 协议
往返,慢机器几十秒到超时)太重,反复优化(按需加载/遮罩/单飞/预热)
都无法兼顾'不阻塞核心行情请求'与'首次可用'。用户决定放弃此功能,
回到简单稳定的 6 位代码输入。

回退 e2bf29e..2523605 共 6 个 commit 的全部改动:
- 删除 StockSearchInput / AppInitOverlay / useStockSearch
- 移除 pypinyin 依赖、/security/search-index 端点、lifespan 预热
- SymbolPicker / StocksPicker 恢复为纯 6 位代码输入
- README / CHANGELOG 同步回退

代码状态等同 v1.18.1(一键寻优多进程并发)发布后的干净基线
2026-07-06 02:50:51 +08:00
Justin Gu 25236056f7 fix(web): 单飞改用轮询解耦,预热超时不再波及端点请求
问题:上一版单飞用 asyncio.Future 共享,lifespan 预热用 wait_for(120s)
超时会 cancel 整个 Future,导致同时等待的 /security/search-index 端点
请求收到 CancelledError 返回 500。日志显示预热 TimeoutError + 端点 500。

修复:
- _build_search_index 改用轮询设计:后台 _do_build task 独立运行
  (fire-and-forget),调用方通过轮询 _SEARCH_INDEX 每 0.5s 检查结果
- 调用方被 cancel(预热超时)只退出轮询,不影响后台构建 task
- lifespan 预热去掉 wait_for,改为纯 best-effort 触发,无权取消构建
- 冒烟验证:预热被 cancel 后,端点请求仍能正常拿到结果(5206 条)

另外从日志看慢机器爬全名单 >120s,轮询上限提到 300s 兜底
2026-07-06 02:39:02 +08:00
Justin Gu 04fd720a05 fix(web-ui): 搜索索引前端超时 15s→120s + 后端单飞去重
问题:另一台机器首次加载报 'signal is aborted without reason'——
不是取不到数据,是前端 AbortController 15 秒就掐断了请求,但遮罩
却告诉用户等 30-60 秒,自相矛盾。

修复:
- 前端 fetchSearchIndex 超时 15s → 120s:遮罩本就是要等的,不能自己掐断
- 后端 _build_search_index 单飞去重:用 asyncio.Future 让 lifespan 预热
  与 /security/search-index 端点请求共享同一任务,避免并发各爬一次全名单
  (两个任务抢同一把 _execute_lock 反而互相拖慢)
- lifespan 预热改为调 _build_search_index(复用单飞),加 120s 超时保护
2026-07-06 02:31:20 +08:00
Justin Gu 6b01c0687c feat(web-ui): 股票搜索索引加载期间全站遮罩 + 后端启动预热
新机器首次打开页面时,拼音搜索索引构建要走全量 get_security_list_all
(几十秒),期间无任何反馈,体验极差。本次双管齐下解决:

前端:
- 新增 AppInitOverlay 全局遮罩:加载期间全屏灰底 + blur + spinner +
  诚实文案(预计 30-60 秒)。就绪后 fade-out;失败也消失(不卡死)
- useStockSearch 改为模块级响应式状态,App 根 setup 时 eagerLoad 触发
- 方案 A:无跳过按钮强制等待(一次性代价),失败时降级为可手输代码

后端:
- lifespan 启动钩子加后台预热:asyncio.create_task 触发一次
  get_security_list_all('all'),不阻塞服务启动,让本地缓存提前建立
- 用户打开页面时后端缓存大概率已就绪,遮罩可秒消失
2026-07-06 02:20:12 +08:00
Justin Gu 9048dfecaa fix(web-ui): 拼音搜索索引改为按需加载,避免首次构建阻塞行情请求
问题:新机器首次打开回测/寻优页时,组件挂载即触发 /security/search-index,
后端要走全量 get_security_list_all(几十次 TDX 协议往返,几十秒)。
而 AsyncTdxClient 是单连接 + _execute_lock,索引构建期间独占连接,
把 /bars 行情请求和寻优请求全部堵在后面,表现为点「一键寻优」后
等好久才出现第一根 bars 请求。

修复(三招组合):
- useStockSearch: 挂载即拉 → 改为显式 ensureLoaded(),只在首次聚焦
  输入框时才触发,确保「打开页面直接点取行情/寻优」的核心路径不被拖累
- fetchSearchIndex: 加 AbortController 15s 超时,避免长时间独占连接
- 静默失败降级:索引拉取失败只记 loadError 显示小 ⚠,不弹错不阻塞
2026-07-06 02:01:47 +08:00
Justin Gu dfbafd5ad4 docs(readme): 补充拼音声母搜索的升级提示(v1.19.0 需重装 [web] extra)
- 前置条件代码块注释加 pypinyin
- 新增 ⚠️ 升级注意:旧版本升级必须重跑 pip install -e ".[web]" 否则搜索端点 500
- 单标的回测取行情描述补上拼音声母搜索能力说明
2026-07-06 01:49:49 +08:00
Justin Gu e2bf29e931 feat(web-ui): 股票代码输入支持拼音声母搜索(zjxc→中际旭创)
所有股票代码输入框(回测/寻优/组合页)支持代码/中文名/拼音声母三路
匹配的下拉联想。复用项目里早已有但从未被前端消费的 security_list_all
数据(沪深 A 股 5206 只完整中文名表)。

后端:
- 新增 GET /api/v1/security/search-index 端点,返回 [{code,name,initials}]
- 声母用 pypinyin FIRST_LETTER 预计算,进程内缓存,首次算一次常驻
- web extra 新增 pypinyin>=0.50 依赖

前端:
- 新增 useStockSearch composable(模块级缓存 + 三路过滤 + 防抖)
- 新增 StockSearchInput.vue(下拉 + 键盘导航 + 市场标签)
- SymbolPicker / StocksPicker 接入新组件,保留直接输 6 位代码的快路径

实测 zjxc→中际旭创、gzmt→贵州茅台、cjxc→楚江新材,5000 条本地过滤 <5ms
2026-07-06 01:43:57 +08:00
Justin Gu 044e21064c chore: bump version to 1.18.1 + CHANGELOG
本次发布两个主题:
1. 一键寻优所有策略支持多进程并发(串行/4/8/16)
2. 策略库组合回测结果加组合评级 + 市场前缀纠正
2026-07-06 00:59:41 +08:00
Justin Gu d32c5e17b4 feat(web-ui): 一键寻优所有策略支持多进程并发(串行/4/8/16)
回测是 numpy/pandas 的 CPU 密集计算并持有 GIL,多线程无加速,必须用
多进程。照搬项目里已跑通的 screen/scanner.py 进程池模板。

后端:
- OptimizeAllBacktestRequest 新增 workers 字段(默认 1=串行,0-32)
- 抽出模块顶层函数 _optimize_one_strategy(可被 ProcessPoolExecutor
  pickle),策略类在子进程内构造,避开跨进程 pickle 问题
- _run_optimize_all 在 workers>=2 时用进程池并行,否则走原串行逻辑
- 顺带修复 backtest_schemas.py 的 E501 历史遗留

前端:
- OptimizeView 配置区新增「一键寻优并发」选择器:自动检测 CPU 核数
  + 串行/4/8/16 进程下拉,默认串行,标注推荐档 min(CPU, 8)
- types.ts/api.ts 透传 workers 字段

实测 8 进程 vs 串行提速 4-6x;端到端冒烟验证并行与串行结果完全一致
2026-07-06 00:59:26 +08:00
Justin Gu 5aec4996c6 feat(web-ui): 策略库组合回测结果加组合评级 + 市场前缀纠正
- StrategiesView: 组合回测结果区新增「组合评级」详情块,复用
  gradePortfolio(与 /portfolio 页和单标的回测页同口径的 5 维度评分)
- StrategiesView: 新增 normalizeSymbol,发请求前用 detectMarket 重算
  市场前缀,纠正历史保存策略的错配(如 SZ:515030 → SH:515030)
- BacktestView: fullSymbol 改用 market.ts 的 detectMarket,修复 5 开头
  沪市基金/ETF 被错判为深市的问题(与 SymbolPicker/StocksPicker 统一)
2026-07-06 00:59:13 +08:00
Justin Gu 6f121dc156 fix(web-ui): 参数寻优「查看」用寻优时的标的快照,避免输入框/路由切换污染 URL
- OptimizeView「查看」拼 URL 前从输入框实时值取 code/startDate/endDate/category,
  导致两个问题:(1) 寻优后改输入框代码再点查看,URL 指向没回测过的标的;
  (2) 切走再回 /optimize,组件 ref 丢失,URL 里 symbol/startDate/endDate 全空。
- 将标的上下文快照(取行情成功那一刻冻结)下沉到 Pinia store 的 optimizeContext,
  与 optimizeResult/optimizeAllResult 同生命周期持久化,跨路由保留。
- buildBacktestQuery 改读 store.optimizeContext,输入框改动与组件卸载都不再污染。
2026-07-05 18:03:12 +08:00
Justin Gu 173069d518 docs(example): 异动示例改为分页拉取全部(单次 600 协议上限)
get_unusual 的 count 实际被 min(count,600) 截断,写 1000 也只能拿到 600。
改用 start 翻页 + pd.concat 合并,直到某页不足 600 视为到尾。
2026-07-05 18:02:38 +08:00
Justin Gu 5c520c513d docs: 上手手册更新到 v1.18.0(策略组合保存+一键看今日信号+Tab 分类)
新增「第十章补充」完整章节,详细写:
- 如何保存策略组合(💾 保存为组合按钮 + 弹窗)
- 如何载入组合(↻ 重跑到今天,自动用今天重跑)
- 持仓三态徽章含义(持有/持有·浮亏/空仓·等买点)
- 三处警示(过拟合/模型仓位水印/载入确认)
- 完整日常使用流程(每天收盘后重跑看信号变化)
- 组合的局限(模型仓位≠真实账户、资金均分、过拟合风险)

补充更新:
- 第四章 4.4 加投资大师名言轮播说明(100 条/3 秒轮播)
- 第六章 6.2 加策略库 Tab 分类说明(单标的/组合)
- 封面 + footer 版本号 v1.17.14 → v1.18.0
- 目录加第十章补充链接

保持原作者口语化新手教学风格,沿用原文半角标点惯例。
2026-07-05 03:06:40 +08:00
Justin Gu c57cde156b feat(web-ui): 策略组合保存 + Tab 分类 + 审计修复 (v1.18.0)
v1.18.0

策略库新增 kind='multi' 类型:组合回测结果可保存为组合,
卡片「↻ 重跑到今天」一键用今天重跑得到截至当天的策略信号
(持仓/空仓/浮盈/浮亏三态高亮)。策略库拆成「单标的」「组合」
两个 Tab,避免数量多了混排难找。

核心机制:保存配置 → 载入即重跑到今天 → 看持仓表 = 今日信号。
复用现有 SQLite 单表(kind 字段本就是 TEXT),零数据库迁移,
零后端逻辑改动,仅 schema Literal 加 'multi'。

含 10 项代码审计修复:multi-icon 字体误用、lastComboCash 漏赋、
弹窗 ARIA/ESC/focus、三态函数收敛 computed、警示样式合并、
重跑按钮改橙、items 运行时校验、ref 替代 querySelector、
selectedIds 静默替换、文案精简。

补记 v1.17.15 CHANGELOG(橙色按钮 + 名言轮播)。

- 改 web-ui/src/views/StrategiesView.vue(保存组合+Tab+三态+审计修复)
- 改 src/easy_tdx/web/backtest_schemas.py(kind Literal 加 'multi')
- 改 web-ui/src/types.ts(TS 类型同步)
- 改 CHANGELOG.md(补 v1.17.15 + 新增 v1.18.0)
- bump pyproject.toml 1.17.15 → 1.18.0
2026-07-05 03:01:09 +08:00
Justin Gu 15af7a92ee feat(web-ui): 一键寻优按钮改橙色 + 等待期间投资大师名言轮播
v1.17.15

一键寻优所有策略按钮改为暖橙渐变(#f59e0b → #ea580c),比 primary
蓝更醒目,hover 加亮上移、运行中保留暗橙识别度。

寻优运行中右侧不再空白:展示 100 条全球投资大师名言(巴菲特/芒格/
格雷厄姆/林奇/索罗斯/利弗莫尔/塔勒布/达利欧等),3 秒随机轮播一条,
Fisher-Yates 洗牌避免短期重复,让等待不枯燥、还能学到东西。

视觉细节:
- 卡片顶部 3 秒线性进度条(暗示后台在跑)
- fade + slide-up 350ms 过渡动画
- 底部脉动橙点 + '后台寻优进行中' 提示

纯前端 TypeScript + 原生 CSS,零后端改动,零依赖新增。

- 新增 web-ui/src/data/investment-quotes.ts(100 条名言)
- 新增 web-ui/src/components/QuoteCarousel.vue(轮播组件)
- 改 web-ui/src/views/OptimizeView.vue(按钮样式 + 引入轮播)
- bump pyproject.toml 1.17.14 → 1.17.15
2026-07-05 02:17:22 +08:00
Justin Gu cf843cebad release: v1.17.14 — Web UI 数据评级系统(S/A/B/C/D 五档,三入口覆盖)
纯前端 TypeScript 实现,零后端改动。回测/组合/寻优三处结果顶部
显示评级徽章,让普通人 1 秒判断「适不适合经常参与」。

评级不看收益率(避免被近期大涨误导),只看风险调整后的持有体验:
卡玛/最大回撤/胜率/利润因子/夏普/波动率六维加权 + 一票否决。
京东方「收益 126% 但胜率 35%」案例 = D 档(核心验证点)。

长线低频策略(6年6笔)不会被冤枉:交易<10笔时只降权胜率/利润因子
维度,不否决整个评级(修复用户实测反馈)。

- 新增 web-ui/src/grading/ 核心模块(engine/thresholds/combinedMetrics/index)
- 新增 GradeBadge.vue + GradeDetails.vue 组件
- 接入 BacktestView/PortfolioView/OptimizeView(寻优排名表加评级列)
- 15 个自检测试(node:test + rolldown 打包),覆盖京东方 D / 长线 B 等场景
2026-07-05 01:21:18 +08:00
Justin Gu 66c49d1a8d feat(web-ui): 三处回测入口默认起始日期统一为 2020-01-06
单标的回测、组合回测、参数寻优原默认起始日为「3 年前」(动态滚动),
统一改为固定日期 2020-01-06,便于跨时间横向对比。结束日仍为今天。
SymbolPicker 共用组件的 defineModel 默认值同步更新。
2026-07-05 00:15:00 +08:00
毛利哥 64d0812257 ci: 新增 frontend job 防前端构建回归(v1.17.9 数值 bug 教训) (#16)
web-ui 此前只有本地 vue-tsc,CI 不覆盖,类型错误 / 破坏性 import
只有发布后用户才发觉。新增独立 frontend job 跑 npm ci + npm run build,
与 Python 版本无关故独立于 test matrix,避免跑 6 遍。

本地 npm run build 验证通过(686 模块,575ms)。
2026-07-05 00:03:14 +08:00
Justin Gu 2f3826d84d docs(README): 添加头部宣传图 2026-07-04 23:14:39 +08:00
Justin Gu 2f4d7bef8e docs(README): 添加 DeepWiki 徽章 2026-07-04 23:03:33 +08:00
Justin Gu 239ff46ac8 release: v1.17.13 — 修复多策略组合回测最大回撤虚高(分母误用初始值而非峰值)
用户反馈:3 个策略各自最大回撤仅 45.53%/40.16%/16.89%,组合却显示 83.76%。
根因:_build_combined_equity 的 drawdown_pct 分母误用 initial(固定初始资金),
净值大涨后(总收益 545%,峰值≈6.45×初始)绝对回撤额除以小初始值被等比放大。
改为 drawdown / peak(相对当时峰值,0~1),与单标的 PortfolioTracker 一致。
连带修复卡玛比率(年化/最大回撤)。其余指标经核对均正确。加回归守卫。
2026-07-04 21:07:39 +08:00
Justin Gu ee628c1b34 release: v1.17.12 — 修复 CI 在新版 FastAPI 上路由注册失败
DELETE /api/v1/strategies/{id} 原用 status_code=204,较新 FastAPI/Starlette
在路由注册阶段(add_api_route)就抛 AssertionError: Status code 204 must
not have a response body,导致 CI ubuntu 矩阵 21 个 web 测试因 fixture
导入 router 连带 ERROR。改为返回 200 + {"deleted": id} 确认体。
2026-07-04 20:50:07 +08:00
Justin Gu 05dc9a74af release: v1.17.11 — Web UI 策略库(SQLite 持久化)+ 多策略资金分仓组合回测
新增两层能力:
1. 策略库:单标的/组合回测结果可保存到本地 SQLite 单文件
   (~/.easy_tdx/strategies.db),策略库页可载入回填、重跑、删除。
2. 多策略组合回测:勾选 N 个单标的策略,各拿 1/N 资金、各跑原标的,
   净值曲线按日期并集对齐求和,组合结果含 19 项完整绩效指标 + 持仓表。

后端:strategy_store.py(SQLite CRUD) + multi_strategy_engine.py(资金分仓引擎)
+ routers/strategies.py + /backtest/multi-strategy/run/async。
前端:StrategiesView.vue + 保存策略按钮 + 复用组合页图表组件。

895 单测全绿(+24 新增),ruff/mypy strict/前端 vue-tsc 全通过。
2026-07-04 20:40:57 +08:00
Justin Gu 4db7104209 docs(README): Web UI 截图改为一行一张 2026-07-04 11:39:44 +08:00
Justin Gu 1838995c51 docs(README): 添加 Web UI 回测可视化截图展示 2026-07-04 11:37:01 +08:00
Justin Gu 07205b6166 release: v1.17.10 — 一键寻优「查看」跳转携带完整行情上下文
- SymbolPicker code/category/startDate/endDate 改 defineModel 双向同步
- OptimizeView 两个「查看」按钮跳转 URL 附带 symbol/startDate/endDate/category
- BacktestView onMounted 回填标的/周期/日期到 SymbolPicker 表单
- 向后兼容:老 URL(只有 strategy/params) 仍正常,缺失字段保持默认
- 前端 vue-tsc / vite build 通过,后端 870 单测全绿(无回归)
2026-07-04 04:12:46 +08:00
Justin Gu 57cad1f3db release: v1.17.9 — 修复回测交易统计离谱数值(前后端口径错配 + 持仓天数跨月放大)
- 交易盈亏指标改为收益率口径(avg_win/loss/max_win/loss = pnl/cost_basis)
- 平均持仓天数改用真实日历日相减(原 YYYYMMDD 整数差跨月放大)
- 盈亏比无亏损时记 999.0(原 0.0,与 100% 胜率自相矛盾)
- 新增 Trade.cost_basis 字段 + engine 填充
- 3 个回归守卫;870 单测全绿,ruff/mypy strict/前端 vue-tsc 通过
2026-07-04 03:55:05 +08:00
Justin Gu f7cb015ec6 docs(README): 补充港股 goods_transaction_all + 逐笔倒序语义说明
在 Python API「扩展市场」章节加 goods_transaction_all 示例(港股当日全部逐笔),
并补 start 倒序语义说明(start=0 为最新/收盘方向,count=2000 默认只取最近 2000
笔会集中在尾盘,需全天用 goods_transaction_all)。同步 GitHub Wiki。
2026-07-04 03:19:11 +08:00
Justin Gu cd790c9404 release: v1.17.8 — 修复 windows 3.10 flaky 测试超时
test_task_runner_does_not_evict_running 用 release.wait(timeout=5) 钉住慢任务保持
running,但 CI 慢环境(windows 3.10)下整个测试执行超过 5s 后任务因超时自动完成、
状态变 done,掩盖了「running 任务被 LRU 错误淘汰」的回归断言。

改为 timeout=30 留足 CI 慢环境余量(release.set() 仍是确定性释放点)。该测试用于
守护 LRU 淘汰跳过 running 任务的并发正确性,与港股逐笔成交无关,是 v1.17.2 引入的
预先存在问题。

本地连跑 5 次稳定通过,867 单测全绿。
2026-07-04 02:24:48 +08:00
Justin Gu 56153eeb97 release: v1.17.7 — 修复 Windows CI fixture 读取编码
1.17.5 引入的 tests/fixtures/ex_history_transaction.json 含中文注释,Windows CI
默认用 cp1252 解码 UTF-8 文件触发 UnicodeDecodeError: 'charmap' codec can't
decode byte 0x8f,导致 3 个 Windows 矩阵的 test_parse_ex_history_transaction_hk
失败(Linux 矩阵因默认 utf-8 不受影响)。

修复:所有 fixture 读取显式指定 encoding='utf-8':
- test_hk_transaction.py 的 load_hex/load_json(直接触发点)
- test_commands_offline.py 的 load_hex(纯 ASCII hex,当前未触发但一并统一)
- test_decode_errors.py 的 _load_hex(同上)

867 单测全绿,ruff format/check / mypy strict 通过。
2026-07-04 02:11:27 +08:00
Justin Gu 94f631a06b release: v1.17.6 — 港股逐笔成交全量取数 + start 倒序语义文档
回应 issue #14 后用户反馈:默认 count=2000 取回的成交时间全集中在尾盘。
根因是通达信逐笔协议(A 股 0x122F 与港股 ex 0x23FC/0x2406 一致)的 start 为
倒序语义——start=0 指向最新一笔(收盘方向),并非 bug。02715 全天成交 13327
笔,count=2000 只取最近 2000 笔故集中在尾盘。

新增:
- goods_transaction_all(MacExClient 同步+异步)—— 港股股票类市场自动按 1800/页
  翻页取全天全部逐笔成交,安全上限 50 页(90000 条)。返回协议原生倒序,需正序
  由调用方 df.iloc[::-1]。market 非港股时抛 ValueError。
- _fetch_all_hk_transactions_sync/async(_hk_transaction.py)底层实现。

变更:
- goods_transaction docstring 补 start 倒序语义说明,引导需全天数据用
  goods_transaction_all。

修复:
- 1.17.5 的 test_hk_transaction.py 未过 CI ruff format --check(注释对齐、
  MacTransaction 单行化、文末空行)。

867 单测全绿(+5 全量取数测试),ruff format/check / mypy strict 通过。
2026-07-04 02:03:47 +08:00
Justin Gu 33239525a9 style(tests): ruff format 修复 test_hk_transaction.py (CI format check)
CI 的 ruff format --check 报该文件需重格式化:参数化注释前双空格对齐为
单空格、两处 MacTransaction 单行化、文末多余空行。仅格式调整,无逻辑变更。
2026-07-04 01:45:35 +08:00
Justin Gu 40ae2eba53 release: v1.17.5 — 港股逐笔成交协议路由修复 (issue #14)
MacExClient.goods_transaction 此前对所有扩展市场统一复用 A 股 MAC 协议的
SymbolTransactionCmd(0x122F),但 0x122F 数据源未接入港股,服务器对港股
market 一律返回 39 字节空响应(count=0)。

港股股票类市场(HK_STOCK_MARKETS = {27,31,48,49,71,98})改走 ex 扩展行情
协议:query_date=None → GetExTransactionDataCmd(0x23FC 当日),指定日期 →
GetExHistoryTransactionDataCmd(0x2406 历史)。返回的 ExTransactionRecord
(price 为整数、单位 0.001 HKD)映射为与 A 股 MacTransaction 一致的 schema,
价格 ÷1000 换算为港元浮点,与港股分时图 float 价格对齐。count > 1800 时按
1800/页自动分页。其余扩展市场(美股 / 期货等)保持 MAC 0x122F 路径不变。

新增 src/easy_tdx/ex/_hk_transaction.py(市场判定 + 字段映射 + 自动分页,
同步/异步共用);回归测试 24 例(tests/unit/test_hk_transaction.py)+ 真实
fixture(港股 00700 在 2026-07-03 的 0x2406 响应)。862 单测全绿,ruff /
mypy strict 通过。
2026-07-04 01:29:17 +08:00
Justin Gu 648c9ccfaf style: ruff format 修复预存格式债(CI ruff format --check 转绿)
v1.17.2 引入的 QFQ 重算代码(adjust.py/client.py/test_mac_qfq_integration.py)
含 magic trailing comma 导致 ruff format 期望多行参数展开,CI 的
'ruff format --check src/ tests/' 自 v1.17.0 起一直失败。纯格式变更,
零逻辑改动(20 例 QFQ 测试全过)。
2026-07-04 00:17:06 +08:00
Justin Gu 78bbe46d01 release: v1.17.4 — Web UI 回测交互重构 + 一键寻优全策略 2026-07-04 00:09:19 +08:00
Justin Gu ecd8af1676 feat(web-ui): 取行情整合 + 市场智能识别 + 一键寻优全策略
- 取消单标的/寻优页独立的「取行情」按钮,整合进「开始回测/开始寻测」
  (SymbolPicker 经 defineExpose 暴露 loadBars,父组件串联取数+回测)
- 取消市场手动选择(沪/深/北交所下拉),改为 market.ts 按代码段智能
  识别(17 边界用例验证),代码框旁显示识别结果
- 成交价下拉精简为中文「开盘价/收盘价」,初始资金默认 100 万
- 寻优页 ParamGridPicker 切换策略自动填入预设参数网格
- 寻优页新增「一键寻优所有策略」按钮 + 全局策略排名表
  (OptimizeView 复用 store.runOptimizeAll,结果区含最佳/排名/合计网格点)
- types/api/store 新增 OptimizeAll 契约 + submitOptimizeAllTask
- vue-tsc + vite build 通过
2026-07-04 00:09:05 +08:00
Justin Gu f6ae69845d feat(backtest): 成交价精简 + 一键寻优全策略 + 预设网格
- 成交价精简为 next_open/next_close,删除 this_close/worst/best 三种
  非真实模式(this_close 有未来函数偏差,worst/best 为压力测试边界)
- 初始资金默认统一为 1,000,000(原 10万/20万)
- 新增 presets.py:18 策略各配 1-2 参数的预设寻优网格(笛卡尔积≤200)
- registry.to_schema() 返回 preset_grid 字段供前端自动填充
- 新增 POST /backtest/optimize-all/run/async:逐策略预设网格寻优 +
  全局排名(OptimizeAllResult:ranking/best/per_strategy/total_grid_points)
- 新增 optimize-all 端到端单测 2 例(838 单测全绿)
2026-07-04 00:08:38 +08:00
Justin Gu c15bd8232f release: v1.17.2 — QFQ 深层历史负价修复
通达信服务端 QFQ 模式对长期重度除权股票(如 601088)深层历史页
返回负价格,导致回测总收益 -3087%、回撤 326.85%、年化 nan、
bollinger 崩溃、10 策略 invalid-value-in-scalar-power、MyTT divide-by-zero。

客户端兜底:检测 QFQ 负价时用 NONE+XDXR 本地重算前复权
(因子以除权日前一交易日含权收盘价为基准,保证除权日前后连续)。
同步+异步双路径一致修复,失败降级返回原值。

- 新增 src/easy_tdx/mac/adjust.py(纯函数 compute_forward_factor/
  apply_forward_adjust/has_bad_prices)
- MacClient/AsyncMacClient 触发本地重算,XDXR 按 (market,code) 缓存
- tests: +20 例(16 纯函数 + 4 集成),844 全绿,ruff/mypy 通过
2026-07-03 22:42:04 +08:00
毛利哥 ea1a5c6f4a Merge pull request #13 from shom/main
修复easy-tdx tick SH 600519 --days 5
2026-07-03 15:23:44 +08:00
Justin Gu 49c892b317 docs: 扩充 README Web UI 使用说明(前置条件/启动/四页操作步骤/注意事项) 2026-07-03 12:04:15 +08:00
Justin Gu bd419da83d release: v1.17.0 — 回测可视化 Web UI(单标的/组合/寻优/对比)
回测从命令行升级到浏览器可视化。Vue3 + ECharts 单页应用,零代码完成
单标的回测、组合回测、参数网格寻优、结果对比四大场景。

后端新增回测 REST API + 策略注册表 + 后台任务执行器,
内置策略从 5 个扩充到 18 个。823 单测全绿。

README 新增 Web UI 章节,CHANGELOG 记录本次全部变更。
2026-07-03 11:55:04 +08:00
Justin Gu 8323223937 fix: 寻优跳过参数范围/对比支持组合/日线x轴年份/寻优跳转填充参数
4 个独立修复:

1. 寻优跳过参数范围检查:Param.validate 加 skip_bounds 参数,
   RegisteredStrategy.build 透传,ParamGridOptimizer 传 skip_bounds=True。
   修复寻优时 fast=250 被 max_value=60 拦截的问题(探索超范围值是寻优目的)。

2. 对比页支持组合回测:CompareView 新增 extractComparable() 统一提取净值+指标,
   支持单标的(performance/equity_curve)和组合(total_performance/combined_equity)。
   修复勾选组合回测任务报「非单标的回测」错误。

3. K线x轴日线显示完整年份:isIntraday 判断从 length>10 改为检查时分秒非零
   (日线归一化后带 T00:00:00 后缀,长度也 >10 导致误判为分钟线,slice 砍年份)。

4. 寻优跳转填充参数:BacktestView 加 useRoute(),onMounted 读 query.strategy +
   query.params,await nextTick 后覆盖(避免 StrategyPicker watch 重置)。
   修复寻优点「查看」跳转后参数未填充、仍用默认值的问题。

测试:823 passed, mypy 211 files OK, vue-tsc OK
2026-07-03 11:32:44 +08:00
Justin Gu 80d62aec56 fix: 图表x轴显示完整日期 + 组合回测取数翻页
图表日期格式(4 个组件):KlineChart/EquityChart/CompareChart/
PortfolioCompareChart 的 xAxis 加 axisLabel.formatter,强制显示原始
日期字符串。ECharts 默认把 ISO 日期当时间对象解析,丢失年份只显示
月-日+00:00。

组合回测取数翻页(_fetch_portfolio_bars):固定 count=800 改为翻页循环
(与前端 fetchBars 同逻辑),拼接后 sort_values 排序再过滤。修复组合
回测图表从 800 根最早点(2023-03-14)开始、缺失早期数据的问题。

实测组合回测 2019-01-07 起:净值 1814 点(不再是 800),首日正确。
2026-07-03 04:54:04 +08:00
Justin Gu 2903d8c800 feat(strategies): 内置策略从 5 个扩充到 18 个
新增 13 个经典策略,覆盖趋势/通道/震荡/均线四大类:

趋势类:
- ema_cross  EMA 双线交叉(比 MA 反应更灵敏)
- triple_ma  三均线系统(多头/空头排列)
- dmi        DMI 趋向指标(+DI/-DI 交叉)
- trix       TRIX 三重平滑(过滤短期波动)

通道/突破类:
- donchian   唐安奇通道突破(海龟交易法)
- keltner    肯特纳通道(ATR-based)
- atr_breakout ATR 通道突破(均线±K×ATR)

震荡/反转类:
- cci        CCI 超卖反弹
- wr_reversal WR 威廉超卖
- bias_reversal BIAS 乖离反弹
- emv        EMV 简易波动(量价结合)
- dpo        DPO 区间震荡

均线类:
- bbi        BBI 多空指标(4 均线综合)

全部基于 MyTT 现成指标实现,每个策略声明参数 schema 供 Web 表单动态渲染。
18/18 策略冒烟测试通过(合成数据回测无运行时错误)。
2026-07-03 04:36:04 +08:00
Justin Gu 0cabae4eda fix(web-ui): 翻页拼接后按时间排序,修复>800根数据图表/成交记录错乱
翻页拼接 concat 后页间时间逆序(page1=最新段,page2=更旧段),
导致 index 800 处时间从 2026 倒退到 2019。引擎/图表只正确处理前 800 根,
成交记录在 800 根后出现 2019 年数据。

修复:concat 后加 sort((a,b) => datetime.localeCompare)。
实测 1600 根排序后整体时间正序,index 799→800 连续递增。
2026-07-03 04:30:45 +08:00
Justin Gu 360f836001 fix(web-ui): ECharts 数字两位小数 + 取数自动翻页
图表格式化:新增 format.ts(fmt2/fmtPct),在 KlineChart/EquityChart/
CompareChart/PortfolioCompareChart/OptimizeHeatmap 统一应用——tooltip 用
valueFormatter、axis 用 axisLabel.formatter,所有数值显示两位小数。

取数翻页:fetchBars 当日期跨度超过 800 根时,循环调用 start=0,800,1600...
拼接结果,直到覆盖 startDate 或达上限(10 页 ≈ 32 年日线)。翻页后再按
日期范围闭区间过滤。实测 SZ:000001 翻 2 页拿到 1600 根(2019~2026)。
2026-07-03 04:18:23 +08:00
Justin Gu ff3476f83a feat(backtest): 回测结果对比页
选 2-4 个已完成的回测 task,叠加净值曲线 + 横向指标对比。

后端(最小增量):
- task_runner.list_recent(limit):返回最近 N 个任务摘要(LRU 倒序)
- GET /backtest/tasks?limit=20:任务摘要列表端点(不含完整 result)
- TaskSummary / TaskListResponse schema

前端(/compare 对比页):
- CompareView:左栏勾选已完成 task,右栏叠加对比
- CompareChart:多 task 净值叠加图(归一化为初始=1)
- CompareTable:多 task 指标横向对比表(8 项指标)
- 按 result 结构判断可对比性(仅单标的 BacktestResult 可对比)

复用现有 task_runner LRU 表,无新增存储。测试:823 passed(+2 列表端点)
2026-07-03 04:11:29 +08:00
Justin Gu 87fafe9131 feat(backtest): 参数网格寻优(optimizer + 前端寻优页)
对单个策略的 1-2 个参数做网格搜索,遍历用户指定的取值列表笛卡尔积,
每个组合跑一次回测,按 total_return 排序,返回排名表 + 热力图。

后端:
- ParamGridOptimizer(backtest/optimizer.py):itertools.product 遍历网格,
  每点 entry.build(params) + BacktestEngine.run(df),复用同一 DataFrame
- 网格大小上限 200 防组合爆炸,单点失败容错(跳过不中断)
- 2 参数时生成热力图矩阵(x/y 轴取值 + cell 收益率)
- POST /backtest/optimize/run/async 端点(后台任务)
- OptimizeBacktestRequest schema(param_grid 1-2 参数)

前端(/optimize 寻优页):
- ParamGridPicker:勾选 1-2 个寻优参数,逗号分隔填取值列表
- OptimizeResultTable:网格点排名表(按收益降序,最优高亮)
- OptimizeHeatmap:2 参数热力图(ECharts heatmap,绿→红映射收益)
- 最优点「查看」按钮跳转单标的页用该参数回测

测试:821 passed(+10 寻优器单测 + 3 寻优路由测试)
2026-07-03 03:55:35 +08:00
Justin Gu 6aab7a82b5 feat(web-ui): Vue3 + ECharts 回测前端(单标的 + 组合回测)
独立前端工程(web-ui/),Vue3 + Vite + TypeScript + Pinia + ECharts。
通过 vite proxy 对接后端 /api,开发期无跨域。

单标的回测(/):
- StrategyPicker: 策略下拉 + 按 schema 动态渲染参数表单
- SymbolPicker: 选标的 + 日期范围取行情(默认3年,结束日=今天)
- KlineChart: K线主图 + 买卖点 markPoint(按 datetime 对齐)
- EquityChart: 净值曲线 + 回撤双轴图
- MetricTable: 19 项绩效指标(收益/风险/交易分组)
- TradeTable: 成交记录表

组合回测(/portfolio):
- StocksPicker: 多标的输入(增删标签)
- PortfolioCompareChart: 各标的净值叠加(归一化对比)
- PortfolioSummaryTable: 各标的绩效横向对比
- 顶部导航切换单标的/组合

契约修复(/check 审计):
- fetchBars 归一化 date/datetime 列名(日线返回 date)
- K线买卖点按完整 datetime 对齐(分钟线不再折叠)
- count 上限对齐后端 800
- ECharts 注册内联 dark 主题
- 参数表单空输入不打断编辑
- 网络错误友好提示

技术栈:Vue 3.5 / Vite 8 / TS 6 / Pinia / ECharts(按需引入 725KB)
2026-07-03 03:35:20 +08:00
Justin Gu 38731114b6 feat(backtest): 回测 REST API + 策略注册表 + 组合回测引擎
后端回测系统完整实现:

策略注册表(backtest/strategies/):
- Param schema 声明机制,支持动态表单渲染
- 5 个内置策略:MA交叉/MACD/布林/RSI/KDJ
- 参数校验(含 NaN/Inf 拦截、范围检查、类型强制转换)

REST API(web/routers/backtest.py):
- GET  /backtest/strategies  策略枚举 + 参数 schema
- POST /backtest/run         同步回测(内联 OHLCV)
- POST /backtest/run/async   后台任务回测(含 symbol 取行情)
- POST /backtest/portfolio/run/async  组合回测(多标的)
- GET  /backtest/tasks/{id}  任务轮询

后台任务执行器(web/task_runner.py):
- ThreadPoolExecutor + 进程内 LRU 任务表
- status-aware 淘汰(不淘汰 running 任务)
- 线程安全单例 + lifespan shutdown 接入

组合回测引擎改造(portfolio_engine.py):
- 接受策略实例,参数透传到每个标的
- 新增组合净值曲线(按日期并集 forward-fill 对齐求和)

审计修复(/check 三轮):
- Param.validate 拦截 NaN/Inf/giant-int(防 DoS)
- ohlcv max_length=2000(防内存耗尽)
- LRU 淘汰跳过 running 任务(修复结果丢失竞态)
- get_runner double-checked locking(修复单例竞态)
- shutdown 接入 lifespan(修复资源泄漏)

测试:808 passed(含 39 回测路由 + 8 组合引擎 + 安全回归)
2026-07-03 03:34:34 +08:00
Justin Gu 803f2fc3ad fix(deps): bump pytest 8.3.5 -> 9.0.3 修复 tmpdir 安全漏洞 (Dependabot #1)
CVE: pytest <9.0.3 存在临时目录处理安全问题(中等严重程度)。
本地 Python 3.11 venv + pytest 9.0.3 全量测试通过:761 passed, 2 skipped。
pytest 9.0.3 要求 Python>=3.9,满足 CI 矩阵 3.10/3.12/3.13。
2026-07-03 01:33:11 +08:00
Justin Gu 1e7766754e style: ruff format tests/unit/test_a_share_extensions.py
修复 GitHub Actions 中 ruff format --check 的格式化失败。
2026-07-03 01:30:40 +08:00
Justin Gu 133c208510 release: v1.16.3 — 修复 market-stat 全市场涨跌统计家数偏小10倍
通达信统计指数 880005/880006 的计数类字段返回真实家数的 1/10,
get_market_stat 未做缩放还原,导致 up/down/total/limit_up 等系统性偏小 10 倍。
对 6 个计数字段统一 round(field * 10),成交额/量/市值不动。
重写 test_get_market_stat_mapping 覆盖还原逻辑。761 单测全绿。
2026-07-02 12:46:27 +08:00
Justin Gu 3945800728 feat(cli): company-info 传板块名自动读完整正文(分块循环 + 多服务器重试)
修复体验问题:此前传板块名仍需用户关心 --offset/--length,太笨拙。
现在传板块名即自动按目录 length 分块循环读取整个板块(单次上限 30720 字节,
大板块如「公司大事」77万字节也能一次读全),--offset/--length 仅传文件名时生效。

- _resolve_filename 返回板块 length,_run_content 分块循环读取完整内容
- 修复分块 offset 推进 bug:原按解码字符串 GBK 重编码计字节,遇 U+FFFD 崩溃;
  改为按请求字节数推进(服务器按字节偏移工作)
- 修复多服务器目录版本不一致:传板块名未命中时自动重试多个服务器(最多4次)
- bump 版本号至 1.15.3
2026-06-27 04:37:44 +08:00
Justin Gu 297a479928 chore: 从 git 跟踪移除 CLAUDE.md(本地文件保留)
修正上一个 commit:CLAUDE.md 应从版本控制移除(本地配置,不递交 GitHub),
但被误重新加入。本次 git rm --cached 真正移除跟踪,本地文件保留继续使用。
2026-06-27 04:23:46 +08:00