15. Vị thế, PPMMR & Max mua/bán

Đọc vị thế cổ phiếu (T1/T2), sức mua PPMMR, vị thế phái sinh và khối lượng mua/bán tối đa

Mục tiêu

Đọc các dữ liệu danh mục và sức mua của tài khoản mà không đặt lệnh: vị thế cổ phiếu kèm khối lượng chờ về T1/T2, PPMMR (sức mua, tỷ lệ margin, tiền mua/bán chờ thanh toán), vị thế và PPMMR phái sinh, và khối lượng mua/bán tối đa của một mã. Tương ứng Sample 15 trong repo tutorials (SDK v3.3.2). Cần OTP khi authenticate.

Sample chỉ đọc dữ liệu nên an toàn để chạy, nhưng kết quả là dữ liệu tài khoản thật. Không dán nguyên output vào ticket, chat hay tài liệu.

Luồng xử lý

Auth → Vị thế cổ phiếu → PPMMR cổ phiếu → (Phái sinh) → Max mua/bán
  1. get_equity_positions(account): danh sách vị thế, kèm KL mua/bán chờ về T1/T2
  2. get_equity_ppmmr(account): sức mua, tỷ lệ margin, tiền mua/bán T0/T1/T2
  3. Nếu có tài khoản phái sinh: get_derivative_positions và get_derivative_ppmmr
  4. get_max_buy_sell(account, symbol, price) và get_max_buy_sell_at_market_price(account, symbol)

Các mẫu trong repo

Ngôn ngữFile
Pythonpython/sample_15_positions_ppmmr.py, python/sample_15_positions_ppmmr_async.py
Node.jsnode/sample_15_positions_ppmmr.js
.NETdotnet/Sample15PositionsPpmmr.cs
Gogo/sample_15_positions_ppmmr.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) trong config.json, xem Tutorial 1. Tài khoản phái sinh là tùy chọn (derivative_account). Sample repo đọc cấu hình qua config.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.py trong repo, gồm mask_account (che số tài khoản khi in) và require_account (dừng nếu thiếu tài khoản).

Sample Code — Sync

Code dưới đây rút gọn từ sample trong repo, giữ nguyên luồng. Các lời gọi danh mục có thể trả None khi tài khoản không có dữ liệu, nên code kiểm tra None trước khi duyệt.

python/sample_15_positions_ppmmr.py
from decimal import Decimal

from ssi_sdk import Auth, Data, Trading, Config
from auth_helper import ensure_auth
from helpers import 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>"
DERIVATIVE_ACCOUNT_NO = None  # đặt số tài khoản phái sinh nếu có
SYMBOL = "SSI"


def money(value) -> str:
    return "-" if value is None else f"{value:,.0f}"


require_account(ACCOUNT_NO)

with Auth(config) as auth:
    ensure_auth(auth, otp="<your_otp>")

    with Trading(auth) as trading, Data(auth) as data:
        # Bước 1: Vị thế cổ phiếu
        print(f"--- Vị thế cổ phiếu ({mask_account(ACCOUNT_NO)}) ---")
        positions = trading.portfolio.get_equity_positions(ACCOUNT_NO) or []
        print(f"{'Mã':<8}{'SL':>9}{'Bán được':>10}{'T1 mua':>9}{'T2 mua':>9}{'T1 bán':>9}{'T2 bán':>9}{'Giá vốn':>12}")
        for pos in positions:
            print(
                f"{pos.symbol:<8}{pos.quantity:>9}{pos.sellable_quantity:>10}"
                f"{pos.t1_buy_quantity:>9}{pos.t2_buy_quantity:>9}"
                f"{pos.t1_sell_quantity:>9}{pos.t2_sell_quantity:>9}{pos.cost_price:>12,.0f}"
            )
        if not positions:
            print("  (không có vị thế)")

        # Bước 2: PPMMR tài khoản cổ phiếu
        print("\n--- PPMMR cổ phiếu ---")
        eq = trading.portfolio.get_equity_ppmmr(ACCOUNT_NO)
        if eq is None:
            print("  (không có dữ liệu PPMMR)")
        else:
            print(f"  Sức mua (purchasing_power) : {money(eq.purchasing_power)}")
            print(f"  Tỷ lệ margin               : {eq.margin_ratio}")
            print(f"  Tổng tài sản / Tổng nợ     : {money(eq.total_assets)} / {money(eq.total_debt)}")
            print(f"  Tiền mua T0/T1/T2          : {money(eq.buy_t0)} / {money(eq.buy_t1)} / {money(eq.buy_t2)}")
            print(f"  Tiền bán T0/T1/T2          : {money(eq.sell_t0)} / {money(eq.sell_t1)} / {money(eq.sell_t2)}")

        # Bước 3: Tài khoản phái sinh (tùy chọn)
        if DERIVATIVE_ACCOUNT_NO:
            print(f"\n--- Phái sinh ({mask_account(DERIVATIVE_ACCOUNT_NO)}) ---")
            all_pos = trading.portfolio.get_derivative_positions(DERIVATIVE_ACCOUNT_NO) or []
            for group in all_pos:
                for p in group.open_positions:
                    print(f"  MỞ   {p.symbol:<12} long={p.long} short={p.short} net={p.net} floating P/L={money(p.floating_pl)}")
                for p in group.closed_positions:
                    print(f"  ĐÓNG {p.symbol:<12} long={p.long} short={p.short} net={p.net} trading P/L={money(p.trading_pl)}")
            dv = trading.portfolio.get_derivative_ppmmr(DERIVATIVE_ACCOUNT_NO)
            if dv is not None:
                print(f"  Số dư: {money(dv.account_balance)} | Tổng P/L: {money(dv.total_pl)} | Rút được: {money(dv.withdrawable)}")
                print(f"  Ký quỹ yêu cầu SSI: {money(dv.margin_req_ssi)} | Tỷ lệ TK SSI: {dv.account_ratio_ssi}")
        else:
            print("\n(Bỏ qua phái sinh: chưa cấu hình derivative_account)")

        # Bước 4: Max mua / bán
        print(f"\n--- Max mua/bán {SYMBOL} ---")
        master = next((m for m in data.market_data.get_master_data() if m.symbol == SYMBOL), None)
        if master is not None and master.ref_price > 0:
            price = Decimal(str(master.ref_price))
            mx = trading.trading.get_max_buy_sell(ACCOUNT_NO, SYMBOL, price)
            print(f"  @ giá tham chiếu {price}: mua {mx.max_buy_quantity} | bán {mx.max_sell_quantity}"
                  f" | sức mua {mx.purchasing_power} | margin {mx.margin_ratio}")
        mx_mkt = trading.trading.get_max_buy_sell_at_market_price(ACCOUNT_NO, SYMBOL)
        print(f"  @ giá thị trường: mua {mx_mkt.max_buy_quantity} | bán {mx_mkt.max_sell_quantity}")

