Skip to content

Commit 84813a0

Browse files
committed
feat: Add support for Webull JP
1 parent 9724cfb commit 84813a0

17 files changed

Lines changed: 221 additions & 40 deletions

‎README.md‎

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
# Webull OpenAPI Python SDK
22

3+
Note: This is the new version of the Webull SDK, currently applicable only to Webull Hong Kong, Webull US, or Webull JP customers.
4+
35
Webull OpenAPI aims to provide quantitative trading investors with convenient, fast and secure services. Webull aims to help every quant traders achieve flexible and changeable trading or market strategies.
46

57
The main function:
@@ -20,6 +22,7 @@ Subscription to real-time information: Subscribe to order status changes, market
2022
|-----------|-------------------------------------------------|
2123
| Webull HK | https://www.webull.hk/open-api |
2224
| Webull US | https://www.webull.com/center#openApiManagement |
25+
| Webull JP | https://www.webull.co.jp/center/openapi/manage |
2326

2427
- Requires Python 3.7 through 3.11.
2528

@@ -35,7 +38,8 @@ The bottom layer of Webull OpenAPI provides three protocols, HTTP / GRPC / MQTT,
3538

3639
## Developer documentation
3740

38-
| Broker | Link |
39-
|-----------|----------------------------------------|
40-
| Webull HK | https://developer.webull.hk/apis/docs |
41-
| Webull US | https://developer.webull.com/apis/docs |
41+
| Broker | Link |
42+
|-----------|------------------------------------------|
43+
| Webull HK | https://developer.webull.hk/apis/docs |
44+
| Webull US | https://developer.webull.com/apis/docs |
45+
| Webull JP | https://developer.webull.co.jp/apis/docs |

‎samples/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '2.0.3'
1+
__version__ = '2.0.4'

‎samples/trade/trade_client_v3.py‎

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,12 @@
4949
if res.status_code == 200:
5050
print('get account position res:', res.json())
5151

52+
# Currently, only Webull JP is supported.
53+
# instrument_id = None # Get the instrument_id from the position list.
54+
# res = trade_client.account_v2.get_account_position_details(account_id, instrument_id)
55+
# if res.status_code == 200:
56+
# print('get account position details res:', res.json())
57+
5258
# ============================================================
5359
# Equity Order Example
5460
# ============================================================
@@ -66,10 +72,13 @@
6672
"order_type": "LIMIT",
6773
"limit_price": "188",
6874
"quantity": "1",
69-
"support_trading_session": "N",
75+
"support_trading_session": "CORE",
7076
"side": "BUY",
7177
"time_in_force": "DAY",
72-
"entrust_type": "QTY"
78+
"entrust_type": "QTY",
79+
# Currently, only Webull JP is supported.
80+
# "account_tax_type":"GENERAL",
81+
# "margin_type": "ONE_DAY"
7382
}
7483
]
7584

‎webull/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '2.0.3'
1+
__version__ = '2.0.4'

‎webull/core/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = '2.0.3'
1+
__version__ = '2.0.4'
22

33
import logging
44

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
# Copyright 2022 Webull
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# Licensed to the Apache Software Foundation (ASF) under one
16+
# or more contributor license agreements. See the NOTICE file
17+
# distributed with this work for additional information
18+
# regarding copyright ownership. The ASF licenses this file
19+
# to you under the Apache License, Version 2.0 (the
20+
# "License"); you may not use this file except in compliance
21+
# with the License. You may obtain a copy of the License at
22+
#
23+
# http://www.apache.org/licenses/LICENSE-2.0
24+
#
25+
# Unless required by applicable law or agreed to in writing,
26+
# software distributed under the License is distributed on an
27+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
28+
# KIND, either express or implied. See the License for the
29+
# specific language governing permissions and limitations
30+
# under the License.
31+
32+
# coding=utf-8
33+
34+
"""
35+
This file borrowed some of its methods from a modified fork of the
36+
https://github.com/aliyun/aliyun-openapi-python-sdk/blob/master/aliyun-python-sdk-core/aliyunsdkcore/auth/algorithm/sha_hmac256.py
37+
which was part of Alibaba Group.
38+
"""
39+
import hashlib
40+
import hmac
41+
42+
from webull.core.compat import ensure_string
43+
from webull.core.compat import ensure_bytes
44+
from webull.core.compat import b64_encode_bytes
45+
46+
47+
def get_sign_string(source, secret):
48+
source = ensure_bytes(source)
49+
secret = ensure_bytes(secret)
50+
h = hmac.new(secret, source, hashlib.sha256)
51+
signature = ensure_string(b64_encode_bytes(h.digest()).strip())
52+
return signature
53+
54+
55+
def get_signer_name():
56+
return "HMAC-SHA256"
57+
58+
59+
def get_signer_version():
60+
return "1.0"
61+
62+
63+
def get_signer_type():
64+
return ""

