Skill: ndtchan/vnstock-free-expert (version 1.0.3)

--- name: vnstock-free-expert description: Runs an end-to-end vnstock workflow for free-tier-safe Vietnam stock valuation, ranking, and API operations with strict rate-limit control; used when users request Vietnamese stock analysis under free-tier constraints. compatibility: Requires Python 3.x, vnstock package, pandas, internet access, and optional VNSTOCK_API_KEY in .env. --- # VNStock Free Expert Use this skill when the user needs advanced Vietnam stock analysis with `vnstock`, while staying safe on free-tier limits. ## Important packaging note This skill is self-contained and does not require shipping a separate `vnstock/` docs folder. All operational knowledge needed by the agent is stored under: - `references/` ## Read order 1. Read `references/capabilities.md`. 2. Read `references/method_matrix.md` for exact class/method mapping. 3. Read `references/free_tier_playbook.md` before large runs. ## Scope and constraints - Library: `vnstock` only. - Preferred sources: `kbs` first, `vci` fallback. - Never use `tcbs`. - Treat `Screener API` as unavailable unless user confirms it is restored in their installed version. ## Free-tier operating rules - No API key: target <= 20 requests/minute. - Free API key: target <= 60 requests/minute. - Safe default pacing in scripts: 3.2s/request. - Reuse cached artifacts between steps. ## Shared confidence rubric (required) Report confidence as `High` / `Medium` / `Low` using this standard: - `High`: universe coverage >= 95%, critical metrics coverage >= 80%, and hard errors <= 5% of symbols. - `Medium`: universe coverage >= 80%, critical metrics coverage >= 60%, and hard errors <= 15%. - `Low`: below `Medium` thresholds or material missing fields that can flip ranking results. Always output: 1. Confidence level. 2. Coverage stats (`symbols_requested`, `symbols_scored`, `% missing by key metric`). 3. Top missing fields that may change conclusions. ## API key configuration (implemented) - Skill-local key file: `.env` - Variable: `VNSTOCK_API_KEY` - All API-calling scripts auto-load this key and call vnstock auth setup before requests. - You can override per run with `--api-key "..."`. ## Execution workflow (ordered) 1. Validate environment (`python`, `vnstock`, `pandas`) and load optional API key from `.env`. 2. Build a universe using `scripts/build_universe.py` (`group`, `exchange`, or `symbols` mode). 3. Collect market data with `scripts/collect_market_data.py` using safe pacing. 4. Collect fundamentals with `scripts/collect_fundamentals.py`. 5. Score and rank using `scripts/score_stocks.py`. 6. Generate analyst-style memo with `scripts/generate_report.py`. 7. Apply confidence rubric, disclose missing fields, and summarize risks. ## Downstream handoff bundle (required when doing single-ticker deep dive) When the user request is about valuing or building a memo for a **specific ticker** (or a small list), output a compact JSON bundle that downstream skills can reuse: - `ticker`, `as_of_date`, `currency` - `financials` (income/balance/cashflow + key ratios if available) - `price_history` (returns 1m/3m/6m/12m) - `peer_set` (if you built one) - `metadata.source` and `data_quality_notes` Handoff stability rule: - Keep field names stable so downstream skills can consume the bundle without reinterpretation. - Never omit a required top-level key; use `null`, an empty object, or an empty list when data is unavailable. - Put provider errors and missing fields in `metadata.data_quality_notes`. This bundle is designed to feed `equity-valuation-framework` and `portfolio-risk-manager`. ## Script map ### A) Discovery and universal invocation (for broad feature coverage) 1. `catalog_vnstock.py` Path: `scripts/catalog_vnstock.py` Use when: - You need to inspect available classes/methods in the installed `vnstock` version. - You want to confirm compatibility before running a method. 2. `invoke_vnstock.py` Path: `scripts/invoke_vnstock.py` Use when: - You need to call any supported class/method beyond the prebuilt valuation pipeline. - You want one generic entry point for `Listing`, `Quote`, `Company`, `Finance`, `Trading`, `Fund`, or other exported classes. This script supports dynamic invocation by class name and method name with JSON kwargs. ### B) Valuation pipeline scripts 1. `build_universe.py` Use when building symbol universe from index/exchange/custom symbol list. Input: source + mode + group/exchange/symbols. Output: `outputs/universe_*.csv` and latest pointers. 2. `collect_market_data.py` Use when collecting OHLCV/momentum fields (3M, 6M, 12M returns). Input: universe CSV path. Output: `outputs/market_data_*.csv` + per-symbol errors in JSON. 3. `collect_fundamentals.py` Use when collecting valuation and quality metrics from finance/company APIs. Input: universe CSV path. Output: `outputs/fundamentals_*.csv` + per-symbol errors in JSON. 4. `score_stocks.py` Use when ranking symbols with composite scoring. Input: market + fundamentals CSV files. Output: `outputs/ranking_*.csv`. 5. `generate_report.py` Use when converting ranking output to analyst-style markdown memo. Input: ranking CSV file. Output: `outputs/investment_memo_*.md`. 6. `run_pipeline.py` Use when running the end-to-end pipeline in one command. Input: source + universe mode. Output: all artifacts above in one run. ## Error handling rules 1. Log symbol-level failures and continue processing remaining symbols. 2. Do not claim missing metrics as zeros; mark them as missing. 3. If a critical step fails, stop and report failed step + command + suggested retry scope. ## Recommended decision logic 1. If request is “standard valuation/ranking”: run pipeline scripts. 2. If request needs a specific vnstock capability not in pipeline: use `catalog_vnstock.py` then `invoke_vnstock.py`. 3. If request volume is large: apply `free_tier_playbook.md` throttling and chunking strategy. ## Confidence aggregation (required) When output includes ranking and valuation interpretation: 1. Compute data confidence from coverage metrics (`symbols_scored`, missing key fields, error ratio). 2. Compute model confidence from method robustness (single metric vs multi-factor consistency). 3. Final confidence = lower of data confidence and model confidence. 4. In `Low` confidence cases, provide directional output only and list required missing inputs. ## Required output template 1. `What Was Run`: scripts, source, universe scope, and pacing profile. 2. `Coverage`: requested symbols, scored symbols, and missingness by key field. 3. `Top Results`: ranked list with score columns. 4. `Key Risks`: concentration, stale data, missing metrics, or provider limitations. 5. `Confidence and Gaps`: final confidence + exact blockers. ## Quick command examples ```bash python scripts/catalog_vnstock.py --outdir ./outputs python scripts/invoke_vnstock.py --class-name Quote --init-kwargs '{"source":"kbs","symbol":"VCB"}' --method history --method-kwargs '{"start":"2024-01-01","end":"2024-12-31","interval":"1D"}' --outdir ./outputs python scripts/run_pipeline.py --source kbs --mode group --group VN30 --outdir ./outputs ``` ## Trigger examples - "Analyze VN30 using vnstock but keep it free-tier safe." - "Rank Vietnamese stocks by value/quality/momentum with KBS data." - "Run a full vnstock pipeline and return top candidates with risk notes."

path: _meta.json
content: ownerId: kn7aqz9b9xv2mmyvsg54n5kecn81kkaf
slug: vnstock-free-expert
version: 1.0.2
publishedAt: 1771982403105

path: agents/openai.yaml
content: display_name: VNStock Free Expert
short_description: Free-tier-safe stock valuation and ranking pipeline using vnstock. 
default_prompt: Run a professional vnstock free-tier workflow to screen, value, rank, and summarize Vietnamese stocks.


path: references/capabilities.md
content: # VNStock Capability Reference (Self-Contained)

## Core capabilities
- Symbol universe and classification: exchanges, industries, index groups, derivatives/bonds/warrants listings.
- Company intelligence: overview, shareholders, officers, subsidiaries, affiliates, events, reports (source-dependent).
- Market data: historical OHLCV, intraday prints, board snapshots, market depth (source-dependent).
- Financial statements and ratios: income statement, balance sheet, cash flow, ratio with period modes.
- Fund data: investment fund listings and related metadata.
- External connectors: available depending on environment and API keys.

## Source strategy
- Primary source: `kbs`.
- Secondary fallback: `vci`.
- Do not use `tcbs`.

## Data caveats
- Method availability differs by source.
- Some methods may return empty frames for specific symbols/time windows.
- Realtime/near-realtime outputs depend on market hours and provider freshness.

## Safe analysis pattern
1. Fetch raw data.
2. Validate shape/columns.
3. Handle empty/missing values.
4. Compute derived metrics.
5. Separate factual output from interpretation.


path: references/free_tier_playbook.md
content: # Free-Tier Playbook

## Rate limit profile
- Guest/no key: 20 requests/minute.
- Free key: up to 60 requests/minute.

## Execution controls
- Default minimum interval: 3.2 seconds/request.
- For unstable network/provider: increase to 4-5 seconds/request.
- Process symbols in chunks (e.g. 20-50 symbols/batch).

## Resilience checklist
- Retry transient failures with backoff.
- Log per-symbol failures; do not fail the whole run if a single symbol fails.
- Save intermediate artifacts (`universe`, `market_data`, `fundamentals`) for resume.

## Data quality checklist
- Confirm expected columns exist before calculation.
- Track missing metrics ratio.
- Flag stale data windows and low-bar histories.

## Portfolio-analysis checklist
- Use relative ranking, not absolute threshold only.
- Validate top picks against recent macro/news context.
- Apply sector diversification and risk caps.


path: references/invocation_recipes.md
content: # Invocation Recipes

## 1) Quote history
```bash
python /Users/teahan/Projects/vscode-workspace/vn_stock_skill/skills/vnstock-free-expert/scripts/invoke_vnstock.py \
  --class-name Quote \
  --init-kwargs '{"source":"kbs","symbol":"VCB"}' \
  --method history \
  --method-kwargs '{"start":"2024-01-01","end":"2024-12-31","interval":"1D"}' \
  --outdir ./outputs
```

## 2) Company overview
```bash
python /Users/teahan/Projects/vscode-workspace/vn_stock_skill/skills/vnstock-free-expert/scripts/invoke_vnstock.py \
  --class-name Company \
  --init-kwargs '{"source":"kbs","symbol":"FPT"}' \
  --method overview \
  --method-kwargs '{}' \
  --outdir ./outputs
```

## 3) Finance ratio (year)
```bash
python /Users/teahan/Projects/vscode-workspace/vn_stock_skill/skills/vnstock-free-expert/scripts/invoke_vnstock.py \
  --class-name Finance \
  --init-kwargs '{"source":"kbs","symbol":"ACB"}' \
  --method ratio \
  --method-kwargs '{"period":"year"}' \
  --outdir ./outputs
```

## 4) Listing by group
```bash
python /Users/teahan/Projects/vscode-workspace/vn_stock_skill/skills/vnstock-free-expert/scripts/invoke_vnstock.py \
  --class-name Listing \
  --init-kwargs '{"source":"kbs"}' \
  --method symbols_by_group \
  --method-kwargs '{"group":"VN30"}' \
  --outdir ./outputs
```


path: references/method_matrix.md
content: # VNStock Method Matrix

This matrix lists common class-method combinations used in practice. Use `catalog_vnstock.py` to verify exact availability in the local installed version.

## Listing
- `all_symbols()`
- `symbols_by_exchange()`
- `symbols_by_industries()`
- `industries_icb()`
- `symbols_by_group()`
- `all_indices()`
- `indices_by_group()`
- `all_future_indices()`
- `all_government_bonds()`
- `all_covered_warrant()`
- `all_bonds()`

## Quote
- `history(...)`
- `intraday(...)`
- `price_depth(...)` (source-dependent)

## Company
- `overview()`
- `shareholders()`
- `officers()`
- `subsidiaries()`
- `affiliate()`
- `news()`
- `events()`
- `ownership()` (source-dependent)
- `capital_history()` (source-dependent)
- `insider_trading()` (source-dependent)
- `reports()` (source-dependent)
- `trading_stats()` (source-dependent)
- `ratio_summary()` (source-dependent)

## Finance
- `income_statement(period=...)`
- `balance_sheet(period=...)`
- `cash_flow(period=...)`
- `ratio(period=...)`

## Trading
- `price_board(symbols_list=...)`

## Fund
- `listing(...)`

## Universal method access
Use `invoke_vnstock.py` for any class/method not explicitly hardcoded in pipeline scripts.


path: references/vnstock/01-overview.md
content: # Vnstock 3.4.0 - Tổng Quan Kiến Trúc & Dữ Liệu

**Phiên bản:** 3.4.0+  

**Cập nhật:** Tháng 1, 2026  

**Trạng thái:** Hoạt động

---

## 📚 Mục Lục