Sample Code — Async

Async dùng cùng luồng; chỉ khác AsyncAuth / AsyncData / AsyncTrading và await.

python/sample_15_positions_ppmmr_async.py
import asyncio
from decimal import Decimal

from ssi_sdk import AsyncAuth, AsyncData, AsyncTrading, Config
from auth_helper import ensure_auth_async
from helpers import mask_account, require_account

config = Config(...)
ACCOUNT_NO = "<your_account_no>"
SYMBOL = "SSI"


async def main():
    require_account(ACCOUNT_NO)

    async with AsyncAuth(config) as auth:
        await ensure_auth_async(auth, otp="<your_otp>")

        async with AsyncTrading(auth) as trading, AsyncData(auth) as data:
            print(f"--- Vị thế cổ phiếu ({mask_account(ACCOUNT_NO)}) ---")
            positions = await trading.portfolio.get_equity_positions(ACCOUNT_NO) or []
            for pos in positions:
                print(f"{pos.symbol:<8} {pos.quantity:>9} T1 mua={pos.t1_buy_quantity} T1 bán={pos.t1_sell_quantity}")

            eq = await trading.portfolio.get_equity_ppmmr(ACCOUNT_NO)
            if eq is not None:
                print(f"Sức mua: {eq.purchasing_power} | margin: {eq.margin_ratio}")

            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 not None and master.ref_price > 0:
                price = Decimal(str(master.ref_price))
                mx = await trading.trading.get_max_buy_sell(ACCOUNT_NO, SYMBOL, price)
                print(f"Max mua {mx.max_buy_quantity} | Max bán {mx.max_sell_quantity}")


asyncio.run(main())

Các trường dễ nhầm

TrườngĐơn vịÝ nghĩa
EquityPosition.t1_buy_quantity, t2_buy_quantityCổ phiếuKhối lượng mua đang chờ về ở T1 / T2
EquityPosition.t1_sell_quantity, t2_sell_quantityCổ phiếuKhối lượng bán đang chờ thanh toán ở T1 / T2
EquityPPMMR.buy_t0/t1/t2, sell_t0/t1/t2VNDSố tiền mua / bán chờ thanh toán
purchasing_power (PPMMR, Max mua/bán)VNDSức mua. Thay cho purchase_power (deprecated)
margin_ratioTỷ lệTỷ lệ margin. Với tài khoản phái sinh, purchasing_power và margin_ratio là None chứ không phải 0

Lưu ý

  • Vị thế cổ phiếu chỉ có T1/T2: vị thế cổ phiếu không có trường T0. Số tiền mua/bán T0 chỉ nằm trong PPMMR và số dư tiền.
  • None khác danh sách rỗng: khi tài khoản không có vị thế (hoặc server trả 204), get_equity_positions và get_derivative_positions trả None. get_equity_ppmmr, get_derivative_ppmmr, get_equity_balance, get_derivative_balance cũng có thể trả None. Luôn kiểm tra trước khi duyệt hoặc đọc thuộc tính.
  • get_open_derivative_positions / get_closed_derivative_positions: ở SDK 3.3.2 có thể raise AttributeError khi không có vị thế. Để an toàn, gọi get_derivative_positions và kiểm tra None như sample.
  • Dòng tổng hợp vị thế (query_summary) không có ở REST, chỉ có qua Trading WebSocket, xem Trading WebSocket.
  • Max mua/bán theo giá: get_max_buy_sell tính theo giá bạn truyền vào (sample dùng giá tham chiếu); get_max_buy_sell_at_market_price tính theo giá thị trường. Đây là số ước tính tại thời điểm gọi, không phải cam kết khớp lệnh.
  • Bảo mật dữ liệu: dùng mask_account khi in số tài khoản, không log nguyên response, và không đưa số tài khoản hay số dư thật vào tài liệu.
  • Mô tả đầy đủ model và method: PortfolioService (Python). Số dư cơ bản và sức mua trước khi đặt lệnh: Số dư tài khoản.
  • Cần xử lý lỗi khi gọi các API này (rate limit, lỗi server, validate): xem Xử lý lỗi & Rate limit.

Trên trang này