Публічний реєстр
Звіт про здоров'я програмного забезпеченнясхема 0.27.0 · метрики 1.13.0 · 2026-07-30 17:18 UTC

handsomejustin / easy_tdx

免费Python量化SDK:通达信协议直连 A股/港股/美股数据,内置技术指标、缠论分析、回测引擎与 Web 可视化,JSON 输出适配 AI Agent

PythonВласна ліцензія★ 636 зірок⑂ 194 форкиз трав. 2026 р.Переглянути на GitHub ↗

handsomejustin/easy_tdx має індекс здоров’я 62 зі 100, що відповідає смузі «Помірний». Найвищий показник — Engineering Quality (69/100), найнижчий — Security (43/100). Останнє оновлення було 17 днів тому. Більшість нещодавньої роботи виконує один учасник.

62
загалом / 100
Помірний

Індекс здоров'я програмного забезпечення

Метрики згруповано у зважені категорії на шкалі 1–100. Загальна оцінка починається як їхнє середнє; коли публічні дані активують Політику юрисдикцій високого ризику, рейтинг коригується й отримує верхню межу 49 («Під ризиком»). Готовність до ШІ не входить до індексу.

62
Відмінний85-100Зразковий; відповідає практично всім перевіреним критеріям
Добрий70-84Здоровий; незначні прогалини
Помірний50-69Прийнятний, але з помітними прогалинами; рекомендовано перевірку
У зоні ризику30-49Суттєві слабкі місця; впровадження потребує обережності
Критичний1-29Серйозні проблеми (покинутий, єдиний мейнтейнер, без базової гігієни)
ЖиттєздатністьСпільнота тавпровадженняСталість таврядуванняІнженернаякістьБезпекаГотовність доШІ

Профіль оцінок

Кожна вісь — окрема категорія. Форма важить більше, ніж середнє: здоровий об'єкт заповнює всю фігуру, тоді як профіль із піками та провалами означає, що сила в одному вимірі маскує ризик в іншому.

Власність

毛利哥Особистий обліковий запис
26 підписників72 публічні репозиторіїз черв. 2014 р.23

Цей репозиторій належить особистому обліковому запису. Проєкт з єдиним власником несе більший ризик безперервності, ніж підтримуваний організацією.

Пакетні екосистеми

РеєстрПакетВерсіяЗавантажень / місВерсіїОстання публікація
PyPIeasy-tdx1.20.415 8048217 днів тому

Метрики за категоріями

Життєздатність

Чи живий проєкт — чи пишеться код і чи виходять релізи?

68Помірний · 22% загального індексу
Як обчислюється оцінка
28.8/36Свіжість push — останній push 17 дн. тому
6.9/36Ритм комітів — 10/52 тижнів із комітами
18/18Обсяг комітів — 289 комітів за останній рік
0/10OpenSSF Scorecard: Maintained — project was created within the last 90 days. Please review its contents carefully
Використані вхідні дані
commits_last_year289
human_commit_share1
days_since_last_push17
active_weeks_last_year10
Як обчислюється оцінка
27/27Випускає релізи — опубліковано 65 релізів
36/36Свіжість релізів — останній реліз 17 дн. тому
27/27Ритм релізів — реліз кожні ~0,6 дн.
0/10OpenSSF Scorecard: Signed-Releases — Project has not signed or included provenance with any releases.
Використані вхідні дані
releases_count65
latest_release_tagv1.20.4
releases_from_tagsні
days_since_latest_release17
mean_days_between_releases0,6

Спільнота та впровадження

Чи має проєкт користувачів, завантаження, увагу та влаштовані умови для контриб’юторів?

61Помірний · 18% загального індексу
Як обчислюється оцінка
45.5/60Зірки — 636 зірок
19/25Форки — 194 форків
6/15Спостерігачі — 13 спостерігачів
Використані вхідні дані
forks194
stars636
watchers13
growth_stateunverified
growth_factor_pct100
growth_unverified_reasonno_history

Здоров'я спільноти

44У зоні ризику
Як обчислюється оцінка
22.5/22.5README
16.9/22.5Ліцензія — файл ліцензії наявний, не є визнаною ліцензією
0/18Настанови CONTRIBUTING
0/13.5Кодекс поведінки
0/7.2Шаблон issue
0/6.3Шаблон PR
Використані вхідні дані
has_readmeтак
has_licenseтак
has_contributingні
has_issue_templateні
has_code_of_conductні
has_pull_request_templateні
Як обчислюється оцінка
56/80Щомісячні завантаження — 15 804 завантажень/місяць у pypi
0/20Залежні пакети в реєстрі — ця екосистема цього не повідомляє
Використані вхідні дані
packageseasy-tdx
dependents
ecosystemspypi
total_downloads
monthly_downloads15 804
Виключено з оцінювання (немає даних або не застосовно): Залежні пакети в реєстрі. Залишкові ваги перенормовано.