1. [Giới Thiệu](#giới-thiệu)
2. [Các Plan & Rate Limit](#các-plan--rate-limit)
3. [Kiến Trúc Tổng Thể](#kiến-trúc-tổng-thể)
4. [Phân Tầng Dữ Liệu (Data Layers)](#phân-tầng-dữ-liệu-data-layers)
5. [Các APIs & Dữ Liệu Hiện Có](#các-apis--dữ-liệu-hiện-có)
6. [Nguồn Dữ Liệu & Connectors](#nguồn-dữ-liệu--connectors)
7. [Core Utilities](#core-utilities)
8. [Cách Sử Dụng Cơ Bản](#cách-sử-dụng-cơ-bản)

---

## 📖 Giới Thiệu

**Vnstock** là thư viện Python mạnh mẽ để lấy dữ liệu chứng khoán Việt Nam từ nhiều nguồn uy tín. Thư viện được thiết kế với kiến trúc provider-based, cho phép dễ dàng chuyển đổi giữa các nguồn dữ liệu khác nhau mà không thay đổi code.

### 🎯 Đặc Điểm Chính

- **Nhiều nguồn dữ liệu**: VCI, KBS, MSN, và các connectors bên ngoài (FMP, DNSE)
- **API thống nhất**: Cùng một interface cho tất cả các nguồn dữ liệu
- **Dữ liệu lịch sử & Real-time**: Giá lịch sử, dữ liệu trong ngày, giá realtime
- **Dữ liệu công ty**: Hồ sơ công ty, cổ đông chính, nhân viên quản lý
- **Dữ liệu tài chính**: Báo cáo tài chính, chỉ số tài chính, các dòng tiền
- **Lọc & Phân loại**: Tìm kiếm theo ngành, sàn giao dịch, chỉ số
- **Xử lý lỗi thông minh**: Retry tự động với exponential backoff

⚠️ **TCBS**: Đã ngưng cập nhật từ v3.4.0, sẽ loại bỏ trong v3.5.0 (tháng 3/2026)

---

## 💳 So sánh các gói sử dụng & giới hạn

Vnstock cung cấp các gói sử dụng khác nhau phù hợp với từng nhu cầu cụ thể, xem thông tin chính xác được chia sẻ tại website Vnstock [Gói tài trợ Vnstock](https://vnstocks.com/insiders-program):

### So sánh gói sử dụng

| Tiêu Chí          | Khách | Cộng đồng (Tiêu chuẩn)  | Bronze    | Silver    | Golden    |
| ----------------- | ----- | ----- | --------- | --------- | --------- |
| **Giới Hạn/Phút** | 20    | 60    | 180 (3x)  | 300 (5x) | 500 (10x) |
| **Giới Hạn/Giờ**  | 1.2K  | 3.6K  | 10.8K     | 18K       | 36K       |
| **Giới Hạn/Ngày** | 5K    | 10K   | 50K       | 100K      | 150K      |
| **Đăng Nhập**     | ❌    | ✅    | ✅        | ✅        | ✅        |
| **API Key**       | ❌    | ✅    | ✅        | ✅        | ✅        |
| **vnstock_data**  | ❌    | ❌    | ✅        | ✅        | ✅        |
| **Hỗ Trợ**        | ❌    | ❌    | ✅        | ✅        | ✅        |
| **Cam Kết**       | Không | Không | Linh Hoạt | Quý       | 1 Năm     |

(*) **Lưu ý quan trọng về Rate Limit:**
- Khi chạm giới hạn API, chương trình sẽ tự động dừng để bảo vệ hệ thống
- Số lượng request trên mang tính tham khảo và có thể thay đổi
- Giới hạn thực tế phụ thuộc vào: giới hạn của Vnstock và giới hạn của server nguồn dữ liệu
- Khuyến nghị: Sử dụng cache dữ liệu để tối ưu hiệu suất

### 🎯 Chọn Plan Nào?

#### 1. **Guest** - Trải Nghiệm Nhanh

- **Ai nên dùng**: Người mới, thử nghiệm, không cam kết
- **Đặc điểm**: 
    - Không cần đăng nhập hay API key
    - Giới hạn 20 request/phút (1.2K/giờ, 5K/ngày)
    - Thích hợp cho khám phá nhanh
- **Ví dụ**: `quote = Quote(source="vci", symbol="VCB")`

#### 2. **Free** - Học Tập & Phát Triển

- **Ai nên dùng**: Sinh viên, developer mới, người học Python
- **Đặc điểm**:
    - Cần đăng nhập tài khoản vnstock & API key
    - Giới hạn 60 request/phút (3.6K/giờ, 10K/ngày) - **3x Guest**
    - Đủ cho phát triển & kiểm thử cơ bản
- **Cách bắt đầu**: Đăng ký miễn phí tại https://vnstocks.com/login
- **Ví dụ**: 

  ```python
  from vnstock import config
  config.set_api_key("your_api_key")
  quote = Quote(source="vci", symbol="VCB")
  ```

#### 3. **Bronze** - Dữ Liệu Cơ Bản

- **Ai nên dùng**: Nhà phân tích, trader cá nhân, startup
- **Đặc điểm**:
    - Giới hạn 180 request/phút (10.8K/giờ, 50K/ngày) - **9x Guest**
    - Truy cập **vnstock_data** với dữ liệu nâng cao
    - Plan linh hoạt (hàng tháng hoặc quý)
    - Hỗ trợ cơ bản
- **Tính năng nâng cao**: Xem [vnstock_data Overview](../vnstock-data/01-overview.md)
- **Tham gia**: https://vnstocks.com/insiders-program

#### 4. **Silver** - Chức Năng Mở Rộng

- **Ai nên dùng**: nhóm, quản lý quỹ đầu tư, dự án công khai
- **Đặc điểm**:
    - Giới hạn 300 request/phút (18K/giờ, 100K/ngày) - **15x Guest**
    - Truy cập hầu hết chức năng nâng cao của vnstock_data
    - Plan quý (3 tháng)
    - Hỗ trợ ưu tiên
- **Tính năng nâng cao**: Xem [vnstock_data Overview](../vnstock-data/01-overview.md)
- **Tham gia**: https://vnstocks.com/insiders-program

#### 5. **Golden** - Toàn Bộ Chức Năng

- **Ai nên dùng**: Dự án lâu dài, đồng hành bền vững cùng dự án
- **Đặc điểm**:
    - Giới hạn 600 request/phút (36K/giờ, 150K/ngày) - **30x Guest**
    - Truy cập **tất cả** chức năng của bộ thư viện tài trợ
    - Plan 1 năm (cam kết lâu dài)
    - Hỗ trợ tối ưu & ưu đãi chi phí tốt nhất
- **Tính năng nâng cao**: Xem [vnstock_data Overview](../vnstock-data/01-overview.md)
- **Tham gia**: https://vnstocks.com/insiders-program

### 📊 Rate Limit Chi Tiết

```python
TIER_LIMITS = {
    "guest": {"min": 20, "hour": 1200, "day": 5000},
    "free": {"min": 60, "hour": 3600, "day": 10000},
    "bronze": {"min": 180, "hour": 10800, "day": 50000},
    "silver": {"min": 300, "hour": 15000, "day": 100000},
    "golden": {"min": 500, "hour": 30000, "day": 150000}
}
```

### 🚀 Nâng Cấp

Khi bạn gặp rate limit:

```python
from vnstock.core.quota import RateLimitExceeded

try:
    quote = Quote(source="vci", symbol="VCB")
    df = quote.history(start="2024-01-01", end="2024-12-31")
except RateLimitExceeded as e:
    print(e)  # Sẽ hiển thị hướng dẫn nâng cấp phù hợp
```

---

## 🏗️ Kiến Trúc Tổng Thể

Vnstock được thiết kế theo **Adapter Pattern** với các tầng rõ ràng:

```
┌─────────────────────────────────────────┐
│         User Code (Your App)            │
├─────────────────────────────────────────┤
│  Quote | Listing | Company | Finance    │  ← Unified API Layer
│  Trading | Misc (Gold, FX)              │
├─────────────────────────────────────────┤
│  Provider Registry (Dynamic Discovery)  │
├─────────────────────────────────────────┤
│        Explorer (Web Scraping)          │
│  ┌──────────────────────────────────┐   │
│  │ VCI | KBS | MSN | FMarket        │   │
│  └──────────────────────────────────┘   │
│                                          │
│    Connector (Official APIs)             │
│  ┌──────────────────────────────────┐   │
│  │ FMP | DNSE | Binance             │   │
│  └──────────────────────────────────┘   │
└─────────────────────────────────────────┘
```

### Cấu Trúc Thư Mục Hiện Tại

```
vnstock/
├── api/                          # Unified API Layer (Facade)
│   ├── __init__.py
│   ├── quote.py                  # Quote API
│   ├── company.py                # Company API
│   ├── financial.py              # Finance API
│   ├── trading.py                # Trading API
│   ├── listing.py                # Listing API
│   └── ...
│
├── explorer/                     # Data Explorers (Source-specific)
│   ├── kbs/                      # KB Securities
│   │   ├── quote.py
│   │   ├── company.py
│   │   ├── financial.py
│   │   ├── trading.py
│   │   ├── listing.py
│   │   └── const.py
│   │
│   ├── vci/                      # VCI
│   │   ├── quote.py
│   │   ├── company.py
│   │   ├── financial.py
│   │   ├── trading.py
│   │   ├── listing.py
│   │   └── const.py
│   │
│   ├── misc/                     # Miscellaneous (Utilities)
│   │   ├── gold_price.py         # Giá vàng
│   │   └── exchange_rate.py      # Tỷ giá ngoại tệ
│   │
│   └── ... (MAS, VND, CafeF, FMarket, MBK, SPL, MSN, TCBS, v.v.)
│
├── connector/                    # Low-level Connectors
│   ├── dnse/                     # DNSE Trading
│   ├── fmp/                      # FMP (Financial Modeling Prep)
│   ├── binance/                  # Binance (Crypto - sắp tới)
│   └── ...
│
├── core/                         # Core Utilities & Infrastructure
│   ├── utils/
│   │   ├── market.py             # Giờ giao dịch, trạng thái thị trường
│   │   ├── interval.py           # Xử lý timeframe (1D, 1H, 1m, v.v.)
│   │   ├── lookback.py           # Xử lý lookback period (1M, 3M, 100D, v.v.)
│   │   ├── transform.py          # Chuyển đổi dữ liệu (long/wide format)
│   │   ├── parser.py             # Parse dữ liệu từ các nguồn
│   │   ├── validation.py         # Kiểm tra dữ liệu
│   │   ├── auth.py               # Xác thực API key
│   │   ├── client.py             # HTTP client
│   │   ├── proxy_manager.py      # Quản lý proxy
│   │   ├── logger.py             # Logging
│   │   └── ... (19+ utilities)
│   │
│   ├── types.py                  # Type definitions
│   ├── models.py                 # Data models
│   ├── registry.py               # Provider registry
│   └── ...
│
├── base.py                       # Base classes (BaseAdapter, etc.)
├── config.py                     # Configuration
├── constants.py                  # Constants
└── __init__.py                   # Package initialization
```

### Cách Hoạt Động

1. **Adapter Layer**: Bạn sử dụng các class như `Quote`, `Listing`, `Company` v.v.
2. **Provider Registry**: Thư viện tìm kiếm provider phù hợp dựa trên `source` parameter
3. **Dynamic Method Detection**: Chỉ các phương thức mà provider hỗ trợ mới được gọi
4. **Parameter Filtering**: Tự động lọc tham số để phù hợp với provider signature

---

## 📊 Phân Tầng Dữ Liệu (Data Layers)

Vnstock tổ chức dữ liệu thành các tầng theo mô hình tham khảo từ các nguồn quốc tế như Bloomberg Terminal, FinancialModelingPrep, vv

### Tầng 1: Reference Data (Dữ Liệu Tham Chiếu)

**Mục đích:** Master data, identifiers, classifications

**Dữ Liệu Hiện Có:**

- **Listing API**: Danh sách chứng khoán, chỉ số, sàn giao dịch
- **Company API**: Thông tin công ty, cổ đông, ban lãnh đạo

**Methods:**

```python
from vnstock import Listing, Company

# Listing - Danh sách chứng khoán
listing = Listing(source="vci")
symbols = listing.all_symbols()           # Tất cả mã chứng khoán
indices = listing.indices()               # Danh sách chỉ số
bonds = listing.government_bonds()        # Trái phiếu (VCI only)

# Company - Thông tin công ty
company = Company(source="vci", symbol="VCB")
profile = company.overview()              # Thông tin tổng quan
shareholders = company.shareholders()     # Cổ đông lớn
officers = company.officers()             # Ban lãnh đạo
subsidiaries = company.subsidiaries()     # Công ty con
capital_history = company.capital_history()  # Lịch sử vốn (KBS only)
```

---

### Tầng 2: Market Data (Dữ Liệu Thị Trường)

**Mục đích:** Giá, khối lượng, sổ lệnh, dữ liệu tick

**Dữ Liệu Hiện Có:**

- **Quote API**: Giá lịch sử, intraday, sổ lệnh
- **Trading API**: Bảng giá, thống kê giao dịch

**Methods:**

```python
from vnstock import Quote, Trading

# Quote - Dữ liệu giá
quote = Quote(source="vci", symbol="VCB")
history = quote.history(
    start="2024-01-01",
    end="2024-12-31",
    interval="1D"  # 1D, 1H, 1m, 5m, 15m, 30m
)
intraday = quote.intraday()               # Dữ liệu trong ngày
depth = quote.price_depth()               # Sổ lệnh

# Trading - Dữ liệu giao dịch
trading = Trading(source="vci")
board = trading.price_board(["VCB", "VNM"])  # Bảng giá
price_history = trading.price_history()      # Lịch sử giá (VCI only)
trading_stats = trading.trading_stats()      # Thống kê giao dịch (VCI only)
```

**Hỗ trợ TimeFrame:**

- Intraday: `1m`, `5m`, `15m`, `30m`, `1H`, `4h`
- Daily+: `1D`, `1W`, `1M`

---

### Tầng 3: Fundamental Data (Dữ Liệu Cơ Bản)

**Mục đích:** Báo cáo tài chính, chỉ số, tỷ lệ

**Dữ Liệu Hiện Có:**

- **Finance API**: Báo cáo tài chính (Income, Balance Sheet, Cash Flow, Ratios)

**Methods:**

```python
from vnstock import Finance

finance = Finance(source="vci", symbol="VCB")

# Báo cáo tài chính
income = finance.income_statement(period="year")      # Báo cáo thu nhập
balance = finance.balance_sheet(period="quarter")     # Bảng cân đối
cashflow = finance.cash_flow(period="year")           # Dòng tiền
ratios = finance.ratio(period="year")                 # Chỉ số tài chính
```

**Hỗ trợ Periods:**

- `year` - Hàng năm
- `quarter` - Hàng quý

---

### Tầng 4: Alternative Data (Dữ Liệu Thay Thế)

**Mục đích:** Tin tức, sự kiện, dữ liệu tiện ích

**Dữ Liệu Hiện Có:**

- **Company.news()**: Tin tức công ty
- **Misc utilities**: Giá vàng, tỷ giá ngoại tệ

**Methods:**

```python
from vnstock import Company
from vnstock.explorer.misc import GoldPrice, ExchangeRate

# Tin tức
company = Company(source="vci", symbol="VCB")
news = company.news()

# Giá vàng
gold = GoldPrice()
gold_price = gold.get_latest()

# Tỷ giá ngoại tệ
fx = ExchangeRate()
usd_vnd = fx.get_rate("USD", "VND")
```

---

### Tầng 5-7: Analytics, Macro, Insights

**Trạng thái:** Chưa triển khai đầy đủ

- **Layer 5 (Analytics)**: Chỉ số kỹ thuật, mô hình định giá, vv (chưa đầy đủ) - có thư viện vnstock_ta cung cấp tính toán bộ chỉ báo kỹ thuật.​
- **Layer 6 (Macro)**: Chỉ số kinh tế, hàng hóa - chỉ có trong thư viện vnstock_data yêu cầu tham gia gói tài trợ Vnstock.
- **Layer 7 (Insights)**: Screener, rankings top stocks, vv - (Chưa đầy đủ) - chỉ có trong thư viện vnstock_data yêu cầu tham gia gói tài trợ Vnstock.

---

## 📋 Các APIs & Dữ Liệu Hiện Có

### 1. Quote API - Dữ Liệu Giá

| Method          | Mô Tả                        | Sources            |
| --------------- | ---------------------------- | ------------------ |
| `history()`     | Dữ liệu lịch sử OHLCV        | KBS, VCI, MSN, FMP |
| `intraday()`    | Dữ liệu giao dịch trong ngày | KBS, VCI           |
| `price_depth()` | Sổ lệnh (order book)         | KBS, VCI           |

**Ứng Dụng:** Phân tích kỹ thuật, backtest chiến lược, tính toán chỉ số

---

### 2. Company API - Thông Tin Công Ty

| Method              | Mô Tả               | Sources             |
| ------------------- | ------------------- | ------------------- |
| `overview()`        | Thông tin tổng quan | KBS, VCI, TCBS, FMP |
| `officers()`        | Ban lãnh đạo        | KBS, VCI, TCBS      |
| `shareholders()`    | Cổ đông lớn         | KBS, VCI, TCBS      |
| `subsidiaries()`    | Công ty con         | KBS, VCI, TCBS      |
| `news()`            | Tin tức             | KBS, VCI, TCBS      |
| `capital_history()` | Lịch sử vốn         | KBS only            |
| `ratio_summary()`   | Tóm tắt chỉ số      | VCI only            |

**Ứng Dụng:** Nghiên cứu công ty, phân tích quản trị, theo dõi thay đổi cấp quản lý

---

### 3. Finance API - Báo Cáo Tài Chính

| Method               | Mô Tả                | Sources             |
| -------------------- | -------------------- | ------------------- |
| `income_statement()` | Báo cáo thu nhập     | KBS, VCI, TCBS, FMP |
| `balance_sheet()`    | Bảng cân đối kế toán | KBS, VCI, TCBS, FMP |
| `cash_flow()`        | Báo cáo dòng tiền    | KBS, VCI, TCBS, FMP |
| `ratio()`            | Chỉ số tài chính     | KBS, VCI, TCBS, FMP |

**Ứng Dụng:** Phân tích cơ bản, định giá công ty, so sánh ngành

---

### 4. Trading API - Dữ Liệu Giao Dịch

| Method            | Mô Tả              | Sources        |
| ----------------- | ------------------ | -------------- |
| `price_board()`   | Bảng giá realtime  | KBS, VCI, TCBS |
| `price_history()` | Lịch sử giá        | VCI only       |
| `trading_stats()` | Thống kê giao dịch | VCI only       |
| `side_stats()`    | Thống kê mua/bán   | VCI only       |

**Ứng Dụng:** Theo dõi giá thị trường, phân tích dòng tiền

---

### 5. Listing API - Danh Sách Chứng Khoán

| Method                  | Mô Tả                 | Sources            |
| ----------------------- | --------------------- | ------------------ |
| `all_symbols()`         | Tất cả mã chứng khoán | KBS, VCI, MSN, FMP |
| `symbols_by_exchange()` | Mã theo sàn           | VCI only           |
| `government_bonds()`    | Trái phiếu chính phủ  | VCI only           |
| `indices()`             | Danh sách chỉ số      | VCI, MSN, FMP      |

**Ứng Dụng:** Xây dựng danh sách chứng khoán, lọc theo tiêu chí

---

### 6. Misc/Utils - Dữ Liệu Tiện Ích

| Module         | Mô Tả           | Source       |
| -------------- | --------------- | ------------ |
| `GoldPrice`    | Giá vàng        | Web scraping |
| `ExchangeRate` | Tỷ giá ngoại tệ | Web scraping |

**Ứng Dụng:** Theo dõi giá vàng, chuyển đổi tiền tệ

---

## 🔌 Nguồn Dữ Liệu & Connectors

### Explorer (Web Scraping)

| Nguồn       | Domain       | Hỗ Trợ                                    | Phương Pháp  | Trạng Thái      |
| ----------- | ------------ | ----------------------------------------- | ------------ | --------------- |
| **VCI**     | vci.com.vn   | Quote, Listing, Company, Finance, Trading | Web Scraping | ✅ Hoạt động    |
| **KBS**     | kbsec.com.vn | Quote, Listing, Company, Finance, Trading | Web Scraping | ✅ Mới (v3.4.0) |
| **MSN**     | msn.com      | Quote, Listing                            | Web Scraping | ✅ Hoạt động    |
| **FMarket** | fmarket.vn   | Listing (Fund)                            | Web Scraping | ✅ Hoạt động    |
| **TCBS**    | tcbs.com.vn  | Quote, Listing, Company, Finance, Trading | Web Scraping | ⚠️ Ngưng hỗ trợ  |

### Connector (Official APIs)

| API         | Domain                    | Đặc Điểm                   | Chi Phí  | Trạng Thái   |
| ----------- | ------------------------- | -------------------------- | -------- | ------------ |
| **FMP**     | financialmodelingprep.com | Dữ liệu tài chính toàn cầu | Freemium | ✅ Hoạt động |
| **DNSE**    | dnse.vn                   | API giao dịch  | Miễn phí   | ✅ Hoạt động |
| **Binance** | binance.com               | Dữ liệu crypto             | Miễn phí | 📋 Sắp tới  |

---

## 🛠️ Core Utilities

Vnstock cung cấp các utilities hỗ trợ:

### Market Utilities (`core/utils/market.py`)

- `trading_hours()` - Lấy giờ giao dịch
- `is_trading_hour()` - Kiểm tra giờ giao dịch
- `market_status()` - Trạng thái thị trường (preparing, real_time, settling, historical_only)

### Interval Utilities (`core/utils/interval.py`)

- Chuẩn hóa timeframe: `1D`, `1H`, `1m`, `5m`, `15m`, `30m`, `1W`, `1M`
- Hỗ trợ aliases: `d`, `h`, `m`, `w`, `M`

### Lookback Utilities (`core/utils/lookback.py`)

- Xử lý lookback periods: `1M`, `3M`, `6M`, `1Y`, `3Y`, `5Y`, `100D`, v.v.

### Transform Utilities (`core/utils/transform.py`)

- Chuyển đổi format: Long ↔ Wide, DataFrame ↔ JSON

### Validation & Auth

- `validation.py` - Kiểm tra dữ liệu

---

## 📈 Các Loại Dữ Liệu Chi Tiết

### 1. Dữ Liệu Giá (Quote Data)

```
- Giá lịch sử: Open, High, Low, Close, Volume
- Dữ liệu trong ngày (Intraday)
- Bảng giá realtime
- Độ sâu giá (Price Depth / Order Book)
```

### 2. Dữ Liệu Danh Sách (Listing Data)

```
- Tất cả mã chứng khoán
- Lọc theo sàn giao dịch (HOSE, HNX, UPCOM)
- Lọc theo ngành (ICB Industries)
- Lọc theo chỉ số (VN30, VNMID, VNSML, v.v.)
- Futures, Bonds, Warrants, Funds
```

### 3. Dữ Liệu Công Ty (Company Data)

```
- Hồ sơ công ty
- Thông tin cổ đông chính
- Danh sách nhân viên quản lý
- Công ty con & chi nhánh
- Tin tức & sự kiện
```

### 4. Dữ Liệu Tài Chính (Financial Data)

```
- Báo cáo tài chính:
  ├─ Bảng cân đối kế toán (Balance Sheet)
  ├─ Báo cáo kết quản kinh doanh (Income Statement)
  ├─ Lưu chuyển tiền tệ (Cash Flow)
  └─ Chỉ số tài chính (Ratios)
- Theo kỳ: Hàng quý (Quarter) hoặc hàng năm (Year)
```

### 5. Dữ Liệu Giao Dịch (Trading Data)

```
- Khối lượng giao dịch
- Giá trị giao dịch
- Giao dịch cổ đông lớn
- Lịch sử chia cổ tức
```

---

## 💱 Sàn Giao Dịch (Exchanges)

```
- HOSE: Sở giao dịch Hà Nội (HOSE) - Thị trường chính
- HNX: Sở giao dịch Hà Nội (HNX) - Thị trường phụ
- UPCOM: Thị trường chứng khoán chưa niêm yết (UPCOM)
```

---

## 📑 Chỉ Số Thị Trường (Indices)

### Chỉ Số HOSE (6 chỉ số)

- **VN30**: 30 cổ phiếu vốn hóa lớn nhất & thanh khoản tốt nhất
- **VN100**: 100 cổ phiếu có vốn hoá lớn nhất HOSE
- **VNMID**: Mid-Cap Index - nhóm cổ phiếu vốn hóa trung bình
- **VNSML**: Small-Cap Index - nhóm cổ phiếu vốn hóa nhỏ
- **VNALL**: Tất cả cổ phiếu trên HOSE và HNX
- **VNSI**: Vietnam Small-Cap Index

### Chỉ Số Ngành (10 chỉ số ICB)

- **VNIT**: Công nghệ thông tin
- **VNIND**: Công nghiệp
- **VNCONS**: Hàng tiêu dùng
- **VNCOND**: Hàng tiêu dùng thiết yếu
- **VNHEAL**: Chăm sóc sức khoẻ
- **VNENE**: Năng lượng
- **VNUTI**: Dịch vụ tiện ích
- **VNREAL**: Bất động sản
- **VNFIN**: Tài chính
- **VNMAT**: Nguyên vật liệu

### Chỉ Số Đầu Tư (3 chỉ số)

- **VNDIAMOND**: Chỉ số các cổ phiếu có triển vọng lớn
- **VNFINLEAD**: Chỉ số tài chính đầu ngành
- **VNFINSELECT**: Chỉ số tài chính được chọn lọc

---

## 🔄 Cách Sử Dụng Cơ Bản

### Khởi Tạo

```python
from vnstock import Quote, Listing, Company, Finance, Trading

# Quote - Giá chứng khoán
quote = Quote(source="vci", symbol="VCB")

# Listing - Danh sách chứng khoán
listing = Listing(source="vci")

# Company - Dữ liệu công ty
company = Company(source="vci", symbol="VCB")

# Finance - Dữ liệu tài chính
finance = Finance(source="vci", symbol="VCB")

# Trading - Dữ liệu giao dịch
trading = Trading(source="vci")
```

### Parameters Phổ Biến

```python
# Common parameters
Quote(
    source="vci",           # Nguồn dữ liệu: vci, kbs, msn, fmp, etc.
    symbol="VCB",           # Mã chứng khoán
    random_agent=False,     # Sử dụng random user agent
    show_log=False          # Hiển thị log chi tiết
)
```

### Chỉ Định Source

```python
from vnstock.core.types import DataSource

# Liệt kê tất cả available sources
print(DataSource.all_sources())
# Output: ['vci', 'kbs', 'msn', 'dnse', 'fmp', 'fmarket']

# Sử dụng enum
quote_vci = Quote(source=DataSource.VCI, symbol="VCB")
quote_kbs = Quote(source=DataSource.KBS, symbol="VCB")
quote_msn = Quote(source=DataSource.MSN, symbol="VCB")

# ⚠️ TCBS đã ngưng được hỗ trợ, không nên sử dụng
```

---

## 🛡️ Xử Lý Lỗi & Retry

Vnstock tự động xử lý lỗi tạm thời với:

- **Retry tự động**: Tối đa 5 lần (có thể cấu hình)
- **Exponential Backoff**: Tăng độ trễ giữa các lần thử
- **Timeout thông minh**: Tránh treo khi kết nối chậm

```python
from vnstock.config import Config

# Tuỳ chỉnh retry behavior
Config.RETRIES = 3  # Số lần retry
Config.BACKOFF_MULTIPLIER = 2  # Hệ số backoff
Config.BACKOFF_MIN = 1  # Độ trễ tối thiểu (giây)
Config.BACKOFF_MAX = 30  # Độ trễ tối đa (giây)
```

---

## 📚 Cấu Trúc Dữ Liệu Trả Về

### DataFrame (Pandas)

Hầu hết các phương thức trả về `pd.DataFrame`:

```python
df = quote.history(
    symbol="VCB",
    start="2024-01-01",
    end="2024-12-31"
)

# Output: DataFrame với các cột
# Columns: time, open, high, low, close, volume, value
```

### Dictionary

Một số phương thức trả về `dict`:

```python
profile = company.overview()

# Output: Dictionary với thông tin công ty
# {
#     'symbol': 'VCB',
#     'company_name': '...',
#     'exchange': 'HOSE',
#     ...
# }
```

### List

Danh sách:

```python
symbols = listing.all_symbols()

# Output: List of strings
# ['AAA', 'AAH', 'AAT', 'ABS', 'ABT', ...]
```

---

## ✅ Kiểm Tra Lỗi Thường Gặp

### 1. ValueError: Invalid Source

```python
# ❌ Sai
quote = Quote(source="invalid_source", symbol="VCB")

# ✅ Đúng
quote = Quote(source="vci", symbol="VCB")
```

### 2. NotImplementedError

```python
# ❌ Sai - MSN không hỗ trợ Finance
finance = Finance(source="msn", symbol="VCB")
df = finance.balance_sheet()  # NotImplementedError

# ✅ Đúng - Sử dụng KBS hoặc VCI
finance = Finance(source="kbs", symbol="VCB")
df = finance.balance_sheet()
```

### 3. TCBS Deprecated

```python
# ❌ Không nên sử dụng
quote = Quote(source="tcbs", symbol="VCB")

# ✅ Sử dụng KBS hoặc VCI thay thế
quote = Quote(source="vci", symbol="VCB")
```

---

## 🔗 Bước Tiếp Theo

1. **[02-Installation](02-installation.md)** - Cài đặt & cấu hình
2. **[03-Listing API](03-listing-api.md)** - Tìm kiếm chứng khoán
3. **[04-Quote & Price](04-quote-price-api.md)** - Giá lịch sử & realtime
4. **[05-Financial API](05-financial-api.md)** - Dữ liệu tài chính
5. **[06-Company API](06-company-api.md)** - Thông tin công ty
6. **[07-Trading API](07-trading-api.md)** - Dữ liệu giao dịch
7. **[08-Best Practices](08-best-practices.md)** - Mẹo & kinh nghiệm

---

**Last Updated**: Tháng 1, 2026  

**Version**: 3.4.0  

**Status**: Hoạt động  

**Important**: TCBS đã ngưng được hỗ trợ, sẽ bị loại bỏ vào khoảng tháng 3/2026.

path: references/vnstock/02-installation.md
content: # 02 - Cài Đặt & Cấu Hình

## 📦 Yêu Cầu Hệ Thống

- **Python**: 3.8 hoặc cao hơn (khuyến nghị 3.10+)
- **OS**: Windows, macOS, hoặc Linux
- **Internet**: Kết nối internet ổn định

## 🚀 Cài Đặt Nhanh

### Option 1: Cài từ PyPI (Stable)

```bash
pip install vnstock
```

### Option 2: Cài từ GitHub (Latest Development)

```bash
pip install git+https://github.com/vnstock-lab/vnstock.git
```

### Option 3: Cài từ Local (Dev Version)

```bash
# Clone hoặc copy thư mục private_packages
pip install git+https://github.com/vnstock-lab/vnstock.git
```

## 📋 Dependencies

VNStock phụ thuộc vào các package sau:

```
pandas>=1.3.0          # Xử lý DataFrame
requests>=2.25.0       # HTTP requests
beautifulsoup4>=4.9.0  # Web scraping
lxml>=4.6.0            # XML parsing
pydantic>=1.8.0        # Data validation
tenacity>=8.0.0        # Retry logic
python-dateutil>=2.8.0 # Date utilities
aiohttp>=3.7.0         # Async HTTP
tqdm>=4.60.0           # Progress bars
packaging>=20.0        # Version parsing
python-dotenv>=0.19.0  # Env file support
```

### Cài đặt tự động (Recommended)

```bash
# Tất cả dependencies sẽ tự động được cài
pip install vnstock
```

### Cài đặt thủ công

```bash
pip install pandas requests beautifulsoup4 lxml pydantic tenacity \
    python-dateutil aiohttp tqdm packaging python-dotenv
```

## Xác Thực API Key

VNStock hỗ trợ các cấp độ sử dụng khác nhau với giới hạn requests tương ứng:

### Cấp Độ Sử Dụng

| Cấp độ | Giới hạn | Yêu cầu | Mô tả |
|--------|----------|---------|-------|
| **Khách (Guest)** | 20 requests/phút | Không cần đăng ký | Sử dụng miễn phí, giới hạn thấp |
| **Cộng đồng (Community)** | 60 requests/phút | Đăng ký miễn phí | Phù hợp cá nhân mới tìm hiểu |
| **Tài trợ (Sponsor)** | 180-600 requests/phút | Thành viên tài trợ| Dành cho nghiên cứu chuyên sâu |

### Đăng Ký API Key (Miễn Phí)

** 1. Đăng ký tương tác**

```python
from vnstock.core.utils.auth import register_user

# Chạy đăng ký tương tác
register_user()
```

Quá trình đăng ký sẽ:
1. Kiểm tra nếu đã có API key
2. Hướng dẫn đến trang đăng nhập: https://vnstocks.com/login
3. Nhập API key từ tài khoản Vnstock của người dùng
4. Lưu và xác thực API key

** 2. Đổi API key**

1. Truy cập https://vnstocks.com/login
2. Đăng nhập bằng tài khoản Google
3. Lấy API key từ trang quản lý tài khoản
4. Lưu API key bằng code:

```python
from vnstock.core.utils.auth import change_api_key

# Thay đổi API key
change_api_key("your_api_key_here")
```

### Kiểm Tra Trạng Thái

```python
from vnstock.core.utils.auth import check_status

# Kiểm tra trạng thái hiện tại
status = check_status()
# Output:
# ✓ API key: ab12***ef34
#   Tier: Community
#   Giới hạn: 60 requests/phút
```

### Sử Dụng Sau Khi Đăng Ký

Sau khi đăng ký API key, VNStock sẽ tự động sử dụng key cho tất cả requests:

```python
from vnstock import Quote, Listing

# Sẽ tự động sử dụng API key đã đăng ký
quote = Quote(source="KBS", symbol="VCI")
df = quote.history(start="2024-01-01", end="2024-12-31")

# Không cần cấu hình thêm gì!
```

### Lưu Ý Quan Trọng

- **API key được lưu trữ**: Không cần nhập lại. Nếu chạy trên môi trường Google Colab, sẽ phải lặp lại nhập API key mỗi lần sử dụng.
- **Miễn phí**: Sử dụng bậc miễn phí dành cho đào tạo cộng đồng với nhu cầu trải nghiệm thấp
- **Google OAuth**: Đăng nhập nhanh bằng tài khoản Google

## � Cấu Hình

### 1. Basic Configuration

VNStock có thể dùng ngay sau khi cài đặt mà không cần cấu hình:

```python
from vnstock import Quote, Listing

# Khởi tạo với KBS (khuyến nghị)
quote = Quote(source="KBS", symbol="VCI")
listing = Listing(source="KBS")

# Hoặc VCI
quote_kbs = Quote(source="VCI", symbol="VCI")
listing_kbs = Listing(source="VCI")
```

### 2. Environment Variables

Tạo file `.env` trong project directory:

```bash
# .env file
VNSTOCK_TIMEOUT=30
VNSTOCK_RETRIES=5
VNSTOCK_BACKOFF_MULTIPLIER=2
```

Load trong code:

```python
from dotenv import load_dotenv
import os

load_dotenv()

timeout = os.getenv('VNSTOCK_TIMEOUT', '30')
retries = os.getenv('VNSTOCK_RETRIES', '5')
```

### 3. Configuration Object

```python
from vnstock.config import Config

# Thay đổi cấu hình
Config.RETRIES = 3
Config.BACKOFF_MULTIPLIER = 2
Config.BACKOFF_MIN = 1
Config.BACKOFF_MAX = 30
Config.TIMEOUT = 30
```

### 4. External API Keys

Nếu sử dụng external APIs như FMP, XNO, DNSE:

```bash
# .env file
FMP_API_KEY=your_fmp_api_key_here
XNO_API_KEY=your_xno_api_key_here
DNSE_API_KEY=your_dnse_api_key_here
BINANCE_API_KEY=your_binance_key_here
BINANCE_API_SECRET=your_binance_secret_here
```

Load trong code:

```python
from vnstock import Quote
import os
from dotenv import load_dotenv

load_dotenv()

# Sử dụng FMP API
quote = Quote(
    source="fmp",
    symbol="VCI",
    [REDACTED_SECRET]'FMP_API_KEY')
)
```

## ✅ Kiểm Tra Cài Đặt

### 1. Kiểm Tra Import

```python
# test_installation.py
import sys

print("📦 Checking imports...")

try:
    from vnstock import Quote, Listing, Company, Finance, Trading, Screener
    print("✅ All main classes imported successfully")
except ImportError as e:
    print(f"❌ Import Error: {e}")
    sys.exit(1)

try:
    from vnstock.core.types import DataSource, TimeFrame
    print("✅ Core types imported successfully")
except ImportError as e:
    print(f"❌ Core types Error: {e}")
    sys.exit(1)

try:
    from vnstock.constants import INDICES_INFO, EXCHANGES, SECTOR_IDS
    print("✅ Constants imported successfully")
except ImportError as e:
    print(f"❌ Constants Error: {e}")
    sys.exit(1)

print("\n📊 Available Data Sources:", DataSource.all_sources())
print("⚠️  Note: TCBS is deprecated, use VCI or KBS instead")
print("🆕 KBS is now available in v3.4.0")
print("⏱️ Available TimeFrames:", [t.value for t in TimeFrame])
print("\n✅ All checks passed!")
```

Chạy test:

```bash
python test_installation.py
```

### 2. Quick Test

```python
# quick_test.py
from vnstock import Quote, Listing
from vnstock.core.types import TimeFrame

print("Testing Quote...")# Khởi tạo với KBS (khuyến nghị)
quote = Quote(source="KBS", symbol="VCI")
print(f"✅ Quote initialized: {quote}")

print("\nTesting Listing...")
listing = Listing(source="KBS")
print(f"✅ Listing initialized: {listing}")

print("\n✅ Installation successful!")
```

## 🐛 Troubleshooting

### Issue 1: `ModuleNotFoundError: No module named 'vnstock'`

**Giải pháp:**

```bash
# Cài đặt lại
pip uninstall vnstock -y
pip install vnstock
```

### Issue 2: `ModuleNotFoundError: No module named 'pandas'`

**Giải pháp:**

```bash
# Cài dependencies
pip install pandas requests beautifulsoup4 lxml pydantic tenacity

# Hoặc cài toàn bộ
pip install vnstock --upgrade
```

### Issue 3: `ImportError: cannot import name 'Quote'`

**Giải pháp:**

```python
# ✅ Đúng cách import
from vnstock import Quote, Listing, Company

# ❌ Sai cách
from vnstock.Quote import Quote  # Không cần như này
```

### Issue 4: Network/Connection Errors

**Lỗi:**
```
requests.exceptions.ConnectionError: 
Failed to establish a new connection
```

**Giải pháp:**

```python
from vnstock import Quote
from vnstock.config import Config

# Tăng timeout
Config.TIMEOUT = 60

# Hoặc sử dụng proxy
quote = Quote(
    source="vci",
    symbol="VCI",
    proxy="http://your-proxy:port"
)
```

### Issue 5: Rate Limit / 429 Error

**Lỗi:**
```
HTTPError: 429 Too Many Requests
```

**Giải pháp:**

**Cách 1: Đăng ký API key miễn phí**

```python
from vnstock.core.utils.auth import register_user

# Đăng ký để tăng từ 20 lên 60 requests/phút
register_user()
```

**Cách 2: Tăng retry và delay**

```python
from vnstock.config import Config
import time

# Tăng delay giữa requests
Config.RETRIES = 5
Config.BACKOFF_MULTIPLIER = 3

# Hoặc thêm delay thủ công
def safe_request(func, *args, **kwargs):
    try:
        return func(*args, **kwargs)
    except Exception as e:
        if "429" in str(e):
            time.sleep(5)  # Chờ 5 giây rồi thử lại
            return func(*args, **kwargs)
        raise

result = safe_request(quote.history, symbol="VCI", start_date="2024-01-01")
```

## 📖 Project Structure

Thư mục tiêu chuẩn khi làm việc với vnstock:

```
my_project/
├── .env                      # Cấu hình & API keys
├── .gitignore               # Bỏ qua .env khi commit
├── requirements.txt         # Dependencies
├── main.py                  # Code chính
├── data/                    # Lưu dữ liệu
│   └── cache/              # Cache dữ liệu
├── logs/                    # Log files
└── tests/
    └── test_vnstock.py      # Unit tests
```

### Ví dụ requirements.txt

```
vnstock>=3.4.0
vnai>=2.3.9
pandas>=1.3.0
numpy>=1.20.0
matplotlib>=3.3.0
python-dotenv>=0.19.0
```

### Ví dụ .gitignore

```
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg

# Environment
.env
.venv
env/
venv/
ENV/

# IDE
.vscode/
.idea/
*.swp
*.swo

# Data
data/
*.csv
*.xlsx
logs/

# Cache
.cache/
*.pyc
```

## 🚀 Getting Started - Ví dụ Đơn Giản

### Ví dụ 1: Lấy Danh Sách Cổ Phiếu

```python
# example1_list_symbols.py
from vnstock import Listing

# Khởi tạo với KBS (khuyến nghị)
listing = Listing(source="KBS")

# Lấy tất cả mã chứng khoán
all_symbols = listing.all_symbols(to_df=True)
print(f"Tổng số mã: {len(all_symbols)}")
print(all_symbols.head())

# Lấy theo sàn
hose_symbols = listing.symbols_by_exchange(exchange="HOSE")
print(f"\nTổng mã HOSE: {len(hose_symbols)}")
print(hose_symbols[:10])

# Lấy theo chỉ số
vn30_symbols = listing.symbols_by_group(group="VN30")
print(f"\nTổng mã VN30: {len(vn30_symbols)}")
print(vn30_symbols)
```

Chạy:

```bash
python example1_list_symbols.py
```

### Ví dụ 2: Lấy Giá Lịch Sử

```python
# example2_price_history.py
from vnstock import Quote
from vnstock.core.types import TimeFrame

# Khởi tạo với KBS (khuyến nghị)
quote = Quote(source="KBS", symbol="VCI")

# Lấy giá lịch sử
df = quote.history(
    start_date="2024-01-01",
    end_date="2024-12-31",
    resolution=TimeFrame.DAILY
)

print("Giá lịch sử:")
print(df.head())
print(f"\nTổng cộng: {len(df)} ngày")
print(f"Giá cao nhất: {df['high'].max()}")
print(f"Giá thấp nhất: {df['low'].min()}")
print(f"Khối lượng trung bình: {df['volume'].mean():,.0f}")
```

### Ví dụ 3: Lấy Thông Tin Công Ty

```python
# example3_company_info.py
from vnstock import Company

# Khởi tạo với KBS (khuyến nghị)
company = Company(source="KBS", symbol="VCI")

# Lấy thông tin công ty
overview = company.overview()
print("Thông tin công ty:")
print(overview)

# Lấy cổ đông chính
shareholders = company.shareholders()
print("\nCổ đông chính:")
print(shareholders)

# Lấy nhân viên quản lý
officers = company.officers()
print("\nNhân viên quản lý:")
print(officers)
```

## 📚 Các Bước Tiếp Theo

1. ✅ **Installation** - Bạn đã ở đây
2. [01-Overview](01-overview.md) - Tổng quan thư viện
3. [03-Listing API](03-listing-api.md) - Tìm kiếm chứng khoán
4. [04-Quote & Price](04-quote-price-api.md) - Giá lịch sử & realtime
5. [05-Financial API](05-financial-api.md) - Dữ liệu tài chính
6. [06-Connector Guide](06-connector-guide.md) - API bên ngoài
7. [07-Best Practices](07-best-practices.md) - Mẹo & kinh nghiệm

---

**Last Updated**: 2024-12-17  
**Version**: 3.4.0  
**Status**: Actively Maintained  
**Important**: TCBS deprecated, use VCI or KBS instead


path: references/vnstock/03-listing-api.md
content: # 03 - Listing API - Tìm Kiếm & Lọc Chứng Khoán

## 📖 Giới Thiệu

Listing API cung cấp các phương thức tìm kiếm, lọc và lấy thông tin về các chứng khoán có sẵn trên thị trường. Dữ liệu bao gồm:

- Danh sách tất cả mã chứng khoán
- Lọc theo sàn giao dịch (HOSE, HNX, UPCOM)
- Lọc theo ngành công nghiệp (ICB)
- Lọc theo chỉ số (VN30, VNMID, VNSML, etc.)
- Futures, Bonds, Warrants, Funds
- Industries & Sector classification

## 🔌 So Sánh Nguồn Dữ Liệu

| Method | KBS | VCI | Ghi Chú |
|--------|-----|-----|---------|
| **all_symbols()** | ✅ | ✅ | Cấu trúc giống nhau |
| **symbols_by_exchange()** | ✅ | ✅ | KBS 6 columns, VCI 7 columns |
| **symbols_by_industries()** | ✅ | ✅ | KBS 3 columns, VCI 10 columns |
| **symbols_by_group()** | ✅ | ✅ | Cả hai đều trả về Series |
| **industries_icb()** | ✅ | ✅ | KBS có thể rỗng, VCI đầy đủ |
| **all_future_indices()** | ✅ | ✅ | Cả hai đều Series |
| **all_government_bonds()** | ✅ | ✅ | Cả hai đều Series |
| **all_covered_warrant()** | ✅ | ✅ | Cả hai đều Series |
| **all_bonds()** | ✅ | ✅ | Cả hai đều Series |
| **all_etf()** | ✅ | ❌ | **KBS độc quyền** |
| **get_supported_groups()**  | ✅ | ❌ | **KBS độc quyền** |
| **all_indices()** | ✅ | ✅ | chung |
| **indices_by_group()** | ✅ | ✅ | chung |

**Tổng số methods:**
- **KBS**: 12 methods
- **VCI**: 13 methods

**Khuyến nghị:**
- **KBS**: Ổn định hơn cho Google Colab/Kaggle
- **VCI**: Dữ liệu đầy đủ hơn, có ICB classification và indices

## 🏗️ Khởi Tạo

```python
from vnstock import Listing

# Khởi tạo Listing adapter
# Hỗ trợ KBS, VCI, MSN
listing = Listing(
    source="vci",           # Nguồn dữ liệu (khuyến nghị)
    random_agent=False      # Sử dụng random user agent
)

# Hoặc với KBS (mới trong v3.4.0)
listing_kbs = Listing(source="kbs")

# ⚠️ TCBS đã deprecated, không nên sử dụng
# listing_tcbs = Listing(source="tcbs")  # DeprecatedWarning sẽ hiện ra
```

## 📋 Các Phương Thức

### 1. all_symbols() - Tất Cả Mã Chứng Khoán

Lấy danh sách tất cả mã chứng khoán.

**Parameters:**

```
- to_df (bool): Trả về DataFrame (default: True)
- lang (str): Ngôn ngữ ('vi' hoặc 'en')
```

**Ví dụ:**

**Với KBS (khuyến nghị):**
```python
# Khởi tạo với KBS
listing = Listing(source="KBS")

# Trả về DataFrame
df = listing.all_symbols(to_df=True)
print(f"Shape: {df.shape}")  # (1565, 2)
print(f"Columns: {list(df.columns)}")
print(f"Dtypes:\n{df.dtypes}")
# Output:
# Shape: (1565, 2)
# Columns: ['symbol', 'organ_name']
# Dtypes:
# symbol        object
# organ_name    object
df.head()
# Output với KBS:
#   symbol          organ_name
# 0    DPP  CTCP Dược Đồng Nai
# 1    SDA  CTCP Simco Sông Đà

# Trả về list
symbols = listing.all_symbols(to_df=False)
print(f"Type: {type(symbols)}")  # <class 'list'>
print(f"Length: {len(symbols)}")  # 1565
print(symbols[:10])
# Output: ['DPP', 'SDA', 'SDC', 'SDH', 'SDS', 'SDT', 'SDV', 'SDW', 'SDY', 'SDZ']
```

**Với VCI (nguồn truyền thống):**
```python
# Khởi tạo với VCI
listing = Listing(source="VCI")

# Trả về DataFrame
df = listing.all_symbols(to_df=True)
print(f"Shape: {df.shape}")  # (1733, 2)
print(f"Columns: {list(df.columns)}")
print(f"Dtypes:\n{df.dtypes}")
# Output:
# Shape: (1733, 2)
# Columns: ['symbol', 'organ_name']
# Dtypes:
# symbol        object
# organ_name    object
df.head()
# Output với VCI:
#   symbol                                         organ_name
# 0    YTC  Công ty Cổ phần Xuất nhập khẩu Y tế Thành phố ...
# 1    YEG                     Công ty Cổ phần Tập đoàn Yeah1

# Trả về list
symbols = listing.all_symbols(to_df=False)
print(f"Length: {len(symbols)}")  # 1733
print(symbols[:10])
# Output: ['YTC', 'YEG', 'YBM', 'YBC', 'XPH', 'XDC', 'XDC1', 'XDA', 'XDA1', 'XDG']
```

### 2. symbols_by_exchange() - Lọc Theo Sàn

Lấy danh sách mã chứng khoán theo sàn giao dịch.

**Parameters:**

```
- exchange (str): Sàn giao dịch
  ├─ 'HOSE': Sở giao dịch Hà Nội (HOSE) - Thị trường chính
  ├─ 'HNX': Sở giao dịch Hà Nội (HNX) - Thị trường phụ
  └─ 'UPCOM': Chứng khoán chưa niêm yết (UPCOM)
- lang (str): Ngôn ngữ ('vi' hoặc 'en')
```

**Ví dụ:**

**Với KBS (khuyến nghị):**
```python
# Khởi tạo với KBS
listing = Listing(source="KBS")

# Lấy các mã HOSE
hose_symbols = listing.symbols_by_exchange(exchange="HOSE", to_df=True)
print(f"Shape: {hose_symbols.shape}")  # (1952, 6)
print(f"Columns: {list(hose_symbols.columns)}")
print(f"Dtypes:\n{hose_symbols.dtypes}")
# Output:
# Shape: (1952, 6)
# Columns: ['symbol', 'organ_name', 'en_organ_name', 'exchange', 'type', 'id']
# Dtypes:
# symbol           object
# organ_name       object
# en_organ_name    object
# exchange         object
# type             object
# id                int64
print(hose_symbols[['symbol', 'exchange', 'type']].head())
# Output với KBS:
#   symbol exchange   type  id
# 0    DPP    UPCOM  stock   1
# 1    SDA      HNX  stock   1

# Lấy các mã HNX
hnx_symbols = listing.symbols_by_exchange(exchange="HNX", to_df=True)
print(f"HNX symbols: {len(hnx_symbols)}")

# Lấy các mã UPCOM
upcom_symbols = listing.symbols_by_exchange(exchange="UPCOM", to_df=True)
print(f"UPCOM symbols: {len(upcom_symbols)}")

# Chỉ lấy list symbols
hose_list = listing.symbols_by_exchange(exchange="HOSE", to_df=False)
print(f"Type: {type(hose_list)}")  # <class 'list'>
print(f"First 10: {hose_list[:10]}")
```

**Với VCI (nguồn truyền thống):**
```python
# Khởi tạo với VCI
listing = Listing(source="VCI")

# Lấy các mã HOSE
hose_symbols = listing.symbols_by_exchange(exchange="HOSE", to_df=True)
print(f"Shape: {hose_symbols.shape}")  # (3210, 7)
print(f"Columns: {list(hose_symbols.columns)}")
print(f"Dtypes:\n{hose_symbols.dtypes}")
# Output:
# Shape: (3210, 7)
# Columns: ['symbol', 'exchange', 'type', 'organ_short_name', 'organ_name', 'product_grp_id', 'icb_code2']
# Dtypes:
# symbol              object
# exchange            object
# type                object
# organ_short_name    object
# organ_name          object
# product_grp_id      object
# icb_code2           object
print(hose_symbols[['symbol', 'exchange', 'type']].head())
# Output với VCI:
#   symbol exchange   type organ_short_name                                         organ_name product_grp_id icb_code2
# 0    YTC    UPCOM  STOCK  XNK Y tế TP.HCM  Công ty Cổ phần Xuất nhập khẩu Y tế Thành phố ...            UPX      4500
# 1    YEG      HSX  STOCK   Tập đoàn Yeah1                     Công ty Cổ phần Tập đoàn Yeah1            STO      5500

# Chỉ lấy list symbols
hose_list = listing.symbols_by_exchange(exchange="HOSE", to_df=False)
print(f"Type: {type(hose_list)}")  # <class 'list'>
print(f"First 10: {hose_list[:10]}")
```

**Kiến Thức Nâng Cao:**

```python
# Đếm mã theo sàn
from collections import Counter

all_df = listing.all_symbols(to_df=True)
exchange_counts = all_df['exchange'].value_counts()
print(exchange_counts)
# Output:
# HOSE     1020
# HNX      140
# UPCOM     80
# Name: exchange, dtype: int64

# So sánh giữa các sàn
hose_df = all_df[all_df['exchange'] == 'HOSE']
hnx_df = all_df[all_df['exchange'] == 'HNX']

print(f"HOSE industries: {hose_df['industry'].nunique()}")
print(f"HNX industries: {hnx_df['industry'].nunique()}")
```

### 3. symbols_by_industries() - Lọc Theo Ngành

Lấy danh sách mã chứng khoán theo ngành công nghiệp.

**Parameters:**

```
- to_df (bool): Trả về DataFrame
- lang (str): Ngôn ngữ
```

**Ví dụ:**

**Với KBS (khuyến nghị):**
```python
# Khởi tạo với KBS
listing = Listing(source="KBS")

# Lọc theo ngành cụ thể
banking_df = listing.symbols_by_industries(industry_name='Ngân hàng', to_df=True)
print(f"Total Banking stocks: {len(banking_df)}")
print(banking_df.head())
# Output với KBS:
# Total Banking stocks: 697
#   symbol  industry_code           industry_name
# 0    ABR              6  Công nghệ và thông tin
# 1    ADC              6  Công nghệ và thông tin
# 2    BED              6  Công nghệ và thông tin
# 3    CKV              6  Công nghệ và thông tin
# 4    CMG              6  Công nghệ và thông tin

# Lấy tất cả các ngành (không lọc)
all_industries = listing.symbols_by_industries(to_df=True)
print(f"Total symbols with industry: {len(all_industries)}")
print(all_industries.head())
# Output:
#   symbol  industry_code           industry_name
# 0    MGC              1  Nông nghiệp - lâm nghiệp và thủy sản
# 1    GVT              1  Nông nghiệp - lâm nghiệp và thủy sản
# 2    SWC              1  Nông nghiệp - lâm nghiệp và thủy sản
# 3    SLD              1  Nông nghiệp - lâm nghiệp và thủy sản
# 4    VID              1  Nông nghiệp - lâm nghiệp và thủy sản

# Lấy danh sách các ngành duy nhất
unique_industries = all_industries['industry_name'].unique()
print(f"Total industries: {len(unique_industries)}")
print(f"First 10 industries: {list(unique_industries[:10])}")
# Output: Total industries: 28
```

**Với VCI (nguồn truyền thống):**
```python
# Khởi tạo với VCI
listing = Listing(source="VCI")

# Lọc theo ngành cụ thể
banking_df = listing.symbols_by_industries(lang='vi', to_df=True)
print(f"Total Banking stocks: {len(banking_df)}")
print(banking_df.head())
# Output với VCI:
# Total Banking stocks: 35
#   symbol                                         organ_name                   icb_name3  ... icb_code2 icb_code3 icb_code4
# 0    STB                      Ngân hàng TMCP Sài Gòn                     Ngân hàng  ...      8000      8350      8353
# 1    TCB                      Ngân hàng TMCP Kỹ thương Việt Nam                 Ngân hàng  ...      8000      8350      8353
# 2    CTG                      Ngân hàng TMCP Công thương Việt Nam                 Ngân hàng  ...      8000      8350      8353

# Lấy tất cả các ngành (không lọc)
all_industries = listing.symbols_by_industries(lang='vi', to_df=True)
print(f"Total symbols with industry: {len(all_industries)}")
print(f"Total industries: {len(all_industries)}")
industries = listing.symbols_by_industries(to_df=True)
unique_industries = industries['industry_name'].unique()
print(f"Total industries: {len(unique_industries)}")
print(unique_industries)

# Top 5 ngành có nhiều mã nhất
industry_counts = industries['industry_name'].value_counts().head(5)
print(industry_counts)
# Output:
# Finance           200
# Technology        150
# Real Estate       120
# ...

# Lấy thông tin chi tiết về các ngành ICB (Industry Classification Benchmark) - chỉ hỗ trợ với VCI.
# Parameters:
# - lang (str): Ngôn ngữ
# Ví dụ (với VCI):
# Top 5 ngành có nhiều mã nhất
top_5 = industry_counts.head(5)
print(top_5)
```

### 4. industries_icb() - Phân Loại ICB

⚠️ **Lưu ý với KBS**: KBS không cung cấp ICB classification. Sử dụng `symbols_by_industries()` để lấy mã theo ngành.

Lấy thông tin chi tiết về các ngành ICB (Industry Classification Benchmark) - chỉ hỗ trợ với VCI.

**Parameters:**

```
- lang (str): Ngôn ngữ
```

**Ví dụ (với VCI):**

```python
# Sử dụng VCI cho ICB
listing_vci = Listing(source="vci")

# Lấy danh sách ICB
icb_df = listing_vci.industries_icb()
print(icb_df.head())
# Output:
#   icb_id  icb_code  icb_name            super_group
# 0  6001    1000     Oil & Gas           Energy
# 1  6002    1001     Coal                Energy
# 2  6003    1010     Alternative Energy Energy
# ...

# Thong tin chi tiet
print(f"Total ICB categories: {len(icb_df)}")
print(f"Columns: {icb_df.columns.tolist()}")

# Tim theo super_group
energy = icb_df[icb_df['super_group'] == 'Energy']
print(f"Energy sectors: {energy['icb_name'].tolist()}")
```

**Lỗi với KBS:**

```python
# ❌ Sẽ gây lỗi với KBS
try:
    icb_df = listing.industries_icb()
except NotImplementedError as e:
    print(f"Lỗi: {e}")
# Output: Lỗi: KBS không cung cấp ICB classification. Sử dụng symbols_by_industries() để lấy mã theo ngành.
```

**Kiến Thức:**

```python
# Lấy danh sách các super_group
super_groups = icb_df['super_group'].unique()
print(f"Total super_groups: {len(super_groups)}")
for group in super_groups:
    sectors = icb_df[icb_df['super_group'] == group]
    print(f"{group}: {len(sectors)} sectors")
```

### 5. symbols_by_group() - Lọc Theo Chỉ Số

Lấy danh sách mã chứng khoán theo chỉ số (Index Group).

**Parameters:**

```
- group (str): Tên chỉ số
  ├─ VN30, VN100, VNMID, VNSML, VNALL, VNSI
  ├─ VNIT, VNIND, VNCONS, VNCOND, VNHEAL, VNENE
  ├─ VNUTI, VNREAL, VNFIN, VNMAT
  ├─ VNDIAMOND, VNFINLEAD, VNFINSELECT
  └─ VNX50, VNXALL
```

**Ví dụ:**

```python
# VN30 - 30 cổ phiếu vốn hóa lớn nhất
vn30 = listing.symbols_by_group(group_name="VN30", to_df=True)
print(f"VN30 symbols: {vn30['symbol'].tolist()}")
# Output với KBS:
# VN30 symbols: ['ACB', 'BCM', 'BID', 'CTG', 'DGC', 'FPT', 'GAS', 'GVR', 'HDB', 'HPG', 
#                'LPB', 'MBB', 'MSN', 'MWG', 'PLX', 'SAB', 'SHB', 'SSB', 'SSI', 'STB', 
#                'TCB', 'TPB', 'VCB', 'VHM', 'VIB', 'VIC', 'VJC', 'VNM', 'VPB', 'VRE']
print(f"Total VN30: {len(vn30)}")
# Output: Total VN30: 30

# HNX30 - 30 cổ phiếu trên HNX
hnx30 = listing.symbols_by_group(group_name="HNX30", to_df=True)
print(f"HNX30 symbols: {hnx30['symbol'].tolist()}")
# Output với KBS:
# HNX30 symbols: ['BVS', 'CAP', 'CEO', 'DHT', 'DP3', 'DTD', 'DVM', 'DXP', 'HGM', 'HUT', 
#                 'IDC', 'IDV', 'L14', 'L18', 'LAS', 'LHC', 'MBS', 'NTP', 'PLC', 'PSD', 
#                 'PVB', 'PVC', 'PVI', 'PVS', 'SHS', 'SLS', 'TMB', 'TNG', 'VC3', 'VCS']
print(f"Total HNX30: {len(hnx30)}")
# Output: Total HNX30: 30

# Chỉ lấy list symbols
vn30_list = listing.symbols_by_group(group_name="VN30", to_df=False)
print(f"First 10 VN30: {vn30_list[:10]}")
# Output: First 10 VN30: ['ACB', 'BCM', 'BID', 'CTG', 'DGC', 'FPT', 'GAS', 'GVR', 'HDB', 'HPG']
```

**Kiến Thức Nâng Cao:**

```python
from vnstock.constants import INDEX_GROUPS

# Lấy tất cả chỉ số
all_groups = []
for group_category, indices in INDEX_GROUPS.items():
    print(f"{group_category}: {indices}")
    all_groups.extend(indices)

# Lấy tất cả mã từ VN30 đến VN100
vn30_symbols = set(listing.symbols_by_group(group="VN30"))
vn100_symbols = set(listing.symbols_by_group(group="VN100"))

# Mã ở VN100 nhưng không ở VN30
vn31_to_100 = vn100_symbols - vn30_symbols
print(f"VN31-100 symbols: {sorted(list(vn31_to_100))}")
```

### 6. all_future_indices() - Futures

Lấy danh sách tất cả hợp đồng tương lai.

**Ví dụ:**

```python
# Lấy danh sách futures
futures_df = listing.all_future_indices()
print(futures_df.head())
# Output:
#   symbol  contract_name  maturity_date
# 0   VNI   VN Index Futures  2024-12-31
# 1   VI1   VN30 Dec24        2024-12-31
# ...

print(f"Total futures: {len(futures_df)}")
```

### 7. all_government_bonds() - Trái Phiếu Chính Phủ

Lấy danh sách trái phiếu chính phủ.

**Ví dụ:**

```python
# Lấy danh sách trái phiếu
bonds_df = listing.all_government_bonds()
print(bonds_df.head())
# Output:
#   symbol  bond_name  maturity_date  coupon
# 0  GB01   10Y Bond   2030-01-01     5.5%
# ...
```

### 8. all_covered_warrant() - Warrant

Lấy danh sách warrant được phủ (Covered Warrant).

**Ví dụ:**

```python
# Lấy danh sách warrant
warrants_df = listing.all_covered_warrant()
print(warrants_df[['symbol', 'underlying', 'expiry_date']].head())
```

### 9. all_bonds() - Trái Phiếu Doanh Nghiệp

Lấy danh sách trái phiếu doanh nghiệp.

**Ví dụ:**

```python
# Lấy danh sách corporate bonds
bonds_df = listing.all_bonds()
print(bonds_df[['symbol', 'issuer', 'coupon', 'maturity']].head())
```

## 🔄 Kết Hợp & Lọc Nâng Cao

### Ví dụ 1: Cổ Phiếu Lớn ở Ngành Tài Chính

```python
import pandas as pd
from vnstock import Listing

listing = Listing(source="vci")

# Lấy dữ liệu
all_symbols = listing.all_symbols(to_df=True)
industries = listing.symbols_by_industries(to_df=True)

# Kết hợp dữ liệu
merged = all_symbols.merge(industries, on='symbol', how='left')

# Lọc theo ngành Finance và sàn HOSE
finance_hose = merged[
    (merged['industry'] == 'Finance') & 
    (merged['exchange'] == 'HOSE')
]

print(f"Finance stocks on HOSE: {len(finance_hose)}")
print(finance_hose[['symbol', 'company_name']].head())
```

### Ví dụ 2: So Sánh VN30 vs VN31-100

```python
# Lấy dữ liệu
vn30_set = set(listing.symbols_by_group(group="VN30"))
vn100_set = set(listing.symbols_by_group(group="VN100"))

# VN30
print("VN30 symbols:")
print(sorted(vn30_set))

# VN31-100 (ở VN100 nhưng không ở VN30)
vn31_100 = sorted(vn100_set - vn30_set)
print(f"\nVN31-100 symbols ({len(vn31_100)} stocks):")
print(vn31_100)

# Lấy chi tiết của VN31-100
all_df = listing.all_symbols(to_df=True)
vn31_100_df = all_df[all_df['symbol'].isin(vn31_100)]
print("\nVN31-100 details:")
print(vn31_100_df[['symbol', 'company_name', 'industry']].to_string())
```

### Ví dụ 3: Ngành Công Nghệ

```python
# Lấy tất cả cổ phiếu IT
vnit_symbols = listing.symbols_by_group(group="VNIT")
print(f"IT stocks ({len(vnit_symbols)}): {vnit_symbols}")

# Lấy chi tiết
industries_df = listing.symbols_by_industries(to_df=True)
it_stocks = industries_df[industries_df['symbol'].isin(vnit_symbols)]
print("\nIT stocks details:")
print(it_stocks[['symbol', 'industry_name']].to_string())
```

### Ví dụ 4: Export Danh Sách

```python
# Export VN30
vn30 = listing.symbols_by_group(group="VN30")
with open('vn30_symbols.txt', 'w') as f:
    for symbol in vn30:
        f.write(symbol + '\n')

# Export tất cả cổ phiếu theo ngành
industries = listing.symbols_by_industries(to_df=True)
industries.to_excel('all_stocks_by_industry.xlsx', index=False)

# Export VN100 chi tiết
all_df = listing.all_symbols(to_df=True)
vn100_symbols = listing.symbols_by_group(group="VN100")
vn100_df = all_df[all_df['symbol'].isin(vn100_symbols)]
vn100_df.to_csv('vn100_details.csv', index=False)

print("✅ Exported successfully!")
```

## � Methods Độc Quyền

### 1. get_supported_groups() - Danh Sách Nhóm Hỗ Trợ (Chỉ KBS)

Lấy danh sách tất cả các nhóm được hỗ trợ bởi KBS.

**Ví dụ:**
```python
# Khởi tạo với KBS
listing = Listing(source="KBS")

# Lấy danh sách nhóm hỗ trợ
supported_groups = listing.get_supported_groups()
print(f"Shape: {supported_groups.shape}")  # (16, 4)
print(f"Columns: {list(supported_groups.columns)}")
print(f"Dtypes:\n{supported_groups.dtypes}")
# Output:
# Shape: (16, 4)
# Columns: ['group_name', 'group_code', 'category', 'description']
# Dtypes:
# group_name     object
# group_code     object
# category       object
# description    object
print(supported_groups[['group_name', 'category']].head())
```

**Output với KBS:**
```
  group_name       category
0       BOND     Trái phiếu
1         CW    Chứng quyền
2        ETF        ETF/Quỹ
3   FU_INDEX      Phái sinh
4        HNX  Sàn giao dịch
```

### 2. all_indices() - Tất Cả Chỉ Số (Hỗ trợ từ tất cả sources qua `Listing`)

Lấy danh sách tất cả các chỉ số tiêu chuẩn hóa với thông tin đầy đủ. Trước đây chỉ có trên VCI, từ phiên bản 3.4.1 hàm này đã được chuẩn hoá và có thể gọi từ bất kỳ adapter nào thông qua `Listing(source=...)`. Kết quả trả về là `pd.DataFrame` với các cột tiêu chuẩn: [`symbol`, `name`, `description`, `full_name`, `group`, `index_id`, `sector_id`] (nếu có).

**Ví dụ (VCI):**
```python
# Khởi tạo với VCI
listing = Listing(source="VCI")

all_indices_vci = listing.all_indices()
print(f"Shape: {all_indices_vci.shape}")
print(all_indices_vci[['symbol', 'name', 'group']].head())
```

**Ví dụ (KBS):**
```python
# Khởi tạo với KBS
listing = Listing(source="KBS")

all_indices_kbs = listing.all_indices()
print(f"Shape: {all_indices_kbs.shape}")
print(all_indices_kbs[['symbol', 'name', 'group']].head())
```

**Lưu ý:**
- Một số provider có thể không có đầy đủ `sector_id` hoặc metadata giống VCI; hàm sẽ trả về những chỉ số sẵn có và giữ định dạng chuẩn để thuận tiện cho phân tích.

### 3. indices_by_group() - Chỉ Số Theo Nhóm (Hỗ trợ từ tất cả sources qua `Listing`)

Lấy danh sách chỉ số theo nhóm tiêu chuẩn hóa (ví dụ: các chỉ số HOSE, chỉ số ngành/sector). Hàm này hiện đã hỗ trợ gọi từ `Listing` với mọi `source` (ví dụ: `kbs`, `vci`, `msn`) và trả về dữ liệu đã được chuẩn hoá.

**Tham số:**
- `group` (str): Tên nhóm (VD: `'HOSE'`, `'Sector Indices'`, ...)

**Ví dụ (HOSE từ KBS):**
```python
# Khởi tạo với KBS
listing = Listing(source="KBS")

indices = listing.indices_by_group(group="HOSE")
if indices is not None:
    print(f"Shape: {indices.shape}")
    print(indices[['symbol', 'name']].head())
else:
    print("Không có dữ liệu cho nhóm này")
```

**Ví dụ (HOSE từ VCI):**
```python
# Khởi tạo với VCI
listing = Listing(source="VCI")

indices = listing.indices_by_group(group="HOSE")
print(indices[['symbol', 'name']].head())
```

**Lưu ý:**
- Một số source có thể cung cấp các nhóm khác nhau; nếu không có dữ liệu cho `group` truyền vào, hàm có thể trả về `None`.

## �📊 Performance & Caching

### Caching Dữ Liệu

```python
import pickle
import os
from vnstock import Listing

listing = Listing(source="vci")

CACHE_FILE = 'listing_cache.pkl'

# Lấy hoặc load từ cache
if os.path.exists(CACHE_FILE):
    with open(CACHE_FILE, 'rb') as f:
        all_symbols = pickle.load(f)
    print("✅ Loaded from cache")
else:
    all_symbols = listing.all_symbols(to_df=True)
    with open(CACHE_FILE, 'wb') as f:
        pickle.dump(all_symbols, f)
    print("✅ Cached for next time")

print(all_symbols.head())
```

### Batch Operations

```python
# Lấy dữ liệu một lần, dùng nhiều lần
all_symbols = listing.all_symbols(to_df=True)
industries = listing.symbols_by_industries(to_df=True)
icb = listing.industries_icb()

# Lọc theo nhiều tiêu chí
hose_df = all_symbols[all_symbols['exchange'] == 'HOSE']
print(f"HOSE: {len(hose_df)}")

finance_df = hose_df[hose_df['industry'] == 'Finance']
print(f"HOSE Finance: {len(finance_df)}")
```

## ❌ Các Lỗi Thường Gặp

### Lỗi 1: ValueError - Invalid Source

```python
# ❌ Sai
listing = Listing(source="invalid")

# ✅ Đúng - KBS (khuyến nghị), VCI, MSN
listing = Listing(source="kbs")  # Nguồn mới, ổn định
listing = Listing(source="vci")  # Nguồn truyền thống
listing = Listing(source="msn")  # Nguồn dữ liệu quốc tế, crypto

# ⚠️ TCBS đã deprecated
# listing = Listing(source="tcbs")  # DeprecatedWarning
```

### Lỗi 2: NotImplementedError - ICB với KBS

```python
# ❌ KBS không hỗ trợ ICB
try:
    icb_df = listing.industries_icb()
except NotImplementedError as e:
    print(f"Lỗi: {e}")
    # Solution: Sử dụng symbols_by_industries() thay thế
    industries = listing.symbols_by_industries()
```

### Lỗi 3: Network/Timeout

```python
# Tăng timeout
from vnstock.config import Config
Config.TIMEOUT = 60

# Hoặc retry
from tenacity import retry, stop_after_attempt

@retry(stop=stop_after_attempt(3))
def get_symbols():
    return listing.all_symbols()
```

### Lỗi 4: Empty Result

```python
# Nếu không có dữ liệu
symbols = listing.symbols_by_group(group_name="INVALID_INDEX")
if not symbols or len(symbols) == 0:
    print("⚠️ No symbols found for this group")
```

## 📚 Bước Tiếp Theo

1. [02-Installation](02-installation.md) - Cài đặt
2. [01-Overview](01-overview.md) - Tổng quan
3. ✅ **03-Listing API** - Bạn đã ở đây
4. [04-Quote & Price](04-quote-price-api.md) - Giá lịch sử
5. [05-Financial API](05-financial-api.md) - Dữ liệu tài chính
6. [06-Connector Guide](06-connector-guide.md) - API bên ngoài
7. [07-Best Practices](07-best-practices.md) - Mẹo & kinh nghiệm

---

**Last Updated**: 2024-12-17  
**Version**: 3.4.0  
**Status**: Actively Maintained  
**Important**: KBS là nguồn dữ liệu mới được khuyến nghị, ổn định hơn VCI cho Google Colab/Kaggle


path: references/vnstock/04-company-api.md
content: # 04 - Company API - Thông Tin Công Ty

## 📖 Giới Thiệu

**Company API** cung cấp thông tin chi tiết về các công ty cổ phần, bao gồm hồ sơ cơ bản, cấu trúc cổ đông, nhân viên quản lý, sự kiện công ty, và tin tức.

## 🔌 So Sánh Nguồn Dữ Liệu

| Method | KBS | VCI | Ghi Chú |
|--------|-----|-----|---------|
| **overview()** | ✅ | ✅ | KBS có 30 columns, VCI có 10 columns |
| **shareholders()** | ✅ | ✅ | KBS trả về 1 dòng, VCI trả về nhiều dòng |
| **officers()** | ✅ | ✅ | VCI có filter_by, KBS không |
| **subsidiaries()** | ✅ | ✅ | Cấu trúc khác nhau |
| **affiliate()** | ✅ | ✅ | Cả hai đều có |
| **news()** | ✅ | ✅ | KBS có pagination, VCI không |
| **events()** | ✅ | ✅ | KBS có thể rỗng, VCI đầy đủ |
| **ownership()** | ✅ | ❌ | Chỉ KBS hỗ trợ |
| **capital_history()** | ✅ | ❌ | Chỉ KBS hỗ trợ |
| **insider_trading()** | ✅ | ❌ | Chỉ KBS hỗ trợ |
| **reports()** | ❌ | ✅ | Chỉ VCI hỗ trợ |
| **trading_stats()** | ❌ | ✅ | Chỉ VCI hỗ trợ |
| **ratio_summary()** | ❌ | ✅ | Chỉ VCI hỗ trợ |

**Khuyến nghị:**
- **KBS**: Ổn định hơn cho Google Colab/Kaggle, có thêm dữ liệu insider trading
- **VCI**: Dữ liệu đầy đủ hơn cho events, có financial reports và trading stats

## 🔌 Nguồn Dữ Liệu

| Nguồn | Hỗ Trợ | Ghi Chú |
|-------|--------|--------|
| **KBS** | ✅ | Web scraping - Khuyến nghị, ổn định |
| VCI | ✅ | Web scraping - Nguồn truyền thống |
| TCBS | ⚠️ | Web scraping - Deprecated, sẽ loại bỏ v3.5.0 |

## 🚀 Bắt Đầu

```python
from vnstock import Company

# Khởi tạo với KBS (khuyến nghị)
company = Company(source="KBS", symbol="VCI")

# Xem thông tin cổ đông
shareholders = company.shareholders()
print(shareholders)

# Hoặc với VCI
company_vci = Company(source="VCI", symbol="VCI")

# ⚠️ TCBS đã deprecated, không nên sử dụng
# company_tcbs = Company(source="TCBS", symbol="VCI")
```

## 📚 Phương Thức Chính

### 1. overview() - Thông Tin Cơ Bản

Lấy thông tin tổng quan về công ty.

**Tham số:** Không có

**Trả về:** `pd.DataFrame` (1 dòng) với các cột:
- `symbol` - Mã chứng khoán
- `issue_share` - Số cổ phiếu phát hành
- `company_profile` - Hồ sơ công ty (JSON)
- `icb_name2`, `icb_name3`, `icb_name4` - Phân loại ngành (ICB)
- `financial_ratio_issue_share` - Thông tin tài chính
- `charter_capital` - Vốn điều lệ

**Ví dụ:**

**Với KBS (khuyến nghị):**
```python
# Khởi tạo với KBS
company = Company(source="KBS", symbol="VCI")
overview = company.overview()
print(f"Shape: {overview.shape}")  # (1, 30)
print(f"Columns: {list(overview.columns)}")
print(f"Dtypes:\n{overview.dtypes}")
# Output:
# Shape: (1, 30)
# Columns: ['business_model', 'symbol', 'founded_date', 'charter_capital', 
#           'number_of_employees', 'listing_date', 'par_value', 'exchange', ...]
# Dtypes:
# business_model           object
# symbol                   object
# founded_date             object
# charter_capital           int64
# number_of_employees       int64
# ...
print(overview[['symbol', 'charter_capital', 'exchange']].head())
```

**Output với KBS:**
```
                                      business_model symbol founded_date  charter_capital  exchange
0  \n- Môi giới chứng khoán và giao dịch cho vay ...    VCI   06/08/2007      8501000000000      HOSE
```

**Với VCI (nguồn truyền thống):**
```python
# Khởi tạo với VCI
company = Company(source="VCI", symbol="VCI")
overview = company.overview()
print(f"Shape: {overview.shape}")  # (1, 10)
print(f"Columns: {list(overview.columns)}")
print(f"Dtypes:\n{overview.dtypes}")
# Output:
# Shape: (1, 10)
# Columns: ['symbol', 'id', 'issue_share', 'history', 'company_profile', 
#           'icb_name3', 'icb_name2', 'icb_name4', 'financial_ratio_issue_share', 'charter_capital']
# Dtypes:
# symbol                         object
# id                             object
# issue_share                     int64
# history                        object
# ...
print(overview[['symbol', 'charter_capital', 'icb_name4']].head())
```

**Output với VCI:**
```
  symbol     id  issue_share  ...             icb_name4 charter_capital
0    VCI  75885    850100000  ...  Môi giới chứng khoán   8501000000000
```

### 2. shareholders() - Cổ Đông Chính

Lấy danh sách các cổ đông lớn.

**Tham số:** Không có

**Trả về:** `pd.DataFrame` với các cột:
- `name` - Tên cổ đông (str)
- `update_date` - Ngày cập nhật (str, format: "YYYY-MM-DDTHH:MM:SS")
- `shares_owned` - Số cổ phiếu sở hữu (int64)
- `ownership_percentage` - Tỷ lệ sở hữu (float64, %)

**Ví dụ:**

**Với KBS (khuyến nghị):**
```python
# Khởi tạo với KBS
company = Company(source="KBS", symbol="VCI")
shareholders = company.shareholders()
print(shareholders.shape)  # (1, 4)
print(shareholders[['name', 'shares_owned', 'ownership_percentage']].head())
```

**Output với KBS:**
```
      name          update_date  shares_owned  ownership_percentage
0  Tô Hải  2025-06-30T00:00:00     128889403                 17.95
```

**Với VCI (nguồn truyền thống):**
```python
# Khởi tạo với VCI
company = Company(source="VCI", symbol="VCI")
shareholders = company.shareholders()
print(shareholders.shape)  # (33, 5)
print(shareholders[['share_holder', 'quantity', 'share_own_percent']].head(3))
```

**Output với VCI:**
```
         id           share_holder   quantity  share_own_percent update_date
0  96744105                 Tô Hải  129139403            0.17870  2025-10-31
1  96742707         PYN Elite Fund    8132100            0.04910  2025-01-24
2  96734076  Nguyễn Phan Minh Khôi    7483872            0.04591  2025-01-24
```

### 3. officers() - Ban lãnh đạo

Lấy danh sách ban lãnh đạo (Ban điều hành, Hội đồng quản trị).

**Tham số:**
- `filter_by` (str, tùy chọn): Loại lọc
  - `"all"` - Tất cả (mặc định)
  - `"ceo"` - Chỉ CEO
  - `"boc"` - Board of Directors

**Trả về:** `pd.DataFrame` với các cột:
- `from_date` - Năm bắt đầu (int)
- `position` - Vị trí công việc (str, VN)
- `name` - Tên nhân viên (str)
- `position_en` - Vị trí công việc (str, EN)
- `owner_code` - Mã sở hữu (str)

**Ví dụ:**

**Với KBS (khuyến nghị):**
```python
# Khởi tạo với KBS
company = Company(source="KBS", symbol="VCI")
officers = company.officers()
print(officers.shape)  # (12, 5)
print(officers[['name', 'position', 'from_date']].head(3))
```

**Output với KBS:**
```
                   name        position  from_date
0  Bà Nguyễn Thanh Phượng          CTHĐQT       2007
1       Ông Đinh Quang Hoàn  TVHĐQT/Phó TGĐ       2007
2                Ông Tô Hải      TGĐ/TVHĐQT       2007
```

**Với VCI (nguồn truyền thống):**
```python
# Khởi tạo với VCI
company = Company(source="VCI", symbol="VCI")
officers = company.officers()
print(officers.shape)  # (14, 7)
print(officers[['officer_name', 'officer_position', 'officer_own_percent']].head(3))
```

**Output với VCI:**
```
   id         officer_name                            officer_position  officer_own_percent   quantity
0  11               Tô Hải  Tổng Giám đốc/Thành viên Hội đồng Quản trị               0.1787  129139403
1  14  Nguyễn Thanh Phượng                  Chủ tịch Hội đồng Quản trị               0.0318   22815000
2   4     Nguyễn Quang Bảo                           Phó Tổng Giám đốc               0.0032    2324156
```

### 4. subsidiaries() - Công Ty Con

Lấy danh sách công ty con.

**Tham số:**
- `filter_by` (str, tùy chọn): 
  - `"subsidiary"` - Công ty con trực tiếp
  - `"all"` - Tất cả

**Trả về:** `pd.DataFrame` với các cột:
- `update_date` - Ngày cập nhật (str, format: "YYYY-MM-DDTHH:MM:SS")
- `name` - Tên công ty con (str)
- `charter_capital` - Vốn điều lệ (int64)
- `ownership_percent` - Tỷ lệ sở hữu (float64, %)
- `currency` - Loại tiền tệ (str)
- `type` - Loại quan hệ (str)

**Ví dụ:**

**Với KBS (khuyến nghị):**
```python
# Khởi tạo với KBS
company = Company(source="KBS", symbol="VCI")
subsidiaries = company.subsidiaries()
print(subsidiaries.shape)  # (1, 6)
print(subsidiaries[['name', 'charter_capital', 'ownership_percent']])
```

**Output với KBS:**
```
                                          name  charter_capital  ownership_percent
0  CTCP Quản lý Quỹ Đầu tư Chứng khoán Bản Việt     130000000000                 51
```

**Với VCI (nguồn truyền thống):**
```python
# Khởi tạo với VCI
company = Company(source="VCI", symbol="VCI")
try:
    subsidiaries = company.subsidiaries()
    print(subsidiaries.shape)
    print(subsidiaries.head())
except Exception as e:
    print(f"VCI subsidiaries error: {e}")
# Output: VCI subsidiaries error: RetryError[<Future...>]
```

### 5. affiliate() - Công Ty Liên Kết

Lấy danh sách công ty liên kết.

**Tham số:** Không có

**Trả về:** `pd.DataFrame`

⚠️ **Lưu ý:** Phương thức này có thể trả về lỗi nếu không có dữ liệu

### 6. news() - Tin Tức

Lấy tin tức gần đây về công ty.

**Tham số:** Không có

**Trả về:** `pd.DataFrame` với các cột:
- `head` - Tiêu đề tin (str)
- `article_id` - ID bài viết (int64)
- `publish_time` - Thời gian xuất bản (str, format: "YYYY-MM-DDTHH:MM:SS")
- `url` - Liên kết tin tức (str)

**Ví dụ:**

**Với KBS (khuyến nghị):**
```python
# Khởi tạo với KBS
company = Company(source="KBS", symbol="VCI")
news = company.news()
print(news.shape)  # (1, 5)
print(news[['head', 'publish_time']].head())
```

**Output với KBS:**
```
                                           head  article_id           publish_time                                                url
0  VCI- Thông báo về ngày đăng ký cuối cùng...    1386720  2025-12-31T14:03:26  /2025/12/vci-thong-bao-ve-ngay-dang-ky-cuoi-cu...
```

**Với VCI (nguồn truyền thống):**
```python
# Khởi tạo với VCI
company = Company(source="VCI", symbol="VCI")
news = company.news()
print(news.shape)  # (10, 18)
print(news[['news_title', 'public_date', 'price_change_pct']].head(3))
```

**Output với VCI:**
```
        id                                         news_title  ...  price_change_pct
0  9121667  VCI: Thông báo về việc giao dịch chứng khoán t...  ...        -0.013235
1  9108930  VCI: Giấy phép điều chỉnh giấy phép thành lập ...  ...         0.019118
2  9095781  VCI: Quyết định về việc thay đổi đăng ký niêm yết                 ...        -0.002825
```

### 7. events() - Sự Kiện Công Ty

Lấy danh sách sự kiện công ty (chia cổ tức, phát hành cổ phiếu, niêm yết, v.v.).

⚠️ **Lưu ý với KBS**: Có thể không có dữ liệu sự kiện cho một số công ty.

**Tham số:** Không có

**Trả về:** `pd.DataFrame` với các cột:
- `id` - ID sự kiện
- `event_title` - Tiêu đề sự kiện (str, VN)
- `en__event_title` - Tiêu đề sự kiện (str, EN)
- `public_date` - Ngày công bố (str)
- `issue_date` - Ngày phát hành (str)
- `source_url` - Liên kết tài liệu
- `event_list_code` - Mã loại sự kiện (str)
- `event_list_name` - Tên loại sự kiện (str, VN)
- `en__event_list_name` - Tên loại sự kiện (str, EN)
- `ratio` - Tỷ lệ (float64, VD: 0.35 = 35%)
- `value` - Giá trị (float64)
- `record_date` - Ngày ghi danh (str)
- `exright_date` - Ngày hết quyền (str)

**Ví dụ:**

**Với KBS (khuyến nghị):**
```python
# Khởi tạo với KBS
company = Company(source="KBS", symbol="VCI")
events = company.events()
print(events.shape)  # (0, 0) - Có thể rỗng

# Nếu không có dữ liệu với KBS, thử VCI
if events.empty:
    company_vci = Company(source="VCI", symbol="VCI")
    events = company_vci.events()
    print(f"VCI events: {events.shape}")
```

**Với VCI (nguồn truyền thống):**
```python
# Khởi tạo với VCI
company = Company(source="VCI", symbol="VCI")
events = company.events()
print(events.shape)  # (32, 13)
print(events[['event_title', 'event_list_name', 'public_date']].head(5))
```

**Output với VCI:**
```
         id                                        event_title  ...           event_list_name en__event_list_name
0   1868825  VCI - Trả cổ tức Đợt 1, 2021 bằng tiền 1200 VN...  ...  Trả cổ tức bằng tiền mặt       Cash Dividend
1  16582552  VCI - Trả cổ tức Đợt 1 năm 2022 bằng tiền 700 ...  ...  Trả cổ tức bằng tiền mặt       Cash Dividend
2  22322707  VCI - Trả cổ tức Đợt 2 năm 2022 bằng tiền 500 ...  ...  Trả cổ tức bằng tiền mặt       Cash Dividend
3  42249237  VCI - Trả cổ tức Đợt 1 năm 2024 bằng tiền 400 ...  ...  Trả cổ tức bằng tiền mặt       Cash Dividend
4  50556599  VCI - Trả cổ tức Đợt 2 năm 2024 bằng tiền 250 ...  ...  Trả cổ tức bằng tiền mặt       Cash Dividend
```

## 💡 Ví Dụ Thực Tế

### Phân Tích Cấu Trúc Cổ Đông

**Với KBS (khuyến nghị):**
```python
from vnstock import Company

# Khởi tạo với KBS
company = Company(source="KBS", symbol="VCI")
shareholders = company.shareholders()

# Top cổ đông lớn (KBS chỉ trả về 1 dòng)
top_shareholder = shareholders.nlargest(1, 'shares_owned')
print("Cổ đông lớn nhất:")
print(top_shareholder[['name', 'shares_owned', 'ownership_percentage']])

# Tính tỷ lệ sở hữu
total_ownership = shareholders['ownership_percentage'].sum()
print(f"\nTổng tỷ lệ sở hữu: {total_ownership:.2f}%")
```

**Với VCI (nguồn truyền thống):**
```python
from vnstock import Company

# Khởi tạo với VCI
company = Company(source="VCI", symbol="VCI")
shareholders = company.shareholders()

# Top 5 cổ đông lớn
top_5 = shareholders.nlargest(5, 'quantity')
print("Top 5 cổ đông:")
print(top_5[['share_holder', 'quantity', 'share_own_percent']])

# Tính tập trung cổ đông
top_10_pct = shareholders.nlargest(10, 'share_own_percent')['share_own_percent'].sum()
print(f"\nTrong lượng cổ đông top 10: {top_10_pct:.2f}%")
```

### Theo Dõi Ban Quản Trị

```python
from vnstock import Company

# Khởi tạo với KBS
company = Company(source="KBS", symbol="VCI")
officers = company.officers()

# Các vị trí lãnh đạo
positions = officers['position'].unique()
print(f"Số lượng vị trí quản lý: {len(positions)}")
print(f"Các vị trí: {list(positions)}")

# Cổ đông nội bộ (có sở hữu cổ phiếu)
insiders = officers[officers['position'].str.contains('TGĐ|CTHĐQT|TVHĐQT', na=False)]
print(f"\nBan lãnh đạo: {len(insiders)} người")
print(insiders[['name', 'position']])
```

### Theo Dõi Sự Kiện

```python
from vnstock import Company

# Khởi tạo với KBS
company = Company(source="KBS", symbol="VCI")
events = company.events()

# Kiểm tra nếu có sự kiện
if not events.empty:
    # Sự kiện chia cổ tức
    dividend_events = events[events['event_list_code'] == 'DIV']
    print(f"Số lần chia cổ tức: {len(dividend_events)}")
    
    # Sự kiện phát hành cổ phiếu
    issue_events = events[events['event_list_code'] == 'ISS']
    print(f"Số lần phát hành cổ phiếu: {len(issue_events)}")
else:
    print("Không có dữ liệu sự kiện với KBS, thử VCI:")
    company_vci = Company(source="VCI", symbol="VCI")
    events = company_vci.events()
    print(f"VCI events: {events.shape}")
```

### 8. ownership() - Cấu Trúc Cổ Đông (Chỉ KBS)

Lấy thông tin cơ cấu cổ đông theo tỷ lệ sở hữu - chỉ có ở KBS.

**Tham số:** Không có

**Trả về:** `pd.DataFrame` với các cột:
- `owner_type` - Loại cổ đông (str)
- `ownership_percentage` - Tỷ lệ sở hữu (float64, %)
- `shares_owned` - Số cổ phiếu sở hữu (int64)
- `update_date` - Ngày cập nhật (str, format: "YYYY-MM-DDTHH:MM:SS")

**Ví dụ:**
```python
# Khởi tạo với KBS
company = Company(source="KBS", symbol="VCI")
ownership = company.ownership()
print(ownership.shape)  # (3, 4)
print(ownership)
```

**Output với KBS:**
```
                owner_type  ownership_percentage  shares_owned          update_date
0     CĐ nắm trên 5% số CP                 17.95     128889403  2024-12-31T00:00:00
1  CĐ nắm từ 1% - 5% số CP                 39.65     284754680  2024-12-31T00:00:00
2     CĐ nắm dưới 1% số CP                 42.40     304455397  2024-12-31T00:00:00
```

### 9. capital_history() - Lịch Sử Vốn Điều Lệ (Chỉ KBS)

Lấy lịch sử thay đổi vốn điều lệ của công ty - chỉ có ở KBS.

**Tham số:** Không có

**Trả về:** `pd.DataFrame` với các cột:
- `date` - Ngày thay đổi (str, format: "YYYY-MM-DD")
- `charter_capital` - Vốn điều lệ (int64)
- `currency` - Loại tiền tệ (str)

**Ví dụ:**
```python
# Khởi tạo với KBS
company = Company(source="KBS", symbol="VCI")
capital_history = company.capital_history()
print(capital_history.shape)  # (19, 3)
print(capital_history.head())
```

**Output với KBS:**
```
        date  charter_capital currency
0 2025-12-17    8501000000000      VND
1 2025-03-07    7226000000000      VND
2 2024-06-12    7180994800000      VND
3 2024-10-10    5744694800000      VND
4 2024-05-08    4419000000000      VND
```

### 10. insider_trading() - Giao Dịch Nội Bộ (Chỉ KBS)

Lấy thông tin giao dịch của người nội bộ - chỉ có ở KBS.

**Tham số:**
- `page` (int, tùy chọn): Số trang (mặc định: 1)
- `page_size` (int, tùy chọn): Kích thước trang (mặc định: 10)

**Trả về:** `pd.DataFrame` (có thể rỗng)

**Ví dụ:**
```python
# Khởi tạo với KBS
company = Company(source="KBS", symbol="VCI")
insider_trading = company.insider_trading()
print(f"Shape: {insider_trading.shape}")
# Output: Shape: (0, 0) - Có thể rỗng nếu không có dữ liệu
```

### 11. reports() - Báo Cáo Phân Tích (Chỉ VCI)

Lấy báo cáo phân tích về công ty - chỉ có ở VCI.

**Tham số:** Không có

**Trả về:** `pd.DataFrame` (có thể rỗng)

**Ví dụ:**
```python
# Khởi tạo với VCI
company = Company(source="VCI", symbol="VCI")
reports = company.reports()
print(f"Shape: {reports.shape}")
# Output: Shape: (0, 0) - Có thể rỗng nếu không có báo cáo
```

### 12. trading_stats() - Thống Kê Giao Dịch (Chỉ VCI)

Lấy thống kê giao dịch của công ty - chỉ có ở VCI.

**Tham số:** Không có

**Trả về:** `pd.DataFrame` với 24 columns bao gồm:
- `symbol`, `exchange`, `ev`, `ceiling`, `floor`
- `avg_match_volume_2w`, `foreign_holding_room`, `current_holding_ratio`
- `max_holding_ratio`, và nhiều thống kê khác

**Ví dụ:**
```python
# Khởi tạo với VCI
company = Company(source="VCI", symbol="VCI")
trading_stats = company.trading_stats()
print(trading_stats.shape)  # (1, 24)
print(trading_stats[['symbol', 'exchange', 'ev', 'foreign_holding_room']].head())
```

**Output với VCI:**
```
  symbol exchange              ev  ceiling  foreign_holding_room
0    VCI     HOSE  29498470000000    37250             144233070
```

### 13. ratio_summary() - Tóm Tắt Tỷ Lệ Tài Chính (Chỉ VCI)

Lấy tóm tắt các tỷ lệ tài chính của công ty - chỉ có ở VCI.

**Tham số:** Không có

**Trả về:** `pd.DataFrame` với 46 columns tài chính

**Ví dụ:**
```python
# Khởi tạo với VCI
company = Company(source="VCI", symbol="VCI")
ratio_summary = company.ratio_summary()
print(ratio_summary.shape)  # (1, 46)
print(ratio_summary[['symbol', 'year_report', 'revenue', 'ebit']].head())
```

**Output với VCI:**
```
  symbol  year_report        revenue          ebit
0    VCI         2025  1443289075867  716139241499
```

```

## ⚠️ Ghi Chú Quan Trọng

1. **KBS là nguồn khuyến nghị**: Ổn định hơn VCI cho Google Colab/Kaggle
2. **Dữ liệu không đầy đủ**: Không phải công ty nào cũng có đầy đủ thông tin cho tất cả phương thức
3. **KBS hạn chế**: Events có thể rỗng, chỉ trả về 1 cổ đông lớn nhất
4. **Giá trị NaN**: Nếu không có dữ liệu, sẽ trả về `NaN` hoặc rỗng
5. **Phụ thuộc vào nguồn**: Thông tin khác nhau giữa KBS, VCI và TCBS
6. **TCBS deprecated**: Sẽ loại bỏ trong v3.5.0, không nên sử dụng
7. **Dữ liệu lịch sử**: Thông tin lịch sử được cập nhật định kỳ
8. **Methods độc quyền**: KBS có ownership/capital_history/insider_trading, VCI có reports/trading_stats/ratio_summary

## 🔗 Xem Thêm

- **[03-Listing API](03-listing-api.md)** - Tìm kiếm chứng khoán
- **[05-Trading API](05-trading-api.md)** - Dữ liệu giao dịch
- **[06-Financial API](06-financial-api.md)** - Dữ liệu tài chính
- **[08-Best Practices](08-best-practices.md)** - Mẹo tối ưu hóa

---

**Last Updated**: 2024-12-17  
**Version**: 3.4.0  
**Status**: Actively Maintained  
**Important**: KBS là nguồn dữ liệu mới được khuyến nghị, ổn định hơn VCI cho Google Colab/Kaggle


path: references/vnstock/05-trading-api.md
content: # 05 - Trading API - Dữ Liệu Giao Dịch

## 📖 Giới Thiệu

**Trading API** cung cấp dữ liệu giao dịch chi tiết: bảng giá real-time và mức bid/ask thị trường.

## 🔌 So Sánh Nguồn Dữ Liệu

| Method | KBS | VCI | Ghi Chú |
|--------|-----|-----|---------|
| **price_board()** | ✅ | ✅ | Cả hai đều có flat columns |

**Tổng số methods:**
- **KBS**: 1 method
- **VCI**: 1 method

**Khuyến nghị:**
- **KBS**: Dữ liệu mới, áp dụng bộ tiêu chuẩn dữ liệu mới từ Vnstock, ổn định hơn và phù hợp cho sử dụng cả với Google Colab.
- **VCI**: Dữ liệu cực kỳ chi tiết (77 columns), phù hợp cho phân tích sâu

## 🚀 Bắt Đầu

```python
from vnstock import Trading

# Khởi tạo với KBS (khuyến nghị)
trading_kbs = Trading(source="KBS", symbol="VCI")

# Khởi tạo với VCI
trading_vci = Trading(source="VCI", symbol="VCI")

# Lấy bảng giá thị trường
board_kbs = trading_kbs.price_board(symbols_list=['VCI', 'VCB', 'ACB'])
board_vci = trading_vci.price_board(symbols_list=['VCI', 'VCB', 'ACB'])
```

## 📚 Phương Thức Chính

### 1. price_board() - Bảng Giá Real-Time

Lấy thông tin bảng giá của các mã chứng khoán theo thời gian thực.

**Parameters:**

**KBS:**
```
- symbols_list (List[str]): Danh sách mã chứng khoán
- exchange (str): Sàn giao dịch ('HOSE', 'HNX', 'UPCOM') - Mặc định 'HOSE'
- show_log (bool): Hiển thị log debug
- get_all (bool): Lấy tất cả columns - Mặc định False
```

**VCI:**
```
- symbols_list (List[str]): Danh sách mã chứng khoán
- show_log (bool): Hiển thị log debug
```

**Ví dụ:**

**Với KBS (khuyến nghị):**
```python
# Khởi tạo với KBS
trading = Trading(source="KBS", symbol="VCI")

# Lấy bảng giá (standard columns)
board = trading.price_board(symbols_list=['VCI', 'VCB', 'ACB'])
print(f"Shape: {board.shape}")  # (3, 28)
print(f"Columns: {list(board.columns)}")
print(f"Dtypes:\n{board.dtypes}")
# Output:
# Shape: (3, 28)
# Columns: ['symbol', 'exchange', 'ceiling_price', 'floor_price', 'reference_price',
#           'open_price', 'high_price', 'low_price', 'close_price', 'average_price',
#           'total_trades', 'total_value', 'price_change', 'percent_change',
#           'bid_price_1', 'bid_vol_1', 'bid_price_2', 'bid_vol_2', 'bid_price_3', 'bid_vol_3',
#           'ask_price_1', 'ask_vol_1', 'ask_price_2', 'ask_vol_2', 'ask_price_3', 'ask_vol_3',
#           'foreign_buy_volume', 'foreign_sell_volume']
# Dtypes:
# symbol                  object
# exchange                object
# ceiling_price            int64
# floor_price              int64
# reference_price          int64
# ...
print(board[['symbol', 'exchange', 'reference_price', 'price_change', 'percent_change']].head())
```

**Output với KBS:**
```
  symbol exchange  reference_price  price_change  percent_change
0    VCI     HOSE            34850           -50         -0.1435
1    VCB     HOSE            84500          100          0.1183
2    ACB     HOSE            23450           -50         -0.2128
```

**Với VCI (nguồn chi tiết):**
```python
# Khởi tạo với VCI
trading = Trading(source="VCI", symbol="VCI")

# Lấy bảng giá (flat columns)
board = trading.price_board(symbols_list=['VCI', 'VCB', 'ACB'])
print(f"Shape: {board.shape}")  # (3, 77)
print(f"Columns sample: {list(board.columns)[:10]}...")  # Flat columns
print(f"Dtypes sample:\n{board.dtypes.head(10)}")
# Output:
# Shape: (3, 77)
# Columns sample: ['symbol', 'ceiling', 'floor', 'ref_price', 'stock_type', 'exchange',
#                'trading_status', 'trading_status_code', 'transaction_time', 'bid_count', ...]
# Dtypes sample:
# symbol                  object
# ceiling                 int64
# floor                   int64
# ref_price               int64
# stock_type              object
# exchange                object
# ...

# Truy cập columns dễ dàng
print(board[['symbol', 'ref_price', 'match_price', 'total_volume']])
```

**Output với VCI:**
```
  symbol  ref_price  match_price  total_volume
0    VCI      34850        34700      11768600
1    VCB      84500        84600       2923100
2    ACB      23450        23350      12219800
```

## 🎯 So Sánh Dữ Liệu Chi Tiết

### price_board() Structure Comparison

| Feature | KBS | VCI | Ưu Điểm |
|---------|-----|-----|---------|
| **Columns** | 28 | 77 | VCI cực kỳ chi tiết |
| **Structure** | Flat columns | Flat columns | Cả hai đều dễ xử lý |
| **Price Data** | OHLC, change | Full market depth | VCI đầy đủ hơn |
| **Bid/Ask** | 3 levels | 3 levels | Cả hai đều có |
| **Foreign Trading** | Buy/Sell volume | Buy/Sell value | VCI có thêm value |
| **Processing** | Simple | Simple | Cả hai đều đơn giản |

### Khi Nào Dùng Nguồn Nào?

**Dùng KBS khi:**
- Cần dữ liệu nhanh và ổn định
- Chỉ cần thông tin cơ bản (giá, KL, thay đổi)
- Xử lý data đơn giản với flat columns
- Muốn data gọn gàng, dễ sử dụng

**Dùng VCI khi:**
- Cần phân tích sâu thị trường
- Cần market detail đầy đủ (77 columns)
- Cần foreign trading value
- Muốn data chi tiết với flat columns (dễ xử lý)

## 💡 Mẹo Sử Dụng

### 1. Truy Cập Columns Dễ Dàng

```python
# Cả KBS và VCI đều có flat columns
trading_kbs = Trading(source="KBS", symbol="VCI")
trading_vci = Trading(source="VCI", symbol="VCI")

# KBS - 28 columns
board_kbs = trading_kbs.price_board(symbols_list=['VCI', 'VCB'])
print(board_kbs[['symbol', 'reference_price', 'price_change']])

# VCI - 77 columns
board_vci = trading_vci.price_board(symbols_list=['VCI', 'VCB'])
print(board_vci[['symbol', 'ref_price', 'match_price', 'total_volume']])

# Cả hai đều dễ truy cập
print(f"KBS columns: {len(board_kbs.columns)}")
print(f"VCI columns: {len(board_vci.columns)}")
```

### 2. Lọc và Phân Tích Dữ Liệu

```python
# KBS - Lọc dữ liệu theo điều kiện
trading = Trading(source="KBS", symbol="VCI")
board = trading.price_board(symbols_list=['VCI', 'VCB', 'ACB', 'BID', 'CTG'])

# Lọc các cổ phiếu tăng giá
risers = board[board['price_change'] > 0]
print("Cổ phiếu tăng giá:")
print(risers[['symbol', 'reference_price', 'price_change', 'percent_change']])

# Lọc theo khối lượng giao dịch
high_volume = board[board['total_trades'] > 1000]
print("\nCổ phiếu giao dịch sôi động:")
print(high_volume[['symbol', 'total_trades', 'total_value']])

# Tính toán thống kê
avg_change = board['percent_change'].mean()
total_value = board['total_value'].sum()
print(f"\nTrung bình thay đổi: {avg_change:.2f}%")
print(f"Tổng giá trị giao dịch: {total_value:,.0f}")
```

### 3. Real-time Monitoring

```python
import time
from vnstock import Trading

def monitor_price(symbols, interval=30):
    """Monitor price changes in real-time"""
    trading = Trading(source="KBS", symbol=symbols[0])
    
    while True:
        try:
            board = trading.price_board(symbols_list=symbols)
            
            # Hiển thị thông tin chính
            for _, row in board.iterrows():
                change_emoji = "📈" if row['price_change'] > 0 else "📉" if row['price_change'] < 0 else "➡️"
                print(f"{change_emoji} {row['symbol']}: {row['reference_price']} "
                      f"({row['price_change']:+,} {row['percent_change']:+.2f}%)")
            
            print("-" * 50)
            time.sleep(interval)
            
        except KeyboardInterrupt:
            print("\nStopped monitoring.")
            break
        except Exception as e:
            print(f"Error: {e}")
            time.sleep(5)

# Monitor VN30 stocks
vn30_stocks = ['VCI', 'VCB', 'ACB', 'BID', 'CTG', 'HDB', 'MBB', 'SSB', 'STB', 'TCB', 'TPB', 'VIB']
monitor_price(vn30_stocks, interval=30)
```

### 4. Export và Analysis

```python
# Export data cho analysis
trading = Trading(source="KBS", symbol="VCI")

# Lấy dữ liệu và export
board = trading.price_board(symbols_list=['VCI', 'VCB', 'ACB'])
board.to_csv('price_board.csv', index=False)

# Analysis với pandas
import pandas as pd

# Đọc lại data
df = pd.read_csv('price_board.csv')

# Phân tích theo sàn
exchange_stats = df.groupby('exchange').agg({
    'total_value': 'sum',
    'total_trades': 'sum',
    'symbol': 'count'
}).rename(columns={'symbol': 'stock_count'})
print("Thống kê theo sàn:")
print(exchange_stats)

# Phân tích theo mức thay đổi
df['change_category'] = pd.cut(df['percent_change'], 
                              bins=[-10, -2, 0, 2, 10], 
                              labels=['Giảm mạnh', 'Giảm nhẹ', 'Đứng giá', 'Tăng'])
change_dist = df['change_category'].value_counts()
print("\nPhân bổ thay đổi giá:")
print(change_dist)
```

## 🚨 Lưu Ý Quan Trọng

1. **Rate Limits**: Cả hai nguồn đều có rate limits, tránh request quá nhanh
2. **Market Hours**: Dữ liệu chỉ có trong giờ giao dịch (9:00-15:00)
3. **Data Freshness**: KBS thường nhanh hơn VCI
4. **Error Handling**: Luôn try-catch khi gọi API
5. **Memory**: VCI data lớn hơn, cẩn thận với memory usage

## 📚 Bước Tiếp Theo

1. [02-Installation](02-installation.md) - Cài đặt
2. [01-Overview](01-overview.md) - Tổng quan
3. [03-Listing API](03-listing-api.md) - Danh sách mã
4. [04-Company API](04-company-api.md) - Thông tin công ty
5. ✅ **05-Trading API** - Bạn đã ở đây
6. [06-Quote & Price](06-quote-price-api.md) - Giá lịch sử
7. [07-Financial API](07-financial-api.md) - Dữ liệu tài chính

---

**Last Updated**: 2024-12-17  
**Version**: 3.4.0  
**Status**: Actively Maintained


path: references/vnstock/06-quote-price-api.md
content: # 06 - Quote & Price API - Giá Lịch Sử & Dữ Liệu Real-time

## 📖 Giới Thiệu

Quote API cung cấp các phương thức lấy dữ liệu giá chứng khoán, bao gồm:

- **Giá lịch sử (Historical Data)**: OHLCV data từ các khoảng thời gian khác nhau
- **Dữ liệu trong ngày (Intraday)**: Giá khớp lệnh thực tế theo từng tick
- **Bảng giá realtime (Price Board)**: Giá mua/bán, khối lượng hiện tại
- **Dư mua - Dư bán (Price Depth)**: Order book, mức bid/ask

### 🚀 Quick Start

```python
from vnstock import Quote

# Khởi tạo với VCI (khuyến nghị)
quote = Quote(symbol="VCI")

# Lấy giá lịch sử - đơn giản với length
df = quote.history(length="1M", interval="1D")  # 1 tháng gần nhất
print(df[['time', 'open', 'high', 'low', 'close', 'volume']].head())

# Lấy 100 ngày gần nhất
df_100d = quote.history(length=100, interval="1D")
print(f"100 ngày: {len(df_100d)} bars")

# Lấy 50 nến (bars) gần nhất  
df_50b = quote.history(length="50b", interval="1D")
print(f"50 nến: {len(df_50b)} bars")

# Lấy dữ liệu intraday
intraday_df = quote.intraday(page_size=100)
print(intraday_df[['time', 'price', 'volume', 'match_type']].head())
```

## 🔌 So Sánh Nguồn Dữ Liệu

| Method            | KBS | VCI | Ghi Chú                          |
| ----------------- | --- | --- | -------------------------------- |
| **history()**     | ✅  | ✅  | Cả hai đều hỗ trợ OHLCV          |
| **intraday()**    | ✅  | ✅  | KBS có get_all, VCI có last_time |
| **price_depth()** | ❌  | ✅  | **VCI độc quyền**                |

**Tổng số methods:**

- **KBS**: 2 methods
- **VCI**: 3 methods

**Khuyến nghị:**

- **KBS**: Dữ liệu ổn định, có thêm cột value với get_all
- **VCI**: Đầy đủ features, có price depth, linh hoạt hơn

## 🏗️ Khởi Tạo

```python
from vnstock import Quote

# Khởi tạo với VCI (khuyến nghị)
quote_vci = Quote(
    symbol="VCI",           # Mã chứng khoán (bắt buộc)
    random_agent=False,      # Sử dụng random user agent
    proxy_mode=None,         # Chế độ proxy: 'try', 'rotate', 'random', 'single'
    proxy_list=None          # Danh sách proxy URLs
)

# Khởi tạo với KBS
quote_kbs = Quote(
    symbol="VCI",           # Mã chứng khoán (bắt buộc)
    random_agent=False,      # Sử dụng random user agent
    proxy_mode=None,         # Chế độ proxy
    proxy_list=None          # Danh sách proxy URLs
)

# ⚠️ Lưu ý: Cần cung cấp symbol khi khởi tạo
# quote = Quote(symbol="")  # Sẽ gây lỗi nếu symbol rỗng
```

## 📊 Dữ Liệu OHLCV

OHLCV là viết tắt của:

- **O**pen (Giá mở): Giá đóng cửa hôm trước
- **H**igh (Giá cao): Giá cao nhất trong khoảng thời gian
- **L**ow (Giá thấp): Giá thấp nhất trong khoảng thời gian
- **C**lose (Giá đóng): Giá cuối cùng trong khoảng thời gian
- **V**olume (Khối lượng): Số lượng cổ phiếu giao dịch

```
Ví dụ:
         time   open   high    low  close     volume
0  2024-01-01  21.00  21.50  20.80  21.40   1234567
1  2024-01-02  21.40  21.80  21.10  21.50   2345678
2  2024-01-03  21.50  21.90  21.30  21.60   1567890
```

## 🔄 Khung Thời Gian Lấy Mẫu

### Khung thời gian hỗ trợ

Cả KBS và VCI đều hỗ trợ các interval sau với nhiều định dạng khác nhau:

| Standard | Alias | Human Readable | Mô Tả   | KBS | VCI |
| -------- | ----- | -------------- | ------- | --- | --- |
| `"1m"`   | `"m"` | `"minute"`     | 1 phút  | ✅  | ✅  |
| `"5m"`   | -     | -              | 5 phút  | ✅  | ✅  |
| `"15m"`  | -     | -              | 15 phút | ✅  | ✅  |
| `"30m"`  | -     | -              | 30 phút | ✅  | ✅  |
| `"1H"`   | `"h"` | `"hour"`       | 1 giờ   | ✅  | ✅  |
| `"1D"`   | `"d"` | `"day"`        | 1 ngày  | ✅  | ✅  |
| `"1W"`   | `"w"` | `"week"`       | 1 tuần  | ✅  | ✅  |
| `"1M"`   | `"M"` | `"month"`      | 1 tháng | ✅  | ✅  |

**Lưu ý quan trọng:**

- **Case-sensitive**: `"M"` cho tháng (viết hoa), `"m"` cho phút (viết thường)
- KBS và VCI đều dùng string format (không dùng TimeFrame enum)
- Interval phải chính xác như trong bảng trên

### 🎯 Alias Ngắn Gọn (Quick Reference)

Các alias này giúp viết code nhanh hơn và tương thích với các trading platform khác:

| Alias | Tương Đương | Mô Tả   |
| ----- | ----------- | ------- |
| `"m"` | `"1m"`      | 1 phút  |
| `"h"` | `"1H"`      | 1 giờ   |
| `"d"` | `"1D"`      | 1 ngày  |
| `"w"` | `"1W"`      | 1 tuần  |
| `"M"` | `"1M"`      | 1 tháng |

**Ví dụ sử dụng:**

```python
# Các cách viết đều tương đương
quote.history(interval="1D")  # Standard format
quote.history(interval="d")   # Alias ngắn gọn
quote.history(interval="day") # Human readable

# Chú ý case sensitivity cho tháng/phút
quote.history(interval="M")   # ✅ Tháng (viết hoa)
quote.history(interval="m")   # ✅ Phút (viết thường)
```

## 📚 Phương Thức Chính

### 1. history() - Lịch Sử Giá OHLCV

Lấy dữ liệu giá lịch sử theo các khoảng thời gian khác nhau.

**Parameters:**

**Cả KBS và VCI:**

```
- start (str): Ngày bắt đầu (YYYY-MM-DD). Bắt buộc nếu không có length/count_back
- end (str): Ngày kết thúc (YYYY-MM-DD). Mặc định None (hiện tại)
- interval (str): Khung thời gian. Mặc định "1D"
- length (str/int): Khoảng thời gian lùi lại từ mốc thời gian hiện tại hoặc số nến
```

**KBS thêm:**

```
- get_all (bool): Lấy tất cả các cột. Mặc định False
```

### 🎯 Lookback với Parameter `length`

Parameter `length` hỗ trợ nhiều định dạng linh hoạt:

**1. Standard Periods (Chu kỳ tiêu chuẩn):**

```
'1W'  = 7 ngày    (1 tuần)
'2W'  = 14 ngày   (2 tuần)
'3W'  = 21 ngày   (3 tuần)
'1M'  = 30 ngày   (1 tháng)
'6W'  = 45 ngày   (6 tuần)
'2M'  = 60 ngày   (2 tháng)
'3M'  = 90 ngày   (1 quý)
'4M'  = 120 ngày  (4 tháng)
'5M'  = 150 ngày  (5 tháng)
'6M'  = 180 ngày  (6 tháng)
'9M'  = 270 ngày  (3 quý)
'1Y'  = 365 ngày  (1 năm)
'18M' = 540 ngày  (1.5 năm)
'2Y'  = 730 ngày  (2 năm)
'3Y'  = 1095 ngày (3 năm)
'5Y'  = 1825 ngày (5 năm)
```

**2. Custom Periods (Tùy chỉnh):**

```
'10D' = 10 ngày
'3W'  = 21 ngày (3 × 7)
'6M'  = 180 ngày (6 × 30)
'2Y'  = 730 ngày (2 × 365)
'1Q'  = 90 ngày (1 quý)
```

**3. Number of Days (Số ngày cụ thể):**

```
150      = 150 ngày
'150'    = 150 ngày
```

**4. Number of Bars (Số nến):**

```
'100b'   = 100 nến (bars)
'50b'    = 50 nến
'200b'   = 200 nến
```

**Ví dụ:**

**Với KBS:**

```python
from vnstock import Quote

quote = Quote(symbol="VCI")

# 1. Standard periods - Chu kỳ tiêu chuẩn
df_1m = quote.history(length="1M", interval="1D")      # 1 tháng gần nhất
df_3m = quote.history(length="3M", interval="1D")      # 3 tháng (1 quý)
df_6m = quote.history(length="6M", interval="1D")      # 6 tháng
df_1y = quote.history(length="1Y", interval="1D")      # 1 năm
df_2y = quote.history(length="2Y", interval="1D")      # 2 năm

# 2. Custom periods - Tùy chỉnh
df_10d = quote.history(length="10D", interval="1D")     # 10 ngày
df_3w = quote.history(length="3W", interval="1D")       # 3 tuần
df_1q = quote.history(length="1Q", interval="1D")       # 1 quý

# 3. Number of days/bars - Số ngày/nến cụ thể
df_150 = quote.history(length=150, interval="1D")       # 150 ngày
df_100b = quote.history(length="100b", interval="1D")   # 100 nến

# 4. Kết hợp với end date
df_ytd = quote.history(end="2024-12-31", length="1Y", interval="1D")  # Cả năm 2024

# 5. Lấy tất cả các cột (bao gồm value)
df_all = quote.history(length="1M", interval="1D", get_all=True)
print(f"Shape: {df_all.shape}")
print(f"Columns: {list(df_all.columns)}")
# Output:
# Shape: (22, 7)
# Columns: ['time', 'open', 'high', 'low', 'close', 'volume', 'value']

print(df_all[['time', 'open', 'high', 'low', 'close', 'volume', 'value']].head())
```

**Với VCI:**

```python
quote = Quote(symbol="VCI")

# 1. Standard periods
df_1m = quote.history(length="1M", interval="1D")      # 1 tháng
df_3m = quote.history(length="3M", interval="1D")      # 3 tháng
df_6m = quote.history(length="6M", interval="1D")      # 6 tháng

# 2. Custom periods với các interval khác nhau
df_1w_hourly = quote.history(length="1W", interval="1H")   # 1 tuần data hourly
df_2w_15m = quote.history(length="2W", interval="15m")   # 2 tuần data 15 phút

# 3. Number of bars
df_100b = quote.history(length="100b", interval="1D")   # 100 nến ngày
df_200b_hourly = quote.history(length="200b", interval="1H")  # 200 nến giờ

# 4. Kết hợp với count_back
df_combined = quote.history(length="3M", interval="1D", count_back=50)  # 3 tháng nhưng chỉ 50 nến cuối

print(f"Shape: {df_combined.shape}")
print(f"Columns: {list(df_combined.columns)}")
# Output:
# Shape: (50, 6)
# Columns: ['time', 'open', 'high', 'low', 'close', 'volume']

print(df_combined[['time', 'open', 'high', 'low', 'close', 'volume']].head())
```

### 2. intraday() - Dữ Liệu Khớp Lệnh Trong Ngày

Lấy dữ liệu khớp lệnh thực tế theo từng tick trong ngày.

**Parameters:**

**KBS:**

```
- page_size (int): Số bản ghi mỗi trang. Mặc định 100
- page (int): Trang dữ liệu. Mặc định 1. Không cần thay đổi tham số này, chỉ thay đổi page_size để đảm bảo lấy về dữ liệu từ 9:15 đến 14:45.
- get_all (bool): Lấy tất cả các cột có sẵn trong API thay vì áp dụng chuẩn hoá. Mặc định False
```

**VCI:**

```
- page_size (int): Số bản ghi mỗi trang. Mặc định 100
- last_time (str/int/float): Thời gian cắt dữ liệu
- last_time_format (str): Định dạng của last_time
```

**Ví dụ:**

**Với KBS:**

```python
quote = Quote(symbol="VCI")

# Lấy 100 bản ghi khớp lệnh (cột chuẩn hóa)
df = quote.intraday(page_size=100)
print(f"Shape: {df.shape}")
print(f"Columns: {list(df.columns)}")
# Output:
# Shape: (100, 5)
# Columns: ['time', 'price', 'volume', 'match_type', 'id']
print(df[['time', 'price', 'volume', 'match_type']].head())
```

**Output với KBS:**

```
                 time   price  volume match_type
0  2024-12-17 14:27:23  34700     150       sell
1  2024-12-17 14:27:22  34700     200        buy
2  2024-12-17 14:27:21  34700     100       sell
3  2024-12-17 14:27:20  34700     300        buy
4  2024-12-17 14:27:19  34700     150       sell
```

**Với VCI:**

```python
quote = Quote(symbol="VCI")

# Lấy dữ liệu intraday
df = quote.intraday(page_size=50)
print(f"Shape: {df.shape}")
print(f"Columns: {list(df.columns)}")
# Output:
# Shape: (50, 5)
# Columns: ['time', 'price', 'volume', 'match_type', 'id']
print(df[['time', 'price', 'volume', 'match_type']].head())
```

**Output với VCI:**

```
                 time   price  volume match_type
0  2024-12-17 14:27:23  34700     150       sell
1  2024-12-17 14:27:22  34700     200        buy
2  2024-12-17 14:27:21  34700     100       sell
3  2024-12-17 14:27:20  34700     300        buy
4  2024-12-17 14:27:19  34700     150       sell
```

### 3. price_depth() - Thống kê Dư Mua, Dư Bán theo bước giá và khối lượng (Chỉ VCI)

Thống kê khối lượng khối lệnh theo từng mức giá.

**Parameters:**

```
- (Không có parameter bắt buộc)
```

**Ví dụ:**

```python
quote = Quote(symbol="VCI")

# Lấy dữ liệu độ sâu giá
df = quote.price_depth()
print(f"Shape: {df.shape}")
print(f"Columns: {list(df.columns)}")
# Output:
# Shape: (50, 4)
# Columns: ['price', 'volume', 'buy_volume', 'sell_volume']
print(df[['price', 'volume', 'buy_volume', 'sell_volume']].head())
```

**Output với VCI:**

```
   price  volume  buy_volume  sell_volume
0  34700    1500        800         700
1  34650    2200       1200        1000
2  34600    1800        900         900
3  34550    2500       1500        1000
4  34500    3000       2000        1000
```

## 🚨 Lưu Ý Quan Trọng

1. **Rate Limits**: Cả hai nguồn đều có rate limits, tránh request quá nhanh
2. **Market Hours**: Dữ liệu intraday có trong ngày giao dịch đến trước 7:00 ngày tiếp theo
3. **Error Handling**: Luôn try-catch khi gọi API
4. **Memory Usage**: Intraday data có thể rất lớn, cẩn thận với page_size
5. **Time Zone**: Tất cả dữ liệu đều ở timezone Việt Nam (UTC+7)

## 🔗 Xem Thêm

- **[03-Listing API](03-listing-api.md)** - Tìm kiếm chứng khoán
- **[04-Company API](04-company-api.md)** - Thông tin công ty
- **[05-Trading API](05-trading-api.md)** - Bảng giá giao dịch
- **[08-Best Practices](08-best-practices.md)** - Mẹo tối ưu hóa

---

**Last Updated**: 2024-12-17  
**Version**: 3.4.0  
**Status**: Actively Maintained

path: references/vnstock/07-financial-api.md
content: # 07 - Financial API - Dữ Liệu Tài Chính

## 📖 Giới Thiệu

Financial API cung cấp các phương thức lấy dữ liệu tài chính doanh nghiệp, bao gồm:

- **Báo cáo tài chính**: Bảng cân đối kế toán, Khoản lợi nhập, Lưu chuyển tiền tệ
- **Chỉ số tài chính**: ROE, ROA, EPS, P/E, Debt ratio, v.v.
- **Chu kỳ báo cáo**: Hàng quý (Quarter) hoặc hàng năm (Year)
- **Phân tích**: Xu hướng tài chính, so sánh ngành

## 🔌 So Sánh Nguồn Dữ Liệu

| Method                 | KBS | VCI | Ghi Chú                         |
| ---------------------- | --- | --- | ------------------------------- |
| **income_statement()** | ✅  | ✅  | KBS: 90 items, VCI: 25+ columns |
| **balance_sheet()**    | ✅  | ✅  | KBS: 162 items, VCI: 36 columns |
| **cash_flow()**        | ✅  | ✅  | KBS: 159 items, VCI: 39 columns |
| **ratio()**            | ✅  | ✅  | KBS: 27 ratios, VCI: 37+ ratios |

**Tổng số methods:**

- **KBS**: 4 methods
- **VCI**: 4 methods

**Khuyến nghị:**

- **KBS**: Dữ liệu chi tiết theo dòng (item-based), phù hợp phân tích chuyên sâu
- **VCI**: Dữ liệu theo cột (column-based), dễ sử dụng và tích hợp

## 🏗️ Khởi Tạo

### KBS Finance (Khuyến nghị)

```python
from vnstock import Finance

# Khởi tạo với KBS
finance_kbs = Finance(
    source="kbs",           # Nguồn dữ liệu
    symbol="VCI",            # Mã chứng khoán
    standardize_columns=True,  # Chuẩn hóa tên cột
    random_agent=False      # Sử dụng random user agent
)

# Khởi tạo với VCI
finance_vci = Finance(
    source="vci",            # Nguồn dữ liệu
    symbol="VCI",            # Mã chứng khoán
    period="quarter",        # Chu kỳ mặc định
    get_all=True,            # Lấy tất cả các trường
    show_log=False           # Hiển thị log
)
```

**Các tham số:**

- `symbol` (str): Mã chứng khoán (VD: 'VCI', 'ACB')
- `standardize_columns` (bool): Chuẩn hóa tên cột theo schema. Mặc định: True
- `random_agent` (bool): Sử dụng random user agent. Mặc định: False
- `show_log` (bool): Hiển thị log debug. Mặc định: False
- `proxy_mode` (str): Chế độ proxy. Mặc định: None
    - `'try'`: Thử proxy, nếu fail thì dùng direct
    - `'rotate'`: Xoay vòng proxy trong danh sách
    - `'random'`: Chọn proxy ngẫu nhiên
    - `'single'`: Dùng proxy đầu tiên
- `proxy_list` (list): Danh sách URL proxy. Mặc định: None

## 📊 Cấu Trúc Dữ Liệu So Sánh

### KBS Data Structure

**Format:** Item-based (dòng-based)

- **Shape**: (N items, 10 columns)
- **Index**: Không có index name
- **Columns tiêu chuẩn**:

  ```
  ['item', 'item_en', 'item_id', 'unit', 'levels', 'row_number', 
   '2025-Q3', '2025-Q2', '2025-Q1', '2024-Q4']
  ```
- **Đặc điểm**:
    - Mỗi dòng là một chỉ tiêu tài chính
    - Các cột thời gian là các quý/ năm
    - Có cả tên tiếng Việt và tiếng Anh
    - Có hierarchical levels

### VCI Data Structure

**Format:** Column-based (cột-based)

- **Shape**: (51 periods, N columns)
- **Index**: Không có index name
- **Columns tiêu chuẩn**:

  ```
  ['ticker', 'yearReport', 'lengthReport', ...financial_fields...]
  ```
- **Đặc điểm**:
    - Mỗi dòng là một kỳ báo cáo
    - Các cột là các chỉ tiêu tài chính
    - Tên cột theo tiếng Anh có đơn vị
    - MultiIndex cho ratios

## 📚 Phương Thức Chính

### Field Display Mode

Từ phiên bản v3.4.0+, tất cả các phương thức báo cáo tài chính hỗ trợ `display_mode` parameter để kiểm soát cách hiển thị các trường dữ liệu:

| Mode                    | Tên             | Mô Tả                                        | Columns                         |
| ----------------------- | --------------- | -------------------------------------------- | ------------------------------- |
| `FieldDisplayMode.STD`  | Standardized    | Chỉ hiển thị 'item' và 'item_id' (chuẩn hóa) | item, item_id, periods          |
| `FieldDisplayMode.ALL`  | All Fields      | Hiển thị tất cả: item (VN), item_en, item_id | item, item_en, item_id, periods |
| `FieldDisplayMode.AUTO` | Auto Convert    | Tự động chuyển đổi dựa trên loại dữ liệu     | item, item_en, item_id, periods |
| `'vi'`                  | Vietnamese Only | Chỉ tiếng Việt (backward compatible)         | item, item_id, periods          |
| `'en'`                  | English Only    | Chỉ tiếng Anh (backward compatible)          | item_en, item_id, periods       |

**Ví dụ sử dụng display_mode:**

```python
from vnstock import Finance
from vnstock.explorer.kbs.financial import FieldDisplayMode

finance = Finance(symbol="VCI")

# Mode 1: Standardized (mặc định) - Chỉ item tiếng Việt + item_id
df_std = finance.income_statement(
    period="quarter",
    display_mode=FieldDisplayMode.STD
)
print(df_std.columns)
# ['item', 'item_id', 'unit', 'levels', 'row_number', '2025-Q3', '2025-Q2', ...]

# Mode 2: All Fields - Hiển thị cả item (VN), item_en, item_id
df_all = finance.income_statement(
    period="quarter",
    display_mode=FieldDisplayMode.ALL
)
print(df_all.columns)
# ['item', 'item_en', 'item_id', 'unit', 'levels', 'row_number', '2025-Q3', '2025-Q2', ...]

# Mode 3: Auto Convert - Tự động chuyển đổi
df_auto = finance.income_statement(
    period="quarter",
    display_mode=FieldDisplayMode.AUTO
)

# Mode 4: chỉ Tiếng Việt chỉ (backward compatible)
df_vi = finance.income_statement(
    period="quarter",
    display_mode='vi'
)

# Mode 5: chỉ Tiếng Anh (backward compatible)
df_en = finance.income_statement(
    period="quarter",
    display_mode='en'
)
```

### 1. income_statement() - Báo Cáo Kết Quả Kinh Doanh

Lấy dữ liệu báo cáo kết quả hoạt động kinh doanh.

**KBS:**

```python
finance = Finance(source="kbs", symbol="VCI")
df = finance.income_statement(period="quarter")

print(f"Shape: {df.shape}")  # (90, 10)
print(f"Columns: {list(df.columns)}")
# ['item', 'item_id', 'unit', 'levels', 'row_number', 
#  '2025-Q3', '2025-Q2', '2025-Q1', '2024-Q4']

# Lấy dữ liệu năm
df_year = finance.income_statement(period="year")
print(f"Shape: {df_year.shape}")  # (90, years_available)

# Lấy với tất cả các trường (item VN, item_en, item_id)
df_all = finance.income_statement(
    period="quarter",
    display_mode="all"  # hoặc FieldDisplayMode.ALL
)

# Xem các chỉ tiêu chính
print(df[df['levels'] == 1][['item', 'item_id', '2025-Q3']].head())
```

**Output KBS:**

```
Shape: (90, 10)
     item item_id  2025-Q3
0  Doanh thu      revenue    1200.5
1  Lợi nhuận gộp  gross_profit   450.2
2  Lợi nhuận hoạt động  operating_profit   180.3
3  Lợi nhuận trước thuế  profit_before_tax   165.1
4  Lợi nhuận sau thuế  net_profit   132.4
```

### 2. balance_sheet() - Bảng Cân Đối Kế Toán

Lấy dữ liệu bảng cân đối kế toán.

**KBS:**

```python
from vnstock import Finance

finance = Finance(symbol="VCI")

# Lấy dữ liệu quý
df = finance.balance_sheet(period="quarter")
print(f"Shape: {df.shape}")  # (162, 10)

# Lấy các chỉ tiêu quan trọng
key_items = ['Tổng tài sản', 'Tài sản ngắn hạn', 'Vốn chủ sở hữu', 'Nợ phải trả']
print(df[df['item'].isin(key_items)][['item', 'item_id', '2025-Q3']])

# Lấy dữ liệu năm
df_year = finance.balance_sheet(period="year")
```

**KBS Output:**

```
                item        item_id  2025-Q3
0         Tổng tài sản      total_assets   50000
1      Tài sản ngắn hạn  current_assets   25000
2      Vốn chủ sở hữu    owner_equity   15000
3        Nợ phải trả        liabilities   35000
```

### 3. cash_flow() - Báo Cáo Lưu Chuyển Tiền Tệ

Lấy dữ liệu báo cáo lưu chuyển tiền tệ.

**KBS:**

```python
from vnstock import Finance

finance = Finance(symbol="VCI")

# Lấy dữ liệu quý
df = finance.cash_flow(period="quarter")
print(f"Shape: {df.shape}")  # (159, 10)

# Các dòng tiền quan trọng
cash_items = ['Lưu chuyển tiền từ hoạt động', 'Lưu chuyển tiền từ đầu tư', 
              'Lưu chuyển tiền từ tài chính', 'Thay đổi tiền mặt']
print(df[df['item'].isin(cash_items)][['item', 'item_id', '2025-Q3']])

# Lấy dữ liệu năm
df_year = finance.cash_flow(period="year")
```

**KBS Output:**

```
                  item          item_id  2025-Q3
0  Lưu chuyển từ hoạt động  cash_from_operations   8000
1  Lưu chuyển từ đầu tư    cash_from_investing   -2000
2  Lưu chuyển từ tài chính   cash_from_financing   1000
3  Thay đổi tiền mặt       net_cash_change       7000
```

### 4. ratio() - Chỉ Số Tài Chính

Lấy các chỉ số tài chính quan trọng.

**KBS:**

```python
from vnstock import Finance

finance = Finance(symbol="VCI")

# Lấy dữ liệu quý
df = finance.ratio(period="quarter")
print(f"Shape: {df.shape}")  # (27, 10)

# Các chỉ số quan trọng
ratio_items = ['PE', 'PB', 'ROE', 'ROA', 'Beta']
print(df[df['item'].isin(ratio_items)][['item', 'item_id', '2025-Q3']])

# Lấy dữ liệu năm
df_year = finance.ratio(period="year")
```

**KBS Output:**

```
Shape: (27, 10)
    item item_id  2025-Q3
0    PE     pe      12.5
1    PB     pb       1.8
2   ROE    roe      15.2
3   ROA    roa       8.7
4  Beta   beta       1.2
```

## 🎯 So Sánh Chi Tiết

### Data Format Comparison (v3.4.0+)

**Thay đổi chính từ phiên bản trước:**

- ✅ Giới thiệu FieldDisplayMode cho kiểm soát hiển thị trường linh hoạt
- ✅ Advanced field handling với Field ID generation tự động
- ✅ Hỗ trợ chuẩn hóa schema (schema standardization)
- ✅ Xử lý va chạm field ID với auto-resolution
- ✅ Proxy configuration trong khởi tạo
- ✅ Improved language support (Vi/En/Both)

| Feature                    | v3.3.x | v3.4.0+                       | Ưu Điểm                      |
| -------------------------- | ------ | ----------------------------- | ---------------------------- |
| **Field Display Mode**     | Không  | ✅ STD/ALL/AUTO               | Kiểm soát hiển thị linh hoạt |
| **Item ID Generation**     | Manual | ✅ Auto + collision detection | Tự động hóa & nhất quán      |
| **Proxy Support**          | Không  | ✅ Có (try/rotate/random)     | Vượt IP blocking             |
| **Schema Standardization** | Cơ bản | ✅ Advanced                   | Tối ưu hóa tên cột           |
| **Language Support**       | Vi/En  | ✅ Vi/En/Both flexible        | Linh hoạt hơn                |

### Field ID Generation & Collision Detection

**Tính năng mới:** Field ID tự động tạo từ item_en hoặc item (Việt) với xử lý va chạm tự động

```python
from vnstock import Finance

finance = Finance(symbol="VCI", show_log=True)

# Các item có tên tương tự sẽ tự động xử lý collision
df = finance.income_statement(
    period="quarter",
    display_mode="all"  # Hiển thị item_en để thấy ID generation
)

# Xem item_id được tạo (tự động từ item_en)
print(df[['item', 'item_en', 'item_id']].head(10))

# Output ví dụ:
# item                item_en              item_id
# Doanh thu           Revenue              revenue
# Giá vốn hàng bán   Cost of Sales        cost_of_sales
# Lợi nhuận gộp      Gross Profit         gross_profit
# ...
```

### KBS vs VCI (Chỉ tham khảo)

Từ v3.4.0, **KBS là data source mặc định** và được khuyến nghị:

| Feature          | KBS                        | VCI          | Khuyến nghị      |
| ---------------- | -------------------------- | ------------ | ---------------- |
| **Số items**     | 90 (income), 162 (balance) | 25+ columns  | KBS chi tiết hơn |
| **Language**     | Vi + En                    | En only      | KBS đa ngôn ngữ  |
| **Hierarchical** | Có (levels)                | Không        | KBS có cấu trúc  |
| **Format**       | Item-based (rows)          | Period-based | KBS linh hoạt    |
| **Field IDs**    | ✅ Auto generated          | -            | KBS chuẩn hóa    |

**Mapping từ KBS → Item IDs:**

```
Doanh thu → revenue
Lợi nhuận gộp → gross_profit
Lợi nhuận hoạt động → operating_profit
Lợi nhuận trước thuế → profit_before_tax
Lợi nhuận sau thuế → net_profit
Tổng tài sản → total_assets
Vốn chủ sở hữu → owner_equity
Nợ phải trả → liabilities
```

## 💡 Mẹo Sử Dụng

### 1. Proxy Support cho Cloud Environments

```python
from vnstock import Finance

# Tránh IP blocking trên Google Colab/Kaggle
finance = Finance(
    symbol="VCI",
    proxy_mode="rotate",  # Xoay vòng proxy
    proxy_list=[
        "http://proxy1.com:8080",
        "http://proxy2.com:8080",
        "http://proxy3.com:8080"
    ]
)

# Hoặc dùng single proxy
finance = Finance(
    symbol="VCI",
    proxy_mode="single",
    proxy_list=["http://proxy.com:8080"]
)

# Hoặc dùng try mode - tự động fallback nếu proxy fail
finance = Finance(
    symbol="VCI",
    proxy_mode="try",
    proxy_list=["http://proxy.com:8080"]
)

df = finance.income_statement(period="quarter")
```

### 2. Working with Field Display Modes

```python
from vnstock import Finance
from vnstock.explorer.kbs.financial import FieldDisplayMode

finance = Finance(symbol="VCI", show_log=False)

# 1. Standardized Mode (mặc định) - Tối ưu cho phân tích
df_std = finance.income_statement(period="quarter")
# Columns: ['item', 'item_id', 'unit', 'levels', 'row_number', 'periods...']

# 2. All Fields Mode - Cho nghiên cứu chi tiết
df_all = finance.income_statement(
    period="quarter",
    display_mode=FieldDisplayMode.ALL
)
# Columns: ['item', 'item_en', 'item_id', 'unit', 'levels', 'row_number', 'periods...']

# 3. Lọc theo mục đích sử dụng
# Để lấy chỉ tiêu chính (level 1)
key_items = df_std[df_std['levels'] == 1]

# Để lấy tất cả chi tiết (tất cả levels)
all_items = df_std[df_std['levels'] > 0]

# Để lọc theo item_id
revenue = df_std[df_std['item_id'] == 'revenue']
expenses = df_std[df_std['item_id'].str.contains('expense')]
```

### 3. Field ID Collision Handling

```python
from vnstock import Finance

finance = Finance(symbol="VCI", show_log=True)

# Các item có tên tương tự sẽ được xử lý tự động
df = finance.income_statement(
    period="quarter",
    display_mode="all"
)

# Kiểm tra item_id có bị collision không
from collections import Counter
id_counts = Counter(df['item_id'])
duplicates = {k: v for k, v in id_counts.items() if v > 1}

if duplicates:
    print("Các item_id bị collision:", duplicates)
    # Các duplicate item sẽ được thêm counter tự động
    # VD: 'revenue', 'revenue_1', 'revenue_2', ...
else:
    print("✅ Không có collision")
```

### 4. Schema Standardization

```python
from vnstock import Finance

# Khởi tạo với chuẩn hóa (mặc định)
finance_std = Finance(symbol="VCI", standardize_columns=True)
df_std = finance_std.income_statement(period="quarter")

# Khởi tạo không chuẩn hóa (giữ tên gốc)
finance_raw = Finance(symbol="VCI", standardize_columns=False)
df_raw = finance_raw.income_statement(period="quarter")

# So sánh:
print("Standardized columns:", df_std.columns.tolist())
print("Raw columns:", df_raw.columns.tolist())
```

### 5. Lấy các chỉ tiêu quan trọng

```python
from vnstock import Finance

finance = Finance(symbol="VCI")

# Lấy dữ liệu quý
df = finance.income_statement(period="quarter")

# 1. Lọc theo levels (mặc định level 1 = chỉ tiêu chính)
key_items = df[df['levels'] == 1]

# 2. Lọc theo item_id
important_ids = ['revenue', 'cost_of_sales', 'net_profit', 'operating_profit']
important_data = df[df['item_id'].isin(important_ids)]

# 3. Xem xu hướng (so sánh quý)
columns_to_show = ['item', 'item_id'] + [col for col in df.columns if 'Q' in col or col.isdigit()]
trend = key_items[columns_to_show]
print(trend)

# 4. Tính tăng trưởng quý trên quý (QoQ)
revenue_row = df[df['item_id'] == 'revenue'].iloc[0]
periods = [col for col in df.columns if 'Q' in col or col.isdigit()]
qoq_growth = {}
for i in range(1, len(periods)):
    prev = revenue_row[periods[i]]
    curr = revenue_row[periods[i-1]]
    if prev != 0:
        qoq_growth[periods[i]] = ((curr - prev) / prev) * 100

print(f"Revenue QoQ Growth: {qoq_growth}")
```

### 6. Kết hợp dữ liệu từ nhiều báo cáo

```python
from vnstock import Finance
import pandas as pd

finance = Finance(symbol="VCI")

# Lấy dữ liệu từ các báo cáo khác nhau
income = finance.income_statement(period="year")
balance = finance.balance_sheet(period="year")
cash = finance.cash_flow(period="year")
ratios = finance.ratio(period="year")

# Lọc các chỉ tiêu cần thiết
net_profit = income[income['item_id'] == 'net_profit'].iloc[0]
total_assets = balance[balance['item_id'] == 'total_assets'].iloc[0]
roa = ratios[ratios['item_id'] == 'roa'].iloc[0]

# Tạo bảng tổng hợp
summary_data = {
    'Net Profit': net_profit[['2025', '2024', '2023']].to_dict(),
    'Total Assets': total_assets[['2025', '2024', '2023']].to_dict(),
    'ROA': roa[['2025', '2024', '2023']].to_dict()
}

summary_df = pd.DataFrame(summary_data)
print(summary_df)
```

## 🚨 Lưu Ý Quan Trọng

### 1. Phiên bản v3.4.0+ (Khuyến nghị)

✅ **Các tính năng mới & cải tiến:**

- Field Display Mode (STD/ALL/AUTO) để kiểm soát hiển thị
- Auto Field ID generation với collision detection
- Proxy support (try/rotate/random) cho cloud environments
- Advanced schema standardization
- Improved language support

### 2. Data Validation & Handling

```python
from vnstock import Finance

finance = Finance(symbol="VCI")

# Luôn kiểm tra shape và columns
df = finance.income_statement(period="quarter")

if df is None or len(df) == 0:
    print("❌ Không có dữ liệu")
else:
    print(f"✅ Dữ liệu: {df.shape[0]} items, {df.shape[1]} columns")
    
# Kiểm tra missing data
missing = df.isna().sum()
if missing.sum() > 0:
    print("⚠️ Có missing data:", missing[missing > 0])

# Validate period columns
period_cols = [col for col in df.columns if 'Q' in col or col.isdigit()]
print(f"Periods: {period_cols}")
```

### 3. Field ID & Item ID

- **Tự động tạo từ:** `item_en` (tiếng Anh) hoặc `item` (tiếng Việt)
- **Format:** snake_case (VD: `revenue`, `gross_profit`)
- **Collision handling:** Tự động thêm counter nếu trùng (VD: `revenue_1`, `revenue_2`)
- **Không thay đổi:** Luôn nhất quán trong cùng một phiên

### 4. Period Format

```python
# KBS periods format
'2025-Q3', '2025-Q2', '2025-Q1', '2024-Q4'  # Quý
'2025', '2024', '2023'                      # Năm

# Lọc periods
periods = [col for col in df.columns if 'Q' in col or col.isdigit()]
```

### 5. Proxy Configuration

```python
# ✅ Recommended cho cloud environments
finance = Finance(
    symbol="VCI",
    proxy_mode="rotate",  # Tốt nhất cho high-traffic
    proxy_list=["proxy1", "proxy2", "proxy3"]
)

# hoặc
finance = Finance(
    symbol="VCI",
    proxy_mode="try",  # Fallback to direct nếu fail
    proxy_list=["proxy.com:8080"]
)
```

### 6. Performance Tips

```python
# 1. Lấy dữ liệu một lần, tái sử dụng
df = finance.income_statement(period="year")
revenue = df[df['item_id'] == 'revenue']
profit = df[df['item_id'] == 'net_profit']

# 2. Lọc trước khi xử lý
important = df[df['levels'] == 1]  # Chỉ level 1
result = important[['item_id', '2025', '2024']]

# 3. Cache dữ liệu nếu sử dụng nhiều lần
import pickle
with open('financial_cache.pkl', 'wb') as f:
    pickle.dump(df, f)
```

## ❌ Các Lỗi Thường Gặp & Cách Khắc Phục

### Lỗi 1: Invalid Symbol

```python
# ❌ Sai - Symbol không hợp lệ
finance = Finance(symbol="INVALID123")
df = finance.income_statement()
# ValueError: Mã CK không hợp lệ hoặc không phải cổ phiếu

# ✅ Đúng
finance = Finance(symbol="VCI")  # hoặc "ACB", "VNM", v.v.
```

### Lỗi 2: Invalid Period

```python
# ❌ Sai
df = finance.income_statement(period="monthly")

# ✅ Đúng - Chỉ hỗ trợ 'quarter' hoặc 'year'
df = finance.income_statement(period="quarter")
df = finance.income_statement(period="year")
```

### Lỗi 3: Không Có Dữ Liệu

```python
# ❌ Lỗi thường gặp
finance = Finance(symbol="UNKNOWN")
df = finance.balance_sheet(period="year")
# Kết quả: DataFrame rỗng

# ✅ Xử lý đúng
from vnstock import Finance

try:
    finance = Finance(symbol="VCI")
    df = finance.balance_sheet(period="year")
    
    if df is None or len(df) == 0:
        print("❌ Không có dữ liệu tài chính cho cổ phiếu này")
    else:
        print(f"✅ Lấy {len(df)} items")
        print(df.head())
        
except ValueError as e:
    print(f"❌ Lỗi: {e}")
```

### Lỗi 4: Display Mode Invalid

```python
# ❌ Sai
df = finance.income_statement(display_mode="invalid_mode")

# ✅ Đúng
from vnstock.explorer.kbs.financial import FieldDisplayMode

df = finance.income_statement(display_mode=FieldDisplayMode.STD)
# hoặc
df = finance.income_statement(display_mode=FieldDisplayMode.ALL)
# hoặc backward compatible
df = finance.income_statement(display_mode='vi')
df = finance.income_statement(display_mode='en')
```

### Lỗi 5: Proxy Configuration

```python
# ❌ Sai - Proxy list không đúng format
finance = Finance(
    symbol="VCI",
    proxy_list="http://proxy.com:8080"  # String instead of list
)

# ✅ Đúng
finance = Finance(
    symbol="VCI",
    proxy_mode="rotate",
    proxy_list=["http://proxy1.com:8080", "http://proxy2.com:8080"]
)

# ✅ Hoặc không dùng proxy
finance = Finance(symbol="VCI")  # Direct connection
```

### Lỗi 6: Accessing Non-existent Columns

```python
# ❌ Sai
df = finance.income_statement(period="quarter")
print(df['item_en'])  # KeyError nếu dùng display_mode=STD

# ✅ Đúng - Kiểm tra columns trước
if 'item_en' in df.columns:
    print(df['item_en'])
else:
    print("item_en không có trong columns")
    print(f"Available columns: {df.columns.tolist()}")

# ✅ Hoặc lấy với display_mode=ALL
df_all = finance.income_statement(period="quarter", display_mode="all")
print(df_all['item_en'])
```

### Lỗi 7: IP Blocking (Cloud Environments)

```python
# ❌ Lỗi thường gặp trên Colab/Kaggle
finance = Finance(symbol="VCI")
df = finance.income_statement()
# HTTPError: 403 Forbidden (IP blocked)

# ✅ Khắc phục với Proxy
finance = Finance(
    symbol="VCI",
    proxy_mode="rotate",
    proxy_list=[
        "http://proxy1.com:8080",
        "http://proxy2.com:8080",
        "http://proxy3.com:8080"
    ]
)
df = finance.income_statement()

# hoặc try mode (fallback to direct)
finance = Finance(
    symbol="VCI",
    proxy_mode="try",
    proxy_list=["http://proxy.com:8080"]
)
```

### Lỗi 8: Field ID Collision

```python
# Thường không cần xử lý, nhưng nếu muốn debug
from vnstock import Finance
from collections import Counter

finance = Finance(symbol="VCI", show_log=True)
df = finance.income_statement(period="quarter", display_mode="all")

# Kiểm tra collision
id_counts = Counter(df['item_id'])
duplicates = {k: v for k, v in id_counts.items() if v > 1}

if duplicates:
    print(f"⚠️ Collision detected: {duplicates}")
    # Hệ thống tự động xử lý bằng cách thêm counter
    # VD: revenue, revenue_1, revenue_2, ...
else:
    print("✅ Không có collision")
```

## 📚 Bước Tiếp Theo

1. [02-Installation](02-installation.md) - Cài đặt
2. [01-Overview](01-overview.md) - Tổng quan
3. [03-Listing API](03-listing-api.md) - Danh sách chứng khoán
4. [04-Company API](04-company-api.md) - Thông tin công ty
5. [05-Trading API](05-trading-api.md) - Dữ liệu giao dịch
6. [06-Quote & Price API](06-quote-price-api.md) - Giá lịch sử
7. ✅ **07-Financial API** - Bạn đã ở đây
8. [08-Fund API](08-fund-api.md) - Dữ liệu quỹ
9. [09-Screener API](09-screener-api.md) - Lọc cổ phiếu
10. [10-Connector Guide](10-connector-guide.md) - API bên ngoài
11. [11-Best Practices](11-best-practices.md) - Mẹo & kinh nghiệm
12. [12-Migration Guide](12-migration-guide.md) - Nâng cấp vnstock_data

## 📋 Tóm Tắt Thay Đổi Phiên Bản

### v3.4.0+ (Hiện tại)

✅ Field Display Mode (STD/ALL/AUTO)

✅ Auto Field ID generation + collision detection

✅ Proxy support (try/rotate/random)

✅ Advanced schema standardization

✅ KBS là default source

✅ Improved error handling

---

**Last Updated**: 2026-01-23  

**Version**: 3.4.0+  

**Status**: Actively Maintained  

**Maintained By**: Thịnh Vũ & Vnstock Team

path: references/vnstock/08-fund-api.md
content: # 07 - Fund API - Dữ Liệu Quỹ Đầu Tư

## 📖 Giới Thiệu

**Fund API** cung cấp thông tin chi tiết về các quỹ đầu tư mở từ Fmarket, bao gồm danh sách quỹ, hiệu suất, và giá trị tài sản ròng (NAV).

## 🔌 Nguồn Dữ Liệu

| Nguồn | Hỗ Trợ | Loại Dữ Liệu |
|-------|--------|-------------|
| **Fmarket** | ✅ | Quỹ đầu tư mở (58 quỹ) |

## 🚀 Bắt Đầu

```python
from vnstock import Fund

# Khởi tạo
fund = Fund()

# Xem danh sách quỹ
funds_list = fund.listing()
print(f"Tổng quỹ: {len(funds_list)}")
print(funds_list.head())
```

## 📚 Phương Thức Chính

### 1. listing() - Danh Sách Quỹ

Lấy danh sách tất cả các quỹ mở hiện có trên Fmarket.

**Tham số:**
- `fund_type` (str, tùy chọn): Loại quỹ
  - `""` - Tất cả quỹ (mặc định)
  - `"STOCK"` - Quỹ cổ phiếu
  - `"BOND"` - Quỹ trái phiếu
  - `"BALANCED"` - Quỹ cân bằng

**Trả về:** `pd.DataFrame` (58 dòng, 21 cột)

**Các cột chính:**
- `short_name` - Mã quỹ, ví dụ: "DCDS", "VFMVFQ" (str)
- `name` - Tên đầy đủ quỹ (str)
- `fund_type` - Loại quỹ (str)
- `fund_owner_name` - Công ty quản lý (str)
- `management_fee` - Phí quản lý hàng năm (float64, %)
- `inception_date` - Ngày thành lập (str, "YYYY-MM-DD")
- `nav` - Giá NAV hiện tại (float64, VND)
- `nav_update_at` - Ngày cập nhật NAV (str)

**Cột Lợi Suất (Return, %):**
- `nav_change_previous` - Thay đổi từ phiên trước (float64, %)
- `nav_change_last_year` - Lợi suất năm ngoái (float64, %)
- `nav_change_1m` - Lợi suất 1 tháng (float64, %)
- `nav_change_3m` - Lợi suất 3 tháng (float64, %)
- `nav_change_6m` - Lợi suất 6 tháng (float64, %)
- `nav_change_12m` - Lợi suất 1 năm (float64, %)
- `nav_change_24m` - Lợi suất 2 năm (float64, %)
- `nav_change_36m` - Lợi suất 3 năm (float64, %)
- `nav_change_36m_annualized` - Lợi suất 3 năm (annualized, %)
- `nav_change_inception` - Lợi suất từ khi thành lập (float64, %)

**Ví dụ:**
```python
from vnstock import Fund

fund = Fund()

# Tất cả quỹ
all_funds = fund.listing()
print(f"Tổng quỹ: {len(all_funds)}")  # 58

# Quỹ cổ phiếu
stock_funds = fund.listing(fund_type="STOCK")
print(f"Quỹ cổ phiếu: {len(stock_funds)}")

# Top 5 quỹ theo lợi suất 1 năm
top_5 = all_funds.nlargest(5, 'nav_change_12m')
print(top_5[['short_name', 'name', 'nav', 'nav_change_12m']])
```

**Output:**
```
  short_name                        name        nav  nav_change_12m
0       DCDS  QUỸ ĐẦU TƯ...      105760.69          33.25
1      SSISCA  QUỸ ĐẦU TƯ...       45770.78          15.17
2       BVFED  QUỸ ĐẦU TƯ...       31194.00          40.72
```

## 💡 Ví Dụ Thực Tế

### So Sánh Hiệu Suất Quỹ

```python
from vnstock import Fund

fund = Fund()

# Lấy quỹ cổ phiếu
stock_funds = fund.listing(fund_type="STOCK")

# So sánh lợi suất
comparison = stock_funds[[
    'short_name', 'name', 'nav', 
    'nav_change_12m', 'nav_change_36m', 
    'nav_change_36m_annualized'
]].copy()

# Sắp xếp theo lợi suất 1 năm
comparison_sorted = comparison.sort_values('nav_change_12m', ascending=False)
print("Top 10 quỹ cổ phiếu (lợi suất 1 năm):")
print(comparison_sorted.head(10).to_string())
```

### Phân Loại Quỹ

```python
from vnstock import Fund

fund = Fund()
all_funds = fund.listing()

# Đếm theo loại quỹ
fund_types = all_funds['fund_type'].value_counts()
print("Phân loại quỹ:")
print(fund_types)

# Trung bình phí quản lý theo loại
avg_fee = all_funds.groupby('fund_type')['management_fee'].mean()
print("\nPhí quản lý trung bình (%):")
print(avg_fee)
```

### Tìm Quỹ Phù Hợp

```python
from vnstock import Fund

fund = Fund()
all_funds = fund.listing()

# Tiêu chí: lợi suất 1 năm cao, phí quản lý thấp
filtered = all_funds[
    (all_funds['nav_change_12m'] > 20) &  # Lợi suất > 20%
    (all_funds['management_fee'] < 1.5)   # Phí < 1.5%
].copy()

filtered['score'] = (
    filtered['nav_change_12m'] - 
    filtered['management_fee'] * 5
)

top_picks = filtered.nlargest(10, 'score')
print("Top quỹ theo tiêu chí:")
print(top_picks[[
    'short_name', 'nav_change_12m', 'management_fee', 'score'
]])
```

## 📊 Loại Quỹ

### Quỹ Cổ Phiếu (Stock Funds)

- **Mục tiêu**: Tăng trưởng vốn
- **Danh mục**: 80-100% cổ phiếu
- **Rủi ro**: Cao
- **Horizon**: Dài hạn (3-5+ năm)

### Quỹ Trái Phiếu (Bond Funds)

- **Mục tiêu**: Sinh lợi tức ổn định
- **Danh mục**: 80-100% trái phiếu
- **Rủi ro**: Thấp đến trung bình
- **Horizon**: Trung bình đến dài hạn

### Quỹ Cân Bằng (Balanced Funds)

- **Mục tiêu**: Cân bằng giữa tăng trưởng và lợi tức
- **Danh mục**: Hỗn hợp cổ phiếu + trái phiếu
- **Rủi ro**: Trung bình
- **Horizon**: Trung bình (2-3+ năm)

## ⚠️ Ghi Chú Quan Trọng

1. **58 Quỹ Đủ**: Hiện tại có 58 quỹ mở trên Fmarket
2. **NAV Hàng Ngày**: Giá NAV được cập nhật hàng ngày
3. **Lợi Suất Quá Khứ**: Không đảm bảo lợi suất tương lai
4. **Phí Quản Lý**: Đã tính vào lợi suất trả về
5. **Chi Tiết Quỹ**: Hiện tại API không hỗ trợ `details.overview()`, `details.nav_history()`, `details.holdings()`

## 🔗 Xem Thêm

- **[02-Installation](02-installation.md)** - Cài đặt
- **[08-Best Practices](08-best-practices.md)** - Mẹo tối ưu hóa

---

**Last Updated**: 2024-12-04  
**Version**: 3.3.0  
**Status**: Verified with actual data ✅


path: references/vnstock/09-screener-api.md
content: # 09 - Screener API - Lọc Chứng Khoán Nâng Cao

> Tạm thời không hoạt động, chờ bổ sung nguồn dữ liệu mới do bộ API của TCBS đã thay đổi và yêu cầu xác thực tài khoản.


path: references/vnstock/10-connector-guide.md
content: # 06 - Connector Guide - API Bên Ngoài

## 📖 Giới Thiệu

VNStock hỗ trợ kết nối với các API dữ liệu tài chính bên ngoài, giúp bạn lấy dữ liệu từ nhiều nguồn:

- **FMP (Financial Modeling Prep)**: Dữ liệu tài chính toàn cầu.
- **XNO (XNO Data)**: Dữ liệu thị trường (market data) cho kiểm thử chiến lược định lượng.
- **DNSE (DNSE)**: API đặt lệnh.

## 🔌 Kiến Trúc Connector

```
┌────────────────────────────┐
│   Your Application Code    │
├────────────────────────────┤
│  Quote | Company | Finance │  ← API thống nhất
├────────────────────────────┤
│  Provider Registry         │
├────────────────────────────┤
│  Connector Implementations │
│  ┌──────────────────────┐  │
│  │  FMP | XNO | DNSE    │  │  ← Available sources
│  └──────────────────────┘  │
└────────────────────────────┘
```

## 📊 Các Nguồn Dữ Liệu Hỗ Trợ

vnstock hỗ trợ các connector API chính thức sau:

| Provider | Giá Lịch Sử | Tài Chính | Danh Sách | Ghi Chú |
|----------|:---:|:---:|:---:|---|
| **FMP** | ✅ | ✅ | ✅ | Cần API key, dữ liệu toàn cầu |
| **XNO** | ✅ | ❌ | ❌ | Cần API key, chứng khoán Việt Nam |
| **DNSE** | ✅ | ❌ | ❌ | Cần API key, API đặt lệnh |

## 🔑 FMP - Financial Modeling Prep

### Giới Thiệu

FMP cung cấp dữ liệu tài chính toàn cầu, bao gồm:

- Báo cáo tài chính (10K, 10Q)
- Giá lịch sử
- Chỉ số tài chính
- Dữ liệu macro kinh tế
- Dữ liệu crypto

**Website**: https://financialmodelingprep.com  
**API Key**: Có gói free và premium

### Cài Đặt

```bash
# FMP không cần thư viện bổ sung
# Chỉ cần API key
```

### Lấy API Key

1. Truy cập https://financialmodelingprep.com
2. Đăng ký tài khoản free
3. Lấy API key từ dashboard

### Cấu Hình

```python
import os
from dotenv import load_dotenv

load_dotenv()

FMP_API_KEY = os.getenv('FMP_API_KEY')

if not FMP_API_KEY:
    print("❌ Please set FMP_API_KEY in .env")
else:
    print("✅ FMP_API_KEY configured")
```

### Ví dụ Sử Dụng

```python
from vnstock import Quote
import os

# Đảm bảo FMP_API_KEY được set trong environment
if not os.getenv('FMP_API_KEY'):
    print("❌ Please set FMP_API_KEY environment variable")
    print("   export FMP_API_KEY='your_api_key'")
else:
    # Khởi tạo với FMP
    # Lưu ý: API key tự động đọc từ FMP_API_KEY environment variable
    quote = Quote(source="fmp", symbol="AAPL")
    
    # Lấy giá lịch sử
    df = quote.history(
        start="2024-01-01",
        end="2024-12-31",
        resolution="1D"
    )
    
    print(df.head())
```

## 🔑 XNO - XNO Data

### Giới Thiệu

XNO cung cấp dữ liệu chứng khoán Việt Nam:

- Giá lịch sử chi tiết
- Báo cáo tài chính công ty Việt Nam
- Dữ liệu thị trường
- Phân tích kỹ thuật

**Website**: https://xnoquant.vn  
**API Key**: Cần đăng ký (miễn phí)

### Cấu Hình

```python
import os
from dotenv import load_dotenv
from vnstock import Quote

load_dotenv()

XNO_API_KEY = os.getenv('XNO_API_KEY')

if not XNO_API_KEY:
    print("❌ Please set XNO_API_KEY in .env")
else:
    quote = Quote(source="xno", symbol="VCI")
    df = quote.history(
        start="2024-01-01",
        end="2024-12-31",
        resolution="1D"
    )
    print(df.head())
```

## 🔑 DNSE - DNSE API

### Giới Thiệu

DNSE cung cấp API giao dịch và dữ liệu từ Sở Giao Dịch Chứng Khoán Hà Nội:

- API đặt lệnh giao dịch
- Dữ liệu realtime
- Dữ liệu giao dịch

**Website**: https://www.dnse.vn  
**API Key**: Cần liên hệ (có phí)

### Cấu Hình

```python
import os
from dotenv import load_dotenv
from vnstock import Quote

load_dotenv()

DNSE_API_KEY = os.getenv('DNSE_API_KEY')

if not DNSE_API_KEY:
    print("❌ Please set DNSE_API_KEY in .env")
else:
    quote = Quote(source="dnse", symbol="VCI")
    df = quote.history(
        start="2024-01-01",
        end="2024-12-31",
        resolution="1D"
    )
    print(df.head())
```

## ⚠️ Các Connector Không Được Hỗ Trợ

### Binance

Các nguồn sau **không được hỗ trợ** trong phiên bản hiện tại của vnstock:

- **Binance**: Không có connector implementation

**Ghi chú**:

- Để lấy dữ liệu tiền điện tử: sử dụng các thư viện chuyên dụng (ví dụ: `python-binance`)





## 🔄 Chuyển Đổi Giữa Connectors

### Ví dụ: So Sánh Dữ Liệu từ FMP và XNO

```python
import os
from dotenv import load_dotenv
from vnstock import Quote

load_dotenv()

FMP_API_KEY = os.getenv('FMP_API_KEY')
XNO_API_KEY = os.getenv('XNO_API_KEY')

# Lấy dữ liệu từ FMP (cổ phiếu quốc tế)
if FMP_API_KEY:
    quote_fmp = Quote(source="fmp", symbol="AAPL")
    df_fmp = quote_fmp.history(start="2024-01-01", end="2024-12-31", resolution="1D")
    print(f"FMP AAPL: {len(df_fmp)} records")

# Lấy dữ liệu từ XNO (cổ phiếu Việt Nam)
if XNO_API_KEY:
    quote_xno = Quote(source="xno", symbol="VCI")
    df_xno = quote_xno.history(start="2024-01-01", end="2024-12-31", resolution="1D")
    print(f"XNO VCI: {len(df_xno)} records")
```

## 🛡️ Error Handling

### Xử Lý API Key Missing

```python
import os
from dotenv import load_dotenv
from vnstock import Quote

load_dotenv()

def get_quote_with_fallback(symbol, symbol_type="international"):
    """Cố gắng kết nối đến connector với fallback strategy"""
    
    if symbol_type == "international":
        fmp_key = os.getenv('FMP_API_KEY')
        if fmp_key:
            try:
                quote = Quote(source="fmp", symbol=symbol)
                return quote, "fmp"
            except Exception as e:
                print(f"❌ FMP failed: {e}")
    else:
        xno_key = os.getenv('XNO_API_KEY')
        if xno_key:
            try:
                quote = Quote(source="xno", symbol=symbol)
                return quote, "xno"
            except Exception as e:
                print(f"❌ XNO failed: {e}")
    
    return None, None

quote, source = get_quote_with_fallback("AAPL", "international")
if quote:
    df = quote.history(start="2024-01-01", end="2024-12-31")
    print(f"✅ Data from {source}: {len(df)} records")
else:
    print("❌ Could not initialize quote from any connector")
```

### Xử Lý Rate Limiting

```python
import time
from tenacity import retry, stop_after_attempt, wait_exponential
from vnstock import Quote

@retry(
    stop=stop_after_attempt(5),
    wait=wait_exponential(multiplier=1, min=4, max=10)
)
def fetch_with_retry(quote, start, end):
    return quote.history(start=start, end=end, resolution="1D")

quote = Quote(source="fmp", symbol="AAPL")
df = fetch_with_retry(quote, "2024-01-01", "2024-12-31")
print(f"✅ Data fetched: {len(df)} rows")
```

## 📊 Multi-Source Data Collection

### Ví dụ: Thu Thập Dữ Liệu từ Nhiều Connector

```python
from vnstock import Quote
import os
from dotenv import load_dotenv

load_dotenv()

# Danh sách connector cần thử
CONNECTORS = {
    "fmp": {
        "api_key": os.getenv('FMP_API_KEY'),
        "symbols": ["AAPL", "MSFT"]
    },
    "xno": {
        "api_key": os.getenv('XNO_API_KEY'),
        "symbols": ["VCI", "ACB"]
    },
    "dnse": {
        "api_key": os.getenv('DNSE_API_KEY'),
        "symbols": ["VCI", "BID"]
    }
}

results = {}

for connector, config in CONNECTORS.items():
    [REDACTED_SECRET]'api_key')
    
    if not [REDACTED_SECRET]"⚠️  {connector}: No API key found")
        continue
    
    results[connector] = {}
    
    for symbol in config['symbols']:
        try:
            quote = Quote(source=connector, symbol=symbol)
            df = quote.history(
                start="2024-01-01",
                end="2024-12-31",
                resolution="1D"
            )
            
            results[connector][symbol] = {
                'success': True,
                'rows': len(df)
            }
            print(f"✅ {connector:10} {symbol:10}: {len(df)} records")
        except Exception as e:
            results[connector][symbol] = {
                'success': False,
                'error': str(e)
            }
            print(f"❌ {connector:10} {symbol:10}: {str(e)[:40]}")
```

## 💡 Best Practices

### 1. Caching để Giảm API Calls

```python
import pickle
import os
import datetime
from vnstock import Quote

CACHE_DIR = 'cache'
CACHE_TTL = 3600  # 1 hour

def get_quote_data_cached(source, symbol, start, end):
    cache_file = f"{CACHE_DIR}/{source}_{symbol}_{start}_{end}.pkl"
    
    # Kiểm tra cache
    if os.path.exists(cache_file):
        file_age = datetime.datetime.now().timestamp() - os.path.getmtime(cache_file)
        if file_age < CACHE_TTL:
            with open(cache_file, 'rb') as f:
                return pickle.load(f)
    
    # Fetch từ connector API
    quote = Quote(source=source, symbol=symbol)
    df = quote.history(start=start, end=end, resolution="1D")
    
    # Save to cache
    os.makedirs(CACHE_DIR, exist_ok=True)
    with open(cache_file, 'wb') as f:
        pickle.dump(df, f)
    
    return df

df = get_quote_data_cached("fmp", "AAPL", "2024-01-01", "2024-12-31")
print(f"✅ Data loaded (from cache or API): {len(df)} rows")
```

### 2. Rate Limiting

```python
import time
from vnstock import Quote

def fetch_multiple_symbols(symbols, source="fmp"):
    """Fetch từ connector với rate limiting"""
    results = {}
    
    for i, symbol in enumerate(symbols):
        try:
            quote = Quote(source=source, symbol=symbol)
            df = quote.history(
                start="2024-01-01",
                end="2024-12-31",
                resolution="1D"
            )
            results[symbol] = df
            print(f"✅ {symbol}: {len(df)} records")
            
            # Rate limiting: chờ giữa các requests
            if i < len(symbols) - 1:
                time.sleep(1)
        except Exception as e:
            print(f"❌ {symbol}: {type(e).__name__}")
    
    return results

symbols = ["AAPL", "MSFT", "GOOGL"]
data = fetch_multiple_symbols(symbols, source="fmp")
print(f"✅ Fetched data for {len(data)} symbols")
```

## ❌ Troubleshooting

### Lỗi 1: Invalid API Key

```
Error: Invalid API Key
```

**Giải pháp:**
- Kiểm tra API key trong .env
- Kiểm tra quyền truy cập API
- Nếu free tier, có thể bị rate limit

### Lỗi 2: Source Not Supported

```
Error: Source 'invalid_source' is not supported
```

**Giải pháp:**
- Kiểm tra danh sách hỗ trợ: `DataSource.all_sources()`
- Đảm bảo source name chính xác

### Lỗi 3: No Data Available

```
Error: No data available for this symbol/date range
```

**Giải pháp:**
- Kiểm tra symbol có hợp lệ không
- Kiểm tra date range (không quá lâu)
- Thử symbol khác để test

## 📚 Bước Tiếp Theo

1. [02-Installation](02-installation.md) - Cài đặt
2. [01-Overview](01-overview.md) - Tổng quan
3. [03-Listing API](03-listing-api.md) - Danh sách chứng khoán
4. [04-Quote & Price](04-quote-price-api.md) - Giá lịch sử
5. [05-Financial API](05-financial-api.md) - Dữ liệu tài chính
6. ✅ **06-Connector Guide** - Bạn đã ở đây
7. [07-Best Practices](07-best-practices.md) - Mẹo & kinh nghiệm

---

**Last Updated**: 2024-12-03  
**Version**: 3.3.0  
**Status**: Actively Maintained


path: references/vnstock/11-best-practices.md
content: # 07 - Best Practices - Mẹo & Kinh Nghiệm

## 📖 Giới Thiệu

Tài liệu này cung cấp các mẹo, kinh nghiệm, và best practices khi sử dụng VNStock.

## 🚀 Performance & Optimization

### 1. Batch Processing - Xử Lý Hàng Loạt

Khi lấy dữ liệu cho nhiều cổ phiếu, khởi tạo một lần và thay đổi symbol:

```python
from vnstock import Quote

symbols = ['VCI', 'ACB', 'BID', 'CTG', 'SBT']
quote = Quote(source="vci", symbol="VCI")

results = {}
for symbol in symbols:
    quote.symbol = symbol
    df = quote.history(start="2024-01-01", end="2024-12-31", resolution="1D")
    results[symbol] = df
    print(f"✅ {symbol}: {len(df)} rows")

# Xử lý kết quả
for symbol, df in results.items():
    print(f"{symbol}: {len(df)} dòng dữ liệu")
```

### 2. Caching - Lưu Trữ Tạm

```python
import pickle
import os
from datetime import datetime, timedelta

class QuoteCache:
    def __init__(self, cache_dir='./cache', ttl_hours=24):
        self.cache_dir = cache_dir
        self.ttl_seconds = ttl_hours * 3600
        os.makedirs(cache_dir, exist_ok=True)
    
    def _get_cache_path(self, source, symbol, start, end):
        return f"{self.cache_dir}/{source}_{symbol}_{start}_{end}.pkl"
    
    def _is_cache_valid(self, filepath):
        if not os.path.exists(filepath):
            return False
        age = datetime.now().timestamp() - os.path.getmtime(filepath)
        return age < self.ttl_seconds
    
    def get(self, source, symbol, start, end):
        from vnstock import Quote
        
        cache_path = self._get_cache_path(source, symbol, start, end)
        
        # Từ cache nếu còn hợp lệ
        if self._is_cache_valid(cache_path):
            with open(cache_path, 'rb') as f:
                return pickle.load(f)
        
        # Fetch từ API
        quote = Quote(source=source, symbol=symbol)
        df = quote.history(start=start, end=end)
        
        # Lưu cache
        with open(cache_path, 'wb') as f:
            pickle.dump(df, f)
        
        return df

# Sử dụng
cache = QuoteCache(ttl_hours=24)
df = cache.get("vci", "VCI", "2024-01-01", "2024-12-31")
print(f"✅ Loaded {len(df)} rows (from cache or API)")
```

### 3. Connection Pooling - Tái Sử Dụng Kết Nối

```python
from vnstock import Quote
import threading
from queue import Queue

class QuotePool:
    """Thư viện Quote Connection Pool"""
    
    def __init__(self, source="vci", pool_size=5):
        self.source = source
        self.pool = Queue(maxsize=pool_size)
        for _ in range(pool_size):
            self.pool.put(Quote(source=source))
    
    def get_quote(self, symbol):
        quote = self.pool.get()
        quote.symbol = symbol
        return quote
    
    def return_quote(self, quote):
        self.pool.put(quote)
    
    def fetch_history(self, symbol, start, end):
        quote = self.get_quote(symbol)
        try:
            df = quote.history(start=start, end=end)
            return df
        finally:
            self.return_quote(quote)

# Sử dụng
pool = QuotePool(source="vci", pool_size=3)
df = pool.fetch_history("VCI", "2024-01-01", "2024-12-31")
print(f"✅ Fetched {len(df)} rows")
```

## 🛡️ Error Handling & Resilience

### 1. Retry Logic - Xử Lý Lỗi Tạm Thời

```python
from tenacity import (
    retry,
    stop_after_attempt,
    wait_exponential,
    retry_if_exception_type
)
import requests

@retry(
    retry=retry_if_exception_type((requests.RequestException, TimeoutError)),
    stop=stop_after_attempt(5),
    wait=wait_exponential(multiplier=1, min=2, max=10)
)
def fetch_with_retry(quote, start, end):
    return quote.history(start=start, end=end)

from vnstock import Quote

quote = Quote(source="vci", symbol="VCI")
df = fetch_with_retry(quote, "2024-01-01", "2024-12-31")
print(f"✅ Data fetched: {len(df)} rows")
```

### 2. Circuit Breaker - Dừng Khi Lỗi Liên Tục

```python
from datetime import datetime, timedelta

class CircuitBreaker:
    def __init__(self, failure_threshold=5, timeout_seconds=60):
        self.failure_threshold = failure_threshold
        self.timeout_seconds = timeout_seconds
        self.failure_count = 0
        self.last_failure_time = None
        self.is_open = False
    
    def call(self, func, *args, **kwargs):
        if self.is_open:
            if datetime.now() - self.last_failure_time > timedelta(seconds=self.timeout_seconds):
                self.is_open = False
                self.failure_count = 0
            else:
                raise Exception("Circuit Breaker is OPEN")
        
        try:
            result = func(*args, **kwargs)
            self.failure_count = 0
            return result
        except Exception as e:
            self.failure_count += 1
            self.last_failure_time = datetime.now()
            
            if self.failure_count >= self.failure_threshold:
                self.is_open = True
                raise Exception(f"Circuit Breaker opened after {self.failure_count} failures")
            
            raise e

# Sử dụng
from vnstock import Quote

circuit = CircuitBreaker(failure_threshold=3, timeout_seconds=60)
quote = Quote(source="vci", symbol="VCI")

try:
    df = circuit.call(
        quote.history,
        start="2024-01-01",
        end="2024-12-31"
    )
    print(f"✅ Data fetched: {len(df)} rows")
except Exception as e:
    print(f"❌ Error: {e}")
```

### 3. Fallback - Dự Phòng

```python
from vnstock import Quote

def get_price_data_with_fallback(symbol, start, end):
    """Thử các source theo thứ tự ưu tiên"""
    
    sources = ["vci", "tcbs"]  # Web scraping sources (fallback options)
    
    for source in sources:
        try:
            quote = Quote(source=source, symbol=symbol)
            df = quote.history(start=start, end=end)
            print(f"✅ Got data from {source}")
            return df
        except Exception as e:
            print(f"⚠️ {source} failed: {e}")
            continue
    
    raise Exception("All sources failed!")

# Sử dụng
try:
    df = get_price_data_with_fallback("VCI", "2024-01-01", "2024-12-31")
except Exception as e:
    print(f"❌ {e}")
```

## 📊 Data Quality & Validation

### 1. Data Validation - Kiểm Tra Chất Lượng Dữ Liệu

```python
import pandas as pd
from vnstock import Quote

def validate_quote_data(df):
    """Kiểm tra dữ liệu giá"""
    
    issues = []
    
    # 1. Kiểm tra cột bắt buộc
    required_cols = ['time', 'open', 'high', 'low', 'close', 'volume']
    missing = [col for col in required_cols if col not in df.columns]
    if missing:
        issues.append(f"Missing columns: {missing}")
    
    # 2. Kiểm tra dữ liệu trống
    if df.isnull().any().any():
        issues.append(f"Found {df.isnull().sum().sum()} null values")
    
    # 3. Kiểm tra logic OHLC (High >= Low >= Close >= Open)
    invalid = (df['high'] < df['low']).sum()
    if invalid > 0:
        issues.append(f"Found {invalid} rows where High < Low")
    
    # 4. Kiểm tra volume > 0
    zero_vol = (df['volume'] == 0).sum()
    if zero_vol > 0:
        issues.append(f"Found {zero_vol} rows with zero volume")
    
    # 5. Kiểm tra time series liên tục
    if 'time' in df.columns:
        df_sorted = df.sort_values('time')
        time_gaps = df_sorted['time'].diff()
        max_gap = time_gaps.max()
        issues.append(f"Max time gap: {max_gap}")
    
    if issues:
        print("⚠️ Data Quality Issues:")
        for issue in issues:
            print(f"  - {issue}")
        return False
    else:
        print("✅ Data quality check passed")
        return True

# Sử dụng
quote = Quote(source="vci", symbol="VCI")
df = quote.history(start="2024-01-01", end="2024-12-31")
validate_quote_data(df)
```

### 2. Outlier Detection - Phát Hiện Bất Thường

```python
import pandas as pd
import numpy as np

def detect_outliers(df, column='close', threshold=3):
    """Phát hiện giá trị bất thường sử dụng Z-score"""
    
    z_scores = np.abs((df[column] - df[column].mean()) / df[column].std())
    outliers = df[z_scores > threshold]
    
    if len(outliers) > 0:
        print(f"⚠️ Found {len(outliers)} outliers:")
        print(outliers[['time', 'open', 'high', 'low', 'close']])
    else:
        print("✅ No outliers detected")
    
    return outliers

# Sử dụng
from vnstock import Quote

quote = Quote(source="vci", symbol="VCI")
df = quote.history(start="2024-01-01", end="2024-12-31")
detect_outliers(df, column='close', threshold=3)
```

## 🔐 Security & Privacy

### 1. API Key Management

```python
import os
from dotenv import load_dotenv

# ❌ Sai - API key hardcoded
[REDACTED_SECRET]"

# ✅ Đúng - Từ environment variable
load_dotenv()
[REDACTED_SECRET]'API_KEY')

if not api_key:
    raise ValueError("API_KEY not set in environment")
```

### 2. Secure Configuration

```python
# .env file (gitignored)
FMP_API_KEY=your_key_here
XNO_API_KEY=your_key_here
DNSE_API_KEY=your_key_here

# .gitignore
.env
*.key
*.secret
```

### 3. Encrypted Storage

```python
from cryptography.fernet import Fernet
import json

class SecureConfig:
    def __init__(self, config_file=".secure_config"):
        self.config_file = config_file
    
    def save(self, data, password):
        cipher = Fernet(password.encode().ljust(32)[:32])
        encrypted = cipher.encrypt(json.dumps(data).encode())
        with open(self.config_file, 'wb') as f:
            f.write(encrypted)
    
    def load(self, password):
        cipher = Fernet(password.encode().ljust(32)[:32])
        with open(self.config_file, 'rb') as f:
            encrypted = f.read()
        return json.loads(cipher.decrypt(encrypted).decode())

# Sử dụng
config = SecureConfig()
config.save({
    'fmp_key': 'your_key',
    'xno_key': 'your_key'
}, [REDACTED_SECRET]')
```

## 📈 Logging & Monitoring

### 1. Setup Logging

```python
import logging
import sys

# Cấu hình logging
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('vnstock.log'),
        logging.StreamHandler(sys.stdout)
    ]
)

logger = logging.getLogger(__name__)

# Sử dụng
from vnstock import Quote

try:
    logger.info("Fetching VCI price data...")
    quote = Quote(source="vci", symbol="VCI")
    df = quote.history(start="2024-01-01", end="2024-12-31")
    logger.info(f"✅ Successfully fetched {len(df)} rows")
except Exception as e:
    logger.error(f"❌ Error: {e}", exc_info=True)
```

### 2. Performance Monitoring

```python
import time
import logging

logger = logging.getLogger(__name__)

def monitor_performance(func):
    """Decorator để monitor performance"""
    def wrapper(*args, **kwargs):
        start = time.time()
        result = func(*args, **kwargs)
        elapsed = time.time() - start
        
        logger.info(f"{func.__name__} took {elapsed:.2f}s")
        
        if elapsed > 5:  # Warning nếu > 5 giây
            logger.warning(f"{func.__name__} took too long: {elapsed:.2f}s")
        
        return result
    return wrapper

# Sử dụng
from vnstock import Quote

@monitor_performance
def fetch_vci_data():
    quote = Quote(source="vci", symbol="VCI")
    return quote.history(start="2024-01-01", end="2024-12-31")

df = fetch_vci_data()
```

## 📚 Testing

### 1. Unit Testing

```python
import unittest
from unittest.mock import Mock, patch
from vnstock import Quote
import pandas as pd

class TestQuoteAPI(unittest.TestCase):
    def setUp(self):
        self.quote = Quote(source="vci", symbol="VCI")
    
    def test_history_returns_dataframe(self):
        df = self.quote.history(start="2024-01-01", end="2024-01-31")
        self.assertIsInstance(df, pd.DataFrame)
        self.assertGreater(len(df), 0)
    
    def test_history_columns_exist(self):
        df = self.quote.history(start="2024-01-01", end="2024-01-31")
        required_cols = ['time', 'open', 'high', 'low', 'close', 'volume']
        for col in required_cols:
            self.assertIn(col, df.columns)
    
    @patch('vnstock.Quote.history')
    def test_history_with_mock(self, mock_history):
        mock_history.return_value = pd.DataFrame({
            'time': ['2024-01-01'],
            'open': [21.0],
            'high': [21.5],
            'low': [20.8],
            'close': [21.4],
            'volume': [1000000]
        })
        
        df = self.quote.history(start="2024-01-01", end="2024-01-01")
        self.assertEqual(len(df), 1)

if __name__ == '__main__':
    unittest.main()
```

Chạy test:

```bash
python -m unittest test_quote.py
```

## 🔗 Integration Examples

### Real-world: Stock Screener

```python
from vnstock import Quote, Listing, Finance
import pandas as pd

def screen_stocks(min_pe=15, max_pe=25):
    """Tìm cổ phiếu theo tiêu chí P/E"""
    
    listing = Listing(source="vci")
    all_stocks = listing.all_symbols(to_df=True)
    
    results = []
    for idx, row in all_stocks.iterrows():
        symbol = row['symbol']
        
        try:
            # Lấy giá hiện tại
            quote = Quote(source="vci", symbol=symbol)
            df = quote.history(start="2024-11-01", end="2024-12-03")
            current_price = df['close'].iloc[-1] if len(df) > 0 else None
            
            # Lấy dữ liệu tài chính
            finance = Finance(source="vci", symbol=symbol)
            ratios = finance.ratio()
            pe = ratios.iloc[0].get('pe_ratio') if len(ratios) > 0 else None
            
            # Kiểm tra tiêu chí
            if pe and min_pe <= pe <= max_pe:
                results.append({
                    'symbol': symbol,
                    'price': current_price,
                    'pe_ratio': pe
                })
        except:
            pass
    
    return pd.DataFrame(results)

# Sử dụng
screened = screen_stocks(min_pe=15, max_pe=25)
print(screened.sort_values('pe_ratio'))
```

## 📚 Bước Tiếp Theo

1. [02-Installation](02-installation.md) - Cài đặt
2. [01-Overview](01-overview.md) - Tổng quan
3. [03-Listing API](03-listing-api.md) - Danh sách chứng khoán
4. [04-Quote & Price](04-quote-price-api.md) - Giá lịch sử
5. [05-Financial API](05-financial-api.md) - Dữ liệu tài chính
6. [06-Connector Guide](06-connector-guide.md) - API bên ngoài
7. ✅ **07-Best Practices** - Bạn đã ở đây

---

**Last Updated**: 2024-12-03  
**Version**: 3.3.0  
**Status**: Actively Maintained


path: references/vnstock/README.md
content: # VNStock 3.4.0 - Tài Liệu Hướng Dẫn

## 🎯 Giới Thiệu

**VNStock** là thư viện Python để lấy dữ liệu chứng khoán Việt Nam từ nhiều nguồn uy tín. Thiết kế với kiến trúc provider-based, cho phép chuyển đổi linh hoạt giữa các nguồn dữ liệu khác nhau.

### ✨ Tính Năng Chính

- ✅ **Nhiều nguồn dữ liệu**: VCI, KBS, MSN (API công khai); FMP, DNSE (API chính thức)
- ⚠️ **TCBS**: Ngưng cập nhật thêm từ v3.4.0, sẽ loại bỏ trong v3.5.0 (tháng 3/2026)
- ✅ **API thống nhất**: Cùng interface cho tất cả nguồn
- ✅ **Dữ liệu lịch sử & Real-time**: Giá, công ty, tài chính
- ✅ **Dữ liệu công ty**: Hồ sơ, cổ đông, nhân viên quản lý
- ✅ **Dữ liệu tài chính**: Báo cáo, chỉ số, lưu chuyển tiền tệ
- ✅ **Lọc & Phân loại**: Theo ngành, sàn giao dịch, chỉ số

## 📚 Hướng Dẫn Sử Dụng

| Tài Liệu | Nội Dung | Mức Độ |
|---------|---------|--------|
| **[01-Overview](01-overview.md)** | Tổng quan kiến trúc, các loại dữ liệu | Cơ bản |
| **[02-Installation](02-installation.md)** | Cài đặt, thiết lập, kiểm tra | Cơ bản |
| **[03-Listing API](03-listing-api.md)** | API tìm kiếm và lọc chứng khoán | Cơ bản |
| **[04-Company API](04-company-api.md)** | Thông tin công ty, cổ đông, nhân viên quản lý | Cơ bản |
| **[05-Trading API](05-trading-api.md)** | Dữ liệu giao dịch, bid/ask, thống kê | Cơ bản |
| **[06-Quote & Price](06-quote-price-api.md)** | API lấy giá lịch sử và real-time | Cơ bản |
| **[07-Financial API](07-financial-api.md)** | API dữ liệu tài chính và báo cáo | Trung cấp |
| **[08-Fund API](08-fund-api.md)** | Dữ liệu quỹ đầu tư mở (Fmarket) | Trung cấp |
| **[09-Screener API](09-screener-api.md)** | Công cụ lọc chứng khoán nâng cao | Nâng cao |
| **[10-Connector Guide](10-connector-guide.md)** | Hướng dẫn API bên ngoài (FMP, XNO, DNSE) | Nâng cao |
| **[11-Best Practices](11-best-practices.md)** | Mẹo tối ưu hóa, xử lý lỗi, security | Nâng cao |

## 🚀 Bắt Đầu Nhanh

### Cài Đặt

```bash
pip install vnstock
```

Xem chi tiết tại **[02-Installation](02-installation.md)**

## 📖 Cấu Trúc Tài Liệu

Tài liệu được chia thành 11 phần theo thứ tự từ cơ bản đến nâng cao:

1. **[01-Overview](01-overview.md)** - Hiểu kiến trúc và các loại dữ liệu
2. **[02-Installation](02-installation.md)** - Cài đặt và kiểm tra môi trường
3. **[03-Listing API](03-listing-api.md)** - Tìm kiếm danh sách chứng khoán
4. **[04-Company API](04-company-api.md)** - Lấy thông tin công ty chi tiết
5. **[05-Trading API](05-trading-api.md)** - Dữ liệu giao dịch thị trường
6. **[06-Quote & Price](06-quote-price-api.md)** - Lấy dữ liệu giá
7. **[07-Financial API](07-financial-api.md)** - Truy cập dữ liệu tài chính
8. **[08-Fund API](08-fund-api.md)** - Thông tin quỹ đầu tư mở
9. **[09-Screener API](09-screener-api.md)** - Lọc chứng khoán nâng cao
10. **[10-Connector Guide](10-connector-guide.md)** - Sử dụng API bên ngoài
11. **[11-Best Practices](11-best-practices.md)** - Tối ưu hóa và xử lý lỗi

## Kiến Trúc Hệ Thống

VNStock sử dụng kiến trúc provider-based cho phép chuyển đổi linh hoạt giữa các nguồn dữ liệu:

```
Ứng Dụng
   ↓
API Thống Nhất (Quote, Listing, Finance, Company)
   ↓
Adapter Layer (Chuẩn hóa dữ liệu)
   ↓
Các Nguồn Dữ Liệu (Web Scraping & API bên ngoài)
```

## 📊 Nguồn Dữ Liệu

### Web Scraping

| Nguồn | Danh Sách | Giá | Công Ty | Tài Chính | Trạng Thái |
|-------|----------|-----|--------|----------|-----------|
| **VCI** | ✅ | ✅ | ✅ | ✅ | Hoạt động |
| **KBS** | ✅ | ✅ | ✅ | ✅ | Mới (v3.4.0) |
| **MSN** | ✅ | ✅ | ❌ | ❌ | Hoạt động |

### API Bên Ngoài

| API | Giá | Tài Chính | Công Ty |
|-----|-----|----------|---------|
| **FMP** | ✅ | ✅ | ✅ |
| **XNO** | ✅ | ✅ | ✅ |
| **DNSE** | ✅ | ❌ | ❌ |

## 🎓 Lộ Trình Học Tập

Khuyến nghị làm theo thứ tự từ trên xuống để hiểu toàn bộ hệ thống:

1. **[01-Overview](01-overview.md)** - Nắm vững kiến trúc và các khái niệm cơ bản
2. **[02-Installation](02-installation.md)** - Cài đặt và xác nhận môi trường hoạt động
3. **[03-Listing API](03-listing-api.md)** - Tìm kiếm chứng khoán theo tiêu chí
4. **[03a-Company API](03a-company-api.md)** - Tìm hiểu chi tiết về công ty
5. **[03b-Trading API](03b-trading-api.md)** - Phân tích dữ liệu giao dịch
6. **[04-Quote & Price](04-quote-price-api.md)** - Truy cập dữ liệu giá chứng khoán
7. **[05-Financial API](05-financial-api.md)** - Lấy dữ liệu tài chính chi tiết
8. **[05a-Fund API](05a-fund-api.md)** - Khám phá quỹ đầu tư mở
9. **[06-Connector Guide](06-connector-guide.md)** - Sử dụng API bên ngoài (FMP, XNO, DNSE)
10. **[06a-Screener API](06a-screener-api.md)** - Lọc chứng khoán theo tiêu chí nâng cao
11. **[07-Best Practices](07-best-practices.md)** - Áp dụng tối ưu hóa, xử lý lỗi, security

## 🔗 Liên Kết Hữu Ích

- **[GitHub](https://github.com/thinh-vu/vnstock)** - Mã nguồn và issue tracking
- **[PyPI](https://pypi.org/project/vnstock)** - Cài đặt package
- **[Website](https://vnstocks.com)** - Trang chính thức

## ℹ️ Thông Tin Phiên Bản

- **Phiên bản**: 3.4.0
- **Cập nhật lần cuối**: 2024-12-17
- **Trạng thái**: Đang bảo trì ✅
- **Thông báo**: TCBS đã ngưng được cập nhật, sẽ loại bỏ trong v3.5.0 (tháng 3/2026)
- **License**: MIT


path: scripts/build_universe.py
content: #!/usr/bin/env python3
from __future__ import annotations

import argparse
from pathlib import Path

try:
    import pandas as pd
except ModuleNotFoundError:
    pd = None

from common import configure_vnstock_api_key, ensure_outdir, timestamp, write_json, write_latest_alias


def from_group(source: str, group: str):
    from vnstock import Listing

    listing = Listing(source=source)
    data = listing.symbols_by_group(group=group)
    if isinstance(data, list):
        return pd.DataFrame({"symbol": [x.upper() for x in data]})
    if isinstance(data, pd.Series):
        return pd.DataFrame({"symbol": data.astype(str).str.upper().tolist()})
    if "symbol" in data.columns:
        data["symbol"] = data["symbol"].astype(str).str.upper()
    return data


def from_exchange(source: str, exchange: str):
    from vnstock import Listing

    listing = Listing(source=source)
    data = listing.symbols_by_exchange()
    if "exchange" in data.columns:
        data = data[data["exchange"].astype(str).str.upper().eq(exchange.upper())]
    if "symbol" in data.columns:
        data["symbol"] = data["symbol"].astype(str).str.upper()
    return data


def from_symbols(symbols: str):
    rows = [x.strip().upper() for x in symbols.split(",") if x.strip()]
    return pd.DataFrame({"symbol": rows})


def main() -> None:
    parser = argparse.ArgumentParser(description="Build stock universe for vnstock valuation pipeline")
    parser.add_argument("--source", default="kbs", choices=["kbs", "vci"], help="Data source")
    parser.add_argument("--mode", required=True, choices=["group", "exchange", "symbols"], help="Universe mode")
    parser.add_argument("--group", default="VN30", help="Group name for mode=group")
    parser.add_argument("--exchange", default="HOSE", help="Exchange name for mode=exchange")
    parser.add_argument("--symbols", default="", help="Comma-separated symbols for mode=symbols")
    parser.add_argument("--outdir", default="./outputs", help="Output directory")
    parser.add_argument("--api-key", default="", help="Optional VNStock API key override")
    args = parser.parse_args()

    if pd is None:
        raise ModuleNotFoundError("Missing dependency: pandas. Install with `pip install pandas`.")

    outdir = ensure_outdir(args.outdir)
    configure_vnstock_api_key(args.api_key or None)

    if args.mode == "group":
        df = from_group(args.source, args.group)
    elif args.mode == "exchange":
        df = from_exchange(args.source, args.exchange)
    else:
        df = from_symbols(args.symbols)

    if df.empty or "symbol" not in df.columns:
        raise RuntimeError("Failed to build universe with non-empty symbol column.")

    df = df.dropna(subset=["symbol"]).drop_duplicates(subset=["symbol"]).reset_index(drop=True)
    ts = timestamp()
    csv_path = outdir / f"universe_{ts}.csv"
    json_path = outdir / f"universe_{ts}.json"

    df.to_csv(csv_path, index=False)
    write_json(
        json_path,
        {
            "generated_at": ts,
            "source": args.source,
            "mode": args.mode,
            "count": len(df),
            "symbols": df["symbol"].tolist(),
        },
    )

    # Latest pointers for easier pipeline chaining.
    (outdir / "universe_latest.csv").write_text(str(csv_path), encoding="utf-8")
    (outdir / "universe_latest.json").write_text(str(json_path), encoding="utf-8")

    print(f"Saved: {csv_path}")
    print(f"Saved: {json_path}")


if __name__ == "__main__":
    main()


path: scripts/catalog_vnstock.py
content: #!/usr/bin/env python3
from __future__ import annotations

import argparse
import importlib
import inspect
import pkgutil
from pathlib import Path

from common import configure_vnstock_api_key, ensure_outdir, timestamp, write_json


def is_public_method(name: str, member) -> bool:
    return callable(member) and not name.startswith("_")


def add_module_classes(module, bag):
    for name in dir(module):
        if name.startswith("_"):
            continue
        obj = getattr(module, name)
        if inspect.isclass(obj):
            methods = []
            for mname in dir(obj):
                member = getattr(obj, mname)
                if is_public_method(mname, member):
                    try:
                        sig = str(inspect.signature(member))
                    except Exception:
                        sig = "(signature unavailable)"
                    methods.append({"method": mname, "signature": sig})
            if methods:
                full = f"{obj.__module__}.{obj.__name__}"
                bag[full] = sorted(methods, key=lambda x: x["method"])


def main() -> None:
    parser = argparse.ArgumentParser(description="Catalog available vnstock classes and public methods")
    parser.add_argument("--outdir", default="./outputs", help="Output directory")
    parser.add_argument("--deep", action="store_true", help="Scan vnstock submodules in addition to top-level exports")
    parser.add_argument("--api-key", default="", help="Optional VNStock API key override")
    args = parser.parse_args()

    configure_vnstock_api_key(args.api_key or None)
    import vnstock

    outdir = ensure_outdir(args.outdir)
    ts = timestamp()

    exported_classes = {}
    add_module_classes(vnstock, exported_classes)

    scanned_modules = ["vnstock"]
    if args.deep and hasattr(vnstock, "__path__"):
        for m in pkgutil.walk_packages(vnstock.__path__, prefix="vnstock."):
            mod_name = m.name
            try:
                mod = importlib.import_module(mod_name)
                add_module_classes(mod, exported_classes)
                scanned_modules.append(mod_name)
            except Exception:
                # Skip problematic internal modules; catalog should stay best-effort.
                continue

    payload = {
        "generated_at": ts,
        "vnstock_version": getattr(vnstock, "__version__", "unknown"),
        "deep_scan": args.deep,
        "scanned_modules_count": len(scanned_modules),
        "classes": exported_classes,
    }

    json_path = Path(outdir) / f"vnstock_catalog_{ts}.json"
    md_path = Path(outdir) / f"vnstock_catalog_{ts}.md"
    write_json(json_path, payload)

    lines = [
        f"# VNStock Catalog ({ts})",
        "",
        f"- Version: `{payload['vnstock_version']}`",
        f"- Class count: `{len(exported_classes)}`",
        "",
        "## Classes",
    ]

    for cls, methods in sorted(exported_classes.items()):
        lines.append(f"### {cls}")
        for m in methods:
            lines.append(f"- `{m['method']}{m['signature']}`")
        lines.append("")

    md_path.write_text("\n".join(lines), encoding="utf-8")

    (Path(outdir) / "vnstock_catalog_latest.json").write_text(str(json_path), encoding="utf-8")
    (Path(outdir) / "vnstock_catalog_latest.md").write_text(str(md_path), encoding="utf-8")

    print(f"Saved: {json_path}")
    print(f"Saved: {md_path}")


if __name__ == "__main__":
    main()


path: scripts/collect_fundamentals.py
content: #!/usr/bin/env python3
from __future__ import annotations

import argparse
from pathlib import Path

try:
    import pandas as pd
except ModuleNotFoundError:
    pd = None

from common import RateLimiter, configure_vnstock_api_key, ensure_outdir, find_metric, timestamp, write_json


def load_symbols(universe_file: str):
    p = Path(universe_file)
    if not p.exists():
        raise FileNotFoundError(f"Universe file not found: {universe_file}")
    df = pd.read_csv(p)
    if "symbol" not in df.columns:
        raise RuntimeError("Universe file must include symbol column.")
    return [s.strip().upper() for s in df["symbol"].astype(str).tolist() if s.strip()]


def main() -> None:
    parser = argparse.ArgumentParser(description="Collect valuation and quality fundamentals")
    parser.add_argument("--source", default="kbs", choices=["kbs", "vci"], help="Data source")
    parser.add_argument("--universe-file", required=True, help="CSV with symbol column")
    parser.add_argument("--outdir", default="./outputs", help="Output directory")
    parser.add_argument("--min-interval-sec", type=float, default=3.2, help="Rate-limit pacing")
    parser.add_argument("--api-key", default="", help="Optional VNStock API key override")
    args = parser.parse_args()

    if pd is None:
        raise ModuleNotFoundError("Missing dependency: pandas. Install with `pip install pandas`.")

    from vnstock import Company, Finance

    outdir = ensure_outdir(args.outdir)
    configure_vnstock_api_key(args.api_key or None)
    symbols = load_symbols(args.universe_file)
    limiter = RateLimiter(min_interval_sec=args.min_interval_sec)

    rows = []
    errors = []

    for symbol in symbols:
        row = {"symbol": symbol, "source": args.source}
        try:
            limiter.wait()
            finance = Finance(source=args.source, symbol=symbol)
            ratios = finance.ratio(period="year")

            row["pe"] = find_metric(ratios, ["p/e", "pe", "price to earnings"])
            row["pb"] = find_metric(ratios, ["p/b", "pb", "price to book"])
            row["roe"] = find_metric(ratios, ["roe", "return on equity"])
            row["debt_equity"] = find_metric(ratios, ["debt/equity", "debt to equity", "d/e"])

            limiter.wait()
            company = Company(source=args.source, symbol=symbol)
            overview = company.overview()
            if overview is not None and isinstance(overview, pd.DataFrame) and not overview.empty:
                local = overview.iloc[0].to_dict()
                row["exchange"] = local.get("exchange")
                row["industry"] = local.get("icb_name3") or local.get("icb_name2")

            rows.append(row)
        except Exception as e:
            errors.append({"symbol": symbol, "error": str(e)})

    df = pd.DataFrame(rows)
    ts = timestamp()
    csv_path = outdir / f"fundamentals_{ts}.csv"
    json_path = outdir / f"fundamentals_{ts}.json"

    df.to_csv(csv_path, index=False)
    write_json(
        json_path,
        {
            "generated_at": ts,
            "source": args.source,
            "rows": df.to_dict(orient="records"),
            "errors": errors,
        },
    )

    (outdir / "fundamentals_latest.csv").write_text(str(csv_path), encoding="utf-8")
    (outdir / "fundamentals_latest.json").write_text(str(json_path), encoding="utf-8")

    print(f"Saved: {csv_path}")
    print(f"Saved: {json_path}")


if __name__ == "__main__":
    main()


path: scripts/collect_market_data.py
content: #!/usr/bin/env python3
from __future__ import annotations

import argparse
from datetime import datetime, timedelta
from pathlib import Path

try:
    import pandas as pd
except ModuleNotFoundError:
    pd = None

from common import RateLimiter, configure_vnstock_api_key, ensure_outdir, pct_return, safe_float, timestamp, write_json


def load_symbols(universe_file: str):
    p = Path(universe_file)
    if not p.exists():
        raise FileNotFoundError(f"Universe file not found: {universe_file}")
    df = pd.read_csv(p)
    if "symbol" not in df.columns:
        raise RuntimeError("Universe file must include symbol column.")
    return [s.strip().upper() for s in df["symbol"].astype(str).tolist() if s.strip()]


def main() -> None:
    parser = argparse.ArgumentParser(description="Collect historical market data and momentum metrics")
    parser.add_argument("--source", default="kbs", choices=["kbs", "vci"], help="Data source")
    parser.add_argument("--universe-file", required=True, help="CSV with symbol column")
    parser.add_argument("--outdir", default="./outputs", help="Output directory")
    parser.add_argument("--interval", default="1D", help="Price interval")
    parser.add_argument("--lookback-days", type=int, default=260, help="Bars target")
    parser.add_argument("--min-interval-sec", type=float, default=3.2, help="Rate-limit pacing")
    parser.add_argument("--api-key", default="", help="Optional VNStock API key override")
    args = parser.parse_args()

    if pd is None:
        raise ModuleNotFoundError("Missing dependency: pandas. Install with `pip install pandas`.")

    from vnstock import Quote

    outdir = ensure_outdir(args.outdir)
    configure_vnstock_api_key(args.api_key or None)
    symbols = load_symbols(args.universe_file)
    limiter = RateLimiter(min_interval_sec=args.min_interval_sec)

    end = datetime.now().date()
    start = end - timedelta(days=max(args.lookback_days * 2, 400))

    rows = []
    errors = []

    for symbol in symbols:
        try:
            limiter.wait()
            quote = Quote(source=args.source, symbol=symbol)
            hist = quote.history(start=str(start), end=str(end), interval=args.interval)

            row = {"symbol": symbol, "source": args.source}
            if hist is not None and not hist.empty and "close" in hist.columns:
                last = hist.sort_values("time").iloc[-1]
                row["last_close"] = safe_float(last.get("close"))
                row["ret_3m_pct"] = pct_return(hist, 63)
                row["ret_6m_pct"] = pct_return(hist, 126)
                row["ret_12m_pct"] = pct_return(hist, 252)
                row["bars"] = len(hist)
            rows.append(row)
        except Exception as e:
            errors.append({"symbol": symbol, "error": str(e)})

    df = pd.DataFrame(rows)
    ts = timestamp()
    csv_path = outdir / f"market_data_{ts}.csv"
    json_path = outdir / f"market_data_{ts}.json"

    df.to_csv(csv_path, index=False)
    write_json(
        json_path,
        {
            "generated_at": ts,
            "source": args.source,
            "rows": df.to_dict(orient="records"),
            "errors": errors,
        },
    )

    (outdir / "market_data_latest.csv").write_text(str(csv_path), encoding="utf-8")
    (outdir / "market_data_latest.json").write_text(str(json_path), encoding="utf-8")

    print(f"Saved: {csv_path}")
    print(f"Saved: {json_path}")


if __name__ == "__main__":
    main()


path: scripts/common.py
content: #!/usr/bin/env python3
from __future__ import annotations

import json
import math
import os
import time
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from typing import Dict, List, Optional


@dataclass
class RateLimiter:
    min_interval_sec: float = 3.2
    last_call_ts: float = 0.0

    def wait(self) -> None:
        now = time.time()
        elapsed = now - self.last_call_ts
        if elapsed < self.min_interval_sec:
            time.sleep(self.min_interval_sec - elapsed)
        self.last_call_ts = time.time()


def safe_float(v) -> Optional[float]:
    try:
        if v is None:
            return None
        if isinstance(v, str):
            v = v.replace(",", "").replace("%", "").strip()
        f = float(v)
        if math.isfinite(f):
            return f
    except Exception:
        return None
    return None


def find_metric(df, candidates: List[str]) -> Optional[float]:
    if df is None or df.empty:
        return None

    local = df.copy()
    local.columns = [str(c).strip().lower() for c in local.columns]

    for c in local.columns:
        for k in candidates:
            if k in c:
                v = local.iloc[0][c]
                fv = safe_float(v)
                if fv is not None:
                    return fv

    item_cols = [c for c in local.columns if c in {"item", "item_id", "item_en"}]
    period_cols = [
        c
        for c in local.columns
        if c not in {"item", "item_id", "item_en", "unit", "levels", "row_number"}
    ]
    if item_cols and period_cols:
        id_col = item_cols[0]
        latest_col = sorted(period_cols, reverse=True)[0]
        for _, row in local.iterrows():
            name = str(row.get(id_col, "")).lower()
            if any(k in name for k in candidates):
                fv = safe_float(row.get(latest_col))
                if fv is not None:
                    return fv
    return None


def pct_return(history, days: int) -> Optional[float]:
    if history is None or history.empty or "close" not in history.columns:
        return None
    h = history.sort_values("time").copy()
    if len(h) < days + 1:
        return None
    start_price = safe_float(h.iloc[-days - 1]["close"])
    end_price = safe_float(h.iloc[-1]["close"])
    if start_price is None or end_price is None or start_price == 0:
        return None
    return (end_price / start_price - 1.0) * 100.0


def zscore(series, reverse: bool = False):
    clean = series.astype(float)
    std = clean.std(ddof=0)
    if std == 0 or str(std) == "nan":
        z = clean * 0
    else:
        z = (clean - clean.mean()) / std
    return -z if reverse else z


def ensure_outdir(outdir: str) -> Path:
    p = Path(outdir)
    p.mkdir(parents=True, exist_ok=True)
    return p


def timestamp() -> str:
    return datetime.now().strftime("%Y%m%d_%H%M%S")


def write_latest_alias(path: Path, alias_name: str) -> None:
    alias = path.parent / alias_name
    alias.write_text(str(path), encoding="utf-8")


def write_json(path: Path, payload: Dict) -> None:
    path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")


def _read_env_file(path: Path) -> Dict[str, str]:
    out: Dict[str, str] = {}
    if not path.exists():
        return out
    for line in path.read_text(encoding="utf-8").splitlines():
        s = line.strip()
        if not s or s.startswith("#") or "=" not in s:
            continue
        k, v = s.split("=", 1)
        out[k.strip()] = v.strip().strip("'").strip('"')
    return out


def resolve_vnstock_api_key(explicit_key: Optional[str] = None) -> Optional[str]:
    if explicit_key:
        return explicit_key.strip()

    env_key = os.getenv("VNSTOCK_API_KEY")
    if env_key:
        return env_key.strip()

    # Skill-local fallback: <skill_root>/.env
    # scripts/common.py -> skill root is parents[1]
    env_path = Path(__file__).resolve().parents[1] / ".env"
    values = _read_env_file(env_path)
    file_key = values.get("VNSTOCK_API_KEY", "").strip()
    return file_key or None


def configure_vnstock_api_key(explicit_key: Optional[str] = None) -> bool:
    key = resolve_vnstock_api_key(explicit_key)
    if not key:
        return False

    try:
        from vnstock.core.utils.auth import change_api_key

        change_api_key(key)
        return True
    except Exception:
        # Best-effort: continue even if auth helper is unavailable in this runtime.
        return False


path: scripts/generate_report.py
content: #!/usr/bin/env python3
from __future__ import annotations

import argparse
from pathlib import Path

try:
    import pandas as pd
except ModuleNotFoundError:
    pd = None

from common import ensure_outdir, timestamp


def label_bucket(score: float) -> str:
    if score >= 0.8:
        return "High Conviction"
    if score >= 0.2:
        return "Watchlist"
    return "Caution"


def main() -> None:
    parser = argparse.ArgumentParser(description="Generate markdown investment memo from ranking CSV")
    parser.add_argument("--ranking-file", required=True, help="Ranking CSV path")
    parser.add_argument("--outdir", default="./outputs", help="Output directory")
    parser.add_argument("--top-n", type=int, default=10, help="Top candidates to show")
    args = parser.parse_args()

    if pd is None:
        raise ModuleNotFoundError("Missing dependency: pandas. Install with `pip install pandas`.")

    outdir = ensure_outdir(args.outdir)
    df = pd.read_csv(args.ranking_file)
    if df.empty:
        raise RuntimeError("Ranking file is empty.")

    df["bucket"] = df["total_score"].astype(float).apply(label_bucket)
    top = df.head(args.top_n)

    ts = timestamp()
    md_path = outdir / f"investment_memo_{ts}.md"

    lines = [
        f"# Investment Memo ({ts})",
        "",
        "## Top Ranked Candidates",
        top[["symbol", "total_score", "bucket", "value_score", "quality_score", "momentum_score", "pe", "pb", "roe", "ret_12m_pct"]].to_markdown(index=False),
        "",
        "## Notes",
        "- Scores are relative within the analyzed universe.",
        "- Treat this as a quantitative shortlist, not a final buy/sell recommendation.",
        "- Validate each candidate with recent news, liquidity, and risk events.",
        "",
        "## Risk Checklist",
        "- Data completeness risk (missing metrics, stale financials).",
        "- Macro/event risk (policy, rates, FX, geopolitics).",
        "- Concentration risk (avoid over-weighting one sector).",
    ]

    md_path.write_text("\n".join(lines), encoding="utf-8")
    (outdir / "investment_memo_latest.md").write_text(str(md_path), encoding="utf-8")
    print(f"Saved: {md_path}")


if __name__ == "__main__":
    main()


path: scripts/invoke_vnstock.py
content: #!/usr/bin/env python3
from __future__ import annotations

import argparse
import importlib
import json
from pathlib import Path

from common import RateLimiter, configure_vnstock_api_key, ensure_outdir, timestamp, write_json


def parse_json_arg(raw: str, name: str):
    try:
        parsed = json.loads(raw)
        if not isinstance(parsed, dict):
            raise ValueError(f"{name} must be a JSON object")
        return parsed
    except json.JSONDecodeError as e:
        raise ValueError(f"Invalid JSON for {name}: {e}") from e


def parse_json_list_arg(raw: str, name: str):
    try:
        parsed = json.loads(raw)
        if not isinstance(parsed, list):
            raise ValueError(f"{name} must be a JSON array")
        return parsed
    except json.JSONDecodeError as e:
        raise ValueError(f"Invalid JSON for {name}: {e}") from e


def to_serializable(result):
    # pandas DataFrame / Series
    try:
        import pandas as pd

        if isinstance(result, pd.DataFrame):
            return {
                "type": "dataframe",
                "shape": list(result.shape),
                "columns": [str(c) for c in result.columns],
                "records": result.to_dict(orient="records"),
            }
        if isinstance(result, pd.Series):
            return {"type": "series", "name": str(result.name), "values": result.to_dict()}
    except Exception:
        pass

    if isinstance(result, (dict, list, str, int, float, bool)) or result is None:
        return {"type": "native", "value": result}

    # fallback repr
    return {"type": "repr", "value": repr(result)}


def main() -> None:
    parser = argparse.ArgumentParser(description="Invoke arbitrary vnstock class + method with JSON kwargs")
    parser.add_argument("--module", default="vnstock", help="Module path containing class, default vnstock")
    parser.add_argument("--class-name", required=True, help="vnstock exported class name, e.g. Quote")
    parser.add_argument("--init-kwargs", default="{}", help="JSON object for class initialization kwargs")
    parser.add_argument("--method", required=True, help="Method name to call")
    parser.add_argument("--method-args", default="[]", help="JSON array for positional method args")
    parser.add_argument("--method-kwargs", default="{}", help="JSON object for method kwargs")
    parser.add_argument("--outdir", default="./outputs", help="Output directory")
    parser.add_argument("--min-interval-sec", type=float, default=3.2, help="Optional pacing for free-tier safety")
    parser.add_argument("--api-key", default="", help="Optional VNStock API key override")
    args = parser.parse_args()

    init_kwargs = parse_json_arg(args.init_kwargs, "init-kwargs")
    method_args = parse_json_list_arg(args.method_args, "method-args")
    method_kwargs = parse_json_arg(args.method_kwargs, "method-kwargs")
    configure_vnstock_api_key(args.api_key or None)

    mod = importlib.import_module(args.module)

    if not hasattr(mod, args.class_name):
        raise AttributeError(f"Class not found in module {args.module}: {args.class_name}")

    cls = getattr(mod, args.class_name)
    client = cls(**init_kwargs)

    if not hasattr(client, args.method):
        raise AttributeError(f"Method not found on {args.class_name}: {args.method}")

    method = getattr(client, args.method)

    limiter = RateLimiter(min_interval_sec=args.min_interval_sec)
    limiter.wait()

    result = method(*method_args, **method_kwargs)
    serial = to_serializable(result)

    outdir = ensure_outdir(args.outdir)
    ts = timestamp()
    json_path = Path(outdir) / f"invoke_{args.class_name}_{args.method}_{ts}.json"
    md_path = Path(outdir) / f"invoke_{args.class_name}_{args.method}_{ts}.md"

    payload = {
        "generated_at": ts,
        "module": args.module,
        "class_name": args.class_name,
        "init_kwargs": init_kwargs,
        "method": args.method,
        "method_args": method_args,
        "method_kwargs": method_kwargs,
        "result": serial,
    }
    write_json(json_path, payload)

    lines = [
        f"# VNStock Invocation Result ({ts})",
        "",
        f"- Class: `{args.class_name}`",
        f"- Method: `{args.method}`",
        f"- Module: `{args.module}`",
        f"- Init kwargs: `{json.dumps(init_kwargs, ensure_ascii=False)}`",
        f"- Method args: `{json.dumps(method_args, ensure_ascii=False)}`",
        f"- Method kwargs: `{json.dumps(method_kwargs, ensure_ascii=False)}`",
        f"- Result type: `{serial.get('type')}`",
    ]

    if serial.get("type") == "dataframe":
        lines.append(f"- Shape: `{serial.get('shape')}`")
        cols = serial.get("columns", [])[:20]
        lines.append(f"- Columns sample: `{cols}`")
    md_path.write_text("\n".join(lines), encoding="utf-8")

    (Path(outdir) / "invoke_latest.json").write_text(str(json_path), encoding="utf-8")
    (Path(outdir) / "invoke_latest.md").write_text(str(md_path), encoding="utf-8")

    print(f"Saved: {json_path}")
    print(f"Saved: {md_path}")


if __name__ == "__main__":
    main()


path: scripts/run_pipeline.py
content: #!/usr/bin/env python3
from __future__ import annotations

import argparse
import subprocess
import sys
from pathlib import Path


def run(cmd):
    print("Running:", " ".join(cmd))
    subprocess.run(cmd, check=True)


def main() -> None:
    parser = argparse.ArgumentParser(description="Run full vnstock free-tier valuation pipeline")
    parser.add_argument("--source", default="kbs", choices=["kbs", "vci"], help="Data source")
    parser.add_argument("--outdir", default="./outputs", help="Output directory")
    parser.add_argument("--mode", default="group", choices=["group", "exchange", "symbols"], help="Universe mode")
    parser.add_argument("--group", default="VN30", help="Group for mode=group")
    parser.add_argument("--exchange", default="HOSE", help="Exchange for mode=exchange")
    parser.add_argument("--symbols", default="", help="Comma-separated symbols for mode=symbols")
    parser.add_argument("--api-key", default="", help="Optional VNStock API key override")
    args = parser.parse_args()

    base = Path(__file__).resolve().parent

    run([
        sys.executable,
        str(base / "build_universe.py"),
        "--source", args.source,
        "--mode", args.mode,
        "--group", args.group,
        "--exchange", args.exchange,
        "--symbols", args.symbols,
        "--outdir", args.outdir,
        "--api-key", args.api_key,
    ])

    universe_latest = str(Path(args.outdir) / "universe_latest.csv")
    universe_path = Path(universe_latest).read_text(encoding="utf-8").strip()

    run([
        sys.executable,
        str(base / "collect_market_data.py"),
        "--source", args.source,
        "--universe-file", universe_path,
        "--outdir", args.outdir,
        "--api-key", args.api_key,
    ])

    run([
        sys.executable,
        str(base / "collect_fundamentals.py"),
        "--source", args.source,
        "--universe-file", universe_path,
        "--outdir", args.outdir,
        "--api-key", args.api_key,
    ])

    market_path = Path(args.outdir, "market_data_latest.csv").read_text(encoding="utf-8").strip()
    fundamentals_path = Path(args.outdir, "fundamentals_latest.csv").read_text(encoding="utf-8").strip()

    run([
        sys.executable,
        str(base / "score_stocks.py"),
        "--market-file", market_path,
        "--fundamentals-file", fundamentals_path,
        "--outdir", args.outdir,
    ])

    ranking_path = Path(args.outdir, "ranking_latest.csv").read_text(encoding="utf-8").strip()

    run([
        sys.executable,
        str(base / "generate_report.py"),
        "--ranking-file", ranking_path,
        "--outdir", args.outdir,
    ])


if __name__ == "__main__":
    main()


path: scripts/score_stocks.py
content: #!/usr/bin/env python3
from __future__ import annotations

import argparse

try:
    import pandas as pd
except ModuleNotFoundError:
    pd = None

from common import ensure_outdir, timestamp, write_json, zscore


def main() -> None:
    parser = argparse.ArgumentParser(description="Score and rank stocks from market + fundamentals inputs")
    parser.add_argument("--market-file", required=True, help="market_data CSV path")
    parser.add_argument("--fundamentals-file", required=True, help="fundamentals CSV path")
    parser.add_argument("--outdir", default="./outputs", help="Output directory")
    args = parser.parse_args()

    if pd is None:
        raise ModuleNotFoundError("Missing dependency: pandas. Install with `pip install pandas`.")

    outdir = ensure_outdir(args.outdir)
    mdf = pd.read_csv(args.market_file)
    fdf = pd.read_csv(args.fundamentals_file)

    df = mdf.merge(fdf, on=["symbol", "source"], how="inner")
    if df.empty:
        raise RuntimeError("Merged dataset is empty. Check input files and symbols.")

    for col in ["pe", "pb", "roe", "debt_equity", "ret_6m_pct", "ret_12m_pct"]:
        if col not in df.columns:
            df[col] = pd.NA

    df["value_score"] = (
        zscore(df["pe"].fillna(df["pe"].median()), reverse=True)
        + zscore(df["pb"].fillna(df["pb"].median()), reverse=True)
    ) / 2

    df["quality_score"] = (
        zscore(df["roe"].fillna(df["roe"].median()), reverse=False)
        + zscore(df["debt_equity"].fillna(df["debt_equity"].median()), reverse=True)
    ) / 2

    df["momentum_score"] = (
        zscore(df["ret_6m_pct"].fillna(df["ret_6m_pct"].median()), reverse=False)
        + zscore(df["ret_12m_pct"].fillna(df["ret_12m_pct"].median()), reverse=False)
    ) / 2

    df["total_score"] = 0.35 * df["value_score"] + 0.35 * df["quality_score"] + 0.30 * df["momentum_score"]
    df = df.sort_values("total_score", ascending=False).reset_index(drop=True)

    ts = timestamp()
    csv_path = outdir / f"ranking_{ts}.csv"
    json_path = outdir / f"ranking_{ts}.json"

    df.to_csv(csv_path, index=False)
    write_json(
        json_path,
        {
            "generated_at": ts,
            "rows": df.to_dict(orient="records"),
        },
    )

    (outdir / "ranking_latest.csv").write_text(str(csv_path), encoding="utf-8")
    (outdir / "ranking_latest.json").write_text(str(json_path), encoding="utf-8")

    print(f"Saved: {csv_path}")
    print(f"Saved: {json_path}")


if __name__ == "__main__":
    main()


Security scan verdicts — ClawScan: suspicious | static analysis: clean | VirusTotal:  | SkillSpector: suspicious (CRITICAL)