Skip to content

Repository files navigation

DNSE OpenAPI Python SDK

English · Tiếng Việt · Tutorial dnse_auto_bot · Write a strategy

Official Python SDK for integrating with DNSE OpenAPI — REST trading, market data, broker APIs, WebSocket realtime, and derivative auto-trading helpers.

Table of Contents


Overview

DNSE OpenAPI is an API-first trading platform for brokerage, trading, margin, and market data.

This repository provides:

Layer Module Role
Official transport dnse_sdk.dnse (DNSEClient / TradingClient) Signed REST + base WebSocket (do not modify)
High-level facade dnse_auto_bot.sdk.DNSEClientV2 Recommended — REST + hardened WS + stock/derivative helpers
Order precision dnse_auto_bot.sdk.order_utils Tick/lot, IMAP OTP, stock & derivative limits
Models / resources dnse_auto_bot.sdk.models, resources, errors Opt-in Pydantic parse, resource namespaces, rate-limit errors
Engine dnse_auto_bot.engine Config, strategy registry, multi-symbol bot, paper/backtest, alerts/metrics
Derivative bot package dnse_auto_bot Public re-exports of SDK + engine

API catalog: dnse_api.md · Tiếng Việt.


Project layout

dnse_sdk/                     # Official DNSE OpenAPI package (dnse_sdk/dnse) — transport only
dnse_auto_bot/
  sdk/
    client.py                 # DNSEClientV2 facade
    trading.py                # DerivativeTrading domain layer
    order_utils.py            # Tick/lot, IMAP OTP
    hardened_ws.py            # HardenedTradingClient (PONG / reconnect / rotate)
    resources.py              # client.accounts|orders|positions|market|registration
    models/                   # Pydantic REST + stream models (opt-in typed=True)
    errors.py / enums.py / sanitize.py
  engine/                     # config, registry, bot, RiskGateway, OMS, paper, backtest, SWITCH
  strategies/                 # @register factories — see strategies/README.md
  ops/                        # watch_health (stale health.json TTL)
  configs/                    # default, paper, live_conservative, smc_paper, smc_live
  tests/                      # Unit + live integration (needs .env keys)
  examples/                   # Bot CLIs, OTP, TP/SL, smoke/reverify, SMC
dnse_api.md / dnse_api.vi.md  # DNSEClientV2 method reference
dnse-documents-api.md         # Live-verified request/response samples
examples/                     # Stock / V2 suite scripts (repo root)
trading-api/ …                # Also mirrored under dnse_sdk/ — raw REST / WS samples
requirements.txt              # urllib3, websockets, certifi, python-dotenv, msgpack, pydantic

Quick start

1. .env

Copy the shared template (no secrets) then fill your keys:

cp .env.example .env

Minimal required keys:

api-key=YOUR_API_KEY
secret-key=YOUR_API_SECRET

# Optional — required for auto OTP / place order
EMAIL_ADDRESS=you@gmail.com
EMAIL_PASSWORD=your_gmail_app_password
IMAP_HOST=imap.gmail.com

Bot defaults (paper mode, EMA, VN30F1M, …) are also listed in .env.example.
Full reference: Environment variables.
Never commit .env (gitignored). Commit / share .env.example only.

2. Dependencies

pip install -r requirements.txt

(urllib3, websockets, certifi, python-dotenv, msgpack, pydantic>=2.0)

3. First call

from dnse_auto_bot import DNSEClientV2

client = DNSEClientV2()  # reads .env
status, accounts = client.get_accounts()
# Opt-in typed parse via resource facade:
status, accounts_m = client.accounts.list(typed=True)
status, quote = client.get_latest_quote("HPG", board_id="G1")
print(status, accounts, quote)

4. Derivative bot facade

from dnse_auto_bot import DerivativeTrading

trader = DerivativeTrading(default_symbol="VN30F1M")
print(trader.resolve("VN30F1M"))
# → contract=41I1G8000, trading=VN30F1M

trader.ensure_trading_token()
snap = trader.snapshot()
# trader.buy(price=snap["quote"]["ask"], quantity=1)

Environment variables

Template: .env.example → copy to .env and fill secrets.
Engine JSON (dnse_auto_bot/configs/default.json) is the base; any set env var below overrides that config when load_config(apply_env=True).

DNSE API credentials

Variable Required Default Description
api-key Yes DNSE OpenAPI key. Aliases: API_KEY, DNSE_API_KEY
secret-key Yes DNSE OpenAPI secret. Aliases: SECRET_KEY, DNSE_API_SECRET, api-secret

Email OTP / IMAP (trading token)