Сталість та врядування

Чи переживе проєкт своїх людей — бас-фактор, реактивність, хто за ним стоїть і як супроводжуються пакети?

62Помірний · 24% загального індексу
Як обчислюється оцінка
9/54Бас-фактор — на 1 контриб’ютор(ів) припадає половина всіх комітів
9.5/22.5Розподіл комітів — головний контриб’ютор — автор 58% комітів
5.4/13.5Широта контриб’юторів — 4 контриб’юторів
6/10OpenSSF Scorecard: Contributors — project has 2 contributing companies or organizations -- score normalized to 6
Використані вхідні дані
bus_factor1
contributors_sampled4
top_contributor_share0,58
Як обчислюється оцінка
37.8/46.8Вирішення issue — закрито 81% issue
38.2/38.3Прийняття PR — злито 9/9 вирішених PR
0/15OpenSSF Scorecard: Code-Review — Found 0/22 approved changesets -- score normalized to 0
Використані вхідні дані
merged_prs9
open_issues5
closed_issues21
issue_closed_ratio0,808
closed_unmerged_prs0
Як обчислюється оцінка
10/30Підтримка власника — особистий (користувацький) обліковий запис
0/20Верифікований домен — не застосовно до користувацьких облікових записів
10.3/25Охоплення власника — 26 підписників у handsomejustin
25/25Послужний список — 72 публічних репозиторіїв, вік облікового запису ~12 р.
Використані вхідні дані
followers26
owner_typeUser
is_verified
owner_loginhandsomejustin
public_repos72
account_age_days4 442
Виключено з оцінювання (немає даних або не застосовно): Верифікований домен. Залишкові ваги перенормовано.

Супровід пакетів

100Відмінний
Як обчислюється оцінка
25/25Опубліковано й доступно — 1 пакет(ів) у pypi
35/35Свіжість публікацій — остання публікація 17 дн. тому
20/20Історія версій — 82 опублікованих версій
20/20Не застарілий — активний, не deprecated і не yanked
Використані вхідні дані
packageseasy-tdx
ecosystemspypi
any_deprecatedні
min_days_since_publish17

Інженерна якість

Чи наявні базові інженерні практики та документація?

69Помірний · 20% загального індексу
Як обчислюється оцінка
24/24Процеси CI — 3 процес(ів) CI
24/24Наявні тести
0/16Конфігурація лінтера
0/9.6Pre-commit-хуки
0/6.4.editorconfig
10/20OpenSSF Scorecard: CI-Tests — 4 out of 7 merged PRs checked by a CI test -- score normalized to 5
Використані вхідні дані
has_ciтак
has_testsтак
has_editorconfigні
has_linter_configні
has_precommit_configні

Документація

85Відмінний
Як обчислюється оцінка
30/30README
25/25Каталог документації
0/15Сайт документації / домашня сторінка
10/10Опис репозиторію
10/10Теми — 10 тем
10/10Wiki
Використані вхідні дані
topicschinese-stock-market, financial-data, mootdx, pytdx, tdx, technical-analysis, a-share-tdx, stock-analysis, stock-data, stock-trading
has_wikiтак
homepage
has_readmeтак
has_docs_dirтак
has_descriptionтак

Безпека

Чи міцні видимі практики безпеки й ланцюга постачання, без непослабленої пов’язаності з юрисдикціями високого ризику?

43У зоні ризику · 16% загального індексу

Стан безпеки

