15. Positions, PPMMR & Max Buy/Sell

Read equity positions (T1/T2), PPMMR purchasing power, derivative positions and the maximum buy/sell quantity

Objective

Read the portfolio and buying-power data of an account without placing orders: equity positions with the quantity pending settlement at T1/T2, PPMMR (purchasing power, margin ratio, buy/sell amounts pending settlement), derivative positions and PPMMR, and the maximum buy/sell quantity for a symbol. Matches Sample 15 in the tutorials repo (SDK v3.3.2). Requires OTP during authentication.

The sample only reads data, so it is safe to run, but the output is real account data. Do not paste the raw output into tickets, chats or documentation.

Flow

Auth → Equity positions → Equity PPMMR → (Derivatives) → Max buy/sell
  1. get_equity_positions(account): positions, with buy/sell quantity pending at T1/T2
  2. get_equity_ppmmr(account): purchasing power, margin ratio, buy/sell amounts at T0/T1/T2
  3. If you have a derivative account: get_derivative_positions and get_derivative_ppmmr
  4. get_max_buy_sell(account, symbol, price) and get_max_buy_sell_at_market_price(account, symbol)

Samples in the repo

LanguageFile
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

This page uses Python for illustration. Repo: ssi-fastconnect-v3-tutorials.

Prerequisites

  • Credentials and account number (equity_account) in config.json, see Tutorial 1. The derivative account is optional (derivative_account). The repo samples load the config through config.py; placeholders are used here to keep the code short.
  • auth_helper.py (token cache, Tutorial 1) and an OTP for the first login.
  • helpers.py from the repo: mask_account (masks the account number when printing) and require_account (stops if no account is configured).

Sample Code — Sync

The code below is condensed from the repo sample and keeps the same flow. Portfolio calls can return None when the account has no data, so the code checks for None before iterating.

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  # set a derivative account number if you have one
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:
        # Step 1: Equity positions
        print(f"--- Equity positions ({mask_account(ACCOUNT_NO)}) ---")
        positions = trading.portfolio.get_equity_positions(ACCOUNT_NO) or []
        print(f"{'Symbol':<8}{'Qty':>9}{'Sellable':>10}{'T1 buy':>9}{'T2 buy':>9}{'T1 sell':>9}{'T2 sell':>9}{'Cost':>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("  (no positions)")

        # Step 2: Equity PPMMR
        print("\n--- Equity PPMMR ---")
        eq = trading.portfolio.get_equity_ppmmr(ACCOUNT_NO)
        if eq is None:
            print("  (no PPMMR data)")
        else:
            print(f"  Purchasing power           : {money(eq.purchasing_power)}")
            print(f"  Margin ratio               : {eq.margin_ratio}")
            print(f"  Total assets / Total debt  : {money(eq.total_assets)} / {money(eq.total_debt)}")
            print(f"  Buy amount T0/T1/T2        : {money(eq.buy_t0)} / {money(eq.buy_t1)} / {money(eq.buy_t2)}")
            print(f"  Sell amount T0/T1/T2       : {money(eq.sell_t0)} / {money(eq.sell_t1)} / {money(eq.sell_t2)}")

        # Step 3: Derivative account (optional)
        if DERIVATIVE_ACCOUNT_NO:
            print(f"\n--- Derivatives ({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"  OPEN   {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"  CLOSED {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"  Balance: {money(dv.account_balance)} | Total P/L: {money(dv.total_pl)} | Withdrawable: {money(dv.withdrawable)}")
                print(f"  SSI required margin: {money(dv.margin_req_ssi)} | SSI account ratio: {dv.account_ratio_ssi}")
        else:
            print("\n(Skipping derivatives: derivative_account is not configured)")

        # Step 4: Max buy / sell
        print(f"\n--- Max buy/sell {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"  @ reference price {price}: buy {mx.max_buy_quantity} | sell {mx.max_sell_quantity}"
                  f" | purchasing power {mx.purchasing_power} | margin {mx.margin_ratio}")
        mx_mkt = trading.trading.get_max_buy_sell_at_market_price(ACCOUNT_NO, SYMBOL)
        print(f"  @ market price: buy {mx_mkt.max_buy_quantity} | sell {mx_mkt.max_sell_quantity}")

Sample Code — Async

Async uses the same flow; the only differences are AsyncAuth / AsyncData / AsyncTrading and 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"--- Equity positions ({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 buy={pos.t1_buy_quantity} T1 sell={pos.t1_sell_quantity}")

            eq = await trading.portfolio.get_equity_ppmmr(ACCOUNT_NO)
            if eq is not None:
                print(f"Purchasing power: {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 buy {mx.max_buy_quantity} | Max sell {mx.max_sell_quantity}")


asyncio.run(main())

Easy-to-confuse fields

FieldUnitMeaning
EquityPosition.t1_buy_quantity, t2_buy_quantitySharesBuy quantity pending settlement at T1 / T2
EquityPosition.t1_sell_quantity, t2_sell_quantitySharesSell quantity pending settlement at T1 / T2
EquityPPMMR.buy_t0/t1/t2, sell_t0/t1/t2VNDBuy / sell amounts pending settlement
purchasing_power (PPMMR, Max buy/sell)VNDPurchasing power. Replaces purchase_power (deprecated)
margin_ratioRatioMargin ratio. For a derivative account, purchasing_power and margin_ratio are None, not 0

Notes

  • Equity positions only have T1/T2: an equity position has no T0 field. T0 buy/sell amounts only appear in PPMMR and the cash balance.
  • None is not an empty list: when the account has no positions (or the server returns 204), get_equity_positions and get_derivative_positions return None. get_equity_ppmmr, get_derivative_ppmmr, get_equity_balance and get_derivative_balance may also return None. Always check before iterating or reading attributes.
  • get_open_derivative_positions / get_closed_derivative_positions: in SDK 3.3.2 these may raise AttributeError when there are no positions. To be safe, call get_derivative_positions and check for None, as the sample does.
  • Position summary row (query_summary) is not available over REST, only through the Trading WebSocket, see Trading WebSocket.
  • Max buy/sell by price: get_max_buy_sell calculates at the price you pass (the sample uses the reference price); get_max_buy_sell_at_market_price calculates at the market price. The figures are estimates at the time of the call, not a promise that an order will match.
  • Data protection: use mask_account when printing account numbers, do not log whole responses, and never put real account numbers or balances in documentation.
  • Full model and method reference: PortfolioService (Python). Basic balance and buying power before placing orders: Account Balance.
  • To handle errors from these calls (rate limit, server errors, validation), see Error Handling & Rate Limits.

On this page