Variable Required Default Description
EMAIL_ADDRESS For auto OTP Mailbox that receives DNSE OTP. Alias: IMAP_USER
EMAIL_PASSWORD For auto OTP IMAP password / Gmail App Password. Aliases: EMAIL_APP_PASSWORD, IMAP_PASSWORD
IMAP_HOST No imap.gmail.com IMAP server host
IMAP_PORT No 993 IMAP SSL port
TEST_SEND_EMAIL_OTP No 1 = feature-test suites may call send_email_otp
TRADING_OTP / DNSE_OTP No Paste OTP manually instead of reading IMAP (debug)

Auto-bot token flow (canonical): send_email_otp → IMAP read OTP → create_trading_token.
Live/smoke always use this when obtain_token=true. Do not rely on SKIP_TOKEN / TRADING_TOKEN shortcuts for the bot (TokenManager no longer seeds from TRADING_TOKEN).

Derivative auto-bot

Variable Required Default Description
BOT_CONFIG No dnse_auto_bot/configs/default.json Path to engine JSON config
BOT_MODE No from JSON (paper) paper | live | smoke | forever (forever → live + run_forever)
DERIV_SYMBOL No from JSON Futures symbolType (e.g. VN30F1M). Replaces config symbols with one symbol
BOT_STRATEGY No ema Registry: ema, ema_with_bracket, hold, smc_lq_ob, or custom @register(...)
EMA_FAST No 9 Fast EMA period (when strategy is EMA)
EMA_SLOW No 21 Slow EMA period
OHLC_RESOLUTION No 5 Bar size (minutes as string: 1, 5, 15, …) applied to all symbols
OHLC_LOOKBACK_DAYS No 5 OHLC history window for strategy context
BOT_RISK_PROFILE No balanced Portfolio preset — see Portfolio risk: conservative | balanced | aggressive | fixed_qty | custom
BOT_QTY No profile default Default order quantity (CLI / risk override)
BOT_MAX_QTY No profile default Max quantity per order
BOT_REQUIRE_AFFORDABLE No 1 0 = allow place even when qmax=0 (unsafe)
BOT_USE_EXCHANGE_TP_SL No live=1 1 = after fill, push nested SL/TP to DNSE pnl-configs + GET verify; 0 = bot-polled hard exits
BOT_POLL_SECONDS No 60 Seconds between evaluate loops
BOT_MAX_LOOPS No 1 Sync bot.run() loop count (run_forever ignores this)
BOT_ENABLE_WS No 1 0 = REST-only (no WebSocket)
BOT_WS_MAX_RETRIES No 0 WS reconnect attempts; 0 = unlimited
BOT_INSTANCE_LOCK No 1 0 = disable runtime/bot.lock single-instance guard
BOT_EXECUTION_WORKERS No 1 Async execution pipeline workers (run_forever)
BOT_AGGRESSIVE_TICKS No 0 Tick offset when building aggressive LO price
BOT_PASSIVE No 0 1 = use bid/ask passive price instead of aggressive
BOT_PAPER_SLIPPAGE_TICKS No 0 Simulated slippage (ticks) in paper mode
BOT_OHLC_PERSIST No 1 0 = disable candle cache runtime/ohlc/
BOT_OMS_RESUME No 1 0 = skip OMS resume on startup

Alerts

Variable Required Default Description
ALERTS_ENABLED No 1 0 = log only, no Telegram/Discord posts
ALERTS_MIN_LEVEL No warning Minimum level: debug | info | warning | error | critical
TELEGRAM_BOT_TOKEN No Telegram BotFather token
TELEGRAM_CHAT_ID No Destination chat / channel id (inbound /status /kill /pause /switch only from this id)
DISCORD_WEBHOOK_URL No Discord incoming webhook URL

Example scripts & low-level SDK

Variable Used by Description
SKIP_LIVE_ORDER derivative_ema_strategy.py 1 = signal/account checks only (no OTP / no order)
FORCE_DERIV_ORDER derivative_ema_strategy.py 1 = still post_order when qmax < 1
DERIV_QTY derivative_ema_strategy.py Quantity for that example (default 1)
RUN_LIVE_ORDER_FLOW examples/order_flow.py 1 = allow live stock order-flow path
ORDER_SYMBOL / ORDER_SIDE / ORDER_PRICE / ORDER_QTY examples/place_order_recommended.py Stock demo order params
DEBUG dnse.api true = verbose HTTP signing / debug
DNSE_API_VERSION dnse.api Override API version header
DATE_HEADER dnse.api Override date header name (default Date)

Recommended starter profiles

Safe paper (no OTP, no WS):

BOT_MODE=paper
BOT_ENABLE_WS=0
BOT_MAX_LOOPS=1
BOT_USE_EXCHANGE_TP_SL=0
SKIP_LIVE_ORDER=1

