Changelog finlens Python: nhật ký thay đổi thư viện
Nhật ký thay đổi thư viện Python finlens: bản 1.4.0 thêm quỹ đầu tư, trái phiếu, thuyết minh BCTC và tỷ lệ chuyển đổi chứng quyền.
Nhật ký thay đổi của thư viện Python finlens. Bản mới nhất là 1.2.0 —
kiểm bằng finlens.__version__.
Các mục 1.0.0a1 đến 1.0.0a7 bên dưới là bản thử nghiệm, chỉ phát hành trên
TestPyPI và chưa bao giờ có trên PyPI. Nếu bạn cài bằng pip install finlens
thì bản đầu tiên bạn nhận được là 1.1.1; các mục alpha giữ lại để tra cứu
lịch sử chứ không phải để nâng cấp theo.
Dòng 1.x là một bản viết lại, không phải một bản nâng cấp tiệm tiến. Code viết cho 0.1.x sẽ không chạy nguyên trạng; bảng ngay dưới đây là danh sách đầy đủ những chỗ gãy.
Nâng cấp từ 0.1.x — mọi chỗ gãy
Năm nhóm. Gần như tất cả đều ném lỗi ngay nên bạn thấy liền — trừ một dòng
duy nhất: đơn vị của net_active_value đổi mà không có exception nào. Đó là
dòng đáng đọc kỹ nhất trong cả trang.
1. Đổi tên
| Bản 0.1.x | Bản 1.x |
|---|---|
client.eod.market | client.eod.index |
client.reporting | client.financials |
Module finlens.types | finlens.typing |
Cả ba đều ném AttributeError hoặc ModuleNotFoundError, nên không có kịch bản
nào chạy tiếp với dữ liệu sai.
2. Đổi chữ ký
| Bản 0.1.x | Bản 1.x |
|---|---|
Tham số symbol (số ít) | symbols; ở cấp ngành là icb |
ohlcv("HPG", start, end, "1W") | Chỉ tham số đầu tiên là positional, phần còn lại keyword-only |
Ngành nhận tên tiếng Việt "Ngân hàng" | Ngành nhận mã ICB dạng chuỗi: "8600" |
statement(statement="balance", period="year") | statement(kind="balance_sheet", period="annual") |
Luật keyword-only là hàng rào cho một lớp bug đã xảy ra thật: ở 0.1.x một chuỗi interval truyền theo vị trí có thể rơi vào một tham số boolean, không lỗi và dữ liệu vẫn về.
3. Đổi từ vựng interval
interval="1M" và interval="1m" đều bị từ chối bằng
finlens.InvalidIntervalError với mã FL_VALIDATION_INTERVAL_AMBIGUOUS. Trong
từ vựng cũ hai giá trị này chỉ khác nhau ở chữ hoa nhưng lệch nhau 43.200 lần.
- Dữ liệu cuối ngày:
1d·1w·1mo·3mo·6mo·1y - Dữ liệu trong phiên:
1min·5min·15min·30min·1h·4h
4. Đổi hình dạng dữ liệu — nhóm im lặng nhất
| Bản 0.1.x | Bản 1.x |
|---|---|
Cột Date, Open, Close, Volume | snake_case viết thường: date, open, close, volume |
Bảng dòng tiền không có cột group | luôn có group, kể cả khi chỉ hỏi một nhóm |
| Kết quả rỗng có thể thiếu cột | Frame luôn đủ cột đúng dtype, kể cả 0 dòng |
net_active_value gọi là "tỷ đồng" | VND thô — xem mục 1.0.0 |
df["Date"] ném KeyError chắc chắn chứ không thỉnh thoảng: vì frame rỗng cũng
đủ cột đúng dtype, không có đường nào để một tên cột viết hoa lọt qua.
Đơn vị của net_active_value là chỗ duy nhất không ném lỗi khi bạn đọc sai.
Đọc df.attrs["finlens"]["units"] thay vì giả định.
5. Đổi môi trường
| Bản 0.1.x | Bản 1.x |
|---|---|
| Python ≥ 3.10 | Python ≥ 3.11 |
| pandas ≥ 2.1.2 | pandas ≥ 3.0 |
requests | httpx |
pydantic là phụ thuộc bắt buộc | đã gỡ — còn đúng ba: pandas, httpx, packaging |
| Khoá API không tiền tố | Khoá mới có tiền tố flk_ |
Trên Python 3.10 hoặc cũ hơn, pip install finlens không báo lỗi — nó lặng
lẽ lùi về một bản 0.1.x. Chi tiết ở Cài đặt.
1.4.0 — 11/08/2026
Bốn nhóm dữ liệu mới, và bốn chỗ sửa mà bạn thấy được ngay.
Thêm — client.funds.*: quỹ đầu tư
188 quỹ Việt Nam. NAV theo ngày từ 1995, danh mục nắm giữ theo tháng từ 2014, và phép tra ngược: mã này đang được quỹ nào nắm.
client.funds.list(fund_type="Quỹ ETF") # 32 quỹ ETF
client.funds.nav("E1VFVN30", start="2026-07-01")
client.funds.holders("HPG") # 80 quỹ đang nắm HPG
client.funds.managers("E1VFVN30")Mã quỹ không phải mã chứng khoán
Nó giữ nguyên hoa thường và chứa được cả dấu cách lẫn dấu + — A+ Fund,
ACBC-AGF, CSOP FTSE VN. Gõ đúng như cột fund_code của funds.list().
Và nó không duy nhất: VVDIF thuộc hai tổ chức. Lời gọi sẽ ném
finlens.AmbiguousFundError, và ngoại lệ ấy mang theo organization_ids để bạn
chọn đúng quỹ.
Tiền tệ là VND thô, khác nghìn VND của giá cổ phiếu. Bốn cột _ratio nằm
thang 0–1, không phải phần trăm.
Thêm — client.bonds.*: trái phiếu doanh nghiệp
6.820 lô trái phiếu của 940 tổ chức phát hành: điều khoản từng lô, dư nợ đang lưu hành, và hồ sơ tổ chức đã tổng hợp sẵn.
client.bonds.list(sector="Bất động sản", outstanding=True)
client.bonds.issuers(has_stock=True)Cột tiền đi theo CẶP `_mvnd` / `_usd`
Có 23 lô phát hành bằng USD bên cạnh 6.795 lô bằng VNĐ, và mệnh giá
100.000 tồn tại ở cả hai tiền tệ. Nên mỗi cột tiền có hai bản — mỗi dòng
đúng một cái khác null — thay vì một cột trần mà mean() trộn hai đơn vị lệch
nhau 26.000 lần.
Thư viện không quy đổi USD sang VND: không có bảng tỷ giá nào trong nguồn.
Thêm — client.financials.notes(): thuyết minh BCTC và phân nhóm nợ
Chi tiết mà bốn báo cáo chính không mang: dư nợ chia theo năm nhóm chất lượng nợ theo Thông tư 11, cho vay theo ngành, tiền gửi theo loại, thu dịch vụ từng khoản. 22 nhóm trên 30 tổ chức tín dụng, 2006 → nay.
df = client.financials.notes("VCB", section="loan_quality", start_year=2025)
df[["code", "parent_code", "line_item", "value"]]
client.financials.note_sections() # 22 nhóm, kèm độ phủ đo lúc gọicode là số thứ tự in trong chính bản BCTC (4.1, 4.2) — nó khớp đúng
dòng trong bản PDF bạn mở cạnh. parent_code đi kèm mỗi dòng nên dựng lại cây
bằng một groupby.
Ba điều dễ đọc sai
quarter = 0là CẢ NĂM, không phải "thiếu quý". Lọcdf["quarter"] > 0là cách nhanh nhất để mất sạch số liệu năm, im lặng.- Dòng cả năm bằng đúng quý 4 với số dư cân đối kế toán. Cộng bốn quý rồi cộng thêm dòng cả năm là cộng quý 4 hai lần.
- 30 mã có dữ liệu, nhưng 28 mã mới là ngân hàng.
EVFvàTINlà công ty tài chính.
Chỉ có báo cáo hợp nhất — nguồn không có báo cáo riêng lẻ.
Trang đầy đủ: Thuyết minh BCTC.
Thêm — năm cột cho client.meta.warrants()
Từ 5 lên 10 cột. Thứ quan trọng nhất là tỷ lệ chuyển đổi — mảnh còn thiếu để tính giá trị nội tại và điểm hoà vốn của một chứng quyền.
w = client.meta.warrants(underlying="HPG")
w[["symbol", "conversion_ratio", "exercise_price", "last_trading_date"]]Năm cột mới: conversion_ratio · issue_price · shares_issued ·
listing_date · last_trading_date.
Hai điều quyết định nếu bạn dựng chiến lược
conversion_ratio không phải số nguyên. 393 trên 750 chứng quyền có tỷ lệ
thập phân — 1.7245, 3.5704 — vì nó được điều chỉnh sau sự kiện quyền của mã
cơ sở. Ép int() là mất thông tin ở hơn nửa danh mục.
last_trading_date sớm hơn maturity_date 2–7 ngày, ở mọi dòng. Canh theo
ngày đáo hạn nghĩa là giữ một thứ không bán được trong tối đa một tuần cuối.
maturity_date là ngày thực hiện quyền, không phải ngày giao dịch cuối.
Mọi chứng quyền Việt Nam hôm nay là quyền mua, kiểu Âu (chỉ thực hiện tại ngày đáo hạn), thanh toán tiền, niêm yết HOSE.
Sửa — một bảng ĐẦY ĐỦ tự khai là đã bị cắt
funds.nav() với nhiều quỹ lấy trọn dữ liệu rồi vẫn đặt
df.attrs["finlens"]["truncated"] = True và phát cảnh báo "Kết quả đã bị
cắt". Đo trên 100 quỹ: 129.457 trên 129.457 dòng — đầy đủ — mà vẫn báo bị
cắt.
Nếu bạn từng chia nhỏ khoảng thời gian vì tin cảnh báo đó, giờ không cần nữa.
Sửa — mọi ngoại lệ đều mang một liên kết chết
Mỗi ngoại lệ kết bằng một doc_url trỏ tới một trang chưa bao giờ tồn tại,
và nó nằm ở đúng chỗ dễ thấy nhất: cuối traceback. Giờ nó trỏ về
trang xử lý lỗi có thật.
Sửa — thông báo lỗi tiếng Việt không còn nuốt mất chính ngoại lệ
Trên console Windows dùng bảng mã cũ, một thông báo lỗi có dấu có thể làm mất chính nội dung ngoại lệ. Giờ thông báo luôn tới được, kể cả khi terminal không đọc được dấu.
Sửa — cảnh báo độ trễ dữ liệu trong phiên giờ thật sự tới tay bạn
df.attrs["finlens"] thiếu hai kênh mà máy chủ vẫn gửi: source_updated_at và
warnings. Hệ quả cụ thể: docstring của intraday.stock.ohlcv() hứa
meta.warnings mang CAGG_LAG — cảnh báo dữ liệu trong phiên đang trễ — nhưng
bạn không có cách nào đọc nó. Giờ có.
Gỡ — cột company_type khỏi client.financials.periods()
⚠️ Đây là thay đổi phá tương thích, dù cột ấy chưa bao giờ có giá trị:
nó rỗng ở mọi dòng qua bốn bản phát hành. df["company_type"] trên frame của
periods() nay ném KeyError.
Cần ánh xạ mã → loại doanh nghiệp thì dùng client.meta.symbols().
statement() và indicators() vẫn có company_type thật.
1.3.0 — 10/08/2026
Thêm — client.financials.indicators(): chỉ tiêu tài chính tính sẵn
P/E, ROE, NIM, nợ xấu, CAR và hàng chục chỉ tiêu khác, đã tính sẵn theo năm, theo quý hoặc theo bốn quý gần nhất (TTM) — không phải tự dựng từ khoản mục báo cáo.
client.financials.indicators("VCB", codes="roe,nim,npl_ratio", period="annual")
client.financials.indicator_catalog(com_type="NH") # nhãn, đơn vị, công thứcindicator_catalog() là bảng tra: mỗi mã chỉ tiêu đo cái gì, bằng đơn vị nào,
theo công thức nào. Gọi một lần rồi giữ lại, merge theo cặp
(company_type, code) để gắn nhãn tiếng Việt vào frame.
Hai quy ước phải đọc trước khi dùng
Đơn vị nằm ở cột unit của TỪNG DÒNG, không phải ở df.attrs. Một frame chỉ
tiêu mang nhiều đơn vị cùng lúc — roe là tỷ lệ, total_assets là VND,
inventory_days là ngày.
unit = "ratio" là phân số, và nó KHÔNG bị chặn trong 0–1. roe = 0.16721
nghĩa là 16,721%. Nhưng cir = 1.827 của NVB kỳ 2024Q1 cũng là số thật —
một ngân hàng có chi phí hoạt động vượt thu nhập. Đừng coi mọi giá trị lớn hơn 1
là "chắc đang ở thang phần trăm": phép chia 100 ấy làm hỏng đúng những kỳ đáng
chú ý nhất.
Lọc kỳ bằng cột period_type, đừng lọc bằng quarter một mình. quarter = 2
xuất hiện ở cả một quý rời rạc lẫn một cửa sổ TTM kết thúc ở quý đó, và hai
đại lượng ấy chênh nhau khoảng bốn lần.
Thêm — client.watchlist: danh mục theo dõi, đọc và GHI
Đây là nhóm phương thức duy nhất trong thư viện có ghi dữ liệu, và nó dùng
chung với web: cùng một bảng, cùng một id. Sửa ở Python thì thấy trên
finlens.vn, và ngược lại.
dm = client.watchlist.create("Ngân hàng")
client.watchlist.add(dm["id"], ["VCB", "TCB", "ACB"])
df = client.watchlist.symbols(dm["id"])
df[["symbol", "close", "return_1m", "return_1m_rank"]]Ba cách làm mất dữ liệu
create() gọi hai lần với cùng một tên tạo HAI danh mục — trùng tên được cho
phép. Đó cũng là lý do thư viện không bao giờ tự gửi lại một create() đã
timeout; hãy list() để kiểm rồi tự quyết.
replace() XOÁ những mã không nằm trong danh sách bạn gửi. Muốn thêm mà
không xoá gì thì dùng add().
delete() và remove() thành công vô điều kiện, kể cả khi không có gì để
xoá — nên bạn không phân biệt được "vừa xoá xong" với "chưa từng tồn tại".
Mọi cột *_rank xếp hạng trong chính danh mục đó, không phải trong thị
trường hay trong ngành.
Trang đầy đủ: Danh mục theo dõi.
1.2.0 — 10/08/2026
Không phương thức nào đổi chữ ký, không cột nào đổi tên, không đơn vị nào đổi nghĩa. Nhưng bản này không phải chỉ thêm, và hai chỗ dưới đây phải đọc trước khi nâng cấp:
ta-libthành phụ thuộc bắt buộc —pip install -U finlenssẽ kéo về một gói mới.- Sàn macOS nâng lên 13.0 (Intel) và 14.0 (Apple Silicon) — trên macOS cũ hơn, lệnh cài thất bại ở nơi trước đó nó thành công.
Thêm — 135 hàm TA-Lib: finlens.ta và df.finlens.*
df = client.eod.stock.ohlcv(["HPG", "VCB"], start="2024-01-01")
df = df.finlens.rsi(14).finlens.macd()
df.finlens.patterns("doji")
hpg = df[df["symbol"] == "HPG"]["close"] # tầng mảng: một mã một lần
finlens.ta.RSI(hpg, timeperiod=14)74 chỉ báo và 61 mẫu nến, ở hai tầng: df.finlens.* nhận DataFrame và trả
về bản sao kèm cột mới, còn finlens.ta.* nhận mảng và trả về mảng. Tầng thứ
nhất có 73 chỉ báo — MAVP chỉ tồn tại ở tầng mảng vì nó cần một mảng chu kỳ
theo từng thanh chứ không phải một cột giá.
⚠️ Trộn mã là cách hỏng mà tầng accessor sinh ra để chặn. Gọi thẳng
talib.RSI(df["close"]) trên một frame nhiều mã cho cửa sổ đầu của mã sau ăn
giá cuối của mã trước. Đo trên ba mã × 1.107 phiên: sai 738 dòng — 66,7%, tức
mọi dòng của hai mã đứng sau, chỗ lệch lớn nhất 56,27 điểm, và mọi giá trị
sai đều nằm trong khoảng 0–100 hợp lệ. df.finlens.rsi(14) tự tách theo cột
symbol.
Tên hàm, tên tham số và giá trị mặc định giữ nguyên của TA-Lib, nên một đoạn
code TA-Lib có sẵn chạy được sau khi đổi mỗi dòng import. Khác đúng ba chỗ, cả
ba để chặn một cách hỏng im lặng: tham số là keyword-only (RSI(close, 14)
ném TypeError, vì MACD có ba số nguyên liền nhau và STOCH có năm); mọi
lỗi đều là finlens.FinLensError; và timeperiod=14.5 bị từ chối thay vì
được cắt phần thập phân trong im lặng.
Tên cột mang theo tham số: sma_20 và sma_50 là hai cột chứ không đè lên
nhau, macd() cho macd_12_26_9 · macdsignal_12_26_9 · macdhist_12_26_9.
Đơn vị của cột mới đi vào df.attrs["finlens"]["units"] cùng chỗ với đơn vị cột
từ máy chủ, và mỗi lời gọi để lại một mục ở df.attrs["finlens"]["ta"].
⚠️ 24 trên 74 chỉ báo có trạng thái không ổn định — đổi start= của lời gọi
dữ liệu là đổi con số bạn nhận về, chứ không chỉ ở vùng warm-up. Trên 369 phiên
của HPG, RSI(c)[200:] lệch RSI(c[200:]) tới 5,93 điểm, và phải tới phần
tử thứ 96 chênh lệch mới xuống dưới 0,01. SMA thì không.
⚠️ Cột signal của patterns() không chỉ có ±100. Ba mẫu ra thêm ±80 và
hai mẫu ra thêm ±200, nên df[df.signal == 100] âm thầm đánh rơi chúng — trên
ba mã × 1.107 phiên là 81 trên 1.265 dòng tăng giá. Lọc bằng df.signal > 0,
hoặc bằng cột direction đã suy sẵn.
⚠️ Warm-up biểu diễn khác nhau ở hai họ hàm. Chỉ báo ra NaN; mẫu nến ra số
0, không phân biệt được với "đã quét và không thấy gì". Nhóm ngắn hơn
warm-up ra toàn NaN / 0 mà TA-Lib không báo lỗi; ở tầng accessor nó thành
một DataQualityWarning. Một ô thiếu ở giữa chuỗi lan tới hết chuỗi.
Chi tiết ở Chỉ báo kỹ thuật.
Thêm — finlens.IndicatorError và bốn mã lỗi
IndicatorError kế thừa FinLensError, dành cho những gì TA-Lib từ chối tính.
FL_TA_PARAM và FL_TA_INPUT ánh xạ về ValidationError — tức cũng là
ValueError; FL_TA và FL_TA_UNAVAILABLE về IndicatorError.
Tách PARAM khỏi INPUT vì hai bệnh khác nhau và cách sửa khác nhau:
timeperiod=14.5 là bạn gõ sai tham số, còn thiếu cột high là frame này
không phải thứ chỉ báo đó cần — thông báo của mã sau liệt kê các cột đang có.
Đổi — ta-lib là phụ thuộc bắt buộc
Phụ thuộc lúc chạy từ ba lên bốn: pandas, httpx, packaging, ta-lib.
Bắt buộc chứ không phải extra tuỳ chọn: một pip install finlens[ta] sẽ làm
df.finlens.rsi(14) là một AttributeError tuỳ máy, và ranh giới "tính năng
nào cần extra nào" là thứ bạn phải học thuộc mà không có gì nhắc.
Đổi — sàn macOS: 13.0 cho Intel, 14.0 cho Apple Silicon
⚠️ Đây là thay đổi duy nhất trong bản này có thể làm pip install thất bại ở
nơi trước đó nó thành công. Trên macOS cũ hơn hai mốc trên, lệnh cài báo
no matching distribution ngay lúc resolve.
Con số bám theo wheel của ta-lib. Giữ sàn thấp hơn nghĩa là bạn cài được
finlens rồi mới hỏng lúc import, vì không có wheel ta-lib nào khớp máy —
một dòng báo lỗi lúc cài là câu trả lời thật thà hơn.
Sàn Linux và Windows không đổi.
Sửa — thiếu khoá API thì thông báo nói đã tìm ở đâu
Thông báo cũ nêu hai cách trong khi client thử bốn tầng. Nay nó liệt kê đủ bốn, theo đúng thứ tự thật, kèm trạng thái từng chỗ:
Thiếu API key. Đã tìm theo thứ tự:
1. tham số finlens.client(api_key=...) — không truyền
2. env FINLENS_API_KEY — không đặt
3. file D:\proj\finlens.toml — có file, nhưng không có `api_key`
4. file C:\...\finlens\config.toml — KHÔNG ĐƯỢC ĐỌC — file ở trên đã che nóDòng thứ tư là dòng đáng giá nhất. File cấu hình đọc theo lối
first-match-wins, không gộp: file đầu tiên tồn tại là file duy nhất được
đọc. Một ./finlens.toml chỉ chứa timeout che hoàn toàn cấu hình cấp máy, và
triệu chứng bạn gặp là "tôi đặt api_key trong config máy rồi mà vẫn báo
thiếu".
1.1.1 — 06/08/2026
Bản này chỉ thêm. Không phương thức nào đổi chữ ký, không cột nào đổi tên,
không đơn vị nào đổi nghĩa — code đang chạy trên 1.1.0 chạy nguyên trạng.
Năm nhóm dữ liệu mới. Bốn nhóm đầu nằm ở những chỗ dễ đọc nhầm thành một bảng đã có; nhóm thứ năm — vĩ mô — lại phá ba giả định nền của thư viện, nên nó đứng đầu danh sách dưới đây. Các cảnh báo đáng đọc trước khi gõ dòng đầu tiên.
Thêm — client.macro.*: dữ liệu vĩ mô
ds = client.macro.indicators(topic="cpi", freq="monthly")
df = client.macro.series(ds["code"].tolist()[:5])Bốn phương thức: indicators() · series() · omo() · trade(). Nguồn là Tổng
cục Thống kê và Ngân hàng Nhà nước; 3.348 chuỗi chỉ tiêu.
⚠️ Đây là namespace đầu tiên mà unit là một CỘT chứ không phải thuộc tính
của cả bảng. Mọi namespace trước đó không bao giờ trộn đơn vị vì mỗi loại tài
sản nằm ở một namespace riêng; vĩ mô không có ranh giới nào như thế, nên
series(["gia_vang_giao_ngay_daily", "ty_gia_trung_tam_daily"]) trả về
USD/Ounce nằm cạnh VND trong cùng cột value. Đọc unit theo từng dòng;
df.attrs["finlens"]["units"] không trả lời được câu hỏi đó.
⚠️ Cột date là CUỐI KỲ QUAN SÁT, không phải một phiên giao dịch — loại cột
thời gian thứ ba của thư viện. Nó đi kèm cột period mang nhãn kỳ ("7-2026",
"Q1-2026") vì 2026-07-31 một mình không nói được đây là số của tháng 7.
⚠️ Mã chỉ tiêu phải TRA, không được dựng từ tên. Nó là một bản đồ tra cứu chứ không phải một hàm: nguồn đổi tên thì mã giữ nguyên, tên trùng nhau được khử bằng hậu tố thứ tự, và bảng ánh xạ thì sửa tay được. Chiều ngược lại thì an toàn: mã được cấp một lần rồi thôi, lưu lại thoải mái.
Chi tiết ở Dữ liệu vĩ mô.
Thêm — supply_demand(): khối lượng và số lệnh ĐẶT vào sổ
client.eod.stock.supply_demand("HPG", start="2026-08-01")Sáu cột số: buy_order_volume · sell_order_volume · net_order_volume ·
buy_order_count · sell_order_count · net_order_count.
⚠️ Đây là lệnh ĐẶT, không phải lệnh đã khớp. Trung vị của tỷ lệ
buy_order_volume / volume — cùng mã, cùng phiên, volume lấy từ ohlcv() — là
2,756 lần; 99,77% số dòng có khối lượng đặt lớn hơn hoặc bằng khối lượng
khớp. Đó là lý do mọi tên cột mang chữ _order_: trừ một cột ở đây cho volume
của ohlcv() là so hai đại lượng chênh nhau khoảng ba lần, và không có
exception nào chặn.
Ba cột _count dùng Int64 viết hoa và mang được giá trị thiếu — máy chủ
phát giá trị thiếu ở khoảng 11% số dòng. Kiểm bằng .isna(), đừng so với
0. Khi gộp kỳ, một phiên thiếu số lệnh làm cả ba cột _count của kỳ đó thiếu
theo.
Chỉ có ở client.eod.stock. Chỉ số, ngành, chứng quyền và phái sinh không có
dữ liệu sổ lệnh nào, nên client.eod.warrant.supply_demand là AttributeError
chứ không phải một bảng rỗng.
Thêm — active_volume(): khối lượng khớp lệnh chủ động cuối ngày
client.eod.stock.active_volume("HPG")
client.eod.derivative.active_volume("VN30F1M")Ba cột: active_buy_volume · active_sell_volume · net_active_volume.
⚠️ Không phải client.intraday.*.net_active_value(), và hai frame không cộng
được với nhau. Cùng một phiên VN30F1M, ba cột tương ứng lệch nhau 57,5% ·
520% · 30,1%. Chúng khác ở cả ba chiều: số rổ (hai ở đây, ba ở kia vì kia
tách riêng phần khớp lệnh định kỳ), đơn vị (khối lượng ở đây và không có
cột tiền nào; VND ở kia), và nguồn. Phép cộng hai frame không ném exception
nào, chỉ cho ra một con số sai.
Phần khớp lệnh định kỳ không biến mất — nó bị trộn vào:
active_buy_volume + active_sell_volume bằng đúng khối lượng khớp cả phiên.
Đơn vị khối lượng đổi theo namespace — cổ phiếu ở client.eod.stock, hợp
đồng ở client.eod.derivative — trong khi bộ tên cột giống hệt nhau. Đọc
df.attrs["finlens"]["units"].
Thêm — basis(): chênh lệch phái sinh và chỉ số cơ sở
client.eod.derivative.basis("VN30F1M") # theo phiên
client.intraday.derivative.basis("VN30F1M") # bước 1 phútBảy cột: symbol · index_symbol · date (hoặc time) · future_close ·
spot_close · basis · basis_pct.
basis = future_close - spot_close # điểm chỉ số
basis_pct = basis / spot_close * 100 # phần trăm, thang 0–100⚠️ basis_pct là cột _pct đầu tiên của thư viện. Mẫu số là spot_close
chứ không phải giá hợp đồng, và thang là 0–100 chứ không phải 0–1. Hai mẫu số
chỉ lệch nhau khoảng 0,3% nên chọn nhầm gần như không nhìn ra được trên một
biểu đồ — đó là lý do công thức được viết thẳng vào tài liệu thay vì để bạn suy.
Không phương thức nào trong cặp này có interval, kể cả bản theo phiên: gộp
một chênh lệch qua nhiều bước không có nghĩa hiển nhiên nào — trung bình, giá trị
cuối kỳ và biên độ là ba câu trả lời khác nhau. Dùng df.resample(...) nếu bạn
cần, ở đó bạn tự chọn.
index_symbol là một cột thật, không phải một chi tiết trong
df.attrs: attrs biến mất ngay khi bạn pd.concat hai frame.
Bản trong phiên bắt đầu từ 09:15, không phải 09:00 — trước 09:15 chỉ số chưa được tính theo từng phút, còn bar 09:00 là kết quả khớp lệnh định kỳ ATO, một cơ chế hình thành giá khác hẳn các bar liên tục phía sau.
Thêm — dòng tiền nhà đầu tư ở cấp hợp đồng phái sinh
client.eod.derivative.investor.flow("VN30F1M")
client.eod.derivative.investor.breakdown("VN30F1M")Cùng bộ chín cột với client.eod.stock.investor.*, nên hai bảng ghép được
với nhau. Khác đúng hai chỗ, và cả hai nằm trong kiểu của tham số:
- Chỉ
foreignvàproprietary. Bốn nhóm chi tiết nhận 400 — nguồn của chúng khoá theo danh mục doanh nghiệp niêm yết, nơi không hợp đồng phái sinh nào có mặt.DerivativeInvestorGroupthu hẹpLiteralđể lỗi đó thành một gạch đỏ lúc gõ code, vàbreakdown()bỏ trốnggroupshỏi hai nhóm chứ không phải sáu như ở cấp mã. - Khối lượng tính bằng hợp đồng, không phải cổ phiếu.
⚠️ Đây là hợp đồng KHỚP ròng và giá trị DANH NGHĨA. net_volume là chênh
lệch hợp đồng đã khớp trong kỳ, không phải vị thế mở — cộng dồn nó qua nhiều
phiên không cho ra vị thế tích luỹ. Các cột *_value là giá trị danh nghĩa của
số hợp đồng đó, đã quy đổi ra VND thô bằng hệ số hợp đồng ở máy chủ, không phải
số tiền thực bỏ ra. Tự nhân net_volume với giá đóng cửa mà quên hệ số hợp đồng
là lệch 100.000 lần, và phép nhân đó vẫn chạy.
Thêm — mã lỗi FL_DATA_NO_UNDERLYING
Ánh xạ về finlens.NoDataError. Nó đi trong phần lỗi theo từng mã, nên một
hợp đồng không có chuỗi chỉ số cơ sở không làm hỏng cả lời gọi basis().
Mã riêng chứ không dùng lại FL_DATA_EMPTY vì hai bệnh khác nhau:
FL_DATA_EMPTY là khoảng bạn hỏi không có dòng nào, còn mã này là hợp đồng
này không có chỉ số cơ sở trong dữ liệu, tức không đổi start/end nào cứu
được. Nó cũng cố ý không phải InvalidSymbolError — mã bạn gõ hoàn toàn
đúng, và bảo người dùng đi kiểm tra một mã đã đúng là một chẩn đoán sai.
Thêm — hai kiểu công khai
finlens.DerivativeInvestorGroup và finlens.DerivativeInvestorGroupsArg.
Alias riêng chứ không dùng lại SectorInvestorGroup dù trùng tập giá trị: hai
chỗ hẹp lại vì hai lý do khác nhau — ở cấp ngành là một nguồn thực chất chỉ có
sàn HOSE, còn ở đây là một danh mục không chứa hợp đồng phái sinh — và hai lý do
đó có thể đổi độc lập.
1.1.0 — 05/08/2026
Đã đổi — báo cáo tài chính dùng item_id, và cột field bị bỏ
client.financials.statement() và client.financials.line_items() nay trả
item_id thay cho line_item_id, và không còn cột field. Cột parent_id
mang item_id của khoản mục cha; level và order_index không đổi.
df = client.financials.statement("HPG", kind="balance_sheet")
df["item_id"] # trước là df["line_item_id"] — tên cũ ném KeyError
df["field"] # KeyError: cột này không cònitem_id là mã hiển thị do máy chủ cấp, ổn định theo thời gian, và chỉ có
nghĩa trong phạm vi một (company_type, kind) — cùng một con số ở hai loại hình
doanh nghiệp là hai khoản mục khác hẳn nhau. Nó không phải định danh của hệ thống
nguồn, nên đừng dùng nó để tra ngược sang nơi khác.
Bản 1.0.0 và 1.0.1 gãy ở statement()
Máy chủ đã chuyển sang bộ cột mới. Nếu bạn đang chạy 1.0.0 hoặc 1.0.1,
client.financials.statement() ném TypeError — không phải lỗi của finlens nên
thông báo không nhắc gì tới phiên bản. Chữa bằng pip install -U finlens. Các
nhóm dữ liệu khác (eod, intraday, meta) vẫn chạy bình thường ở bản cũ.
Đã đổi — cột date của một kỳ đã gộp nhóm là phiên cuối, không phải đầu kỳ
Với interval có gộp nhóm (1w · 1mo · 3mo · 6mo · 1y), cột date nay
mang phiên giao dịch cuối cùng thực sự có dữ liệu trong kỳ. Trước bản này nó
mang mốc đầu kỳ. interval="1d" không đổi — nó vốn đã là ngày phiên.
client.eod.stock.ohlcv("HPG", start="2023-01-01", end="2023-12-31", interval="1mo")
# trước: 2023-01-01, 2023-02-01, 2023-03-01, 2023-04-01, ...
# nay : 2023-01-31, 2023-02-28, 2023-03-31, 2023-04-28, ...Số dòng không đổi — bề rộng kỳ giữ nguyên, chỉ cái nhãn đổi.
Ba lý do, theo thứ tự quan trọng:
closecủa dòng đó là giá đóng cửa của phiên mang ngày đó. Trước đây ngày và giá kể hai chuyện khác nhau: dòng1modán nhãn2023-01-01nhưngcloselấy từ phiên cuối tháng.- Kỳ đang dở không mang mốc tương lai. Nếu dùng ngày cuối theo lịch thì
1ycủa năm nay sẽ là2026-12-31, một mốc tương lai nằm giữa chuỗi dữ liệu, vàdf[df["date"] <= hôm_nay]sẽ âm thầm đánh rơi kỳ hiện tại. - Ngày trả về luôn là một phiên có thật, không phải một mốc tính ra. Lấy ngày
cuối theo lịch thì 39% số kỳ
1mosẽ mang một ngày thị trường đóng cửa — đo trên dữ liệu thật 2020–2026. Tháng 1/2025 nghỉ Tết là ví dụ rõ nhất: phiên cuối là 24/01 trong khi lịch nói 31/01.
Áp dụng cho client.eod.*.ohlcv(), client.eod.sector.ohlcv() và
client.eod.*.investor.flow(). Với dòng tiền, mọi nhóm nhà đầu tư trong cùng một
kỳ nhận cùng một date, kể cả khi các nguồn cập nhật lệch nhau một phiên.
Nếu bạn đang lưu kết quả đã resample: ngày cũ và ngày mới không khớp nhau, nên
mọi phép join theo date với dữ liệu đã lưu sẽ trượt. Lấy lại dữ liệu thay vì
ghép hai bên.
1.0.1 — 05/08/2026
Mã của thư viện không đổi một dòng nào so với 1.0.0. Nếu bạn đang dùng
1.0.0 thì không có lý do gì phải nâng cấp — bản này chỉ sửa trang giới thiệu
trên PyPI.
Điều đó kiểm chứng được, không phải lời hứa: so từng byte hai wheel
cp312-manylinux của 1.0.0 và 1.0.1 thì cả 15 module đã biên dịch đều
giống hệt nhau; chỉ bốn file đổi, và cả bốn đều là siêu dữ liệu (METADATA,
RECORD, _version.py, _build_info.py).
Đã sửa — bốn liên kết trên trang PyPI trả về 404
README.md là phần mô tả của gói, và PyPI hiển thị nó một mình, không có
cây thư mục nào bên cạnh để một đường dẫn tương đối trỏ tới. Bốn liên kết vì
vậy hỏng ngay trên trang của 1.0.0: huy hiệu giấy phép, hai liên kết
CHANGELOG.md, và liên kết LICENSE. Nay chúng trỏ về chính trang bạn đang
đọc và về toàn văn giấy phép MIT.
PyPI không cho tải lại một phiên bản đã phát hành. Trang của 1.0.0 vì thế
giữ nguyên bốn liên kết hỏng vĩnh viễn — đó là toàn bộ lý do 1.0.1 tồn tại.
1.0.0 — 05/08/2026
Bản chính thức đầu tiên trên PyPI. Cài bằng pip install finlens.
pip install finlensGom toàn bộ bề mặt đã thử nghiệm ở các bản alpha, cộng một thay đổi về cache mô tả ngay dưới đây.
Đã sửa — dữ liệu cuối ngày của phiên vừa đóng không còn bị đóng băng
Bảng cuối ngày được cập nhật liên tục trong phiên, 08:45–15:00, và nguồn còn chạm vào dòng của ngày đó một lát sau khi đóng cửa: chốt giá ATC, tính lại hệ số điều chỉnh, bổ sung số khối ngoại.
Trước bản này, từ đúng 15:00 kết quả bị giữ trong bộ nhớ đệm bảy ngày — một bản chụp giữa chừng bị ghim suốt cả tuần, không ngoại lệ, không cảnh báo.
| Bạn gọi lúc | end bỏ trống | end là hôm nay | end là năm cũ |
|---|---|---|---|
| 08:45 – 14:59 | 60 giây | 60 giây | 7 ngày |
| từ 15:00 | 60 giây | 30 phút | 7 ngày |
Điều kiện hẹp ở "khoảng chạm phiên vừa đóng", không phải "mọi khoảng đã chốt": một truy vấn năm 2024 đã bất biến thật, cho nó hết hạn sau 30 phút chỉ bắt bạn gọi lại cùng một thứ mà không đổi lấy gì.
Không cần làm gì để nhận thay đổi này — nó nằm trong logic chọn thời gian sống
của bộ nhớ đệm. Muốn bỏ qua đệm ở một lời gọi bất kỳ thì thêm refresh=True.
Bề mặt đầy đủ của 1.0.0
client.eod.stock.ohlcv("HPG", start="2026-01-01", adjusted=False)
client.eod.sector.ohlcv("8355")
client.eod.stock.investor.breakdown("HPG")
client.intraday.stock.ticks("HPG", date="2026-08-03")
client.intraday.stock.net_active_value("HPG", interval="1h")
client.financials.statement("HPG", kind="balance_sheet", period="quarterly")
client.meta.symbols(exchange="HOSE", icb="8300")Chi tiết từng nhóm nằm ở các mục alpha bên dưới, hoặc ở trang tham chiếu tương ứng trong mục lục bên trái.
1.0.0a7 — 04/08/2026
Bốn bề mặt mới.
Thêm — dữ liệu trong phiên
client.intraday.stock.ohlcv("HPG", interval="5min")
client.intraday.stock.ticks("HPG", date="2026-08-03")
client.intraday.stock.net_active_value("HPG")- Cột thời gian tên
timevà mang múi giờAsia/Ho_Chi_Minh, khác cộtdatenaive của EOD. Bỏ múi giờ thì pandas đoán UTC và 09:15 giờ Việt Nam bị đọc lệch bảy tiếng — ra ngoài cả phiên giao dịch. - Giá trong phiên là giá thô, không điều chỉnh quyền — ngược với
client.eod.stock. Nối hai chuỗi mà không đọcdf.attrs["finlens"]["price_basis"]sẽ cho một bậc thang giả ở mỗi lần chia tách quyền. ticks()nhận một mã, một phiên mỗi lời gọi, và giới hạn đó nằm trong chữ ký — tham số đầu tênsymbolsố ít, không phảisymbols.client.intraday.indexkhông cónet_active_value(): dữ liệu tick của chỉ số không ghi chiều lệnh chủ động. Một sự vắng mặt trong dữ liệu là một sự vắng mặt trong API.- Cột
sidecó ba giá trị chứ không phải hai:buy·sell·auction.
Chi tiết ở Dữ liệu trong phiên.
Đã sửa — net_active_value từng có ba đơn vị cho một tên cột
Đây là thay đổi phá vỡ tương thích nặng nhất của bản này, và nó nằm trong chính con số chứ không chỉ ở tên hàm:
| Bản 0.1.x | Bản 1.x | |
|---|---|---|
| Cổ phiếu | chia cho 10⁶ | VND thô |
| Phái sinh | không quy đổi hệ số hợp đồng | VND thô, đã nhân hệ số hợp đồng |
| Tài liệu | gọi là "tỷ đồng" | df.attrs["finlens"]["units"] khai rõ từng cột |
Với cổ phiếu, con số mới lớn hơn con số cũ 10⁶ lần. Với phái sinh, con số cũ vốn không thuộc đơn vị nào nên không quy đổi được — đọc lại từ đầu.
Mã phái sinh chưa có hệ số hợp đồng xác nhận thì các cột *_value trả null
kèm cảnh báo, chứ không phải một con số đoán; các cột *_volume vẫn trả bình
thường.
Thêm — báo cáo tài chính
client.financials.statement("HPG,VCB,SSI", kind="income_statement",
period="quarterly", start_year=2023)
client.financials.line_items(com_type="NH", kind="balance_sheet")
client.financials.periods("HPG")Frame ở dạng long và mỗi dòng mang company_type của chính nó, nên một lời
gọi được phép trộn nhiều loại hình doanh nghiệp. Ràng buộc "mỗi lần một
loại" của 0.1.x là do client tự áp.
Chi tiết ở Báo cáo tài chính.
Thêm — tra cứu danh mục
client.meta.symbols(exchange="HOSE", icb="8300")
client.meta.sectors(level=2)
client.meta.warrants(underlying="HPG")Câu hỏi mở đầu một phiên làm việc không phải "giá HPG hôm qua" mà là "có những mã nào". Trước bản này thư viện không trả lời được.
- Một tham số
icb=nhận cả mã cấp 2 lẫn mã cấp 4 — bạn không phải biết trước mã mình cầm thuộc cấp nào. - Chứng chỉ quỹ nằm cùng bảng với cổ phiếu, phân biệt bằng cột
kind. exchangenhận"HOSE"·"HNX"·"UPCOM".
Chi tiết ở Tra cứu danh mục.
Thêm — chỉ số ngành ICB
client.eod.sector.ohlcv("8600") trả chuỗi giá của chỉ số ngành, đơn vị điểm
chỉ số. Namespace client.eod.sector trước đó chỉ có investor.flow().
⚠️ Hai phương thức của namespace này khác thứ nguyên nhau: ohlcv() là điểm
chỉ số, investor.flow() là VND thô.
Thêm — adjusted cho eod.*.ohlcv()
client.eod.stock.ohlcv("VCB", adjusted=False) # giá khớp lệnh thậtMặc định vẫn là True, nên không lời gọi nào đang chạy bị đổi kết quả. Cơ sở
giá đọc ở df.attrs["finlens"]["price_basis"].
⚠️ client.intraday.*.ohlcv() không có tham số này — giá trong phiên vốn đã
là giá thô. Bất đối xứng có chủ đích, không phải một chỗ quên.
1.0.0a6 — 04/08/2026
Đã sửa — lỗi chứng chỉ TLS không còn bị báo cáo thành lỗi máy chủ
Trên máy nằm sau proxy kiểm tra TLS, bản trước báo "backend đang không phản hồi" trong khi máy chủ hoàn toàn khoẻ — thông báo ấy gửi người đọc đi kiểm đúng chỗ không có vấn đề gì. Lỗi chứng chỉ còn bị thử lại ba lần, dù một chứng chỉ không tin được sẽ không trở nên đáng tin sau hai giây.
Nay lỗi chứng chỉ được nhận là vĩnh viễn: không thử lại, và có lớp riêng
finlens.TlsVerificationError — kế thừa ConnectionFailedError nên code
đang bắt lớp cha vẫn chạy y nguyên. Thông báo chỉ thẳng đường sửa:
finlens.client(ca_bundle=...) hoặc biến môi trường FINLENS_CA_BUNDLE.
Đã sửa — finlens.build_info()["built_at"] trả cùng một mốc cho mọi bản
Trường này trả một dấu thời gian cố định cho mọi bản build. Với một gói mà bạn không đọc được mã nguồn, câu hỏi đầu tiên khi báo lỗi là "đang chạy binary nào, build lúc nào" — và trường đó đang trả lời sai cho tất cả mọi người. Nay nó là mốc thật của bản build.
1.0.0a5 — 04/08/2026
Thêm — investor.flow() và investor.breakdown()
client.eod.stock.investor.flow("HPG", group="foreign")
client.eod.stock.investor.breakdown(["HPG", "VCB"])
client.eod.sector.investor.flow("8600")
client.eod.index.investor.flow("VNINDEX")flow() hỏi một nhóm, breakdown() hỏi nhiều nhóm — cùng một schema, nên
hai bảng ghép được với nhau mà không phải đổi hình.
Đã sửa — sáu nhóm nhà đầu tư không cùng một định nghĩa
Đây là chỗ sửa lỗi ngữ nghĩa nặng nhất so với 0.1.x. Tài liệu cũ liệt kê sáu nhóm phẳng như sáu thứ ngang hàng. Thực tế:
- Bốn nhóm chi tiết (cá nhân / tổ chức × trong nước / nước ngoài) là một phân
hoạch đầy đủ — tổng
net_valuecủa chúng bằng 0. foreignlấy cả giao dịch thoả thuận, nên nó không bằngforeign_individual + foreign_institutional.proprietary(tự doanh) đến từ một vũ trụ dữ liệu khác và không phải nhóm con củalocal_institutional, dù trực giác nói ngược lại.
Cộng cả sáu nhóm cho ra một con số vô nghĩa, và 0.1.x không có gì ngăn điều đó.
Cột group nay luôn có mặt, kể cả khi lời gọi chỉ hỏi một nhóm — đó là chỗ
duy nhất mang được "con số này tính trên cơ sở nào" ở mức từng dòng.
Ở cấp ngành, kiểu SectorInvestorGroup chỉ nhận "foreign" và "proprietary",
nên xin bốn nhóm chi tiết ở đó bị chặn ngay lúc gõ code.
Đã sửa — response sai hình dạng ném lỗi thay vì trả bảng rỗng
Tới bản trước, một response mà client không hiểu được có thể trở thành một bảng
rỗng hoặc vài dòng bịa, im lặng cả hai trường hợp. Bảng rỗng là kịch bản xấu
nhất của cả dự án: người dùng đọc "khối ngoại mua ròng 0 đồng" trong khi thật ra
client không đọc được dữ liệu. Nay nó ném finlens.SchemaMismatchError với mã
FL_DATA_SCHEMA.
1.0.0a4 — 03/08/2026
Thay đổi phá vỡ tương thích — Python và pandas
Yêu cầu pandas >= 3.0 và Python >= 3.11 (trước đó là pandas >= 2.1.2
và Python >= 3.10).
Hai dòng pandas khác nhau ở những chỗ im lặng: cùng một chuỗi ngày cho ra
datetime64[us] trên pandas 3.0 nhưng datetime64[ns] trên 2.x — nên một frame
rỗng và một frame có dữ liệu mang dtype khác nhau tuỳ phiên bản pandas của người
dùng. Python 3.11 là hệ quả kéo theo: pandas 3.0 chỉ có wheel cho CPython 3.11
đến 3.14.
Wheel Linux đòi glibc ≥ 2.28 (Debian 10 / RHEL 8 / Ubuntu 18.10 trở lên) — chính ràng buộc mà pandas đặt ra, không phải một lựa chọn riêng.
1.0.0a1 — 03/08/2026
Bản alpha đầu tiên, chỉ phát hành trên TestPyPI. Có client.eod.stock,
client.eod.index, client.eod.derivative, client.eod.warrant.
Toàn bộ nền tảng của dòng 1.x xuất hiện ở bản này:
finlens.client()vàfinlens.AsyncClient— bản async có cùng chữ ký với bản đồng bộ.- Tạo client không gọi mạng. Đặt
finlens.client()ở cell đầu notebook luôn an toàn; muốn kiểm khoá ngay thì gọiclient.connect(). - Cấu hình bốn tầng: tham số truyền vào > biến môi trường
FINLENS_*>finlens.toml> mặc định. - Cây ngoại lệ hoàn chỉnh gốc ở
finlens.FinLensError, kèm mã lỗiFL_*ổn định và bảy lớp cảnh báo tắt được chọn lọc. finlens.PartialFetchErrormang theo dữ liệu đã lấy được ở.data— không bao giờ mất 49 mã thành công vì 1 mã hỏng.finlens.ValidationErrorkế thừa đôi từValueError, nên code cũ viếtexcept ValueError:vẫn chạy sau khi nâng cấp.ca_bundlecho môi trường có proxy kiểm tra TLS. Cố ý không có tuỳ chọn tắt kiểm chứng chỉ.
Đã sửa so với 0.1.11
CompanyTypeMismatchErrornằm ngoài câyFinLensError. Nay nó làfinlens.CompanyTypeMismatchError(ValidationError), tức vừa trong cây vừa vẫn làValueError. Hệ quả:except finlens.FinLensErrorgiờ là đủ — nếu code của bạn còn một nhánhexcept RuntimeErrorviết theo hướng dẫn cũ, nhánh đó nay là code chết.- Cây ngoại lệ đảo chiều phụ thuộc, nguyên nhân khiến
except FinLensError:ở 0.1.x gần như không bắt được gì. - So sánh phiên bản bằng chuỗi:
"0.1.9" < "0.1.11"làFalse, nên cổng kiểm tra hỗ trợ đã chết đúng ở vùng phiên bản đang dùng. finlens.__version__báo sai. 0.1.x fallback về"0.0.0"ở mọi bản chưa cài đầy đủ.
Dòng 0.1.x — đã đóng băng
Bản phát hành cuối là 0.1.11 (09/01/2026). Dòng này không còn được cập nhật và không được tài liệu này mô tả — mọi trang tham chiếu ở đây nói về 1.x.
Nếu bạn đang đọc một tài liệu, một bài viết hay một câu trả lời của trợ lý AI mà
nó dùng clients. (số nhiều), client.eod.market, client.reporting, cột
Date viết hoa hay interval="1D", thì đó là nội dung của dòng 0.1.x — không
lời gọi nào trong số đó chạy được trên bản 1.x.
Từng chỗ gãy nằm ở bảng Nâng cấp từ 0.1.x đầu trang.
Câu hỏi thường gặp
Phiên bản mới nhất của thư viện Python finlens là bản nào?
Bản mới nhất là **1.2.0**, phát hành ngày 10/08/2026 trên PyPI. Cài bằng `pip install finlens` và kiểm bằng `finlens.__version__` — chuỗi trả về phải bắt đầu bằng `1.`. Nếu nó vẫn là `0.1.x` thì máy đang chạy Python 3.10 hoặc cũ hơn: bản 1.x khai `requires-python >= 3.11` nên pip lặng lẽ lùi về dòng cũ thay vì báo lỗi. Các bản `1.0.0a1` đến `1.0.0a7` chỉ tồn tại trên TestPyPI và chưa bao giờ có trên PyPI.
Nâng finlens từ 0.1.x lên 1.x thì đoạn code nào bị gãy?
Bốn nhóm. Một, đổi tên: `client.eod.market` thành `client.eod.index`, `client.reporting` thành `client.financials`, `finlens.types` thành `finlens.typing`. Hai, tên cột nay viết thường theo snake_case nên `df["Date"]` ném `KeyError`, phải đổi thành `df["date"]`. Ba, `interval="1M"` bị từ chối — dùng `"1mo"` cho dữ liệu cuối ngày và `"1min"` cho dữ liệu trong phiên. Bốn, chữ ký: tham số tên `symbols` (cấp ngành là `icb`) và chỉ tham số đầu tiên là positional, nên `ohlcv("HPG", "2026-01-01", "1w")` ném `TypeError`.
Vì sao client.eod.market báo AttributeError?
Vì ở bản 1.x của thư viện Python finlens, namespace chỉ số đã đổi tên thành `client.eod.index`. Gọi `client.eod.index.ohlcv("VNINDEX")` thay cho `client.eod.market.ohlcv("VNINDEX")`. Cùng đợt đổi tên đó, `client.reporting` thành `client.financials`, và bản 1.x thêm hai namespace giá cuối ngày chưa từng có: `client.eod.derivative` cho hợp đồng phái sinh và `client.eod.warrant` cho chứng quyền có bảo đảm. Namespace `client.eod.sector` nay có thêm `ohlcv()` trả chỉ số ngành ICB, tính bằng điểm chỉ số.
Vì sao df['Date'] báo KeyError khi đọc dữ liệu chứng khoán bằng Python?
Vì tên cột đã đổi sang snake_case viết thường, không có ngoại lệ nào. Trong thư viện Python finlens bản 1.x, bảy cột của `ohlcv()` là `symbol`, `date`, `open`, `high`, `low`, `close`, `volume`; cấp ngành dùng `icb`, `icb_name`, `icb_level` thay cho cột mã cũ. Đây là `KeyError` chắc chắn chứ không phải thỉnh thoảng, vì bản 1.x bảo đảm frame luôn đủ cột đúng dtype kể cả khi không có dòng nào — không có đường nào để một tên cột viết hoa lọt qua.
interval='1M' báo lỗi ở bản mới, phải thay bằng gì?
Thay bằng `"1mo"` nếu bạn muốn một tháng, hoặc `"1min"` nếu bạn muốn một phút. Trong từ vựng cũ `1M` là một tháng còn `1m` là một phút, chỉ khác nhau ở chữ hoa nhưng lệch nhau 43.200 lần, nên thư viện Python finlens từ chối thẳng cả hai bằng `finlens.InvalidIntervalError` với mã `FL_VALIDATION_INTERVAL_AMBIGUOUS` kèm gợi ý thay vì đoán ý. Dữ liệu cuối ngày nhận `1d`, `1w`, `1mo`, `3mo`, `6mo`, `1y`; dữ liệu trong phiên nhận `1min`, `5min`, `15min`, `30min`, `1h`, `4h`.
Bản 1.x của finlens thêm những nhóm dữ liệu nào so với 0.1.x?
Bốn bề mặt mới. Dữ liệu trong phiên: `client.intraday.stock` và `client.intraday.derivative` với `ohlcv()`, `ticks()` (từng lệnh đã khớp) và `net_active_value()`, còn `client.intraday.index` có `ohlcv()` và `ticks()`. Báo cáo tài chính: `client.financials.statement()`, `periods()`, `line_items()`. Tra cứu danh mục: `client.meta.symbols()`, `sectors()`, `warrants()`. Ở giá cuối ngày có thêm hai namespace `client.eod.derivative` và `client.eod.warrant`, còn `client.eod.sector` được bổ sung `ohlcv()` trả chỉ số ngành ICB. Kèm theo là tham số `adjusted`, `refresh`, `on_error` và siêu dữ liệu `df.attrs["finlens"]` mang đơn vị từng cột.
Bản 1.1.1 của thư viện Python finlens thêm những gì?
Bản 1.1.1 chỉ thêm, không phương thức nào đổi chữ ký và không cột nào đổi tên. Năm nhóm dữ liệu mới: `client.eod.stock.supply_demand()` trả khối lượng và số lệnh ĐẶT vào sổ — là lệnh đặt chứ không phải lệnh đã khớp, trung vị tỷ lệ trên khối lượng khớp là 2,756 lần; `client.eod.stock.active_volume()` cùng `client.eod.derivative.active_volume()` trả khối lượng khớp chủ động hai rổ và không có cột tiền, nên không cộng được với `client.intraday.*.net_active_value()` vốn có ba rổ tính bằng VND; `client.eod.derivative.basis()` cùng `client.intraday.derivative.basis()` trả chênh lệch hợp đồng tương lai với chỉ số cơ sở, cả hai đều không có tham số `interval`; và `client.eod.derivative.investor.flow()` cùng `breakdown()` trả dòng tiền cấp hợp đồng phái sinh với đúng hai nhóm `foreign` và `proprietary`. và `client.macro.*` với bốn phương thức `indicators()`, `series()`, `omo()`, `trade()` trả dữ liệu vĩ mô từ Tổng cục Thống kê và Ngân hàng Nhà nước — 3.348 chuỗi chỉ tiêu, nghiệp vụ thị trường mở và số liệu xuất nhập khẩu, và là namespace đầu tiên mà đơn vị nằm ở một cột chứ không phải thuộc tính của cả bảng. Kèm mã lỗi `FL_DATA_NO_UNDERLYING` và các kiểu `DerivativeInvestorGroup`, `DerivativeInvestorGroupsArg`, `MacroCodesArg`, `MacroFreqArg`, `MacroTopicArg`, `OmoKindArg`, `OmoFreqArg`, `TradeFlowArg`, `TradeBreakdownArg`, `TradeFreqArg`.
Bản 1.2.0 của thư viện Python finlens thêm những gì?
Bản 1.2.0 thêm 135 hàm TA-Lib — 74 chỉ báo và 61 mẫu nến — ở hai tầng: accessor `df.finlens.rsi(14)` trả về bản sao của `DataFrame` kèm cột mới và tự tách theo cột `symbol`, còn `finlens.ta.RSI(mang, timeperiod=14)` chạy trên mảng với tham số keyword-only. Kèm theo là lớp `finlens.IndicatorError` và bốn mã `FL_TA_PARAM`, `FL_TA_INPUT`, `FL_TA`, `FL_TA_UNAVAILABLE`. Hai chỗ phải đọc trước khi nâng cấp: `ta-lib` thành phụ thuộc bắt buộc, và sàn macOS nâng lên 13.0 trên Intel cùng 14.0 trên Apple Silicon.
Cập nhật lần cuối