fin68 0.1.3__cp310-cp310-macosx_10_9_universal2.whl

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 (55) hide show
  1. fin68/__init__.py +323 -0
  2. fin68/__init__.pyi +81 -0
  3. fin68/_core/__init__.py +0 -0
  4. fin68/_version.py +17 -0
  5. fin68/auth/__init__.py +0 -0
  6. fin68/auth/api_key.py +0 -0
  7. fin68/clients/__init__.py +3 -0
  8. fin68/clients/base.py +17 -0
  9. fin68/clients/eod/MarketEod.py +142 -0
  10. fin68/clients/eod/MarketEod.pyi +95 -0
  11. fin68/clients/eod/SectorEod.py +146 -0
  12. fin68/clients/eod/SectorEod.pyi +86 -0
  13. fin68/clients/eod/StockEod.py +134 -0
  14. fin68/clients/eod/StockEod.pyi +92 -0
  15. fin68/clients/eod/__init__.py +6 -0
  16. fin68/clients/eod/helper.py +65 -0
  17. fin68/clients/eod/icb_types.py +111 -0
  18. fin68/clients/eod_main.py +169 -0
  19. fin68/clients/eod_main.pyi +102 -0
  20. fin68/clients/financials.py +0 -0
  21. fin68/clients/index.py +0 -0
  22. fin68/clients/info.py +0 -0
  23. fin68/config.py +0 -0
  24. fin68/exceptions.py +28 -0
  25. fin68/models/__init__.py +0 -0
  26. fin68/models/base.py +0 -0
  27. fin68/models/eod.py +0 -0
  28. fin68/models/financials.py +0 -0
  29. fin68/models/index.py +0 -0
  30. fin68/models/meta.py +0 -0
  31. fin68/transport/__init__.py +0 -0
  32. fin68/transport/cache.py +0 -0
  33. fin68/transport/http.py +0 -0
  34. fin68/transport/rate_limiter.py +0 -0
  35. fin68/types.py +90 -0
  36. fin68/utils/__init__.py +5 -0
  37. fin68/utils/_validate_date_eod.py +9 -0
  38. fin68/utils/convert.py +0 -0
  39. fin68/utils/pagination.py +0 -0
  40. fin68/utils/time.py +0 -0
  41. fin68/validators.py +0 -0
  42. fin68-0.1.3.dist-info/METADATA +129 -0
  43. fin68-0.1.3.dist-info/RECORD +55 -0
  44. fin68-0.1.3.dist-info/WHEEL +6 -0
  45. fin68-0.1.3.dist-info/licenses/LICENSE +21 -0
  46. fin68-0.1.3.dist-info/top_level.txt +2 -0
  47. private_core/__init__.py +1 -0
  48. private_core/auth_core.cpython-310-darwin.so +0 -0
  49. private_core/eod_core.cpython-310-darwin.so +0 -0
  50. private_core/financials_core.cpython-310-darwin.so +0 -0
  51. private_core/helpers.cpython-310-darwin.so +0 -0
  52. private_core/http_core.cpython-310-darwin.so +0 -0
  53. private_core/index_core.cpython-310-darwin.so +0 -0
  54. private_core/progress.cpython-310-darwin.so +0 -0
  55. private_core/utils_core.cpython-310-darwin.so +0 -0