Live forever (needs ~35–40M remainSecure / qmax≥1 + IMAP email OTP):

BOT_MODE=forever
BOT_ENABLE_WS=1
BOT_RISK_PROFILE=conservative
BOT_QTY=1
BOT_REQUIRE_AFFORDABLE=1
BOT_USE_EXCHANGE_TP_SL=1
EMAIL_ADDRESS=you@gmail.com
EMAIL_PASSWORD=your_gmail_app_password

DNSEClientV2

DNSEClientV2 (dnse_auto_bot.sdk.client) is the recommended high-level entry point.
Full method list: dnse_api.md · Tiếng Việt.

Purpose

  • One class for REST + WebSocket over official dnse_sdk.dnse
  • Load api-key / secret-key from .env
  • Auto-resolve account_no / investor_id from get_accounts
  • Parse JSON (incl. sanitize broken metadata); optionally raise DNSEAPIError when raise_on_error=True
  • Auto-retry HTTP 429; classify auth / session-expired / rate-limit errors
  • set_trading_token + auto-inject on order / PnL / close mutations
  • Stock & derivative helpers; resource namespaces (client.accounts …)

Feature groups

Group Capabilities
Account get_accounts, balances (stock/derivative/bond/egg), loan packages, PPSE, default_account_no / investor_id / custody_code
Trading REST Paginated orders, detail/history, executions, positions, close price, corporate actions
Auth / token Email OTP, IMAP, obtain_trading_token, set_trading_token
Stock order flow Limits, normalize, build/place/modify/cancel_stock_order
Derivative helpers Resolve VN30F1M, limits, build/place, close_position
Exchange TP/SL Nested pnl-configs GET/POST, set_tp_sl_position, verify matcher
Market data Instruments, secdef, trade/quote, expected price, foreign, OHLC, sessions, working dates
Resources (DX) client.accounts / orders / positions / market / registration — optional typed=True
Broker get_list_care_by (broker role)
WebSocket HardenedTradingClient + full market/private channels; opt-in typed=True on subscribe
from dnse_auto_bot import DNSEClientV2, DNSEAPIError, DNSERateLimitError

client = DNSEClientV2(raise_on_error=True)
try:
    client.get_order_detail(order_id="1")
except DNSERateLimitError as e:
    print("retry_after", e.retry_after)
except DNSEAPIError as e:
    print(e.status, e.code, e.body)

Stock order flow

Helpers in DNSEClientV2 + dnse_auto_bot.sdk.order_utils for HOSE equities.

Method Description
get_order_limits(symbol) Floor / ceiling / tick / lot from secdef
normalize_order_price Display ↔ VND, align tick, clamp band
normalize_order_quantity Align to lot (default 100)
build_stock_order Build validated payload + PPSE warnings
obtain_trading_token Send email OTP → IMAP read → trading token
place_stock_order Full place pipeline
modify_stock_order / cancel_stock_order Modify / cancel with error wrapping
client = DNSEClientV2()
result = client.place_stock_order(
    "HQC",
    side="NB",
    price=2.03,          # display or VND (auto)
    quantity=100,
    obtain_token=True,   # OTP via email
)
print(result["order_id"], result["warnings"])

Price units (stocks)

Source Unit Example HPG
Market data / secdef Display (nghìn đồng) 22.55
Order / PPSE API VND (đồng) 22550

HOSE tick (VND): <10k → 10, <50k → 50, ≥50k → 100. Lot G1: 100.


DerivativeTrading

DerivativeTrading (dnse_auto_bot.sdk.trading) is the recommended facade for derivative auto-trading bots.

Why it exists

DNSE uses two symbol forms for futures:

Use case Symbol form Example
Orders, PPSE, latest quote/trade, secdef Contract code 41I1G8000
OHLC REST, loanPackages query, many WS OHLC symbolType VN30F1M

With DerivativeTrading, you only pass symbol="VN30F1M" — the class resolves and routes the correct code automatically.

Init

from dnse_auto_bot import DerivativeTrading, RiskConfig

trader = DerivativeTrading(
    default_symbol="VN30F1M",
    risk=RiskConfig(
        max_quantity=1,
        default_quantity=1,
        require_affordable=True,  # block place if qmax < qty
        dry_run=False,
    ),
)

Feature catalog

Symbol

Method Description
resolve(symbol) VN30F1M / 41I1G8000ResolvedSymbol (cached)
contract_of(symbol) Exchange contract code
trading_of(symbol) Friendly symbolType
list_instruments(enrich_quotes=True) All FU/DVX futures + optional live quotes

Auth