29Критичний
Як обчислюється оцінка
7.5/7.5Binary-Artifacts — no binaries found in the repo
0/7.5Branch-Protection — branch protection not enabled on development/release branches
1.2/2.5CI-Tests — 4 out of 7 merged PRs checked by a CI test -- score normalized to 5
0/2.5CII-Best-Practices — no effort to earn an OpenSSF best practices badge detected
0/7.5Code-Review — Found 0/22 approved changesets -- score normalized to 0
1.5/2.5Contributors — project has 2 contributing companies or organizations -- score normalized to 6
10/10Dangerous-Workflow — no dangerous workflow patterns detected
0/7.5Dependency-Update-Tool — no update tool detected
0/5Fuzzing — project is not fuzzed
2.2/2.5Ліцензія — license file detected
0/7.5Maintained — project was created within the last 90 days. Please review its contents carefully
5/5Packaging — packaging workflow detected
1.5/5Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 3
0/5SAST — SAST tool is not run on all commits -- score normalized to 0
0/5Security-Policy — security policy file not detected
0/7.5Signed-Releases — Project has not signed or included provenance with any releases.
0/7.5Token-Permissions — detected GitHub workflow tokens with excessive permissions
1.5/7.5Vulnerabilities — 8 existing vulnerabilities detected
Використані вхідні дані
sourceopenssf_scorecard
checks_evaluated18
scorecard_versionv5.5.0
checks_inconclusive0
scorecard_aggregate2,9
Як обчислюється оцінка
35/35Прямі залежності без відомих сповіщень — жодна пряма залежність не має відомих сповіщень
25/25Непрямі залежності без відомих сповіщень — жодна непряма залежність не має відомих сповіщень
0/40Немає задавнених сповіщень — жодне сповіщення не має дати публікації
Використані вхідні дані
sourceosv
advisories0
affected_packages0
assessed_packages7
unassessed_packages0
affected_by_severitynone
direct_affected_packages0
Виключено з оцінювання (немає даних або не застосовно): Немає задавнених сповіщень. Залишкові ваги перенормовано. Звірено з runtime-замиканням залежностей pypi:easy-tdx@1.20.4 — тим, що тягне за собою встановлення опублікованого пакета, — 7 пакетів. Досяжність не аналізується.

Готовність до ШІ

Наскільки репозиторій оснащений для розробки та супроводу за участі ШІ-агентів? Незалежний, експериментальний бейдж — вага 0.0, тож він подається окремо і не впливає на загальний індекс здоров'я.

49У зоні ризику · 0% загального індексу
Як обчислюється оцінка
0/45Інструкції для агентів — немає CLAUDE.md / AGENTS.md / правил редактора
0/15Машиночитана документація (llms.txt)
40/40Читабельна історія комітів — намір зазначено у 99 з 100 людських комітів (структурований заголовок або пояснювальний текст)
Використані вхідні дані
has_llms_txtні
legible_history_share0,99
agent_instruction_files
agent_instruction_max_bytes
Як обчислюється оцінка
0/18Розгортання однією командою
22/22Автоматизовані тести
0/11Конфігурація лінтера / форматера
11/11Статична перевірка типів — src/easy_tdx/py.typed, web-ui/tsconfig.json
10/10Відтворюване середовище — lockfile
0/10Підтверджена практика роботи з агентами — серед останніх 100 комітів немає створених агентом
0/8Автоматизоване супроводження — автоматичних оновлень залежностей не виявлено
3/10OpenSSF Scorecard: Pinned-Dependencies — dependency not pinned by hash detected -- score normalized to 3
Використані вхідні дані
has_nixні
has_testsтак
lockfilespackage-lock.json
has_dockerfileні
typed_languageні
bootstrap_files
has_devcontainerні
has_linter_configні
typecheck_configssrc/easy_tdx/py.typed, web-ui/tsconfig.json
agent_commit_share0
toolchain_manifests
dependency_bot_commit_share0
Як обчислюється оцінка
27/45Типізований код — Python з конфігурацією перевірки типів (src/easy_tdx/py.typed, web-ui/tsconfig.json)
54.7/55Керовані розміри файлів — 2/389 файлів вихідного коду понад 60 КБ
Використані вхідні дані
primary_languagePython
largest_source_bytes74 718
source_files_sampled389
oversized_source_files2
Як обчислюється оцінка
0/40Схема API (OpenAPI/GraphQL/proto)
0/20Сервер MCP
40/40Придатні до запуску приклади — examples
Використані вхідні дані
example_dirsexamples
has_mcp_signalні
api_schema_files

Ключові факти

636зірок GitHub
4контриб'юторів
289комітів за останні 12 місяців
17днів від останнього пушу
65релізів
1бас-фактор
5відкритих issue
npm, PyPIпакетних екосистем

Попередження щодо збору даних

  • Star history unavailable: GitHub GraphQL error: Resource not accessible by personal access token

Докладніше

Історія зірок і форків 0 ★ / 194 ⇿
0Зірки
194Форки
65Релізи

Коли додано кожну зірку й форк — зібрано з GitHub і згруповано за днями. Кумулятивне зростання розміщено просто над денними додаваннями, з яких воно складається, тож їх видно одне проти одного: рівномірне органічне накопичення виглядає зовсім інакше, ніж різкий короткочасний сплеск. Там, де цю різницю можна виміряти, її подано як автентичність росту.

