Skip to content

Commit 15f161a

Browse files
author
方佳
committed
Merge branch 'main_merge_1221' into 'main'
feat: Webull JP Support Python SDK See merge request webull/openapi-python-sdk!6
2 parents 8b0ad82 + 5fb51d9 commit 15f161a

File tree

28 files changed

+477
-24
lines changed

28 files changed

+477
-24
lines changed

README.md

+10-8
Original file line numberDiff line numberDiff line change
@@ -16,10 +16,11 @@ Subscription to real-time information: Subscribe to order status changes, market
1616

1717
- Please first generate the app key and app secret on the Webull official website.
1818

19-
| Market | Link |
20-
|--------|------------------------|
21-
| HK | https://www.webull.hk |
22-
| US | https://www.webull.com |
19+
| Market | Link |
20+
|--------|--------------------------|
21+
| HK | https://www.webull.hk |
22+
| US | https://www.webull.com |
23+
| JP | https://www.webull.co.jp |
2324

2425
- Requires Python 3.7 and above.
2526

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

3637
## Developer documentation
3738

38-
| Market | Link |
39-
|------|---------------------------------------|
40-
| HK | https://developer.webull.hk/api-doc/ |
41-
| US | https://developer.webull.com/api-doc/ |
39+
| Market | Link |
40+
|--------|------------------------------------------|
41+
| HK | https://developer.webull.hk/api-doc/ |
42+
| US | https://developer.webull.com/api-doc/ |
43+
| JP | https://developer.webull.co.jp/api-doc/ |

webull-python-sdk-core/webullsdkcore/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = "0.1.8"
1+
__version__ = "0.1.9"
22

33
import logging
44

webull-python-sdk-core/webullsdkcore/common/region.py

+1
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
class Region(Enum):
55
US = 'us'
66
HK = 'hk'
7+
JP = 'jp'

webull-python-sdk-core/webullsdkcore/data/endpoints.json

+6-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"default_region": "us",
3-
"regions": ["us", "hk"],
3+
"regions": ["us", "hk", "jp"],
44
"region_mapping": {
55
"us": {
66
"api": "api.webull.com",
@@ -11,6 +11,11 @@
1111
"api": "api.webull.hk",
1212
"quotes-api": "quotes-api.webull.hk",
1313
"events-api": "events-api.webull.hk"
14+
},
15+
"jp": {
16+
"api": "api.webull.co.jp",
17+
"quotes-api": "",
18+
"events-api": "events-api.webull.co.jp"
1419
}
1520
}
1621
}

webull-python-sdk-demos/setup.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
LONG_DESCRIPTION = fp.read()
1616

1717
requires = [
18-
"webull-python-sdk-mdata==0.1.8",
19-
"webull-python-sdk-trade==0.1.8"
18+
"webull-python-sdk-mdata==0.1.9",
19+
"webull-python-sdk-trade==0.1.9"
2020
]
2121