Method Description
ensure_trading_token(...) Canonical: send email OTP → IMAP → trading token (process cache)
trading_token Get / set current in-memory token

Market data

Method Description
get_limits(symbol) Floor / ceiling / tick 0.1 / lot 1
normalize_price(price, symbol) Align to derivative tick + band
get_quote(symbol) BBO (bid / ask)
get_trade(symbol) Last match
get_last_price(symbol) Best available last price
get_ohlc(symbol, resolution, lookback_days) Historical bars (DERIVATIVE)
get_session() FIO trading session
is_tradable(symbol) Halt / sanction check

Risk / account

Method Description
get_balances() remainSecure, usedSecure, …
get_loan_package_id(symbol) Cached derivative loan package
get_ppse(symbol, price) qmaxBuy / qmaxSell
can_afford(side, qty, symbol, price) Affordability check
check_risk(side, qty, ...) Hard risk gate list

Orders

Method Description
build_order(side, price, qty, symbol) Build payload (always uses contract code)
place / buy / sell Place LO (NB/NS)
modify(order_id, price=, quantity=) Replace order
cancel(order_id) Cancel one order
get_order / get_orders Detail / book (get_orders(..., raise_on_error=False) soft-fail)
wait_order(order_id, timeout=) Poll until terminal
cancel_open_orders(symbol=) Cancel working orders

Positions

Method Description
get_positions(raise_on_error=True) All derivative positions (False = soft-fail → [])
get_position(symbol) Position for one symbol
get_position_by_id(id) Detail by id
set_tp_sl_position(id, …) Nested pnl-configs POST + optional GET verify
get_tp_sl_position(id) GET current pnl-configs for a deal
close_position(id) Close by id
flatten(symbol=) Close all (optionally filtered)

Bot helpers

Method Description
snapshot(symbol) One-shot state; soft-fail positions / orders so flaky 400s do not kill loops
market_order_price(side, ...) Near-market LO suggestion
passive_order_price(side, ...) Floor/ceiling (safe API smoke)
events Internal event log

WebSocket (auto-resolve symbols)

Method Description
ws_connect / ws_disconnect Lifecycle
subscribe_quotes / subscribe_trades Contract symbols
subscribe_ohlc symbolType (VN30F1M)
subscribe_orders / subscribe_positions Private
subscribe_order_event / subscribe_position_event Private events

Usage examples

trader = DerivativeTrading(default_symbol="VN30F1M")
trader.ensure_trading_token()

# Place with friendly symbol only
trader.buy(price=1915.5, quantity=1, symbol="VN30F1M")
trader.sell(price=1916.0, quantity=1)  # uses default_symbol

# Manage
orders = trader.get_orders()
trader.modify(order_id, price=1915.7)
trader.cancel(order_id)
trader.flatten("VN30F1M")

Listed futures (live sample)

symbolType Contract Name
VN30F1M 41I1G8000 HĐTL VN30 1 tháng
VN30F2M 41I1G9000 HĐTL VN30 2 tháng
VN30F1Q 41I1GC000 HĐTL VN30 1 quý
VN30F2Q 41I1H3000 HĐTL VN30 2 quý
V100F1M 41I2G8000 HĐTL VN100 1 tháng
V100F2M 41I2G9000 HĐTL VN100 2 tháng
V100F1Q 41I2GC000 HĐTL VN100 1 quý
V100F2Q 41I2H3000 HĐTL VN100 2 quý

Derivative precision: index points, tick 0.1, lot 1.
Typical VN30 initial margin ≈ price × 100_000 × initialRate (~35M+ VND / contract).


DNSEClientV2 method catalog

Complete tables live in dnse_api.md. Summary:

Account & trading REST

Method Notes
get_accounts Investor + sub-accounts
get_balances stock / derivative / bond / egg sections
get_loan_packages(market_type, symbol) STOCK / DERIVATIVE
get_ppse(...) Buying / selling power
get_orders(..., page_index, page_size) Intraday book (paginated, API 2026-07-23+)
get_order_detail / get_order_history Detail / history (order id is string)
get_execution_detail Fills
get_positions / get_position_by_id STOCK | DERIVATIVE
get_pnl_configs_position / get_position_pnl_configs GET TP/SL (aliases)
post_pnl_configs_position / post_position_pnl_configs POST TP/SL
set_tp_sl_position / build_pnl_config_body / pnl_config_matches Nested OpenAPI + verify
get_close_price / get_corporate_action_history Close / CA
send_email_otp / create_trading_token / obtain_trading_token Email OTP auth
set_trading_token Store token for mutations
post_order / put_order / cancel_order Raw CRUD (POST /accounts/{accountNo}/orders)
close_position DERIVATIVE only

