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")
    except DnseAPIError as e:
        print(f"API error {e.status_code}: {e.body}")

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.3.2.tar.gz (136.6 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.3.2-py3-none-any.whl (33.6 kB view details)

Uploaded Python 3

File details

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

File metadata

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

File hashes

Hashes for dnse-0.3.2.tar.gz
Algorithm Hash digest
SHA256 c50e8b0b4ee13873455d51226c7f60b1cdad7304201b769972e065295bc76287
MD5 6da2789084a82a6e3580888ac541b188
BLAKE2b-256 d85b28f32e273f2039bd8417c816651b039f1785bf11e7774d31da45eb5cb4be

See more details on using hashes here.

Provenance

The following attestation bundles were made for dnse-0.3.2.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.3.2-py3-none-any.whl.

File metadata

  • Download URL: dnse-0.3.2-py3-none-any.whl
  • Upload date:
  • Size: 33.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.3.2-py3-none-any.whl
Algorithm Hash digest
SHA256 5182dc2aa2ae2570b717c837a441c2f363b43dfa46c9749fa279b9146b864c8d
MD5 5d78f5db711722c8fce97b712b31b59c
BLAKE2b-256 f50905a55fe8381dc11f24a0407abc6428c2fcc630488000f7169f7ee03e009d

See more details on using hashes here.

Provenance

The following attestation bundles were made for dnse-0.3.2-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