0408012016020019482026-052026-062026-07
Мажорні 0Мінорні 12Патчі 53
OpenSSF Scorecard 2.9 / 10
2.9сукупно

Незалежна, не прив'язана до інструментів оцінка безпеки від відкритого проєкту OpenSSF Scorecard. Кожна перевірка винагороджує практику безпеки, а не інструмент конкретного постачальника. Перевірки, які Scorecard не зміг визначити, позначено н/д і виключено з оцінки безпеки (вони ніколи не зараховуються як нуль).Scorecard v5.5.0 · 2026-07-30 17:18 UTC

10Binary-Artifactsno binaries found in the repo
0Branch-Protectionbranch protection not enabled on development/release branches
5CI-Tests4 out of 7 merged PRs checked by a CI test -- score normalized to 5
0CII-Best-Practicesno effort to earn an OpenSSF best practices badge detected
0Code-ReviewFound 0/22 approved changesets -- score normalized to 0
6Contributorsproject has 2 contributing companies or organizations -- score normalized to 6
10Dangerous-Workflowno dangerous workflow patterns detected
0Dependency-Update-Toolno update tool detected
0Fuzzingproject is not fuzzed
9Licenselicense file detected
0Maintainedproject was created within the last 90 days. Please review its contents carefully
10Packagingpackaging workflow detected
3Pinned-Dependenciesdependency not pinned by hash detected -- score normalized to 3
0SASTSAST tool is not run on all commits -- score normalized to 0
0Security-Policysecurity policy file not detected
0Signed-ReleasesProject has not signed or included provenance with any releases.
0Token-Permissionsdetected GitHub workflow tokens with excessive permissions
2Vulnerabilities8 existing vulnerabilities detected
Прямі залежності 7
РеєстрПакетОбмеження версіїМаніфест
PyPIpandas>=2.0,<3pyproject.toml
PyPItzdata>=2024.1pyproject.toml
PyPIclick>=8.0,<9pyproject.toml
npmecharts^6.1.0web-ui/package.json
npmpinia^3.0.4web-ui/package.json
npmvue^3.5.39web-ui/package.json
npmvue-router^4.6.4web-ui/package.json
Усі залежності 123

Повний розв'язаний набір залежностей із графа залежностей GitHub: 7 прямих і 116 непрямих (транзитивних) пакетів. Транзитивне замикання є повним, коли в репозиторії закомічено lockfile.

