settfex 0.5.0__tar.gz → 0.7.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.
Files changed (150) hide show
  1. {settfex-0.5.0 → settfex-0.7.0}/.gitignore +1 -1
  2. {settfex-0.5.0 → settfex-0.7.0}/CHANGELOG.md +35 -0
  3. settfex-0.7.0/CLAUDE.md +234 -0
  4. {settfex-0.5.0 → settfex-0.7.0}/PKG-INFO +39 -2
  5. {settfex-0.5.0 → settfex-0.7.0}/README.md +35 -1
  6. settfex-0.7.0/docs/settfex/services/set/chart_quotation.md +191 -0
  7. {settfex-0.5.0 → settfex-0.7.0}/docs/settfex/services/set/earnings_call.md +38 -4
  8. {settfex-0.5.0 → settfex-0.7.0}/examples/set/12_earnings_call.ipynb +33 -0
  9. settfex-0.7.0/examples/set/13_chart_quotation.ipynb +221 -0
  10. {settfex-0.5.0 → settfex-0.7.0}/pyproject.toml +18 -4
  11. {settfex-0.5.0 → settfex-0.7.0}/settfex/__init__.py +1 -1
  12. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/set/__init__.py +19 -0
  13. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/set/earnings_call.py +102 -1
  14. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/set/stock/__init__.py +2 -0
  15. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/set/stock/chart_quotation.py +108 -0
  16. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/set/stock/stock.py +34 -0
  17. {settfex-0.5.0 → settfex-0.7.0}/settfex/utils/__init__.py +2 -0
  18. settfex-0.7.0/settfex/utils/youtube_transcript.py +83 -0
  19. settfex-0.7.0/tests/services/set/stock/__init__.py +0 -0
  20. settfex-0.7.0/tests/services/set/stock/test_chart_quotation.py +405 -0
  21. {settfex-0.5.0 → settfex-0.7.0}/tests/services/set/test_earnings_call.py +70 -0
  22. settfex-0.7.0/tests/utils/test_youtube_transcript.py +66 -0
  23. {settfex-0.5.0 → settfex-0.7.0}/uv.lock +23 -2
  24. {settfex-0.5.0 → settfex-0.7.0}/.editorconfig +0 -0
  25. {settfex-0.5.0 → settfex-0.7.0}/.github/ISSUE_TEMPLATE/bug_report.md +0 -0
  26. {settfex-0.5.0 → settfex-0.7.0}/.github/ISSUE_TEMPLATE/feature_request.md +0 -0
  27. {settfex-0.5.0 → settfex-0.7.0}/.github/PULL_REQUEST_TEMPLATE.md +0 -0
  28. {settfex-0.5.0 → settfex-0.7.0}/.github/copilot-instructions.md +0 -0
  29. {settfex-0.5.0 → settfex-0.7.0}/.github/dependabot.yml +0 -0
  30. {settfex-0.5.0 → settfex-0.7.0}/.github/instructions/core-architectrual-principles.instructions.md +0 -0
  31. {settfex-0.5.0 → settfex-0.7.0}/.github/instructions/documentation-standards.instructions.md +0 -0
  32. {settfex-0.5.0 → settfex-0.7.0}/.github/instructions/file-organization.instructions.md +0 -0
  33. {settfex-0.5.0 → settfex-0.7.0}/.github/instructions/git-commit.instructions.md +0 -0
  34. {settfex-0.5.0 → settfex-0.7.0}/.github/instructions/python-dependency-management.instructions.md +0 -0
  35. {settfex-0.5.0 → settfex-0.7.0}/.github/prompts/Coding.prompt.md +0 -0
  36. {settfex-0.5.0 → settfex-0.7.0}/.github/prompts/Git-Commit-Reviewer.prompt.md +0 -0
  37. {settfex-0.5.0 → settfex-0.7.0}/.github/prompts/Prompt-Engineer.prompt.md +0 -0
  38. {settfex-0.5.0 → settfex-0.7.0}/.github/prompts/Python-Architect.prompt.md +0 -0
  39. {settfex-0.5.0 → settfex-0.7.0}/.github/workflows/ci.yml +0 -0
  40. {settfex-0.5.0 → settfex-0.7.0}/.github/workflows/release.yml +0 -0
  41. {settfex-0.5.0 → settfex-0.7.0}/.github/workflows/security.yml +0 -0
  42. {settfex-0.5.0 → settfex-0.7.0}/.pre-commit-config.yaml +0 -0
  43. {settfex-0.5.0 → settfex-0.7.0}/.python-version +0 -0
  44. {settfex-0.5.0 → settfex-0.7.0}/CODE_OF_CONDUCT.md +0 -0
  45. {settfex-0.5.0 → settfex-0.7.0}/COMPREHENSIVE_AUDIT.md +0 -0
  46. {settfex-0.5.0 → settfex-0.7.0}/CONTRIBUTING.md +0 -0
  47. {settfex-0.5.0 → settfex-0.7.0}/LICENSE +0 -0
  48. {settfex-0.5.0 → settfex-0.7.0}/MANIFEST.in +0 -0
  49. {settfex-0.5.0 → settfex-0.7.0}/RELEASING.md +0 -0
  50. {settfex-0.5.0 → settfex-0.7.0}/SECURITY.md +0 -0
  51. {settfex-0.5.0 → settfex-0.7.0}/docs/guide/PYTHON_LIBRARY_BEST_PRACTICES.md +0 -0
  52. {settfex-0.5.0 → settfex-0.7.0}/docs/index.md +0 -0
  53. {settfex-0.5.0 → settfex-0.7.0}/docs/settfex/services/set/API_PROTECTION_NOTE.md +0 -0
  54. {settfex-0.5.0 → settfex-0.7.0}/docs/settfex/services/set/board_of_director.md +0 -0
  55. {settfex-0.5.0 → settfex-0.7.0}/docs/settfex/services/set/corporate_action.md +0 -0
  56. {settfex-0.5.0 → settfex-0.7.0}/docs/settfex/services/set/financial.md +0 -0
  57. {settfex-0.5.0 → settfex-0.7.0}/docs/settfex/services/set/highlight_data.md +0 -0
  58. {settfex-0.5.0 → settfex-0.7.0}/docs/settfex/services/set/list.md +0 -0
  59. {settfex-0.5.0 → settfex-0.7.0}/docs/settfex/services/set/nvdr_holder.md +0 -0
  60. {settfex-0.5.0 → settfex-0.7.0}/docs/settfex/services/set/price_performance.md +0 -0
  61. {settfex-0.5.0 → settfex-0.7.0}/docs/settfex/services/set/profile_company.md +0 -0
  62. {settfex-0.5.0 → settfex-0.7.0}/docs/settfex/services/set/profile_stock.md +0 -0
  63. {settfex-0.5.0 → settfex-0.7.0}/docs/settfex/services/set/shareholder.md +0 -0
  64. {settfex-0.5.0 → settfex-0.7.0}/docs/settfex/services/set/trading_stat.md +0 -0
  65. {settfex-0.5.0 → settfex-0.7.0}/docs/settfex/services/tfex/list.md +0 -0
  66. {settfex-0.5.0 → settfex-0.7.0}/docs/settfex/services/tfex/trading_statistics.md +0 -0
  67. {settfex-0.5.0 → settfex-0.7.0}/docs/settfex/utils/data_fetcher.md +0 -0
  68. {settfex-0.5.0 → settfex-0.7.0}/docs/settfex/utils/session_caching.md +0 -0
  69. {settfex-0.5.0 → settfex-0.7.0}/docs/solution/FINDINGS.md +0 -0
  70. {settfex-0.5.0 → settfex-0.7.0}/docs/solution/Performance Boost Session Caching Implementation.md +0 -0
  71. {settfex-0.5.0 → settfex-0.7.0}/docs/solution/SESSION_CACHE_SUMMARY.md +0 -0
  72. {settfex-0.5.0 → settfex-0.7.0}/docs/solution/SOLUTION_100_PERCENT.md +0 -0
  73. {settfex-0.5.0 → settfex-0.7.0}/examples/README.md +0 -0
  74. {settfex-0.5.0 → settfex-0.7.0}/examples/set/01_stock_list.ipynb +0 -0
  75. {settfex-0.5.0 → settfex-0.7.0}/examples/set/02_highlight_data.ipynb +0 -0
  76. {settfex-0.5.0 → settfex-0.7.0}/examples/set/03_stock_profile.ipynb +0 -0
  77. {settfex-0.5.0 → settfex-0.7.0}/examples/set/04_company_profile.ipynb +0 -0
  78. {settfex-0.5.0 → settfex-0.7.0}/examples/set/05_corporate_action.ipynb +0 -0
  79. {settfex-0.5.0 → settfex-0.7.0}/examples/set/06_shareholder.ipynb +0 -0
  80. {settfex-0.5.0 → settfex-0.7.0}/examples/set/07_nvdr_holder.ipynb +0 -0
  81. {settfex-0.5.0 → settfex-0.7.0}/examples/set/08_board_of_director.ipynb +0 -0
  82. {settfex-0.5.0 → settfex-0.7.0}/examples/set/09_trading_statistics.ipynb +0 -0
  83. {settfex-0.5.0 → settfex-0.7.0}/examples/set/10_price_performance.ipynb +0 -0
  84. {settfex-0.5.0 → settfex-0.7.0}/examples/set/11_financial.ipynb +0 -0
  85. {settfex-0.5.0 → settfex-0.7.0}/examples/set/README.md +0 -0
  86. {settfex-0.5.0 → settfex-0.7.0}/examples/set/cpall_balance_sheet.csv +0 -0
  87. {settfex-0.5.0 → settfex-0.7.0}/examples/set/market_dashboard_20251005_173221.csv +0 -0
  88. {settfex-0.5.0 → settfex-0.7.0}/examples/set/market_dashboard_20251005_210737.csv +0 -0
  89. {settfex-0.5.0 → settfex-0.7.0}/examples/set/market_dashboard_20251006_091440.csv +0 -0
  90. {settfex-0.5.0 → settfex-0.7.0}/examples/set/thailand_banking_stocks.csv +0 -0
  91. {settfex-0.5.0 → settfex-0.7.0}/examples/set/thailand_set_main_board.csv +0 -0
  92. {settfex-0.5.0 → settfex-0.7.0}/examples/set/thailand_stock_universe.csv +0 -0
  93. {settfex-0.5.0 → settfex-0.7.0}/examples/tfex/01_series_list.ipynb +0 -0
  94. {settfex-0.5.0 → settfex-0.7.0}/examples/tfex/02_trading_statistics.ipynb +0 -0
  95. {settfex-0.5.0 → settfex-0.7.0}/examples/tfex/03_underlying_price.ipynb +0 -0
  96. {settfex-0.5.0 → settfex-0.7.0}/settfex/py.typed +0 -0
  97. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/__init__.py +0 -0
  98. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/set/constants.py +0 -0
  99. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/set/list.py +0 -0
  100. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/set/stock/board_of_director.py +0 -0
  101. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/set/stock/corporate_action.py +0 -0
  102. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/set/stock/financial/__init__.py +0 -0
  103. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/set/stock/financial/financial.py +0 -0
  104. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/set/stock/highlight_data.py +0 -0
  105. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/set/stock/latest_historical_trading.py +0 -0
  106. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/set/stock/nvdr_holder.py +0 -0
  107. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/set/stock/price_performance.py +0 -0
  108. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/set/stock/profile_company.py +0 -0
  109. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/set/stock/profile_stock.py +0 -0
  110. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/set/stock/shareholder.py +0 -0
  111. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/set/stock/trading_stat.py +0 -0
  112. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/set/stock/utils.py +0 -0
  113. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/tfex/__init__.py +0 -0
  114. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/tfex/constants.py +0 -0
  115. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/tfex/list.py +0 -0
  116. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/tfex/trading_statistics.py +0 -0
  117. {settfex-0.5.0 → settfex-0.7.0}/settfex/services/tfex/underlying_price.py +0 -0
  118. {settfex-0.5.0 → settfex-0.7.0}/settfex/utils/data_fetcher.py +0 -0
  119. {settfex-0.5.0 → settfex-0.7.0}/settfex/utils/http.py +0 -0
  120. {settfex-0.5.0 → settfex-0.7.0}/settfex/utils/logging.py +0 -0
  121. {settfex-0.5.0 → settfex-0.7.0}/settfex/utils/parsing.py +0 -0
  122. {settfex-0.5.0 → settfex-0.7.0}/settfex/utils/session_cache.py +0 -0
  123. {settfex-0.5.0 → settfex-0.7.0}/settfex/utils/session_manager.py +0 -0
  124. {settfex-0.5.0 → settfex-0.7.0}/tests/__init__.py +0 -0
  125. {settfex-0.5.0 → settfex-0.7.0}/tests/conftest.py +0 -0
  126. {settfex-0.5.0 → settfex-0.7.0}/tests/services/__init__.py +0 -0
  127. {settfex-0.5.0 → settfex-0.7.0}/tests/services/set/__init__.py +0 -0
  128. {settfex-0.5.0 → settfex-0.7.0}/tests/services/set/stock/financial/__init__.py +0 -0
  129. {settfex-0.5.0 → settfex-0.7.0}/tests/services/set/stock/financial/test_financial.py +0 -0
  130. {settfex-0.5.0 → settfex-0.7.0}/tests/services/set/test_board_of_director.py +0 -0
  131. {settfex-0.5.0 → settfex-0.7.0}/tests/services/set/test_client.py +0 -0
  132. {settfex-0.5.0 → settfex-0.7.0}/tests/services/set/test_corporate_action.py +0 -0
  133. {settfex-0.5.0 → settfex-0.7.0}/tests/services/set/test_historical.py +0 -0
  134. {settfex-0.5.0 → settfex-0.7.0}/tests/services/set/test_realtime.py +0 -0
  135. {settfex-0.5.0 → settfex-0.7.0}/tests/services/set/test_shareholder.py +0 -0
  136. {settfex-0.5.0 → settfex-0.7.0}/tests/services/tfex/__init__.py +0 -0
  137. {settfex-0.5.0 → settfex-0.7.0}/tests/services/tfex/test_client.py +0 -0
  138. {settfex-0.5.0 → settfex-0.7.0}/tests/services/tfex/test_historical.py +0 -0
  139. {settfex-0.5.0 → settfex-0.7.0}/tests/services/tfex/test_list.py +0 -0
  140. {settfex-0.5.0 → settfex-0.7.0}/tests/services/tfex/test_realtime.py +0 -0
  141. {settfex-0.5.0 → settfex-0.7.0}/tests/services/tfex/test_trading_statistics.py +0 -0
  142. {settfex-0.5.0 → settfex-0.7.0}/tests/services/tfex/test_underlying_price.py +0 -0
  143. {settfex-0.5.0 → settfex-0.7.0}/tests/utils/__init__.py +0 -0
  144. {settfex-0.5.0 → settfex-0.7.0}/tests/utils/test_data_fetcher.py +0 -0
  145. {settfex-0.5.0 → settfex-0.7.0}/tests/utils/test_formatting.py +0 -0
  146. {settfex-0.5.0 → settfex-0.7.0}/tests/utils/test_http.py +0 -0
  147. {settfex-0.5.0 → settfex-0.7.0}/tests/utils/test_logging.py +0 -0
  148. {settfex-0.5.0 → settfex-0.7.0}/tests/utils/test_parsing.py +0 -0
  149. {settfex-0.5.0 → settfex-0.7.0}/tests/utils/test_session_manager.py +0 -0
  150. {settfex-0.5.0 → settfex-0.7.0}/tests/utils/test_validation.py +0 -0
