Logo FinLensFinLens Docs

Danh mục theo dõi bằng Python — đọc và ghi, chung với web

Dùng finlens tạo, sửa và đọc danh mục theo dõi trên finlens.vn từ Python: thêm bớt mã, lấy giá phiên gần nhất và xếp hạng suất sinh lợi trong danh mục.

Đây là nhóm phương thức duy nhất trong thư viện có GHI dữ liệu. Mọi thứ còn lại chỉ đọc, nên một lỗi ở đó cùng lắm cho ra số sai — một lỗi ở đây sửa danh mục thật của bạn.

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.

Phương thứcTrả về
list()Mọi danh mục của bạn — list[WatchlistInfo]
create(name)Danh mục mới — WatchlistInfo
rename(id, name=…)WatchlistInfo đã đổi tên
delete(id)None
symbols(id)DataFrame 18 cột: giá phiên gần nhất + xếp hạng
add(id, symbols)list[str] — các mã đã ghi
replace(id, symbols)list[str] — ⚠️ xoá những mã không gửi
remove(id, symbol)None

`list()` KHÔNG trả DataFrame

Bảy phương thức ở đây trả dict, list[str] hoặc None. Chỉ symbols() trả DataFrame.

Đó là bất đối xứng có chủ đích: một danh mục theo dõi là một đối tượngid và tên, không phải một bảng số liệu. Nhưng nó khác phần còn lại của thư viện, nên đừng gọi .head() trên kết quả của list().

Vòng làm việc thường gặp

import finlens

client = finlens.client()

dm = client.watchlist.create("Ngân hàng")
client.watchlist.add(dm["id"], ["VCB", "TCB", "ACB"])

df = client.watchlist.symbols(dm["id"])
print(df[["symbol", "close", "return_1m", "return_1m_rank"]])

create() trả về một WatchlistInfo — một dict với bốn khoá:

KhoáNội dung
idĐịnh danh, dùng cho mọi thao tác sau đó. Dùng chung với web.
nameTên. ⚠️ Nhận None
display_indexThứ tự người dùng sắp. ⚠️ Nhận None, và trùng nhau được
symbol_countSố mã, có sẵn nên đếm không tốn thêm lời gọi

Ba cách làm mất dữ liệu

Đọc trước khi gọi thao tác ghi đầu tiên

1. create() không idempotent. Trùng tên được cho phép, nên gọi hai lần với cùng một tên tạo ra hai danh mục. Đó là lý do client không bao giờ tự gửi lại một create() đã timeout — gửi lại có thể tạo bản thứ hai mà bạn không biết. Khi timeout, gọi list() xem nó đã kịp tạo chưa rồi tự quyết.

2. replace() xoá. Nó làm danh mục chỉ còn đúng những mã bạn gửi. Muốn thêm mà không xoá gì thì dùng add().

3. delete()remove() thành công vô điều kiện, kể cả khi không có gì để xoá. Chúng thử lại được mà không phải bắt ngoại lệ; cái giá là bạn không phân biệt được "vừa xoá xong" với "chưa từng tồn tại".

Đừng sắp lại list()

ds = client.watchlist.list()
print([w["name"] for w in ds])          # đúng thứ tự web hiển thị

`sorted()` trên `name` hoặc `display_index` sẽ ném `TypeError`

Cả hai trường đều nhận None, và đó là dữ liệu thật chứ không phải một khả năng lý thuyết: web tạo được danh mục không đặt tên, và những dòng cũ chưa từng được cấp số thứ tự.

sorted(ds, key=lambda w: w["name"])          # TypeError khi gặp None
sorted(ds, key=lambda w: w["display_index"]) # TypeError, và hai danh mục trùng
                                             # số cũng là chuyện có thật

list() đã trả về đúng thứ tự web hiển thị, nên không có gì để sắp. Cần một thứ tự ổn định giữa hai lần gọi thì dùng id.

symbols() — 18 cột, và ba quy ước đơn vị

df = client.watchlist.symbols(42)
df.attrs["finlens"]["units"]["close"]   # 'kVND'
Nhóm cộtĐơn vị
close, changenghìn VND đã điều chỉnh quyền — 23.4 là 23.400 đồng
change_ratio, sáu cột return_*tỷ lệ0.0234 là +2,34%, không phải 2,34
sáu cột return_*_rankhạng, không có đơn vị
symbol, sort_index, tradeable

`*_rank` xếp trong CHÍNH danh mục này

