Skip to main content

Python SDK for the DNSE Open API

Project description

dnse

Python SDK for the DNSE Open API. Supports sync and async HTTP via httpx, WebSocket streaming, HMAC-SHA256 authentication, Pydantic v2 response models, and strict typing.

Installation

pip install dnse

Quickstart

Authentication

from dnse import DnseClient

with DnseClient(api_key="your-api-key", api_secret="your-api-secret") as client:
    # Step 1: request OTP email
    client.registration.send_otp()

    # Step 2: verify OTP → sets trading token on client
    client.registration.verify_otp("123456")

    # Step 3: use resources
    accounts = client.accounts.list()
    orders = client.orders.list(accounts.accounts[0].id, market_type="STOCK", order_category="NORMAL")

Resource API

from dnse import BoardId, DnseClient, PlaceOrderRequest, UpdateOrderRequest

with DnseClient(api_key="k", api_secret="s") as client:
    # Accounts
    accts = client.accounts.list()
    balances = client.accounts.balances("0003979888")
    packages = client.accounts.loan_packages("0003979888", market_type="STOCK", symbol="HPG")
    ppse = client.accounts.ppse("0003979888", market_type="STOCK")

    # Orders (trading token required for mutations)
    client.registration.verify_otp("123456")

    # Get security info for valid price range
    secs = client.market.security_info("HPG", board_id=BoardId.ROUND_LOT)
    sec = secs[0]
    print(sec.ceiling_price, sec.floor_price)

    order = client.orders.place(PlaceOrderRequest(
        account_no="0003979888",
        symbol="HPG",
        side="NB",        # NB = buy, NS = sell
        order_type="LO",  # limit order
        quantity=100,
        price=sec.floor_price,
    ))

    detail = client.orders.get("0003979888", order.id or 0)
    active = client.orders.list("0003979888", market_type="STOCK", order_category="NORMAL")
    history = client.orders.history("0003979888", **{"from": "2026-01-01", "to": "2026-03-01"})

    # Update (cancel-then-replace) — returned id is the NEW order id
    updated = client.orders.update("0003979888", order.id or 0, UpdateOrderRequest(
        price=sec.ceiling_price,
        quantity=100,
    ))

    client.orders.cancel("0003979888", order.id or 0)

    # Deals
    deals = client.deals.list("0003979888", market_type="STOCK")

Async

from dnse import AsyncDnseClient

async with AsyncDnseClient(api_key="k", api_secret="s") as client:
    await client.registration.verify_otp("123456")
    orders = await client.orders.list("0003979888", market_type="STOCK", order_category="NORMAL")

WebSocket Streaming

Market Data

from dnse import DnseMarketStream

stream = DnseMarketStream(api_key="k", api_secret="s")

async def on_trade(msg):
    print(msg.symbol, msg.price, msg.volume)

async def on_quote(msg):
    print(msg.bid_price, msg.ask_price)

async def on_ohlc(msg):
    print(msg.open, msg.high, msg.low, msg.close)

async def on_expected_price(msg):
    print(msg.price)

async def on_secdef(msg):
    print(msg.ceiling, msg.floor, msg.ref_price)

stream.subscribe_trades(["HPG", "VIC"], on_trade)
stream.subscribe_quotes(["HPG"], on_quote)
stream.subscribe_ohlc(["HPG"], on_ohlc, timeframe="1m")
stream.subscribe_expected_price(["HPG"], on_expected_price)
stream.subscribe_security_def(["HPG"], on_secdef)

stream.run()  # blocking; call from a thread or wrap with asyncio.to_thread

Trading Events (private)

from dnse import DnseTradingStream

stream = DnseTradingStream(api_key="k", api_secret="s")

async def on_order(msg):
    print(msg.order_id, msg.status)

async def on_position(msg):
    print(msg.symbol, msg.qty, msg.avg_price)

async def on_account(msg):
    print(msg.account_no, msg.balance, msg.equity)

stream.subscribe_orders(on_order)
stream.subscribe_positions(on_position)
stream.subscribe_account(on_account)

stream.run()

Error Handling

from dnse import DnseClient, DnseAuthError, DnseRateLimitError, DnseSessionExpiredError, DnseAPIError
from dnse.stream.exceptions import DnseStreamError, DnseStreamAuthError, DnseStreamConnectionError

