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.3.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.3-py3-none-any.whl (33.6 kB view details)

Uploaded Python 3

File details

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

File metadata

  • Download URL: dnse-0.3.3.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.3.tar.gz
Algorithm Hash digest
SHA256 fec6320c9c5fbc986cc7dc719a5ba2f7800a60b8e20e1044f6d97bec90aafd51
MD5 6ccca4471fd7d8481d87fce30e219a19
BLAKE2b-256 947b1e6f74ca57c6b0ddd61013ea3259d8ee2926a7f80d8603892ca1862c5526

See more details on using hashes here.

Provenance

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

File metadata

  • Download URL: dnse-0.3.3-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.3-py3-none-any.whl
Algorithm Hash digest
SHA256 b295faa71e723cfc9ac3a0439cce1b3598e9d159806e52d3cc98840d7efabf09
MD5 d8741faafe2e9cff2a739959699c0087
BLAKE2b-256 de41d686b48248d1d8ca71fe80e3c70abdf57907d2ce5229b86142b89664e646

See more details on using hashes here.

Provenance

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