2222
setup_args = {

webull-python-sdk-demos/tests/core/endpoint/test_endpoint.py

+15
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,11 @@ def test_quotes_endpoint(self):
6363
endpoint = resolver.resolve(endpoint_request)
6464
self.assertEqual(endpoint, 'quotes-api.webull.hk')
6565

66+
_region_id = 'jp'
67+
endpoint_request = ResolveEndpointRequest(_region_id, api_type.QUOTES)
68+
endpoint = resolver.resolve(endpoint_request)
69+
self.assertEqual(endpoint, '')
70+
6671
def test_api_endpoint(self):
6772
"""
6873
Set by Request, and it only takes effect for the current Request. The sample code is as follows.
@@ -78,6 +83,11 @@ def test_api_endpoint(self):
7883
endpoint = resolver.resolve(endpoint_request)
7984
self.assertEqual(endpoint, 'api.webull.hk')
8085

86+
_region_id = 'jp'
87+
endpoint_request = ResolveEndpointRequest(_region_id)
88+
endpoint = resolver.resolve(endpoint_request)
89+
self.assertEqual(endpoint, 'api.webull.co.jp')
90+
8191
def test_event_endpoint(self):
8292
resolver = DefaultEndpointResolver(self)
8393
_region_id = 'us'
@@ -89,3 +99,8 @@ def test_event_endpoint(self):
8999
endpoint_request = ResolveEndpointRequest(_region_id, api_type.EVENTS)
90100
endpoint = resolver.resolve(endpoint_request)
91101
self.assertEqual(endpoint, 'events-api.webull.hk')
102+
103+
_region_id = 'jp'
104+
endpoint_request = ResolveEndpointRequest(_region_id, api_type.EVENTS)
105+
endpoint = resolver.resolve(endpoint_request)
106+
self.assertEqual(endpoint, 'events-api.webull.co.jp')
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
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+
import unittest
16+
from webullsdkmdata.request.get_corp_action_request import GetCorpActionRequest
17+
from webullsdkcore.client import ApiClient
18+
19+
PRE_OPENAPI_ENDPOINT = "<api_endpoint>"
20+
21+
22+
class TestGetEodBarsRequest(unittest.TestCase):
23+
24+
def test_request(self):
25+
26+
request = GetCorpActionRequest()
27+
request.set_instrument_ids("913303964,913256135")
28+
request.set_event_types("301,302")
29+
request.set_start_date("2022-07-18")
30+
request.set_end_date("2022-07-18")
31+
request.set_page_number(1)
32+
request.set_page_size(200)
33+
request.set_last_update_time("2022-07-20 03:17:15")
34+
request.set_endpoint(PRE_OPENAPI_ENDPOINT)
35+
client = ApiClient(app_key="<your_app_key>", app_secret="<your_app_secret>")
36+
response = client.get_response(request)
37+
self.assertTrue(response.status_code == 200)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
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+
import unittest
16+
from webullsdkmdata.request.get_eod_bars_request import GetEodBarsRequest
17+
from webullsdkcore.client import ApiClient
18+
19+
PRE_OPENAPI_ENDPOINT = "<api_endpoint>"
20+
21+
22+
class TestGetEodBarsRequest(unittest.TestCase):
23+
24+
def test_request(self):
25+
26+
request = GetEodBarsRequest()
27+
request.set_instrument_ids("913303964,913256135")
28+
request.set_date("2024-10-10")
29+
request.set_count("10")
30+
request.set_endpoint(PRE_OPENAPI_ENDPOINT)
31+
client = ApiClient(app_key="<your_app_key>", app_secret="<your_app_secret>")
32+
response = client.get_response(request)
33+
self.assertTrue(response.status_code == 200)

webull-python-sdk-demos/tests/trade/request/test_order_operation.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ def test_order_operation(self):
4444
"extended_hours_trading": False,
4545
}
4646
}
47-
res = order_operation.place_order(account_id, **stock_order['stock_order'])
47+
res = order_operation.place_order(account_id, stock_order['stock_order'])
4848
if res.status_code == 200:
4949
print('place order status:', res.json())
5050

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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+
import unittest
16+
17+
from webullsdkcore.client import ApiClient
18+
from webullsdkcore.exception.exceptions import ServerException
19+
from webullsdktrade.request.get_tradeable_instruments_request import TradeableInstrumentRequest;
20+
21+
optional_api_endpoint = "<api_endpoint>"
22+
your_app_key = "<your_app_key>"
23+
your_app_secret = "<your_app_secret>"
24+
api_client = ApiClient(your_app_key, your_app_secret)
25+
26+
27+
class TestTradeableInstruments(unittest.TestCase):
28+
29+
def test_tradeable_instruments(self):
30+
request = TradeableInstrumentRequest()
31+
request.set_endpoint(optional_api_endpoint)
32+
request.set_last_instrument_id("")
33+
request.set_page_size(10)
34+
try:
35+
response = api_client.get_response(request)
36+
print(response.json())
37+
except ServerException as se:
38+
print(se.get_error_code(), ":", se.get_error_msg())

webull-python-sdk-demos/tests/trade/test_api.py

+19-1
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@
2626
your_app_key = "<your_app_key>"
2727
your_app_secret = "<your_app_secret>"
2828
account_id = "<your_account_id>"
29-
# 'hk' or 'us'
29+
# 'hk' or 'us' or 'jp'
3030
region_id = "<region_id>"
3131
api_client = ApiClient(your_app_key, your_app_secret, region_id)
3232
api_client.add_endpoint(region_id, optional_api_endpoint)
@@ -74,6 +74,15 @@ def test_api(self):
7474
res = api.market_data.get_history_bar('600000', 'CN_STOCK', 'M1')
7575
if res.status_code == 200:
7676
print('cn stock quote:', res.json())
77+
res = api.market_data.get_eod_bar(instrument_ids='913303964', count=10)
78+
if res.status_code == 200:
79+
print('eod bar quote:', res.json())
80+
res = api.market_data.get_corp_action(instrument_ids="913303964,913256135", event_types="301,302")
81+
if res.status_code == 200:
82+
print('corp action quote:', res.json())
83+
res = api.trade_instrument.get_tradeable_instruments()
84+
if res.status_code == 200:
85+
print('tradeable instruments:', res.json())
7786
res = api.account.get_app_subscriptions()
7887
if res.status_code == 200:
7988
print('app subscriptions:', res.json())
@@ -173,6 +182,15 @@ def test_api(self):
173182
res = api.order.place_order(stock_order['account_id'], **stock_order['stock_order'])
174183
if res.status_code == 200:
175184
print('place order res:', res.json())
185+
res = api.order.replace_order(stock_order['account_id'], **stock_order['stock_order'])
186+
if res.status_code == 200:
187+
print('replace order res:', res.json())
188+
res = api.order.place_order_v2(stock_order['account_id'], stock_order['stock_order'])
189+
if res.status_code == 200:
190+
print('place order v2 res:', res.json())
191+
res = api.order.replace_order_v2(stock_order['account_id'], stock_order['stock_order'])
192+
if res.status_code == 200:
193+
print('replace order v2 res:', res.json())
176194
res = api.order.list_open_orders(account_id, page_size=20)
177195
if res.status_code == 200:
178196
print('open orders:', res.json())

webull-python-sdk-mdata/setup.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@
1515
LONG_DESCRIPTION = fp.read()
1616

1717
requires = [
18-
"webull-python-sdk-core==0.1.8",
19-
"webull-python-sdk-quotes-core==0.1.8"
18+
"webull-python-sdk-core==0.1.9",
19+
"webull-python-sdk-quotes-core==0.1.9"
2020
]
2121

2222
setup_args = {
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# coding=utf-8
22

3-
__version__ = '0.1.8'
3+
__version__ = '0.1.9'

webull-python-sdk-mdata/webullsdkmdata/quotes/market_data.py

+68
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,9 @@
1414

1515
from webullsdkmdata.request.get_historical_bars_request import GetHistoricalBarsRequest
1616
from webullsdkmdata.request.get_snapshot_request import GetSnapshotRequest
17+
from webullsdkmdata.request.get_eod_bars_request import GetEodBarsRequest
18+
from webullsdkmdata.request.get_corp_action_request import GetCorpActionRequest
19+
1720

1821

1922
class MarketData:
@@ -55,3 +58,68 @@ def get_snapshot(self, symbols, category):
5558
quote_request.set_category(category)
5659
response = self.client.get_response(quote_request)
5760
return response
61+
62+
def get_eod_bar(self, instrument_ids, date=None, count='1'):
63+
"""
64+
Only for Webull JP
65+
66+
Query end-of-day market information according to instrument_id.
67+
68+
:param instrument_ids: Instrument id collection, such as: 913256135,913303964.
69+
Multiple instrument_ids should be separated by ,.
70+
A single query supports up to 200 instrument_id
71+
72+
:param date: UTC time. Time format: yyyy-MM-dd, and the default check is conducted on the latest date
73+
74+
:param count: With “date” as the deadline, the end-of-day market data of the last “count” trading days:
75+
the default is 1, and the maximum limit is 800
76+
"""
77+
eod_bar_request = GetEodBarsRequest()
78+
eod_bar_request.set_instrument_ids(instrument_ids)
79+
if date is not None:
80+
eod_bar_request.set_date(date)
81+
eod_bar_request.set_count(count)
82+
response = self.client.get_response(eod_bar_request)
83+
return response
84+
85+
def get_corp_action(self, instrument_ids, event_types, start_date=None, end_date=None, page_number=None,
86+
page_size=None, last_update_time=None):
87+
"""
88+
Only for Webull JP
89+
90+
Supports the query of the corporate events for stock splits and reverse stock split,
91+
including past and upcoming events.
92+
93+
:param instrument_ids: Instrument id collection, such as: 913256135,913303964.
94+
Multiple instrument_ids should be separated by ,.
95+
A single query supports up to 100 instrument_id
96+
97+
:param event_types: Event type collection. Multiple event_types should be separated by ,
98+
99+
:param start_date: Event start date, UTC time.Time format: yyyy-MM-dd
100+
101+
:param end_date: Event end date, UTC time.Time format: yyyy-MM-dd
102+
103+
:param page_number: The initial value, if not passed, the first page will be searched by default
104+
105+
:param page_size: Number of entries per page: default value is 20, and maximum value is 200.
106+
Integers can be filled
107+
108+
:param last_update_time: Incremental update time, UTC time. Time format: yyyy-MM-dd HH:mm:ss
109+
110+
"""
111+
eod_corp_action_request = GetCorpActionRequest()
112+
eod_corp_action_request.set_instrument_ids(instrument_ids)
113+
eod_corp_action_request.set_event_types(event_types)
114+
if start_date is not None:
115+
eod_corp_action_request.set_start_date(start_date)
116+
if end_date is not None:
117+
eod_corp_action_request.set_end_date(end_date)
118+
if page_number is not None:
119+
eod_corp_action_request.set_page_number(page_number)
120+
if page_size is not None:
121+
eod_corp_action_request.set_page_size(page_size)
122+
if last_update_time is not None:
123+
eod_corp_action_request.set_last_update_time(last_update_time)
124+
response = self.client.get_response(eod_corp_action_request)
125+
return response

0 commit comments

Comments
 (0)