@@ -197,6 +197,6 @@ config/*.yml
197
197
  !config/*.default.yml
198
198
 
199
199
  # AI Assistant and Meta Files
200
- CLAUDE.md
200
+ # CLAUDE.md is tracked (project instructions for AI assistants); .claude/ and scripts/ stay local.
201
201
  .claude/
202
202
  scripts/
@@ -7,6 +7,41 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
7
7
 
8
8
  ## [Unreleased]
9
9
 
10
+ ## [0.7.0] - 2026-06-21
11
+
12
+ ### Added
13
+
14
+ - **Latest traded price for SET stocks** — on top of the existing chart-quotation service, a
15
+ first-class way to get the most recent *traded* price relative to now. The SET intraday feed
16
+ pre-populates the rest of the session with null/no-trade buckets; these are excluded
17
+ automatically.
18
+ - `get_latest_price(symbol, period="1D", accumulated=False, as_of=None) -> Quotation | None` —
19
+ top-level convenience returning the latest traded quotation (time, price, volume, change), or
20
+ `None` if nothing has traded yet.
21
+ - `ChartQuotation.get_latest_quotation(as_of=None) -> Quotation | None` and
22
+ `ChartQuotation.get_latest_price(as_of=None) -> float | None` (scalar, falls back to `prior`) —
23
+ pure, timezone-safe selection in Asia/Bangkok; `as_of` defaults to now (naive values are
24
+ treated as Bangkok local time).
25
+ - `Stock.get_latest_price(period="1D", accumulated=False, as_of=None)` on the unified Stock class.
26
+ - Hyphenated warrant symbols (e.g. `JAS-W4`) are preserved.
27
+ - The chart-quotation models, service, and `get_chart_quotation` are now also exported from
28
+ `settfex.services.set` (previously only from `settfex.services.set.stock`).
29
+
30
+ ## [0.6.0] - 2026-06-19
31
+
32
+ ### Added
33
+
34
+ - **Thai YouTube transcripts for earnings calls** (raw text for AI/LLM use), behind a new optional
35
+ `transcript` extra (`pip install "settfex[transcript]"`, backed by `youtube-transcript-api`):
36
+ - `fetch_youtube_transcript(video_id, *, languages=("th",), proxies=None) -> str | None` — a
37
+ generic async wrapper that returns the caption text as one string, or `None` when the video has
38
+ no matching captions / they're disabled / the request is blocked (never raises for those).
39
+ - `fetch_transcripts(items, ...) -> list[EarningsCallItem]` — fills `EarningsCallItem.transcript`
40
+ for every item that has a YouTube video (bounded concurrency, default 3; optional progress bar;
41
+ per-item tolerant; items without a video are skipped).
42
+ - `get_earnings_call_transcript(id, ...) -> str | None` — one presentation's transcript by id.
43
+ - New `EarningsCallItem.transcript: str | None` field (populated only by the above).
44
+
10
45
  ## [0.5.0] - 2026-06-19
11
46
 
12
47
  ### Added
@@ -0,0 +1,234 @@
1
+ # CLAUDE.md - AI Assistant Context
2
+
3
+ Essential context and guidelines for AI assistants working on the settfex project.
4
+
5
+ ## Project Overview
6
+
7
+ **settfex** is a Python library that fetches real-time and historical data from:
8
+ - **SET** (Stock Exchange of Thailand)
9
+ - **TFEX** (Thailand Futures Exchange)
10
+
11
+ Published on PyPI, targeting Python 3.11+ with modern async patterns.
12
+
13
+ ## Project Structure
14
+
15
+ ```
16
+ settfex/
17
+ ├── settfex/ # Main package
18
+ │ ├── services/ # Business logic and API integrations
19
+ │ │ ├── set/ # SET-specific services (11 services)
20
+ │ │ │ ├── constants.py, list.py
21
+ │ │ │ └── stock/ # Stock services: highlight_data, profile_stock,
22
+ │ │ │ # profile_company, corporate_action, shareholder,
23
+ │ │ │ # nvdr_holder, board_of_director, trading_stat,
24
+ │ │ │ # price_performance, financial/, stock.py, utils.py
25
+ │ │ └── tfex/ # TFEX services: list.py, trading_statistics.py
26
+ │ └── utils/ # http.py, data_fetcher.py, session_manager.py,
27
+ │ # session_cache.py, logging.py
28
+ ├── tests/ # Mirror of settfex/ with test_ prefix
29
+ ├── docs/ # Service docs, guides, solutions
30
+ ├── examples/ # 13 Jupyter notebooks (11 SET + 2 TFEX)
31
+ ├── scripts/ # Verification scripts per service
32
+ ├── .github/ # CI and agent instructions
33
+ ├── pyproject.toml # uv-based config
34
+ └── README.md
35
+ ```
36
+
37
+ ## Architecture Principles
38
+
39
+ 1. **Modular Design**: Clear separation between SET and TFEX services
40
+ 2. **Service Layer**: All external API interactions encapsulated in `services/`
41
+ 3. **Utilities**: Reusable helpers in `utils/` for cross-cutting concerns
42
+ 4. **Type Safety**: Full type hints and Pydantic validation throughout
43
+ 5. **Modern Python**: Python 3.11+ with async/await patterns
44
+ 6. **Testing**: Comprehensive pytest coverage (>80% target)
45
+ 7. **Documentation**: Maintained docs for all public APIs
46
+
47
+ ## Development Guidelines
48
+
49
+ ### Code Style
50
+ - PEP 8 with 100-char line length; Ruff linting; mypy strict mode
51
+ - All functions must have type hints
52
+
53
+ ### Dependencies
54
+ - **curl_cffi**: Async HTTP with browser impersonation (replaced httpx 2025-10-01)
55
+ - **loguru**: Structured logging with colored output, rotation, compression (replaced stdlib logging 2025-10-01)
56
+ - **pydantic**: Runtime validation and settings management
57
+ - Minimize external dependencies
58
+
59
+ ### Testing
60
+ - Write tests for all new features; mock external API calls
61
+ - Use pytest fixtures in `conftest.py` for shared setup
62
+ - Maintain >80% coverage
63
+
64
+ ### Documentation
65
+ - Update docs when adding features; include docstrings for all public APIs
66
+ - Keep Jupyter notebook examples up-to-date
67
+
68
+ ## Common Tasks
69
+
70
+ ### Adding a New Service (SET or TFEX)
71
+ 1. Create module in `settfex/services/{set,tfex}/`
72
+ 2. Add tests in `tests/services/{set,tfex}/`
73
+ 3. Update the appropriate `__init__.py` to export the service
74
+ 4. Document with docstrings + create verification script in `scripts/settfex/services/`
75
+ 5. Add Jupyter notebook example in `examples/`
76
+
77
+ ### Adding Utility Functions
78
+ 1. Add to appropriate module in `settfex/utils/` or create new one
79
+ 2. Add tests in `tests/utils/`; ensure utilities are generic and reusable
80
+
81
+ ## Service Design Patterns (Must Follow)
82
+
83
+ Every service follows this consistent pattern:
84
+ - **Pydantic models** for all data with full type annotations
85
+ - **Two fetch methods**: `fetch_*()` returns Pydantic models; `fetch_*_raw()` returns raw dicts
86
+ - **Convenience function**: `get_*()` top-level function for one-line access
87
+ - **Dual language**: `en`/`th` support via `normalize_language()` (accepts: en/eng/english, th/tha/thai)
88
+ - **Symbol normalization**: Auto-uppercase via `normalize_symbol()`
89
+ - **SessionManager**: All cookie/bot-detection handled automatically (no manual cookie params)
90
+ - **Async-first**: All I/O uses async/await via `AsyncDataFetcher`
91
+ - **Bot bypass**: Symbol-specific referer header + SessionManager cookies (Incapsula bypass)
92
+
93
+ Typical usage:
94
+ ```python
95
+ from settfex.services.set import Stock, get_highlight_data, get_stock_list
96
+
97
+ stock = Stock("CPALL")
98
+ data = await stock.get_highlight_data() # via unified Stock class
99
+ data = await get_highlight_data("CPALL") # or convenience function
100
+ all_stocks = await get_stock_list() # no cookie params needed
101
+ ```
102
+
103
+ ## Services Inventory (16 total)
104
+
105
+ ### SET Services (14)
106
+
107
+ | # | Service | Module | Endpoint Pattern | Key Data |
108
+ |---|---|---|---|---|
109
+ | 1 | Stock List | `list.py` | `/api/set/stock/list` | All SET/MAI stocks, filter by market/industry/symbol |
110
+ | 2 | Highlight Data | `stock/highlight_data.py` | `/api/set/stock/{sym}/highlight-data` | P/E, P/B, market cap, beta, dividends, 52-wk range, NVDR |
111
+ | 3 | Stock Profile | `stock/profile_stock.py` | `/api/set/stock/{sym}/profile` | Listing details, IPO, sector, foreign limits, ISIN, warrants |
112
+ | 4 | Company Profile | `stock/profile_company.py` | `/api/set/company/{sym}/profile` | ESG rating, CG score, auditors, management, capital structure |
113
+ | 5 | Corporate Actions | `stock/corporate_action.py` | `/api/set/stock/{sym}/corporate-action` | Dividends (XD), meetings (XM/AGM/EGM), payment dates |
114
+ | 6 | Shareholders | `stock/shareholder.py` | `/api/set/stock/{sym}/shareholder` | Major holders, free float %, ownership distribution |
115
+ | 7 | NVDR Holders | `stock/nvdr_holder.py` | `/api/set/stock/{sym}/nvdr-holder` | NVDR ownership, Thai vs foreign holders |
116
+ | 8 | Board of Directors | `stock/board_of_director.py` | `/api/set/company/{sym}/board-of-director` | Directors, positions (Chairman, CEO, Independent) |
117
+ | 9 | Trading Statistics | `stock/trading_stat.py` | `/api/set/factsheet/{sym}/trading-stat` | 30+ fields: price/volume/valuation/beta, 5 periods (YTD-1Y) |
118
+ | 10 | Price Performance | `stock/price_performance.py` | `/api/set/factsheet/{sym}/price-performance` | Stock vs sector vs market (5D/1M/3M/6M/YTD), P/E, P/B |
119
+ | 11 | Financial Statements | `stock/financial/financial.py` | `/api/set/factsheet/{sym}/financialstatement` | Balance sheet, income, cash flow (multi-period, en/th) |
120
+ | 12 | Earnings Call (Opportunity Day) | `earnings_call.py` | `POST api.lcp.setgroup.or.th/.../investor/search/archive` (+ `GET /investor/vdo/{id}`, `/investor/filter/*`) | OPPDAY calendar (symbol, company, date, clip duration, YouTube URL); concurrent `fetch_all`/`get_all_earnings_calls` (+ optional `tqdm` progress); detail-by-id (`get_earnings_call_detail`); 7 filter helpers; pandas `to_dataframe()`; **Thai YouTube transcripts** for AI (`fetch_transcripts` / `get_earnings_call_transcript` / `fetch_youtube_transcript`, `EarningsCallItem.transcript`); stateless host (no SessionManager); optional extras: `dataframe` (pandas) / `progress` (tqdm) / `transcript` (youtube-transcript-api) |
121
+ | 13 | Chart Quotation / Latest Price | `stock/chart_quotation.py` | `/api/set/stock/{sym}/chart-quotation` | Intraday/historical per-minute series (price/volume/value/%chg, intermissions, prior close); **latest *traded* price relative to now** — `get_latest_price()` (→ `Quotation`), model `get_latest_quotation()`/`get_latest_price()` (→ float, `prior` fallback); skips null future/lunch/no-trade buckets; Asia/Bangkok tz-safe `as_of`; hyphen-safe symbols (`JAS-W4`) |
122
+ | 14 | Latest Historical Trading | `stock/latest_historical_trading.py` | `/api/set/stock/{sym}/latest-historical-trading` | Latest trading-day summary: OHLCV, change/%change, and valuation metrics |
123
+
124
+ ### TFEX Services (2)
125
+
126
+ | # | Service | Module | Endpoint Pattern | Key Data |
127
+ |---|---|---|---|---|
128
+ | 1 | Series List | `list.py` | `/api/set/tfex/series/list` | Futures/options, 8 filter methods, contract details |
129
+ | 2 | Trading Statistics | `trading_statistics.py` | `/api/set/tfex/series/{sym}/trading-statistics` | Settlement, margin (IM/MM), theoretical price, days to maturity |
130
+
131
+ ### Unified Stock Class (`stock/stock.py`)
132
+ Single entry point for SET stock data — initialize with symbol, access all services via lazy-init properties:
133
+ ```python
134
+ stock = Stock("CPALL")
135
+ highlight = await stock.get_highlight_data()
136
+ profile = await stock.get_profile()
137
+ financials = await stock.get_balance_sheet()
138
+ latest = await stock.get_latest_price() # latest traded price vs now
139
+ # ... all stock services accessible
140
+ ```
141
+
142
+ ## API Design Principles
143
+
144
+ 1. **Consistency**: SET and TFEX services follow identical patterns
145
+ 2. **Simplicity**: Simple, intuitive APIs; one-line convenience functions
146
+ 3. **Async-first**: async/await for all I/O operations
147
+ 4. **Error Handling**: Clear, informative error messages
148
+ 5. **Validation**: Pydantic models for all inputs and outputs
149
+ 6. **Documentation**: All public APIs well-documented
150
+
151
+ ## Key Technical Decisions
152
+
153
+ | Area | Choice | Reason |
154
+ |---|---|---|
155
+ | HTTP client | `curl_cffi` | Browser impersonation for bot detection bypass |
156
+ | Logging | `loguru` | Colored output, auto-rotation, better exception traces |
157
+ | Validation | Pydantic | Full type safety with runtime validation |
158
+ | Async | `asyncio.to_thread` | Wraps sync curl_cffi for async compatibility |
159
+ | Session mgmt | `SessionManager` | 25x speedup via cookie warming + caching |
160
+ | Build | `uv` (pyproject.toml) | Fast dependency resolution |
161
+ | Lint | Ruff + mypy strict | Modern, fast tooling |
162
+
163
+ ## Target Users
164
+
165
+ - Python developers building trading applications
166
+ - Financial analysts needing Thailand market data
167
+ - Quantitative researchers and data scientists
168
+ - Automated trading system developers
169
+
170
+ ## Important Notes
171
+
172
+ - This library is **not officially affiliated** with SET or TFEX
173
+ - Always respect API rate limits and terms of service
174
+ - Handle sensitive data (API keys, credentials) securely
175
+ - Never commit credentials or API keys to version control
176
+
177
+ ## Recent Change History (Condensed)
178
+
179
+ ### 2025-10-05
180
+ - **Jupyter Examples**: 13 notebooks covering all services with beginner guides, professional use cases, and CSV/pandas export
181
+ - **TFEX Trading Statistics**: Settlement prices, margin (IM/MM), days to maturity, theoretical pricing
182
+ - **TFEX Series List**: Complete futures/options discovery with 8 filter methods
183
+ - **Financial Service**: Balance sheet, income statement, cash flow — multi-period with en/th support, 98% test coverage
184
+
185
+ ### 2025-10-04
186
+ - **Price Performance**: Stock vs sector vs market across 5 periods (5D–YTD), P/E, P/B, turnover
187
+ - **Trading Statistics (SET)**: 30+ fields across YTD/1M/3M/6M/1Y periods — price, volume, valuation, beta
188
+
189
+ ### 2025-10-03
190
+ - **Board of Directors**: Director names, positions, en/th support
191
+ - **NVDR Holders**: NVDR ownership data with Thai/foreign holder identification
192
+ - **Corporate Actions**: Dividends (XD) and meetings (XM) with full detail
193
+ - **Company Profile**: ESG ratings, CG scores, management, auditors, capital structure
194
+ - **Shareholders**: Major holders, free float, ownership distribution
195
+ - **Refactoring**: Removed legacy cookie generation (~200 lines); SessionManager now handles all bot detection
196
+
197
+ ### 2025-10-02
198
+ - **Stock Profile**: 30+ fields — listing, IPO, sector, foreign limits, ISIN, warrants
199
+ - **Highlight Data**: P/E, P/B, market cap, dividends, NVDR, 52-week range
200
+ - **Unified Stock class**: Single entry point for all stock services with lazy init
201
+ - **Stock utilities**: `normalize_symbol()`, `normalize_language()` shared across all services
202
+
203
+ ### 2025-10-01
204
+ - **Stock List Service**: Full SET/MAI stock list with market/industry/symbol filtering
205
+ - **AsyncDataFetcher**: Core HTTP client with Unicode/Thai support, retry logic, bot impersonation
206
+ - **Migration**: httpx → curl_cffi (bot bypass), stdlib logging → loguru
207
+ - Established the service architecture pattern used by all subsequent services
208
+
209
+ ## Future Enhancements (Ideas)
210
+
211
+ - WebSocket support for real-time streaming
212
+ - Data caching mechanisms, rate limiting
213
+ - CLI tool for quick data queries
214
+ - pandas/polars integration
215
+ - Historical data export (CSV, Parquet)
216
+
217
+ ## When Working on This Project
218
+
219
+ 1. **Read First**: Check existing code patterns before implementing
220
+ 2. **Test**: Write tests before or alongside code
221
+ 3. **Document**: Update docs when adding features
222
+ 4. **Consistency**: Follow existing patterns and naming conventions
223
+ 5. **Type Safety**: Always use type hints
224
+ 6. **Ask Questions**: If unclear about architecture, ask for clarification
225
+
226
+ ## Contact & Resources
227
+
228
+ - Documentation: `docs/` directory
229
+ - Issues: GitHub Issues
230
+ - License: MIT
231
+
232
+ ---
233
+
234
+ *This file should be kept up-to-date as the project evolves.*
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: settfex
3
- Version: 0.5.0
3
+ Version: 0.7.0
4
4
  Summary: Async Python library for fetching real-time and historical data from the Stock Exchange of Thailand (SET) and Thailand Futures Exchange (TFEX)
5
5
  Project-URL: Homepage, https://github.com/lumduan/settfex
6
6
  Project-URL: Documentation, https://github.com/lumduan/settfex#readme
@@ -38,8 +38,11 @@ Requires-Dist: matplotlib>=3.7.0; extra == 'examples'
38
38
  Requires-Dist: notebook>=7.0.0; extra == 'examples'
39
39
  Requires-Dist: pandas>=2.0.0; extra == 'examples'
40
40
  Requires-Dist: tqdm>=4.66.0; extra == 'examples'
41
+ Requires-Dist: youtube-transcript-api>=1.0.0; extra == 'examples'
41
42
  Provides-Extra: progress
42
43
  Requires-Dist: tqdm>=4.66.0; extra == 'progress'
44
+ Provides-Extra: transcript
45
+ Requires-Dist: youtube-transcript-api>=1.0.0; extra == 'transcript'
43
46
  Description-Content-Type: text/markdown
44
47
 
45
48
  # settfex
@@ -98,6 +101,8 @@ All examples include beginner explanations, professional trading use cases, and
98
101
  9. **[Trading Statistics](examples/set/09_trading_statistics.ipynb)** - Multi-period performance and volatility
99
102
  10. **[Price Performance](examples/set/10_price_performance.ipynb)** - Sector comparison and alpha calculation
100
103
  11. **[Financial Statements](examples/set/11_financial.ipynb)** - Balance sheet, income, cash flow analysis
104
+ 12. **[Earnings Call (Opportunity Day)](examples/set/12_earnings_call.ipynb)** - OPPDAY calendar, YouTube links, Thai transcripts for AI
105
+ 13. **[Chart Quotation & Latest Price](examples/set/13_chart_quotation.ipynb)** - Intraday series and the latest traded price relative to now
101
106
 
102
107
  ### 📈 TFEX Examples (Thailand Futures Exchange)
103
108
 
@@ -125,7 +130,7 @@ Want to dig deeper? Check out our detailed guides:
125
130
  - **[Trading Statistics Service](docs/settfex/services/set/trading_stat.md)** - Historical trading performance and metrics
126
131
  - **[Price Performance Service](docs/settfex/services/set/price_performance.md)** - Stock, sector, and market price performance comparison
127
132
  - **[Financial Service](docs/settfex/services/set/financial.md)** - Balance sheet, income statement, and cash flow data
128
- - **[Chart Quotation Service](docs/settfex/services/set/chart_quotation.md)** - Intraday and historical price chart data
133
+ - **[Chart Quotation Service](docs/settfex/services/set/chart_quotation.md)** - Intraday/historical price chart series, plus the latest *traded* price relative to now
129
134
  - **[Latest Historical Trading Service](docs/settfex/services/set/latest_historical_trading.md)** - Latest trading day summary with OHLCV and valuation metrics
130
135
  - **[Earnings Call (Opportunity Day) Service](docs/settfex/services/set/earnings_call.md)** - OPPDAY earnings-call calendar with YouTube links, as models or a DataFrame
131
136
 
@@ -354,6 +359,27 @@ print(f"Market ({data.market.symbol}): {data.market.ytd_percent_change:+.2f}%")
354
359
 
355
360
  ---
356
361
 
362
+ #### ⏱️ Get the Latest Traded Price (Intraday)
363
+
364
+ Fetch the intraday chart-quotation series, or jump straight to the latest *traded* price relative to now (the null future/lunch/no-trade buckets are excluded automatically):
365
+
366
+ ```python
367
+ from settfex.services.set import get_latest_price, get_chart_quotation
368
+
369
+ # The latest TRADED quotation right now (or None if nothing has traded yet)
370
+ quote = await get_latest_price("CPALL")
371
+ if quote:
372
+ print(f"{quote.local_datetime}: {quote.price} (vol {quote.volume:,.0f})")
373
+
374
+ # Or work with the full series; the model also exposes a prior-fallback scalar
375
+ data = await get_chart_quotation("CPALL", period="1D")
376
+ print(f"Prior close: {data.prior}, latest price: {data.get_latest_price()}")
377
+ ```
378
+
379
+ **👉 [Learn more about Chart Quotation & Latest Price](docs/settfex/services/set/chart_quotation.md)**
380
+
381
+ ---
382
+
357
383
  #### 💰 Get Financial Statements
358
384
 
359
385
  Fetch comprehensive financial data including balance sheet, income statement, and cash flow:
@@ -448,8 +474,19 @@ hann = await get_earnings_calls(keyword="HANN")
448
474
  # (pip install "settfex[progress]"):
449
475
  from settfex.services.set import get_all_earnings_calls
450
476
  everything = await get_all_earnings_calls(progress=True) # ~9520 records in ~15s (~10x faster)
477
+
478
+ # Thai subtitles as raw text for AI/LLM use (pip install "settfex[transcript]"):
479
+ from settfex.services.set import fetch_transcripts, get_earnings_call_transcript
480
+ await fetch_transcripts(hann.items) # fills item.transcript (Thai) per video
481
+ text = await get_earnings_call_transcript(6319) # …or one presentation's transcript by id
451
482
  ```
452
483
 
484
+ > ⚠️ **Transcripts & YouTube limits.** YouTube rate-limits / IP-blocks aggressively (especially
485
+ > from cloud servers), so transcript fetching is built for a **filtered** set (a company /
486
+ > quarter) — not the full ~9,520-video archive — and defaults to low concurrency (3). A
487
+ > blocked/missing/disabled transcript simply comes back as `None`; if your host IP is blocked,
488
+ > pass `proxies={"http": ..., "https": ...}`. Results can vary by IP.
489
+
453
490
  **👉 [Learn more about the Earnings Call Service](docs/settfex/services/set/earnings_call.md)**
454
491
 
455
492
  ---
@@ -54,6 +54,8 @@ All examples include beginner explanations, professional trading use cases, and
54
54
  9. **[Trading Statistics](examples/set/09_trading_statistics.ipynb)** - Multi-period performance and volatility
55
55
  10. **[Price Performance](examples/set/10_price_performance.ipynb)** - Sector comparison and alpha calculation
56
56
  11. **[Financial Statements](examples/set/11_financial.ipynb)** - Balance sheet, income, cash flow analysis
57
+ 12. **[Earnings Call (Opportunity Day)](examples/set/12_earnings_call.ipynb)** - OPPDAY calendar, YouTube links, Thai transcripts for AI
58
+ 13. **[Chart Quotation & Latest Price](examples/set/13_chart_quotation.ipynb)** - Intraday series and the latest traded price relative to now
57
59
 
58
60
  ### 📈 TFEX Examples (Thailand Futures Exchange)
59
61
 
@@ -81,7 +83,7 @@ Want to dig deeper? Check out our detailed guides:
81
83
  - **[Trading Statistics Service](docs/settfex/services/set/trading_stat.md)** - Historical trading performance and metrics
82
84
  - **[Price Performance Service](docs/settfex/services/set/price_performance.md)** - Stock, sector, and market price performance comparison
83
85
  - **[Financial Service](docs/settfex/services/set/financial.md)** - Balance sheet, income statement, and cash flow data
84
- - **[Chart Quotation Service](docs/settfex/services/set/chart_quotation.md)** - Intraday and historical price chart data
86
+ - **[Chart Quotation Service](docs/settfex/services/set/chart_quotation.md)** - Intraday/historical price chart series, plus the latest *traded* price relative to now
85
87
  - **[Latest Historical Trading Service](docs/settfex/services/set/latest_historical_trading.md)** - Latest trading day summary with OHLCV and valuation metrics
86
88
  - **[Earnings Call (Opportunity Day) Service](docs/settfex/services/set/earnings_call.md)** - OPPDAY earnings-call calendar with YouTube links, as models or a DataFrame
87
89
 
@@ -310,6 +312,27 @@ print(f"Market ({data.market.symbol}): {data.market.ytd_percent_change:+.2f}%")
310
312
 
311
313
  ---
312
314
 
315
+ #### ⏱️ Get the Latest Traded Price (Intraday)
316
+
317
+ Fetch the intraday chart-quotation series, or jump straight to the latest *traded* price relative to now (the null future/lunch/no-trade buckets are excluded automatically):
318
+
319
+ ```python
320
+ from settfex.services.set import get_latest_price, get_chart_quotation
321
+
322
+ # The latest TRADED quotation right now (or None if nothing has traded yet)
323
+ quote = await get_latest_price("CPALL")
324
+ if quote:
325
+ print(f"{quote.local_datetime}: {quote.price} (vol {quote.volume:,.0f})")
326
+
327
+ # Or work with the full series; the model also exposes a prior-fallback scalar
328
+ data = await get_chart_quotation("CPALL", period="1D")
329
+ print(f"Prior close: {data.prior}, latest price: {data.get_latest_price()}")
330
+ ```
331
+
332
+ **👉 [Learn more about Chart Quotation & Latest Price](docs/settfex/services/set/chart_quotation.md)**
333
+
334
+ ---
335
+
313
336
  #### 💰 Get Financial Statements
314
337
 
315
338
  Fetch comprehensive financial data including balance sheet, income statement, and cash flow:
@@ -404,8 +427,19 @@ hann = await get_earnings_calls(keyword="HANN")
404
427
  # (pip install "settfex[progress]"):
405
428
  from settfex.services.set import get_all_earnings_calls
406
429
  everything = await get_all_earnings_calls(progress=True) # ~9520 records in ~15s (~10x faster)
430
+
431
+ # Thai subtitles as raw text for AI/LLM use (pip install "settfex[transcript]"):
432
+ from settfex.services.set import fetch_transcripts, get_earnings_call_transcript
433
+ await fetch_transcripts(hann.items) # fills item.transcript (Thai) per video
434
+ text = await get_earnings_call_transcript(6319) # …or one presentation's transcript by id
407
435
  ```
408
436
 
437
+ > ⚠️ **Transcripts & YouTube limits.** YouTube rate-limits / IP-blocks aggressively (especially
438
+ > from cloud servers), so transcript fetching is built for a **filtered** set (a company /
439
+ > quarter) — not the full ~9,520-video archive — and defaults to low concurrency (3). A
440
+ > blocked/missing/disabled transcript simply comes back as `None`; if your host IP is blocked,
441
+ > pass `proxies={"http": ..., "https": ...}`. Results can vary by IP.
442
+
409
443
  **👉 [Learn more about the Earnings Call Service](docs/settfex/services/set/earnings_call.md)**
410
444
 
411
445
  ---
@@ -0,0 +1,191 @@
1
+ # Stock Chart Quotation Service
2
+
3
+ ## Overview
4
+
5
+ The Stock Chart Quotation Service fetches the **intraday (and historical) chart-quotation feed**
6
+ for an individual SET stock symbol — the per-interval price / volume / value / change series shown
7
+ on the SET price chart. On top of that raw series it provides a first-class way to retrieve the
8
+ **latest *traded* price relative to now**, correctly excluding the null-valued buckets the API
9
+ pre-populates for the remainder of the trading day.
10
+
11
+ Endpoint: `GET /api/set/stock/{symbol}/chart-quotation?period=1D&accumulated=false`
12
+
13
+ ## Key Features
14
+
15
+ - **Intraday + historical** — `period` of `1D`, `5D`, `1M`, `3M`, `6M`, `1Y`, `3Y`, `5Y`, `MAX`.
16
+ - **Latest traded price vs. now** — `get_latest_price()` returns the most recent quotation that
17
+ actually traded (non-null `volume`), skipping future / lunch-break / no-trade buckets.
18
+ - **Timezone-correct** — quotation timestamps are tz-aware (`+07:00`); `as_of` inputs (naive or any
19
+ zone) are normalized to Asia/Bangkok before comparison.
20
+ - **Hyphen-safe symbols** — warrant symbols like `JAS-W4` are preserved (only upper-cased/trimmed).
21
+ - **Type-safe** — full Pydantic models with camelCase→snake_case aliases.
22
+ - **Bot-detection handled** — `SessionManager` warms cookies and builds the symbol-specific referer
23
+ automatically (Incapsula bypass); no manual cookie params.
24
+
25
+ ## Installation
26
+
27
+ ```bash
28
+ pip install settfex
29
+ ```
30
+
31
+ ## Quick Start
32
+
33
+ ### Latest traded price (the headline capability)
34
+
35
+ ```python
36
+ import asyncio
37
+ from settfex.services.set import get_latest_price
38
+
39
+ async def main():
40
+ quotation = await get_latest_price("CPALL") # latest TRADED point, relative to now
41
+ if quotation:
42
+ print(f"{quotation.local_datetime}: {quotation.price} (vol {quotation.volume})")
43
+ else:
44
+ print("Nothing has traded yet today.")
45
+
46
+ asyncio.run(main())
47
+ ```
48
+
49
+ ### Using the Stock class
50
+
51
+ ```python
52
+ from settfex.services.set import Stock
53
+
54
+ stock = Stock("CPALL")
55
+ data = await stock.get_chart_quotation(period="1D") # full ChartQuotation series
56
+ latest = await stock.get_latest_price() # latest traded Quotation (or None)
57
+ ```
58
+
59
+ ### Using the convenience function
60
+
61
+ ```python
62
+ from settfex.services.set import get_chart_quotation
63
+
64
+ data = await get_chart_quotation("JAS-W4", period="1D") # hyphenated warrant symbol is fine
65
+ print(f"Prior close: {data.prior}, points: {len(data.quotations)}")
66
+ print(f"Latest price (scalar, prior-fallback): {data.get_latest_price()}")
67
+ ```
68
+
69
+ ### Using the service class
70
+
71
+ ```python
72
+ from settfex.services.set.stock import ChartQuotationService
73
+
74
+ service = ChartQuotationService()
75
+ data = await service.fetch_chart_quotation("CPALL", period="1D", accumulated=False)
76
+ raw = await service.fetch_chart_quotation_raw("CPALL") # unvalidated dict
77
+ ```
78
+
79
+ ## Latest-price selection rule
80
+
81
+ `ChartQuotation.get_latest_quotation(as_of=None)` returns the quotation with the **greatest
82
+ timestamp that is `<= as_of` and has a non-null `volume`** — i.e. the latest minute that actually
83
+ traded. This matters because the API returns one bucket per minute for the **whole session up to
84
+ end of day**; minutes that are in the future, fall in the lunch `intermission`, or simply had no
85
+ trade carry `volume = null` (the `price` may still be carried forward).
86
+
87
+ - **`as_of`** defaults to **now in `Asia/Bangkok`**. A naive `as_of` is treated as Bangkok local
88
+ time; an aware `as_of` (e.g. UTC) is converted. Naive and aware datetimes are never compared
89
+ directly — both sides are normalized to Bangkok first.
90
+ - **`get_latest_price(as_of=None)`** (on the model) returns that quotation's `price`, **falling
91
+ back to `prior`** (the previous session's close) when nothing has traded yet, or `None` if
92
+ `prior` is also unavailable.
93
+ - **Top-level `get_latest_price(symbol, …)`** returns the full **`Quotation`** object (time, price,
94
+ volume, change), or `None` — a single request, an O(n) scan, no re-fetching.
95
+
96
+ | Situation | `get_latest_quotation` | `model.get_latest_price` |
97
+ |---|---|---|
98
+ | Mid-session, after trades | latest traded `Quotation` | its `price` |
99
+ | Before the open / all-null series | `None` | `prior` (fallback) |
100
+ | `as_of` inside the lunch break | last morning trade | its `price` |
101
+ | Empty series, `prior` set | `None` | `prior` |
102
+ | Empty series, `prior` is `None` | `None` | `None` |
103
+
104
+ ## API Reference
105
+
106
+ ### Models
107
+
108
+ #### `Quotation`
109
+
110
+ | Field | Type | JSON alias | Description |
111
+ |-------|------|-----------|-------------|
112
+ | `quote_datetime` | `datetime` | `datetime` | Timestamp, timezone-aware (`+07:00`) |
113
+ | `local_datetime` | `datetime` | `localDatetime` | Same instant, naive Bangkok local time |
114
+ | `price` | `float \| None` | `price` | Trade price (may be carried forward on no-trade) |
115
+ | `volume` | `float \| None` | `volume` | Trade volume (shares); `None` = no trade this bucket |
116
+ | `value` | `float \| None` | `value` | Trade value (THB) |
117
+ | `change` | `float \| None` | `change` | Price change from prior close |
118
+ | `percent_change` | `float \| None` | `percentChange` | Percentage change from prior close |
119
+
120
+ #### `Intermission`
121
+
122
+ | Field | Type | Description |
123
+ |-------|------|-------------|
124
+ | `begin` | `datetime` | Intermission start (e.g. lunch break), naive Bangkok |
125
+ | `end` | `datetime` | Intermission end, naive Bangkok |
126
+
127
+ #### `ChartQuotation`
128
+
129
+ | Field / Method | Type | Description |
130
+ |----------------|------|-------------|
131
+ | `prior` | `float \| None` | Prior trading day's close |
132
+ | `intermissions` | `list[Intermission]` | Trading intermission periods |
133
+ | `quotations` | `list[Quotation]` | Per-interval quotation points |
134
+ | `get_latest_quotation(as_of=None)` | `Quotation \| None` | Latest *traded* quotation at/before `as_of` |
135
+ | `get_latest_price(as_of=None)` | `float \| None` | Latest traded price, falling back to `prior` |
136
+
137
+ ### Service Class — `ChartQuotationService`
138
+
139
+ | Method | Returns | Description |
140
+ |--------|---------|-------------|
141
+ | `fetch_chart_quotation(symbol, period="1D", accumulated=False)` | `ChartQuotation` | Validated model |
142
+ | `fetch_chart_quotation_raw(symbol, period="1D", accumulated=False)` | `dict` | Unvalidated raw dict |
143
+
144
+ ### Convenience Functions
145
+
146
+ | Function | Returns | Description |
147
+ |----------|---------|-------------|
148
+ | `get_chart_quotation(symbol, period="1D", accumulated=False, config=None)` | `ChartQuotation` | One-line fetch |
149
+ | `get_latest_price(symbol, period="1D", accumulated=False, as_of=None, config=None)` | `Quotation \| None` | Latest traded quotation vs `as_of` |
150
+
151
+ ## Usage Examples
152
+
153
+ ### Example 1 — "What's the price right now?"
154
+
155
+ ```python
156
+ from settfex.services.set import get_latest_price
157
+
158
+ q = await get_latest_price("PTT")
159
+ print("No trades yet" if q is None else f"{q.price} @ {q.local_datetime} (vol {q.volume})")
160
+ ```
161
+
162
+ ### Example 2 — Price as of a specific instant
163
+
164
+ ```python
165
+ from datetime import datetime
166
+ from settfex.services.set import get_latest_price
167
+
168
+ # Naive datetime is interpreted as Asia/Bangkok local time
169
+ q = await get_latest_price("CPALL", as_of=datetime(2026, 6, 19, 11, 0))
170
+ print(q.price if q else "Nothing traded by 11:00")
171
+ ```
172
+
173
+ ### Example 3 — Build a simple intraday table
174
+
175
+ ```python
176
+ from settfex.services.set import get_chart_quotation
177
+
178
+ data = await get_chart_quotation("CPALL", period="1D")
179
+ for q in data.quotations:
180
+ if q.volume is not None: # skip null/no-trade buckets
181
+ print(f"{q.local_datetime:%H:%M} {q.price:>8} {q.volume:>12,.0f}")
182
+ ```
183
+
184
+ ## Error Handling & Troubleshooting
185
+
186
+ - **Empty symbol** → `ValueError`.
187
+ - **Non-200 HTTP response** → `Exception` with the status code (e.g. `HTTP 403`).
188
+ - **`get_latest_price` returns `None`** → nothing has traded by `as_of` (before the open, a holiday,
189
+ or an all-null series). Use the model's `get_latest_price()` for the `prior`-fallback scalar.
190
+ - **Markets closed** → the `1D` series for the most recent session is returned; the latest traded
191
+ point is that session's last trade.