dnse-sdk 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: dnse-sdk
3
+ Version: 0.1.0
4
+ Summary: DNSE Python SDK
5
+ Author: DNSE
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+
9
+ # DNSE Python SDK
10
+
11
+ This SDK provides a simple client for calling the DNSE APIs with HMAC signatures.
12
+
13
+ ## Requirements
14
+
15
+ - Python 3.8+
16
+
17
+ ## Usage
18
+
19
+ ```python
20
+ from dnse import DNSEClient
21
+
22
+ client = DNSEClient(
23
+ api_key="replace-with-api-key",
24
+ api_secret="replace-with-api-secret",
25
+ base_url="https://openapi.dnse.com.vn",
26
+ )
27
+
28
+ status, body = client.get_accounts(dry_run=False)
29
+ print(status, body)
30
+ ```
31
+
32
+ ## Dry run
33
+
34
+ Set `dry_run=True` on any API call to print the request and skip the network call.
35
+
36
+ ## Examples
37
+
38
+ Run any example from the `sdk/python/trading-api` directory:
39
+
40
+ ```bash
41
+ python sdk/python/trading-api/get_accounts.py
42
+ ```
43
+
44
+ Available examples:
45
+
46
+ - cancel_order.py
47
+ - create_trading_token.py
48
+ - get_accounts.py
49
+ - get_balances.py
50
+ - get_deals.py
51
+ - get_loan_packages.py
52
+ - get_ohlc.py
53
+ - get_order_detail.py
54
+ - get_order_history.py
55
+ - get_orders.py
56
+ - get_ppse.py
57
+ - get_security_definition.py
58
+ - post_order.py
59
+ - put_order.py
60
+ - send_email_otp.py
@@ -0,0 +1,52 @@
1
+ # DNSE Python SDK
2
+
3
+ This SDK provides a simple client for calling the DNSE APIs with HMAC signatures.
4
+
5
+ ## Requirements
6
+
7
+ - Python 3.8+
8
+
9
+ ## Usage
10
+
11
+ ```python
12
+ from dnse import DNSEClient
13
+
14
+ client = DNSEClient(
15
+ api_key="replace-with-api-key",
16
+ api_secret="replace-with-api-secret",
17
+ base_url="https://openapi.dnse.com.vn",
18
+ )
19
+
20
+ status, body = client.get_accounts(dry_run=False)
21
+ print(status, body)
22
+ ```
23
+
24
+ ## Dry run
25
+
26
+ Set `dry_run=True` on any API call to print the request and skip the network call.
27
+
28
+ ## Examples
29
+
30
+ Run any example from the `sdk/python/trading-api` directory:
31
+
32
+ ```bash
33
+ python sdk/python/trading-api/get_accounts.py
34
+ ```
35
+
36
+ Available examples:
37
+
38
+ - cancel_order.py
39
+ - create_trading_token.py
40
+ - get_accounts.py
41
+ - get_balances.py
42
+ - get_deals.py
43
+ - get_loan_packages.py
44
+ - get_ohlc.py
45
+ - get_order_detail.py
46
+ - get_order_history.py
47
+ - get_orders.py
48
+ - get_ppse.py
49
+ - get_security_definition.py
50
+ - post_order.py
51
+ - put_order.py
52
+ - send_email_otp.py
@@ -0,0 +1,4 @@
1
+ #!/usr/bin/env python3
2
+ from .client import DNSEClient
3
+
4
+ __all__ = ["DNSEClient"]
@@ -0,0 +1,291 @@
1
+ #!/usr/bin/env python3
2
+ import json
3
+ import os
4
+ from urllib import parse, request
5
+
6
+ from .common import build_signature, get_date_header_name
7
+
8
+
9
+ class DNSEClient:
10
+ def __init__(
11
+ self,
12
+ api_key,
13
+ api_secret,
14
+ base_url="https://openapi.dnse.com.vn",
15
+ algorithm="hmac-sha256",
16
+ hmac_nonce_enabled=True,
17
+ ):
18
+ self._api_key = api_key
19
+ self._api_secret = api_secret
20
+ self._base_url = base_url.rstrip("/")
21
+ self._algorithm = algorithm
22
+ self._hmac_nonce_enabled = hmac_nonce_enabled
23
+
24
+ def get_accounts(self, dry_run=False):
25
+ return self._request("GET", "/accounts", dry_run=dry_run)
26
+
27
+ def get_balances(self, account_no, dry_run=False):
28
+ return self._request("GET", f"/accounts/{account_no}/balances", dry_run=dry_run)
29
+
30
+ def get_loan_packages(self, account_no, market_type, symbol=None, dry_run=False):
31
+ query = {"marketType": market_type}
32
+ if symbol:
33
+ query["symbol"] = symbol
34
+ return self._request(
35
+ "GET",
36
+ f"/accounts/{account_no}/loan-packages",
37
+ query=query,
38
+ dry_run=dry_run,
39
+ )
40
+
41
+ def get_deals(self, account_no, market_type, dry_run=False):
42
+ return self._request(
43
+ "GET",
44
+ f"/accounts/{account_no}/deals",
45
+ query={"marketType": market_type},
46
+ dry_run=dry_run,
47
+ )
48
+
49
+ def get_orders(self, account_no, market_type, order_category=None, dry_run=False):
50
+ query = {"marketType": market_type}
51
+ if order_category:
52
+ query["orderCategory"] = order_category
53
+ return self._request(
54
+ "GET",
55
+ f"/accounts/{account_no}/orders",
56
+ query=query,
57
+ dry_run=dry_run,
58
+ )
59
+
60
+ def get_order_detail(self, account_no, order_id, market_type, order_category=None, dry_run=False):
61
+ query = {"marketType": market_type}
62
+ if order_category:
63
+ query["orderCategory"] = order_category
64
+ return self._request(
65
+ "GET",
66
+ f"/accounts/{account_no}/orders/{order_id}",
67
+ query=query,
68
+ dry_run=dry_run,
69
+ )
70
+
71
+ def get_order_history(
72
+ self,
73
+ account_no,
74
+ market_type,
75
+ from_date=None,
76
+ to_date=None,
77
+ page_size=None,
78
+ page_index=None,
79
+ dry_run=False,
80
+ ):
81
+ query = {"marketType": market_type}
82
+ if from_date:
83
+ query["from"] = from_date
84
+ if to_date:
85
+ query["to"] = to_date
86
+ if page_size is not None:
87
+ query["pageSize"] = page_size
88
+ if page_index is not None:
89
+ query["pageIndex"] = page_index
90
+ return self._request(
91
+ "GET",
92
+ f"/accounts/{account_no}/orders/history",
93
+ query=query,
94
+ dry_run=dry_run,
95
+ )
96
+
97
+ def get_ppse(self, account_no, market_type, symbol, price, loan_package_id, dry_run=False):
98
+ return self._request(
99
+ "GET",
100
+ f"/accounts/{account_no}/ppse",
101
+ query={
102
+ "marketType": market_type,
103
+ "symbol": symbol,
104
+ "price": str(price),
105
+ "loanPackageId": str(loan_package_id),
106
+ },
107
+ dry_run=dry_run,
108
+ )
109
+
110
+ def get_security_definition(self, symbol, board_id=None, dry_run=False):
111
+ query = {}
112
+ if board_id:
113
+ query["boardId"] = board_id
114
+ return self._request(
115
+ "GET",
116
+ f"/price/secdef/{symbol}",
117
+ query=query if query else None,
118
+ dry_run=dry_run,
119
+ )
120
+
121
+ def get_ohlc(self, bar_type, query=None, dry_run=False):
122
+ request_query = dict(query or {})
123
+ request_query["type"] = bar_type
124
+ return self._request(
125
+ "GET",
126
+ "/price/ohlc",
127
+ query=request_query,
128
+ dry_run=dry_run,
129
+ )
130
+
131
+ def post_order(self, market_type, payload, trading_token, order_category="NORMAL", dry_run=False):
132
+ headers = {"trading-token": trading_token}
133
+ query = {"marketType": market_type}
134
+ if order_category:
135
+ query["orderCategory"] = order_category
136
+ return self._request(
137
+ "POST",
138
+ "/accounts/orders",
139
+ query=query,
140
+ body=payload,
141
+ headers=headers,
142
+ dry_run=dry_run,
143
+ )
144
+
145
+ def put_order(
146
+ self,
147
+ account_no,
148
+ order_id,
149
+ market_type,
150
+ payload,
151
+ trading_token,
152
+ order_category=None,
153
+ dry_run=False,
154
+ ):
155
+ headers = {"trading-token": trading_token}
156
+ query = {"marketType": market_type}
157
+ if order_category:
158
+ query["orderCategory"] = order_category
159
+ return self._request(
160
+ "PUT",
161
+ f"/accounts/{account_no}/orders/{order_id}",
162
+ query=query,
163
+ body=payload,
164
+ headers=headers,
165
+ dry_run=dry_run,
166
+ )
167
+
168
+ def cancel_order(
169
+ self,
170
+ account_no,
171
+ order_id,
172
+ market_type,
173
+ trading_token,
174
+ order_category=None,
175
+ dry_run=False,
176
+ ):
177
+ headers = {"trading-token": trading_token}
178
+ query = {"marketType": market_type}
179
+ if order_category:
180
+ query["orderCategory"] = order_category
181
+ return self._request(
182
+ "DELETE",
183
+ f"/accounts/{account_no}/orders/{order_id}",
184
+ query=query,
185
+ headers=headers,
186
+ dry_run=dry_run,
187
+ )
188
+
189
+ def create_trading_token(self, otp_type, passcode, dry_run=False):
190
+ return self._request(
191
+ "POST",
192
+ "/registration/trading-token",
193
+ body={"otpType": otp_type, "passcode": passcode},
194
+ dry_run=dry_run,
195
+ )
196
+
197
+ def send_email_otp(self, dry_run=False):
198
+ return self._request(
199
+ "POST",
200
+ "/registration/send-email-otp",
201
+ dry_run=dry_run,
202
+ )
203
+
204
+ def close_deal(self, deal_id, account_no, market_type, payload, trading_token, dry_run=False):
205
+ headers = {"trading-token": trading_token}
206
+ query = {"marketType": market_type}
207
+ return self._request(
208
+ "POST",
209
+ f"/accounts/{account_no}/deals/{deal_id}/close",
210
+ query=query,
211
+ body=payload,
212
+ headers=headers,
213
+ dry_run=dry_run,
214
+ )
215
+
216
+ def _request(self, method, path, query=None, body=None, headers=None, dry_run=False):
217
+ debug = os.getenv("DEBUG", "").lower() == "true"
218
+ url = self._build_url(path, query)
219
+ date_value, signature_header_value = self._signature_headers(method, path)
220
+ date_header_name = get_date_header_name()
221
+
222
+ data = None
223
+ if body is not None:
224
+ data = json.dumps(body).encode("utf-8")
225
+
226
+ req = request.Request(url, data=data, method=method)
227
+ req.add_header(date_header_name, date_value)
228
+ req.add_header("X-Signature", signature_header_value)
229
+ req.add_header("x-api-key", self._api_key)
230
+
231
+ if body is not None:
232
+ req.add_header("Content-Type", "application/json")
233
+
234
+ if headers:
235
+ for key, value in headers.items():
236
+ req.add_header(key, value)
237
+
238
+ if debug or dry_run:
239
+ prefix = "DRY RUN" if dry_run else "DEBUG"
240
+ print(f"{prefix} url:", url)
241
+ print(f"{prefix} method:", method)
242
+ print(f"{prefix} query_params:", query or {})
243
+ print(f"{prefix} headers:", dict(req.header_items()))
244
+ print(f"{prefix} body:", body)
245
+
246
+ if dry_run:
247
+ return None, None
248
+
249
+ try:
250
+ with request.urlopen(req) as resp:
251
+ body_text = resp.read().decode("utf-8")
252
+ return resp.status, body_text
253
+ except request.HTTPError as err:
254
+ body_text = err.read().decode("utf-8") if err.fp else ""
255
+ return err.code, body_text
256
+
257
+ def _build_url(self, path, query):
258
+ url = f"{self._base_url}{path}"
259
+ if query:
260
+ url = f"{url}?{parse.urlencode(query)}"
261
+ return url
262
+
263
+ def _signature_headers(self, method, path):
264
+ date_value = self._date_header()
265
+ nonce = None
266
+ if self._hmac_nonce_enabled:
267
+ import uuid
268
+
269
+ nonce = uuid.uuid4().hex
270
+
271
+ headers_list, signature = build_signature(
272
+ self._api_secret,
273
+ method,
274
+ path,
275
+ date_value,
276
+ self._algorithm,
277
+ nonce=nonce,
278
+ header_name=get_date_header_name(),
279
+ )
280
+ signature_header_value = (
281
+ f'Signature keyId="{self._api_key}",algorithm="{self._algorithm}",'
282
+ f'headers="{headers_list}",signature="{signature}"'
283
+ )
284
+ if nonce:
285
+ signature_header_value += f',nonce="{nonce}"'
286
+ return date_value, signature_header_value
287
+
288
+ def _date_header(self):
289
+ from datetime import datetime, timezone
290
+
291
+ return datetime.now(timezone.utc).strftime("%a, %d %b %Y %H:%M:%S %z")
@@ -0,0 +1,103 @@
1
+ #!/usr/bin/env python3
2
+ import base64
3
+ import hashlib
4
+ import hmac
5
+ import json
6
+ import os
7
+ from datetime import datetime, timezone
8
+ from urllib import parse, request
9
+ from uuid import uuid4
10
+
11
+
12
+ def get_date_header_name():
13
+ return os.getenv("DATE_HEADER", "Date")
14
+
15
+
16
+ def build_signature(secret, method, path, date_value, algorithm, nonce=None, header_name=None):
17
+ header_name = header_name or get_date_header_name()
18
+ header_key = header_name.lower()
19
+ headers = f"(request-target) {header_key}"
20
+ signature_string = (
21
+ f"(request-target): {method.lower()} {path}\n" f"{header_key}: {date_value}"
22
+ )
23
+ if nonce:
24
+ signature_string += f"\nnonce: {nonce}"
25
+
26
+ if algorithm == "hmac-sha256":
27
+ digestmod = hashlib.sha256
28
+ elif algorithm == "hmac-sha384":
29
+ digestmod = hashlib.sha384
30
+ elif algorithm == "hmac-sha512":
31
+ digestmod = hashlib.sha512
32
+ else:
33
+ digestmod = hashlib.sha1
34
+
35
+ mac = hmac.new(secret.encode("utf-8"), signature_string.encode("utf-8"), digestmod)
36
+ encoded = base64.b64encode(mac.digest()).decode("utf-8")
37
+ escaped = parse.quote(encoded, safe="")
38
+
39
+ return headers, escaped
40
+
41
+
42
+ def send_signed_request(
43
+ url,
44
+ method,
45
+ headers,
46
+ body,
47
+ api_key,
48
+ api_secret,
49
+ algorithm="hmac-sha256",
50
+ hmac_nonce_enabled=True,
51
+ ):
52
+ debug = os.getenv("DEBUG", "").lower() == "true"
53
+ parsed = parse.urlparse(url)
54
+ path = parsed.path
55
+ date_value = datetime.now(timezone.utc).strftime("%a, %d %b %Y %H:%M:%S %z")
56
+ date_header_name = get_date_header_name()
57
+
58
+ nonce = uuid4().hex if hmac_nonce_enabled else None
59
+ headers_list, signature = build_signature(
60
+ api_secret,
61
+ method,
62
+ path,
63
+ date_value,
64
+ algorithm,
65
+ nonce=nonce,
66
+ header_name=date_header_name,
67
+ )
68
+ signature_header_value = (
69
+ f'Signature keyId="{api_key}",algorithm="{algorithm}",'
70
+ f'headers="{headers_list}",signature="{signature}"'
71
+ )
72
+ if nonce:
73
+ signature_header_value += f',nonce="{nonce}"'
74
+
75
+ data = None
76
+ if body is not None:
77
+ data = json.dumps(body).encode("utf-8")
78
+ headers.setdefault("Content-Type", "application/json")
79
+
80
+ req = request.Request(url, data=data, method=method)
81
+ req.add_header(date_header_name, date_value)
82
+ req.add_header("X-Signature", signature_header_value)
83
+
84
+ for key, value in headers.items():
85
+ req.add_header(key, value)
86
+
87
+ if debug:
88
+ query_params = parse.parse_qs(parsed.query)
89
+ print("DEBUG url:", url)
90
+ print("DEBUG method:", method)
91
+ print("DEBUG query_params:", query_params)
92
+ print("DEBUG headers:", dict(req.header_items()))
93
+ print("DEBUG body:", body)
94
+
95
+ try:
96
+ with request.urlopen(req) as resp:
97
+ body_text = resp.read().decode("utf-8")
98
+ print(body_text)
99
+ except request.HTTPError as err:
100
+ body_text = err.read().decode("utf-8") if err.fp else ""
101
+ print(f"HTTP {err.code} {err.reason}")
102
+ if body_text:
103
+ print(body_text)
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: dnse-sdk
3
+ Version: 0.1.0
4
+ Summary: DNSE Python SDK
5
+ Author: DNSE
6
+ Requires-Python: >=3.8
7
+ Description-Content-Type: text/markdown
8
+
9
+ # DNSE Python SDK
10
+
11
+ This SDK provides a simple client for calling the DNSE APIs with HMAC signatures.
12
+
13
+ ## Requirements
14
+
15
+ - Python 3.8+
16
+
17
+ ## Usage
18
+
19
+ ```python
20
+ from dnse import DNSEClient
21
+
22
+ client = DNSEClient(
23
+ api_key="replace-with-api-key",
24
+ api_secret="replace-with-api-secret",
25
+ base_url="https://openapi.dnse.com.vn",
26
+ )
27
+
28
+ status, body = client.get_accounts(dry_run=False)
29
+ print(status, body)
30
+ ```
31
+
32
+ ## Dry run
33
+
34
+ Set `dry_run=True` on any API call to print the request and skip the network call.
35
+
36
+ ## Examples
37
+
38
+ Run any example from the `sdk/python/trading-api` directory:
39
+
40
+ ```bash
41
+ python sdk/python/trading-api/get_accounts.py
42
+ ```
43
+
44
+ Available examples:
45
+
46
+ - cancel_order.py
47
+ - create_trading_token.py
48
+ - get_accounts.py
49
+ - get_balances.py
50
+ - get_deals.py
51
+ - get_loan_packages.py
52
+ - get_ohlc.py
53
+ - get_order_detail.py
54
+ - get_order_history.py
55
+ - get_orders.py
56
+ - get_ppse.py
57
+ - get_security_definition.py
58
+ - post_order.py
59
+ - put_order.py
60
+ - send_email_otp.py
@@ -0,0 +1,11 @@
1
+ README.md
2
+ pyproject.toml
3
+ setup.cfg
4
+ setup.py
5
+ dnse/__init__.py
6
+ dnse/client.py
7
+ dnse/common.py
8
+ dnse_sdk.egg-info/PKG-INFO
9
+ dnse_sdk.egg-info/SOURCES.txt
10
+ dnse_sdk.egg-info/dependency_links.txt
11
+ dnse_sdk.egg-info/top_level.txt
@@ -0,0 +1 @@
1
+ dnse
@@ -0,0 +1,3 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68", "wheel"]
3
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1,22 @@
1
+ [metadata]
2
+ name = dnse-sdk
3
+ version = 0.1.0
4
+ description = DNSE Python SDK
5
+ long_description = file: README.md
6
+ long_description_content_type = text/markdown
7
+ author = DNSE
8
+
9
+ [options]
10
+ packages = find:
11
+ python_requires = >=3.8
12
+ include_package_data = True
13
+
14
+ [options.packages.find]
15
+ include =
16
+ dnse
17
+ dnse.*
18
+
19
+ [egg_info]
20
+ tag_build =
21
+ tag_date = 0
22
+
@@ -0,0 +1,4 @@
1
+ from setuptools import setup
2
+
3
+
4
+ setup()