Hướng dẫn tích hợp stockprice.Dist
Hệ thống phân phối dữ liệu thị trường chứng khoán thời gian thực qua WebSocket pub/sub.
Tổng quan
stockprice.Dist là hệ thống phân phối dữ liệu thị trường theo thời gian thực dựa trên WebSocket. Hệ thống cung cấp chức năng xuất bản/đăng ký (publish/subscribe) hiệu suất cao theo topic để truyền tải dữ liệu thị trường tài chính bao gồm giá cổ phiếu, dữ liệu chỉ số và dữ liệu biểu đồ lịch sử.
Bắt đầu
Yêu cầu
- Tài khoản người dùng đang hoạt động với quyền tương ứng theo thỏa thuận dịch vụ
- Phiên xác thực hợp lệ (sử dụng action Login của AuthPlugin)
- Client hỗ trợ WebSocket (trình duyệt, Python, v.v.)
- Truy cập mạng đến endpoint dịch vụ stockprice.Dist
Endpoint WebSocket / HTTP
UAT:
wss://dist-uat.stockprice.vn/v1/ws?Id={sessionId}
https://dist-uat.stockprice.vn/v1/
Production:
wss://dist.stockprice.vn/v1/ws?Id={sessionId}
https://dist.stockprice.vn/v1/
Trong đó {sessionId} là ID phiên được lấy từ header Set-Id trong yêu cầu HTTP đầu tiên, sau đó gửi kèm header Id trong các yêu cầu tiếp theo.
Luồng kết nối
- Xác thực qua HTTP: Thực hiện yêu cầu HTTP để lấy session ID từ header
Set-Id - Kết nối WebSocket: Kết nối đến endpoint WebSocket với tham số
Id - Đăng nhập (nếu dùng endpoint local): Sử dụng action Login của AuthPlugin (alias:
l) - Đăng ký các nguồn dữ liệu mong muốn sử dụng action đăng ký
- Nhận dữ liệu qua tin nhắn WebSocket dưới dạng đối tượng JSON
- Duy trì kết nối — xem mục Keep-Alive bên dưới
- Hủy đăng ký khi không còn cần để giải phóng tài nguyên
Keep-Alive
Để duy trì kết nối hoạt động và ngăn chặn timeout phiên, triển khai cả hai cơ chế keep-alive sau (mỗi 60 giây):
HTTP Keep-Alive
Gửi yêu cầu POST đến https://dist.stockprice.vn/v1/P kèm header Id chứa session ID.
WebSocket Keep-Alive
Gửi action Ping qua kết nối WebSocket:
{ "A": "P", "I": request_id }
Ví dụ JavaScript
// HTTP Keep-Alive (mỗi 60 giây)
setInterval(() => {
fetch('https://dist.stockprice.vn/v1/P', {
method: 'POST',
headers: { 'Id': sessionId }
}).catch(err => console.error('HTTP keep-alive thất bại:', err));
}, 60000);
// WebSocket Keep-Alive (mỗi 60 giây)
setInterval(() => {
if (ws.readyState === WebSocket.OPEN) {
requestId++;
ws.send(JSON.stringify({ A: 'P', I: requestId }));
}
}, 60000);
Ví dụ Python
import threading, time, requests, json
def http_keep_alive(session_id):
while True:
try:
requests.post('https://dist.stockprice.vn/v1/P',
headers={'Id': session_id})
except Exception as e:
print(f'HTTP keep-alive thất bại: {e}')
time.sleep(60)
def websocket_keep_alive(ws):
global request_id
while True:
try:
request_id += 1
ws.send(json.dumps({'A': 'P', 'I': request_id}))
except Exception as e:
print(f'WebSocket keep-alive thất bại: {e}')
time.sleep(60)
threading.Thread(target=http_keep_alive, args=(session_id,), daemon=True).start()
threading.Thread(target=websocket_keep_alive, args=(ws,), daemon=True).start()
Lưu ý: Nếu một trong hai keep-alive thất bại, thử kết nối lại và khôi phục đăng ký. Thiếu tín hiệu keep-alive có thể dẫn đến timeout phiên.
Định dạng Request/Response
Yêu cầu Action (ActInf)
Tất cả các yêu cầu action sử dụng cấu trúc JSON:
{ "A": "action_alias", "P": "parameters", "I": request_id }
| Trường | Kiểu | Bắt buộc | Mô tả |
|---|---|---|---|
A | string | Có | Alias action (ví dụ: "ss", "sx", "sd") |
P | string | Không | Tham số (định dạng thay đổi theo action) |
I | number | Có (WS) | ID yêu cầu để khớp phản hồi. BẮT BUỘC cho WebSocket, không cần cho HTTP. |
Phản hồi Action (ActRsl)
{ "C": 0, "D": "data_or_message", "I": request_id }
| Trường | Kiểu | Mô tả |
|---|---|---|
C | int | Mã kết quả (0 = thành công, 1+ = lỗi) |
D | string | Dữ liệu hoặc thông báo lỗi |
I | number | ID yêu cầu trả lại từ yêu cầu gốc |
Tin nhắn dữ liệu (Data Message)
Dữ liệu được đẩy từ máy chủ dưới dạng:
{ "t": "topic_name", "d": "csv_data" }
Khi snapshot ban đầu hoàn thành, máy chủ gửi tín hiệu hoàn thành (chỉ có trường t, không có d):
{ "t": "topic_name" }
Mẫu bộ đếm yêu cầu
let requestId = 0;
function sendRequest(action, params) {
requestId++;
ws.send(JSON.stringify({ A: action, P: params, I: requestId }));
return requestId;
}
Bảo mật
Bảng tham khảo nhanh
| Action | Alias | Tham số | Mô tả |
|---|---|---|---|
| GetServerTime | 0 | Không | Lấy giờ trên máy chủ |
| GetRSAPublicKey | r | Không | Lấy public key RSA |
| Login | l | Username/mật khẩu đã mã hóa RSA | Đăng nhập vào máy chủ |
| Logout | o | Không | Đăng xuất khỏi phiên |
| ChangePassword | c | Mật khẩu cũ/mới được mã hóa RSA | Đổi mật khẩu |
GetServerTime
// Yêu cầu
{ "A": "0" }
// Phản hồi
{ "C": 0, "D": "1690207350" }
GetRSAPublicKey
// Yêu cầu
{ "A": "r" }
// Phản hồi
{ "C": 0, "D": "{\"Exponent\":\"AQAB\",\"Modulus\":\"xqBYwy...vQ==\"}" }
Login
Tham số đầu vào là chuỗi được mã hóa RSA với cấu trúc: random;username;password
// Yêu cầu
{ "A": "l", "P": "...chuỗi đã mã hóa RSA..." }
// Phản hồi thành công (D = thời gian hết hạn epoch)
{ "C": 0, "D": "1680702231" }
// Phản hồi lỗi
{ "C": 1, "D": "Invalid credentials" }
random là chuỗi ngẫu nhiên do client sinh ra. username và password là tài khoản đăng nhập.
Logout
// Yêu cầu
{ "A": "o" }
// Phản hồi
{ "C": 0, "D": null }
ChangePassword
Tham số đầu vào: chuỗi mã hóa RSA với cấu trúc random;old_password;new_password
// Yêu cầu
{ "A": "c", "P": "...chuỗi đã mã hóa RSA...", "S": null }
// Phản hồi
{ "C": 0, "D": null }
Đăng ký dữ liệu thời gian thực
SubscribeStockData (ss)
Đăng ký cập nhật giá cổ phiếu thời gian thực cho tất cả các cổ phiếu. Topic: stock_data
// Yêu cầu
{ "A": "ss", "I": 1 }
// Phản hồi
{ "C": 0, "D": "success", "I": 1 }
// Tin nhắn dữ liệu
{ "t": "stock_data", "d": "SSI,75.5,78.3,72.7,74.0,100,..." }
// Tín hiệu hoàn thành
{ "t": "stock_data" }
SubscribeIndexData (sx)
Đăng ký cập nhật dữ liệu chỉ số thời gian thực cho tất cả các chỉ số. Topic: index_data
// Yêu cầu
{ "A": "sx", "I": 2 }
// Phản hồi
{ "C": 0, "D": "success", "I": 2 }
// Tin nhắn dữ liệu
{ "t": "index_data", "d": "VNINDEX,1640995200,O,1200.5,1205.5,..." }
GetStockInfo — WebSocket (ssi)
Yêu cầu thông tin cổ phiếu (đẩy một lần, không phải đăng ký liên tục). Topic: stock_info
// Yêu cầu
{ "A": "ssi", "I": 3 }
// Phản hồi
{ "C": 0, "D": "success", "I": 3 }
// Tin nhắn dữ liệu
{ "t": "stock_info", "d": "SSI,HSX,STK,STX,SSI Securities Inc.,..." }
GetStockInfo — HTTP (ssi)
Lấy thông tin cổ phiếu cho các mã cụ thể qua HTTP (tối đa 50 mã).
POST https://dist.stockprice.vn/v1/S HTTP/1.1
Content-Type: application/json
Id: your-session-id
{ "A": "ssi", "P": "SSI,VNM,HDB" }
// Phản hồi thành công
{ "C": 0, "D": "SSI,HSX,STK,STX,...\nVNM,HSX,STK,STX,..." }
// Phản hồi lỗi
{ "C": 1, "D": "Maximum 50 symbols allowed" }
Đăng ký dữ liệu lịch sử
Định dạng tham số chung: mã1,lastKey1\nmã2,lastKey2\n...
Trong đó lastKey là Unix epoch timestamp (giây). Dùng 0 để lấy tất cả dữ liệu có sẵn.
SubscribeHistD (sd) — Dữ liệu theo ngày
Topics: D/{MÃ} (ví dụ: D/SSI, D/VNM)
// Yêu cầu
{ "A": "sd", "P": "SSI,1640995200\nVNM,0\nHDB,1641081600", "I": 4 }
// Phản hồi
{ "C": 0, "D": "success", "I": 4 }
// Tin nhắn dữ liệu (cho mỗi mã)
{ "t": "D/SSI", "d": "1640995200,1.0,75.5,76.0,77.0,74.5,5000000,..." }
// Tín hiệu hoàn thành (cho mỗi mã)
{ "t": "D/SSI" }
SubscribeHistM (sm) — Dữ liệu theo phút
Topics: M/{MÃ} (ví dụ: M/SSI, M/VNM)
// Yêu cầu
{ "A": "sm", "P": "SSI,1640995200\nVNM,0", "I": 5 }
// Phản hồi
{ "C": 0, "D": "success", "I": 5 }
// Tin nhắn dữ liệu
{ "t": "M/SSI", "d": "1640995200,75.5,76.0,75.0,75.8,10000,..." }
SubscribeTrade (st) — Dữ liệu giao dịch
Topics: T/{MÃ} (ví dụ: T/SSI, T/VNM). Lưu ý: Dữ liệu giao dịch KHÔNG có sẵn cho các mã chỉ số.
// Yêu cầu
{ "A": "st", "P": "SSI,1640995200\nVNM,0", "I": 6 }
// Phản hồi thành công
{ "C": 0, "D": "success", "I": 6 }
// Phản hồi lỗi (nếu bao gồm mã chỉ số)
{ "C": 1, "D": "Trade data not available for indices", "I": 6 }
// Tin nhắn dữ liệu
{ "t": "T/SSI", "d": "1640995200,75.5,1000,B\r\n1640995205,75.6,500,S" }
Hủy đăng ký
Tất cả các action hủy đăng ký không yêu cầu kiểm tra quyền.
| Action | Alias | Tham số | Mô tả |
|---|---|---|---|
| UnsubscribeStockData | us | Không | Hủy đăng ký giá cổ phiếu |
| UnsubscribeIndexData | ux | Không | Hủy đăng ký dữ liệu chỉ số |
| UnsubscribeHistD | ud | Danh sách mã hoặc rỗng | Hủy đăng ký dữ liệu theo ngày |
| UnsubscribeHistM | um | Danh sách mã hoặc rỗng | Hủy đăng ký dữ liệu theo phút |
| UnsubscribeTrade | ut | Danh sách mã hoặc rỗng | Hủy đăng ký dữ liệu giao dịch |
| UnsubscribeAll | ua | Không | Hủy đăng ký tất cả các topic |
Ví dụ
// Hủy đăng ký giá cổ phiếu
{ "A": "us", "I": 7 }
// Hủy đăng ký các mã lịch sử cụ thể
{ "A": "ud", "P": "SSI,VNM,HDB", "I": 9 }
// Hủy đăng ký tất cả dữ liệu theo phút
{ "A": "um", "P": "", "I": 12 }
// Hủy đăng ký tất cả
{ "A": "ua", "I": 15 }
Truy xuất dữ liệu qua HTTP
Các action sau cung cấp truy cập dữ liệu thị trường qua HTTP mà không yêu cầu kết nối WebSocket. Hữu ích cho polling đơn giản hoặc các nền tảng không hỗ trợ WebSocket.
- Yêu cầu HTTP KHÔNG yêu cầu trường
I - Yêu cầu WebSocket YÊU CẦU trường
I - Tất cả gửi qua POST đến https://dist.stockprice.vn/v1/S kèm header
Id
GetStockData (gs)
Lấy dữ liệu cổ phiếu hiện tại. Tham số: lastChangeId (tùy chọn, mặc định 0).
// Yêu cầu HTTP
{ "A": "gs", "P": "12345" }
// Phản hồi
{ "C": 0, "D": "{\"csv\":\"SSI,75.5,...\\nVNM,82.3,...\",\"dataId\":12450}" }
Sử dụng dataId trong yêu cầu tiếp theo để chỉ lấy thay đổi mới (cập nhật gia tăng).
GetIndexData (gi)
Lấy dữ liệu chỉ số hiện tại. Tham số: lastChangeId (tùy chọn, mặc định 0).
// Yêu cầu HTTP
{ "A": "gi", "P": "0" }
// Phản hồi
{ "C": 0, "D": "{\"csv\":\"VNINDEX,1640995200,O,1200.5,...\",\"dataId\":8920}" }
GetHistD (gd) — Lịch sử theo ngày
Tham số: JSON {"symbol":"...", "lastTime":...}
// Yêu cầu HTTP
{ "A": "gd", "P": "{\"symbol\":\"SSI\",\"lastTime\":1640995200}" }
// Phản hồi (CSV trực tiếp)
{ "C": 0, "D": "1640995200,1.0,75.5,76.0,77.0,74.5,5000000,...\n..." }
GetHistM (gm) — Lịch sử theo phút
// Yêu cầu HTTP
{ "A": "gm", "P": "{\"symbol\":\"SSI\",\"lastTime\":1640995200}" }
// Phản hồi (CSV trực tiếp)
{ "C": 0, "D": "1640995200,75.5,76.0,75.0,75.8,10000,...\n..." }
GetHistTrade (gt) — Lịch sử giao dịch
Lưu ý: Dữ liệu giao dịch KHÔNG có sẵn cho các mã chỉ số.
// Yêu cầu HTTP
{ "A": "gt", "P": "{\"symbol\":\"SSI\",\"lastTime\":1640995200}" }
// Phản hồi
{ "C": 0, "D": "1640995200,75.5,1000,B\n1640995205,75.6,500,S\n..." }
Ví dụ HTTP Polling (JavaScript)
let lastDataId = 0;
async function pollStockData() {
const response = await fetch('https://dist.stockprice.vn/v1/S', {
method: 'POST',
headers: { 'Content-Type': 'application/json', 'Id': sessionId },
body: JSON.stringify({ A: 'gs', P: lastDataId.toString() })
});
const result = await response.json();
if (result.C === 0) {
const data = JSON.parse(result.D);
console.log('Stock CSV:', data.csv);
lastDataId = data.dataId; // Lưu cho yêu cầu tiếp theo
}
}
setInterval(pollStockData, 1000);
Đặc tả định dạng dữ liệu
Tổng quan định dạng CSV
- Các trường cách nhau bởi dấu phẩy
, - Nhiều bản ghi cách nhau bởi
\n(xuống dòng) - Trường rỗng = giá trị mặc định/zero
- Kiểu số thực:
75.5, rỗng = 0.0 - Kiểu số nguyên:
1000, rỗng = 0 - Timestamp: Unix epoch (giây, giờ địa phương), rỗng = null
StockData (topic: stock_data)
Dữ liệu giá cổ phiếu thời gian thực với độ sâu bid/ask tối đa 10 cấp (68 trường).
| # | Trường | Kiểu | Mô tả |
|---|---|---|---|
| 1 | Symbol | string | Mã cổ phiếu |
| 2 | RefPx | double | Giá tham chiếu |
| 3 | CeilingPx | double | Giá trần |
| 4 | FloorPx | double | Giá sàn |
| 5-6 | BestBidPx3, BestBidVol3 | double, long | Giá/KL mua cấp 3 |
| 7-8 | BestBidPx2, BestBidVol2 | double, long | Giá/KL mua cấp 2 |
| 9-10 | BestBidPx1, BestBidVol1 | double, long | Giá/KL mua cấp 1 (cao nhất) |
| 11-12 | LastMatchedPx, LastMatchedVol | double, long | Giá/KL khớp lệnh gần nhất |
| 13-14 | BestAskPx1, BestAskVol1 | double, long | Giá/KL bán cấp 1 (thấp nhất) |
| 15-18 | BestAskPx2-3, BestAskVol2-3 | double, long | Giá/KL bán cấp 2-3 |
| 19-20 | TotalVolTraded, TotalValTraded | long, long | Tổng KL/Giá trị giao dịch |
| 21-23 | OpenPx, HighPx, LowPx | double | Giá mở/cao/thấp trong ngày |
| 24-27 | SellVol, SellVal, BuyVol, BuyVal | long | Tổng KL/Giá trị mua/bán |
| 28-31 | PtMatchedPx, PtMatchedVol, PtVol, PtVal | double, long | Thỏa thuận |
| 32-34 | CurrentRoom, OpenInterest, Inav | long, long, double | Room NN, Vị thế mở, iNAV |
| 35-38 | FrnBuyVol, FrnBuyVal, FrnSellVol, FrnSellVal | long | Giao dịch nước ngoài |
| 39-40 | TotalBidVol, TotalAskVol | long | Tổng KL đặt mua/bán |
| 41-68 | BestBidPx4-10, BestAskPx4-10... | double, long | Cấp giá mua/bán 4-10 (độ sâu mở rộng) |
IndexData (topic: index_data)
Dữ liệu chỉ số thị trường thời gian thực (27 trường).
| # | Trường | Kiểu | Mô tả |
|---|---|---|---|
| 1 | Symbol | string | Mã chỉ số (VNINDEX, VN30...) |
| 2 | TradingTime | long | Ngày giao dịch (epoch) |
| 3 | StatusCode | string | Trạng thái: "O"=Mở, "C"=Đóng |
| 4 | RefIndex | double | Chỉ số tham chiếu |
| 5 | MarketIndex | double | Chỉ số hiện tại |
| 6-8 | HighIndex, LowIndex, OpenIndex | double | Cao/Thấp/Mở cửa |
| 9-10 | Vol, Val | long | Tổng KL/Giá trị |
| 11-16 | Advances, Ceiling, NoChanges, NoTrade, Declines, Floor | int | Thống kê mã tăng/giảm/trần/sàn |
| 17-26 | PtVol...FrnSellVal | long | Thỏa thuận, mua/bán, nước ngoài |
| 27 | TotalCount | int | Tổng số mã trong chỉ số |
StockInfo (topic: stock_info)
Thông tin cổ phiếu tĩnh (23 trường).
| # | Trường | Kiểu | Mô tả |
|---|---|---|---|
| 1 | Symbol | string | Mã cổ phiếu |
| 2 | ExchangeId | string | Sàn (HSX, HNX, UPCOM) |
| 3-4 | MarketId, GroupId | string | Mã thị trường, nhóm ngành |
| 5-6 | Name, NameEn | string | Tên công ty (VI/EN) |
| 7-10 | Address, Phone, Fax, Website | string | Thông tin liên hệ |
| 11-13 | ListedVol, ListedDate, TradingVol | long | KL niêm yết, ngày, KL giao dịch |
| 14-21 | MaturityDate...ExerciseRatio | mixed | Thông tin chứng quyền |
| 22-23 | OpenInterest, TotalRoom | long | Vị thế mở, Room NN |
DData (topic: D/{MÃ}) — Dữ liệu theo ngày
Dữ liệu nến theo ngày (24 trường).
| # | Trường | Kiểu | Mô tả |
|---|---|---|---|
| 1 | TradingTime | long | Ngày giao dịch (epoch) |
| 2 | AdjustRate | double | Tỷ lệ điều chỉnh (1.0 = không điều chỉnh) |
| 3 | RefPx | double | Giá tham chiếu |
| 4-7 | OpenPx, ClosePx, HighPx, LowPx | double | OHLC |
| 8-9 | Vol, Val | long | Tổng KL/Giá trị |
| 10-17 | BuyVol...FrnSellVal | long | Mua/bán, nước ngoài |
| 18-19 | PtVol, PtVal | long | Thỏa thuận |
| 20 | OpenInterest | long | Vị thế mở (phái sinh) |
| 21-24 | PortBuyVol...PortSellVal | long | Danh mục đầu tư |
Lưu ý AdjustRate: Khi phân tích kỹ thuật, nhân giá với AdjustRate / 1000000. Khi AdjustRate thay đổi, cần tính lại cho tất cả các ngày trước đó.
MData (topic: M/{MÃ}) — Dữ liệu theo phút
Dữ liệu nến theo phút (14 trường).
| # | Trường | Kiểu | Mô tả |
|---|---|---|---|
| 1 | TradingTime | long | Thời gian (epoch) |
| 2-5 | OpenPx, HighPx, LowPx, ClosePx | double | OHLC |
| 6 | Vol | long | Tổng KL |
| 7-10 | BuyVol, BuyVal, SellVol, SellVal | long | Mua/bán |
| 11-14 | FrnBuyVol, FrnBuyVal, FrnSellVol, FrnSellVal | long | Nước ngoài |
TradeData (topic: T/{MÃ}) — Dữ liệu giao dịch
Dữ liệu giao dịch theo từng lệnh (4 trường).
| # | Trường | Kiểu | Mô tả |
|---|---|---|---|
| 1 | TradingTime | long | Timestamp giao dịch (epoch) |
| 2 | ClosePx | double | Giá giao dịch |
| 3 | Vol | long | Khối lượng giao dịch |
| 4 | Side | string | "B" (Mua), "S" (Bán), rỗng (không xác định) |
Tên Topic
Topics thời gian thực
stock_data— Giá cổ phiếu thời gian thực cho tất cả cổ phiếuindex_data— Dữ liệu chỉ số thời gian thực cho tất cả chỉ sốstock_info— Thông tin cổ phiếu (đẩy một lần)
Topics lịch sử
D/{MÃ}— Dữ liệu lịch sử theo ngày (ví dụ: D/SSI, D/VNM, D/VNINDEX)M/{MÃ}— Dữ liệu lịch sử theo phút (ví dụ: M/SSI, M/VNM)T/{MÃ}— Dữ liệu giao dịch (chỉ cổ phiếu, ví dụ: T/SSI, T/VNM)
Quy tắc đặt tên
- Topics thời gian thực sử dụng tên viết thường đơn giản
- Topics lịch sử sử dụng định dạng:
{LoạiDữLiệu}/{Mã} - Mã không phân biệt chữ hoa/thường (tự động chuyển sang IN HOA)
- Mã chỉ số CÓ THỂ dùng với D và M
- Mã chỉ số KHÔNG THỂ dùng với T
Xử lý lỗi
Lỗi thường gặp
| Lỗi | Nguyên nhân | Giải pháp |
|---|---|---|
"WebSocket was not connected" | Kết nối WS chưa thiết lập hoặc đã đóng | Đảm bảo kết nối WS đang mở trước khi gửi action |
"Missing symbol list" | Tham số P bị thiếu hoặc rỗng | Cung cấp danh sách mã đúng định dạng |
"Trade data not available for indices" | Đăng ký T cho mã chỉ số | Loại bỏ mã chỉ số, dùng D hoặc M thay thế |
"Maximum 50 symbols allowed" | Vượt quá giới hạn HTTP ssi | Giảm số mã xuống dưới 50 |
Thực hành tốt
- Luôn kiểm tra mã phản hồi (trường
C): 0 = thành công, 1+ = lỗi - Triển khai logic kết nối lại với backoff theo hàm mũ
- Khôi phục đăng ký sau khi kết nối lại
- Xác thực tham số trước khi gửi (định dạng CSV, mã hợp lệ)
- Mã không hợp lệ được bỏ qua âm thầm (không lỗi, chỉ không có dữ liệu)
- Sử dụng action Login của AuthPlugin trước khi đăng ký
Ví dụ Code
JavaScript — Kết nối và đăng ký cơ bản
const ws = new WebSocket('wss://dist.stockprice.vn/v1/ws?Id=' + sessionId);
let requestId = 0;
ws.onopen = function() {
// Đăng ký dữ liệu cổ phiếu
requestId++;
ws.send(JSON.stringify({ A: 'ss', I: requestId }));
// Đăng ký dữ liệu lịch sử theo ngày
requestId++;
ws.send(JSON.stringify({ A: 'sd', P: 'SSI,0\nVNM,0', I: requestId }));
};
ws.onmessage = function(event) {
const msg = JSON.parse(event.data);
// Phản hồi action
if ('C' in msg) {
if (msg.C === 0) console.log('OK:', msg.I, msg.D);
else console.error('Lỗi:', msg.I, msg.D);
return;
}
// Tin nhắn dữ liệu
if ('t' in msg) {
if ('d' in msg) {
const lines = msg.d.split('\n');
lines.forEach(line => {
if (!line) return;
const fields = line.split(',');
console.log(msg.t, fields[0], fields);
});
} else {
console.log('Hoàn thành:', msg.t);
}
}
};
JavaScript — Đăng ký nhiều loại dữ liệu
function subscribeAll(ws) {
// Giá cổ phiếu thời gian thực
ws.send(JSON.stringify({ A: 'ss', I: ++requestId }));
// Chỉ số thời gian thực
ws.send(JSON.stringify({ A: 'sx', I: ++requestId }));
// Thông tin cổ phiếu
ws.send(JSON.stringify({ A: 'ssi', I: ++requestId }));
// Lịch sử theo ngày
ws.send(JSON.stringify({ A: 'sd', P: 'SSI,1640995200\nVNM,0', I: ++requestId }));
// Lịch sử theo phút
ws.send(JSON.stringify({ A: 'sm', P: 'SSI,1640995200', I: ++requestId }));
// Giao dịch (chỉ cổ phiếu)
ws.send(JSON.stringify({ A: 'st', P: 'SSI,0\nVNM,0', I: ++requestId }));
}
Python — Kết nối WebSocket cơ bản
import websocket
import json
import threading
request_id = 0
def get_next_id():
global request_id
request_id += 1
return request_id
def on_message(ws, message):
msg = json.loads(message)
if 'C' in msg:
print(f"{'OK' if msg['C']==0 else 'Lỗi'} ({msg.get('I')}): {msg.get('D')}")
elif 't' in msg:
if 'd' in msg:
lines = msg['d'].split('\n')
for line in lines:
if line:
fields = line.split(',')
print(f" {msg['t']}: {fields[:5]}...")
else:
print(f" Hoàn thành: {msg['t']}")
def on_open(ws):
# Đăng ký dữ liệu cổ phiếu
ws.send(json.dumps({'A': 'ss', 'I': get_next_id()}))
# Đăng ký lịch sử
ws.send(json.dumps({'A': 'sd', 'P': 'SSI,0\nVNM,0', 'I': get_next_id()}))
ws = websocket.WebSocketApp(
f'wss://dist.stockprice.vn/v1/ws?Id={session_id}',
on_message=on_message,
on_open=on_open,
on_error=lambda ws, e: print(f'Lỗi: {e}'),
on_close=lambda ws, c, m: print('Đã đóng')
)
ws.run_forever()
Python — Lấy dữ liệu lịch sử qua HTTP
import requests, json
def get_historical(session_id, symbol, action, last_time=0):
"""action: 'gd' (ngày), 'gm' (phút), 'gt' (giao dịch)"""
params = {"symbol": symbol, "lastTime": last_time}
resp = requests.post(
'https://dist.stockprice.vn/v1/S',
headers={'Content-Type': 'application/json', 'Id': session_id},
json={'A': action, 'P': json.dumps(params)}
)
result = resp.json()
if result['C'] == 0:
for line in result['D'].split('\n'):
if line:
print(line.split(','))
else:
print(f"Lỗi: {result['D']}")
# Sử dụng
get_historical(session_id, 'SSI', 'gd', 0) # Theo ngày
get_historical(session_id, 'VNM', 'gm', 0) # Theo phút
get_historical(session_id, 'HDB', 'gt', 0) # Giao dịch