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/sellget_equity_positions(account): positions, with buy/sell quantity pending at T1/T2get_equity_ppmmr(account): purchasing power, margin ratio, buy/sell amounts at T0/T1/T2- If you have a derivative account:
get_derivative_positionsandget_derivative_ppmmr get_max_buy_sell(account, symbol, price)andget_max_buy_sell_at_market_price(account, symbol)
Samples in the repo
| Language | File |
|---|---|
| Python | python/sample_15_positions_ppmmr.py, python/sample_15_positions_ppmmr_async.py |
| Node.js | node/sample_15_positions_ppmmr.js |
| .NET | dotnet/Sample15PositionsPpmmr.cs |
| Go | go/sample_15_positions_ppmmr.go |
This page uses Python for illustration. Repo: ssi-fastconnect-v3-tutorials.
Prerequisites
- Credentials and account number (
equity_account) inconfig.json, see Tutorial 1. The derivative account is optional (derivative_account). The repo samples load the config throughconfig.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.pyfrom the repo:mask_account(masks the account number when printing) andrequire_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.
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.
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
| Field | Unit | Meaning |
|---|---|---|
EquityPosition.t1_buy_quantity, t2_buy_quantity | Shares | Buy quantity pending settlement at T1 / T2 |
EquityPosition.t1_sell_quantity, t2_sell_quantity | Shares | Sell quantity pending settlement at T1 / T2 |
EquityPPMMR.buy_t0/t1/t2, sell_t0/t1/t2 | VND | Buy / sell amounts pending settlement |
purchasing_power (PPMMR, Max buy/sell) | VND | Purchasing power. Replaces purchase_power (deprecated) |
margin_ratio | Ratio | Margin 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.
Noneis not an empty list: when the account has no positions (or the server returns204),get_equity_positionsandget_derivative_positionsreturnNone.get_equity_ppmmr,get_derivative_ppmmr,get_equity_balanceandget_derivative_balancemay also returnNone. Always check before iterating or reading attributes.get_open_derivative_positions/get_closed_derivative_positions: in SDK 3.3.2 these may raiseAttributeErrorwhen there are no positions. To be safe, callget_derivative_positionsand check forNone, 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_sellcalculates at the price you pass (the sample uses the reference price);get_max_buy_sell_at_market_pricecalculates 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_accountwhen 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.