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
GitHub
a8c035010e
style(tests): ruff format 修复尾随空行(CI ruff format --check 失败)
2026-07-10 15:17:00 +08:00
GitHub
d84a08b7f2
fix(backtest): v1.20.3 修复回测绩效统计两个 bug(issues #30 #31)
...
#31 : RebalanceEngine 缺失价格导致净值假崩塌
已持仓标的当日缺 K 线(停牌/日历错位)时 prices.get(code,0)=0,
持仓市值记 0 → 净值单日暴跌(159915 在 20210208 缺一天,持仓 ~93%,
净值 1.1M→91,845,全期最大回撤 -92%)。
修复:last_known_price forward-fill,缺失日沿用最近已知收盘价。
附带:_compute_performance 最大回撤改正值口径(与 BacktestEngine 一致)。
验证:真实 ETF 数据 max_drawdown 24.01%(backtrader 基准 27%),
total_return 220.56% 不变。
#30 : PortfolioTracker 交易静默漏单
apply_trades 用 trade.datetime 作 dict key、df["datetime"].to_numpy()[i]
查找;两端类型不一致(int vs datetime64)时永不命中,交易被静默丢弃,
净值恒定(total_return=0 但 trades 表有 PnL)。
修复:改为按"位置索引"匹配(归一化 datetime 后查位置),类型无关。
验证:int-trade+datetime64-df 修复前净值=100000(恒定),修复后=100289。
测试:新增 4 回归测试(未修复代码上均失败,修复后通过);
全套 936 passed;mypy 改动文件零错误;ruff 全绿。
2026-07-10 15:12:47 +08:00
GitHub
06be298c92
ci(publish): 暂时关闭 attestations 规避 OIDC 503 故障
...
GitHub OIDC → sigstore 服务连续 503 overflow(v1.20.2 两次 publish 失败),
非配置问题(v1.20.1 能发成功证明 trusted publishing 配置正确)。
attestations 是可选签名,关闭后上传正常进行;待服务稳定后改回 true。
2026-07-09 22:12:20 +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
GitHub
bc0ea064ba
chore: retrigger CI (Actions queue stuck 20min)
2026-07-09 20:55:16 +08:00
GitHub
a67e5588d0
fix(types): v1.20.2 修复 v1.20.1 引入的 CI mypy 失败
...
v1.20.1 把 BacktestResult.performance 扩大为 dict[str, float | str]
(为塞 diagnostic_warning 字符串),破坏 6 处下游消费方类型契约,
CI mypy job 转红。重构为更干净的设计:
- 诊断信息走独立的 BacktestResult.diagnostic 字段(performance.py
PerformanceAnalyzer.diagnostic 属性 → engine 透出 → to_dict 含该字段
→ CLI 显示),performance 字典回归 dict[str, float](保留别名键)。
- optimizer.py 补 npt.NDArray 标注,消除 3 个既有 ndarray type-arg 错误。
测试:932 passed;mypy backtest/+portfolio/ 零错误;ruff 全绿。
2026-07-09 20:34:23 +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
GitHub
00b8c6374a
fix(backtest): v1.20.1 修复回测引擎 3 个 bug(issues #22 #23 #25)
...
排查发现用户反馈的"回测统计数据缺失/异常"并非服务器连接问题,
而是回测引擎与组合优化器自身的代码缺陷:
#23 : 首根 bar 访问 close[-1] 崩溃
_SeriesAccessor 负向越界改返回 NaN(不抛 IndexError);
BacktestEngine 新增 warmup_bars 参数跳过指标预热期。
#25-A: FactorWeightedOptimizer 权重坍缩
n_stocks=2 且得分接近时,减最小值把低分标的权重压到 ~6e-8,
等于单股满仓、n_stocks 被无视,进而出现持仓1只/-99.98%回撤。
新增 _apply_weight_floor 权重下限保证入选标的都有实质权重。
#25-B: RebalanceEngine total_trades 统计错误
total_trades = len(equity_curve)(天数)改为 len(trades_df)(真实笔数)。
#22 : 绩效别名键 + 数据异常诊断
performance dict 新增 sharpe_ratio/start_cash/end_value 别名键;
资金曲线异常时返回 diagnostic_warning 而非静默全 0,CLI 显示提示。
文档:README 加入回测手册导航;backtest_usage.md 补充 warmup 说明。
测试:新增 6 个回归测试,更新 3 个;932 passed。
2026-07-09 20:16:12 +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
GitHub
052119e6ae
feat(backtest): v4.2 新增 FSL 分水岭指标策略(单标的/组合/寻优)
...
- MyTT.py: 新增 FSL(CLOSE, VOL, CAPITAL) 分水岭指标
- SWL = (EMA(C,5)*7 + EMA(C,10)*3) / 10
- SWS = DMA(EMA(C,12), MAX(1, 100*SUM(VOL,5)/(3*CAPITAL)))
MIN(A,1.0) 模拟通达信 DMA(X,A) 内部钳制 A<=1 的语义
- MyTT.pyi: 补 FSL 类型 stub(CI mypy 用 .pyi 校验)
- strategies/builtin.py: 注册 FslStrategy(SWL/SWS 金叉死叉)
capital 作为 Param 暴露流通股本输入
- strategies/presets.py: fsl 寻优网格(4 档 capital)
- tests: 注册表断言加 fsl,阈值 18→19
2026-07-09 18:42:00 +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
GitHub
d0d65d64b8
feat: v1.20.0 服务器失败自动 ping 切换(无需手动 easy-tdx ping)
...
服务器连不上或返回空数据时,自动测速切到延迟最低的可用服务器再试,
Python API / CLI / Web API 三入口全部自动生效。
核心改动:
- _reconnect.py 新增 select_best_host_sync/async(连接失败 failover)
和 find_working_host_sync/async(空数据逐台实测)
- 8 个 client 的 _execute 注入跨主机故障转移(复用 auto_reconnect 开关)
- get_market_stat 空数据时按延迟顺序逐台实测找返回数据的服务器
- 新增 _reconnect/_areconnect helper 收敛重建连接副本
- MacClient failover 用 save_best_mac_host(延续 v1.19.4 不污染 best_host)
- 顺手修复 test_commands_offline 未使用 import(main CI failure 根因)
测试:925 passed(新增 18 个 failover 测试),ruff/mypy 零新增错误。
2026-07-08 18:07:37 +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
GitHub
e3e8dd492e
fix: K线响应截断容错 + 一键寻优并发默认8进程
...
- security_bars: parse_response 遇末尾残缺记录时丢弃并返回前 N-1 条,
避免 TDX 服务端截断响应导致整页 500(如 000408 count=800 日线)
- OptimizeView: 一键寻优并发默认 8 进程,用户选择持久化到 localStorage
bump version to 1.18.3
2026-07-06 13:23:37 +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