webull-openapi-python-sdk 1.0.1__py3-none-any.whl → 1.0.3__py3-none-any.whl
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.
- samples/__init__.py +1 -1
- samples/trade/trade_client_v2.py +223 -102
- samples/trade/trade_event_client.py +13 -11
- webull/__init__.py +1 -1
- webull/core/__init__.py +1 -1
- webull/core/data/endpoints.json +1 -1
- webull/core/http/initializer/client_initializer.py +43 -7
- webull/core/http/initializer/config/__init__.py +0 -0
- webull/core/http/initializer/config/bean/__init__.py +0 -0
- webull/core/http/initializer/config/bean/query_config_request.py +41 -0
- webull/core/http/initializer/config/config_operation.py +49 -0
- webull/core/http/initializer/token/bean/check_token_request.py +1 -1
- webull/core/http/initializer/token/bean/create_token_request.py +1 -1
- webull/core/http/initializer/token/bean/refresh_token_request.py +1 -1
- webull/core/http/initializer/token/token_operation.py +0 -2
- webull/data/__init__.py +1 -1
- webull/data/data_client.py +3 -0
- webull/data/request/get_batch_historical_bars_request.py +1 -1
- webull/data/request/get_historical_bars_request.py +1 -1
- webull/data/request/get_instruments_request.py +1 -1
- webull/data/request/get_quotes_request.py +1 -1
- webull/data/request/get_snapshot_request.py +1 -1
- webull/data/request/get_tick_request.py +1 -1
- webull/trade/__init__.py +1 -1
- webull/trade/request/palce_order_request.py +3 -2
- webull/trade/request/place_order_request.py +92 -0
- webull/trade/request/v2/cancel_option_request.py +2 -2
- webull/trade/request/v2/cancel_order_request.py +2 -2
- webull/trade/request/v2/get_account_balance_request.py +1 -1
- webull/trade/request/v2/get_account_list.py +3 -0
- webull/trade/request/v2/get_account_list_request.py +23 -0
- webull/trade/request/v2/get_account_positions_request.py +1 -1
- webull/trade/request/v2/get_order_detail_request.py +1 -1
- webull/trade/request/v2/get_order_history_request.py +5 -2
- webull/trade/request/v2/get_order_open_request.py +32 -0
- webull/trade/request/v2/palce_order_request.py +11 -54
- webull/trade/request/v2/place_option_request.py +7 -22
- webull/trade/request/v2/place_order_request.py +44 -0
- webull/trade/request/v2/preview_option_request.py +6 -2
- webull/trade/request/v2/preview_order_request.py +7 -37
- webull/trade/request/v2/replace_option_request.py +6 -2
- webull/trade/request/v2/replace_order_request.py +7 -35
- webull/trade/trade/order_operation.py +5 -2
- webull/trade/trade/v2/account_info_v2.py +8 -11
- webull/trade/trade/v2/order_operation_v2.py +114 -67
- {webull_openapi_python_sdk-1.0.1.dist-info → webull_openapi_python_sdk-1.0.3.dist-info}/METADATA +1 -1
- {webull_openapi_python_sdk-1.0.1.dist-info → webull_openapi_python_sdk-1.0.3.dist-info}/RECORD +51 -43
- {webull_openapi_python_sdk-1.0.1.dist-info → webull_openapi_python_sdk-1.0.3.dist-info}/WHEEL +0 -0
- {webull_openapi_python_sdk-1.0.1.dist-info → webull_openapi_python_sdk-1.0.3.dist-info}/licenses/LICENSE +0 -0
- {webull_openapi_python_sdk-1.0.1.dist-info → webull_openapi_python_sdk-1.0.3.dist-info}/licenses/NOTICE +0 -0
- {webull_openapi_python_sdk-1.0.1.dist-info → webull_openapi_python_sdk-1.0.3.dist-info}/top_level.txt +0 -0
samples/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "
|
|
1
|
+
__version__ = "1.0.3"
|
samples/trade/trade_client_v2.py
CHANGED
|
@@ -11,13 +11,12 @@
|
|
|
11
11
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
|
+
|
|
14
15
|
import json
|
|
15
|
-
import unittest
|
|
16
16
|
import uuid
|
|
17
17
|
from time import sleep
|
|
18
18
|
|
|
19
19
|
from webull.core.client import ApiClient
|
|
20
|
-
from webull.data.common.category import Category
|
|
21
20
|
from webull.trade.trade_client import TradeClient
|
|
22
21
|
|
|
23
22
|
optional_api_endpoint = "<api_endpoint>"
|
|
@@ -34,83 +33,171 @@ if __name__ == '__main__':
|
|
|
34
33
|
|
|
35
34
|
res = trade_client.account_v2.get_account_list()
|
|
36
35
|
if res.status_code == 200:
|
|
37
|
-
print(
|
|
36
|
+
print('get account list:', res.json())
|
|
38
37
|
|
|
39
38
|
res = trade_client.account_v2.get_account_balance(account_id)
|
|
40
39
|
if res.status_code == 200:
|
|
41
|
-
print(
|
|
40
|
+
print('get account balance res:', res.json())
|
|
42
41
|
|
|
43
42
|
res = trade_client.account_v2.get_account_position(account_id)
|
|
44
43
|
if res.status_code == 200:
|
|
45
|
-
print(
|
|
44
|
+
print('get account position res:', res.json())
|
|
45
|
+
|
|
46
|
+
# simple order
|
|
47
|
+
client_order_id = uuid.uuid4().hex
|
|
48
|
+
print('client order id:', client_order_id)
|
|
49
|
+
new_simple_orders = [
|
|
50
|
+
{
|
|
51
|
+
"combo_type": "NORMAL",
|
|
52
|
+
"client_order_id": client_order_id,
|
|
53
|
+
"symbol": "AAPL",
|
|
54
|
+
"instrument_type": "EQUITY",
|
|
55
|
+
"market": "US",
|
|
56
|
+
"order_type": "LIMIT",
|
|
57
|
+
"limit_price": "188",
|
|
58
|
+
"quantity": "1",
|
|
59
|
+
"support_trading_session": "N",
|
|
60
|
+
"side": "BUY",
|
|
61
|
+
"time_in_force": "DAY",
|
|
62
|
+
"entrust_type": "QTY"
|
|
63
|
+
}
|
|
64
|
+
]
|
|
46
65
|
|
|
47
|
-
|
|
48
|
-
"symbol": "AAPL",
|
|
49
|
-
"instrument_type": "EQUITY",
|
|
50
|
-
"market": "US",
|
|
51
|
-
"order_type": "MARKET",
|
|
52
|
-
"quantity": "1",
|
|
53
|
-
"support_trading_session": "N",
|
|
54
|
-
"side": "BUY",
|
|
55
|
-
"time_in_force": "DAY",
|
|
56
|
-
"entrust_type": "QTY"
|
|
57
|
-
}
|
|
58
|
-
res = trade_client.order_v2.preview_order(account_id=account_id, preview_orders=preview_orders)
|
|
66
|
+
res = trade_client.order_v2.preview_order(account_id, new_simple_orders)
|
|
59
67
|
if res.status_code == 200:
|
|
60
|
-
print(
|
|
68
|
+
print('preview order res:', res.json())
|
|
69
|
+
|
|
70
|
+
res = trade_client.order_v2.place_order(account_id, new_simple_orders)
|
|
71
|
+
if res.status_code == 200:
|
|
72
|
+
print('place order res:', res.json())
|
|
73
|
+
sleep(3)
|
|
74
|
+
|
|
75
|
+
modify_simple_orders = [
|
|
76
|
+
{
|
|
77
|
+
"client_order_id": client_order_id,
|
|
78
|
+
"quantity": "100",
|
|
79
|
+
"limit_price": "200"
|
|
80
|
+
}
|
|
81
|
+
]
|
|
82
|
+
res = trade_client.order_v2.replace_order(account_id, modify_simple_orders)
|
|
83
|
+
if res.status_code == 200:
|
|
84
|
+
print('replace order res:', res.json())
|
|
85
|
+
sleep(3)
|
|
86
|
+
|
|
87
|
+
res = trade_client.order_v2.cancel_order(account_id, client_order_id)
|
|
88
|
+
if res.status_code == 200:
|
|
89
|
+
print('cancel order res:', res.json())
|
|
90
|
+
|
|
91
|
+
res = trade_client.order_v2.get_order_open(account_id=account_id)
|
|
92
|
+
if res.status_code == 200:
|
|
93
|
+
print("order_open_res=" + json.dumps(res.json(), indent=4))
|
|
94
|
+
|
|
95
|
+
res = trade_client.order_v2.get_order_history(account_id)
|
|
96
|
+
if res.status_code == 200:
|
|
97
|
+
print('get order history res:', res.json())
|
|
98
|
+
|
|
99
|
+
res = trade_client.order_v2.get_order_detail(account_id, client_order_id)
|
|
100
|
+
if res.status_code == 200:
|
|
101
|
+
print('get order detail res:', res.json())
|
|
102
|
+
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
# Combo Order
|
|
106
|
+
master_client_order_id = uuid.uuid4().hex
|
|
107
|
+
stop_profit_client_order_id = uuid.uuid4().hex
|
|
108
|
+
stop_loss_client_order_id = uuid.uuid4().hex
|
|
109
|
+
print('master_client_order_id:', master_client_order_id)
|
|
110
|
+
print('stop_profit_client_order_id:', stop_profit_client_order_id)
|
|
111
|
+
print('stop_loss_client_order_id:', stop_loss_client_order_id)
|
|
112
|
+
new_combo_orders = [
|
|
113
|
+
{
|
|
114
|
+
"client_order_id": master_client_order_id,
|
|
115
|
+
"combo_type": "MASTER",
|
|
116
|
+
"symbol": "F",
|
|
117
|
+
"instrument_type": "EQUITY",
|
|
118
|
+
"market": "US",
|
|
119
|
+
"order_type": "LIMIT",
|
|
120
|
+
"quantity": "1",
|
|
121
|
+
"support_trading_session": "N",
|
|
122
|
+
"limit_price": "10.5",
|
|
123
|
+
"side": "BUY",
|
|
124
|
+
"entrust_type": "QTY",
|
|
125
|
+
"time_in_force": "DAY"
|
|
126
|
+
},
|
|
127
|
+
{
|
|
128
|
+
"client_order_id": stop_profit_client_order_id,
|
|
129
|
+
"combo_type": "STOP_PROFIT",
|
|
130
|
+
"symbol": "F",
|
|
131
|
+
"instrument_type": "EQUITY",
|
|
132
|
+
"market": "US",
|
|
133
|
+
"order_type": "LIMIT",
|
|
134
|
+
"quantity": "1",
|
|
135
|
+
"support_trading_session": "N",
|
|
136
|
+
"limit_price": "11.5",
|
|
137
|
+
"side": "SELL",
|
|
138
|
+
"entrust_type": "QTY",
|
|
139
|
+
"time_in_force": "DAY"
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
"client_order_id": stop_loss_client_order_id,
|
|
143
|
+
"combo_type": "STOP_LOSS",
|
|
144
|
+
"symbol": "F",
|
|
145
|
+
"instrument_type": "EQUITY",
|
|
146
|
+
"market": "US",
|
|
147
|
+
"order_type": "STOP_LOSS",
|
|
148
|
+
"quantity": "1",
|
|
149
|
+
"support_trading_session": "N",
|
|
150
|
+
"stop_price": "10",
|
|
151
|
+
"side": "SELL",
|
|
152
|
+
"entrust_type": "QTY",
|
|
153
|
+
"time_in_force": "DAY"
|
|
154
|
+
}
|
|
155
|
+
]
|
|
156
|
+
|
|
157
|
+
res = trade_client.order_v2.preview_order(account_id, new_combo_orders)
|
|
158
|
+
if res.status_code == 200:
|
|
159
|
+
print('preview combo order res:', res.json())
|
|
160
|
+
|
|
161
|
+
res = trade_client.order_v2.place_order(account_id, new_combo_orders)
|
|
162
|
+
if res.status_code == 200:
|
|
163
|
+
print('place combo order res:', res.json())
|
|
164
|
+
sleep(3)
|
|
165
|
+
|
|
166
|
+
modify_combo_orders = [
|
|
167
|
+
{
|
|
168
|
+
"client_order_id": master_client_order_id,
|
|
169
|
+
"quantity": "2"
|
|
170
|
+
},
|
|
171
|
+
{
|
|
172
|
+
"client_order_id": stop_profit_client_order_id,
|
|
173
|
+
"quantity": "2"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"client_order_id": stop_loss_client_order_id,
|
|
177
|
+
"quantity": "2"
|
|
178
|
+
}
|
|
179
|
+
]
|
|
180
|
+
res = trade_client.order_v2.replace_order(account_id, modify_combo_orders)
|
|
181
|
+
if res.status_code == 200:
|
|
182
|
+
print('replace combo order res:', res.json())
|
|
183
|
+
sleep(3)
|
|
184
|
+
|
|
185
|
+
res = trade_client.order_v2.cancel_order(account_id, master_client_order_id)
|
|
186
|
+
if res.status_code == 200:
|
|
187
|
+
print('cancel master order res:', res.json())
|
|
188
|
+
|
|
189
|
+
res = trade_client.order_v2.get_order_history(account_id)
|
|
190
|
+
if res.status_code == 200:
|
|
191
|
+
print('get order history res:', res.json())
|
|
192
|
+
|
|
193
|
+
res = trade_client.order_v2.get_order_open(account_id=account_id)
|
|
194
|
+
if res.status_code == 200:
|
|
195
|
+
print("order_open_res=" + json.dumps(res.json(), indent=4))
|
|
196
|
+
|
|
197
|
+
res = trade_client.order_v2.get_order_detail(account_id, master_client_order_id)
|
|
198
|
+
if res.status_code == 200:
|
|
199
|
+
print('get master order detail res:', res.json())
|
|
61
200
|
|
|
62
|
-
client_order_id = uuid.uuid4().hex
|
|
63
|
-
new_orders = {
|
|
64
|
-
"client_order_id": client_order_id,
|
|
65
|
-
"symbol": "AAPL",
|
|
66
|
-
"instrument_type": "EQUITY",
|
|
67
|
-
"market": "US",
|
|
68
|
-
"order_type": "LIMIT",
|
|
69
|
-
"limit_price": "188",
|
|
70
|
-
"quantity": "1",
|
|
71
|
-
"support_trading_session": "N",
|
|
72
|
-
"side": "BUY",
|
|
73
|
-
"time_in_force": "DAY",
|
|
74
|
-
"entrust_type": "QTY",
|
|
75
|
-
# "account_tax_type": "GENERAL"
|
|
76
|
-
# "total_cash_amount": "100.20"
|
|
77
|
-
# "sender_sub_id": "123321-lzg",
|
|
78
|
-
# "no_party_ids":[
|
|
79
|
-
# {"party_id":"BNG144.666555","party_id_source":"D","party_role":"3"}
|
|
80
|
-
# ]
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
# This is an optional feature; you can still make a request without setting it.
|
|
84
|
-
custom_headers_map = {"category": Category.US_STOCK.name}
|
|
85
|
-
trade_client.order_v2.add_custom_headers(custom_headers_map)
|
|
86
|
-
res = trade_client.order_v2.place_order(account_id=account_id, new_orders=new_orders)
|
|
87
|
-
trade_client.order_v2.remove_custom_headers()
|
|
88
|
-
if res.status_code == 200:
|
|
89
|
-
print("place_order_res=" + json.dumps(res.json(), indent=4))
|
|
90
|
-
sleep(5)
|
|
91
|
-
|
|
92
|
-
modify_orders = {
|
|
93
|
-
"client_order_id": client_order_id,
|
|
94
|
-
"quantity": "100",
|
|
95
|
-
"limit_price": "200"
|
|
96
|
-
}
|
|
97
|
-
res = trade_client.order_v2.replace_order(account_id=account_id, modify_orders=modify_orders)
|
|
98
|
-
if res.status_code == 200:
|
|
99
|
-
print("replace_order_res=" + json.dumps(res.json(), indent=4))
|
|
100
|
-
sleep(5)
|
|
101
|
-
|
|
102
|
-
res = trade_client.order_v2.cancel_order_v2(account_id=account_id, client_order_id=client_order_id)
|
|
103
|
-
if res.status_code == 200:
|
|
104
|
-
print("cancel_order_res=" + json.dumps(res.json(), indent=4))
|
|
105
|
-
|
|
106
|
-
res = trade_client.order_v2.get_order_history_request(account_id=account_id)
|
|
107
|
-
if res.status_code == 200:
|
|
108
|
-
print("order_history_res=" + json.dumps(res.json(), indent=4))
|
|
109
|
-
|
|
110
|
-
# order detail
|
|
111
|
-
res = trade_client.order_v2.get_order_detail(account_id=account_id, client_order_id=client_order_id)
|
|
112
|
-
if res.status_code == 200:
|
|
113
|
-
print("order detail=" + json.dumps(res.json(), indent=4))
|
|
114
201
|
|
|
115
202
|
# Options
|
|
116
203
|
# For option order inquiries, please use the V2 query interface: api.order_v2.get_order_detail(account_id, client_order_id).
|
|
@@ -121,18 +208,18 @@ if __name__ == '__main__':
|
|
|
121
208
|
"combo_type": "NORMAL",
|
|
122
209
|
"order_type": "LIMIT",
|
|
123
210
|
"quantity": "1",
|
|
124
|
-
"limit_price": "
|
|
211
|
+
"limit_price": "21.25",
|
|
125
212
|
"option_strategy": "SINGLE",
|
|
126
213
|
"side": "BUY",
|
|
127
214
|
"time_in_force": "GTC",
|
|
128
215
|
"entrust_type": "QTY",
|
|
129
|
-
"
|
|
216
|
+
"legs": [
|
|
130
217
|
{
|
|
131
218
|
"side": "BUY",
|
|
132
219
|
"quantity": "1",
|
|
133
|
-
"symbol": "
|
|
134
|
-
"strike_price": "
|
|
135
|
-
"
|
|
220
|
+
"symbol": "TSLA",
|
|
221
|
+
"strike_price": "400",
|
|
222
|
+
"option_expire_date": "2025-12-26",
|
|
136
223
|
"instrument_type": "OPTION",
|
|
137
224
|
"option_type": "CALL",
|
|
138
225
|
"market": "US"
|
|
@@ -140,42 +227,76 @@ if __name__ == '__main__':
|
|
|
140
227
|
]
|
|
141
228
|
}
|
|
142
229
|
]
|
|
230
|
+
|
|
143
231
|
# preview
|
|
144
232
|
res = trade_client.order_v2.preview_option(account_id, option_new_orders)
|
|
145
233
|
if res.status_code == 200:
|
|
146
|
-
print("preview option
|
|
147
|
-
sleep(5)
|
|
148
|
-
# place
|
|
234
|
+
print("preview option res:" + json.dumps(res.json(), indent=4))
|
|
149
235
|
|
|
150
|
-
#
|
|
151
|
-
custom_headers_map = {"category": Category.US_OPTION.name}
|
|
152
|
-
trade_client.order_v2.add_custom_headers(custom_headers_map)
|
|
236
|
+
# place
|
|
153
237
|
res = trade_client.order_v2.place_option(account_id, option_new_orders)
|
|
154
|
-
trade_client.order_v2.remove_custom_headers()
|
|
155
238
|
if res.status_code == 200:
|
|
156
|
-
print("place option
|
|
157
|
-
sleep(
|
|
239
|
+
print("place option res:" + json.dumps(res.json(), indent=4))
|
|
240
|
+
sleep(3)
|
|
158
241
|
|
|
159
|
-
# replace
|
|
160
|
-
option_modify_orders = [
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
res = trade_client.order_v2.
|
|
242
|
+
# replace for Webull HK
|
|
243
|
+
# option_modify_orders = [
|
|
244
|
+
# {
|
|
245
|
+
# "client_order_id": client_order_id,
|
|
246
|
+
# "quantity": "2",
|
|
247
|
+
# "limit_price": "11.3"
|
|
248
|
+
# }
|
|
249
|
+
# ]
|
|
250
|
+
# res = trade_client.order_v2.replace_option(account_id, option_modify_orders)
|
|
251
|
+
# if res.status_code == 200:
|
|
252
|
+
# print("replace option res:" + json.dumps(res.json(), indent=4))
|
|
253
|
+
# sleep(5)
|
|
254
|
+
|
|
255
|
+
# replace for Webull US
|
|
256
|
+
res = trade_client.order_v2.get_order_detail(account_id, client_order_id)
|
|
174
257
|
if res.status_code == 200:
|
|
175
|
-
print(
|
|
176
|
-
|
|
258
|
+
print('get option order detail res:', res.json())
|
|
259
|
+
data = res.json() or {}
|
|
260
|
+
leg_id = (
|
|
261
|
+
data.get("orders", [{}])[0]
|
|
262
|
+
.get("legs", [{}])[0]
|
|
263
|
+
.get("id")
|
|
264
|
+
)
|
|
265
|
+
print('get option order detail id :', leg_id)
|
|
266
|
+
|
|
267
|
+
# If it is a multi-leg option, you need to manually match it to the corresponding sub-leg orderId.
|
|
268
|
+
if leg_id:
|
|
269
|
+
option_modify_orders = [
|
|
270
|
+
{
|
|
271
|
+
"client_order_id": client_order_id,
|
|
272
|
+
"quantity": "2",
|
|
273
|
+
"limit_price": "21.3",
|
|
274
|
+
"legs": [
|
|
275
|
+
{
|
|
276
|
+
"id": leg_id,
|
|
277
|
+
"quantity": "2"
|
|
278
|
+
}
|
|
279
|
+
]
|
|
280
|
+
}
|
|
281
|
+
]
|
|
282
|
+
res = trade_client.order_v2.replace_option(account_id, option_modify_orders)
|
|
283
|
+
if res.status_code == 200:
|
|
284
|
+
print("replace option res:" + json.dumps(res.json(), indent=4))
|
|
285
|
+
sleep(3)
|
|
177
286
|
|
|
178
287
|
# cancel
|
|
179
288
|
res = trade_client.order_v2.cancel_option(account_id, client_order_id)
|
|
180
289
|
if res.status_code == 200:
|
|
181
|
-
print("cancel option
|
|
290
|
+
print("cancel option res:" + json.dumps(res.json(), indent=4))
|
|
291
|
+
|
|
292
|
+
res = trade_client.order_v2.get_order_history(account_id)
|
|
293
|
+
if res.status_code == 200:
|
|
294
|
+
print('get order history res:', res.json())
|
|
295
|
+
|
|
296
|
+
res = trade_client.order_v2.get_order_open(account_id=account_id)
|
|
297
|
+
if res.status_code == 200:
|
|
298
|
+
print("order_open_res=" + json.dumps(res.json(), indent=4))
|
|
299
|
+
|
|
300
|
+
res = trade_client.order_v2.get_order_detail(account_id, client_order_id)
|
|
301
|
+
if res.status_code == 200:
|
|
302
|
+
print('get option order detail res:', res.json())
|
|
@@ -12,36 +12,38 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
|
|
15
|
-
import
|
|
15
|
+
import logging
|
|
16
16
|
|
|
17
|
-
from webull.trade.trade_events_client import TradeEventsClient
|
|
18
17
|
from webull.trade.events.types import ORDER_STATUS_CHANGED, EVENT_TYPE_ORDER
|
|
18
|
+
from webull.trade.trade_events_client import TradeEventsClient
|
|
19
19
|
|
|
20
20
|
your_app_key = "<your_app_key>"
|
|
21
21
|
your_app_secret = "<your_app_secret>"
|
|
22
22
|
account_id = "<your_account_id>"
|
|
23
|
-
region_id = "
|
|
23
|
+
region_id = "<region_id>"
|
|
24
24
|
|
|
25
25
|
optional_api_endpoint = "<event_api_endpoint>"
|
|
26
26
|
|
|
27
27
|
|
|
28
|
+
def _on_log(level, log_content):
|
|
29
|
+
print(logging.getLevelName(level), log_content)
|
|
30
|
+
|
|
31
|
+
def my_on_events_message(event_type, subscribe_type, payload, raw_message):
|
|
32
|
+
if EVENT_TYPE_ORDER == event_type and ORDER_STATUS_CHANGED == subscribe_type:
|
|
33
|
+
print('----request_id:%s----' % payload['request_id'])
|
|
34
|
+
print(payload)
|
|
35
|
+
|
|
28
36
|
if __name__ == '__main__':
|
|
37
|
+
|
|
29
38
|
# Create EventsClient instance
|
|
30
39
|
trade_events_client = TradeEventsClient(your_app_key, your_app_secret, region_id)
|
|
31
|
-
trade_events_client.enable_logger()
|
|
32
40
|
# For non production environment, you need to set the domain name of the subscription service through eventsclient. For example, the domain name of the UAT environment is set here
|
|
33
41
|
# trade_events_client = TradeEventsClient(your_app_key, your_app_secret, region_id, host=optional_api_endpoint)
|
|
42
|
+
trade_events_client.on_log = _on_log
|
|
34
43
|
|
|
35
44
|
# Set the callback function when the event data is received.
|
|
36
45
|
# The data of order status change is printed here
|
|
37
46
|
|
|
38
|
-
def my_on_events_message(event_type, subscribe_type, payload, raw_message):
|
|
39
|
-
if EVENT_TYPE_ORDER == event_type and ORDER_STATUS_CHANGED == subscribe_type:
|
|
40
|
-
print('----request_id:%s----' % payload['request_id'])
|
|
41
|
-
print(payload['account_id'])
|
|
42
|
-
print(payload['client_order_id'])
|
|
43
|
-
print(payload['order_status'])
|
|
44
|
-
|
|
45
47
|
trade_events_client.on_events_message = my_on_events_message
|
|
46
48
|
# Set the account ID to be subscribed and initiate the subscription. This method is synchronous
|
|
47
49
|
trade_events_client.do_subscribe([account_id])
|
webull/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "1.0.
|
|
1
|
+
__version__ = "1.0.3"
|
webull/core/__init__.py
CHANGED
webull/core/data/endpoints.json
CHANGED
|
@@ -35,6 +35,9 @@
|
|
|
35
35
|
|
|
36
36
|
import logging
|
|
37
37
|
|
|
38
|
+
from webull.core import compat
|
|
39
|
+
from webull.core.exception.exceptions import ClientException
|
|
40
|
+
from webull.core.http.initializer.config.config_operation import ConfigOperation
|
|
38
41
|
from webull.core.http.initializer.token.token_manager import TokenManager
|
|
39
42
|
|
|
40
43
|
logger = logging.getLogger(__name__)
|
|
@@ -51,29 +54,62 @@ class ClientInitializer:
|
|
|
51
54
|
@staticmethod
|
|
52
55
|
def init_token(api_client):
|
|
53
56
|
"""Initialize token"""
|
|
54
|
-
|
|
55
|
-
|
|
57
|
+
disable_config_region_ids = ["hk"]
|
|
58
|
+
if api_client.get_region_id() in disable_config_region_ids:
|
|
59
|
+
if not ClientInitializer._check_region_token_enable(api_client):
|
|
60
|
+
return
|
|
61
|
+
else:
|
|
62
|
+
if not ClientInitializer._check_token_enable(api_client):
|
|
63
|
+
return
|
|
56
64
|
|
|
57
65
|
token_manager = TokenManager()
|
|
58
66
|
token_manager.init_token(api_client)
|
|
59
67
|
|
|
60
68
|
@staticmethod
|
|
61
|
-
def
|
|
69
|
+
def _check_region_token_enable(api_client):
|
|
62
70
|
"""
|
|
63
71
|
Check whether token checking is enabled in the specified region
|
|
64
72
|
"""
|
|
65
73
|
if api_client is None:
|
|
66
|
-
logger.warning("
|
|
74
|
+
logger.warning("_check_region_token_enable api_client is null, return False")
|
|
67
75
|
return False
|
|
68
76
|
|
|
69
77
|
if not api_client.get_region_id():
|
|
70
|
-
logger.warning("
|
|
78
|
+
logger.warning("_check_region_token_enable region_id is null, return False")
|
|
71
79
|
return False
|
|
72
80
|
|
|
73
81
|
enable_region_ids = ["hk"]
|
|
74
82
|
result = api_client.get_region_id() in enable_region_ids
|
|
75
83
|
logger.info(
|
|
76
|
-
"
|
|
77
|
-
result, enable_region_ids
|
|
84
|
+
"_check_region_token_enable result is %s, enable regionIds is %s.",
|
|
85
|
+
result, enable_region_ids
|
|
78
86
|
)
|
|
87
|
+
return result
|
|
88
|
+
|
|
89
|
+
@staticmethod
|
|
90
|
+
def _check_token_enable(api_client):
|
|
91
|
+
"""
|
|
92
|
+
Check whether token checking is enabled
|
|
93
|
+
"""
|
|
94
|
+
if api_client is None:
|
|
95
|
+
logger.warning("_check_token_enable api_client is null, return False")
|
|
96
|
+
return False
|
|
97
|
+
|
|
98
|
+
config_operation = ConfigOperation(api_client)
|
|
99
|
+
response = config_operation.get_config()
|
|
100
|
+
|
|
101
|
+
if response.status_code != 200:
|
|
102
|
+
msg = "_check_token_enable get_token_config returned non-200 response, raising exception. status_code:%s" % response.status_code
|
|
103
|
+
logger.error(compat.ensure_string(msg))
|
|
104
|
+
raise ClientException("ERROR_CHECK_TOKEN_ENABLE", msg)
|
|
105
|
+
|
|
106
|
+
token_config = response.json()
|
|
107
|
+
if not token_config:
|
|
108
|
+
msg = "_check_token_enable get_token_config result is empty."
|
|
109
|
+
logger.error(msg)
|
|
110
|
+
raise ClientException("ERROR_CHECK_TOKEN_ENABLE", msg)
|
|
111
|
+
|
|
112
|
+
result = token_config.get("token_check_enabled", False)
|
|
113
|
+
logger.info("_check_token_enable result is %s",result)
|
|
114
|
+
|
|
79
115
|
return result
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
#
|
|
26
|
+
#
|
|
27
|
+
# Unless required by applicable law or agreed to in writing,
|
|
28
|
+
# software distributed under the License is distributed on an
|
|
29
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
30
|
+
# KIND, either express or implied. See the License for the
|
|
31
|
+
# specific language governing permissions and limitations
|
|
32
|
+
# under the License.
|
|
33
|
+
|
|
34
|
+
# coding=utf-8
|
|
35
|
+
|
|
36
|
+
from webull.core.request import ApiRequest
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class GetConfigRequest(ApiRequest):
|
|
40
|
+
def __init__(self):
|
|
41
|
+
super().__init__("/openapi/config", version='v2', method="GET", query_params={})
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
#
|
|
26
|
+
#
|
|
27
|
+
# Unless required by applicable law or agreed to in writing,
|
|
28
|
+
# software distributed under the License is distributed on an
|
|
29
|
+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
|
30
|
+
# KIND, either express or implied. See the License for the
|
|
31
|
+
# specific language governing permissions and limitations
|
|
32
|
+
# under the License.
|
|
33
|
+
|
|
34
|
+
# coding=utf-8
|
|
35
|
+
|
|
36
|
+
from webull.core.http.initializer.config.bean.query_config_request import GetConfigRequest
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
class ConfigOperation:
|
|
40
|
+
def __init__(self, api_client):
|
|
41
|
+
self.client = api_client
|
|
42
|
+
|
|
43
|
+
def get_config(self):
|
|
44
|
+
"""
|
|
45
|
+
Get Config
|
|
46
|
+
"""
|
|
47
|
+
get_config_request = GetConfigRequest()
|
|
48
|
+
response = self.client.get_response(get_config_request)
|
|
49
|
+
return response
|
|
@@ -38,7 +38,7 @@ from webull.core.request import ApiRequest
|
|
|
38
38
|
|
|
39
39
|
class CheckTokenRequest(ApiRequest):
|
|
40
40
|
def __init__(self):
|
|
41
|
-
super().__init__("/auth/token/check", version='v2', method="POST", body_params={})
|
|
41
|
+
super().__init__("/openapi/auth/token/check", version='v2', method="POST", body_params={})
|
|
42
42
|
|
|
43
43
|
def set_token(self, token):
|
|
44
44
|
self.add_body_params("token", token)
|
|
@@ -38,7 +38,7 @@ from webull.core.request import ApiRequest
|
|
|
38
38
|
|
|
39
39
|
class CreateTokenRequest(ApiRequest):
|
|
40
40
|
def __init__(self):
|
|
41
|
-
super().__init__("/auth/token/create", version='v2', method="POST", body_params={})
|
|
41
|
+
super().__init__("/openapi/auth/token/create", version='v2', method="POST", body_params={})
|
|
42
42
|
|
|
43
43
|
def set_token(self, token):
|
|
44
44
|
if token:
|
|
@@ -38,7 +38,7 @@ from webull.core.request import ApiRequest
|
|
|
38
38
|
|
|
39
39
|
class RefreshTokenRequest(ApiRequest):
|
|
40
40
|
def __init__(self):
|
|
41
|
-
super().__init__("/auth/token/refresh", version='v2', method="POST", body_params={})
|
|
41
|
+
super().__init__("/openapi/auth/token/refresh", version='v2', method="POST", body_params={})
|
|
42
42
|
|
|
43
43
|
def set_token(self, token):
|
|
44
44
|
self.add_body_params("token", token)
|
|
@@ -33,8 +33,6 @@
|
|
|
33
33
|
|
|
34
34
|
# coding=utf-8
|
|
35
35
|
|
|
36
|
-
import logging
|
|
37
|
-
|
|
38
36
|
from webull.core.http.initializer.token.bean.check_token_request import CheckTokenRequest
|
|
39
37
|
from webull.core.http.initializer.token.bean.create_token_request import CreateTokenRequest
|
|
40
38
|
from webull.core.http.initializer.token.bean.refresh_token_request import RefreshTokenRequest
|