Market data REST

Method Notes
get_instruments / list_stock_instruments Filters / equity pagination
get_security_definition Band, halt flags
get_latest_trade / get_latest_quote Latest tick / BBO
get_trades / get_quotes / get_foreign_trading History (limit time range)
get_expected_price ATO/ATC expected (changelog 2026-08-06)
get_ohlc(bar_type, query) STOCK / DERIVATIVE
get_working_dates Calendar
get_lastest_session / get_trading_session Session (alias)
get_list_care_by Broker-only

Resource facades (optional typed)

st, bal = client.accounts.balances(typed=True)   # AccountBalanceResponse
st, book = client.orders.list(market_type="DERIVATIVE", typed=True)

Namespaces: client.accounts, client.orders, client.positions, client.market, client.registration.

Stock / derivative helpers

Same as before: get_order_limits, build/place_*_order, resolve_derivative_instrument, …
For bots, prefer DerivativeTrading.


WebSocket

V2 uses HardenedTradingClient (unlimited reconnect when max_retries<=0, proactive PONG ~90s, soft rotate ~7h). Connect once, then subscribe:

import asyncio
from dnse_auto_bot import DNSEClientV2

async def main():
    client = DNSEClientV2()
    await client.ws_connect()
    await client.subscribe_quotes(["HPG"], on_quote=print, board_id="G1")
    await client.subscribe_trades(["HPG"], on_trade=print, typed=True)  # StreamTrade
    await asyncio.sleep(10)
    await client.ws_disconnect()

asyncio.run(main())

Market channels

Method Channel purpose
subscribe_trades Tick
subscribe_trade_extra Tick + aggregates
subscribe_quotes BBO / top price
subscribe_ohlc Forming candle
subscribe_ohlc_closed Closed candle
subscribe_sec_def Security definition
subscribe_expected_price ATO / ATC expected
subscribe_foreign_trading Foreign flow
subscribe_market_index Index
subscribe_estimated_market_index Estimated index
subscribe_session Session events

Private channels

Method Purpose
subscribe_order_event Order updates
subscribe_position_event Position updates
subscribe_orders / subscribe_positions / subscribe_account Snapshots / streams
subscribe_broker_order_event / subscribe_broker_position_event Broker role

Also: on(event, handler), queue(event), unsubscribe(channel, symbols), ws_is_healthy().

Derivative WS via facade:

trader = DerivativeTrading(default_symbol="VN30F1M")
await trader.ws_connect()
await trader.subscribe_quotes()   # → 41I1G8000
await trader.subscribe_ohlc()     # → VN30F1M

Examples

Run from repo root (with .env configured):

# Full V2 regression
python examples/test_all_dnse_client_v2.py

# Basics
python examples/basic_usage.py
python examples/order_flow.py
python examples/place_order_recommended.py
python examples/trading_flow_live_test.py

# Derivatives / auto-bot
python dnse_auto_bot/examples/derivative_ema_strategy.py
python dnse_auto_bot/examples/derivative_auto_trade_bot.py paper
python dnse_auto_bot/examples/smc_full_bot_example.py verify
python dnse_auto_bot/examples/test_otp_email_token_flow.py
File Description
examples/test_all_dnse_client_v2.py Live test all V2 REST + WS methods
examples/basic_usage.py Short REST + WS demo
examples/order_flow.py Stock tick/lot precision
examples/place_order_recommended.py OTP → token → stock order
examples/trading_flow_live_test.py Place/modify/cancel + error matrix
dnse_auto_bot/examples/derivative_ema_strategy.py EMA walkthrough: OTP, nested TP/SL preview, soft-fail
dnse_auto_bot/examples/derivative_auto_trade_bot.py Engine CLI (paper / live / smoke / forever)
dnse_auto_bot/examples/smc_full_bot_example.py Full SMC strategy + verify pipeline
dnse_auto_bot/examples/test_otp_email_token_flow.py Canonical email OTP → trading token
dnse_auto_bot/examples/test_set_tp_sl_position.py Exchange pnl-configs set + verify
dnse_auto_bot/examples/check_derivative_affordability.py qmax / margin estimate across FU symbols

Bot modes (dnse_auto_bot/examples/derivative_auto_trade_bot.py)

Mode Behavior
paper OHLC + strategy + risk — no orders, no OTP, local PositionRisk
smoke Far LO (floor/ceiling) to exercise email OTP / place / detail
live Place/close on signals; default use_exchange_tp_sl after fill
forever Long-running asyncio + WS + reconcile + token refresh
python dnse_auto_bot/examples/derivative_auto_trade_bot.py paper
python dnse_auto_bot/examples/derivative_auto_trade_bot.py smoke
python dnse_auto_bot/examples/derivative_auto_trade_bot.py live
python dnse_auto_bot/examples/derivative_auto_trade_bot.py forever
python dnse_auto_bot/examples/smc_full_bot_example.py verify