РеєстрПакетВерсіяЗв'язок
npmecharts6.1.0пряма
npmpinia3.0.4пряма
npmvue3.5.39пряма
npmvue-router4.6.4пряма
PyPIclickпряма
PyPIpandasпряма
PyPItzdataпряма
npm@babel/helper-string-parser7.29.7непряма
npm@babel/helper-validator-identifier7.29.7непряма
npm@babel/parser7.29.7непряма
npm@babel/types7.29.7непряма
npm@emnapi/core1.11.1непряма
npm@emnapi/runtime1.11.1непряма
npm@emnapi/wasi-threads1.2.2непряма
npm@jridgewell/sourcemap-codec1.5.5непряма
npm@napi-rs/wasm-runtime1.1.6непряма
npm@oxc-project/types0.138.0непряма
npm@rolldown/binding-android-arm641.1.4непряма
npm@rolldown/binding-darwin-arm641.1.4непряма
npm@rolldown/binding-darwin-x641.1.4непряма
npm@rolldown/binding-freebsd-x641.1.4непряма
npm@rolldown/binding-linux-arm-gnueabihf1.1.4непряма
npm@rolldown/binding-linux-arm64-gnu1.1.4непряма
npm@rolldown/binding-linux-arm64-musl1.1.4непряма
npm@rolldown/binding-linux-ppc64-gnu1.1.4непряма
npm@rolldown/binding-linux-s390x-gnu1.1.4непряма
npm@rolldown/binding-linux-x64-gnu1.1.4непряма
npm@rolldown/binding-linux-x64-musl1.1.4непряма
npm@rolldown/binding-openharmony-arm641.1.4непряма
npm@rolldown/binding-wasm32-wasi1.1.4непряма
npm@rolldown/binding-win32-arm64-msvc1.1.4непряма
npm@rolldown/binding-win32-x64-msvc1.1.4непряма
npm@rolldown/pluginutils1.0.1непряма
npm@tybys/wasm-util0.10.3непряма
npm@types/node24.13.2непряма
npm@vitejs/plugin-vue6.0.7непряма
npm@volar/language-core2.4.28непряма
npm@volar/source-map2.4.28непряма
npm@volar/typescript2.4.28непряма
npm@vue/compiler-core3.5.39непряма
npm@vue/compiler-dom3.5.39непряма
npm@vue/compiler-sfc3.5.39непряма
npm@vue/compiler-ssr3.5.39непряма
npm@vue/devtools-api6.6.4непряма
npm@vue/devtools-api7.7.10непряма
npm@vue/devtools-kit7.7.10непряма
npm@vue/devtools-shared7.7.10непряма
npm@vue/language-core3.3.6непряма
npm@vue/reactivity3.5.39непряма
npm@vue/runtime-core3.5.39непряма
npm@vue/runtime-dom3.5.39непряма
npm@vue/server-renderer3.5.39непряма
npm@vue/shared3.5.39непряма
npm@vue/tsconfig0.9.1непряма
npmalien-signals3.2.1непряма
npmbirpc2.9.0непряма
npmcopy-anything4.0.5непряма
npmcsstype3.2.3непряма
npmdetect-libc2.1.2непряма
npmentities7.0.1непряма
npmestree-walker2.0.2непряма
npmfdir6.5.0непряма
npmfsevents2.3.3непряма
npmhookable5.5.3непряма
npmis-what5.5.0непряма
npmlightningcss1.32.0непряма
npmlightningcss-android-arm641.32.0непряма
npmlightningcss-darwin-arm641.32.0непряма
npmlightningcss-darwin-x641.32.0непряма
npmlightningcss-freebsd-x641.32.0непряма
npmlightningcss-linux-arm-gnueabihf1.32.0непряма
npmlightningcss-linux-arm64-gnu1.32.0непряма
npmlightningcss-linux-arm64-musl1.32.0непряма
npmlightningcss-linux-x64-gnu1.32.0непряма
npmlightningcss-linux-x64-musl1.32.0непряма
npmlightningcss-win32-arm64-msvc1.32.0непряма
npmlightningcss-win32-x64-msvc1.32.0непряма
npmmagic-string0.30.21непряма
npmmitt3.0.1непряма
npmmuggle-string0.4.1непряма
npmnanoid3.3.15непряма
npmpath-browserify1.0.1непряма
npmperfect-debounce1.0.0непряма
npmpicocolors1.1.1непряма
npmpicomatch4.0.5непряма
npmpostcss8.5.16непряма
npmrfdc1.4.1непряма
npmrolldown1.1.4непряма
npmsource-map-js1.2.1непряма
npmspeakingurl14.0.1непряма
npmsuperjson2.2.6непряма
npmtinyglobby0.2.17непряма
npmtslib2.3.0непряма
npmtslib2.8.1непряма
npmtypescript6.0.3непряма
npmundici-types7.18.2непряма
npmvite8.1.3непряма
npmvscode-uri3.1.0непряма
npmvue-tsc3.3.6непряма
npmzrender6.1.0непряма
PyPIcoverage7.14.0непряма
PyPIfastapiнепряма
PyPIfastapi0.115.11непряма
PyPIhttpxнепряма
PyPImypyнепряма
PyPImypy2.1.0непряма
PyPImyst-parserнепряма
PyPInumpy2.2.3непряма
PyPIpillowнепряма
PyPIpystrayнепряма
PyPIpytestнепряма
PyPIpytest9.0.3непряма
PyPIpytest-asyncioнепряма
PyPIpytest-cov7.1.0непряма
PyPIruffнепряма
PyPIruff0.11.11непряма
PyPIscipyнепряма
PyPIscipy1.15.3непряма
PyPIsphinxнепряма
PyPIsphinx-autobuildнепряма
PyPIsphinx-rtd-themeнепряма
PyPIuvicornнепряма
PyPIuvicorn0.34.0непряма
Сповіщення про залежності 0

Встановлення pypi:easy-tdx@1.20.4 тягне 7 пакетів, прямих і транзитивних: 0 мають відомі сповіщення, з них 0 — прямі залежності.

Жодне відоме сповіщення не стосується оцінених залежностей.

Сповіщення означає, що версія, записана в графі залежностей, потрапляє в уражений діапазон. Досяжність не аналізується, а граф містить піниї розробки й тестування — знахідка може стосуватися інструментів, а не поставленого коду.

Звіт у форматі 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
}

Оцінки — це сигнали, а не гарантії. Вони відображають публічно видимі практики на GitHub — це не аудит коду й не гарантія безпеки.

Відсутні дані виключаються, а ваги перенормовуються — нуль за відсутність ніколи не ставиться. Методологія версіонована й відкрита: метрики v1.13.0, схема v0.27.0 — повна методологія · вікі метрик.

Як окремий результат виглядає на тлі всього реєстру: сукупна статистикаPyPI.