with DnseClient(api_key="k", api_secret="s") as client:
    try:
        client.orders.list("0003979888", market_type="STOCK", order_category="NORMAL")
    except DnseSessionExpiredError:
        # Trading token expired — re-verify OTP
        client.registration.verify_otp(input("OTP: "))
    except DnseAuthError:
        print("Authentication failed")
    except DnseRateLimitError as e:
        print(f"Rate limited. Retry after: {e.retry_after}s")
        if e.rate_limit_info:
            print(f"Remaining: {e.rate_limit_info.remaining}/{e.rate_limit_info.limit}")
    except DnseAPIError as e:
        print(f"API error {e.status_code}: {e.body}")

Rate Limiting

Catching 429 errors

from dnse import DnseClient, DnseRateLimitError

with DnseClient(api_key="k", api_secret="s") as client:
    try:
        client.accounts.list()
    except DnseRateLimitError as e:
        info = e.rate_limit_info
        if info:
            print(f"Limit: {info.limit}, Remaining: {info.remaining}")
            print(f"Resets in {info.seconds_until_reset:.0f}s")

Proactive quota checking

from dnse import DnseClient, parse_rate_limit_info

with DnseClient(api_key="k", api_secret="s") as client:
    response = client.get("/accounts")
    info = parse_rate_limit_info(dict(response.headers))
    if info and info.remaining is not None and info.remaining < 10:
        print("Running low on API quota")

OTP Methods

verify_otp accepts an otp_type keyword to select the OTP delivery method:

client.registration.verify_otp("123456", otp_type="email_otp")   # default
client.registration.verify_otp("123456", otp_type="smart_otp")

Stream Message Models

Type field Model class Description
"t" StreamTrade Trade tick (price, volume, side)
"te" StreamTradeExtra Trade tick with cumulative total
"q" StreamQuote Best bid/ask quote
"b" StreamOhlc OHLC candlestick bar
"e" StreamExpectedPrice Expected/reference price
"sd" StreamSecurityDef Security definition with price limits
"o" StreamOrder Order update (private)
"p" StreamPosition Position update (private)
"a" StreamAccountUpdate Account balance update (private)

Market Enums

from dnse.models.market import BoardId, MarketId, ProductGrpId, SecurityGroupId, SecurityStatus

Use these for type-safe filtering of SecurityDefinition fields returned by client.market.security_info().

Configuration

Parameter Default Description
api_key "" API key from DNSE portal
api_secret "" API secret for HMAC signing
base_url https://openapi.dnse.com.vn API base URL
timeout 30.0 Request timeout (seconds)
date_header "date" Date header name ("date" or "x-aux-date")

Development

uv sync
uv run pytest
uv run ruff check .
uv run pyright

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

dnse-0.4.0.tar.gz (143.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

dnse-0.4.0-py3-none-any.whl (34.6 kB view details)

Uploaded Python 3

File details

Details for the file dnse-0.4.0.tar.gz.

File metadata

  • Download URL: dnse-0.4.0.tar.gz
  • Upload date:
  • Size: 143.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for dnse-0.4.0.tar.gz
Algorithm Hash digest
SHA256 91545e4cd019dad760ae9d97922a94409670566eeb0cd0852f12e5a9c06b910c
MD5 631d5a879d74bfc83d5346d650b4a91b
BLAKE2b-256 ae87666f5d0260de999326a04017874cf0b102b619b75c74206f0d802635187c

See more details on using hashes here.

Provenance

The following attestation bundles were made for dnse-0.4.0.tar.gz:

Publisher: release.yml on dnse-tech/dnse-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file dnse-0.4.0-py3-none-any.whl.

File metadata

  • Download URL: dnse-0.4.0-py3-none-any.whl
  • Upload date:
  • Size: 34.6 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.7

File hashes

Hashes for dnse-0.4.0-py3-none-any.whl
Algorithm Hash digest
SHA256 47a446635a94b25de63e50546c58c2c6c09dd5673bffefb7524af924cf672f3c
MD5 c8e0f897cbd76f07c54507a079ba3efe
BLAKE2b-256 026dcfe578c0dd787016572417e2fd837dacd5b67ea6ea1d29ba94a3c631e697

See more details on using hashes here.

Provenance

The following attestation bundles were made for dnse-0.4.0-py3-none-any.whl:

Publisher: release.yml on dnse-tech/dnse-py

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page