Env knobs: see Environment variables (or .env.example).
Common: BOT_MODE, DERIV_SYMBOL, BOT_STRATEGY, BOT_RISK_PROFILE, BOT_USE_EXCHANGE_TP_SL, BOT_ENABLE_WS, EMAIL_*.
Tutorial: dnse_auto_bot_tutorial.md · Engine: dnse_auto_system_engine.md · Write a strategy: dnse_auto_bot/strategies/README.md.

Pluggable strategy + portfolio risk

Module Role
dnse_auto_bot/strategy.py Strategy ABC, TradeSignal (BUY/SELL/HOLD/CLOSE* → NB/NS), EmaStrategy, HoldStrategy
dnse_auto_bot/strategies/ Registry factories (@register("ema"), …) — authoring guide
dnse_auto_bot/risk.py Portfolio risk presets + kill-switch / sizing — see Portfolio risk
dnse_auto_bot/runtime_helpers.py Event JSONL, CSV journal, state store, health.json, instance lock
dnse_auto_bot/engine/bot.py Multi-symbol DerivativeAutoBot + execution pipeline
dnse_auto_bot/sdk/trading.py DerivativeTrading facade (symbol="VN30F1M" → contract auto)
dnse_auto_bot/configs/default.json Versioned engine config (mode, symbols, strategy, alerts)

Config + registry (easy strategy swap):

from dnse_auto_bot import DerivativeAutoBot, load_config, register
from dnse_auto_bot.strategy import Strategy, StrategyContext, TradeSignal, ACTION_BUY

@register("my_strategy")
def build_my(**params):
    class MyStrategy(Strategy):
        name = "my_strategy"
        def evaluate(self, ctx: StrategyContext) -> TradeSignal:
            if ctx.last_price and ctx.last_price > 0:
                return TradeSignal(ACTION_BUY, reason="demo", quantity=1)
            return TradeSignal.hold("no_price")
    return MyStrategy()

cfg = load_config(overrides={
    "mode": "paper",
    "symbols": [{"symbol": "VN30F1M", "resolution": "5"}],
    "strategy": {"name": "my_strategy", "params": {}},
    "risk_profile": "conservative",
})
DerivativeAutoBot(cfg).run()

Paper / backtest parity (same evaluate() as live — guide):

from dnse_auto_bot import create_strategy, run_backtest

result = run_backtest(
    create_strategy("ema", {"fast": 9, "slow": 21}),
    symbol="VN30F1M",
    contract="41I1G8000",
    ohlc=ohlc_dict,  # keys t/o/h/l/c/v
)

Ops: Telegram/Discord alerts (TELEGRAM_* / DISCORD_WEBHOOK_URL; inbound /status /kill /pause /switch), live metrics in health.json, trading-token refresh via TokenManager (email OTP only), exchange TP/SL via nested pnl-configs when use_exchange_tp_sl=true, restore_session + OHLC persist, soft-fail get_orders/get_positions in snapshot/reconcile. Switch strategy live via runtime/SWITCH when flat.

Margin note: VN30 initial margin ≈ price × 100_000 × initialRate (~35–40M VND / lot). Product leverage ≈ 1/initialRate (~5.4×); depositing cash raises qmax, not product leverage.


Portfolio risk

Source: dnse_auto_bot/risk.pyRiskProfile, PortfolioRiskConfig, PortfolioRiskManager.

Portfolio risk is the bot’s pre-trade + kill-switch layer. It is not the same as:

Layer Module Role
Portfolio risk risk.py Daily loss / drawdown pause, max positions, spread gate, qty sizing, opposite-signal flatten
Order risk (SDK) OrderRiskConfig on DerivativeTrading Per-order require_affordable, qmax, min remainSecure before place
Position risk engine/position_risk.py Per-deal SL/TP / trail / time-stop after fill (or deferred when use_exchange_tp_sl)
Market gates engine/market_gates.py Allowlist, session, tradable, quote age (before strategy)

What PortfolioRiskManager does

  1. check_kill(...) — each eval loop: pause new entries (and optionally flatten via flatten_on_kill) when daily loss, drawdown, max open positions, or min remainSecure trips.
  2. check_pretrade(...) — before OMS place: qty cap, one-position-per-symbol, max spread, price deviation vs last.
  3. size_quantity(...) — clamp requested qty to [0, max_quantity_per_order] using default_quantity when unset.