fin68/__init__.py ADDED
@@ -0,0 +1,323 @@
1
+ from __future__ import annotations
2
+
3
+ """
4
+ Fin68 Python Client
5
+ ===================
6
+
7
+ Client cấp cao giúp xác thực API Key, hiển thị thông báo từ backend và cung cấp
8
+ các domain client như :class:`EodClient`. Thiết kế theo pattern "facade" để người
9
+ dùng khởi tạo một lần và làm việc qua thuộc tính `client.eod`, v.v.
10
+
11
+ Notes
12
+ -----
13
+ - Sử dụng `private_core.HttpSession` để gắn `base_url`, `timeout` và `user-agent`.
14
+ - Tự động gọi `validate_api_key` khi khởi tạo để nhận thông tin phiên bản, cảnh báo
15
+ và meta của API key.
16
+ - Tất cả cảnh báo/nhắc nâng cấp được hiển thị qua `warnings.warn` hoặc `logging`.
17
+
18
+ See Also
19
+ --------
20
+ EodClient
21
+ Domain client cho dữ liệu EOD.
22
+ """
23
+
24
+ import logging
25
+ import warnings
26
+ from typing import Optional
27
+
28
+ from private_core.auth_core import validate_api_key
29
+ from private_core.http_core import BASE_URL, DEFAULT_TIMEOUT, HttpSession
30
+
31
+ from ._version import __version__
32
+ from .clients import EodClient
33
+ from .exceptions import ApiKeyValidationError, ConfigurationError
34
+ from .types import ApiKeyMeta, ApiKeyValidationResponse, BackendMessage, MessageType
35
+
36
+ logger = logging.getLogger(__name__)
37
+
38
+
39
+ class Fin68Client:
40
+ """
41
+ Điểm truy cập chính tổng hợp các domain client của Fin68.
42
+
43
+ Khi khởi tạo, client sẽ:
44
+ 1) Tạo `HttpSession` với API key.
45
+ 2) Gọi `validate_api_key` để xác thực và lấy thông tin phiên bản.
46
+ 3) Hiển thị các thông báo (nâng cấp/báo lỗi/cảnh báo) từ backend tới người dùng.
47
+ 4) Khởi tạo các domain client (hiện tại: `eod`).
48
+
49
+ Parameters
50
+ ----------
51
+ api_key : str
52
+ API key hợp lệ do Fin68 phát hành.
53
+ extra_context : dict, optional
54
+ Ngữ cảnh bổ sung gửi lên backend khi validate (VD: môi trường chạy, app_name).
55
+
56
+ Attributes
57
+ ----------
58
+ eod : EodClient
59
+ Domain client cho dữ liệu EOD (OHLCV, technical, ...).
60
+ _session : HttpSession
61
+ Phiên HTTP dùng chung cho toàn bộ domain client.
62
+ _api_key_meta : ApiKeyMeta or None
63
+ Thông tin meta của API key sau khi xác thực.
64
+ _messages : list[BackendMessage]
65
+ Danh sách thông điệp backend trả về sau validate.
66
+
67
+ Raises
68
+ ------
69
+ ConfigurationError
70
+ Khi `api_key` rỗng/không được cung cấp.
71
+ ApiKeyValidationError
72
+ Khi xác thực API key thất bại (khóa sai/hết hạn/hệ thống từ chối).
73
+
74
+ Examples
75
+ --------
76
+ Khởi tạo và dùng như context manager:
77
+
78
+ >>> from fin68 import client
79
+ >>> with client(api_key="sk_live_...") as cli:
80
+ ... df = cli.eod.ohlcv("HPG", start="2024-01-01", end="2024-12-31")
81
+
82
+ Hoặc quản lý vòng đời thủ công:
83
+
84
+ >>> cli = client(api_key="sk_live_...")
85
+ >>> try:
86
+ ... meta = cli.api_key_metadata
87
+ ... data = cli.eod.ohlcv("VCB")
88
+ ... finally:
89
+ ... cli.close()
90
+ """
91
+
92
+ def __init__(
93
+ self,
94
+ api_key: str,
95
+ *,
96
+ extra_context: Optional[dict] = None,
97
+ ) -> None:
98
+ """
99
+ Khởi tạo client và xác thực API key ngay lập tức.
100
+
101
+ Parameters
102
+ ----------
103
+ api_key : str
104
+ API key hợp lệ.
105
+ extra_context : dict, optional
106
+ Context bổ sung gửi kèm khi validate.
107
+
108
+ Raises
109
+ ------
110
+ ConfigurationError
111
+ Khi không cung cấp API key.
112
+ ApiKeyValidationError
113
+ Khi backend từ chối API key (hết hạn, không hợp lệ, ...).
114
+ """
115
+ if not api_key:
116
+ raise ConfigurationError("Cần cung cấp API key để khởi tạo fin68.client()")
117
+
118
+ self._session = HttpSession(
119
+ api_key,
120
+ base_url=BASE_URL,
121
+ timeout=DEFAULT_TIMEOUT,
122
+ version=__version__,
123
+ )
124
+ self._api_key_meta: Optional[ApiKeyMeta] = None
125
+ self._messages: list[BackendMessage] = []
126
+
127
+ try:
128
+ validation = validate_api_key(
129
+ self._session,
130
+ client_version=__version__,
131
+ include_messages=True,
132
+ extra_context=extra_context,
133
+ )
134
+ except ApiKeyValidationError:
135
+ # Đảm bảo đóng session nếu validate thất bại
136
+ self._session.close()
137
+ raise
138
+ self._api_key_meta = validation.meta
139
+ self._messages = list(validation.iter_messages())
140
+
141
+ self._surface_messages(validation)
142
+
143
+ # Domain clients
144
+ self.eod = EodClient(self._session)
145
+
146
+ def _surface_messages(self, validation: ApiKeyValidationResponse) -> None:
147
+ """
148
+ Hiển thị các thông điệp từ backend (cảnh báo, lỗi, nhắc nâng cấp).
149
+
150
+ Parameters
151
+ ----------
152
+ validation : ApiKeyValidationResponse
153
+ Kết quả validate từ backend, bao gồm thông tin version và danh sách messages.
154
+
155
+ Notes
156
+ -----
157
+ - Các thông điệp loại `WARNING`, `ERROR`, `NOTIFY_API_KEY_EXPIRING/EXPIRED`
158
+ sẽ được hiển thị qua `warnings.warn(...)`.
159
+ - Thông báo nâng cấp (khi client cũ hơn phiên bản tối thiểu/khuyến nghị)
160
+ cũng được cảnh báo bằng `warnings.warn(...)` để người dùng sớm cập nhật.
161
+ """
162
+ version_info = validation.version
163
+ for message in self._messages:
164
+ if message.is_upgrade_notice() and version_info.requires_upgrade():
165
+ _emit_upgrade_notice(message, version_info.backend_version)
166
+ elif message.type in {
167
+ MessageType.NOTIFY_API_KEY_EXPIRING,
168
+ MessageType.NOTIFY_API_KEY_EXPIRED,
169
+ MessageType.WARNING,
170
+ MessageType.ERROR,
171
+ }:
172
+ warnings.warn(message.message, stacklevel=2)
173
+ else:
174
+ logger.info("Thông báo từ Fin68: %s", message.message)
175
+
176
+ if version_info.requires_upgrade() and not any(m.is_upgrade_notice() for m in self._messages):
177
+ warnings.warn(
178
+ (
179
+ f"Đã có phiên bản fin68 mới hơn ({version_info.backend_version}). "
180
+ "Hãy cập nhật để sử dụng các tính năng và bản sửa lỗi mới nhất."
181
+ ),
182
+ stacklevel=2,
183
+ )
184
+
185
+ if version_info.is_out_of_support():
186
+ warnings.warn(
187
+ (
188
+ f"Phiên bản fin68 hiện tại ({version_info.client_version}) "
189
+ f"đã thấp hơn mức tối thiểu được hỗ trợ ({version_info.minimum_supported_version}). "
190
+ "Một số tính năng có thể không còn hoạt động ổn định."
191
+ ),
192
+ stacklevel=2,
193
+ )
194
+
195
+ @property
196
+ def api_key_metadata(self) -> Optional[ApiKeyMeta]:
197
+ """
198
+ Trả về meta của API key sau khi xác thực.
199
+
200
+ Returns
201
+ -------
202
+ ApiKeyMeta or None
203
+ Meta của API key (ngày hết hạn, trạng thái, ...), hoặc `None` nếu không sẵn có.
204
+ """
205
+ return self._api_key_meta
206
+
207
+ @property
208
+ def messages(self) -> list[BackendMessage]:
209
+ """
210
+ Danh sách thông điệp backend trả về khi validate.
211
+
212
+ Returns
213
+ -------
214
+ list[BackendMessage]
215
+ Bản sao danh sách thông điệp để tránh chỉnh sửa ngoài ý muốn.
216
+ """
217
+ return list(self._messages)
218
+
219
+ def close(self) -> None:
220
+ """
221
+ Đóng phiên HTTP bên dưới và giải phóng tài nguyên.
222
+
223
+ Notes
224
+ -----
225
+ - Nên gọi trong `finally` hoặc dùng context manager để đảm bảo đóng phiên.
226
+ """
227
+ self._session.close()
228
+
229
+ def __enter__(self) -> "Fin68Client":
230
+ """
231
+ Vào ngữ cảnh `with`.
232
+
233
+ Returns
234
+ -------
235
+ Fin68Client
236
+ Chính instance hiện tại.
237
+ """
238
+ return self
239
+
240
+ def __exit__(self, exc_type, exc, tb) -> None:
241
+ """
242
+ Thoát ngữ cảnh `with` và tự động đóng phiên HTTP.
243
+
244
+ Parameters
245
+ ----------
246
+ exc_type, exc, tb
247
+ Thông tin ngoại lệ (nếu có). Không can thiệp vào flow ngoại lệ.
248
+ """
249
+ self.close()
250
+
251
+
252
+ def _emit_upgrade_notice(message: BackendMessage, latest_version: Optional[str]) -> None:
253
+ """
254
+ Hiển thị cảnh báo nâng cấp phiên bản client.
255
+
256
+ Parameters
257
+ ----------
258
+ message : BackendMessage
259
+ Thông điệp nâng cấp từ backend.
260
+ latest_version : str or None
261
+ Phiên bản mới nhất phía backend biết đến (nếu có).
262
+
263
+ Notes
264
+ -----
265
+ - Sử dụng `warnings.warn(..., stacklevel=3)` để đẩy cảnh báo lên callsite người dùng.
266
+ """
267
+ suffix = f" Phiên bản mới nhất: {latest_version}" if latest_version else ""
268
+ warnings.warn(f"{message.message}{suffix}", stacklevel=3)
269
+
270
+
271
+ def client(
272
+ apiKey: Optional[str] = None,
273
+ *,
274
+ api_key: Optional[str] = None,
275
+ extra_context: Optional[dict] = None,
276
+ ) -> Fin68Client:
277
+ """
278
+ Factory tạo ra một :class:`Fin68Client` đã sẵn sàng sử dụng.
279
+
280
+ Hỗ trợ cả hai tên tham số `api_key` (chuẩn Python) và `apiKey` (thân thiện
281
+ với người dùng/JS). Nếu truyền cả hai, `api_key` sẽ được ưu tiên.
282
+
283
+ Parameters
284
+ ----------
285
+ apiKey : str, optional
286
+ API key. Chỉ dùng khi không truyền `api_key`.
287
+ api_key : str, optional
288
+ API key chuẩn (ưu tiên nếu được truyền).
289
+ extra_context : dict, optional
290
+ Ngữ cảnh bổ sung gửi lên backend khi validate.
291
+
292
+ Returns
293
+ -------
294
+ Fin68Client
295
+ Client đã xác thực xong, kèm các domain client như `eod`.
296
+
297
+ Raises
298
+ ------
299
+ ConfigurationError
300
+ Khi không cung cấp `api_key`/`apiKey`.
301
+ ApiKeyValidationError
302
+ Khi backend từ chối API key trong quá trình khởi tạo.
303
+
304
+ Examples
305
+ --------
306
+ >>> from fin68 import client
307
+ >>> cli = client(api_key="sk_live_...")
308
+ >>> try:
309
+ ... df = cli.eod.ohlcv("HPG", start="2024-01-01", end="2024-06-30")
310
+ ... finally:
311
+ ... cli.close()
312
+ """
313
+ key = api_key or apiKey
314
+ if not key:
315
+ raise ConfigurationError("Cần truyền tham số apiKey hoặc api_key khi khởi tạo fin68.client()")
316
+
317
+ return Fin68Client(
318
+ key,
319
+ extra_context=extra_context,
320
+ )
321
+
322
+
323
+ __all__ = ["Fin68Client", "client", "__version__"]
fin68/__init__.pyi ADDED
@@ -0,0 +1,81 @@
1
+ from typing import Optional, List, Dict
2
+ from .clients import EodClient
3
+ from .types import ApiKeyMeta, BackendMessage
4
+
5
+ __version__: str
6
+
7
+ class Fin68Client:
8
+ """
9
+ Client chính của thư viện Fin68 — chịu trách nhiệm xác thực API key,
10
+ khởi tạo phiên làm việc HTTP, và cung cấp các domain client như `eod`.
11
+ """
12
+
13
+ eod: EodClient
14
+ """
15
+ Domain client cho dữ liệu EOD (End-of-Day).
16
+
17
+ Cung cấp ba namespace chính:
18
+ - **stock** (`StockEod`) → Dữ liệu EOD cấp cổ phiếu, bao gồm:
19
+ - OHLCV (Open, High, Low, Close, Volume)
20
+ - Các chỉ báo kỹ thuật (MA, RSI, MACD, Bollinger, v.v.)
21
+ - Thống kê biến động và hiệu suất theo mã
22
+
23
+ - **market** (`MarketEod`) → Dữ liệu chỉ số thị trường (VNINDEX, HNXINDEX, UPCOMINDEX):
24
+ - Giá đóng cửa, khối lượng, vốn hóa, PE/PB toàn thị trường
25
+ - So sánh diễn biến giữa các sàn
26
+
27
+ - **sector** (`SectorEod`) → Dữ liệu EOD cấp ngành (ICB, VNSector...):
28
+ - Hiệu suất ngành, thay đổi vốn hóa, tỷ trọng thanh khoản
29
+ - So sánh ngành/tiểu ngành theo thời gian
30
+
31
+ Examples
32
+ --------
33
+ >>> from fin68 import client
34
+ >>> cli = client(api_key="sk_live_...")
35
+ >>> cli.eod.stock.ohlcv("HPG", start="2024-01-01", end="2024-06-30")
36
+ >>> cli.eod.market.ohlcv("VNINDEX", start="2024-01-01", end="2024-06-30")
37
+ >>> cli.eod.sector.ohlcv("Ngân hàng", start="2024-01-01", end="2024-06-30")
38
+
39
+
40
+ """
41
+
42
+ _api_key_meta: Optional[ApiKeyMeta]
43
+ """Thông tin meta của API key sau khi xác thực."""
44
+
45
+ _messages: List[BackendMessage]
46
+ """Danh sách thông điệp backend trả về sau validate."""
47
+
48
+ def __init__(
49
+ self,
50
+ api_key: str,
51
+ *,
52
+ extra_context: Optional[Dict] = None,
53
+ ) -> None: ...
54
+ """Khởi tạo Fin68Client và tự động xác thực API key."""
55
+
56
+ @property
57
+ def api_key_metadata(self) -> Optional[ApiKeyMeta]: ...
58
+ """Trả về meta của API key sau khi xác thực."""
59
+
60
+ @property
61
+ def messages(self) -> List[BackendMessage]: ...
62
+ """Danh sách thông điệp backend trả về khi validate."""
63
+
64
+ def close(self) -> None: ...
65
+ """Đóng session HTTP và giải phóng tài nguyên."""
66
+
67
+ def __enter__(self) -> "Fin68Client": ...
68
+ """Vào context manager (`with`)."""
69
+
70
+ def __exit__(self, exc_type, exc, tb) -> None: ...
71
+ """Thoát context manager (`with`) và tự động đóng session."""
72
+
73
+ def client(
74
+ apiKey: Optional[str] = None,
75
+ *,
76
+ api_key: Optional[str] = None,
77
+ extra_context: Optional[Dict] = None,
78
+ ) -> Fin68Client: ...
79
+ """Factory khởi tạo và trả về một Fin68Client đã sẵn sàng sử dụng."""
80
+
81
+ __all__: list[str]
File without changes
fin68/_version.py ADDED
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ try:
4
+ from importlib.metadata import PackageNotFoundError, version
5
+ except ImportError: # pragma: no cover - Python <3.8
6
+ from importlib_metadata import PackageNotFoundError, version # type: ignore
7
+
8
+
9
+ def _resolve_version() -> str:
10
+ try:
11
+ return version("fin68")
12
+ except PackageNotFoundError:
13
+ return "0.0.0"
14
+
15
+
16
+ __all__ = ["__version__"]
17
+ __version__ = _resolve_version()
fin68/auth/__init__.py ADDED
File without changes
fin68/auth/api_key.py ADDED
File without changes
@@ -0,0 +1,3 @@
1
+ from .eod_main import EodClient
2
+
3
+ __all__ = ["EodClient"]
fin68/clients/base.py ADDED
@@ -0,0 +1,17 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ if TYPE_CHECKING:
6
+ from private_core.http_core import HttpSession
7
+
8
+
9
+ class BaseClient:
10
+ """Base class for all Fin68 client modules providing shared HTTP session access."""
11
+
12
+ def __init__(self, session: "HttpSession") -> None:
13
+ self._session = session
14
+
15
+ @property
16
+ def session(self) -> "HttpSession":
17
+ return self._session
@@ -0,0 +1,142 @@
1
+ from __future__ import annotations
2
+
3
+ """
4
+ fin68.clients.eod.market
5
+ ========================
6
+
7
+ Cung cấp lớp `MarketEod` — tập hợp các endpoint EOD (End-of-Day)
8
+ dành cho **chỉ số thị trường** như VNINDEX, VN30, HNX, HNX30 và UPCOM.
9
+
10
+ Notes
11
+ -----
12
+ - Dữ liệu được lấy từ `private_core.eod_core.fetch_ohlcv()`.
13
+ - Tất cả kết quả được trả về dưới dạng `pandas.DataFrame`.
14
+ - Có thể truy vấn nhiều chỉ số cùng lúc.
15
+
16
+ """
17
+
18
+ from typing import TYPE_CHECKING, Sequence, Union, Literal
19
+ from private_core import eod_core
20
+ from ..base import BaseClient
21
+ from .helper import DateStr, Interval, _EodMixin
22
+
23
+ if TYPE_CHECKING:
24
+ from pandas import DataFrame
25
+ from private_core.http_core import HttpSession
26
+
27
+ IndexArg = Union[
28
+ Literal["VNINDEX", "VN30", "HNX", "HNX30", "UPCOM"],
29
+ Sequence[Literal["VNINDEX", "VN30", "HNX", "HNX30", "UPCOM"]],
30
+ ]
31
+ INDEXS = {"VNINDEX", "VN30", "HNX", "HNX30", "UPCOM"}
32
+
33
+ class MarketEod(BaseClient, _EodMixin):
34
+ """
35
+ Cung cấp các endpoint EOD cho các chỉ số thị trường (Market Indices).
36
+
37
+ Bao gồm các chỉ số phổ biến:
38
+ - **VNINDEX**: chỉ số toàn thị trường HOSE
39
+ - **VN30**: nhóm 30 cổ phiếu vốn hóa lớn trên HOSE
40
+ - **HNX**, **HNX30**: chỉ số sàn Hà Nội
41
+ - **UPCOM**: thị trường UPCOM
42
+
43
+ Parameters
44
+ ----------
45
+ session : HttpSession
46
+ Phiên HTTP đã xác thực, được truyền từ `EodClient`.
47
+
48
+ Notes
49
+ -----
50
+ - Hỗ trợ nhiều chỉ số trong cùng một lệnh truy vấn (`list[str]`).
51
+ - Tự động kiểm tra tính hợp lệ của khoảng thời gian (`start <= end`).
52
+ - Có thể lấy dữ liệu theo nhiều `interval` (ngày, tuần, tháng, v.v.).
53
+ - Trả về dữ liệu OHLCV (Open, High, Low, Close, Volume) của chỉ số.
54
+ """
55
+
56
+ def __init__(self, session: "HttpSession") -> None:
57
+ """
58
+ Khởi tạo `MarketEod` với session HTTP đã xác thực.
59
+
60
+ Parameters
61
+ ----------
62
+ session : HttpSession
63
+ Phiên HTTP được chia sẻ từ `EodClient`.
64
+ """
65
+ super().__init__(session=session)
66
+
67
+ def ohlcv(
68
+ self,
69
+ symbol: IndexArg,
70
+ start: DateStr = None,
71
+ end: DateStr = None,
72
+ interval: Interval = "1D",
73
+ ) -> "DataFrame":
74
+ """
75
+ Lấy dữ liệu OHLCV (Open, High, Low, Close, Volume) cho một hoặc nhiều chỉ số thị trường.
76
+
77
+ Parameters
78
+ ----------
79
+ symbol : {"VNINDEX", "VN30", "HNX", "HNX30", "UPCOM"} or Sequence
80
+ Chỉ số hoặc danh sách chỉ số cần lấy dữ liệu.
81
+ start : str, optional
82
+ Ngày bắt đầu, định dạng `"YYYY-MM-DD"`.
83
+ Nếu không truyền, hệ thống sẽ chọn mặc định (thường là 1 năm gần nhất).
84
+ end : str, optional
85
+ Ngày kết thúc, định dạng `"YYYY-MM-DD"`.
86
+ Nếu không truyền, mặc định là ngày hiện tại.
87
+ interval : {"1D", "1W", "1M", "3M", "6M", "1Y"}, default "1D"
88
+ Khoảng thời gian dữ liệu:
89
+ - `"1D"`: theo ngày
90
+ - `"1W"`: theo tuần
91
+ - `"1M"`: theo tháng
92
+ - `"3M"`, `"6M"`, `"1Y"`: theo quý, nửa năm hoặc năm
93
+
94
+ Returns
95
+ -------
96
+ DataFrame
97
+ Bảng dữ liệu OHLCV, gồm các cột:
98
+ - `symbol`: tên chỉ số
99
+ - `Date`: ngày giao dịch
100
+ - `Open`, `High`, `Low`, `Close`, `Volume`
101
+
102
+ Raises
103
+ ------
104
+ ValueError
105
+ Nếu ngày bắt đầu > ngày kết thúc hoặc interval không hợp lệ.
106
+ ApiRequestError
107
+ Nếu backend trả lỗi trong quá trình gọi API.
108
+
109
+ Examples
110
+ --------
111
+ >>> from fin68 import client
112
+ >>> cli = client(api_key="sk_live_...")
113
+ >>> df = cli.eod.market.ohlcv("VNINDEX", start="2024-01-01", end="2024-06-30")
114
+ >>> df.head()
115
+ symbol Date Open High Low Close Volume
116
+ 0 VNINDEX 2024-01-02 1120.0 1125.6 1110.2 1119.4 612000000
117
+
118
+ >>> # Lấy đồng thời nhiều chỉ số
119
+ >>> cli.eod.market.ohlcv(["VNINDEX", "VN30", "HNX"])
120
+
121
+ Notes
122
+ -----
123
+ - Có thể dùng để so sánh biến động giữa các chỉ số thị trường.
124
+ - Hàm này là wrapper của `private_core.eod_core.fetch_ohlcv`.
125
+ """
126
+ if not isinstance(symbol, list):
127
+ if symbol.upper() not in INDEXS:
128
+ raise ValueError(f"Chỉ số '{symbol}' không hợp lệ. Vui lòng chọn trong {INDEXS}")
129
+ else:
130
+ for idx in symbol:
131
+ if idx.upper() not in INDEXS:
132
+ raise ValueError(f"Chỉ số '{idx}' không hợp lệ. Vui lòng chọn trong {INDEXS}")
133
+ start_date, end_date = self._normalize_range(start, end)
134
+ interval_value = self._validate_interval(interval)
135
+ payload = eod_core.fetch_ohlcv(
136
+ self.session,
137
+ symbol,
138
+ start=start_date,
139
+ end=end_date,
140
+ interval=interval_value,
141
+ )
142
+ return self._to_dataframe(payload)
@@ -0,0 +1,95 @@
1
+ from __future__ import annotations
2
+
3
+ """
4
+ fin68.clients.eod.market (stub)
5
+ ===============================
6
+
7
+ Type stub cho lớp `MarketEod` — phục vụ autocomplete/typing trong IDE và
8
+ static type checker. Không thực thi logic, chỉ khai báo kiểu.
9
+
10
+ Ghi chú
11
+ -------
12
+ - Docstring giữ phong cách NumPy, giúp IDE hiển thị tooltip rõ ràng.
13
+ - Thân hàm dùng `...` theo chuẩn stub.
14
+ """
15
+
16
+ from typing import TYPE_CHECKING, Sequence, Union, Literal
17
+ from ..base import BaseClient
18
+ from .helper import DateStr, Interval, _EodMixin
19
+
20
+ if TYPE_CHECKING:
21
+ from pandas import DataFrame
22
+ from private_core.http_core import HttpSession
23
+
24
+ IndexArg = Union[
25
+ Literal["VNINDEX", "VN30", "HNX", "HNX30", "UPCOM"],
26
+ Sequence[Literal["VNINDEX", "VN30", "HNX", "HNX30", "UPCOM"]],
27
+ ]
28
+
29
+
30
+ class MarketEod(BaseClient, _EodMixin):
31
+ """
32
+ Endpoint EOD cho các chỉ số thị trường (VNINDEX, VN30, HNX, HNX30, UPCOM).
33
+
34
+ Parameters
35
+ ----------
36
+ session : HttpSession
37
+ Phiên HTTP đã xác thực, được truyền từ `EodClient`.
38
+
39
+ Notes
40
+ -----
41
+ - Hỗ trợ truy vấn nhiều chỉ số cùng lúc (list).
42
+ - Tự động chuẩn hóa khoảng thời gian và validate `interval`.
43
+ - Trả về dữ liệu OHLCV (Open, High, Low, Close, Volume) của chỉ số.
44
+ """
45
+
46
+ def __init__(self, session: "HttpSession") -> None: ...
47
+ """
48
+ Khởi tạo `MarketEod` với session HTTP đã xác thực.
49
+
50
+ Parameters
51
+ ----------
52
+ session : HttpSession
53
+ Phiên HTTP chia sẻ từ `EodClient`.
54
+ """
55
+
56
+ def ohlcv(
57
+ self,
58
+ symbol: IndexArg,
59
+ start: DateStr = None,
60
+ end: DateStr = None,
61
+ interval: Interval = "1D",
62
+ ) -> "DataFrame": ...
63
+ """
64
+ Lấy dữ liệu OHLCV cho một hoặc nhiều chỉ số thị trường.
65
+
66
+ Parameters
67
+ ----------
68
+ symbol : {"VNINDEX", "VN30", "HNX", "HNX30", "UPCOM"} or Sequence
69
+ Chỉ số hoặc danh sách chỉ số cần lấy dữ liệu.
70
+ start : str, optional
71
+ Ngày bắt đầu, định dạng "YYYY-MM-DD". Nếu `None`, dùng mặc định hệ thống.
72
+ end : str, optional
73
+ Ngày kết thúc, định dạng "YYYY-MM-DD". Nếu `None`, mặc định là hôm nay.
74
+ interval : {"1D", "1W", "1M", "3M", "6M", "1Y"}, default "1D"
75
+ Khoảng thời gian lấy dữ liệu.
76
+
77
+ Returns
78
+ -------
79
+ DataFrame
80
+ Bảng dữ liệu gồm: `symbol`, `Date`, `Open`, `High`, `Low`, `Close`, `Volume`.
81
+
82
+ Raises
83
+ ------
84
+ ValueError
85
+ Nếu `start > end` hoặc `interval` không hợp lệ.
86
+ ApiRequestError
87
+ Nếu backend trả lỗi khi gọi API.
88
+
89
+ Examples
90
+ --------
91
+ >>> from fin68 import client
92
+ >>> cli = client(api_key="sk_live_...")
93
+ >>> cli.eod.market.ohlcv("VNINDEX", start="2024-01-01", end="2024-06-30")
94
+ >>> cli.eod.market.ohlcv(["VNINDEX", "VN30", "HNX"])
95
+ """