Ví dụ thực chiến — 17 notebook Python
17 Jupyter notebook dùng thư viện finlens phân tích chứng khoán Việt Nam: dashboard thị trường, screener định giá, screener tín hiệu kỹ thuật và backtest.
Kho ví dụ finlens-python-examples
gồm 17 Jupyter notebook đi từ lời gọi API đầu tiên tới bốn sản phẩm dùng được
hằng ngày. Mọi notebook chạy trên dữ liệu thật, đã thực thi từ đầu tới cuối
và commit kèm kết quả — không có số bịa, không có file .csv mẫu.
Mở kho trên GitHub
17 notebook, chạy lại được toàn bộ sau khi có khoá API
Chưa có khoá API?
Bốn cách đặt khoá, kể cả lấy tự động từ extension VS Code
Chạy thử trong ba phút
Tải kho về và dựng môi trường:
git clone https://github.com/manhhung0221/finlens-python-examples.git
cd finlens-python-examples
python -m venv venv
venv\Scripts\activate
pip install -r requirements.txtĐặt khoá API — xem Cài đặt và khoá API cho cả bốn cách:
set FINLENS_API_KEY=flk_...Mở notebook đầu tiên:
jupyter lab notebooks/Bắt đầu ở notebooks/00-bat-dau/00_bat_dau.ipynb. Notebook tự tìm thư mục
gốc để nạp module dùng chung — không cần pip install -e ., không cần đặt
PYTHONPATH.
Bốn sản phẩm hoàn chỉnh
Đây là những notebook chạy được hằng ngày, không phải bài tập minh hoạ.
| Sản phẩm | Notebook | Làm gì |
|---|---|---|
| Dashboard thị trường | 14_dashboard_hang_ngay | Báo cáo một trang sau phiên ATC — chỉ số, thanh khoản, độ rộng, dòng tiền ngoại, top biến động. Xuất HTML tự chứa, dùng 8 lời gọi API |
| Screener định giá | 23_screener_dinh_gia | Chấm điểm toàn sàn HOSE trên bốn trục, z-score trong từng ngành, xuất Excel |
| Screener tín hiệu | 33_screener_tin_hieu | Quét 6 tín hiệu kỹ thuật toàn sàn, kèm đo hiệu quả lịch sử từng tín hiệu |
| Dashboard vĩ mô | 44_vi_mo_va_thi_truong | CPI, GDP, tín dụng, tỷ giá, OMO, xuất nhập khẩu đặt cạnh VNINDEX |
Năm nhóm notebook
Notebook chia thành năm thư mục theo chủ đề, mỗi thư mục có mục lục riêng.
Track 0 — Bắt đầu
00_bat_dau — khoá API, whoami() và limits(), lời gọi đầu tiên, đơn vị dữ
liệu và df.attrs, cây ngoại lệ FinLensError, tham số on_error, cache.
Đây là điều kiện tiên quyết cho 16 notebook còn lại.
Track 1 — Thị trường và dòng tiền
OHLCV nhiều mã trong một request · interval · giá điều chỉnh so với giá thô
· cây ngành ICB · sức mạnh 19 ngành · độ rộng thị trường · khối ngoại và tự doanh
· dòng tiền theo ngành. Kết thúc bằng dashboard thị trường.
Track 2 — Phân tích cơ bản và định giá
statement() dạng long và cây chỉ tiêu · common-size · 181 chỉ tiêu tính sẵn
với formula và higher_is_better · DuPont ba và năm tầng · screener định giá ·
deep-dive 28 ngân hàng niêm yết (NIM, nợ xấu, bao phủ, CIR, LDR).
Track 3 — Phân tích kỹ thuật và backtest
Hai tầng chỉ báo df.finlens.* và finlens.ta.* · warm-up và NaN lan · 61 mẫu
nến · screener tín hiệu toàn sàn · backtest vectorised tránh look-ahead với
CAGR/MDD/Sharpe.
Track 4 — Intraday, phái sinh, vĩ mô
Dữ liệu tick và ba giá trị của side · VWAP · basis VN30F1M · chứng quyền và bẫy
đơn vị · 3.348 chuỗi vĩ mô đặt cạnh VNINDEX.
Năm cạm bẫy được đo bằng số
Các notebook không kể lại cạm bẫy — chúng chạy cả cách sai lẫn cách đúng rồi in ra độ lệch. Đây là những con số đo được trên dữ liệu thật:
1. Đơn vị sai âm thầm. Giá cổ phiếu tính bằng nghìn VND, giá chứng quyền
bằng VND thô. Sau pd.concat, HPG hiện ra 22.1 nằm cạnh chứng quyền
1630.0 trong cùng một cột close — cả hai đều hợp lệ, và df.attrs mang đơn
vị đã biến mất. Đọc df.attrs["finlens"]["units"] trước khi ghép frame.
2. Chỉ báo kỹ thuật trên frame nhiều mã. talib.RSI(df["close"]) lấy 13 giá
cuối của mã trước để tính cửa sổ đầu của mã sau. Đo trên frame HPG + VCB 500
dòng: mã đầu đúng hoàn toàn, mã thứ hai sai 174/250 dòng, lệch tới 18 điểm
RSI — và mọi giá trị sai đều nằm trong khoảng 0–100 hợp lệ. Dùng
df.finlens.rsi(14), nó tự tách nhóm theo symbol.
3. Kỳ của chỉ số tài chính khác nhau theo loại hình doanh nghiệp. Trong
period="quarterly", pe và eps luôn tính trên 4 quý gần nhất, còn roe và
roa thì tuỳ loại hình: CT phi tài chính cho giá trị riêng quý đó
(Q4 ÷ năm ≈ 0,27), NH/CK/BH cho giá trị đã quy về năm (≈ 1,00). Hệ quả:
xếp hạng ROE quý đặt VCB ở 18,0% và FPT ở 6,3%, trong khi ROE năm của FPT là
21,4% — cao hơn VCB. Dùng period="annual" cho mọi phép so cắt ngang.
4. Bốn nhóm nhà đầu tư chi tiết cộng lại bằng 0. Mua ròng của nhóm này chính
là bán ròng của nhóm kia. Đo trên 114 phiên HPG: tổng bốn nhóm lệch tối đa
2 VND. Cộng chúng với nhóm foreign là đếm hai lần.
5. Backtest không đo giả định của chính nó. Cùng một chiến lược đà tăng cho CAGR −5,70% khi đo đúng và +3,04% khi mắc ba lỗi phổ biến: nhìn trước ở bộ lọc vũ trụ, khớp lệnh cùng phiên tín hiệu, và bỏ qua chi phí giao dịch. Khoảng cách 8,7 điểm phần trăm đó là phương pháp, không phải chiến lược.
Môi trường đã kiểm chứng
| Thành phần | Phiên bản |
|---|---|
finlens | 1.3.0 |
pandas | 3.0.5 |
plotly | 6.x |
| Python | 3.12 (thư viện yêu cầu 3.11 trở lên) |
| Extension VS Code | FinLens.finlens 0.2.7 |
Toàn bộ 17 notebook đã chạy sạch từ đầu tới cuối bằng
jupyter nbconvert --to notebook --execute, không lỗi nào.
Kho ví dụ là dự án riêng, phát hành dưới giấy phép MIT. Notebook không phải khuyến nghị đầu tư: screener thu hẹp danh sách từ 400 mã xuống 20, hai mươi mã đó vẫn cần được đọc từng cái một.
Câu hỏi thường gặp
Có ví dụ code mẫu nào cho thư viện Python finlens không?
Có. Kho `finlens-python-examples` trên GitHub gồm 17 Jupyter notebook chạy trên dữ liệu thật, chia thành năm nhóm theo chủ đề: bắt đầu, thị trường và dòng tiền, phân tích cơ bản và định giá, phân tích kỹ thuật và backtest, rồi intraday cùng vĩ mô. Mỗi notebook đã được thực thi từ đầu tới cuối bằng `jupyter nbconvert --to notebook --execute` và commit kèm kết quả, nên đọc trên GitHub là thấy ngay đầu ra thật chứ không phải chỉ thấy code. Tải về, đặt biến môi trường `FINLENS_API_KEY` rồi mở `notebooks/00-bat-dau/00_bat_dau.ipynb` là chạy lại được toàn bộ.
Làm dashboard thị trường chứng khoán Việt Nam bằng Python thế nào?
Notebook `14_dashboard_hang_ngay` trong kho ví dụ dựng một báo cáo thị trường một trang chỉ bằng 8 lời gọi API: chỉ số VNINDEX và VN30, thanh khoản toàn sàn so với bình quân 20 phiên, độ rộng thị trường (phần trăm số mã nằm trên MA50 cùng số mã tăng giảm), dòng tiền khối ngoại theo ngành ICB, và top biến động đã lọc thanh khoản. Kết quả xuất ra một file HTML tự chứa, mở được không cần mạng vì thư viện plotly.js chỉ nhúng một lần cho cả ba biểu đồ. Chạy được bằng dòng lệnh nên đặt vào Task Scheduler hay cron là có báo cáo tự động mỗi chiều sau phiên ATC.
Viết screener cổ phiếu Việt Nam bằng Python cần lưu ý gì?
Kho ví dụ có hai screener. `23_screener_dinh_gia` chấm điểm toàn sàn HOSE trên bốn trục định giá, sinh lời, tăng trưởng và an toàn tài chính, với ba nguyên tắc: chuẩn hoá z-score trong từng ngành chứ không trên toàn thị trường vì P/E ngân hàng và P/E công nghệ không so được với nhau, cắt đuôi trước khi tính z-score để một mã P/B cực đoan không đè bẹp cả thang, và lấy chiều tốt hay xấu từ cột `higher_is_better` của `indicator_catalog()` thay vì viết tay. `33_screener_tin_hieu` quét sáu tín hiệu kỹ thuật toàn sàn và đo hiệu quả lịch sử từng tín hiệu kèm sai số chuẩn.
Backtest chiến lược chứng khoán bằng Python cần tránh lỗi gì?
Notebook `34_backtest_chien_luoc` đo giá của ba lỗi phổ biến nhất bằng cách chạy cùng một chiến lược theo cả cách đúng lẫn cách sai. Thiên lệch nhìn trước ở bộ lọc vũ trụ xảy ra khi lọc thanh khoản bằng dữ liệu cả kỳ thay vì cửa sổ trượt kết thúc trước ngày ra quyết định. Khớp lệnh cùng phiên tín hiệu giả định mua được ở phiên mà tín hiệu chỉ xuất hiện lúc nó đóng cửa. Bỏ qua chi phí giao dịch biến một chiến lược quay vòng cao thành một chiến lược khác hẳn. Kết quả đo được: cùng chiến lược cho CAGR âm 5,70 phần trăm khi đo đúng và dương 3,04 phần trăm khi mắc cả ba lỗi.
Vì sao không nên dùng talib trực tiếp trên DataFrame nhiều mã?
Vì hàm TA-Lib không biết cột `symbol`. Một DataFrame dạng long chứa nhiều mã xếp chồng lên nhau, nên `talib.RSI(df["close"], 14)` sẽ lấy 13 giá cuối của mã đứng trước để tính cửa sổ đầu tiên của mã đứng sau. Đo trên frame HPG cộng VCB 500 dòng: mã đầu tiên đúng hoàn toàn, mã thứ hai sai 174 trên 250 dòng, lệch tới 18 điểm RSI, và mọi giá trị sai đều nằm trong khoảng 0 đến 100 hợp lệ nên không phép kiểm tự động nào bắt được. Cách đúng là dùng tầng `df.finlens.rsi(14)` — nó tự tách nhóm theo mã và cảnh báo khi dữ liệu chưa sắp xếp.
Cập nhật lần cuối