Không phải trong thị trường, không phải trong ngành. Hạng 1 nghĩa là tốt nhất trong danh mục bạn hỏi.

Hai mã bằng nhau thì bằng hạng. Một mã có suất sinh lợi None thì hạng cũng thiếu, chứ không phải bị đẩy xuống cuối.

`close` thiếu là bình thường

Mã mới niêm yết, mã đã huỷ niêm yết, hoặc một mã không tra được mà web đã ghi vào từ trước — cả ba đều cho close rỗng. Dòng vẫn về đủ cột.

Lọc nó đi sẽ làm một mã biến mất khỏi danh mục của bạn trong kết quả mà không ai xoá nó.

df.attrs["finlens"]["as_of"]ngày phiên của bảng giá, không phải lúc bạn gọi — đó là cách phân biệt "thị trường chưa mở" với "dữ liệu cũ".

symbols() không có tham số refresh vì nó không đi qua cache: kết quả là ảnh chụp của một thứ chính bạn vừa sửa bằng add() hay remove().

Mã lạ không làm hỏng cả lời gọi

ghi_duoc = client.watchlist.add(dm["id"], ["VCB", "KHONGCO"])
# ['VCB'] — và một PartialDataWarning nêu tên mã lỗi

Mã không tra được đi vào phần lỗi theo từng mã, HTTP vẫn 200, và những mã còn lại đã được ghi bình thường. Giá trị trả về là danh sách mã thực sự ghi, nên so nó với danh sách bạn gửi là cách rẻ nhất để biết cái gì đã lọt.

Xem thêm

Câu hỏi thường gặp

Danh mục tạo bằng Python có hiện trên web finlens.vn không?

Có, và ngược lại. Danh mục theo dõi là bề mặt duy nhất mà thư viện Python finlens dùng chung dữ liệu với web: hai bên đọc ghi trên cùng một bảng, nên `client.watchlist.create("Ngân hàng")` tạo ra một danh mục bạn thấy ngay trên finlens.vn, và một mã bạn thêm trên web sẽ xuất hiện trong `client.watchlist.symbols()`. Cùng một `id` chỉ tới cùng một danh mục ở cả hai nơi.

Vì sao gọi create hai lần lại ra hai danh mục cùng tên?

Vì `create()` của thư viện Python finlens không idempotent — trùng tên được cho phép ở tầng dữ liệu, nên mỗi lời gọi tạo một danh mục mới. Đó cũng là lý do client không bao giờ tự gửi lại một `create()` đã timeout: gửi lại có thể tạo bản thứ hai mà bạn không biết. Khi một `create()` timeout, hãy gọi `list()` để xem nó đã kịp tạo chưa rồi tự quyết định.

replace khác add thế nào, có làm mất mã không?

Khác ở chỗ `replace()` XOÁ. `client.watchlist.replace(id, ["VCB"])` làm danh mục chỉ còn đúng `VCB` và bỏ mọi mã khác; `client.watchlist.add(id, ["VCB"])` thêm `VCB` vào và giữ nguyên phần còn lại. Nếu bạn chỉ muốn bổ sung, dùng `add()`. Trong thư viện Python finlens, `replace()` là thao tác duy nhất có thể làm bạn mất mã mà không có cảnh báo nào, vì việc xoá chính là ý nghĩa của nó.

Vì sao sắp danh mục theo tên lại ném TypeError?

Vì cả `name` lẫn `display_index` của `WatchlistInfo` đều nhận `None`, và đó là dữ liệu thật chứ không phải một khả năng lý thuyết — web tạo được danh mục không đặt tên, và những dòng cũ chưa từng được cấp số thứ tự. `sorted(ds, key=lambda w: w["name"])` sẽ ném `TypeError` ngay lần đầu gặp một dòng như vậy. Không cần sắp lại: `list()` của thư viện Python finlens đã trả về đúng thứ tự web hiển thị.

Các cột kết thúc bằng _rank được xếp hạng trong phạm vi nào?

Trong CHÍNH danh mục đó, không phải trong thị trường hay trong ngành. Sáu cột `return_1w_rank` tới `return_1y_rank` của thư viện Python finlens xếp các mã của riêng danh mục bạn hỏi, nên hạng 1 nghĩa là tốt nhất trong danh mục ấy. Hai mã bằng nhau thì bằng hạng, và một mã có suất sinh lợi thiếu thì hạng cũng thiếu chứ không phải bị đẩy xuống cuối.

Cập nhật lần cuối

Nội dung trang