Metadata-Version: 2.4
Name: ssi-sdk
Version: 3.0.2
Summary: Python SDK for SSI trading platform
Author: SSI Team
License-Expression: MIT
Project-URL: Homepage, https://developers.ssi.com.vn
Project-URL: Repository, https://github.com/orgs/SSI-Securities-Inc
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Financial and Insurance Industry
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Office/Business :: Financial :: Investment
Classifier: Typing :: Typed
Requires-Python: >=3.10
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.27.0
Requires-Dist: websockets>=13.0
Requires-Dist: websocket-client>=1.7.0
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
Requires-Dist: ruff>=0.8; extra == "dev"

# SSI Python SDK

Python SDK cho nền tảng giao dịch chứng khoán SSI. Hỗ trợ REST API và WebSocket streaming, cả đồng bộ (sync) và bất đồng bộ (async).

**Yêu cầu:** Python 3.10+

## Mục lục

- [Cài đặt](#cài-đặt)
- [Cấu hình](#cấu-hình)
- [Khởi tạo Client](#khởi-tạo-client)
- [Xác thực](#1-xác-thực)
- [Tài khoản](#2-tài-khoản)
- [Dữ liệu thị trường](#3-dữ-liệu-thị-trường)
- [Danh mục đầu tư](#4-danh-mục-đầu-tư)
- [Giao dịch](#5-giao-dịch)
- [Streaming realtime](#6-streaming-realtime)
- [Xử lý lỗi](#7-xử-lý-lỗi)
- [Cấu hình nâng cao](#8-cấu-hình-nâng-cao)

---

## Cài đặt

```bash
pip install ssi-sdk
```

---

## Cấu hình

Tạo đối tượng `Config` với thông tin xác thực:

```python
from ssi_sdk import Config

config = Config(
    client_id="YOUR_CLIENT_ID",
    api_key="YOUR_API_KEY",
    api_secret="YOUR_API_SECRET",
    private_key="YOUR_PRIVATE_KEY",
)
```

**Tất cả tuỳ chọn cấu hình:**

| Tham số | Kiểu | Mặc định | Mô tả |
|---------|------|----------|-------|
| `client_id` | `str` | *(bắt buộc)* | Client ID xác thực |
| `api_key` | `str` | `""` | API key từ SSI |
| `api_secret` | `str` | `""` | API secret từ SSI |
| `private_key` | `str` | `""` | Private key cho ký lệnh giao dịch |
| `api_url` | `str` | `"https://api.ssi.com.vn"` | URL REST API |
| `streaming_url` | `str` | `"wss://api.ssi.com.vn/ws/v3"` | URL WebSocket streaming |
| `timeout` | `int` | `60` | Timeout request (giây) |
| `max_retries` | `int` | `5` | Số lần retry tối đa |
| `retry_delay` | `float` | `2.0` | Delay cơ sở giữa các lần retry (exponential backoff, giây) |
| `rate_limit_per_second` | `int` | `10` | Giới hạn request/giây (0 = không giới hạn) |
| `log_level` | `str` | `"INFO"` | Mức log: DEBUG, INFO, WARNING, ERROR, CRITICAL |

---

## Khởi tạo Client

SDK cung cấp 2 client: **`SSIClient`** (đồng bộ) và **`AsyncSSIClient`** (bất đồng bộ). Cả hai có cùng API.

### Async Client (khuyến nghị)

```python
import asyncio
from ssi_sdk import AsyncSSIClient, Config

async def main():
    config = Config(
        client_id="YOUR_CLIENT_ID",
        api_key="YOUR_API_KEY",
        api_secret="YOUR_API_SECRET",
        private_key="YOUR_PRIVATE_KEY",
    )

    async with AsyncSSIClient(config) as client:
        # Xác thực
        await client.authenticate(otp="222222")

        # Kết nối WebSocket (cần cho streaming)
        await client.connect()

        # Sử dụng các service...
        accounts = await client.account.get_account_info()
        print(accounts)

asyncio.run(main())
```

### Sync Client

```python
from ssi_sdk import SSIClient, Config

config = Config(
    client_id="YOUR_CLIENT_ID",
    api_key="YOUR_API_KEY",
    api_secret="YOUR_API_SECRET",
    private_key="YOUR_PRIVATE_KEY",
)

with SSIClient(config) as client:
    client.authenticate(otp="222222")
    accounts = client.account.get_account_info()
    print(accounts)
```

### Chỉ dùng REST (không cần WebSocket)

Nếu không cần streaming, chỉ cần gọi `authenticate()` mà không cần `connect()`:

```python
client = SSIClient(config)
client.authenticate(otp="222222")
# Sử dụng market_data, account, portfolio, trading bình thường
client.disconnect()
```

### Truyền config bằng keyword arguments

```python
client = SSIClient(
    client_id="YOUR_CLIENT_ID",
    api_key="YOUR_API_KEY",
    api_secret="YOUR_API_SECRET",
    private_key="YOUR_PRIVATE_KEY",
    timeout=60,
    log_level="DEBUG",
)
```

---

## 1. Xác thực

Truy cập qua `client.token_manager` hoặc dùng `client.authenticate()`.

### 1.1. Xác thực với OTP

```python
# Cách nhanh: dùng client.authenticate()
client.authenticate(otp="222222")

# Hoặc dùng token_manager trực tiếp
token = client.token_manager.authenticate(otp="222222")
print(f"Access token: {token.access_token}")
print(f"Expires at: {token.expires_at}")
```

### 1.2. Yêu cầu gửi OTP

```python
result = client.token_manager.request_otp()
print(result)
```

### 1.3. Làm mới token

```python
token = client.token_manager.refresh()
```

### 1.4. Tự động đảm bảo xác thực

```python
# Tự động refresh nếu token hết hạn, hoặc yêu cầu OTP nếu cần
access_token = client.token_manager.ensure_authenticated(otp="222222")
```

---

## 2. Tài khoản

Truy cập qua `client.account`.

### 2.1. Lấy danh sách tài khoản

```python
accounts = client.account.get_account_info()

for acc in accounts:
    print(f"{acc.account_no} - {acc.customer_name} ({acc.accounts})")
```

**Trả về:** `list[Account]`

---

## 3. Dữ liệu thị trường

Truy cập qua `client.market_data`.

### 3.1. Dữ liệu OHLC (nến)

SDK cung cấp các method OHLC theo từng timeframe:

**Lấy dữ liệu trong ngày (không cần truyền ngày):**

| Method | Timeframe |
|--------|-----------|
| `get_ohlc_1minute(symbol)` | 1 phút |
| `get_ohlc_3minute(symbol)` | 3 phút |
| `get_ohlc_5minute(symbol)` | 5 phút |
| `get_ohlc_15minute(symbol)` | 15 phút |
| `get_ohlc_1hour(symbol)` | 1 giờ |

```python
# OHLC 1 phút trong ngày
ohlc = client.market_data.get_ohlc_1minute("SSI")
for candle in ohlc:
    print(f"{candle.trading_date}: O={candle.open} H={candle.high} L={candle.low} C={candle.close} V={candle.volume}")
```

**Lấy dữ liệu lịch sử (truyền khoảng ngày + phân trang):**

| Method | Timeframe |
|--------|-----------|
| `get_ohlc_1minute_historical(symbol, from_date, to_date, page, size)` | 1 phút |
| `get_ohlc_3minute_historical(symbol, from_date, to_date, page, size)` | 3 phút |
| `get_ohlc_5minute_historical(symbol, from_date, to_date, page, size)` | 5 phút |
| `get_ohlc_15minute_historical(symbol, from_date, to_date, page, size)` | 15 phút |
| `get_ohlc_1hour_historical(symbol, from_date, to_date, page, size)` | 1 giờ |
| `get_ohlc_1day_historical(symbol, from_date, to_date, page, size)` | 1 ngày |
| `get_ohlc_1week_historical(symbol, from_date, to_date, page, size)` | 1 tuần |
| `get_ohlc_1month_historical(symbol, from_date, to_date, page, size)` | 1 tháng |

```python
# OHLC 1 ngày lịch sử
ohlc = client.market_data.get_ohlc_1day_historical(
    symbol="SSI",
    from_date="2026/03/27 00:00:00",
    to_date="2026/03/27 00:00:00",
    page=1,
    size=100,
)
```

**Tham số historical:**

| Tham số | Kiểu | Mặc định | Mô tả |
|---------|------|----------|-------|
| `symbol` | `str` | *(bắt buộc)* | Mã chứng khoán |
| `from_date` | `str` | *(bắt buộc)* | Ngày bắt đầu |
| `to_date` | `str` | *(bắt buộc)* | Ngày kết thúc |
| `page` | `int` | `1` | Trang |
| `size` | `int` | `1000` | Số bản ghi/trang |

### 3.2. Danh sách chỉ số thị trường

```python
# Lấy tất cả chỉ số
indices = client.market_data.get_indexes()

# Lấy theo sàn
from ssi_sdk.enums import Board
indices = client.market_data.get_indexes_by_board(Board.HOSE)

for idx in indices:
    print(f"{idx.index_code} - {idx.index_name}")
```

### 3.3. Tổng hợp chỉ số (Index Summary)

```python
# Summary hiện tại
summary = client.market_data.get_index_summary("VNINDEX")
print(f"{summary.index_value} ({summary.change:+.2f})")

# Summary lịch sử
summary = client.market_data.get_index_summary_historical("VNINDEX", "2025/01/15")

# Summary theo sàn
summary = client.market_data.get_board_summary(Board.HOSE)

# Summary sàn lịch sử
summary = client.market_data.get_board_summary_historical(Board.HOSE, "2025/01/15")
```

### 3.4. Thông tin chứng khoán

```python
# Lấy thông tin 1 mã
info = client.market_data.get_securities_info("SSI")
print(f"{info.symbol} ({info.market}): Trần={info.ceiling} Sàn={info.floor} TC={info.ref_price}")

# Lấy theo chỉ số
securities = client.market_data.get_securities_info_by_index("VN30")

# Lấy theo sàn
securities = client.market_data.get_securities_info_by_board(Board.HOSE)
```

---

## 4. Danh mục đầu tư

Truy cập qua `client.portfolio`.

### 4.1. Số dư tài khoản

```python
# Số dư tài khoản cơ sở
equity_balance = client.portfolio.get_equity_balance("1234561")
print(f"Cash: {equity_balance.cash}")

# Số dư tài khoản phái sinh
derivative_balance = client.portfolio.get_derivative_balance("1234568")
```

### 4.2. Vị thế (Positions)

```python
# Vị thế cơ sở
positions = client.portfolio.get_equity_positions("1234561")
for pos in positions:
    print(f"{pos.symbol}: {pos.total} cp | Giá TB: {pos.avg_price}")

# Vị thế phái sinh
derivative_positions = client.portfolio.get_derivative_positions("1234568")

# Chỉ vị thế mở phái sinh
open_pos = client.portfolio.get_open_derivative_positions("1234568")

# Chỉ vị thế đóng phái sinh
closed_pos = client.portfolio.get_closed_derivative_positions("1234568")
```

### 4.3. Sổ lệnh (Order Book)

```python
# Lệnh trong ngày
today_orders = client.portfolio.get_today_orders("1234561")
for order in today_orders:
    print(order)

# Lệnh lịch sử
historical_orders = client.portfolio.get_historical_orders(
    "1234561",
    from_date="2025/01/01",
    to_date="2025/01/31",
)
```

### 4.4. PPMMR (Purchasing Power / Margin Maintenance Ratio)

```python
# PPMMR cơ sở
equity_ppmmr = client.portfolio.get_equity_ppmmr("1234561")

# PPMMR phái sinh
derivative_ppmmr = client.portfolio.get_derivative_ppmmr("1234568")
```

---

## 5. Giao dịch

Truy cập qua `client.trading`.

### 5.1. Đặt lệnh

```python
from ssi_sdk.enums import OrderSide, OrderType

# Lệnh giới hạn (LO)
result = client.trading.place_limit_order(
    account_no="1234561",
    symbol="SSI",
    side=OrderSide.BUY,
    quantity=100,
    price=66000,
)
print(f"Order: {result}")

# Lệnh thị trường (MTL)
result = client.trading.place_market_order(
    account_no="1234561",
    symbol="SSI",
    side=OrderSide.BUY,
    quantity=100,
)

# Lệnh ATO (mở cửa)
result = client.trading.place_ato_order(
    account_no="1234561",
    symbol="SSI",
    side=OrderSide.BUY,
    quantity=100,
)

# Lệnh ATC (đóng cửa)
result = client.trading.place_atc_order(
    account_no="1234561",
    symbol="SSI",
    side=OrderSide.SELL,
    quantity=100,
)

# Lệnh tuỳ chỉnh (chọn order_type)
result = client.trading.place_order(
    account_no="1234561",
    symbol="SSI",
    side=OrderSide.BUY,
    quantity=100,
    price=66000,
    order_type=OrderType.LO,
)
```

**Loại lệnh (`OrderType`):**

| Giá trị | Mô tả |
|---------|-------|
| `OrderType.LO` | Lệnh giới hạn (Limit Order) |
| `OrderType.MTL` | Lệnh thị trường (Market To Limit) |
| `OrderType.MP` | Lệnh thị trường (Market Price) |
| `OrderType.ATO` | Lệnh mở cửa (At The Open) |
| `OrderType.ATC` | Lệnh đóng cửa (At The Close) |
| `OrderType.MOK` | Match Or Kill |
| `OrderType.MAK` | Match And Kill |
| `OrderType.PLO` | Post Lunch Order |

### 5.2. Sửa lệnh

Chỉ có thể sửa **giá** hoặc **số lượng** (không đồng thời).

```python
# Sửa giá theo client_request_id
result = client.trading.modify_order_price(
    account_no="1234561",
    client_request_id="REQ123",
    price=68000,
)

# Sửa giá theo order_id
result = client.trading.modify_order_price_by_order_id(
    account_no="1234561",
    order_id="ORD123",
    price=68000,
)

# Sửa số lượng theo client_request_id
result = client.trading.modify_order_quantity(
    account_no="1234561",
    client_request_id="REQ123",
    quantity=200,
)

# Sửa số lượng theo order_id
result = client.trading.modify_order_quantity_by_order_id(
    account_no="1234561",
    order_id="ORD123",
    quantity=200,
)
```

### 5.3. Huỷ lệnh

```python
# Huỷ theo client_request_id
result = client.trading.cancel_order(
    account_no="1234561",
    client_request_id="REQ123",
)

# Huỷ theo order_id
result = client.trading.cancel_order_by_order_id(
    account_no="1234561",
    order_id="ORD123",
)
```

### 5.4. Sức mua/bán tối đa

```python
# Với giá cụ thể
max_bs = client.trading.get_max_buy_sell(
    account_no="1234561",
    symbol="SSI",
    price=66000,
)
print(f"Max buy: {max_bs.max_buy_quantity}")
print(f"Max sell: {max_bs.max_sell_quantity}")

# Với giá thị trường
max_bs = client.trading.get_max_buy_sell_at_market_price(
    account_no="1234561",
    symbol="SSI",
)
```

---

## 6. Streaming realtime

Truy cập qua `client.streaming`. Cần gọi `client.connect()` trước.

### 6.1. Thiết lập callback

```python
# Callback nhận dữ liệu thị trường
def on_market_data(msg):
    print(f"Market data: {msg}")

# Callback nhận dữ liệu giao dịch (order status, portfolio)
def on_trading_data(msg):
    print(f"Trading: {msg}")

client.streaming.on_data = on_market_data
client.streaming.on_trading = on_trading_data
```

**Các message type nhận được qua `on_data`:**

| Topic | Message Type | Mô tả |
|-------|-------------|-------|
| `trade.*` | `TradeMessage` | Dữ liệu khớp lệnh |
| `quote.*` | `QuoteMessage` | Dữ liệu giá (bid/ask) |
| `room.*` | `ForeignRoomMessage` | Room nước ngoài |
| `market.*` | `MarketStatusMessage` | Trạng thái thị trường |
| `put.*` | `PutMessage` | Thoả thuận |
| `oddlot.*` | `OddLotMessage` | Lô lẻ |

**Các message type nhận được qua `on_trading`:**

| Topic | Message Type | Mô tả |
|-------|-------------|-------|
| `order.*` | `OrderStatusMessage` | Trạng thái lệnh |
| `portfolio.*` | `PortfolioMessage` | Thay đổi danh mục |

### 6.2. Subscribe dữ liệu thị trường

```python
# Subscribe tất cả kênh (trade + quote + room) cho mã
client.streaming.subscribe_symbol(["SSI", "HPG", "VIC"])

# Subscribe từng kênh riêng
client.streaming.subscribe_symbol_trade(["SSI", "HPG"])
client.streaming.subscribe_symbol_quote(["SSI", "HPG"])
client.streaming.subscribe_symbol_room(["SSI", "HPG"])
client.streaming.subscribe_symbol_put_through(["SSI"])
client.streaming.subscribe_symbol_odd_lot(["SSI"])

# Subscribe theo sàn
from ssi_sdk.enums import Board
client.streaming.subscribe_board([Board.HOSE, Board.HNX])

# Subscribe theo chỉ số
client.streaming.subscribe_index(["VNINDEX", "VN30"])
```

### 6.3. Unsubscribe

```python
client.streaming.unsubscribe_symbol(["SSI", "HPG"])
client.streaming.unsubscribe_symbol_trade(["SSI"])
client.streaming.unsubscribe_symbol_quote(["SSI"])
client.streaming.unsubscribe_symbol_room(["SSI"])
client.streaming.unsubscribe_symbol_put_through(["SSI"])
client.streaming.unsubscribe_symbol_odd_lot(["SSI"])
client.streaming.unsubscribe_board([Board.HOSE])
client.streaming.unsubscribe_index(["VNINDEX"])
```

### 6.4. Subscribe giao dịch (order status & portfolio)

```python
# Subscribe trạng thái lệnh (tất cả tài khoản)
client.streaming.subscribe_order_status()

# Subscribe cho tài khoản cụ thể
client.streaming.subscribe_order_status(account_no="1234561")

# Subscribe portfolio
client.streaming.subscribe_portfolio()
client.streaming.subscribe_portfolio(account_no="1234561")
```

### 6.5. Heartbeat

```python
client.streaming.ping()
```

### 6.6. Ví dụ streaming hoàn chỉnh (async)

```python
import asyncio
from ssi_sdk import AsyncSSIClient, Config
from ssi_sdk.enums import Board

async def main():
    config = Config(
        client_id="YOUR_CLIENT_ID",
        api_key="YOUR_API_KEY",
        api_secret="YOUR_API_SECRET",
        private_key="YOUR_PRIVATE_KEY",
    )

    async with AsyncSSIClient(config) as client:
        await client.authenticate(otp="222222")
        await client.connect()

        # Đăng ký callback
        client.streaming.on_data = lambda msg: print(f"Data: {msg}")
        client.streaming.on_trading = lambda msg: print(f"Trading: {msg}")

        # Subscribe
        await client.streaming.subscribe_symbol(["SSI", "HPG"])
        await client.streaming.subscribe_order_status()

        # Chờ nhận dữ liệu
        await client.streaming.wait()

asyncio.run(main())
```

---

## 7. Xử lý lỗi

SDK sử dụng hệ thống exception phân cấp:

```python
from ssi_sdk import SSIError

try:
    client.authenticate(otp="wrong_otp")
except SSIError as e:
    print(f"Lỗi: {e.message} (code: {e.code})")
```

**Các exception:**

| Exception | Mô tả |
|-----------|-------|
| `SSIError` | Base exception cho tất cả lỗi SDK |
| `AuthenticationError` | Xác thực thất bại (sai credentials, token hết hạn) |
| `APIError` | API trả về lỗi — có thêm `status_code`, `response_body` |
| `WebSocketError` | Lỗi kết nối hoặc giao tiếp WebSocket |
| `ValidationError` | Lỗi validate input |
| `RateLimitError` | Vượt quá giới hạn request — có thêm `retry_after` |

```python
from ssi_sdk.exceptions import APIError, AuthenticationError, RateLimitError

try:
    result = client.trading.place_limit_order(
        account_no="1234561",
        symbol="SSI",
        side=OrderSide.BUY,
        quantity=100,
        price=68000,
    )
except AuthenticationError:
    print("Cần xác thực lại")
except RateLimitError as e:
    print(f"Rate limited, retry sau {e.retry_after}s")
except APIError as e:
    print(f"API error {e.status_code}: {e.message}")
```

### Debug logging

```python
config = Config(
    client_id="YOUR_CLIENT_ID",
    api_key="YOUR_API_KEY",
    api_secret="YOUR_API_SECRET",
    log_level="DEBUG",
)
```

### Tuỳ chỉnh retry & rate limit

```python
config = Config(
    client_id="YOUR_CLIENT_ID",
    api_key="YOUR_API_KEY",
    api_secret="YOUR_API_SECRET",
    max_retries=3,
    retry_delay=1.0,
    rate_limit_per_second=5,
)
```

---

## API Reference

### Enums

```python
from ssi_sdk.enums import (
    OrderSide,      # BUY, SELL
    OrderType,      # LO, MTL, MP, ATO, ATC, MOK, MAK, PLO
    OrderStatus,    # PENDING_APPROVAL, READY, SENT, QUEUED, FILLED, PARTIAL_FILLED,
                    # PARTIAL_CANCELLED, PENDING_MODIFY, PENDING_CANCEL,
                    # CANCELLED, REJECTED, EXPIRED, PRE_SESSION
    Board,          # HOSE, HNX, UPCOM
    Timeframe,      # MINUTE_1, MINUTE_3, MINUTE_5, MINUTE_15, HOUR_1,
                    # DAY_1, WEEK_1, MONTH_1
)
```

### Services

| Service | Truy cập | Mô tả |
|---------|---------|-------|
| `token_manager` | `client.token_manager` | Xác thực, OTP, refresh token |
| `account` | `client.account` | Thông tin tài khoản |
| `market_data` | `client.market_data` | OHLC, chỉ số, chứng khoán |
| `portfolio` | `client.portfolio` | Số dư, vị thế, sổ lệnh, PPMMR |
| `trading` | `client.trading` | Đặt/sửa/huỷ lệnh, sức mua/bán |
| `streaming` | `client.streaming` | Streaming realtime qua WebSocket |
