14. Đặt/hủy lệnh hàng loạt (Batch)
Đặt và hủy nhiều lệnh trong một request, xử lý kết quả từng lệnh
Mục tiêu
Đặt và hủy nhiều lệnh trong một request đã ký RSA (POST /order/batch và DELETE /order/batch), rồi đọc kết quả của từng lệnh. Tương ứng Sample 14 trong repo tutorials (SDK v3.3.2). Cần OTP khi authenticate.
Cảnh báo: sample này gửi lệnh thật lên tài khoản đã cấu hình. Để an toàn, mọi lệnh là LO mua tại giá sàn (xa thị trường, chỉ nằm chờ) với khối lượng nhỏ, và script yêu cầu gõ
YEStrước khi gửi. Vẫn nên kiểm tra kỹ tài khoản, mã, giá, khối lượng trước khi chạy, và thử trên môi trường UAT trước.
Luồng xử lý
Market Data (giá sàn) → Dựng batch → Trading API (place batch) → Kết quả từng lệnh → Trading API (cancel batch)- Lấy giá sàn từ master data, dựng danh sách
BatchPlaceOrderItem - Kiểm tra số lệnh không vượt
Config.max_batch_orders(mặc định 20) - Gọi
place_batch_orders(items)và xử lý kết quả từng lệnh (success,error_code) - Gọi
cancel_batch_orders(items)cho các lệnh vừa đặt thành công (BatchCancelOrderItem)
Các mẫu trong repo
| Ngôn ngữ | File |
|---|---|
| Python | python/sample_14_batch_orders.py, python/sample_14_batch_orders_async.py |
| Node.js | node/sample_14_batch_orders.js |
| .NET | dotnet/Sample14BatchOrders.cs |
| Go | go/sample_14_batch_orders.go |
Trang này minh họa bằng Python. Repo: ssi-fastconnect-v3-tutorials.
Chuẩn bị
- Cấu hình credentials và số tài khoản (
equity_account) trongconfig.json, xem Tutorial 1. Sample repo đọc cấu hình quaconfig.py; ở đây dùng placeholder để code gọn. auth_helper.py(token cache, Tutorial 1) và OTP cho lần đăng nhập đầu.helpers.pytrong repo, gồmconfirm_real_orders(cảnh báo và yêu cầu gõYES),mask_account(che số tài khoản khi in),require_account(dừng nếu thiếu tài khoản) vàenum_value(lấy chuỗi hiển thị an toàn cho trường enum).
Sample Code — Sync
Code dưới đây rút gọn từ sample trong repo, giữ nguyên luồng.
import uuid
from decimal import Decimal
from ssi_sdk import Auth, Data, Trading, Config
from ssi_sdk.enums import OrderSide, OrderType
from ssi_sdk.exceptions import APIError, DuplicateRequestError, ValidationError
from ssi_sdk.models import BatchCancelOrderItem, BatchPlaceOrderItem
from auth_helper import ensure_auth
from helpers import confirm_real_orders, enum_value, mask_account, require_account
config = Config(
client_id="<your_client_id>",
api_key="<your_api_key>",
api_secret="<your_api_secret>",
private_key="<your_private_key>",
)
ACCOUNT_NO = "<your_account_no>"
SYMBOL = "SSI"
QUANTITY = 100
NUM_ORDERS = 3
require_account(ACCOUNT_NO)
confirm_real_orders(f"{NUM_ORDERS} lệnh LO mua {SYMBOL} (batch)")
with Auth(config) as auth:
ensure_auth(auth, otp="<your_otp>")
# Bước 1: Lấy giá sàn làm giá đặt (lệnh nằm chờ, xa thị trường)
with Data(auth) as data:
master = next((m for m in data.market_data.get_master_data() if m.symbol == SYMBOL), None)
if master is None or master.floor <= 0:
raise SystemExit(f"Không có giá sàn của {SYMBOL}, dừng để an toàn.")
floor_price = Decimal(str(master.floor))
with Trading(auth) as trading:
# Bước 2: Dựng batch, mỗi lệnh có client_request_id riêng
batch_tag = uuid.uuid4().hex[:8]
place_items: list[BatchPlaceOrderItem] = [
{
"account_no": ACCOUNT_NO,
"symbol": SYMBOL,
"side": OrderSide.BUY,
"quantity": QUANTITY,
"price": floor_price,
"order_type": OrderType.LO,
"client_request_id": f"bt{batch_tag}{i:02d}", # <= 20 ký tự
}
for i in range(1, NUM_ORDERS + 1)
]
if len(place_items) > config.max_batch_orders:
raise SystemExit(f"Tối đa {config.max_batch_orders} lệnh / batch (Config.max_batch_orders).")
# Bước 3: Đặt batch
print(f"place_batch_orders: {len(place_items)} lệnh, tài khoản {mask_account(ACCOUNT_NO)}")
try:
response = trading.trading.place_batch_orders(place_items)
except ValidationError as e:
raise SystemExit(f"Batch không hợp lệ (chưa gửi gì): {e}")
except DuplicateRequestError as e:
raise SystemExit(f"Batch id đã dùng trong ngày (409), kiểm tra sổ lệnh trước khi gửi lại: {e}")
except APIError as e:
raise SystemExit(f"Server từ chối batch: code={e.code} status={e.status_code} msg={e.message}")
print(f"Batch ID: {response.batch_request_id}")
placed_ok: list[str] = []
for r in response.results:
if r.success:
placed_ok.append(r.order_id)
print(f" OK {r.client_request_id} -> orderId={r.order_id} status={enum_value(r.status)}")
else:
print(f" FAIL {r.client_request_id} -> code={r.error_code} msg={r.error_message}")
# Bước 4: Hủy batch các lệnh vừa đặt
if not placed_ok:
print("Không có lệnh nào được đặt thành công, bỏ qua bước hủy.")
else:
# Mỗi item chỉ được có ĐÚNG MỘT trong order_id / client_request_id
cancel_items: list[BatchCancelOrderItem] = [
{"account_no": ACCOUNT_NO, "order_id": oid} for oid in placed_ok
]
print(f"cancel_batch_orders: {len(cancel_items)} lệnh")
try:
cancel_resp = trading.trading.cancel_batch_orders(cancel_items)
except (ValidationError, DuplicateRequestError, APIError) as e:
raise SystemExit(f"Hủy batch thất bại: {type(e).__name__}: {e}")
for r in cancel_resp.results:
state = "OK " if r.success else "FAIL"
print(
f" {state} orderId={r.order_id} cancelId={r.client_cancel_id} "
f"status={enum_value(r.status)} {r.error_code} {r.error_message}".rstrip()
)Sample Code — Async
Async dùng cùng luồng; chỉ khác AsyncAuth / AsyncData / AsyncTrading và await.
import asyncio
import uuid
from decimal import Decimal
from ssi_sdk import AsyncAuth, AsyncData, AsyncTrading, Config
from ssi_sdk.enums import OrderSide, OrderType
from ssi_sdk.exceptions import APIError, DuplicateRequestError, ValidationError
from auth_helper import ensure_auth_async
from helpers import confirm_real_orders, enum_value, require_account
config = Config(...)
ACCOUNT_NO = "<your_account_no>"
SYMBOL = "SSI"
async def main():
require_account(ACCOUNT_NO)
confirm_real_orders(f"3 lệnh LO mua {SYMBOL} (batch)")
async with AsyncAuth(config) as auth:
await ensure_auth_async(auth, otp="<your_otp>")
async with AsyncData(auth) as data:
master_data = await data.market_data.get_master_data()
master = next((m for m in master_data if m.symbol == SYMBOL), None)
if master is None or master.floor <= 0:
raise SystemExit(f"Không có giá sàn của {SYMBOL}, dừng để an toàn.")
floor_price = Decimal(str(master.floor))
async with AsyncTrading(auth) as trading:
batch_tag = uuid.uuid4().hex[:8]
place_items = [
{
"account_no": ACCOUNT_NO, "symbol": SYMBOL, "side": OrderSide.BUY,
"quantity": 100, "price": floor_price, "order_type": OrderType.LO,
"client_request_id": f"bt{batch_tag}{i:02d}", # <= 20 ký tự
}
for i in range(1, 4)
]
try:
response = await trading.trading.place_batch_orders(place_items)
except (ValidationError, DuplicateRequestError, APIError) as e:
raise SystemExit(f"Batch thất bại: {type(e).__name__}: {e}")
placed_ok = [r.order_id for r in response.results if r.success]
for r in response.results:
print(f" {'OK' if r.success else 'FAIL'} {r.client_request_id} {enum_value(r.status)}")
if placed_ok:
cancel_items = [{"account_no": ACCOUNT_NO, "order_id": oid} for oid in placed_ok]
cancel_resp = await trading.trading.cancel_batch_orders(cancel_items)
for r in cancel_resp.results:
print(f" {'OK' if r.success else 'FAIL'} orderId={r.order_id}")
asyncio.run(main())Kết quả từng lệnh
| Trường | Ý nghĩa |
|---|---|
response.batch_request_id | ID của cả batch |
r.success | True nếu lệnh này được chấp nhận |
r.order_id, r.status | Mã lệnh và trạng thái ban đầu khi success |
r.error_code, r.error_message | Lý do lỗi khi lệnh này bị từ chối |
r.client_request_id | Khóa bạn tự đặt, dùng để đối chiếu lệnh trong request với kết quả |
r.client_cancel_id | Khóa của yêu cầu hủy (kết quả cancel_batch_orders) |
Lưu ý
- Validate cả batch trước: một lệnh sai sẽ làm cả batch bị từ chối (
ValidationErrorhoặcAPIError). Chỉ khi batch được nhận thì mới có kết quả riêng cho từng lệnh, nên luôn duyệtresponse.resultsthay vì chỉ kiểm tra "không có exception". - Idempotency:
client_request_id(tối đa 20 ký tự) là khóa idempotency của từng lệnh. Tự đặt để còn tra cứu và hủy sau này. - Gửi lại batch với id đã dùng trong ngày trả về
DuplicateRequestError(HTTP 409). SDK không tự retry requestPOST/DELETE; lệnh gốc có thể đã nằm trên sàn, hãy kiểm tra sổ lệnh trước khi gửi lại. - Hủy: mỗi
BatchCancelOrderItemchỉ được có đúng một trongorder_idhoặcclient_request_id. - Giới hạn số lệnh: sample kiểm tra theo
Config.max_batch_orders(mặc định 20) trước khi gọi. Quy định phía API (số lệnh tối đa mỗi request, chống replay...) xem trade-order-batch-place và trade-order-batch-cancel. - Theo dõi trạng thái các lệnh vừa đặt bằng Trạng thái lệnh hoặc WebSocket Trading.
- Cách phân loại lỗi và retry an toàn: Xử lý lỗi & Rate limit. Kiểm tra sức mua và vị thế trước khi đặt: Vị thế, PPMMR & Max mua/bán.