Day PnL for kill can be:

  • Secure mark (default): remainSecure − day_start_secure, or
  • Position PnL: realized + unrealized when use_position_pnl_for_kill=true.

Config:

"risk_profile": "conservative",
"risk_overrides": {
  "default_quantity": 1,
  "max_loss_per_trade_points": 8,
  "daily_loss_limit_pct": 2.0,
  "use_position_pnl_for_kill": true
}

Env: BOT_RISK_PROFILE=conservative (overrides JSON profile name). Overrides still come from JSON risk_overrides (+ CLI BOT_QTY / BOT_MAX_QTY / BOT_REQUIRE_AFFORDABLE in examples).

Supported profiles (RiskProfile)

Defaults from PortfolioRiskConfig.from_profile(...):

Profile Intent default / max qty max open pos daily loss % max DD % max spread (pts) Notable flags
conservative Live / capital preservation 1 / 1 1 2% 5% 1.0 closed_bar_only=true, no add-to-position
balanced Default paper / general 1 / 2 2 5% 10% 2.0 closed_bar_only=true
aggressive Higher throughput / research 1 / 5 5 10% 20% 5.0 allow_add_to_position=true, closed_bar_only=false
fixed_qty Minimal gates, fixed size 1 / 1 1 off (0) off (0) off (0) Still require_affordable=true, closed-bar, flatten opposite
custom Blank slate class defaults (qty 1, kill limits 0=off, …) same off off off Fill everything via risk_overrides

conservative

Safest preset for live money. One lot, one open position, tight spread filter, closed-bar entries only, low daily loss / drawdown pause. Used by configs/live_conservative.json and SMC live/paper configs (often with extra risk_overrides).

balanced

Default engine profile (configs/default.json, paper.json). Allows up to 2 lots / 2 open positions and looser daily loss than conservative — still closed-bar and affordable-gated.

aggressive

Wider limits for more concurrent risk and optional scale-in (allow_add_to_position). Can evaluate intrabar (closed_bar_only=false) — higher noise / more signals; only use if you understand the side effects.

fixed_qty

“Just trade this size” with kill % / drawdown / spread disabled (0). Still blocks unaffordable places when require_affordable=true, keeps one-position / closed-bar / flatten-on-opposite. Good for smoke or controlled size experiments; add kill limits via risk_overrides if needed.

custom

Starts from dataclass defaults (most kill switches off). You must set risk_overrides explicitly — otherwise almost no portfolio kill protection.

Parameter groups (any profile + overrides)

Group Fields Meaning
Sizing default_quantity, max_quantity_per_order, max_open_positions, one_position_per_symbol, allow_add_to_position How large / how many deals
Margin require_affordable, min_remain_secure, margin_buffer Affordability (also mirrored into SDK OrderRiskConfig)
Kill-switch daily_loss_limit_pct, daily_loss_limit_vnd, daily_realized_loss_limit_vnd, max_drawdown_pct, pause_on_kill, use_position_pnl_for_kill Pause entries when day/DD trips
Market quality max_spread_points, max_price_deviation_points Reject entry if BBO too wide / signal far from last
Execution policy closed_bar_only, flatten_on_opposite_signal Bar gate + reverse signal → close
Position brackets* max_loss_per_trade_points, max_hold_seconds, trailing_stop_points, point_value_vnd Fed into PositionRisk / kill PnL conversion (not exchange pnl-configs themselves)

*Hard SL/TP from the strategy still prefer exchange set_tp_sl_position when use_exchange_tp_sl=true; portfolio fields above complement that.

Example

from dnse_auto_bot import PortfolioRiskConfig, RiskProfile, DerivativeAutoBot, load_config

risk = PortfolioRiskConfig.from_profile(
    RiskProfile.CONSERVATIVE,
    default_quantity=1,
    daily_loss_limit_pct=1.5,
    use_position_pnl_for_kill=True,
)
cfg = load_config("dnse_auto_bot/configs/live_conservative.json")
DerivativeAutoBot(cfg, portfolio_risk=risk).run()

Vietnamese walkthrough: dnse_auto_bot_tutorial.md § Portfolio risk.

WebSocket longevity (DNSE rules)

DNSE docs: symbols uppercase; WS max 8 hours; server PING ~3 min, client must PONG within 1 min; client may send PONG proactively.

Implemented in dnse_auto_bot.sdk.hardened_ws.HardenedTradingClient (extends official dnse_sdk TradingClient):

  • Proactive application {"action":"pong"} every ~90s
  • Respond to server ping with pong
  • Soft-rotate session before 8h (max_session_seconds=7h)
  • max_retries <= 0 → unlimited reconnect + re-auth + re-subscribe
  • Symbols forced uppercase on subscribe
  • Bot watchdog restarts unhealthy sockets

