settfex 0.24.2__tar.gz → 0.26.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- settfex-0.26.0/.github/workflows/live-smoke.yml +81 -0
- {settfex-0.24.2 → settfex-0.26.0}/.pre-commit-config.yaml +4 -3
- {settfex-0.24.2 → settfex-0.26.0}/AGENTS.md +113 -9
- {settfex-0.24.2 → settfex-0.26.0}/CHANGELOG.md +222 -0
- {settfex-0.24.2 → settfex-0.26.0}/CLAUDE.md +76 -24
- {settfex-0.24.2 → settfex-0.26.0}/PKG-INFO +114 -98
- {settfex-0.24.2 → settfex-0.26.0}/README.md +113 -97
- settfex-0.26.0/docs/settfex/services/sec/executive_trades.md +257 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/sec/financial_report.md +34 -7
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/analyst_consensus.md +3 -2
- {settfex-0.24.2 → settfex-0.26.0}/examples/README.md +8 -0
- settfex-0.26.0/examples/sec/02_executive_trades.ipynb +235 -0
- {settfex-0.24.2 → settfex-0.26.0}/pyproject.toml +1 -1
- {settfex-0.24.2 → settfex-0.26.0}/settfex/__init__.py +25 -1
- settfex-0.26.0/settfex/deprecations.py +94 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/exceptions.py +149 -6
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/sec/__init__.py +27 -1
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/sec/constants.py +35 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/sec/download.py +127 -15
- settfex-0.26.0/settfex/services/sec/executive_trades.py +1553 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/sec/financial_report.py +83 -9
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/sec/utils.py +25 -9
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/list.py +71 -1
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/stock/analyst_consensus.py +96 -18
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/thaibma/history.py +17 -1
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/thaibma/yield_curve.py +16 -1
- {settfex-0.24.2 → settfex-0.26.0}/settfex/utils/data_fetcher.py +50 -11
- {settfex-0.24.2 → settfex-0.26.0}/settfex/utils/logging.py +16 -8
- {settfex-0.24.2 → settfex-0.26.0}/settfex/utils/parsing.py +132 -2
- settfex-0.26.0/tests/conftest.py +29 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/golden/api_surface.json +1344 -38
- settfex-0.26.0/tests/golden/model_contract/sec_executive_trade.json +88 -0
- settfex-0.26.0/tests/golden/model_contract/sec_executive_trade_report.json +62 -0
- settfex-0.26.0/tests/integration/conftest.py +56 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/integration/test_live_endpoints.py +44 -3
- settfex-0.26.0/tests/services/sec/derive_r59_fixtures.py +318 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/README.md +110 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592000082610_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592000092610_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592000102610_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592000142610_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592000152610_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592000392610_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592000452610_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592000522610_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592000532609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592000542609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592001242610_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592001262610_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592001352609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592001352610_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592001372609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592001432610_en.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592001432610_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592002622609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592002632609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592002772609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592002782609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592002882609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592002892609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592003112609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592003122609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592003452609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592003462609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592003632609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592003652609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592004042609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592004052609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592004192609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592004202609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592004532609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592004542609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592004802609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592004812609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592004992609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592005002609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592005172609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592005182609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/detail_592005512609_th.json +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/en_default_20261007.html +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/th_capped_rcv_20261002.html +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/th_default_20261007.html +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/th_edge_rows.html +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/th_empty.html +16 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/th_kcg_txn_202609.html +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/th_sep_pairs.html +1 -0
- settfex-0.26.0/tests/services/sec/fixtures_sec/r59/th_spali_txn_202609.html +1 -0
- settfex-0.26.0/tests/services/sec/test_download_evidence.py +272 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/test_download_result.py +4 -1
- settfex-0.26.0/tests/services/sec/test_executive_trades.py +1060 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/test_listing_transport.py +174 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/test_utils.py +23 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/stock/test_analyst_consensus.py +119 -1
- settfex-0.26.0/tests/services/set/test_listed_symbol.py +80 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/test_fault_matrix.py +27 -1
- settfex-0.26.0/tests/test_deprecations.py +147 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/test_documented_exceptions.py +24 -22
- {settfex-0.24.2 → settfex-0.26.0}/tests/test_model_contract.py +2 -0
- settfex-0.26.0/tests/test_not_found_taxonomy.py +90 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/test_public_api_surface.py +17 -1
- {settfex-0.24.2 → settfex-0.26.0}/tests/test_readme_links.py +72 -9
- {settfex-0.24.2 → settfex-0.26.0}/tests/test_typing_surface.py +1 -0
- settfex-0.26.0/tests/utils/test_blocked_error.py +250 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/utils/test_logging_neighbourliness.py +38 -0
- {settfex-0.24.2 → settfex-0.26.0}/uv.lock +61 -45
- settfex-0.24.2/tests/conftest.py +0 -1
- settfex-0.24.2/tests/services/sec/fixtures_sec/README.md +0 -36
- {settfex-0.24.2 → settfex-0.26.0}/.editorconfig +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/.github/dependabot.yml +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/.github/instructions/core-architectrual-principles.instructions.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/.github/instructions/documentation-standards.instructions.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/.github/instructions/file-organization.instructions.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/.github/instructions/git-commit.instructions.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/.github/instructions/python-dependency-management.instructions.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/.github/prompts/Coding.prompt.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/.github/prompts/Git-Commit-Reviewer.prompt.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/.github/prompts/Prompt-Engineer.prompt.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/.github/prompts/Python-Architect.prompt.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/.github/workflows/ci.yml +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/.github/workflows/dependency-drift.yml +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/.github/workflows/pre-commit.yml +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/.github/workflows/release.yml +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/.github/workflows/security.yml +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/.gitignore +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/.python-version +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/CODE_OF_CONDUCT.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/COMPREHENSIVE_AUDIT.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/CONTRIBUTING.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/LICENSE +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/RELEASING.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/SECURITY.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/guide/PYTHON_LIBRARY_BEST_PRACTICES.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/index.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/API_PROTECTION_NOTE.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/board_of_director.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/chart_quotation.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/corporate_action.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/dr_indicative_price.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/earnings_call.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/financial.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/highlight_data.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/holiday.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/index.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/info.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/latest_historical_trading.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/list.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/news.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/nvdr_holder.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/price_performance.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/profile_company.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/profile_dr.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/profile_stock.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/shareholder.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/set/trading_stat.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/tfex/list.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/tfex/trading_statistics.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/services/thaibma/yield_curve.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/utils/data_fetcher.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/settfex/utils/session_caching.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/solution/FINDINGS.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/solution/Performance Boost Session Caching Implementation.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/solution/SESSION_CACHE_SUMMARY.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/docs/solution/SOLUTION_100_PERCENT.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/sec/01_financial_report.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/01_stock_list.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/02_highlight_data.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/03_stock_profile.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/04_company_profile.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/05_corporate_action.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/06_shareholder.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/07_nvdr_holder.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/08_board_of_director.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/09_trading_statistics.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/10_price_performance.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/11_financial.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/12_earnings_call.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/13_chart_quotation.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/14_latest_historical_trading.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/15_market_index.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/16_news.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/17_holiday.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/18_dr_and_asset_type.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/19_analyst_consensus.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/20_stock_info.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/README.md +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/cpall_balance_sheet.csv +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/market_dashboard_20251005_173221.csv +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/market_dashboard_20251005_210737.csv +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/market_dashboard_20251006_091440.csv +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/thailand_banking_stocks.csv +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/thailand_set_main_board.csv +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/set/thailand_stock_universe.csv +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/tfex/01_series_list.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/tfex/02_trading_statistics.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/tfex/03_underlying_price.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/examples/thaibma/01_government_yield_curve.ipynb +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/py.typed +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/__init__.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/sec/company.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/sec/sec.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/__init__.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/asset_type.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/constants.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/earnings_call.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/holiday.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/index/__init__.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/index/chart_quotation.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/index/composition.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/index/index.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/index/info.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/index/list.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/index/utils.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/news.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/stock/__init__.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/stock/board_of_director.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/stock/chart_quotation.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/stock/corporate_action.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/stock/dr_indicative_price.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/stock/financial/__init__.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/stock/financial/financial.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/stock/highlight_data.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/stock/info.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/stock/latest_historical_trading.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/stock/nvdr_holder.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/stock/price_performance.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/stock/profile_company.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/stock/profile_dr.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/stock/profile_stock.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/stock/shareholder.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/stock/stock.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/stock/trading_stat.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/set/stock/utils.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/tfex/__init__.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/tfex/constants.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/tfex/list.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/tfex/trading_statistics.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/tfex/underlying_price.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/thaibma/__init__.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/thaibma/availability.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/thaibma/constants.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/thaibma/thaibma.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/services/thaibma/utils.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/utils/__init__.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/utils/http.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/utils/session_cache.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/utils/session_manager.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/settfex/utils/youtube_transcript.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/__init__.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/golden/model_contract/analyst_consensus.json +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/golden/model_contract/dr_profile.json +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/golden/model_contract/earnings_call_item.json +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/golden/model_contract/highlight_data.json +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/golden/model_contract/holiday_calendar.json +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/golden/model_contract/index_list_response.json +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/golden/model_contract/news_search_response.json +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/golden/model_contract/sec_company_match.json +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/golden/model_contract/stock_info.json +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/golden/model_contract/stock_list_response.json +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/golden/model_contract/tfex_series_list_response.json +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/golden/model_contract/tfex_trading_statistics.json +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/golden/model_contract/tfex_underlying_price.json +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/golden/model_contract/yield_curve.json +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/integration/__init__.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/__init__.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/__init__.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/derive_th_fixtures.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/fixtures.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/fixtures_sec/capital_indirection_en.html +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/fixtures_sec/en_mother_fs_h1_2025.html +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/fixtures_sec/en_ptt_56_1.html +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/fixtures_sec/en_ptt_56_2.html +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/fixtures_sec/en_ptt_fs_h1_2025.html +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/fixtures_sec/en_ptt_viewmore_56_2.html +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/fixtures_sec/idisc_505_error_page.html +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/fixtures_sec/th_cpall_viewmore_56_1.html +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/fixtures_sec/th_mother_fs_h1_2025.html +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/fixtures_sec/th_ptt_56_1.html +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/fixtures_sec/th_ptt_56_2.html +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/fixtures_sec/th_ptt_fs_h1_2025.html +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/test_code_isolation.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/test_company.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/test_download.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/test_download_indirection.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/test_financial_report.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/test_financial_report_accounting.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/test_financial_report_th.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/test_sec.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/sec/test_view_more_slugs.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/__init__.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/index/__init__.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/index/test_chart_quotation.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/index/test_composition.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/index/test_index.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/index/test_info.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/index/test_list.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/stock/__init__.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/stock/financial/__init__.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/stock/financial/test_financial.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/stock/test_chart_quotation.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/stock/test_dr_indicative_price.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/stock/test_info.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/stock/test_latest_historical_trading.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/stock/test_profile_dr.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/test_asset_type.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/test_board_of_director.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/test_client.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/test_corporate_action.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/test_earnings_call.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/test_historical.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/test_holiday.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/test_list.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/test_news.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/test_profile_company.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/test_realtime.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/test_shareholder.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/set/test_symbol_suggestion.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/tfex/__init__.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/tfex/test_client.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/tfex/test_historical.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/tfex/test_list.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/tfex/test_realtime.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/tfex/test_trading_statistics.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/tfex/test_underlying_price.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/thaibma/__init__.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/thaibma/fixtures.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/thaibma/test_availability.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/thaibma/test_history.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/thaibma/test_thaibma.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/thaibma/test_utils.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/services/thaibma/test_yield_curve.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/test_impersonate_target.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/utils/__init__.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/utils/test_data_fetcher.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/utils/test_formatting.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/utils/test_http.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/utils/test_logging.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/utils/test_parsing.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/utils/test_session_cache.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/utils/test_session_manager.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/utils/test_validation.py +0 -0
- {settfex-0.24.2 → settfex-0.26.0}/tests/utils/test_youtube_transcript.py +0 -0
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# Live smoke — the integration probes against the real SET / TFEX / SEC / ThaiBMA hosts, weekly.
|
|
2
|
+
#
|
|
3
|
+
# INERT BY DESIGN. Nothing runs until BOTH of these are true:
|
|
4
|
+
# 1. a self-hosted runner labelled `thailand` is registered for this repository, and
|
|
5
|
+
# 2. the repository variable LIVE_SMOKE_ENABLED is set to `true`.
|
|
6
|
+
# Until then the job is skipped on every trigger, and no request leaves any machine.
|
|
7
|
+
#
|
|
8
|
+
# Why self-hosted, and why in Thailand: nothing ran the `integration` marker, so the live endpoints
|
|
9
|
+
# had no pass/fail history (#135). GitHub-hosted runners sit outside Thailand, and SET's and SEC's
|
|
10
|
+
# bot protection treats datacenter IPs differently from a Thai residential one, so a green run
|
|
11
|
+
# there would not mean what it appears to mean.
|
|
12
|
+
#
|
|
13
|
+
# BEFORE ENABLING — read these, they are the reason this ships switched off:
|
|
14
|
+
# * Every machine on the home network shares ONE egress IP with production SET market capture,
|
|
15
|
+
# which cannot be backfilled. A WAF block on that IP would hit the capture too. Pick a window
|
|
16
|
+
# that never overlaps a SET session or the sec-idisc-archive's traffic; the schedule below is
|
|
17
|
+
# a Sunday-morning placeholder, not a verified quiet window.
|
|
18
|
+
# * This repository is PUBLIC. A self-hosted runner on a public repo can be made to run code
|
|
19
|
+
# from a pull request. Before registering one, set Settings → Actions → "Fork pull request
|
|
20
|
+
# workflows" approval to ALL outside contributors (it is `first_time_contributors` today).
|
|
21
|
+
# This workflow itself runs only on schedule and manual dispatch, never on pull_request.
|
|
22
|
+
#
|
|
23
|
+
# Politeness, enforced rather than remembered (CLAUDE.md, "Live probes — politeness budget"):
|
|
24
|
+
# * concurrency 1, and `-x`: the first failure may be a WAF block, and the next request deepens it
|
|
25
|
+
# * SETTFEX_LIVE_BUDGET caps request ATTEMPTS through the fetcher (tests/integration/conftest.py);
|
|
26
|
+
# the cap refuses the next request rather than sending it. Session warmups (one page view per
|
|
27
|
+
# site) are not counted.
|
|
28
|
+
# * no retry ladders: the live tests make one attempt each
|
|
29
|
+
#
|
|
30
|
+
# The holiday endpoint has answered 401 upstream since 2026-09-20 (#140), so it runs as a SIGNAL in
|
|
31
|
+
# its own step (continue-on-error) — it would otherwise keep the gate red for a reason no settfex
|
|
32
|
+
# change can fix.
|
|
33
|
+
|
|
34
|
+
name: Live smoke
|
|
35
|
+
|
|
36
|
+
on:
|
|
37
|
+
schedule:
|
|
38
|
+
- cron: "17 3 * * 0" # Sundays 03:17 UTC (10:17 Asia/Bangkok) — placeholder, see above
|
|
39
|
+
workflow_dispatch:
|
|
40
|
+
|
|
41
|
+
permissions:
|
|
42
|
+
contents: read
|
|
43
|
+
|
|
44
|
+
concurrency:
|
|
45
|
+
group: live-smoke
|
|
46
|
+
cancel-in-progress: false
|
|
47
|
+
|
|
48
|
+
jobs:
|
|
49
|
+
live:
|
|
50
|
+
name: Live endpoints (Thailand runner)
|
|
51
|
+
if: vars.LIVE_SMOKE_ENABLED == 'true' && github.repository == 'lumduan/settfex'
|
|
52
|
+
runs-on: [self-hosted, linux, thailand]
|
|
53
|
+
timeout-minutes: 20
|
|
54
|
+
steps:
|
|
55
|
+
- uses: actions/checkout@v7
|
|
56
|
+
|
|
57
|
+
# setup-uv stopped publishing floating major tags at v8 — pin the exact version.
|
|
58
|
+
- uses: astral-sh/setup-uv@v10.1.0
|
|
59
|
+
with:
|
|
60
|
+
# Pinned by hand like every other setup-uv step; the monthly drift report checks them all.
|
|
61
|
+
version: "0.12.17"
|
|
62
|
+
enable-cache: true
|
|
63
|
+
cache-dependency-glob: "uv.lock"
|
|
64
|
+
|
|
65
|
+
- run: uv python install 3.11
|
|
66
|
+
- run: uv sync --group dev --frozen
|
|
67
|
+
|
|
68
|
+
- name: Live probes (gate)
|
|
69
|
+
env:
|
|
70
|
+
SETTFEX_LIVE_BUDGET: "60"
|
|
71
|
+
run: >-
|
|
72
|
+
uv run pytest tests/integration -m integration --no-cov -x
|
|
73
|
+
--deselect tests/integration/test_live_endpoints.py::test_live_set_holidays
|
|
74
|
+
|
|
75
|
+
- name: Holiday endpoint (signal, not a gate — #140)
|
|
76
|
+
continue-on-error: true
|
|
77
|
+
env:
|
|
78
|
+
SETTFEX_LIVE_BUDGET: "1"
|
|
79
|
+
run: >-
|
|
80
|
+
uv run pytest -m integration --no-cov
|
|
81
|
+
tests/integration/test_live_endpoints.py::test_live_set_holidays
|
|
@@ -21,11 +21,12 @@ repos:
|
|
|
21
21
|
# The SEC listing fixtures are response bodies captured VERBATIM -- byte ranges of what the
|
|
22
22
|
# server sent, with their sha256 recorded in fixtures_sec/README.md. These two hooks rewrite
|
|
23
23
|
# a file's trailing bytes, which would break both the byte-exactness and those hashes. The
|
|
24
|
-
# live pages genuinely end without a newline; that is data, not untidiness.
|
|
24
|
+
# live pages genuinely end without a newline; that is data, not untidiness. The Form 59
|
|
25
|
+
# detail fixtures (r59/*.json) are verbatim API bodies on the same terms.
|
|
25
26
|
- id: trailing-whitespace
|
|
26
|
-
exclude: ^tests/services/sec/fixtures_sec/.*\.html$
|
|
27
|
+
exclude: ^tests/services/sec/fixtures_sec/.*\.(html|json)$
|
|
27
28
|
- id: end-of-file-fixer
|
|
28
|
-
exclude: ^tests/services/sec/fixtures_sec/.*\.html$
|
|
29
|
+
exclude: ^tests/services/sec/fixtures_sec/.*\.(html|json)$
|
|
29
30
|
- id: check-yaml
|
|
30
31
|
- id: check-toml
|
|
31
32
|
- id: check-added-large-files
|
|
@@ -110,6 +110,8 @@ Indices: `get_index_list(lang)`, `get_index_info(symbol, lang)`,
|
|
|
110
110
|
| resolve a company to its SEC id | `resolve_company(query, lang, allow_name_match=False)` |
|
|
111
111
|
| list filings (financial statements, 56-1, 56-2, ratios, MD&A) | `get_sec_documents(query, types=..., from_date=..., to_date=...)` |
|
|
112
112
|
| download the actual file(s) | `download_sec_document(target)` / `download_sec_documents(targets)` |
|
|
113
|
+
| directors'/executives' trades (Form 59, แบบ 59) | `get_executive_trades(received_date=None, symbol=..., date_type=..., start=..., end=..., with_details=False)` |
|
|
114
|
+
| one Form 59 report with holdings before/after | `get_executive_trade_report(batch_no)` |
|
|
113
115
|
|
|
114
116
|
Dates here are **dd/mm/yyyy**. Pass a wide window to see full year history.
|
|
115
117
|
|
|
@@ -185,6 +187,69 @@ Also a Pydantic model since 0.24.0 (`{files, failed, requested}` + computed `is_
|
|
|
185
187
|
failure report survives `model_dump()` and slicing. ⚠️ `isinstance(files, list)` is `False`; use
|
|
186
188
|
`files.files`.
|
|
187
189
|
|
|
190
|
+
**Form 59 (executive trades) — four traps before you count anything:**
|
|
191
|
+
|
|
192
|
+
- **`reporter_name` is who FILED, not who traded.** The trader is `executor_name` / `executor_id`
|
|
193
|
+
(a spouse, a minor child, a family company). `is_self` says whether they are the same.
|
|
194
|
+
- **One trade can be listed twice**: by both spouses when both are executives, or by two executives
|
|
195
|
+
for one company they control. The copy has `duplicate_of`; `duplicate_basis` says why
|
|
196
|
+
(`holdings` / `executor_own_row` / `executor`). `holding_conflict` is True when the two filings
|
|
197
|
+
disagree on the holdings, False when both are known and agree, and **None when unknown** (a
|
|
198
|
+
report was not fetched) — never read None as "no conflict". Rows are never dropped.
|
|
199
|
+
- **Revoked filings stay listed** (`is_revoked=True`, about 5% of rows, mostly re-filings); with
|
|
200
|
+
`with_details=True` a transaction its report marks `CANCELED` is void too.
|
|
201
|
+
- So **count on `result.economic_trades()`**, which drops copies and void rows. `to_table()`
|
|
202
|
+
already skips void rows. **Known overcount:** a trade the same reporter filed twice in different
|
|
203
|
+
batches, with the first left live, is counted twice (duplicates are never merged within one
|
|
204
|
+
reporter). Example: CREDIT 2026-09-28, 10,000 shares, rows 165104_2_1 and 165237_3_1.
|
|
205
|
+
|
|
206
|
+
With no arguments `get_executive_trades()` returns what SEC **received today**, which is not the
|
|
207
|
+
site's default page (that one shows what was *recorded* today), and today's set can still grow.
|
|
208
|
+
The listing `price` is rounded to 2 dp; `with_details=True` (at most 100 reports per call) adds
|
|
209
|
+
`detail.avg_price` at full precision and the holdings. A page whose stated count differs from its
|
|
210
|
+
rows raises `ParseError`, so a result you receive is complete. A method label the library does not
|
|
211
|
+
know shows `side="other"` and is counted on `result.unknown_labels`.
|
|
212
|
+
|
|
213
|
+
**Form 59 recipes — question → call → what to read:**
|
|
214
|
+
|
|
215
|
+
| Question | Call | Read |
|
|
216
|
+
|---|---|---|
|
|
217
|
+
| Which executives reported trades today? | `get_executive_trades()` | `.economic_trades()`; `.to_table()` for a ready Thai summary |
|
|
218
|
+
| Did insiders of X buy or sell recently? | `get_executive_trades(symbol="X", date_type="transaction", start="YYYY-MM-DD", end="YYYY-MM-DD")` | `side` and `quantity` of each row in `.economic_trades()` |
|
|
219
|
+
| What did the executive hold after the trade? | add `with_details=True`, or `get_executive_trade_report(row.batch_no)` | `row.detail.holding_before` / `holding_after` |
|
|
220
|
+
| The exact average price | `with_details=True` | `row.detail.avg_price` (the listing's `price` is rounded to 2 dp) |
|
|
221
|
+
| On-exchange or off? Who received a transfer? | `with_details=True` | `row.detail.market_source`, `row.detail.counterparty` |
|
|
222
|
+
|
|
223
|
+
Dates here are **ISO `YYYY-MM-DD`** strings or `date` objects. This is unlike `get_sec_documents`,
|
|
224
|
+
which takes dd/mm/yyyy. Keep windows to what the question needs: anything over 366 days is fetched
|
|
225
|
+
in several requests, and `with_details=True` sends one request per report, at most 100.
|
|
226
|
+
|
|
227
|
+
```python
|
|
228
|
+
from settfex.services.sec import get_executive_trades
|
|
229
|
+
|
|
230
|
+
result = await get_executive_trades(symbol="PANEL", date_type="transaction",
|
|
231
|
+
start="2026-09-01", end="2026-09-30")
|
|
232
|
+
trades = result.economic_trades() # copies and revoked/cancelled filings removed
|
|
233
|
+
bought = sum(t.quantity for t in trades if t.side == "buy" and t.security_type == "หุ้นสามัญ")
|
|
234
|
+
sold = sum(t.quantity for t in trades if t.side == "sell" and t.security_type == "หุ้นสามัญ")
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
Before you state a number from Form 59:
|
|
238
|
+
- **Count only `economic_trades()`.**
|
|
239
|
+
- **Never add quantities across security types**: shares, warrants and NVDRs are different
|
|
240
|
+
instruments. Filter on `security_type`.
|
|
241
|
+
- **`transfer_in` / `transfer_out` are not market trades** (gifts to children, depositary moves).
|
|
242
|
+
Report them separately.
|
|
243
|
+
- **Name the window and the date type** (received vs transaction), and say the count may include
|
|
244
|
+
the known same-reporter re-filing overcount.
|
|
245
|
+
- **Read `holding_conflict is None` as "unknown"**, not as "no conflict".
|
|
246
|
+
- **With `with_details=True`, check `result.detail_failures` is empty** before relying on
|
|
247
|
+
holdings. Rows of a failed report keep `detail=None`.
|
|
248
|
+
|
|
249
|
+
As JSON (`result.model_dump(mode="json")`), the completeness fields travel with the rows:
|
|
250
|
+
`reported_count`, `revoked_count`, `duplicate_count`, `unknown_labels`, `detail_unmatched` and
|
|
251
|
+
`detail_failures`.
|
|
252
|
+
|
|
188
253
|
### ThaiBMA bonds — `from settfex.services.thaibma import ...`
|
|
189
254
|
|
|
190
255
|
| I want… | Call |
|
|
@@ -226,9 +291,9 @@ being wrong, so none of them will announce itself.
|
|
|
226
291
|
|
|
227
292
|
| Behaviour | What you must do |
|
|
228
293
|
|---|---|
|
|
229
|
-
| **Analyst consensus answers an uncovered symbol with HTTP 500, not 404** — and "uncovered" includes perfectly valid SET stocks (`ABICO`), DRs (`GOOG80`) and warrants (`JAS-W4`) | catch `FetchError
|
|
294
|
+
| **Analyst consensus answers an uncovered symbol with HTTP 500, not 404** — and "uncovered" includes perfectly valid SET stocks (`ABICO`), DRs (`GOOG80`) and warrants (`JAS-W4`) | catch **`NotFoundError` first**: since 0.25.0 a symbol that is not listed on SET raises `SymbolNotFoundError` (with `.suggestion`). What still arrives as a plain `FetchError(500)` is a listed symbol with no coverage — report "no analyst coverage", and **do not** retry it as a typo |
|
|
230
295
|
| **The DR-profile endpoint 404s for every non-DR symbol**, including `CPALL` | a 404 here means "not a DR", not "unknown symbol" |
|
|
231
|
-
| **The consensus summary
|
|
296
|
+
| **The consensus summary cannot tell "unknown" from "uncovered" by itself**: both are HTTP 200 with `overall: []`. Since 0.26.0 an unlisted symbol raises `SymbolNotFoundError` | catch `NotFoundError` (do not retry); `count == 0` now means a listed symbol with no analyst coverage |
|
|
232
297
|
| **The holiday endpoint returns HTTP 401 for any year but the current one** — and transiently on valid requests too | do not treat 401 as auth failure; there is no auth |
|
|
233
298
|
|
|
234
299
|
### Values that are placeholders, not data
|
|
@@ -273,10 +338,14 @@ Everything is importable from one place:
|
|
|
273
338
|
|
|
274
339
|
```python
|
|
275
340
|
from settfex.exceptions import (
|
|
341
|
+
# --- the cross-cutting catch: a name that does not exist (0.25.0) ---
|
|
342
|
+
NotFoundError, # base of SymbolNotFoundError AND CompanyNotFoundError; never retry
|
|
276
343
|
# --- the FETCH family: `except FetchError` catches all of these ---
|
|
277
344
|
FetchError, # HTTP/transport failure; carries .status_code and .symbol
|
|
278
345
|
HTTPStatusError, # a non-2xx, with .url and .report_code as data
|
|
279
|
-
|
|
346
|
+
UnexpectedPageError, # a PAGE where a document was expected (0.26.0); carries the answer
|
|
347
|
+
SoftNotFoundError, # its "file not found" page — can be FALSE; NOT a NotFoundError
|
|
348
|
+
SymbolNotFoundError, # SET 404, or an unlisted analyst-consensus symbol; .suggestion
|
|
280
349
|
StaleDataError, # ThaiBMA rolled back and you asked it to raise
|
|
281
350
|
ParseError, # a response arrived intact and could not be mapped
|
|
282
351
|
IncompleteListingError, # a ParseError: rows classified, then every one was lost
|
|
@@ -287,7 +356,10 @@ from settfex.exceptions import (
|
|
|
287
356
|
InvalidLanguageError, # unrecognized lang
|
|
288
357
|
InvalidDateError, # malformed date string — raised before any request
|
|
289
358
|
)
|
|
290
|
-
from settfex.utils.parsing import
|
|
359
|
+
from settfex.utils.parsing import (
|
|
360
|
+
ResponseParseError, # also a ParseError since 0.24.0
|
|
361
|
+
BlockedError, # a WAF block page (0.25.0): stop, never retry; an UnexpectedPageError
|
|
362
|
+
)
|
|
291
363
|
```
|
|
292
364
|
|
|
293
365
|
**The split is the thing to internalise: a `FetchError` may be worth retrying; a `ValueError`
|
|
@@ -295,12 +367,44 @@ never is.** An input error means the request *succeeded* and your argument was w
|
|
|
295
367
|
loop on one spins forever. `InvalidSymbolError`, `InvalidLanguageError` and `InvalidDateError` are
|
|
296
368
|
raised before any network call at all.
|
|
297
369
|
|
|
298
|
-
⚠️ Two "not found" taxonomies coexist
|
|
299
|
-
while the SEC equivalent is a `CompanyNotFoundError` (a `ValueError`).
|
|
300
|
-
both
|
|
370
|
+
⚠️ Two "not found" taxonomies still coexist: a SET 404 is a `SymbolNotFoundError` (a
|
|
371
|
+
`FetchError`), while the SEC equivalent is a `CompanyNotFoundError` (a `ValueError`). Since 0.25.0
|
|
372
|
+
both are also a **`NotFoundError`** — catch that one, and catch it **before** `FetchError`, because
|
|
373
|
+
a `SymbolNotFoundError` is a `FetchError` too and a retry handler would otherwise retry a typo
|
|
374
|
+
forever:
|
|
375
|
+
|
|
376
|
+
```python
|
|
377
|
+
try:
|
|
378
|
+
data = await get_highlight_data(symbol)
|
|
379
|
+
except NotFoundError:
|
|
380
|
+
... # the name does not exist: fix the input, never retry
|
|
381
|
+
except FetchError:
|
|
382
|
+
... # upstream or transient: a retry may help
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
Neither class moved family; taking `SymbolNotFoundError` out of `FetchError` would break existing
|
|
386
|
+
handlers, so that is a 1.0 decision, tracked on #135.
|
|
387
|
+
|
|
388
|
+
⚠️ A **WAF block page** raises **`BlockedError`** (0.25.0) — **stop and back off; never retry**,
|
|
389
|
+
because every further request deepens the block. It is a `ResponseParseError` subclass so older
|
|
390
|
+
handlers still catch it, which also makes it a `ValueError`: catch `BlockedError` **before**
|
|
391
|
+
`ValueError` if you treat `ValueError` as bad input. Only SEC's block page is recognised; a SET
|
|
392
|
+
block may still arrive as a plain `ResponseParseError` — if one keeps repeating from one host,
|
|
393
|
+
treat it the same way.
|
|
394
|
+
|
|
395
|
+
**A download that answers with a page, not a file, says which page it was (0.26.0).** All three are
|
|
396
|
+
`UnexpectedPageError`s and carry the answer — `.status_code`, `.headers` (credentials removed),
|
|
397
|
+
`.body` (the first 8 KB), `.elapsed_seconds` — so you can decide from evidence, not from message
|
|
398
|
+
text:
|
|
399
|
+
|
|
400
|
+
| You caught | It means | Do |
|
|
401
|
+
|---|---|---|
|
|
402
|
+
| `BlockedError` | the WAF refused you | stop; back off for a long time; never retry soon |
|
|
403
|
+
| `SoftNotFoundError` | the host's "file not found" page | probably gone — but it has been **wrong** (sixty in a row, each slow, for files that existed). If it was slow (`.elapsed_seconds`) or many arrive together, retry later before recording it as gone. `.matched_marker` says which rule matched |
|
|
404
|
+
| `UnexpectedPageError` (itself) | any other page, e.g. an upstream error page | the source is degraded; try again later |
|
|
301
405
|
|
|
302
|
-
|
|
303
|
-
|
|
406
|
+
`download_all` keeps the kind on each failure: `FailedDownload.error_type` names the class and
|
|
407
|
+
`.status_code` the status.
|
|
304
408
|
|
|
305
409
|
---
|
|
306
410
|
|
|
@@ -7,6 +7,228 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
7
7
|
|
|
8
8
|
## [Unreleased]
|
|
9
9
|
|
|
10
|
+
## [0.26.0] - 2026-10-08
|
|
11
|
+
|
|
12
|
+
**SEC Form 59 arrives: directors' and executives' trades, with the report behind each one.**
|
|
13
|
+
- **Downloads:** a download that answers with a page instead of a file now says which page it
|
|
14
|
+
was and carries the answer.
|
|
15
|
+
- **Analyst consensus:** the deprecation announced in 0.25.0 makes its change. An unlisted symbol
|
|
16
|
+
on the consensus summary raises.
|
|
17
|
+
- **Fixed:** a Thai SEC date on a leap day is no longer lost.
|
|
18
|
+
|
|
19
|
+
Nothing is removed from the public API. One behaviour change completes its warning period (see
|
|
20
|
+
**Changed**).
|
|
21
|
+
|
|
22
|
+
### Added
|
|
23
|
+
|
|
24
|
+
- **SEC Form 59 (แบบ 59): directors' and executives' trades** (`settfex.services.sec`, top-level
|
|
25
|
+
`get_executive_trades` / `get_executive_trade_report`). Three tiers for the listing
|
|
26
|
+
(`get_executive_trades`, `fetch_executive_trades`, `fetch_executive_trades_raw`) and three for the
|
|
27
|
+
report behind each row (`get_executive_trade_report`, `fetch_executive_trade_report`, `…_raw`).
|
|
28
|
+
- **Who traded vs who filed:** `reporter_name` vs `executor_name`/`executor_id`, with `is_self`.
|
|
29
|
+
- **Duplicates are marked, not dropped** (`duplicate_of`, `duplicate_basis`). A row is a copy
|
|
30
|
+
when its report's holdings match another's, or when the same trader appears under a different
|
|
31
|
+
reporter with the same trade (`executor_own_row`: the trader filed the trade themselves, the
|
|
32
|
+
spouse case; `executor`: e.g. one company reported by two executives). Rows from the same
|
|
33
|
+
reporter are never merged that way, so a same-reporter re-filing is counted twice (documented).
|
|
34
|
+
`holding_conflict` (`bool | None`) flags a trader-rule merge whose holdings disagree; it is
|
|
35
|
+
`None` when a report was not fetched. All 28 September 2026 pairs are pinned.
|
|
36
|
+
- **Revoked filings are kept and marked** (`is_revoked`), never counted; a transaction the report
|
|
37
|
+
marks `CANCELED` is void too. `economic_trades()` drops copies and void rows.
|
|
38
|
+
- **No silent truncation:** the search page shows at most 100 rows, so the service reads the
|
|
39
|
+
site's "display all results" page, and every response's stated count must equal the rows parsed,
|
|
40
|
+
or `ParseError`. The URL builder refuses to build that page without dates: undated, it is the
|
|
41
|
+
whole database (70 MB).
|
|
42
|
+
- **Fail-loud cells:** an unknown layout, a bracketed amount or an unparseable number or date
|
|
43
|
+
raises. Real absences (`-` price, an empty date, the 19% of history without a link) come back as
|
|
44
|
+
`None`.
|
|
45
|
+
- **`with_details=True`** adds holdings before and after, the unrounded average price, the trading
|
|
46
|
+
channel and broker, the counterparty and `holding_consistent`. That is one request per distinct
|
|
47
|
+
report, at most 100 per call. A failed report is recorded on `detail_failures`; a block page
|
|
48
|
+
stops everything.
|
|
49
|
+
- **Output:** `to_table()` renders the agreed Thai summary; `to_dataframe()` needs the
|
|
50
|
+
`dataframe` extra.
|
|
51
|
+
- Docs: `docs/settfex/services/sec/executive_trades.md`.
|
|
52
|
+
- **A download that answers with a page instead of a file says which page it was, and carries
|
|
53
|
+
the answer** (#135). Three `FetchError` subclasses, with the messages 0.25.0 raised as plain
|
|
54
|
+
`FetchError`s, so every existing `except` keeps catching:
|
|
55
|
+
- `UnexpectedPageError` — any HTML where a document was expected, including an HTML error page
|
|
56
|
+
under a non-200 status. The common base of the other two.
|
|
57
|
+
- `SoftNotFoundError` — the host's "file not found" page, with `.matched_marker`
|
|
58
|
+
(`"sec-thai"` / `"generic-not-found"`). **Deliberately not a `NotFoundError`**: it has been
|
|
59
|
+
wrong (below).
|
|
60
|
+
- `BlockedError` gains `UnexpectedPageError` as a second base (after its 0.25.0 ones), and the
|
|
61
|
+
SEC download path now recognises the block page under **any** status, not only 2xx. JSON
|
|
62
|
+
endpoints still report a non-2xx as `HTTPStatusError`.
|
|
63
|
+
Each carries `url`, `final_url`, `status_code`, `content_type`, `headers` (without
|
|
64
|
+
`Set-Cookie`, `Cookie`, `Authorization`, `Proxy-Authorization`), `body` — the first 8,192 bytes,
|
|
65
|
+
support ID screened, the block detector's own cap — with `body_truncated`, and
|
|
66
|
+
`elapsed_seconds`.
|
|
67
|
+
- `DownloadedFile.elapsed_seconds` and `FailedDownload.status_code`.
|
|
68
|
+
- `SOFT_404_MARKERS` and `SOFT_404_WINDOW_BYTES` in `settfex.services.sec.constants`: the
|
|
69
|
+
soft-404 rule, documented so a caller can reason about it.
|
|
70
|
+
|
|
71
|
+
**Why:** a downstream consumer saw four non-document HTML answers in a row (HTTP 200,
|
|
72
|
+
`text/html`) and could not tell a WAF block page — back off for days — from an upstream error
|
|
73
|
+
page — retry the next night: the body was discarded before raising. The same consumer saw sixty
|
|
74
|
+
consecutive soft-404s, each ~38 s instead of ~3 s, for files that all downloaded the next
|
|
75
|
+
evening; the rule matches the SEC's Thai text **or any "not found"** in the first 400 bytes.
|
|
76
|
+
|
|
77
|
+
### Changed
|
|
78
|
+
|
|
79
|
+
- **`get_consensus_overall()` / `fetch_overall()` raise `SymbolNotFoundError` for a symbol that is
|
|
80
|
+
not listed on SET.** Settrade answers that symbol, and a listed symbol with no analyst coverage,
|
|
81
|
+
with the same empty summary under HTTP 200; the SET stock list tells them apart.
|
|
82
|
+
- 0.25.0 warned (`DeprecationWarning`, registry id `consensus-overall-unknown-symbol`) and
|
|
83
|
+
returned the empty summary.
|
|
84
|
+
- The error carries `status_code=200`, the normalized `symbol` and a `suggestion`. It is a
|
|
85
|
+
`NotFoundError`, so catch `NotFoundError` and do not retry.
|
|
86
|
+
- `count == 0` now means a listed symbol without coverage.
|
|
87
|
+
- The whole-market call (`symbol=None`) is unchanged.
|
|
88
|
+
- `FailedDownload.error_type` names the new subclass (`"SoftNotFoundError"`,
|
|
89
|
+
`"UnexpectedPageError"`, `"BlockedError"`) where it said `"FetchError"`. The message is
|
|
90
|
+
unchanged; a filter on the class-name **string** needs updating.
|
|
91
|
+
|
|
92
|
+
### Removed
|
|
93
|
+
|
|
94
|
+
- The deprecation registry entry `consensus-overall-unknown-symbol`, its change made (above). The
|
|
95
|
+
registry (`settfex.deprecations.DEPRECATIONS`) is empty.
|
|
96
|
+
- The README's "Upgrading to 0.24" notice, kept for two minor releases as planned. The permanent
|
|
97
|
+
copy is the 0.24.0 **Data completeness advisory** below, still linked from the README.
|
|
98
|
+
|
|
99
|
+
### Fixed
|
|
100
|
+
|
|
101
|
+
- **A Thai SEC date on a leap day was lost** (`settfex.services.sec.utils.parse_dmy_date`). The
|
|
102
|
+
Buddhist-era conversion ran *after* the date was built in the B.E. year, and 543 is 3 mod 4, so
|
|
103
|
+
a B.E. year is a Gregorian leap year exactly when its C.E. year is not. `29/02/2567`, the real
|
|
104
|
+
2024-02-29 (a Thursday), was rejected as "day out of range" and came back `None`: a Thai SEC
|
|
105
|
+
listing row received or dated that day had `receive_date` / `as_of` silently empty. The
|
|
106
|
+
impossible `29/02/2568` was accepted in 2568 and then escaped as a bare `ValueError`. The year is
|
|
107
|
+
now converted first. Malformed or impossible input still returns `None`, as documented: nothing
|
|
108
|
+
that parsed before parses differently, except the leap days that used to be lost. The Form 59
|
|
109
|
+
listing alone carries 92 such rows (55 on 29/02/2567, 37 on 29/02/2559).
|
|
110
|
+
|
|
111
|
+
### Known issues
|
|
112
|
+
|
|
113
|
+
- **The SEC's Thai listing page prints a 29 February filing as 28 February** (upstream). A Thai
|
|
114
|
+
B.E. year is never a Gregorian leap year, and the page appears to clamp the day. 103 Thai MD&A
|
|
115
|
+
rows from 2024-02-29 carry 2024-02-28, and the FILEID shows the true date. settfex parses the
|
|
116
|
+
page faithfully. A cross-check is proposed in #153.
|
|
117
|
+
- **Form 59: a trade one reporter filed twice is counted twice** by `economic_trades()`. Proposal:
|
|
118
|
+
#154.
|
|
119
|
+
|
|
120
|
+
## [0.25.0] - 2026-09-23
|
|
121
|
+
|
|
122
|
+
The seven items the 0.25.0 backlog on #135 held, plus two defects the 2026-09-23 checklist audit on
|
|
123
|
+
#135 found on the way. Nothing is removed and no exception leaves its family; one behaviour starts
|
|
124
|
+
its deprecation period (see **Deprecated**).
|
|
125
|
+
|
|
126
|
+
### Added
|
|
127
|
+
|
|
128
|
+
- **`NotFoundError`** (`settfex.exceptions`, also top-level): one catch for "that name does not
|
|
129
|
+
exist". `SymbolNotFoundError` (a `FetchError`) and `CompanyNotFoundError` (a `ValueError`) are
|
|
130
|
+
both `NotFoundError`s now, **without moving family** — every handler written before keeps
|
|
131
|
+
working. Catch it *before* `FetchError`: a retry handler written as `except FetchError` used to
|
|
132
|
+
retry a SET typo forever. Moving `SymbolNotFoundError` out of `FetchError` would break existing
|
|
133
|
+
handlers, so that stays a 1.0 decision (#135).
|
|
134
|
+
- **`BlockedError`** (`settfex.utils.parsing`): a bot-protection block page is now named for what
|
|
135
|
+
it is. The fetcher recognises SEC's BIG-IP ASM page (`<title>Request Rejected</title>` plus
|
|
136
|
+
`Your support ID is:`) on the first 2xx that carries it and raises **without retrying** —
|
|
137
|
+
retrying deepens a block. It carries `url`, `status_code`, `content_type`, the headers minus
|
|
138
|
+
`Set-Cookie`, and the page itself with its support ID screened (kept out of `str(exc)`), so the
|
|
139
|
+
next real block yields the byte-exact fixture the synthetic test page stands in for. A
|
|
140
|
+
`ResponseParseError` subclass, so every handler that caught a block page before still does —
|
|
141
|
+
which also makes it a `ValueError`: **catch `BlockedError` before `ValueError`**. Only the SEC
|
|
142
|
+
page is recognised; no SET/Incapsula block page has ever been captured. Built from the recorded
|
|
143
|
+
markers with **zero live requests** (a single polite request cannot reproduce a block page, and
|
|
144
|
+
tripping the WAF on purpose would block the IP other workloads share).
|
|
145
|
+
- **Batches stop at the first block.** `download_all`, ThaiBMA `fetch_curves` and ThaiBMA history
|
|
146
|
+
send nothing more once a request comes back blocked. Items not yet requested are recorded as
|
|
147
|
+
failures — `FailedDownload(error_type="BlockedError", error="not requested: …")`, or a year on
|
|
148
|
+
`missing_years` — so the return shape is unchanged.
|
|
149
|
+
- **`setup_logger(filter=None)`**: passed to both sinks as loguru's own `filter`.
|
|
150
|
+
`filter="settfex"` gives settfex-only sinks, which is what an application that also logs through
|
|
151
|
+
loguru wants (#146 follow-up). The default stays unfiltered, as documented in 0.24.2.
|
|
152
|
+
- **`settfex.deprecations`**: the registry every `DeprecationWarning` now goes through
|
|
153
|
+
(`DEPRECATIONS`, `warn_deprecated`). The L1 golden records it by content (format 2), and
|
|
154
|
+
`tests/test_deprecations.py` triggers every entry, bans any other `warnings.warn` in the
|
|
155
|
+
package, and fails once an entry's `changes_in` release arrives. The warning names the caller's
|
|
156
|
+
line, not settfex's.
|
|
157
|
+
- **`.github/workflows/live-smoke.yml`** — a weekly run of the integration probes on a Thailand
|
|
158
|
+
self-hosted runner, **inert**: nothing runs until a runner labelled `thailand` is registered and
|
|
159
|
+
the repository variable `LIVE_SMOKE_ENABLED` is `true`. Its header states the two reasons it
|
|
160
|
+
ships switched off (the home egress IP is shared with production capture; the repository is
|
|
161
|
+
public). With it, `SETTFEX_LIVE_BUDGET=N` caps request attempts in any live run
|
|
162
|
+
(`tests/integration/conftest.py`).
|
|
163
|
+
|
|
164
|
+
### Changed
|
|
165
|
+
|
|
166
|
+
- **Analyst consensus tells an unknown symbol from an uncovered one.** Settrade answers both with
|
|
167
|
+
HTTP 500 on the table and an empty list on the summary. On a table 500 the SET stock list now
|
|
168
|
+
decides — the in-process one if `get_stock_list()` already ran, otherwise **one** load per event
|
|
169
|
+
loop (no index enrichment, no retries; a failed load answers "unknowable" and is not repeated) —
|
|
170
|
+
and a symbol that is **not listed** raises `SymbolNotFoundError(status_code=500)` with a real
|
|
171
|
+
`.suggestion`. A listed symbol with no coverage stays the plain `FetchError(500)` it always was,
|
|
172
|
+
and the whole-market summary is never classified. Additive under the deprecation policy's new
|
|
173
|
+
sentence: raising a strict subclass that keeps every documented attribute is not a change.
|
|
174
|
+
- **The deprecation policy says how a deprecation is declared**, and its claim that the golden
|
|
175
|
+
records warnings is now true. Until 0.25.0 it asserted the golden pinned `DeprecationWarning`s;
|
|
176
|
+
the generator recorded none, and no warning existed yet to notice.
|
|
177
|
+
- **README links work on PyPI.** 87 relative links (docs, examples, `AGENTS.md`, `CHANGELOG.md`)
|
|
178
|
+
are absolute `blob/main` / `tree/main` URLs; PyPI resolves nothing relative.
|
|
179
|
+
`tests/test_readme_links.py` now forbids relative links, checks every repo link's target exists,
|
|
180
|
+
and scopes its tag-pin rule to CHANGELOG anchors — docs links deliberately follow `main`.
|
|
181
|
+
- **The live holiday probe makes one attempt.** `test_live_set_holidays` used a 7-attempt ladder
|
|
182
|
+
meant for the transient 401, which 0.24.1 stopped retrying; it only ever fired on 403/429, the
|
|
183
|
+
WAF signals the politeness rule says never to retry.
|
|
184
|
+
- The fault-injection matrix gains a **raised transport error** (`FetchError` from a connection
|
|
185
|
+
reset, as the real fetcher raises once its retries are spent). Every earlier fault was a
|
|
186
|
+
*response*; all 33 entry points now also pass against a request that never completes.
|
|
187
|
+
- `AsyncDataFetcher.fetch()` documents that a non-2xx status is **returned**, not raised.
|
|
188
|
+
|
|
189
|
+
### Deprecated
|
|
190
|
+
|
|
191
|
+
- **`get_consensus_overall()` / `fetch_overall()` for a symbol that is not listed on SET.** It
|
|
192
|
+
answers with an empty summary (`count == 0`) — the same answer as a listed symbol with no
|
|
193
|
+
coverage — and now emits a `DeprecationWarning` (registry id
|
|
194
|
+
`consensus-overall-unknown-symbol`). **In 0.26.0 it raises `SymbolNotFoundError`.** Wrap the
|
|
195
|
+
call in `except NotFoundError` to be ready. `fetch_overall_raw()` never warns.
|
|
196
|
+
|
|
197
|
+
### Fixed
|
|
198
|
+
|
|
199
|
+
- **A WAF block page was saved to disk as the requested filing.** The download guard rejected
|
|
200
|
+
HTML only when the response said it was HTML, and the observed BIG-IP page carries no
|
|
201
|
+
`Content-Type` — so `download()` returned its 242 bytes as a `DownloadedFile` and `download_all`
|
|
202
|
+
wrote them under the filing's filename. Found by the 2026-09-23 checklist audit on #135; closed
|
|
203
|
+
by `BlockedError`.
|
|
204
|
+
- **A "display all results" (ViewMore) page could reduce a section's rows (U-7).** A successful page
|
|
205
|
+
replaced the inline rows with no comparison, so a short or placeholder-only page deleted rows
|
|
206
|
+
while `has_losses` stayed `False`; and a page that failed to *map* raised out of the loop, so the
|
|
207
|
+
whole report code landed on `failed_codes` and every sibling category lost its rows. Both now
|
|
208
|
+
degrade like an unusable page: inline rows kept, a `DegradedSection` recorded, the inline
|
|
209
|
+
page's reported count left for `completeness()`. The rule is a count, not file identity. A
|
|
210
|
+
single-code listing that used to raise `ParseError` here now returns with `has_losses` — the
|
|
211
|
+
behaviour the 0.22.x notes already described.
|
|
212
|
+
|
|
213
|
+
### Dependencies
|
|
214
|
+
|
|
215
|
+
- **pandas 3.0.5 → 3.0.6** (dev group; `dataframe` / `examples` extras keep their `>=2.0.0` floor,
|
|
216
|
+
so installs were never held back). Lock-only: the pandas entry alone moved — +16 wheels are
|
|
217
|
+
upstream's new `cp315`/`cp315t` builds — and upstream's 8 regression fixes and 2 bug fixes touch
|
|
218
|
+
no API settfex calls. Merged as #145 on 2026-09-23.
|
|
219
|
+
|
|
220
|
+
### Migration to 0.25
|
|
221
|
+
|
|
222
|
+
- **Catch `NotFoundError` before `FetchError`** wherever you retry on `FetchError`.
|
|
223
|
+
- **Catch `BlockedError` before `ValueError`** if you treat `ValueError` as bad input — and never
|
|
224
|
+
retry it.
|
|
225
|
+
- A test that pins `type(e) is FetchError` for an analyst-consensus 500 still passes for a listed
|
|
226
|
+
symbol; for an unlisted one the type is now `SymbolNotFoundError` (`isinstance(e, FetchError)`
|
|
227
|
+
still holds).
|
|
228
|
+
- If your application logs through loguru, prefer `setup_logger(filter="settfex")` — or no
|
|
229
|
+
`setup_logger` at all and `logger.enable("settfex")` after `import settfex`.
|
|
230
|
+
- Before 0.26.0: stop relying on `get_consensus_overall(unknown)` returning an empty summary.
|
|
231
|
+
|
|
10
232
|
## [0.24.2] - 2026-09-22
|
|
11
233
|
|
|
12
234
|
### Fixed
|