‎webull/core/auth/composer/default_signature_composer.py‎

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,7 @@
3838
"""
3939

4040
from webull.core.auth.algorithm import sha_hmac1
41+
from webull.core.auth.algorithm import sha_hmac256_new
4142
from webull.core.exception import error_code
4243
from webull.core.exception.exceptions import ClientException
4344
from webull.core.utils import common
@@ -51,27 +52,36 @@
5152
PARAMS_JOIN = "&"
5253
SECRET_TAILER = "&"
5354

54-
def _refresh_sign_headers(host, headers, app_key_id, signer_spec=sha_hmac1):
55+
def _refresh_sign_headers(host, headers, app_key_id, signer_spec):
5556
if not host:
5657
raise ClientException(error_code.SDK_INVALID_PARAMETER)
5758
sign_headers = {}
5859
sign_headers[hd.APP_KEY] = app_key_id
5960
sign_headers[hd.TIMESTAMP] = common.get_iso_8601_date()
61+
62+
if common.is_not_upgrade_api_host(host):
63+
signer_spec = sha_hmac256_new
64+
else:
65+
signer_spec = sha_hmac1
66+
6067
sign_headers[hd.SIGN_VERSION] = signer_spec.get_signer_version()
6168
sign_headers[hd.SIGN_ALGORITHM] = signer_spec.get_signer_name()
6269
sign_headers[hd.NONCE] = common.get_uuid()
6370
headers.update(sign_headers)
6471
# DO NOT PUT Host Header in headers object, just put into sign_headers
6572
sign_headers[hd.NATIVE_HOST] = host
66-
return sign_headers
73+
return sign_headers,signer_spec
6774

68-
def _gen_signature(string_to_sign, secret, signer_spec=sha_hmac1):
75+
def _gen_signature(string_to_sign, secret, signer_spec):
6976
return signer_spec.get_sign_string(string_to_sign, secret + SECRET_TAILER)
7077

71-
def _get_body_string(body_params):
78+
def _get_body_string(body_params, signer_spec):
7279
if body_params is not None:
7380
raw_str = common.json_dumps_compact(body_params)
74-
hex_digest = common.md5_hex(raw_str)
81+
if signer_spec == sha_hmac256_new:
82+
hex_digest = common.sha256_hex(raw_str)
83+
else:
84+
hex_digest = common.md5_hex(raw_str)
7585
return hex_digest.upper()
7686
else:
7787
return None
@@ -100,8 +110,8 @@ def _lower_key_dict(od):
100110
lower_key_dict[k.lower()] = v
101111
return lower_key_dict
102112

103-
def calc_signature(headers, host, uri, queries, body_params, app_key_id, app_key_secret, signer_spec=sha_hmac1):
104-
sign_headers = _refresh_sign_headers(host, headers, app_key_id, signer_spec)
113+
def calc_signature(headers, host, uri, queries, body_params, app_key_id, app_key_secret, signer_spec):
114+
sign_headers,signer_spec = _refresh_sign_headers(host, headers, app_key_id, signer_spec)
105115
logger.debug("sign_headers:%s", sign_headers)
106116
sign_params = _lower_key_dict(sign_headers)
107117
logger.debug("sign_queries:%s", queries)
@@ -114,12 +124,11 @@ def calc_signature(headers, host, uri, queries, body_params, app_key_id, app_key
114124
cv = str(v)
115125
sign_params[k] = cv
116126
logger.debug("body:%s", body_params)
117-
body_string = _get_body_string(body_params)
127+
body_string = _get_body_string(body_params, signer_spec)
118128
logger.debug("body_string:%s" % body_string)
119129
string_to_sign = _build_sign_string(sign_params, uri, body_string)
120130
logger.debug("string_to_sign:%s" % string_to_sign)
121131
signature = _gen_signature(string_to_sign, app_key_secret, signer_spec)
122132
headers[hd.SIGNATURE] = signature
123133
logger.debug("signature:%s", signature)
124-
return signature
125-
134+
return signature

‎webull/core/utils/common.py‎

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,5 +58,20 @@ def md5_hex(content):
5858
content_bytes = ensure_bytes(content)
5959
return hashlib.md5(content_bytes).hexdigest()
6060

61+
def sha256_hex(content):
62+
content_bytes = ensure_bytes(content)
63+
return hashlib.sha256(content_bytes).hexdigest()
64+
6165
def json_dumps_compact(content):
62-
return json.dumps(content, ensure_ascii=False, separators=(',', ':'))
66+
return json.dumps(content, ensure_ascii=False, separators=(',', ':'))
67+
68+
def is_not_upgrade_api_host(host):
69+
upgrade_hosts = {
70+
"api.webull.com","events-api.webull.com",
71+
"api.webull.hk","events-api.webull.hk",
72+
"pre-openapi-us-alb.webullbroker.com","pre-openapi-us-events.webullbroker.com",
73+
"pre-openapi-alb.webullbroker.com","pre-openapi-events.webullbroker.com",
74+
"us-openapi-alb.uat.webullbroker.com","us-openapi-events.uat.webullbroker.com",
75+
"hk-openapi.uat.webullbroker.com", "hk-openapi-events-api.uat.webullbroker.com"
76+
}
77+
return host not in upgrade_hosts

‎webull/data/__init__.py‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# coding=utf-8
22

3-
__version__ = '2.0.3'
3+
__version__ = '2.0.4'

‎webull/data/common/category.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,3 +26,4 @@ class Category(EasyEnum):
2626
US_CRYPTO = (8, "US CRYPTO")
2727
US_FUTURES = (12, "US FUTURES")
2828
US_EVENT = (13, "US EVENT")
29+
HK_FUTURES = (13, "HK FUTURES")

0 commit comments

Comments
 (0)