Runtime artifacts: dnse_auto_bot/runtime/{events.jsonl,journal.csv,state.json,health.json,oms_active.json,ohlc/,KILL,SWITCH,bot.lock}.

Raw sample folders

Folder Contents
trading-api/ accounts, balances, PPSE, orders, OTP, post/put/cancel, positions, …
marketdata-api/ instruments, secdef, trades, quotes, OHLC, sessions, …
websocket-marketdata/ trade, quote, ohlc, index, foreign, session, …
websocket-trading/ order/position (incl. broker)
broker-api/ care-by list (broker role)

Price & precision notes

Stocks

  • Market data: display (22.55)
  • Orders / PPSE: VND (22550)
  • Always use normalize_order_price / place_stock_order

Derivatives

  • Prices: index points (e.g. 1915.5)
  • Tick 0.1, lot 1
  • Prefer DerivativeTrading so VN30F1M auto-maps to 41I1G8000 for orders
  • Using VN30F1M directly in post_order can return STOCK_PRICE_UNDEFINED
  • Insufficient margin → order accepted then Rejected / PURCHASING_POWER_NOT_ENOUGH
  • Exchange TP/SL: nested takeProfit / stopLoss objects; absolute prices convert to positive deltaPrice from averagePrice + side
  • Soft-fail: prefer get_orders(raise_on_error=False) / get_positions(raise_on_error=False) inside long-running bots

Common errors (live)

Code Meaning
INVALID_TRADING_TOKEN Missing / bad token
INVALID_OTP / OA-301 Bad or expired OTP
QMAX_EXCEED / PURCHASING_POWER_NOT_ENOUGH Not enough buying power
CAN_NOT_PLACE_ORDER_ON_HALTED_SYMBOL Symbol halted
EDIT_ORDER_INVALID_SESSION Modify outside continuous session
UNSUPPORTED_MARKET_TYPE close_position on STOCK
BROKER-API-004 Broker API without broker role
STOCK_PRICE_UNDEFINED Wrong derivative symbol form on order

Installation

Requirements

  • Python 3.8+

Install from PyPI (published package)

pip install openapi-sdk
pip install --upgrade openapi-sdk

Local workspace

pip install -r requirements.txt

Run from repo root so dnse_auto_bot and dnse_sdk import cleanly (from dnse_sdk.dnse import DNSEClient).


Low-level SDK usage

Prefer V2 for apps. Raw official client:

from dnse_sdk.dnse import DNSEClient

client = DNSEClient(
    api_key="your_api_key",
    api_secret="your_api_secret",
    base_url="https://openapi.dnse.com.vn",
    api_version="2026-07-23",
)

status, body = client.get_accounts(dry_run=False)
print(status, body)

The SDK sends the API version in the version header. If api_version is omitted, it defaults to the package default; override with DNSE_API_VERSION if needed.

Trading API samples (trading-api/)

Script Description
get_accounts.py Sub-accounts under the API key
get_balances.py Asset balances
get_loan_packages.py Loan packages (needed to place)
get_ppse.py Buying / selling power
get_orders.py Intraday order book
get_order_detail.py Order by id
get_order_history.py Historical orders
get_corporate_action_history.py Corporate actions
get_execution_detail.py Executions
get_positions.py Positions
get_position_by_id.py Position by id
close_position.py Close position (derivative)
send_email_otp.py Request email OTP
create_trading_token.py Create trading token
post_order.py Place order
cancel_order.py Cancel order
put_order.py Modify order

Market data samples (marketdata-api/)

Script Description
get_security_definition.py Secdef / bands
get_instruments.py Instrument list
get_trades.py Historical trades
get_latest_trade.py Latest trade
get_quotes.py / get_latest_quote.py Quotes
get_ohlc.py OHLC history
get_close_price.py Close price
get_working_dates.py Working dates
get_trading_session.py / get_lastest_session.py Session
get_foreign_trading.py Foreign trading

WebSocket market samples

Script Description
sec_def.py Security definition stream
quote.py BBO
trade.py Ticks
trade_extra.py Ticks + aggregates
ohlc.py / ohlc_closed.py Candles
expected_price.py ATO/ATC expected
foreign_investor.py Foreign flow
market_index.py / estimated_market_index.py Index
session.py Session events

Dry run

Set dry_run=True on REST methods to preview the signed request without sending it:

client.get_accounts(dry_run=True)

Full API docs

Verified against live account (derivativeAccount: ACTIVE); changelog surface includes 2026-07-23 / 2026-08-06 (paginated orders, string order ids, expected-price, nested pnl-configs, bond/egg balances).

About

api for dnse light speed v2

Resources

Stars

15 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages