原始 JSON 报告 机器可读
{
"data": {
"repo": {
"topics": [
"chinese-stock-market",
"financial-data",
"mootdx",
"pytdx",
"tdx",
"technical-analysis",
"a-share-tdx",
"stock-analysis",
"stock-data",
"stock-trading"
],
"is_fork": false,
"size_kb": 18248,
"has_wiki": true,
"homepage": null,
"languages": {
"CSS": 1878,
"Vue": 161032,
"HTML": 368,
"Python": 1915565,
"JavaScript": 1544,
"TypeScript": 95589
},
"pushed_at": "2026-07-13T08:36:31Z",
"created_at": "2026-05-21T04:20:17Z",
"owner_type": "User",
"updated_at": "2026-07-30T13:51:42Z",
"description": "免费Python量化SDK:通达信协议直连 A股/港股/美股数据,内置技术指标、缠论分析、回测引擎与 Web 可视化,JSON 输出适配 AI Agent",
"is_archived": false,
"is_disabled": false,
"license_spdx": null,
"default_branch": "main",
"license_spdx_raw": "NOASSERTION",
"primary_language": "Python",
"significant_languages": [
"Python"
]
},
"owner": {
"blog": null,
"name": "毛利哥",
"type": "User",
"login": "handsomejustin",
"company": "23",
"location": null,
"followers": 26,
"avatar_url": "https://avatars.githubusercontent.com/u/7761402?v=4",
"created_at": "2014-06-01T16:13:49Z",
"is_verified": null,
"public_repos": 72,
"account_age_days": 4442
},
"license": {
"state": "custom",
"spdx_id": null,
"raw_spdx": "NOASSERTION",
"file_present": true,
"scorecard_found": true,
"profile_has_license": true
},
"activity": {
"releases": [
{
"tag": "v1.20.4",
"kind": "patch",
"published_at": "2026-07-13T08:39:12Z"
},
{
"tag": "v1.20.3",
"kind": "patch",
"published_at": "2026-07-10T07:19:55Z"
},
{
"tag": "v1.20.2",
"kind": "patch",
"published_at": "2026-07-09T13:08:50Z"
},
{
"tag": "v1.20.1",
"kind": "patch",
"published_at": "2026-07-09T12:20:52Z"
},
{
"tag": "v1.20.0",
"kind": "minor",
"published_at": "2026-07-08T10:16:24Z"
},
{
"tag": "v1.19.7",
"kind": "patch",
"published_at": "2026-07-07T15:12:04Z"
},
{
"tag": "v1.19.6",
"kind": "patch",
"published_at": "2026-07-07T14:26:53Z"
},
{
"tag": "v1.19.5",
"kind": "patch",
"published_at": "2026-07-07T12:52:49Z"
},
{
"tag": "v1.19.4",
"kind": "patch",
"published_at": "2026-07-07T12:39:38Z"
},
{
"tag": "v1.19.3",
"kind": "patch",
"published_at": "2026-07-07T12:16:23Z"
},
{
"tag": "v1.19.2",
"kind": "patch",
"published_at": "2026-07-07T07:21:28Z"
},
{
"tag": "v1.19.1",
"kind": "patch",
"published_at": "2026-07-06T17:58:36Z"
},
{
"tag": "v1.18.3",
"kind": "patch",
"published_at": "2026-07-06T05:24:06Z"
},
{
"tag": "v1.18.2",
"kind": "patch",
"published_at": "2026-07-05T18:56:30Z"
},
{
"tag": "v1.18.1",
"kind": "patch",
"published_at": "2026-07-05T17:00:39Z"
},
{
"tag": "v1.18.0",
"kind": "minor",
"published_at": "2026-07-04T19:02:02Z"
},
{
"tag": "v1.17.15",
"kind": "patch",
"published_at": "2026-07-04T18:18:32Z"
},
{
"tag": "v1.17.14",
"kind": "patch",
"published_at": "2026-07-04T17:22:14Z"
},
{
"tag": "v1.17.13",
"kind": "patch",
"published_at": "2026-07-04T13:08:04Z"
},
{
"tag": "v1.17.12",
"kind": "patch",
"published_at": "2026-07-04T12:51:09Z"
},
{
"tag": "v1.17.11",
"kind": "patch",
"published_at": "2026-07-04T12:42:07Z"
},
{
"tag": "v1.17.10",
"kind": "patch",
"published_at": "2026-07-03T20:18:28Z"
},
{
"tag": "v1.17.8",
"kind": "patch",
"published_at": "2026-07-03T18:25:19Z"
},
{
"tag": "v1.17.7",
"kind": "patch",
"published_at": "2026-07-03T18:11:58Z"
},
{
"tag": "v1.17.6",
"kind": "patch",
"published_at": "2026-07-03T18:04:21Z"
},
{
"tag": "v1.17.5",
"kind": "patch",
"published_at": "2026-07-03T17:30:16Z"
},
{
"tag": "v1.17.4",
"kind": "patch",
"published_at": "2026-07-03T16:12:27Z"
},
{
"tag": "v1.17.0",
"kind": "minor",
"published_at": "2026-07-03T03:56:21Z"
},
{
"tag": "v1.16.3",
"kind": "patch",
"published_at": "2026-07-02T04:47:04Z"
},
{
"tag": "v1.16.2",
"kind": "patch",
"published_at": "2026-07-01T19:38:52Z"
},
{
"tag": "v1.16.1",
"kind": "patch",
"published_at": "2026-07-01T08:24:02Z"
},
{
"tag": "v1.16.0",
"kind": "minor",
"published_at": "2026-06-30T07:03:47Z"
},
{
"tag": "v1.15.4",
"kind": "patch",
"published_at": "2026-06-29T14:15:53Z"
},
{
"tag": "v1.15.3",
"kind": "patch",
"published_at": "2026-06-26T20:38:06Z"
},
{
"tag": "v1.15.2",
"kind": "patch",
"published_at": "2026-06-26T20:26:05Z"
},
{
"tag": "v1.15.1",
"kind": "patch",
"published_at": "2026-06-26T20:07:08Z"
},
{
"tag": "v1.15.0",
"kind": "minor",
"published_at": "2026-06-24T19:34:03Z"
},
{
"tag": "v1.14.5",
"kind": "patch",
"published_at": "2026-06-17T10:17:58Z"
},
{
"tag": "v1.14.4",
"kind": "patch",
"published_at": "2026-06-16T02:04:13Z"
},
{
"tag": "v1.14.3",
"kind": "patch",
"published_at": "2026-06-15T19:04:56Z"
},
{
"tag": "v1.14.2",
"kind": "patch",
"published_at": "2026-06-15T18:53:56Z"
},
{
"tag": "v1.14.1",
"kind": "patch",
"published_at": "2026-06-15T12:52:14Z"
},
{
"tag": "v1.14.0",
"kind": "minor",
"published_at": "2026-06-14T16:35:49Z"
},
{
"tag": "v1.12.0",
"kind": "minor",
"published_at": "2026-06-14T10:50:57Z"
},
{
"tag": "v1.11.6",
"kind": "patch",
"published_at": "2026-06-13T13:26:44Z"
},
{
"tag": "v1.11.5",
"kind": "patch",
"published_at": "2026-06-13T13:11:46Z"
},
{
"tag": "v1.11.1",
"kind": "patch",
"published_at": "2026-06-12T14:13:20Z"
},
{
"tag": "v1.10.5",
"kind": "patch",
"published_at": "2026-06-12T09:20:31Z"
},
{
"tag": "v1.10.2",
"kind": "patch",
"published_at": "2026-06-12T08:47:50Z"
},
{
"tag": "v1.10.0",
"kind": "minor",
"published_at": "2026-06-11T19:37:27Z"
},
{
"tag": "v1.9.10",
"kind": "patch",
"published_at": "2026-06-11T09:41:48Z"
},
{
"tag": "v1.9.9",
"kind": "patch",
"published_at": "2026-06-10T20:24:50Z"
},
{
"tag": "v1.9.8",
"kind": "patch",
"published_at": "2026-06-10T20:13:19Z"
},
{
"tag": "v1.9.7",
"kind": "patch",
"published_at": "2026-06-10T19:59:32Z"
},
{
"tag": "v1.9.5",
"kind": "patch",
"published_at": "2026-06-10T10:53:59Z"
},
{
"tag": "v1.9.4",
"kind": "patch",
"published_at": "2026-06-09T19:41:09Z"
},
{
"tag": "v1.9.3",
"kind": "patch",
"published_at": "2026-06-09T19:25:42Z"
},
{
"tag": "v1.8.2",
"kind": "patch",
"published_at": "2026-06-09T15:20:56Z"
},
{
"tag": "v1.8.0",
"kind": "minor",
"published_at": "2026-06-09T12:42:40Z"
},
{
"tag": "v1.7.1",
"kind": "patch",
"published_at": "2026-06-07T19:10:24Z"
},
{
"tag": "v1.7.0",
"kind": "minor",
"published_at": "2026-06-07T15:33:10Z"
},
{
"tag": "v1.6.1",
"kind": "patch",
"published_at": "2026-06-07T13:56:10Z"
},
{
"tag": "v1.6.0",
"kind": "minor",
"published_at": "2026-06-07T13:18:08Z"
},
{
"tag": "v1.5.0",
"kind": "minor",
"published_at": "2026-06-02T04:33:10Z"
},
{
"tag": "v1.4.2",
"kind": "patch",
"published_at": "2026-05-28T10:15:07Z"
}
],
"recent_commits": [
{
"oid": "3b7ce97f2a6942cf9f39e25ee29c4e113bcfc69f",
"body": "版本包含(PR #37):\n- 新增 _health.py 服务器健康分引擎(降权/冷却/恢复)\n- get_index_bars/get_security_bars 空数据自动逐台换台\n- 8 个 client _execute 统一健康分联动\n- select_best_host_*/find_working_host_* 应用 rank_by_health\n- security_bars 截断日志区分空响应与末尾截断\n- 26 个新增测试,CI 8/8 通过",
"is_bot": false,
"headline": "chore(release): v1.20.4 服务器健康分引擎 + K线空数据故障转移",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-13T08:36:11Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0be4277d0392ea4e08816515b672121e68b5d311",
"body": "…-failover\n\nfeat(reconnect): 服务器健康分引擎 + K线空数据故障转移",
"is_bot": false,
"headline": "Merge pull request #37 from handsomejustin/feat/health-score-and-bars…",
"author_name": "毛利哥",
"author_login": "handsomejustin",
"committed_at": "2026-07-13T08:21:55Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "1f040afabf05a20afc2ec0172f1693da8d7947c4",
"body": "彻底解决通达信服务器\"跳来跳去\"问题:\n\n1. 新增 _health.py 健康分引擎:失败乘性降权(×0.5)、连续失败3次进\n 120s 冷却、成功加性恢复(+0.2)。rank_by_health 按 latency/score\n (有效延迟)重排,冷却中的剔除。全健康时恒等映射,对既有测试零影响。\n\n2. get_index_bars/get_security_bars 空数据时自动逐台换台(此前直接\n 返回空 DataFrame,是日志\"指数K线响应在第1/800条处被截断\"后用户拿\n 不到数据的根因)。复用泛化后的 _find_host_returning_data\n[…]\n入健康分记录:\n 成功 record_success、连接失败 record_failure。\n\n5. security_bars 截断日志区分\"首条即空(服务器无数据)\"与\"末尾截断\"。\n\n测试:26 个新增(15 health + 7 failover + 4 ex-client 健康分追踪),\n全量 reconnect/failover/decode 回归通过,ruff/mypy 通过。",
"is_bot": false,
"headline": "feat(reconnect): 引入服务器健康分引擎 + K线空数据故障转移",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-13T07:59:28Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "5dd1b968185af26fdea977298d03e08957cb3a38",
"body": "…-feed\n\nfeat(realtime): 新增 RealtimeDataFeed 轮询数据源(issue #34)",
"is_bot": false,
"headline": "Merge pull request #36 from handsomejustin/fix/issue-34-realtime-data…",
"author_name": "毛利哥",
"author_login": "handsomejustin",
"committed_at": "2026-07-12T19:58:36Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "afcc100e3539c312cc779edb2226edb3d5450e4f",
"body": "EventBus 原本是纯发布/订阅管道,库内没有任何代码调用 publish(),导致\nRealtimeStrategy 订阅后无数据可收(issue #34 根因)。\n\n新增 RealtimeDataFeed:轮询 get_stock_quotes 快照 → 封装 MarketEvent →\nbus.publish,让 RealtimeStrategy 拿来就能跑。\n\n- feed.py: async(run_async) + sync(run_sync, executor 线程避免卡 loop)\n 支持 interval/dedup(去重)/sessions(交易时段过滤,默认 9:1\n[…]\nPython API 段落(含 issue #34 三个坑说明),\n WebSocket 段落标注「当前未联动数据源」现状\n- examples/11_realtime/: 可直接运行的异步示例\n\n通达信协议无服务端推送,本模块是约 3 秒延迟的快照轮询近似,非逐笔 tick。\n\n验证:ruff + mypy(strict) + pytest 全量 956 passed(+20 新增),无回归。",
"is_bot": false,
"headline": "feat(realtime): 新增 RealtimeDataFeed 轮询数据源(issue #34)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-12T08:56:41Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "9e7d30f33e83896f0315423197feb91b8cc2238a",
"body": "fix(client): get_index_bars/get_security_bars 加空数据故障转移",
"is_bot": false,
"headline": "Merge pull request #35 from handsomejustin/fix/index-bars-empty-failover",
"author_name": "毛利哥",
"author_login": "handsomejustin",
"committed_at": "2026-07-10T11:58:35Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6b98715a14812f4800abcbc3380174ffa9d389da",
"body": "指数/板块指数(880xxx 等)并非所有服务器都提供,延迟最低的不一定返回数据。\n实测约 6/47 台可达主机对所有指数返回空 body 且不报错,from_best_host() 按纯\n延迟选主会选中这种\"快但空\"的主机,导致 get_index_bars 返回空 DataFrame。\n\nget_market_stat 早有 _find_host_returning_quotes 兜底,但 K 线查询缺失同类逻辑。\n本提交补齐对称设计:\n\n- 新增 _find_host_returning_bars(sync + async):照搬 _find_host_returning_quotes\n\n[…]\n + async 共 4 处)接入:空数据且\n auto_reconnect 开启时触发逐台实测,找首台返回数据的 host\n- 新增 TestIndexBarsEmptyFailover(5 个测试):覆盖空数据切 host、全候选空、\n 非空不触发、auto_reconnect=False 不触发、个股 K 线同链路\n\n验证:23 个 failover 测试全绿,941 个单元测试无回归。",
"is_bot": false,
"headline": "fix(client): get_index_bars/get_security_bars 加空数据故障转移",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-10T11:37:31Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "a8c035010eb0901434c16b5a500ba3d319a609cd",
"body": null,
"is_bot": false,
"headline": "style(tests): ruff format 修复尾随空行(CI ruff format --check 失败)",
"author_name": "GitHub",
"author_login": "actions-user",
"committed_at": "2026-07-10T07:17:00Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d84a08b7f27623d233e63ef14426a01653d74936",
"body": "#31: RebalanceEngine 缺失价格导致净值假崩塌\n 已持仓标的当日缺 K 线(停牌/日历错位)时 prices.get(code,0)=0,\n 持仓市值记 0 → 净值单日暴跌(159915 在 20210208 缺一天,持仓 ~93%,\n 净值 1.1M→91,845,全期最大回撤 -92%)。\n 修复:last_known_price forward-fill,缺失日沿用最近已知收盘价。\n 附带:_compute_performance 最大回撤改正值口径(与 BacktestEngine 一致)。\n 验证:真实 ETF 数据 max_drawdown 24.0\n[…]\ntotal_return=0 但 trades 表有 PnL)。\n 修复:改为按\"位置索引\"匹配(归一化 datetime 后查位置),类型无关。\n 验证:int-trade+datetime64-df 修复前净值=100000(恒定),修复后=100289。\n\n测试:新增 4 回归测试(未修复代码上均失败,修复后通过);\n全套 936 passed;mypy 改动文件零错误;ruff 全绿。",
"is_bot": false,
"headline": "fix(backtest): v1.20.3 修复回测绩效统计两个 bug(issues #30 #31)",
"author_name": "GitHub",
"author_login": "actions-user",
"committed_at": "2026-07-10T07:12:47Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "06be298c927182492febe9c6be9ab523583be611",
"body": "GitHub OIDC → sigstore 服务连续 503 overflow(v1.20.2 两次 publish 失败),\n非配置问题(v1.20.1 能发成功证明 trusted publishing 配置正确)。\nattestations 是可选签名,关闭后上传正常进行;待服务稳定后改回 true。",
"is_bot": false,
"headline": "ci(publish): 暂时关闭 attestations 规避 OIDC 503 故障",
"author_name": "GitHub",
"author_login": "actions-user",
"committed_at": "2026-07-09T14:12:20Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "471c79d5e21dbe17e66f394f7bb94099aa8bd2a4",
"body": "fix(types): v1.20.2 修复 v1.20.1 引入的 CI mypy 失败",
"is_bot": false,
"headline": "Merge pull request #29 from handsomejustin/fix/v1.20.2-mypy-ci",
"author_name": "毛利哥",
"author_login": "handsomejustin",
"committed_at": "2026-07-09T12:59:58Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "bc0ea064baf512ffa8be017402d5765598370c57",
"body": null,
"is_bot": false,
"headline": "chore: retrigger CI (Actions queue stuck 20min)",
"author_name": "GitHub",
"author_login": "actions-user",
"committed_at": "2026-07-09T12:55:16Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "a67e5588d055e4c4ae7e9a87f67b293097fff646",
"body": "v1.20.1 把 BacktestResult.performance 扩大为 dict[str, float | str]\n(为塞 diagnostic_warning 字符串),破坏 6 处下游消费方类型契约,\nCI mypy job 转红。重构为更干净的设计:\n\n- 诊断信息走独立的 BacktestResult.diagnostic 字段(performance.py\n PerformanceAnalyzer.diagnostic 属性 → engine 透出 → to_dict 含该字段\n → CLI 显示),performance 字典回归 dict[str, float](保留别名键)。\n- optimizer.py 补 npt.NDArray 标注,消除 3 个既有 ndarray type-arg 错误。\n\n测试:932 passed;mypy backtest/+portfolio/ 零错误;ruff 全绿。",
"is_bot": false,
"headline": "fix(types): v1.20.2 修复 v1.20.1 引入的 CI mypy 失败",
"author_name": "GitHub",
"author_login": "actions-user",
"committed_at": "2026-07-09T12:34:23Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "eb51770608f1c4cfe7e3e62111fc4d79767e88ff",
"body": "fix(backtest): v1.20.1 修复回测引擎 3 个 bug(issues #22 #23 #25)",
"is_bot": false,
"headline": "Merge pull request #27 from handsomejustin/fix/v1.20.1-backtest-bugs",
"author_name": "毛利哥",
"author_login": "handsomejustin",
"committed_at": "2026-07-09T12:17:00Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "00b8c6374a92781f1aee06d75529e5780047e081",
"body": "排查发现用户反馈的\"回测统计数据缺失/异常\"并非服务器连接问题,\n而是回测引擎与组合优化器自身的代码缺陷:\n\n#23: 首根 bar 访问 close[-1] 崩溃\n _SeriesAccessor 负向越界改返回 NaN(不抛 IndexError);\n BacktestEngine 新增 warmup_bars 参数跳过指标预热期。\n\n#25-A: FactorWeightedOptimizer 权重坍缩\n n_stocks=2 且得分接近时,减最小值把低分标的权重压到 ~6e-8,\n 等于单股满仓、n_stocks 被无视,进而出现持仓1只/-99.98%回撤。\n 新增 _ap\n[…]\n据异常诊断\n performance dict 新增 sharpe_ratio/start_cash/end_value 别名键;\n 资金曲线异常时返回 diagnostic_warning 而非静默全 0,CLI 显示提示。\n\n文档:README 加入回测手册导航;backtest_usage.md 补充 warmup 说明。\n测试:新增 6 个回归测试,更新 3 个;932 passed。",
"is_bot": false,
"headline": "fix(backtest): v1.20.1 修复回测引擎 3 个 bug(issues #22 #23 #25)",
"author_name": "GitHub",
"author_login": "actions-user",
"committed_at": "2026-07-09T12:16:12Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "e21c4d595c01e5fd43cc6fc03347ff1445e39855",
"body": "feat(backtest): v4.2 新增 FSL 分水岭指标策略(单标的/组合/寻优)",
"is_bot": false,
"headline": "Merge pull request #26 from handsomejustin/feat/fsl-indicator-strategy",
"author_name": "毛利哥",
"author_login": "handsomejustin",
"committed_at": "2026-07-09T12:10:42Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "052119e6ae3d3a0f67413dbe7ccfbd184c68e430",
"body": "- MyTT.py: 新增 FSL(CLOSE, VOL, CAPITAL) 分水岭指标\n - SWL = (EMA(C,5)*7 + EMA(C,10)*3) / 10\n - SWS = DMA(EMA(C,12), MAX(1, 100*SUM(VOL,5)/(3*CAPITAL)))\n MIN(A,1.0) 模拟通达信 DMA(X,A) 内部钳制 A<=1 的语义\n- MyTT.pyi: 补 FSL 类型 stub(CI mypy 用 .pyi 校验)\n- strategies/builtin.py: 注册 FslStrategy(SWL/SWS 金叉死叉)\n capital 作为 Param 暴露流通股本输入\n- strategies/presets.py: fsl 寻优网格(4 档 capital)\n- tests: 注册表断言加 fsl,阈值 18→19",
"is_bot": false,
"headline": "feat(backtest): v4.2 新增 FSL 分水岭指标策略(单标的/组合/寻优)",
"author_name": "GitHub",
"author_login": "actions-user",
"committed_at": "2026-07-09T10:42:00Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "8bf4ffb322ab924777176bb109f5b763efa45d10",
"body": "feat: v1.20.0 服务器失败自动 ping 切换(无需手动 easy-tdx ping)",
"is_bot": false,
"headline": "Merge pull request #20 from handsomejustin/feat/auto-failover",
"author_name": "毛利哥",
"author_login": "handsomejustin",
"committed_at": "2026-07-08T10:13:03Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d0d65d64b8c54299d588229897a90195305541d4",
"body": "服务器连不上或返回空数据时,自动测速切到延迟最低的可用服务器再试,\nPython API / CLI / Web API 三入口全部自动生效。\n\n核心改动:\n- _reconnect.py 新增 select_best_host_sync/async(连接失败 failover)\n 和 find_working_host_sync/async(空数据逐台实测)\n- 8 个 client 的 _execute 注入跨主机故障转移(复用 auto_reconnect 开关)\n- get_market_stat 空数据时按延迟顺序逐台实测找返回数据的服务器\n- 新增 _reconnect/_are\n[…]\nect helper 收敛重建连接副本\n- MacClient failover 用 save_best_mac_host(延续 v1.19.4 不污染 best_host)\n- 顺手修复 test_commands_offline 未使用 import(main CI failure 根因)\n\n测试:925 passed(新增 18 个 failover 测试),ruff/mypy 零新增错误。",
"is_bot": false,
"headline": "feat: v1.20.0 服务器失败自动 ping 切换(无需手动 easy-tdx ping)",
"author_name": "GitHub",
"author_login": "actions-user",
"committed_at": "2026-07-08T10:07:37Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "14debdb44699ca46e05737ad5de5418d89a5f232",
"body": "新增 web UI 第六个页面「服务器设置」,让用户在浏览器上:\n1. 列出全部 50+ 候选服务器\n2. 一键并发测速(显示延迟和可达性)\n3. 点选切换到最快或可用的服务器(热重连,无需重启)\n\n解决不同地区/运营商对通达信各服务器连通性不同的问题。\n\n新增:\n- AsyncTdxClient.reconnect_to(host) 热切换方法(持锁,关旧建新)\n- GET/POST /api/v1/server/hosts|test|switch 三个端点\n- ServerSettingsView.vue 页面(列表+测速+切换)\n- 导航栏第六个入口\n\n设计: 先reconnect成功再save_best_host(避免污染config),\nhost校验只允许候选列表内IP,不自动测速避免首屏卡顿。",
"is_bot": false,
"headline": "feat(web-ui): v1.19.7 服务器设置页面(测速+切换通达信服务器)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-07T15:08:32Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c2c3e7ffb4e823d14b9259c71acf1a1b26039b3e",
"body": "v1.19.5 EXE 只有 11MB,双击报 ModuleNotFoundError: No module named 'pandas'。\n根因:release.yml 先 pip install -e '.[web,packaging]' 再 Build frontend,\n但 force-include 要求 web-ui/dist 在 pip install 时就存在——install 阶段\ndist 不存在导致 editable install 静默降级,PyInstaller 收集不到第三方包。\n\n修复:调换步骤顺序,Build frontend 在 Install Python deps 之前。\nci.yml 上一 commit 已同样修复。",
"is_bot": false,
"headline": "fix(release): v1.19.6 release.yml 先构建前端再 pip install(修复EXE丢依赖)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-07T14:23:30Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "e8b088bca18850ef7cf15ea6c72c50fe85c65cac",
"body": "pyproject.toml 的 force-include 要求 web-ui/dist 存在,否则 pip install -e .\n报 FileNotFoundError。ci.yml 的 test 和 mypy job 此前没有构建前端步骤,\n导致全部 CI 失败。\n\n修复:test(6 矩阵)和 mypy job 在 pip install 前加 setup-node + npm ci +\nnpm run build。frontend job 本来就有,不变。\n\n同时清理 pyproject.toml:恢复 force-include(之前误改成 build-hook 但\nhatchling 自定义 hook 需要插件注册,过于复杂)。",
"is_bot": false,
"headline": "fix(ci): ci.yml test/mypy job 先构建前端再 pip install",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-07T14:14:03Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "9ab093a3c77281bdab634838fc2b7148d7eac7bf",
"body": "python -m build 先构建 sdist 再从 sdist 构建 wheel。sdist 默认尊重 .gitignore\n不含 web-ui/dist(构建产物),导致 wheel 阶段 force-include 找不到源报\nFileNotFoundError。用 [tool.hatch.build] artifacts 强制 sdist 和 wheel\n都包含 web-ui/dist。\n\n本地验证:sdist 含 4 个 dist 文件,wheel 含 4 个 web/dist 文件。",
"is_bot": false,
"headline": "fix(build): hatchling artifacts 让 sdist 也含 web-ui/dist",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-07T12:50:48Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d5309454bcac075c9f7aa388040a2b7fd38de937",
"body": "根因:PyPI wheel 只有 Python 包,不含前端 dist。_resolve_web_dist_dir\n三级探测(env/_MEIPASS/仓库根)在 PyPI 安装环境下全失败返回 None,\nStaticFiles 不挂载,GET / 返回 404。\n\n修复:\n- pyproject.toml hatchling force-include 把 web-ui/dist 映射到包内\n easy_tdx/web/dist\n- publish.yml 在 python -m build 前先 npm ci && npm run build\n- _resolve_web_dist_dir 新增第 4 级探测:包内 easy_tdx/web/dist\n\n验证:本地构建 wheel 含 4 个前端文件,模拟 PyPI 安装后第 4 级探测\n成功找到 dist。907 测试全过。",
"is_bot": false,
"headline": "fix(packaging): v1.19.5 PyPI wheel 含前端 dist(pip install 后不再 404)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-07T12:46:26Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "3bc15e620c66ba662d7d2296aefcbd2b4f1dd9bd",
"body": "根因:mac/client.py 的 MacClient.from_best_host 和 AsyncMacClient.from_best_host\n调用 save_best_host(best),把选中的 MAC 协议服务器(如 121.36.248.138)\n写进了全局 best_host 字段——但这个字段是标准 TDX 协议用的。之后标准\nAsyncTdxClient 用 get_best_host 读到这个 MAC host,用标准协议请求 MAC\n服务器,返回空 body(偏移 2 剩余 0)。web app lifespan 启动 MAC 客户端\n时触发污染。\n\n修复:\n- c\n[…]\nave_best_mac_host\n- mac/client.py 4 处改用新字段(不再碰 save_best_host / get_best_host)\n- get_best_host 加交叉污染校验:缓存 host 不在标准列表里自动重置\n (自动修复已被污染的 config.json,用户无需手动操作)\n\n测试:新增 5 个回归测试(污染检测 + MAC host 隔离),907 全过。",
"is_bot": false,
"headline": "fix(config): v1.19.4 MAC客户端不再污染标准best_host(取不到数据根因)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-07T12:36:44Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "032f5a40c06c77ee2e44033775f3fba8b79dc67d",
"body": "两个独立问题(日志证据):\n\n1. K线空body 500:SH600519 等正常股票偶发请求时,通达信返回\n ret_count>0 但 body 完全为空(偏移2 剩余0)。v1.18.3 的容错有\n 'if bars:' 条件,bars 为空时 raise → 500。移除该条件,始终 return\n (空列表让前端分页重试比 500 友好)。GetIndexBarsCmd 同改。\n\n2. SPA fallback 缺失:/optimize /portfolio 等前端路由刷新时 404。\n 子类化 StaticFiles 为 SPAStaticFiles,404 时返回 index.html。\n API 路径不受影响。\n\n测试:更新 test_security_bars_truncated_first_record(原断言 raise,\n现断言返回空列表)+ 新增空 body 回归测试。902 全过。",
"is_bot": false,
"headline": "fix(web): v1.19.3 K线空body不再500 + SPA路由刷新404",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-07T12:13:39Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6b514874772aaf4ca41cec8c584a6a1f50f0700a",
"body": "根因:测试用 max_workers=1 串行执行 5 个任务,断言「前 2 个被淘汰」。\n但 LRU 淘汰发生在 submit() 时,淘汰的是「当时最旧的非 running 任务」。\n快机器上 t0 还在 running(被跳过),实际淘汰 t1/t2;慢机器(CI windows\n3.12)上 t0 已完成,淘汰 t0/t1——取决于提交速度 vs 执行速度的竞态。\n\n修复:不断言特定 task_id 被淘汰,改为验证不变量:\n(1) 最后提交的任务一定存活(LRU 最近)\n(2) 至少淘汰 2 个(5 - max_results 3)\n(3) 存活任务数 ≤ max_results\n\n本地跑 10 次全过,不再 flaky。",
"is_bot": false,
"headline": "test: 修复 test_task_runner_lru_eviction 的竞态 flaky",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-07T07:47:13Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "27a8ab1c7630ac19f21233a002e58640cc0a324c",
"body": "合并 e3e8dd4 时采用远程版本,但其 _log.warning 字符串换行未跑\nruff format,导致 ci.yml 的 format 检查失败。纯格式调整,无逻辑变化。",
"is_bot": false,
"headline": "style: ruff format security_bars.py(修复 ci.yml format 检查)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-07T07:27:20Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ecd6e298f51ab25d0b9de5121e80c1845d073c16",
"body": "根因:干净 Windows(无开发环境)注册表里没有 .js 的 Content Type 映射,\nmimetypes.guess_type('.js') 返回 None,StaticFiles 回退到 text/plain。\n浏览器 <script type=\"module\"> 启用严格 MIME 检查,拒绝执行 text/plain\n的 JS → Vue 不挂载 → 页面纯黑(/docs 不受影响因为它是 API 路由)。\n\n修复:mount 前用 mimetypes.add_type 强制注册 .js/.mjs/.css/.svg 的正确\nMIME,无论机器装没装开发工具都生效。\n\n同时改 easy_tdx.spec:datas 用 pathlib.rglob 逐文件展开,更可靠。",
"is_bot": false,
"headline": "fix(packaging): v1.19.2 修复干净 Windows 上 EXE 页面纯黑(MIME 类型)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-07T07:18:53Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "957cb8eeb0237ed23ffa3cc0818cc836d08e1eb9",
"body": "# Conflicts:\n#\tCHANGELOG.md\n#\tpyproject.toml\n#\tsrc/easy_tdx/commands/security_bars.py",
"is_bot": false,
"headline": "Merge commit 'e3e8dd4'",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-06T17:54:20Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "0b4ed9af62e78eac0d05a3716576a7d6c8ab3d6d",
"body": "面向零基础老年用户,easy-tdx 可打包成单一 Windows EXE,双击即用。\n\n新增:\n- 后端同源托管前端 dist(app.py 三级探测:env → _MEIPASS → web-ui/dist)\n- easy-tdx serve 默认 --open-browser,启动后自动开浏览器\n- PyInstaller 打包入口(__main__.py)+ spec 配置(easy_tdx.spec)\n- 系统托盘(tray.py):右下角图标,右键\"打开浏览器/退出\"\n 解决老人不会用任务管理器关闭的问题\n- GitHub Actions release.yml:打 v* tag\n[…]\nimport __main__ → freeze_support + 子进程检测\n 3. 系统托盘需主线程消息泵 → uvicorn 挪到后台线程\n\n文档:\n- README/手册改为三档分流:EXE(零基础)/ Python(一条命令)/ 源码(打包)\n- 删除 npm run dev / 5173 / 两个终端的过时说明\n- 手册补虚拟环境配置 + EXE 打包附录 + EXE 排错 FAQ",
"is_bot": false,
"headline": "feat(packaging): v1.19.1 支持 Windows 单 EXE 打包 + 系统托盘 + 自动发版",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-06T17:48:07Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "e3e8dd492eea9551923acffd709bf08d7b1b6f00",
"body": "- security_bars: parse_response 遇末尾残缺记录时丢弃并返回前 N-1 条,\n 避免 TDX 服务端截断响应导致整页 500(如 000408 count=800 日线)\n- OptimizeView: 一键寻优并发默认 8 进程,用户选择持久化到 localStorage\n\nbump version to 1.18.3",
"is_bot": false,
"headline": "fix: K线响应截断容错 + 一键寻优并发默认8进程",
"author_name": "GitHub",
"author_login": "actions-user",
"committed_at": "2026-07-06T05:23:37Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c901d198cafaf56581bd97c6039611fb82efe830",
"body": "记录移除拼音声母搜索的回退发版",
"is_bot": false,
"headline": "chore: bump version to 1.18.2 + CHANGELOG",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-05T18:55:58Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c9d80617e815e8f547a8ffb5f1444091ecfcc52a",
"body": "拼音搜索的底层依赖(首次需爬沪深 A 股 5000 条全名单,几十次 TDX 协议\n往返,慢机器几十秒到超时)太重,反复优化(按需加载/遮罩/单飞/预热)\n都无法兼顾'不阻塞核心行情请求'与'首次可用'。用户决定放弃此功能,\n回到简单稳定的 6 位代码输入。\n\n回退 e2bf29e..2523605 共 6 个 commit 的全部改动:\n- 删除 StockSearchInput / AppInitOverlay / useStockSearch\n- 移除 pypinyin 依赖、/security/search-index 端点、lifespan 预热\n- SymbolPicker / StocksPicker 恢复为纯 6 位代码输入\n- README / CHANGELOG 同步回退\n\n代码状态等同 v1.18.1(一键寻优多进程并发)发布后的干净基线",
"is_bot": false,
"headline": "revert: 移除拼音声母搜索,回到 6 位代码输入",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-05T18:50:51Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "25236056f7f9bbbd9e6dd8cebc2030ea606ff877",
"body": "问题:上一版单飞用 asyncio.Future 共享,lifespan 预热用 wait_for(120s)\n超时会 cancel 整个 Future,导致同时等待的 /security/search-index 端点\n请求收到 CancelledError 返回 500。日志显示预热 TimeoutError + 端点 500。\n\n修复:\n- _build_search_index 改用轮询设计:后台 _do_build task 独立运行\n (fire-and-forget),调用方通过轮询 _SEARCH_INDEX 每 0.5s 检查结果\n- 调用方被 cancel(预热超时)只退出轮询,不影响后台构建 task\n- lifespan 预热去掉 wait_for,改为纯 best-effort 触发,无权取消构建\n- 冒烟验证:预热被 cancel 后,端点请求仍能正常拿到结果(5206 条)\n\n另外从日志看慢机器爬全名单 >120s,轮询上限提到 300s 兜底",
"is_bot": false,
"headline": "fix(web): 单飞改用轮询解耦,预热超时不再波及端点请求",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-05T18:39:02Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "04fd720a05d5ef9d8a4ee9e9c2209ccef91ab44c",
"body": "问题:另一台机器首次加载报 'signal is aborted without reason'——\n不是取不到数据,是前端 AbortController 15 秒就掐断了请求,但遮罩\n却告诉用户等 30-60 秒,自相矛盾。\n\n修复:\n- 前端 fetchSearchIndex 超时 15s → 120s:遮罩本就是要等的,不能自己掐断\n- 后端 _build_search_index 单飞去重:用 asyncio.Future 让 lifespan 预热\n 与 /security/search-index 端点请求共享同一任务,避免并发各爬一次全名单\n (两个任务抢同一把 _execute_lock 反而互相拖慢)\n- lifespan 预热改为调 _build_search_index(复用单飞),加 120s 超时保护",
"is_bot": false,
"headline": "fix(web-ui): 搜索索引前端超时 15s→120s + 后端单飞去重",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-05T18:31:20Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6b01c0687c6678016c89a5d03ad6def570d31562",
"body": "新机器首次打开页面时,拼音搜索索引构建要走全量 get_security_list_all\n(几十秒),期间无任何反馈,体验极差。本次双管齐下解决:\n\n前端:\n- 新增 AppInitOverlay 全局遮罩:加载期间全屏灰底 + blur + spinner +\n 诚实文案(预计 30-60 秒)。就绪后 fade-out;失败也消失(不卡死)\n- useStockSearch 改为模块级响应式状态,App 根 setup 时 eagerLoad 触发\n- 方案 A:无跳过按钮强制等待(一次性代价),失败时降级为可手输代码\n\n后端:\n- lifespan 启动钩子加后台预热:asyncio.create_task 触发一次\n get_security_list_all('all'),不阻塞服务启动,让本地缓存提前建立\n- 用户打开页面时后端缓存大概率已就绪,遮罩可秒消失",
"is_bot": false,
"headline": "feat(web-ui): 股票搜索索引加载期间全站遮罩 + 后端启动预热",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-05T18:20:12Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "9048dfecaa1275cfba06963481bc108a7b1f013a",
"body": "问题:新机器首次打开回测/寻优页时,组件挂载即触发 /security/search-index,\n后端要走全量 get_security_list_all(几十次 TDX 协议往返,几十秒)。\n而 AsyncTdxClient 是单连接 + _execute_lock,索引构建期间独占连接,\n把 /bars 行情请求和寻优请求全部堵在后面,表现为点「一键寻优」后\n等好久才出现第一根 bars 请求。\n\n修复(三招组合):\n- useStockSearch: 挂载即拉 → 改为显式 ensureLoaded(),只在首次聚焦\n 输入框时才触发,确保「打开页面直接点取行情/寻优」的核心路径不被拖累\n- fetchSearchIndex: 加 AbortController 15s 超时,避免长时间独占连接\n- 静默失败降级:索引拉取失败只记 loadError 显示小 ⚠,不弹错不阻塞",
"is_bot": false,
"headline": "fix(web-ui): 拼音搜索索引改为按需加载,避免首次构建阻塞行情请求",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-05T18:01:47Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "dfbafd5ad44b178c993ada85d4f4ed4f7362bd96",
"body": "- 前置条件代码块注释加 pypinyin\n- 新增 ⚠️ 升级注意:旧版本升级必须重跑 pip install -e \".[web]\" 否则搜索端点 500\n- 单标的回测取行情描述补上拼音声母搜索能力说明",
"is_bot": false,
"headline": "docs(readme): 补充拼音声母搜索的升级提示(v1.19.0 需重装 [web] extra)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-05T17:49:49Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "e2bf29e931171a44edbf14441948d41e11d95903",
"body": "所有股票代码输入框(回测/寻优/组合页)支持代码/中文名/拼音声母三路\n匹配的下拉联想。复用项目里早已有但从未被前端消费的 security_list_all\n数据(沪深 A 股 5206 只完整中文名表)。\n\n后端:\n- 新增 GET /api/v1/security/search-index 端点,返回 [{code,name,initials}]\n- 声母用 pypinyin FIRST_LETTER 预计算,进程内缓存,首次算一次常驻\n- web extra 新增 pypinyin>=0.50 依赖\n\n前端:\n- 新增 useStockSearch composable(模块级缓存 + 三路过滤 + 防抖)\n- 新增 StockSearchInput.vue(下拉 + 键盘导航 + 市场标签)\n- SymbolPicker / StocksPicker 接入新组件,保留直接输 6 位代码的快路径\n\n实测 zjxc→中际旭创、gzmt→贵州茅台、cjxc→楚江新材,5000 条本地过滤 <5ms",
"is_bot": false,
"headline": "feat(web-ui): 股票代码输入支持拼音声母搜索(zjxc→中际旭创)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-05T17:43:57Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "044e21064c9ed18843be7bcdc04fd15f0a72cc9e",
"body": "本次发布两个主题:\n1. 一键寻优所有策略支持多进程并发(串行/4/8/16)\n2. 策略库组合回测结果加组合评级 + 市场前缀纠正",
"is_bot": false,
"headline": "chore: bump version to 1.18.1 + CHANGELOG",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-05T16:59:41Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "d32c5e17b443e92c1d7adf3aedc266bcbdd18236",
"body": "回测是 numpy/pandas 的 CPU 密集计算并持有 GIL,多线程无加速,必须用\n多进程。照搬项目里已跑通的 screen/scanner.py 进程池模板。\n\n后端:\n- OptimizeAllBacktestRequest 新增 workers 字段(默认 1=串行,0-32)\n- 抽出模块顶层函数 _optimize_one_strategy(可被 ProcessPoolExecutor\n pickle),策略类在子进程内构造,避开跨进程 pickle 问题\n- _run_optimize_all 在 workers>=2 时用进程池并行,否则走原串行逻辑\n- 顺带修复 backtest_schemas.py 的 E501 历史遗留\n\n前端:\n- OptimizeView 配置区新增「一键寻优并发」选择器:自动检测 CPU 核数\n + 串行/4/8/16 进程下拉,默认串行,标注推荐档 min(CPU, 8)\n- types.ts/api.ts 透传 workers 字段\n\n实测 8 进程 vs 串行提速 4-6x;端到端冒烟验证并行与串行结果完全一致",
"is_bot": false,
"headline": "feat(web-ui): 一键寻优所有策略支持多进程并发(串行/4/8/16)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-05T16:59:26Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "5aec4996c63142e89e5f01b6fe1edde821751acc",
"body": "- StrategiesView: 组合回测结果区新增「组合评级」详情块,复用\n gradePortfolio(与 /portfolio 页和单标的回测页同口径的 5 维度评分)\n- StrategiesView: 新增 normalizeSymbol,发请求前用 detectMarket 重算\n 市场前缀,纠正历史保存策略的错配(如 SZ:515030 → SH:515030)\n- BacktestView: fullSymbol 改用 market.ts 的 detectMarket,修复 5 开头\n 沪市基金/ETF 被错判为深市的问题(与 SymbolPicker/StocksPicker 统一)",
"is_bot": false,
"headline": "feat(web-ui): 策略库组合回测结果加组合评级 + 市场前缀纠正",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-05T16:59:13Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "6f121dc156f05df6db73c0b2f8974b8430df2f27",
"body": "- OptimizeView「查看」拼 URL 前从输入框实时值取 code/startDate/endDate/category,\n 导致两个问题:(1) 寻优后改输入框代码再点查看,URL 指向没回测过的标的;\n (2) 切走再回 /optimize,组件 ref 丢失,URL 里 symbol/startDate/endDate 全空。\n- 将标的上下文快照(取行情成功那一刻冻结)下沉到 Pinia store 的 optimizeContext,\n 与 optimizeResult/optimizeAllResult 同生命周期持久化,跨路由保留。\n- buildBacktestQuery 改读 store.optimizeContext,输入框改动与组件卸载都不再污染。",
"is_bot": false,
"headline": "fix(web-ui): 参数寻优「查看」用寻优时的标的快照,避免输入框/路由切换污染 URL",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-05T10:03:12Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "173069d5181659336976676d036f0902e1e0b989",
"body": "get_unusual 的 count 实际被 min(count,600) 截断,写 1000 也只能拿到 600。\n改用 start 翻页 + pd.concat 合并,直到某页不足 600 视为到尾。",
"is_bot": false,
"headline": "docs(example): 异动示例改为分页拉取全部(单次 600 协议上限)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-05T10:02:38Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "5c520c513d03068bde5338ea99e75bb91e3bb2f4",
"body": "新增「第十章补充」完整章节,详细写:\n- 如何保存策略组合(💾 保存为组合按钮 + 弹窗)\n- 如何载入组合(↻ 重跑到今天,自动用今天重跑)\n- 持仓三态徽章含义(持有/持有·浮亏/空仓·等买点)\n- 三处警示(过拟合/模型仓位水印/载入确认)\n- 完整日常使用流程(每天收盘后重跑看信号变化)\n- 组合的局限(模型仓位≠真实账户、资金均分、过拟合风险)\n\n补充更新:\n- 第四章 4.4 加投资大师名言轮播说明(100 条/3 秒轮播)\n- 第六章 6.2 加策略库 Tab 分类说明(单标的/组合)\n- 封面 + footer 版本号 v1.17.14 → v1.18.0\n- 目录加第十章补充链接\n\n保持原作者口语化新手教学风格,沿用原文半角标点惯例。",
"is_bot": false,
"headline": "docs: 上手手册更新到 v1.18.0(策略组合保存+一键看今日信号+Tab 分类)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-04T19:06:40Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c57cde156b23ea427d78e3306a164394b6ce69ac",
"body": "v1.18.0\n\n策略库新增 kind='multi' 类型:组合回测结果可保存为组合,\n卡片「↻ 重跑到今天」一键用今天重跑得到截至当天的策略信号\n(持仓/空仓/浮盈/浮亏三态高亮)。策略库拆成「单标的」「组合」\n两个 Tab,避免数量多了混排难找。\n\n核心机制:保存配置 → 载入即重跑到今天 → 看持仓表 = 今日信号。\n复用现有 SQLite 单表(kind 字段本就是 TEXT),零数据库迁移,\n零后端逻辑改动,仅 schema Literal 加 'multi'。\n\n含 10 项代码审计修复:multi-icon 字体误用、lastComboCash 漏赋、\n弹窗 ARIA/ESC/\n[…]\nvue(保存组合+Tab+三态+审计修复)\n- 改 src/easy_tdx/web/backtest_schemas.py(kind Literal 加 'multi')\n- 改 web-ui/src/types.ts(TS 类型同步)\n- 改 CHANGELOG.md(补 v1.17.15 + 新增 v1.18.0)\n- bump pyproject.toml 1.17.15 → 1.18.0",
"is_bot": false,
"headline": "feat(web-ui): 策略组合保存 + Tab 分类 + 审计修复 (v1.18.0)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-04T19:01:09Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "15af7a92eeddc151d22b7aa7bd0dd7b69a12130e",
"body": "v1.17.15\n\n一键寻优所有策略按钮改为暖橙渐变(#f59e0b → #ea580c),比 primary\n蓝更醒目,hover 加亮上移、运行中保留暗橙识别度。\n\n寻优运行中右侧不再空白:展示 100 条全球投资大师名言(巴菲特/芒格/\n格雷厄姆/林奇/索罗斯/利弗莫尔/塔勒布/达利欧等),3 秒随机轮播一条,\nFisher-Yates 洗牌避免短期重复,让等待不枯燥、还能学到东西。\n\n视觉细节:\n- 卡片顶部 3 秒线性进度条(暗示后台在跑)\n- fade + slide-up 350ms 过渡动画\n- 底部脉动橙点 + '后台寻优进行中' 提示\n\n纯前端 TypeScript + 原\n[…]\n零依赖新增。\n\n- 新增 web-ui/src/data/investment-quotes.ts(100 条名言)\n- 新增 web-ui/src/components/QuoteCarousel.vue(轮播组件)\n- 改 web-ui/src/views/OptimizeView.vue(按钮样式 + 引入轮播)\n- bump pyproject.toml 1.17.14 → 1.17.15",
"is_bot": false,
"headline": "feat(web-ui): 一键寻优按钮改橙色 + 等待期间投资大师名言轮播",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-04T18:17:22Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "cf843cebaddace8a0a4b8bdb3ec821132c7e470a",
"body": "纯前端 TypeScript 实现,零后端改动。回测/组合/寻优三处结果顶部\n显示评级徽章,让普通人 1 秒判断「适不适合经常参与」。\n\n评级不看收益率(避免被近期大涨误导),只看风险调整后的持有体验:\n卡玛/最大回撤/胜率/利润因子/夏普/波动率六维加权 + 一票否决。\n京东方「收益 126% 但胜率 35%」案例 = D 档(核心验证点)。\n\n长线低频策略(6年6笔)不会被冤枉:交易<10笔时只降权胜率/利润因子\n维度,不否决整个评级(修复用户实测反馈)。\n\n- 新增 web-ui/src/grading/ 核心模块(engine/thresholds/combinedMetrics/index)\n- 新增 GradeBadge.vue + GradeDetails.vue 组件\n- 接入 BacktestView/PortfolioView/OptimizeView(寻优排名表加评级列)\n- 15 个自检测试(node:test + rolldown 打包),覆盖京东方 D / 长线 B 等场景",
"is_bot": false,
"headline": "release: v1.17.14 — Web UI 数据评级系统(S/A/B/C/D 五档,三入口覆盖)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-04T17:21:18Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "66c49d1a8d26bbaf86ef04d9b6ebe0176664a68a",
"body": "单标的回测、组合回测、参数寻优原默认起始日为「3 年前」(动态滚动),\n统一改为固定日期 2020-01-06,便于跨时间横向对比。结束日仍为今天。\nSymbolPicker 共用组件的 defineModel 默认值同步更新。",
"is_bot": false,
"headline": "feat(web-ui): 三处回测入口默认起始日期统一为 2020-01-06",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-04T16:15:00Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "64d08122574dd71facff4730a3d48db064479d51",
"body": "web-ui 此前只有本地 vue-tsc,CI 不覆盖,类型错误 / 破坏性 import\n只有发布后用户才发觉。新增独立 frontend job 跑 npm ci + npm run build,\n与 Python 版本无关故独立于 test matrix,避免跑 6 遍。\n\n本地 npm run build 验证通过(686 模块,575ms)。",
"is_bot": false,
"headline": "ci: 新增 frontend job 防前端构建回归(v1.17.9 数值 bug 教训) (#16)",
"author_name": "毛利哥",
"author_login": "handsomejustin",
"committed_at": "2026-07-04T16:03:14Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2f3826d84d10a2bbc6f0f578f23c1f904b21f201",
"body": null,
"is_bot": false,
"headline": "docs(README): 添加头部宣传图",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-04T15:14:39Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2f4d7bef8e37710c51ec9b4a9cfe5c825f8d3e64",
"body": null,
"is_bot": false,
"headline": "docs(README): 添加 DeepWiki 徽章",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-04T15:03:33Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "239ff46ac872b166a337cfb48f1367d0ea0eb3fa",
"body": "用户反馈:3 个策略各自最大回撤仅 45.53%/40.16%/16.89%,组合却显示 83.76%。\n根因:_build_combined_equity 的 drawdown_pct 分母误用 initial(固定初始资金),\n净值大涨后(总收益 545%,峰值≈6.45×初始)绝对回撤额除以小初始值被等比放大。\n改为 drawdown / peak(相对当时峰值,0~1),与单标的 PortfolioTracker 一致。\n连带修复卡玛比率(年化/最大回撤)。其余指标经核对均正确。加回归守卫。",
"is_bot": false,
"headline": "release: v1.17.13 — 修复多策略组合回测最大回撤虚高(分母误用初始值而非峰值)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-04T13:07:39Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ee628c1b345c5560601888fb3dec5eae230d52e2",
"body": "DELETE /api/v1/strategies/{id} 原用 status_code=204,较新 FastAPI/Starlette\n在路由注册阶段(add_api_route)就抛 AssertionError: Status code 204 must\nnot have a response body,导致 CI ubuntu 矩阵 21 个 web 测试因 fixture\n导入 router 连带 ERROR。改为返回 200 + {\"deleted\": id} 确认体。",
"is_bot": false,
"headline": "release: v1.17.12 — 修复 CI 在新版 FastAPI 上路由注册失败",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-04T12:50:07Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "05dc9a74af499d793e197d931a7f5d4be30eae14",
"body": "新增两层能力:\n1. 策略库:单标的/组合回测结果可保存到本地 SQLite 单文件\n (~/.easy_tdx/strategies.db),策略库页可载入回填、重跑、删除。\n2. 多策略组合回测:勾选 N 个单标的策略,各拿 1/N 资金、各跑原标的,\n 净值曲线按日期并集对齐求和,组合结果含 19 项完整绩效指标 + 持仓表。\n\n后端:strategy_store.py(SQLite CRUD) + multi_strategy_engine.py(资金分仓引擎)\n+ routers/strategies.py + /backtest/multi-strategy/run/async。\n前端:StrategiesView.vue + 保存策略按钮 + 复用组合页图表组件。\n\n895 单测全绿(+24 新增),ruff/mypy strict/前端 vue-tsc 全通过。",
"is_bot": false,
"headline": "release: v1.17.11 — Web UI 策略库(SQLite 持久化)+ 多策略资金分仓组合回测",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-04T12:40:57Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "4db7104209bdb019aedd875ca89fecf292edea11",
"body": null,
"is_bot": false,
"headline": "docs(README): Web UI 截图改为一行一张",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-04T03:39:44Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "1838995c519703ef4e66f48b908a67edc6db2a33",
"body": null,
"is_bot": false,
"headline": "docs(README): 添加 Web UI 回测可视化截图展示",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-04T03:37:01Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "07205b616698f07c087fcbe9ef4953e6df7d03ba",
"body": "- SymbolPicker code/category/startDate/endDate 改 defineModel 双向同步\n- OptimizeView 两个「查看」按钮跳转 URL 附带 symbol/startDate/endDate/category\n- BacktestView onMounted 回填标的/周期/日期到 SymbolPicker 表单\n- 向后兼容:老 URL(只有 strategy/params) 仍正常,缺失字段保持默认\n- 前端 vue-tsc / vite build 通过,后端 870 单测全绿(无回归)",
"is_bot": false,
"headline": "release: v1.17.10 — 一键寻优「查看」跳转携带完整行情上下文",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-03T20:12:46Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "57cad1f3dbb8fbce982ff97ef796a02aac747e4e",
"body": "- 交易盈亏指标改为收益率口径(avg_win/loss/max_win/loss = pnl/cost_basis)\n- 平均持仓天数改用真实日历日相减(原 YYYYMMDD 整数差跨月放大)\n- 盈亏比无亏损时记 999.0(原 0.0,与 100% 胜率自相矛盾)\n- 新增 Trade.cost_basis 字段 + engine 填充\n- 3 个回归守卫;870 单测全绿,ruff/mypy strict/前端 vue-tsc 通过",
"is_bot": false,
"headline": "release: v1.17.9 — 修复回测交易统计离谱数值(前后端口径错配 + 持仓天数跨月放大)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-03T19:55:05Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "f7cb015ec6e69cd439d67a555a034d6074f80eca",
"body": "在 Python API「扩展市场」章节加 goods_transaction_all 示例(港股当日全部逐笔),\n并补 start 倒序语义说明(start=0 为最新/收盘方向,count=2000 默认只取最近 2000\n笔会集中在尾盘,需全天用 goods_transaction_all)。同步 GitHub Wiki。",
"is_bot": false,
"headline": "docs(README): 补充港股 goods_transaction_all + 逐笔倒序语义说明",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-03T19:19:11Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "cd790c9404157226fd1f7ee704307e6589fb86c8",
"body": "test_task_runner_does_not_evict_running 用 release.wait(timeout=5) 钉住慢任务保持\nrunning,但 CI 慢环境(windows 3.10)下整个测试执行超过 5s 后任务因超时自动完成、\n状态变 done,掩盖了「running 任务被 LRU 错误淘汰」的回归断言。\n\n改为 timeout=30 留足 CI 慢环境余量(release.set() 仍是确定性释放点)。该测试用于\n守护 LRU 淘汰跳过 running 任务的并发正确性,与港股逐笔成交无关,是 v1.17.2 引入的\n预先存在问题。\n\n本地连跑 5 次稳定通过,867 单测全绿。",
"is_bot": false,
"headline": "release: v1.17.8 — 修复 windows 3.10 flaky 测试超时",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-03T18:24:48Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "56153eeb971dbf58ecf598553c5a9afd9e954227",
"body": "1.17.5 引入的 tests/fixtures/ex_history_transaction.json 含中文注释,Windows CI\n默认用 cp1252 解码 UTF-8 文件触发 UnicodeDecodeError: 'charmap' codec can't\ndecode byte 0x8f,导致 3 个 Windows 矩阵的 test_parse_ex_history_transaction_hk\n失败(Linux 矩阵因默认 utf-8 不受影响)。\n\n修复:所有 fixture 读取显式指定 encoding='utf-8':\n- test_hk_transaction.py 的 load_hex/load_json(直接触发点)\n- test_commands_offline.py 的 load_hex(纯 ASCII hex,当前未触发但一并统一)\n- test_decode_errors.py 的 _load_hex(同上)\n\n867 单测全绿,ruff format/check / mypy strict 通过。",
"is_bot": false,
"headline": "release: v1.17.7 — 修复 Windows CI fixture 读取编码",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-03T18:11:27Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "94f631a06b778483b397333b6dfce2a29244f3e0",
"body": "回应 issue #14 后用户反馈:默认 count=2000 取回的成交时间全集中在尾盘。\n根因是通达信逐笔协议(A 股 0x122F 与港股 ex 0x23FC/0x2406 一致)的 start 为\n倒序语义——start=0 指向最新一笔(收盘方向),并非 bug。02715 全天成交 13327\n笔,count=2000 只取最近 2000 笔故集中在尾盘。\n\n新增:\n- goods_transaction_all(MacExClient 同步+异步)—— 港股股票类市场自动按 1800/页\n 翻页取全天全部逐笔成交,安全上限 50 页(90000 条)。返回协议原生倒序,需正序\n[…]\nart 倒序语义说明,引导需全天数据用\n goods_transaction_all。\n\n修复:\n- 1.17.5 的 test_hk_transaction.py 未过 CI ruff format --check(注释对齐、\n MacTransaction 单行化、文末空行)。\n\n867 单测全绿(+5 全量取数测试),ruff format/check / mypy strict 通过。",
"is_bot": false,
"headline": "release: v1.17.6 — 港股逐笔成交全量取数 + start 倒序语义文档",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-03T18:03:47Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "33239525a962223da17eef851dc24aa5e0dd07c5",
"body": "CI 的 ruff format --check 报该文件需重格式化:参数化注释前双空格对齐为\n单空格、两处 MacTransaction 单行化、文末多余空行。仅格式调整,无逻辑变更。",
"is_bot": false,
"headline": "style(tests): ruff format 修复 test_hk_transaction.py (CI format check)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-03T17:45:35Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "40ae2eba53dbec1b212edcdd7d03ef1e49d1b3a2",
"body": "MacExClient.goods_transaction 此前对所有扩展市场统一复用 A 股 MAC 协议的\nSymbolTransactionCmd(0x122F),但 0x122F 数据源未接入港股,服务器对港股\nmarket 一律返回 39 字节空响应(count=0)。\n\n港股股票类市场(HK_STOCK_MARKETS = {27,31,48,49,71,98})改走 ex 扩展行情\n协议:query_date=None → GetExTransactionDataCmd(0x23FC 当日),指定日期 →\nGetExHistoryTransactionDataCmd(0x2406\n[…]\n2F 路径不变。\n\n新增 src/easy_tdx/ex/_hk_transaction.py(市场判定 + 字段映射 + 自动分页,\n同步/异步共用);回归测试 24 例(tests/unit/test_hk_transaction.py)+ 真实\nfixture(港股 00700 在 2026-07-03 的 0x2406 响应)。862 单测全绿,ruff /\nmypy strict 通过。",
"is_bot": false,
"headline": "release: v1.17.5 — 港股逐笔成交协议路由修复 (issue #14)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-03T17:29:17Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "648c9ccfaf72f7594524e098e9cae05cda5583e9",
"body": "v1.17.2 引入的 QFQ 重算代码(adjust.py/client.py/test_mac_qfq_integration.py)\n含 magic trailing comma 导致 ruff format 期望多行参数展开,CI 的\n'ruff format --check src/ tests/' 自 v1.17.0 起一直失败。纯格式变更,\n零逻辑改动(20 例 QFQ 测试全过)。",
"is_bot": false,
"headline": "style: ruff format 修复预存格式债(CI ruff format --check 转绿)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-03T16:17:06Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "78bbe46d016665352da37392310d444c5bca8626",
"body": null,
"is_bot": false,
"headline": "release: v1.17.4 — Web UI 回测交互重构 + 一键寻优全策略",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-03T16:09:19Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ecd8af167656343845a820bb1503186cb799f005",
"body": "- 取消单标的/寻优页独立的「取行情」按钮,整合进「开始回测/开始寻测」\n (SymbolPicker 经 defineExpose 暴露 loadBars,父组件串联取数+回测)\n- 取消市场手动选择(沪/深/北交所下拉),改为 market.ts 按代码段智能\n 识别(17 边界用例验证),代码框旁显示识别结果\n- 成交价下拉精简为中文「开盘价/收盘价」,初始资金默认 100 万\n- 寻优页 ParamGridPicker 切换策略自动填入预设参数网格\n- 寻优页新增「一键寻优所有策略」按钮 + 全局策略排名表\n (OptimizeView 复用 store.runOptimizeAll,结果区含最佳/排名/合计网格点)\n- types/api/store 新增 OptimizeAll 契约 + submitOptimizeAllTask\n- vue-tsc + vite build 通过",
"is_bot": false,
"headline": "feat(web-ui): 取行情整合 + 市场智能识别 + 一键寻优全策略",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-03T16:09:05Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "f6ae69845da6895a154ced75bf22ec0fd9bd75fb",
"body": "- 成交价精简为 next_open/next_close,删除 this_close/worst/best 三种\n 非真实模式(this_close 有未来函数偏差,worst/best 为压力测试边界)\n- 初始资金默认统一为 1,000,000(原 10万/20万)\n- 新增 presets.py:18 策略各配 1-2 参数的预设寻优网格(笛卡尔积≤200)\n- registry.to_schema() 返回 preset_grid 字段供前端自动填充\n- 新增 POST /backtest/optimize-all/run/async:逐策略预设网格寻优 +\n 全局排名(OptimizeAllResult:ranking/best/per_strategy/total_grid_points)\n- 新增 optimize-all 端到端单测 2 例(838 单测全绿)",
"is_bot": false,
"headline": "feat(backtest): 成交价精简 + 一键寻优全策略 + 预设网格",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-03T16:08:38Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "c15bd8232fb90e165915c0d09308f58f11b81dcf",
"body": "通达信服务端 QFQ 模式对长期重度除权股票(如 601088)深层历史页\n返回负价格,导致回测总收益 -3087%、回撤 326.85%、年化 nan、\nbollinger 崩溃、10 策略 invalid-value-in-scalar-power、MyTT divide-by-zero。\n\n客户端兜底:检测 QFQ 负价时用 NONE+XDXR 本地重算前复权\n(因子以除权日前一交易日含权收盘价为基准,保证除权日前后连续)。\n同步+异步双路径一致修复,失败降级返回原值。\n\n- 新增 src/easy_tdx/mac/adjust.py(纯函数 compute_forward_factor/\n apply_forward_adjust/has_bad_prices)\n- MacClient/AsyncMacClient 触发本地重算,XDXR 按 (market,code) 缓存\n- tests: +20 例(16 纯函数 + 4 集成),844 全绿,ruff/mypy 通过",
"is_bot": false,
"headline": "release: v1.17.2 — QFQ 深层历史负价修复",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-03T14:42:04Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "9245bb2ce9e86bc276acec6b95ad5d9eea238964",
"body": "test_web_backtest.py:556 的 isinstance(x, (int, float)) 触发 ruff UP038。\nrequires-python>=3.10 支持 PEP 604 的 int | float 运行时语法, 转换安全。\n该报错会导致 GitHub Actions CI 失败。",
"is_bot": false,
"headline": "chore(tests): 修复 ruff UP038 (isinstance 元组 -> int | float)",
"author_name": "GitHub",
"author_login": "actions-user",
"committed_at": "2026-07-03T07:42:24Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "cc9c1e264d392cc711d7a01f3b248f902ca4300d",
"body": "PR #13 修复了 start_date=None 且最新交易日数据不完整时\ntail_offset 越界/读错位的 bug。原 3 个测试构造的都是完整数据\n(total = days*page_size),走 fallback 分支,未覆盖真正的 partial 分支。\n\n新增 test_multi_tick_charts_partial_day: 构造 days=3, page_size=4,\ntotal=6 的 body,验证 tick_counts 分配为 [2,4,0]、尾部元数据在\nactual_total 反推的偏移处正确解析。已反向验证(回退旧 tail_offset\n公式时该测试立即失败复现 '偏移 239 越界'),非空壳测试。",
"is_bot": false,
"headline": "test(tick_charts): 补 partial-day 回归测试覆盖 PR #13 修复",
"author_name": "GitHub",
"author_login": "actions-user",
"committed_at": "2026-07-03T07:42:06Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ea1a5c6f4a2d94a5759cdaf2b84e892453a0c6be",
"body": "修复easy-tdx tick SH 600519 --days 5",
"is_bot": false,
"headline": "Merge pull request #13 from shom/main",
"author_name": "毛利哥",
"author_login": "handsomejustin",
"committed_at": "2026-07-03T07:23:44Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "49c892b3172b29331dec2c2010f16a77bf2f4871",
"body": null,
"is_bot": false,
"headline": "docs: 扩充 README Web UI 使用说明(前置条件/启动/四页操作步骤/注意事项)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-03T04:04:15Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ef1fea6fcd792ab80f9750c667fc738612721d3b",
"body": null,
"is_bot": false,
"headline": "修复easy-tdx tick SH 600519 --days 5",
"author_name": "shom",
"author_login": "shom",
"committed_at": "2026-07-03T03:58:07Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "bd419da83df9f6db11950dfa55093b24f9e0ffb2",
"body": "回测从命令行升级到浏览器可视化。Vue3 + ECharts 单页应用,零代码完成\n单标的回测、组合回测、参数网格寻优、结果对比四大场景。\n\n后端新增回测 REST API + 策略注册表 + 后台任务执行器,\n内置策略从 5 个扩充到 18 个。823 单测全绿。\n\nREADME 新增 Web UI 章节,CHANGELOG 记录本次全部变更。",
"is_bot": false,
"headline": "release: v1.17.0 — 回测可视化 Web UI(单标的/组合/寻优/对比)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-03T03:55:04Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "83232239377961353d10f1c46d6ebb7df2ef7234",
"body": "4 个独立修复:\n\n1. 寻优跳过参数范围检查:Param.validate 加 skip_bounds 参数,\n RegisteredStrategy.build 透传,ParamGridOptimizer 传 skip_bounds=True。\n 修复寻优时 fast=250 被 max_value=60 拦截的问题(探索超范围值是寻优目的)。\n\n2. 对比页支持组合回测:CompareView 新增 extractComparable() 统一提取净值+指标,\n 支持单标的(performance/equity_curve)和组合(total_performance/comb\n[…]\n填充参数:BacktestView 加 useRoute(),onMounted 读 query.strategy +\n query.params,await nextTick 后覆盖(避免 StrategyPicker watch 重置)。\n 修复寻优点「查看」跳转后参数未填充、仍用默认值的问题。\n\n测试:823 passed, mypy 211 files OK, vue-tsc OK",
"is_bot": false,
"headline": "fix: 寻优跳过参数范围/对比支持组合/日线x轴年份/寻优跳转填充参数",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-03T03:32:44Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "80d62aec563b356372d7aed07d30124086b2b3b4",
"body": "图表日期格式(4 个组件):KlineChart/EquityChart/CompareChart/\nPortfolioCompareChart 的 xAxis 加 axisLabel.formatter,强制显示原始\n日期字符串。ECharts 默认把 ISO 日期当时间对象解析,丢失年份只显示\n月-日+00:00。\n\n组合回测取数翻页(_fetch_portfolio_bars):固定 count=800 改为翻页循环\n(与前端 fetchBars 同逻辑),拼接后 sort_values 排序再过滤。修复组合\n回测图表从 800 根最早点(2023-03-14)开始、缺失早期数据的问题。\n\n实测组合回测 2019-01-07 起:净值 1814 点(不再是 800),首日正确。",
"is_bot": false,
"headline": "fix: 图表x轴显示完整日期 + 组合回测取数翻页",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-02T20:54:04Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2903d8c80039a2984896037cd7b6cf39861a0875",
"body": "新增 13 个经典策略,覆盖趋势/通道/震荡/均线四大类:\n\n趋势类:\n- ema_cross EMA 双线交叉(比 MA 反应更灵敏)\n- triple_ma 三均线系统(多头/空头排列)\n- dmi DMI 趋向指标(+DI/-DI 交叉)\n- trix TRIX 三重平滑(过滤短期波动)\n\n通道/突破类:\n- donchian 唐安奇通道突破(海龟交易法)\n- keltner 肯特纳通道(ATR-based)\n- atr_breakout ATR 通道突破(均线±K×ATR)\n\n震荡/反转类:\n- cci CCI 超卖反弹\n- wr_\n[…]\nal WR 威廉超卖\n- bias_reversal BIAS 乖离反弹\n- emv EMV 简易波动(量价结合)\n- dpo DPO 区间震荡\n\n均线类:\n- bbi BBI 多空指标(4 均线综合)\n\n全部基于 MyTT 现成指标实现,每个策略声明参数 schema 供 Web 表单动态渲染。\n18/18 策略冒烟测试通过(合成数据回测无运行时错误)。",
"is_bot": false,
"headline": "feat(strategies): 内置策略从 5 个扩充到 18 个",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-02T20:36:04Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "0cabae4edaf0982b633ae53c55569b5390724365",
"body": "翻页拼接 concat 后页间时间逆序(page1=最新段,page2=更旧段),\n导致 index 800 处时间从 2026 倒退到 2019。引擎/图表只正确处理前 800 根,\n成交记录在 800 根后出现 2019 年数据。\n\n修复:concat 后加 sort((a,b) => datetime.localeCompare)。\n实测 1600 根排序后整体时间正序,index 799→800 连续递增。",
"is_bot": false,
"headline": "fix(web-ui): 翻页拼接后按时间排序,修复>800根数据图表/成交记录错乱",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-02T20:30:45Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "360f8360016ede45b35382d16c58d8e2d6a10c3d",
"body": "图表格式化:新增 format.ts(fmt2/fmtPct),在 KlineChart/EquityChart/\nCompareChart/PortfolioCompareChart/OptimizeHeatmap 统一应用——tooltip 用\nvalueFormatter、axis 用 axisLabel.formatter,所有数值显示两位小数。\n\n取数翻页:fetchBars 当日期跨度超过 800 根时,循环调用 start=0,800,1600...\n拼接结果,直到覆盖 startDate 或达上限(10 页 ≈ 32 年日线)。翻页后再按\n日期范围闭区间过滤。实测 SZ:000001 翻 2 页拿到 1600 根(2019~2026)。",
"is_bot": false,
"headline": "fix(web-ui): ECharts 数字两位小数 + 取数自动翻页",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-02T20:18:23Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "ff3476f83a764c2c840506ee58f7673b675e822e",
"body": "选 2-4 个已完成的回测 task,叠加净值曲线 + 横向指标对比。\n\n后端(最小增量):\n- task_runner.list_recent(limit):返回最近 N 个任务摘要(LRU 倒序)\n- GET /backtest/tasks?limit=20:任务摘要列表端点(不含完整 result)\n- TaskSummary / TaskListResponse schema\n\n前端(/compare 对比页):\n- CompareView:左栏勾选已完成 task,右栏叠加对比\n- CompareChart:多 task 净值叠加图(归一化为初始=1)\n- CompareTable:多 task 指标横向对比表(8 项指标)\n- 按 result 结构判断可对比性(仅单标的 BacktestResult 可对比)\n\n复用现有 task_runner LRU 表,无新增存储。测试:823 passed(+2 列表端点)",
"is_bot": false,
"headline": "feat(backtest): 回测结果对比页",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-02T20:11:29Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "87fafe91310623712235c2e0913eb106d9a62331",
"body": "对单个策略的 1-2 个参数做网格搜索,遍历用户指定的取值列表笛卡尔积,\n每个组合跑一次回测,按 total_return 排序,返回排名表 + 热力图。\n\n后端:\n- ParamGridOptimizer(backtest/optimizer.py):itertools.product 遍历网格,\n 每点 entry.build(params) + BacktestEngine.run(df),复用同一 DataFrame\n- 网格大小上限 200 防组合爆炸,单点失败容错(跳过不中断)\n- 2 参数时生成热力图矩阵(x/y 轴取值 + cell 收益率)\n- POST /backtest\n[…]\n 寻优页):\n- ParamGridPicker:勾选 1-2 个寻优参数,逗号分隔填取值列表\n- OptimizeResultTable:网格点排名表(按收益降序,最优高亮)\n- OptimizeHeatmap:2 参数热力图(ECharts heatmap,绿→红映射收益)\n- 最优点「查看」按钮跳转单标的页用该参数回测\n\n测试:821 passed(+10 寻优器单测 + 3 寻优路由测试)",
"is_bot": false,
"headline": "feat(backtest): 参数网格寻优(optimizer + 前端寻优页)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-02T19:55:35Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "6aab7a82b5bfe05b6e1ba7d6fc7a8e632329e5f4",
"body": "独立前端工程(web-ui/),Vue3 + Vite + TypeScript + Pinia + ECharts。\n通过 vite proxy 对接后端 /api,开发期无跨域。\n\n单标的回测(/):\n- StrategyPicker: 策略下拉 + 按 schema 动态渲染参数表单\n- SymbolPicker: 选标的 + 日期范围取行情(默认3年,结束日=今天)\n- KlineChart: K线主图 + 买卖点 markPoint(按 datetime 对齐)\n- EquityChart: 净值曲线 + 回撤双轴图\n- MetricTable: 19 项绩效指标(收益/风险/交易分\n[…]\nfetchBars 归一化 date/datetime 列名(日线返回 date)\n- K线买卖点按完整 datetime 对齐(分钟线不再折叠)\n- count 上限对齐后端 800\n- ECharts 注册内联 dark 主题\n- 参数表单空输入不打断编辑\n- 网络错误友好提示\n\n技术栈:Vue 3.5 / Vite 8 / TS 6 / Pinia / ECharts(按需引入 725KB)",
"is_bot": false,
"headline": "feat(web-ui): Vue3 + ECharts 回测前端(单标的 + 组合回测)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-02T19:35:20Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "38731114b6b016c47128298c6e98c430f61f93cf",
"body": "后端回测系统完整实现:\n\n策略注册表(backtest/strategies/):\n- Param schema 声明机制,支持动态表单渲染\n- 5 个内置策略:MA交叉/MACD/布林/RSI/KDJ\n- 参数校验(含 NaN/Inf 拦截、范围检查、类型强制转换)\n\nREST API(web/routers/backtest.py):\n- GET /backtest/strategies 策略枚举 + 参数 schema\n- POST /backtest/run 同步回测(内联 OHLCV)\n- POST /backtest/run/async 后台任务回测(含 s\n[…]\nInf/giant-int(防 DoS)\n- ohlcv max_length=2000(防内存耗尽)\n- LRU 淘汰跳过 running 任务(修复结果丢失竞态)\n- get_runner double-checked locking(修复单例竞态)\n- shutdown 接入 lifespan(修复资源泄漏)\n\n测试:808 passed(含 39 回测路由 + 8 组合引擎 + 安全回归)",
"is_bot": false,
"headline": "feat(backtest): 回测 REST API + 策略注册表 + 组合回测引擎",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-02T19:34:34Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "803f2fc3adfd7f2831cb5dba5259699fcd4cbc8b",
"body": "CVE: pytest <9.0.3 存在临时目录处理安全问题(中等严重程度)。\n本地 Python 3.11 venv + pytest 9.0.3 全量测试通过:761 passed, 2 skipped。\npytest 9.0.3 要求 Python>=3.9,满足 CI 矩阵 3.10/3.12/3.13。",
"is_bot": false,
"headline": "fix(deps): bump pytest 8.3.5 -> 9.0.3 修复 tmpdir 安全漏洞 (Dependabot #1)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-02T17:33:11Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "1e7766754e67f56bdef9ea2fadced66270a0cd23",
"body": "修复 GitHub Actions 中 ruff format --check 的格式化失败。",
"is_bot": false,
"headline": "style: ruff format tests/unit/test_a_share_extensions.py",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-02T17:30:40Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "133c208510bfd1b254e18dbc66a5db56b8592c80",
"body": "通达信统计指数 880005/880006 的计数类字段返回真实家数的 1/10,\nget_market_stat 未做缩放还原,导致 up/down/total/limit_up 等系统性偏小 10 倍。\n对 6 个计数字段统一 round(field * 10),成交额/量/市值不动。\n重写 test_get_market_stat_mapping 覆盖还原逻辑。761 单测全绿。",
"is_bot": false,
"headline": "release: v1.16.3 — 修复 market-stat 全市场涨跌统计家数偏小10倍",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-07-02T04:46:27Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "2067b80958bd622ddb84b3d8a3833b4d12e9be45",
"body": "requirements-dev.txt 锁定的 scipy==1.17.0 与 pyproject 声明的\nrequires-python=>=3.10 冲突(scipy 1.16+ 起要求 Python>=3.11),\n导致 CI 矩阵里 3.10 的 job(ubuntu + windows)pip install 失败。\n\n修复:\n- requirements-dev.txt: scipy 1.17.0 → 1.15.3(支持 >=3.10 的最新 scipy)\n- pyproject.toml [dev]/[science]: scipy>=1.10 → >=1.10,<1.16\n (CI 第一步 pip install -e .[dev] 在 3.10 上也会拉到 1.17,加同上界)\n\n附注:仅影响开发/CI 环境,运行时用户(pip install easy-tdx)不受影响。\n本地 761 单测 + ruff + coverage(62%) 全过。requirements-dev.txt\n头部加注释说明 3.10 兼容约束,防日后再次踩坑。",
"is_bot": false,
"headline": "fix(ci): scipy 1.17 要求 Python>=3.11,CI 跑 3.10 装不上",
"author_name": "GitHub",
"author_login": "actions-user",
"committed_at": "2026-07-01T19:53:25Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "155328df8b9848e568061496a50c5f7300aff2fd",
"body": "经三轮代码审计后的综合质量加固版本,覆盖协议核心层、数据正确性、\n错误处理、测试真实度与可维护性。761 单测全绿(+58),ruff/mypy 全过。\n\n主要修复:\n- 离线 .day 写入原子化(fsync + _repair_tail + 读取校验,CQS 守住)\n- 回测止损前视偏差(延迟下一根开盘 + 跳空保护)\n- VWAP 权重索引 / bar_time fail-fast / 绩效除零保护\n- 闭包绑定 / 路径穿越 / naive datetime 跨时区 / ruff UP038\n\n重构:\n- 抽 AsyncHeartbeatMixin 收敛 4 处心跳副本(12→1)\n- 统一 _RETRY_DELAYS 退避序列 / scanner 失败可观测性\n\n新增 5 个测试文件 + 公共 API 类型契约,CI 加 Windows 矩阵 +\ntrusted publishing 签名 + 锁文件。\n\n详见 CHANGELOG.md",
"is_bot": false,
"headline": "release: v1.16.2 — 三轮审计质量加固(B6.9→A7.9)",
"author_name": "GitHub",
"author_login": "actions-user",
"committed_at": "2026-07-01T19:37:37Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "bc83ffa4ac6086c47c7aac3e85fbff4c445124b1",
"body": null,
"is_bot": false,
"headline": "fix(tick_charts): 多日分时图 minutes>=1440 报 ValueError (Issue #10)",
"author_name": "GitHub",
"author_login": "actions-user",
"committed_at": "2026-07-01T08:22:57Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "615994ad7238bbb95dbad7fa4e5574cde8a251a2",
"body": "通达信协议用bar开始时间打时间戳(5min线上午最后一根标11:25、下午第一根标13:00;午休11:30-13:00无bar),而Tushare/同花顺/聚宽用bar结束时间(标11:30/13:05)。新增bar_time参数让用户一键切换,避免自行+5分钟偏移。\n\n- 全部3条K线路径覆盖:A股get_security_bars/get_index_bars、扩展行情get_instrument_bars、MAC get_stock_kline(含同步+异步、get_stock_kline_with_indicators)\n\n- CLI kline新增--bar-time {star\n[…]\n界\n\n- 协议解码层零改动,偏移作为纯展示语义在client层后处理,单一工具函数_apply_bar_time_align_df/_apply_bar_time_align_bars复用于全部路径\n\n- 新增27个单元测试(test_codec_datetime.py偏移逻辑 + test_kline_bar_time.py三路径覆盖),全量700单测通过\n\n- bump 版本号至 1.16.0",
"is_bot": false,
"headline": "feat(kline): 分钟级K线时间戳可选bar_time对齐Tushare (Discussion #7)",
"author_name": "GitHub",
"author_login": "actions-user",
"committed_at": "2026-06-30T07:02:27Z",
"body_truncated": true,
"is_coding_agent": false
},
{
"oid": "db83e7505d3b04e05974ba8ae5c8cec60b29ab80",
"body": null,
"is_bot": false,
"headline": "fix(security_quotes): 修复 ETF/指数实时行情价格被放大10倍 (Issue #8)",
"author_name": "GitHub",
"author_login": "actions-user",
"committed_at": "2026-06-29T14:15:28Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "3945800728ee63ec9ed0e67569d4a487f585a77a",
"body": "修复体验问题:此前传板块名仍需用户关心 --offset/--length,太笨拙。\n现在传板块名即自动按目录 length 分块循环读取整个板块(单次上限 30720 字节,\n大板块如「公司大事」77万字节也能一次读全),--offset/--length 仅传文件名时生效。\n\n- _resolve_filename 返回板块 length,_run_content 分块循环读取完整内容\n- 修复分块 offset 推进 bug:原按解码字符串 GBK 重编码计字节,遇 U+FFFD 崩溃;\n 改为按请求字节数推进(服务器按字节偏移工作)\n- 修复多服务器目录版本不一致:传板块名未命中时自动重试多个服务器(最多4次)\n- bump 版本号至 1.15.3",
"is_bot": false,
"headline": "feat(cli): company-info 传板块名自动读完整正文(分块循环 + 多服务器重试)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-06-26T20:37:44Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "297a479928012a04c8f6622ea33e53ed66a5a189",
"body": "修正上一个 commit:CLAUDE.md 应从版本控制移除(本地配置,不递交 GitHub),\n但被误重新加入。本次 git rm --cached 真正移除跟踪,本地文件保留继续使用。",
"is_bot": false,
"headline": "chore: 从 git 跟踪移除 CLAUDE.md(本地文件保留)",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-06-26T20:23:46Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "202415dd19eae87b31ee21f0589666423ccd13f3",
"body": "CLAUDE.md 含本地开发指引(venv 使用要求等),属于本地配置。\ngit rm --cached 移除跟踪(本地文件保留),加入 .gitignore 防止误加回。",
"is_bot": false,
"headline": "chore: CLAUDE.md 改为本地文件,不再递交到 GitHub",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-06-26T20:22:50Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "781ee6c48af5ede1ba78f670d00669a2d2fa1782",
"body": "合并 company-info(列目录)与 company-info-content(读正文)为一个命令,\n板块名改为可选位置参数:无参数列目录,有参数读正文。消除两个相似命令名\n导致的混淆(用户曾误用 company-info SH 601088 \"公司概况\" 报错)。\n\n- company-info:name_or_filename 可选,无则列 F10 板块目录,有则读正文\n- company-info-content:保留为隐藏别名(hidden=True),向后兼容 v1.15.1\n- 提取 _run_category/_run_content 模块级函数复用逻辑\n- 新增 examples/06_finance/{README.md,company_cli.sh,company_web_api.py}\n 覆盖 CLI / Python API / Web API 三种调用方式,含 16 个 F10 板块完整列表\n- 更新 company_info.py 板块名为实测的 16 板块\n- bump 版本号至 1.15.2",
"is_bot": false,
"headline": "feat(cli): company-info 命令合并 + examples/06_finance 文档完善",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-06-26T20:22:43Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "3df1ea607fa2f51a3ff295d014647d1201de6ca6",
"body": "同步版本号至 1.15.1:pyproject.toml + CHANGELOG。",
"is_bot": false,
"headline": "chore(release): v1.15.1",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-06-26T20:05:57Z",
"body_truncated": false,
"is_coding_agent": false
},
{
"oid": "29b6ca33aa27ac3fe42db189ef0ff0a778199e9b",
"body": "把 TdxClient 上已封装但未暴露给 CLI 的三个 F10/财务方法做成命令,\n数据源走通达信原生协议(与 Web 层 /finance /company/* 同源),\n覆盖 f10(新浪三表)之外的 F10 全文板块。\n\n- finance-info: 最新财务快照(37 字段单期指标),与 f10 多期三表互补\n- company-info: F10 板块目录(最新提示/公司概况/财务分析/... 等 16 板块)\n- company-info-content: 读 F10 正文,支持板块名(自动解析)或文件名\n --offset 语义随入参而定:板块名=板块内相对偏移,文件名=绝对偏移\n- conn.py: 新增 get_tdx_client() 上下文管理器\n\n补 README 命令表与 CHANGELOG。",
"is_bot": false,
"headline": "feat(cli): 新增 finance-info / company-info / company-info-content 命令",
"author_name": "Justin Gu",
"author_login": "handsomejustin",
"committed_at": "2026-06-26T20:02:09Z",
"body_truncated": false,
"is_coding_agent": false
}
],
"releases_count": 65,
"commits_last_year": 289,
"latest_release_at": "2026-07-13T08:39:12Z",
"latest_release_tag": "v1.20.4",
"releases_from_tags": false,
"days_since_last_push": 17,
"active_weeks_last_year": 10,
"days_since_latest_release": 17,
"mean_days_between_releases": 0.6
},
"community": {
"has_readme": true,
"has_license": true,
"has_description": true,
"has_contributing": false,
"health_percentage": 42,
"has_issue_template": false,
"has_code_of_conduct": false,
"has_pull_request_template": false
},
"ecosystem": {
"packages": [
{
"name": "easy-tdx",
"exists": true,
"license": null,
"keywords": [],
"ecosystem": "pypi",
"matches_repo": null,
"registry_url": "https://pypi.org/project/easy-tdx/",
"is_deprecated": false,
"latest_version": "1.20.4",
"repository_url": null,
"versions_count": 82,
"total_downloads": null,
"dependents_count": null,
"deprecation_note": null,
"maintainers_count": null,
"monthly_downloads": 15804,
"first_published_at": "2026-05-22T04:34:46.425850Z",
"latest_published_at": "2026-07-13T08:37:25.245278Z",
"latest_version_yanked": null,
"days_since_latest_publish": 17
}
]
},
"popularity": {
"forks": 194,
"stars": 636,
"watchers": 13,
"fork_history": {
"days": [
{
"date": "2026-05-23",
"count": 1
},
{
"date": "2026-05-24",
"count": 2
},
{
"date": "2026-05-27",
"count": 1
},
{
"date": "2026-05-28",
"count": 3
},
{
"date": "2026-05-29",
"count": 4
},
{
"date": "2026-05-30",
"count": 2
},
{
"date": "2026-06-01",
"count": 2
},
{
"date": "2026-06-02",
"count": 2
},
{
"date": "2026-06-03",
"count": 3
},
{
"date": "2026-06-04",
"count": 2
},
{
"date": "2026-06-05",
"count": 1
},
{
"date": "2026-06-07",
"count": 1
},
{
"date": "2026-06-08",
"count": 2
},
{
"date": "2026-06-09",
"count": 7
},
{
"date": "2026-06-10",
"count": 5
},
{
"date": "2026-06-11",
"count": 4
},
{
"date": "2026-06-12",
"count": 3
},
{
"date": "2026-06-13",
"count": 2
},
{
"date": "2026-06-14",
"count": 3
},
{
"date": "2026-06-15",
"count": 2
},
{
"date": "2026-06-16",
"count": 3
},
{
"date": "2026-06-17",
"count": 3
},
{
"date": "2026-06-18",
"count": 3
},
{
"date": "2026-06-19",
"count": 1
},
{
"date": "2026-06-20",
"count": 5
},
{
"date": "2026-06-21",
"count": 1
},
{
"date": "2026-06-22",
"count": 2
},
{
"date": "2026-06-23",
"count": 2
},
{
"date": "2026-06-24",
"count": 2
},
{
"date": "2026-06-25",
"count": 6
},
{
"date": "2026-06-26",
"count": 4
},
{
"date": "2026-06-27",
"count": 3
},
{
"date": "2026-06-29",
"count": 1
},
{
"date": "2026-06-30",
"count": 7
},
{
"date": "2026-07-01",
"count": 2
},
{
"date": "2026-07-03",
"count": 4
},
{
"date": "2026-07-04",
"count": 4
},
{
"date": "2026-07-05",
"count": 5
},
{
"date": "2026-07-06",
"count": 4
},
{
"date": "2026-07-07",
"count": 7
},
{
"date": "2026-07-08",
"count": 8
},
{
"date": "2026-07-09",
"count": 7
},
{
"date": "2026-07-10",
"count": 3
},
{
"date": "2026-07-11",
"count": 4
},
{
"date": "2026-07-12",
"count": 4
},
{
"date": "2026-07-13",
"count": 8
},
{
"date": "2026-07-14",
"count": 4
},
{
"date": "2026-07-15",
"count": 4
},
{
"date": "2026-07-16",
"count": 3
},
{
"date": "2026-07-17",
"count": 1
},
{
"date": "2026-07-18",
"count": 2
},
{
"date": "2026-07-20",
"count": 5
},
{
"date": "2026-07-21",
"count": 1
},
{
"date": "2026-07-23",
"count": 1
},
{
"date": "2026-07-24",
"count": 3
},
{
"date": "2026-07-25",
"count": 1
},
{
"date": "2026-07-26",
"count": 2
},
{
"date": "2026-07-27",
"count": 4
},
{
"date": "2026-07-28",
"count": 1
},
{
"date": "2026-07-29",
"count": 5
},
{
"date": "2026-07-30",
"count": 2
}
],
"complete": true,
"collected": 194,
"total_forks": 194
},
"star_history": null,
"open_issues_and_prs": 5
},
"ai_readiness": {
"has_nix": false,
"example_dirs": [
"examples"
],
"has_llms_txt": false,
"has_dockerfile": false,
"has_mcp_signal": false,
"bootstrap_files": [],
"api_schema_files": [],
"has_devcontainer": false,
"typecheck_configs": [
"src/easy_tdx/py.typed",
"web-ui/tsconfig.json"
],
"toolchain_manifests": [],
"largest_source_bytes": 74718,
"source_files_sampled": 389,
"oversized_source_files": 2,
"agent_instruction_files": [],
"agent_instruction_max_bytes": null
},
"dependencies": {
"manifests": [
"docs/requirements.txt",
"pyproject.toml",
"requirements-dev.txt",
"web-ui/package.json"
],
"advisories": {
"error": null,
"scope": "published_package",
"source": "osv",
"findings": [],
"collected": true,
"malicious": [],
"truncated": false,
"by_severity": {},
"advisory_count": 0,
"affected_count": 0,
"assessed_count": 7,
"malicious_count": 0,
"assessed_package": "pypi:easy-tdx@1.20.4",
"unassessed_count": 0,
"direct_affected_count": 0
},
"ecosystems": [
"npm",
"pypi"
],
"dependencies": [
{
"name": "pandas",
"manifest": "pyproject.toml",
"ecosystem": "pypi",
"version_constraint": ">=2.0,<3"
},
{
"name": "tzdata",
"manifest": "pyproject.toml",
"ecosystem": "pypi",
"version_constraint": ">=2024.1"
},
{
"name": "click",
"manifest": "pyproject.toml",
"ecosystem": "pypi",
"version_constraint": ">=8.0,<9"
},
{
"name": "echarts",
"manifest": "web-ui/package.json",
"ecosystem": "npm",
"version_constraint": "^6.1.0"
},
{
"name": "pinia",
"manifest": "web-ui/package.json",
"ecosystem": "npm",
"version_constraint": "^3.0.4"
},
{
"name": "vue",
"manifest": "web-ui/package.json",
"ecosystem": "npm",
"version_constraint": "^3.5.39"
},
{
"name": "vue-router",
"manifest": "web-ui/package.json",
"ecosystem": "npm",
"version_constraint": "^4.6.4"
}
],
"all_dependencies": {
"error": null,
"source": "github-sbom",
"packages": [
{
"name": "echarts",
"direct": true,
"version": "6.1.0",
"ecosystem": "npm"
},
{
"name": "pinia",
"direct": true,
"version": "3.0.4",
"ecosystem": "npm"
},
{
"name": "vue",
"direct": true,
"version": "3.5.39",
"ecosystem": "npm"
},
{
"name": "vue-router",
"direct": true,
"version": "4.6.4",
"ecosystem": "npm"
},
{
"name": "click",
"direct": true,
"version": null,
"ecosystem": "pypi"
},
{
"name": "pandas",
"direct": true,
"version": null,
"ecosystem": "pypi"
},
{
"name": "tzdata",
"direct": true,
"version": null,
"ecosystem": "pypi"
},
{
"name": "@babel/helper-string-parser",
"direct": false,
"version": "7.29.7",
"ecosystem": "npm"
},
{
"name": "@babel/helper-validator-identifier",
"direct": false,
"version": "7.29.7",
"ecosystem": "npm"
},
{
"name": "@babel/parser",
"direct": false,
"version": "7.29.7",
"ecosystem": "npm"
},
{
"name": "@babel/types",
"direct": false,
"version": "7.29.7",
"ecosystem": "npm"
},
{
"name": "@emnapi/core",
"direct": false,
"version": "1.11.1",
"ecosystem": "npm"
},
{
"name": "@emnapi/runtime",
"direct": false,
"version": "1.11.1",
"ecosystem": "npm"
},
{
"name": "@emnapi/wasi-threads",
"direct": false,
"version": "1.2.2",
"ecosystem": "npm"
},
{
"name": "@jridgewell/sourcemap-codec",
"direct": false,
"version": "1.5.5",
"ecosystem": "npm"
},
{
"name": "@napi-rs/wasm-runtime",
"direct": false,
"version": "1.1.6",
"ecosystem": "npm"
},
{
"name": "@oxc-project/types",
"direct": false,
"version": "0.138.0",
"ecosystem": "npm"
},
{
"name": "@rolldown/binding-android-arm64",
"direct": false,
"version": "1.1.4",
"ecosystem": "npm"
},
{
"name": "@rolldown/binding-darwin-arm64",
"direct": false,
"version": "1.1.4",
"ecosystem": "npm"
},
{
"name": "@rolldown/binding-darwin-x64",
"direct": false,
"version": "1.1.4",
"ecosystem": "npm"
},
{
"name": "@rolldown/binding-freebsd-x64",
"direct": false,
"version": "1.1.4",
"ecosystem": "npm"
},
{
"name": "@rolldown/binding-linux-arm-gnueabihf",
"direct": false,
"version": "1.1.4",
"ecosystem": "npm"
},
{
"name": "@rolldown/binding-linux-arm64-gnu",
"direct": false,
"version": "1.1.4",
"ecosystem": "npm"
},
{
"name": "@rolldown/binding-linux-arm64-musl",
"direct": false,
"version": "1.1.4",
"ecosystem": "npm"
},
{
"name": "@rolldown/binding-linux-ppc64-gnu",
"direct": false,
"version": "1.1.4",
"ecosystem": "npm"
},
{
"name": "@rolldown/binding-linux-s390x-gnu",
"direct": false,
"version": "1.1.4",
"ecosystem": "npm"
},
{
"name": "@rolldown/binding-linux-x64-gnu",
"direct": false,
"version": "1.1.4",
"ecosystem": "npm"
},
{
"name": "@rolldown/binding-linux-x64-musl",
"direct": false,
"version": "1.1.4",
"ecosystem": "npm"
},
{
"name": "@rolldown/binding-openharmony-arm64",
"direct": false,
"version": "1.1.4",
"ecosystem": "npm"
},
{
"name": "@rolldown/binding-wasm32-wasi",
"direct": false,
"version": "1.1.4",
"ecosystem": "npm"
},
{
"name": "@rolldown/binding-win32-arm64-msvc",
"direct": false,
"version": "1.1.4",
"ecosystem": "npm"
},
{
"name": "@rolldown/binding-win32-x64-msvc",
"direct": false,
"version": "1.1.4",
"ecosystem": "npm"
},
{
"name": "@rolldown/pluginutils",
"direct": false,
"version": "1.0.1",
"ecosystem": "npm"
},
{
"name": "@tybys/wasm-util",
"direct": false,
"version": "0.10.3",
"ecosystem": "npm"
},
{
"name": "@types/node",
"direct": false,
"version": "24.13.2",
"ecosystem": "npm"
},
{
"name": "@vitejs/plugin-vue",
"direct": false,
"version": "6.0.7",
"ecosystem": "npm"
},
{
"name": "@volar/language-core",
"direct": false,
"version": "2.4.28",
"ecosystem": "npm"
},
{
"name": "@volar/source-map",
"direct": false,
"version": "2.4.28",
"ecosystem": "npm"
},
{
"name": "@volar/typescript",
"direct": false,
"version": "2.4.28",
"ecosystem": "npm"
},
{
"name": "@vue/compiler-core",
"direct": false,
"version": "3.5.39",
"ecosystem": "npm"
},
{
"name": "@vue/compiler-dom",
"direct": false,
"version": "3.5.39",
"ecosystem": "npm"
},
{
"name": "@vue/compiler-sfc",
"direct": false,
"version": "3.5.39",
"ecosystem": "npm"
},
{
"name": "@vue/compiler-ssr",
"direct": false,
"version": "3.5.39",
"ecosystem": "npm"
},
{
"name": "@vue/devtools-api",
"direct": false,
"version": "6.6.4",
"ecosystem": "npm"
},
{
"name": "@vue/devtools-api",
"direct": false,
"version": "7.7.10",
"ecosystem": "npm"
},
{
"name": "@vue/devtools-kit",
"direct": false,
"version": "7.7.10",
"ecosystem": "npm"
},
{
"name": "@vue/devtools-shared",
"direct": false,
"version": "7.7.10",
"ecosystem": "npm"
},
{
"name": "@vue/language-core",
"direct": false,
"version": "3.3.6",
"ecosystem": "npm"
},
{
"name": "@vue/reactivity",
"direct": false,
"version": "3.5.39",
"ecosystem": "npm"
},
{
"name": "@vue/runtime-core",
"direct": false,
"version": "3.5.39",
"ecosystem": "npm"
},
{
"name": "@vue/runtime-dom",
"direct": false,
"version": "3.5.39",
"ecosystem": "npm"
},
{
"name": "@vue/server-renderer",
"direct": false,
"version": "3.5.39",
"ecosystem": "npm"
},
{
"name": "@vue/shared",
"direct": false,
"version": "3.5.39",
"ecosystem": "npm"
},
{
"name": "@vue/tsconfig",
"direct": false,
"version": "0.9.1",
"ecosystem": "npm"
},
{
"name": "alien-signals",
"direct": false,
"version": "3.2.1",
"ecosystem": "npm"
},
{
"name": "birpc",
"direct": false,
"version": "2.9.0",
"ecosystem": "npm"
},
{
"name": "copy-anything",
"direct": false,
"version": "4.0.5",
"ecosystem": "npm"
},
{
"name": "csstype",
"direct": false,
"version": "3.2.3",
"ecosystem": "npm"
},
{
"name": "detect-libc",
"direct": false,
"version": "2.1.2",
"ecosystem": "npm"
},
{
"name": "entities",
"direct": false,
"version": "7.0.1",
"ecosystem": "npm"
},
{
"name": "estree-walker",
"direct": false,
"version": "2.0.2",
"ecosystem": "npm"
},
{
"name": "fdir",
"direct": false,
"version": "6.5.0",
"ecosystem": "npm"
},
{
"name": "fsevents",
"direct": false,
"version": "2.3.3",
"ecosystem": "npm"
},
{
"name": "hookable",
"direct": false,
"version": "5.5.3",
"ecosystem": "npm"
},
{
"name": "is-what",
"direct": false,
"version": "5.5.0",
"ecosystem": "npm"
},
{
"name": "lightningcss",
"direct": false,
"version": "1.32.0",
"ecosystem": "npm"
},
{
"name": "lightningcss-android-arm64",
"direct": false,
"version": "1.32.0",
"ecosystem": "npm"
},
{
"name": "lightningcss-darwin-arm64",
"direct": false,
"version": "1.32.0",
"ecosystem": "npm"
},
{
"name": "lightningcss-darwin-x64",
"direct": false,
"version": "1.32.0",
"ecosystem": "npm"
},
{
"name": "lightningcss-freebsd-x64",
"direct": false,
"version": "1.32.0",
"ecosystem": "npm"
},
{
"name": "lightningcss-linux-arm-gnueabihf",
"direct": false,
"version": "1.32.0",
"ecosystem": "npm"
},
{
"name": "lightningcss-linux-arm64-gnu",
"direct": false,
"version": "1.32.0",
"ecosystem": "npm"
},
{
"name": "lightningcss-linux-arm64-musl",
"direct": false,
"version": "1.32.0",
"ecosystem": "npm"
},
{
"name": "lightningcss-linux-x64-gnu",
"direct": false,
"version": "1.32.0",
"ecosystem": "npm"
},
{
"name": "lightningcss-linux-x64-musl",
"direct": false,
"version": "1.32.0",
"ecosystem": "npm"
},
{
"name": "lightningcss-win32-arm64-msvc",
"direct": false,
"version": "1.32.0",
"ecosystem": "npm"
},
{
"name": "lightningcss-win32-x64-msvc",
"direct": false,
"version": "1.32.0",
"ecosystem": "npm"
},
{
"name": "magic-string",
"direct": false,
"version": "0.30.21",
"ecosystem": "npm"
},
{
"name": "mitt",
"direct": false,
"version": "3.0.1",
"ecosystem": "npm"
},
{
"name": "muggle-string",
"direct": false,
"version": "0.4.1",
"ecosystem": "npm"
},
{
"name": "nanoid",
"direct": false,
"version": "3.3.15",
"ecosystem": "npm"
},
{
"name": "path-browserify",
"direct": false,
"version": "1.0.1",
"ecosystem": "npm"
},
{
"name": "perfect-debounce",
"direct": false,
"version": "1.0.0",
"ecosystem": "npm"
},
{
"name": "picocolors",
"direct": false,
"version": "1.1.1",
"ecosystem": "npm"
},
{
"name": "picomatch",
"direct": false,
"version": "4.0.5",
"ecosystem": "npm"
},
{
"name": "postcss",
"direct": false,
"version": "8.5.16",
"ecosystem": "npm"
},
{
"name": "rfdc",
"direct": false,
"version": "1.4.1",
"ecosystem": "npm"
},
{
"name": "rolldown",
"direct": false,
"version": "1.1.4",
"ecosystem": "npm"
},
{
"name": "source-map-js",
"direct": false,
"version": "1.2.1",
"ecosystem": "npm"
},
{
"name": "speakingurl",
"direct": false,
"version": "14.0.1",
"ecosystem": "npm"
},
{
"name": "superjson",
"direct": false,
"version": "2.2.6",
"ecosystem": "npm"
},
{
"name": "tinyglobby",
"direct": false,
"version": "0.2.17",
"ecosystem": "npm"
},
{
"name": "tslib",
"direct": false,
"version": "2.3.0",
"ecosystem": "npm"
},
{
"name": "tslib",
"direct": false,
"version": "2.8.1",
"ecosystem": "npm"
},
{
"name": "typescript",
"direct": false,
"version": "6.0.3",
"ecosystem": "npm"
},
{
"name": "undici-types",
"direct": false,
"version": "7.18.2",
"ecosystem": "npm"
},
{
"name": "vite",
"direct": false,
"version": "8.1.3",
"ecosystem": "npm"
},
{
"name": "vscode-uri",
"direct": false,
"version": "3.1.0",
"ecosystem": "npm"
},
{
"name": "vue-tsc",
"direct": false,
"version": "3.3.6",
"ecosystem": "npm"
},
{
"name": "zrender",
"direct": false,
"version": "6.1.0",
"ecosystem": "npm"
},
{
"name": "coverage",
"direct": false,
"version": "7.14.0",
"ecosystem": "pypi"
},
{
"name": "fastapi",
"direct": false,
"version": null,
"ecosystem": "pypi"
},
{
"name": "fastapi",
"direct": false,
"version": "0.115.11",
"ecosystem": "pypi"
},
{
"name": "httpx",
"direct": false,
"version": null,
"ecosystem": "pypi"
},
{
"name": "mypy",
"direct": false,
"version": null,
"ecosystem": "pypi"
},
{
"name": "mypy",
"direct": false,
"version": "2.1.0",
"ecosystem": "pypi"
},
{
"name": "myst-parser",
"direct": false,
"version": null,
"ecosystem": "pypi"
},
{
"name": "numpy",
"direct": false,
"version": "2.2.3",
"ecosystem": "pypi"
},
{
"name": "pillow",
"direct": false,
"version": null,
"ecosystem": "pypi"
},
{
"name": "pystray",
"direct": false,
"version": null,
"ecosystem": "pypi"
},
{
"name": "pytest",
"direct": false,
"version": null,
"ecosystem": "pypi"
},
{
"name": "pytest",
"direct": false,
"version": "9.0.3",
"ecosystem": "pypi"
},
{
"name": "pytest-asyncio",
"direct": false,
"version": null,
"ecosystem": "pypi"
},
{
"name": "pytest-cov",
"direct": false,
"version": "7.1.0",
"ecosystem": "pypi"
},
{
"name": "ruff",
"direct": false,
"version": null,
"ecosystem": "pypi"
},
{
"name": "ruff",
"direct": false,
"version": "0.11.11",
"ecosystem": "pypi"
},
{
"name": "scipy",
"direct": false,
"version": null,
"ecosystem": "pypi"
},
{
"name": "scipy",
"direct": false,
"version": "1.15.3",
"ecosystem": "pypi"
},
{
"name": "sphinx",
"direct": false,
"version": null,
"ecosystem": "pypi"
},
{
"name": "sphinx-autobuild",
"direct": false,
"version": null,
"ecosystem": "pypi"
},
{
"name": "sphinx-rtd-theme",
"direct": false,
"version": null,
"ecosystem": "pypi"
},
{
"name": "uvicorn",
"direct": false,
"version": null,
"ecosystem": "pypi"
},
{
"name": "uvicorn",
"direct": false,
"version": "0.34.0",
"ecosystem": "pypi"
}
],
"collected": true,
"truncated": false,
"total_count": 123,
"direct_count": 7,
"indirect_count": 116
}
},
"maintainership": {
"issues": {
"open_prs": 0,
"merged_prs": 9,
"open_issues": 5,
"closed_ratio": 0.808,
"closed_issues": 21,
"closed_unmerged_prs": 0
},
"bus_factor": 1,
"bot_contributors": 0,
"top_contributors": [
{
"type": "User",
"login": "handsomejustin",
"commits": 160,
"avatar_url": "https://avatars.githubusercontent.com/u/7761402?v=4"
},
{
"type": "User",
"login": "actions-user",
"commits": 105,
"avatar_url": "https://avatars.githubusercontent.com/u/65916846?v=4"
},
{
"type": "User",
"login": "minionszyw",
"commits": 10,
"avatar_url": "https://avatars.githubusercontent.com/u/174980299?v=4"
},
{
"type": "User",
"login": "shom",
"commits": 1,
"avatar_url": "https://avatars.githubusercontent.com/u/144610?v=4"
}
],
"contributors_sampled": 4,
"top_contributor_share": 0.58
},
"quality_signals": {
"has_ci": true,
"has_tests": true,
"ci_workflows": [
"ci.yml",
"publish.yml",
"release.yml"
],
"has_docs_dir": true,
"linter_configs": [],
"has_editorconfig": false,
"has_linter_config": false,
"has_precommit_config": false
},
"security_signals": {
"lockfiles": [
"package-lock.json"
],
"scorecard": {
"checks": [
{
"name": "Binary-Artifacts",
"score": 10,
"reason": "no binaries found in the repo",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#binary-artifacts"
},
{
"name": "Branch-Protection",
"score": 0,
"reason": "branch protection not enabled on development/release branches",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#branch-protection"
},
{
"name": "CI-Tests",
"score": 5,
"reason": "4 out of 7 merged PRs checked by a CI test -- score normalized to 5",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#ci-tests"
},
{
"name": "CII-Best-Practices",
"score": 0,
"reason": "no effort to earn an OpenSSF best practices badge detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#cii-best-practices"
},
{
"name": "Code-Review",
"score": 0,
"reason": "Found 0/22 approved changesets -- score normalized to 0",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#code-review"
},
{
"name": "Contributors",
"score": 6,
"reason": "project has 2 contributing companies or organizations -- score normalized to 6",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#contributors"
},
{
"name": "Dangerous-Workflow",
"score": 10,
"reason": "no dangerous workflow patterns detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dangerous-workflow"
},
{
"name": "Dependency-Update-Tool",
"score": 0,
"reason": "no update tool detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#dependency-update-tool"
},
{
"name": "Fuzzing",
"score": 0,
"reason": "project is not fuzzed",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#fuzzing"
},
{
"name": "License",
"score": 9,
"reason": "license file detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#license"
},
{
"name": "Maintained",
"score": 0,
"reason": "project was created within the last 90 days. Please review its contents carefully",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#maintained"
},
{
"name": "Packaging",
"score": 10,
"reason": "packaging workflow detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#packaging"
},
{
"name": "Pinned-Dependencies",
"score": 3,
"reason": "dependency not pinned by hash detected -- score normalized to 3",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#pinned-dependencies"
},
{
"name": "SAST",
"score": 0,
"reason": "SAST tool is not run on all commits -- score normalized to 0",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#sast"
},
{
"name": "Security-Policy",
"score": 0,
"reason": "security policy file not detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#security-policy"
},
{
"name": "Signed-Releases",
"score": 0,
"reason": "Project has not signed or included provenance with any releases.",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#signed-releases"
},
{
"name": "Token-Permissions",
"score": 0,
"reason": "detected GitHub workflow tokens with excessive permissions",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#token-permissions"
},
{
"name": "Vulnerabilities",
"score": 2,
"reason": "8 existing vulnerabilities detected",
"documentation_url": "https://github.com/ossf/scorecard/blob/c395761df6afe1a69e476bc60a013a94bcbc153f/docs/checks.md#vulnerabilities"
}
],
"commit": "3b7ce97f2a6942cf9f39e25ee29c4e113bcfc69f",
"ran_at": "2026-07-30T17:18:23Z",
"aggregate_score": 2.9,
"scorecard_version": "v5.5.0"
},
"has_codeql_workflow": false,
"has_security_policy": false,
"has_dependabot_config": false
},
"contribution_flow": {
"collected": true,
"ci_last_run_at": "2026-07-13T08:37:31Z",
"oldest_open_prs": [],
"last_merged_pr_at": "2026-07-13T08:21:55Z",
"ci_last_conclusion": "SUCCESS",
"oldest_open_issues": [
{
"number": 39,
"created_at": "2026-07-17T11:32:16Z",
"last_comment_at": "2026-07-18T19:06:24Z",
"last_comment_author": "handsomejustin"
},
{
"number": 40,
"created_at": "2026-07-19T11:11:38Z",
"last_comment_at": null,
"last_comment_author": null
},
{
"number": 41,
"created_at": "2026-07-23T06:23:06Z",
"last_comment_at": null,
"last_comment_author": null
},
{
"number": 42,
"created_at": "2026-07-24T06:18:25Z",
"last_comment_at": null,
"last_comment_author": null
},
{
"number": 43,
"created_at": "2026-07-28T02:35:41Z",
"last_comment_at": null,
"last_comment_author": null
}
]
}
},
"config": {
"disabled_metrics": [],
"disabled_categories": [],
"disabled_components": {}
},
"source": {
"url": "https://github.com/handsomejustin/easy_tdx",
"host": "github.com",
"name": "easy_tdx",
"owner": "handsomejustin"
},
"metrics": {
"overall": {
"key": "overall",
"band": "moderate",
"name": "Overall health",
"note": null,
"notes": [],
"value": 62,
"inputs": {
"security": 43,
"vitality": 68,
"community": 61,
"governance": 62,
"engineering": 69
},
"components": []
},
"categories": [
{
"key": "vitality",
"band": "moderate",
"name": "Vitality",
"value": 68,
"weight": 0.22,
"metrics": [
{
"key": "development_activity",
"band": "moderate",
"name": "Development activity",
"note": null,
"notes": [],
"value": 54,
"inputs": {
"commits_last_year": 289,
"human_commit_share": 1,
"days_since_last_push": 17,
"active_weeks_last_year": 10
},
"components": [
{
"key": "push_recency",
"name": "Push recency",
"detail": "last push 17 days ago",
"points": 28.8,
"status": "partial",
"details": [
{
"code": "push_recency",
"params": {
"days": 17
}
}
],
"max_points": 36
},
{
"key": "commit_cadence",
"name": "Commit cadence",
"detail": "10/52 weeks with commits",
"points": 6.9,
"status": "partial",
"details": [
{
"code": "commit_cadence_weeks",
"params": {
"weeks": 10
}
}
],
"max_points": 36
},
{
"key": "commit_volume",
"name": "Commit volume",
"detail": "289 commits in the last year",
"points": 18,
"status": "met",
"details": [
{
"code": "commits_last_year",
"params": {
"count": 289
}
}
],
"max_points": 18
},
{
"key": "openssf_scorecard_maintained",
"name": "OpenSSF Scorecard: Maintained",
"detail": "project was created within the last 90 days. Please review its contents carefully",
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
}
]
},
{
"key": "release_discipline",
"band": "excellent",
"name": "Release discipline",
"note": null,
"notes": [],
"value": 90,
"inputs": {
"releases_count": 65,
"latest_release_tag": "v1.20.4",
"releases_from_tags": false,
"days_since_latest_release": 17,
"mean_days_between_releases": 0.6
},
"components": [
{
"key": "ships_releases",
"name": "Ships releases",
"detail": "65 releases published",
"points": 27,
"status": "met",
"details": [
{
"code": "releases_published",
"params": {
"count": 65
}
}
],
"max_points": 27
},
{
"key": "release_recency",
"name": "Release recency",
"detail": "latest release 17 days ago",
"points": 36,
"status": "met",
"details": [
{
"code": "release_recency",
"params": {
"days": 17
}
}
],
"max_points": 36
},
{
"key": "release_cadence",
"name": "Release cadence",
"detail": "a release every ~0.6 days",
"points": 27,
"status": "met",
"details": [
{
"code": "release_cadence",
"params": {
"gap": 0.6
}
}
],
"max_points": 27
},
{
"key": "openssf_scorecard_signed_releases",
"name": "OpenSSF Scorecard: Signed-Releases",
"detail": "Project has not signed or included provenance with any releases.",
"points": 0,
"status": "missed",
"details": [],
"max_points": 10
}
]
},
{
"key": "abandonment",
"band": "excellent",
"name": "Abandonment",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"cap": null,
"state": "unverified",
"guards": [],
"signals": [],
"red_flag": false,
"multiplier_pct": 100,
"declared_reason": null,
"unverified_reason": "repository_too_young",
"unanswered_open_prs": null,
"unanswered_open_issues": null,
"days_since_last_merged_pr": null,
"days_since_last_human_commit": null,
"days_since_last_human_commit_is_floor": false
},
"components": [
{
"key": "project_is_still_maintained",
"name": "Project is still maintained",
"detail": "maintenance record not established from the collected data",
"points": 100,
"status": "met",
"details": [
{
"code": "abandonment_unverified",
"params": {}
}
],
"max_points": 100
}
]
}
],
"description": "Is the project alive — is code being written and are releases shipping?"
},
{
"key": "community",
"band": "moderate",
"name": "Community & Adoption",
"value": 61,
"weight": 0.18,
"metrics": [
{
"key": "popularity",
"band": "good",
"name": "Popularity & adoption",
"note": null,
"notes": [],
"value": 70,
"inputs": {
"forks": 194,
"stars": 636,
"watchers": 13,
"growth_state": "unverified",
"growth_factor_pct": 100,
"growth_unverified_reason": "no_history"
},
"components": [
{
"key": "stars",
"name": "Stars",
"detail": "636 stars",
"points": 45.5,
"status": "partial",
"details": [
{
"code": "stars",
"params": {
"count": 636
}
}
],
"max_points": 60
},
{
"key": "forks",
"name": "Forks",
"detail": "194 forks",
"points": 19,
"status": "partial",
"details": [
{
"code": "forks",
"params": {
"count": 194
}
}
],
"max_points": 25
},
{
"key": "watchers",
"name": "Watchers",
"detail": "13 watchers",
"points": 6,
"status": "partial",
"details": [
{
"code": "watchers",
"params": {
"count": 13
}
}
],
"max_points": 15
}
]
},
{
"key": "community_health",
"band": "at_risk",
"name": "Community health",
"note": null,
"notes": [],
"value": 44,
"inputs": {
"has_readme": true,
"has_license": true,
"has_contributing": false,
"has_issue_template": false,
"has_code_of_conduct": false,
"has_pull_request_template": false
},
"components": [
{
"key": "readme",
"name": "README",
"detail": null,
"points": 22.5,
"status": "met",
"details": [],
"max_points": 22.5
},
{
"key": "license",
"name": "License",
"detail": "license file present, not a recognized license",
"points": 16.9,
"status": "partial",
"details": [
{
"code": "license_custom",
"params": {}
}
],
"max_points": 22.5
},
{
"key": "contributing_guide",
"name": "CONTRIBUTING guide",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 18
},
{
"key": "code_of_conduct",
"name": "Code of conduct",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 13.5
},
{
"key": "issue_template",
"name": "Issue template",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.2
},
{
"key": "pr_template",
"name": "PR template",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 6.3
}
]
},
{
"key": "ecosystem_adoption",
"band": "good",
"name": "Ecosystem adoption (downloads)",
"note": "Excluded from scoring (no data or not applicable): Registry dependents. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"registry_dependents"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 70,
"inputs": {
"packages": [
"easy-tdx"
],
"dependents": null,
"ecosystems": "pypi",
"total_downloads": null,
"monthly_downloads": 15804
},
"components": [
{
"key": "monthly_downloads",
"name": "Monthly downloads",
"detail": "15,804 downloads/month across pypi",
"points": 56,
"status": "partial",
"details": [
{
"code": "downloads_monthly",
"params": {
"count": 15804,
"ecosystems": "pypi"
}
}
],
"max_points": 80
},
{
"key": "registry_dependents",
"name": "Registry dependents",
"detail": "not reported by this ecosystem",
"points": 0,
"status": "excluded",
"details": [
{
"code": "not_reported_by_this_ecosystem",
"params": {}
}
],
"max_points": 20
}
]
}
],
"description": "Does the project have users, downloads, attention, and a welcoming setup for contributors?"
},
{
"key": "governance",
"band": "moderate",
"name": "Sustainability & Governance",
"value": 62,
"weight": 0.24,
"metrics": [
{
"key": "maintainer_resilience",
"band": "at_risk",
"name": "Maintainer resilience (bus factor)",
"note": null,
"notes": [],
"value": 30,
"inputs": {
"bus_factor": 1,
"contributors_sampled": 4,
"top_contributor_share": 0.58
},
"components": [
{
"key": "bus_factor",
"name": "Bus factor",
"detail": "1 contributor(s) cover half of all commits",
"points": 9,
"status": "partial",
"details": [
{
"code": "bus_factor",
"params": {
"count": 1
}
}
],
"max_points": 54
},
{
"key": "commit_distribution",
"name": "Commit distribution",
"detail": "top contributor authored 58% of commits",
"points": 9.5,
"status": "partial",
"details": [
{
"code": "top_contributor_share",
"params": {
"share": 58
}
}
],
"max_points": 22.5
},
{
"key": "contributor_breadth",
"name": "Contributor breadth",
"detail": "4 contributors",
"points": 5.4,
"status": "partial",
"details": [
{
"code": "contributors_sampled",
"params": {
"count": 4
}
}
],
"max_points": 13.5
},
{
"key": "openssf_scorecard_contributors",
"name": "OpenSSF Scorecard: Contributors",
"detail": "project has 2 contributing companies or organizations -- score normalized to 6",
"points": 6,
"status": "partial",
"details": [],
"max_points": 10
}
]
},
{
"key": "responsiveness",
"band": "good",
"name": "Issue & PR responsiveness",
"note": null,
"notes": [],
"value": 76,
"inputs": {
"merged_prs": 9,
"open_issues": 5,
"closed_issues": 21,
"issue_closed_ratio": 0.808,
"closed_unmerged_prs": 0
},
"components": [
{
"key": "issue_resolution",
"name": "Issue resolution",
"detail": "81% of issues closed",
"points": 37.8,
"status": "partial",
"details": [
{
"code": "issues_closed_share",
"params": {
"share": 81
}
}
],
"max_points": 46.75
},
{
"key": "pr_acceptance",
"name": "PR acceptance",
"detail": "9/9 decided PRs merged",
"points": 38.2,
"status": "met",
"details": [
{
"code": "decided_prs_merged",
"params": {
"merged": 9,
"decided": 9
}
}
],
"max_points": 38.25
},
{
"key": "openssf_scorecard_code_review",
"name": "OpenSSF Scorecard: Code-Review",
"detail": "Found 0/22 approved changesets -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 15
}
]
},
{
"key": "stewardship",
"band": "moderate",
"name": "Ownership & stewardship",
"note": "Excluded from scoring (no data or not applicable): Verified domain. Remaining weights renormalized.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"verified_domain"
]
}
},
{
"code": "weights_renormalized",
"params": {}
}
],
"value": 57,
"inputs": {
"followers": 26,
"owner_type": "User",
"is_verified": null,
"owner_login": "handsomejustin",
"public_repos": 72,
"account_age_days": 4442
},
"components": [
{
"key": "ownership_backing",
"name": "Ownership backing",
"detail": "personal (user) account",
"points": 10,
"status": "partial",
"details": [
{
"code": "owner_personal",
"params": {}
}
],
"max_points": 30
},
{
"key": "verified_domain",
"name": "Verified domain",
"detail": "not applicable to user accounts",
"points": 0,
"status": "excluded",
"details": [
{
"code": "not_applicable_to_user_accounts",
"params": {}
}
],
"max_points": 20
},
{
"key": "owner_reach",
"name": "Owner reach",
"detail": "26 followers of handsomejustin",
"points": 10.3,
"status": "partial",
"details": [
{
"code": "owner_followers",
"params": {
"count": 26,
"login": "handsomejustin"
}
}
],
"max_points": 25
},
{
"key": "track_record",
"name": "Track record",
"detail": "72 public repos, account ~12 yr old",
"points": 25,
"status": "met",
"details": [
{
"code": "public_repos",
"params": {
"count": 72
}
},
{
"code": "account_age_years",
"params": {
"years": 12
}
}
],
"max_points": 25
}
]
},
{
"key": "package_maintenance",
"band": "excellent",
"name": "Package maintenance",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"packages": [
"easy-tdx"
],
"ecosystems": "pypi",
"any_deprecated": false,
"min_days_since_publish": 17
},
"components": [
{
"key": "published_resolvable",
"name": "Published & resolvable",
"detail": "1 package(s) on pypi",
"points": 25,
"status": "met",
"details": [
{
"code": "packages_published",
"params": {
"count": 1,
"ecosystems": "pypi"
}
}
],
"max_points": 25
},
{
"key": "publish_recency",
"name": "Publish recency",
"detail": "latest publish 17 days ago",
"points": 35,
"status": "met",
"details": [
{
"code": "publish_recency",
"params": {
"days": 17
}
}
],
"max_points": 35
},
{
"key": "version_history",
"name": "Version history",
"detail": "82 published versions",
"points": 20,
"status": "met",
"details": [
{
"code": "published_versions",
"params": {
"count": 82
}
}
],
"max_points": 20
},
{
"key": "not_deprecated",
"name": "Not deprecated",
"detail": "active, not deprecated or yanked",
"points": 20,
"status": "met",
"details": [
{
"code": "package_not_deprecated",
"params": {}
}
],
"max_points": 20
}
]
}
],
"description": "Will the project survive its people — bus factor, responsiveness, who backs it, and package upkeep?"
},
{
"key": "engineering",
"band": "moderate",
"name": "Engineering Quality",
"value": 69,
"weight": 0.2,
"metrics": [
{
"key": "engineering_practices",
"band": "moderate",
"name": "Engineering practices",
"note": null,
"notes": [],
"value": 58,
"inputs": {
"has_ci": true,
"has_tests": true,
"has_editorconfig": false,
"has_linter_config": false,
"has_precommit_config": false
},
"components": [
{
"key": "ci_workflows",
"name": "CI workflows",
"detail": "3 workflow(s)",
"points": 24,
"status": "met",
"details": [
{
"code": "ci_workflows",
"params": {
"count": 3
}
}
],
"max_points": 24
},
{
"key": "tests_present",
"name": "Tests present",
"detail": null,
"points": 24,
"status": "met",
"details": [],
"max_points": 24
},
{
"key": "linter_config",
"name": "Linter config",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 16
},
{
"key": "pre_commit_hooks",
"name": "Pre-commit hooks",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 9.6
},
{
"key": "editorconfig",
"name": ".editorconfig",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 6.4
},
{
"key": "openssf_scorecard_ci_tests",
"name": "OpenSSF Scorecard: CI-Tests",
"detail": "4 out of 7 merged PRs checked by a CI test -- score normalized to 5",
"points": 10,
"status": "partial",
"details": [],
"max_points": 20
}
]
},
{
"key": "documentation",
"band": "excellent",
"name": "Documentation",
"note": null,
"notes": [],
"value": 85,
"inputs": {
"topics": [
"chinese-stock-market",
"financial-data",
"mootdx",
"pytdx",
"tdx",
"technical-analysis",
"a-share-tdx",
"stock-analysis",
"stock-data",
"stock-trading"
],
"has_wiki": true,
"homepage": null,
"has_readme": true,
"has_docs_dir": true,
"has_description": true
},
"components": [
{
"key": "readme",
"name": "README",
"detail": null,
"points": 30,
"status": "met",
"details": [],
"max_points": 30
},
{
"key": "documentation_directory",
"name": "Documentation directory",
"detail": null,
"points": 25,
"status": "met",
"details": [],
"max_points": 25
},
{
"key": "documentation_homepage_site",
"name": "Documentation / homepage site",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 15
},
{
"key": "repository_description",
"name": "Repository description",
"detail": null,
"points": 10,
"status": "met",
"details": [],
"max_points": 10
},
{
"key": "topics",
"name": "Topics",
"detail": "10 topics",
"points": 10,
"status": "met",
"details": [
{
"code": "topics_count",
"params": {
"count": 10
}
}
],
"max_points": 10
},
{
"key": "wiki",
"name": "Wiki",
"detail": null,
"points": 10,
"status": "met",
"details": [],
"max_points": 10
}
]
}
],
"description": "Are baseline engineering and documentation practices in place?"
},
{
"key": "security",
"band": "at_risk",
"name": "Security",
"value": 43,
"weight": 0.16,
"metrics": [
{
"key": "security_posture",
"band": "critical",
"name": "Security posture",
"note": null,
"notes": [],
"value": 29,
"inputs": {
"source": "openssf_scorecard",
"checks_evaluated": 18,
"scorecard_version": "v5.5.0",
"checks_inconclusive": 0,
"scorecard_aggregate": 2.9
},
"components": [
{
"key": "binary_artifacts",
"name": "Binary-Artifacts",
"detail": "no binaries found in the repo",
"points": 7.5,
"status": "met",
"details": [],
"max_points": 7.5
},
{
"key": "branch_protection",
"name": "Branch-Protection",
"detail": "branch protection not enabled on development/release branches",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "ci_tests",
"name": "CI-Tests",
"detail": "4 out of 7 merged PRs checked by a CI test -- score normalized to 5",
"points": 1.2,
"status": "partial",
"details": [],
"max_points": 2.5
},
{
"key": "cii_best_practices",
"name": "CII-Best-Practices",
"detail": "no effort to earn an OpenSSF best practices badge detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 2.5
},
{
"key": "code_review",
"name": "Code-Review",
"detail": "Found 0/22 approved changesets -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "contributors",
"name": "Contributors",
"detail": "project has 2 contributing companies or organizations -- score normalized to 6",
"points": 1.5,
"status": "partial",
"details": [],
"max_points": 2.5
},
{
"key": "dangerous_workflow",
"name": "Dangerous-Workflow",
"detail": "no dangerous workflow patterns detected",
"points": 10,
"status": "met",
"details": [],
"max_points": 10
},
{
"key": "dependency_update_tool",
"name": "Dependency-Update-Tool",
"detail": "no update tool detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "fuzzing",
"name": "Fuzzing",
"detail": "project is not fuzzed",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "license",
"name": "License",
"detail": "license file detected",
"points": 2.2,
"status": "partial",
"details": [],
"max_points": 2.5
},
{
"key": "maintained",
"name": "Maintained",
"detail": "project was created within the last 90 days. Please review its contents carefully",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "packaging",
"name": "Packaging",
"detail": "packaging workflow detected",
"points": 5,
"status": "met",
"details": [],
"max_points": 5
},
{
"key": "pinned_dependencies",
"name": "Pinned-Dependencies",
"detail": "dependency not pinned by hash detected -- score normalized to 3",
"points": 1.5,
"status": "partial",
"details": [],
"max_points": 5
},
{
"key": "sast",
"name": "SAST",
"detail": "SAST tool is not run on all commits -- score normalized to 0",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "security_policy",
"name": "Security-Policy",
"detail": "security policy file not detected",
"points": 0,
"status": "missed",
"details": [],
"max_points": 5
},
{
"key": "signed_releases",
"name": "Signed-Releases",
"detail": "Project has not signed or included provenance with any releases.",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "token_permissions",
"name": "Token-Permissions",
"detail": "detected GitHub workflow tokens with excessive permissions",
"points": 0,
"status": "missed",
"details": [],
"max_points": 7.5
},
{
"key": "vulnerabilities",
"name": "Vulnerabilities",
"detail": "8 existing vulnerabilities detected",
"points": 1.5,
"status": "partial",
"details": [],
"max_points": 7.5
}
]
},
{
"key": "dependency_advisories",
"band": "excellent",
"name": "Dependency advisories",
"note": "Excluded from scoring (no data or not applicable): No advisories left outstanding. Remaining weights renormalized. Matched the pypi:easy-tdx@1.20.4 runtime dependency closure — what installing the published package pulls in — 7 packages. Reachability is not analyzed.",
"notes": [
{
"code": "excluded_no_data",
"params": {
"components": [
"no_advisories_left_outstanding"
]
}
},
{
"code": "weights_renormalized",
"params": {}
},
{
"code": "advisories_scope_published",
"params": {
"package": "pypi:easy-tdx@1.20.4",
"assessed": 7
}
},
{
"code": "advisories_reachability",
"params": {}
}
],
"value": 100,
"inputs": {
"source": "osv",
"advisories": 0,
"affected_packages": 0,
"assessed_packages": 7,
"unassessed_packages": 0,
"affected_by_severity": "none",
"direct_affected_packages": 0
},
"components": [
{
"key": "direct_dependencies_free_of_known_advisories",
"name": "Direct dependencies free of known advisories",
"detail": "no direct dependency carries a known advisory",
"points": 35,
"status": "met",
"details": [
{
"code": "no_direct_advisories",
"params": {}
}
],
"max_points": 35
},
{
"key": "indirect_dependencies_free_of_known_advisories",
"name": "Indirect dependencies free of known advisories",
"detail": "no indirect dependency carries a known advisory",
"points": 25,
"status": "met",
"details": [
{
"code": "no_indirect_advisories",
"params": {}
}
],
"max_points": 25
},
{
"key": "no_advisories_left_outstanding",
"name": "No advisories left outstanding",
"detail": "no advisory carries a publication date",
"points": 0,
"status": "excluded",
"details": [
{
"code": "advisories_no_publication_date",
"params": {}
}
],
"max_points": 40
}
]
},
{
"key": "malicious_dependencies",
"band": "excellent",
"name": "Malicious dependencies",
"note": null,
"notes": [],
"value": 100,
"inputs": {
"source": "osv",
"meaning": "reported as a malicious package by the OpenSSF corpus; the remedy is removal or moving off the compromised name, never an upgrade of the same artifact. Versions the registry has since pulled are listed but not scored",
"packages": [],
"red_flag": false,
"assessed_packages": 7,
"malicious_packages": 0,
"direct_malicious_packages": 0,
"withdrawn_malicious_packages": 0,
"installable_malicious_packages": 0
},
"components": [
{
"key": "no_dependency_reported_as_a_malicious_package",
"name": "No dependency reported as a malicious package",
"detail": "no dependency is reported as a malicious package",
"points": 100,
"status": "met",
"details": [
{
"code": "no_malicious_dependencies",
"params": {}
}
],
"max_points": 100
}
]
}
],
"description": "Are visible security and supply-chain practices strong, with no malicious dependency and no unresolved high-risk jurisdiction exposure?"
},
{
"key": "ai_readiness",
"band": "at_risk",
"name": "AI Readiness",
"value": 49,
"weight": 0,
"metrics": [
{
"key": "ai_agent_context",
"band": "at_risk",
"name": "Agent context & guidance",
"note": null,
"notes": [],
"value": 40,
"inputs": {
"has_llms_txt": false,
"legible_history_share": 0.99,
"agent_instruction_files": [],
"agent_instruction_max_bytes": null
},
"components": [
{
"key": "agent_instructions",
"name": "Agent instructions",
"detail": "no CLAUDE.md / AGENTS.md / editor rules",
"points": 0,
"status": "missed",
"details": [
{
"code": "no_agent_instructions",
"params": {}
}
],
"max_points": 45
},
{
"key": "machine_readable_docs_llms_txt",
"name": "Machine-readable docs (llms.txt)",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 15
},
{
"key": "legible_commit_history",
"name": "Legible commit history",
"detail": "99 of 100 human commits state their intent (structured subject or explanatory body)",
"points": 40,
"status": "met",
"details": [
{
"code": "legible_history",
"params": {
"legible": 99,
"sampled": 100
}
}
],
"max_points": 40
}
]
},
{
"key": "ai_verify_loop",
"band": "at_risk",
"name": "Verify loop (build / test / typecheck)",
"note": null,
"notes": [],
"value": 46,
"inputs": {
"has_nix": false,
"has_tests": true,
"lockfiles": [
"package-lock.json"
],
"has_dockerfile": false,
"typed_language": false,
"bootstrap_files": [],
"has_devcontainer": false,
"has_linter_config": false,
"typecheck_configs": [
"src/easy_tdx/py.typed",
"web-ui/tsconfig.json"
],
"agent_commit_share": 0,
"toolchain_manifests": [],
"dependency_bot_commit_share": 0
},
"components": [
{
"key": "one_command_bootstrap",
"name": "One-command bootstrap",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 18
},
{
"key": "automated_tests",
"name": "Automated tests",
"detail": null,
"points": 22,
"status": "met",
"details": [],
"max_points": 22
},
{
"key": "lint_format_config",
"name": "Lint / format config",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 11
},
{
"key": "static_type_checking",
"name": "Static type checking",
"detail": "src/easy_tdx/py.typed, web-ui/tsconfig.json",
"points": 11,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "src/easy_tdx/py.typed, web-ui/tsconfig.json"
}
}
],
"max_points": 11
},
{
"key": "reproducible_environment",
"name": "Reproducible environment",
"detail": "lockfile",
"points": 10,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "lockfile"
}
}
],
"max_points": 10
},
{
"key": "demonstrated_agent_practice",
"name": "Demonstrated agent practice",
"detail": "no agent-authored commits among the last 100",
"points": 0,
"status": "missed",
"details": [
{
"code": "no_agent_authored_commits",
"params": {
"sampled": 100
}
}
],
"max_points": 10
},
{
"key": "automated_maintenance",
"name": "Automated maintenance",
"detail": "no automated dependency updates observed",
"points": 0,
"status": "missed",
"details": [
{
"code": "no_dependency_automation",
"params": {}
}
],
"max_points": 8
},
{
"key": "openssf_scorecard_pinned_dependencies",
"name": "OpenSSF Scorecard: Pinned-Dependencies",
"detail": "dependency not pinned by hash detected -- score normalized to 3",
"points": 3,
"status": "partial",
"details": [],
"max_points": 10
}
]
},
{
"key": "ai_code_legibility",
"band": "good",
"name": "Code legibility for models",
"note": null,
"notes": [],
"value": 82,
"inputs": {
"primary_language": "Python",
"largest_source_bytes": 74718,
"source_files_sampled": 389,
"oversized_source_files": 2
},
"components": [
{
"key": "type_checkable_code",
"name": "Type-checkable code",
"detail": "Python with type-check config (src/easy_tdx/py.typed, web-ui/tsconfig.json)",
"points": 27,
"status": "partial",
"details": [
{
"code": "typecheck_config_language",
"params": {
"files": "src/easy_tdx/py.typed, web-ui/tsconfig.json",
"language": "Python"
}
}
],
"max_points": 45
},
{
"key": "manageable_file_sizes",
"name": "Manageable file sizes",
"detail": "2/389 source files over 60KB",
"points": 54.7,
"status": "partial",
"details": [
{
"code": "oversized_source_files",
"params": {
"kb": 60,
"sampled": 389,
"oversized": 2
}
}
],
"max_points": 55
}
]
},
{
"key": "ai_interfaces",
"band": "at_risk",
"name": "Machine-readable interfaces",
"note": null,
"notes": [],
"value": 40,
"inputs": {
"example_dirs": [
"examples"
],
"has_mcp_signal": false,
"api_schema_files": []
},
"components": [
{
"key": "api_schema_openapi_graphql_proto",
"name": "API schema (OpenAPI/GraphQL/proto)",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 40
},
{
"key": "mcp_server",
"name": "MCP server",
"detail": null,
"points": 0,
"status": "missed",
"details": [],
"max_points": 20
},
{
"key": "runnable_examples",
"name": "Runnable examples",
"detail": "examples",
"points": 40,
"status": "met",
"details": [
{
"code": "file_list",
"params": {
"files": "examples"
}
}
],
"max_points": 40
}
]
}
],
"description": "How well is the repo equipped to be developed and maintained with AI coding agents? An independent, experimental badge — weight 0.0, so it is surfaced on its own and does not affect the overall health score."
}
],
"metrics_version": "1.13.0"
},
"warnings": [
"Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token"
],
"report_type": "repository",
"generated_at": "2026-07-30T17:18:38.616816Z",
"schema_version": "0.27.0",
"badge_url": "https://raw.githubusercontent.com/inspect-software/badges/main/v1/h/handsomejustin/easy_tdx.svg",
"full_name": "handsomejustin/easy_tdx",
"license_state": "custom",
"license_spdx": null
}