webull-openapi-python-sdk 1.0.4__py3-none-any.whl → 1.0.6__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/data/data_client.py +43 -0
- samples/trade/trade_client_v3.py +420 -0
- samples/trade/trade_event_client.py +3 -1
- webull/__init__.py +1 -1
- webull/core/__init__.py +1 -1
- webull/core/data/endpoints.json +1 -1
- webull/data/__init__.py +1 -1
- webull/data/common/category.py +3 -2
- webull/data/common/contract_type.py +20 -0
- webull/data/data_client.py +4 -0
- webull/data/quotes/crypto_market_data.py +58 -0
- webull/data/quotes/futures_market_data.py +89 -0
- webull/data/quotes/instrument.py +79 -2
- webull/data/quotes/market_data.py +0 -2
- webull/data/request/get_crypto_historical_bars_request.py +40 -0
- webull/data/request/get_crypto_instruments_request.py +44 -0
- webull/data/request/get_crypto_snapshot_request.py +31 -0
- webull/data/request/get_futures_depth_request.py +32 -0
- webull/data/request/get_futures_historical_bars_request.py +36 -0
- webull/data/request/get_futures_instruments_by_code_request.py +36 -0
- webull/data/request/get_futures_instruments_request.py +31 -0
- webull/data/request/get_futures_products_request.py +25 -0
- webull/data/request/get_futures_snapshot_request.py +31 -0
- webull/data/request/get_futures_tick_request.py +31 -0
- webull/data/request/get_instruments_request.py +13 -1
- webull/trade/__init__.py +1 -2
- webull/trade/common/category.py +2 -3
- webull/trade/request/v2/palce_order_request.py +5 -3
- webull/trade/request/v2/place_option_request.py +7 -7
- webull/trade/request/v2/place_order_request.py +4 -6
- webull/trade/request/v3/__init__.py +0 -0
- webull/trade/request/v3/cancel_order_request.py +28 -0
- webull/trade/request/v3/get_order_detail_request.py +26 -0
- webull/trade/request/v3/get_order_history_request.py +35 -0
- webull/trade/request/v3/get_order_open_request.py +29 -0
- webull/trade/request/v3/place_order_request.py +56 -0
- webull/trade/request/v3/preview_order_request.py +31 -0
- webull/trade/request/v3/replace_order_request.py +31 -0
- webull/trade/trade/v3/__init__.py +0 -0
- webull/trade/trade/v3/order_opration_v3.py +134 -0
- webull/trade/trade_client.py +2 -0
- {webull_openapi_python_sdk-1.0.4.dist-info → webull_openapi_python_sdk-1.0.6.dist-info}/METADATA +1 -1
- {webull_openapi_python_sdk-1.0.4.dist-info → webull_openapi_python_sdk-1.0.6.dist-info}/RECORD +48 -24
- {webull_openapi_python_sdk-1.0.4.dist-info → webull_openapi_python_sdk-1.0.6.dist-info}/WHEEL +0 -0
- {webull_openapi_python_sdk-1.0.4.dist-info → webull_openapi_python_sdk-1.0.6.dist-info}/licenses/LICENSE +0 -0
- {webull_openapi_python_sdk-1.0.4.dist-info → webull_openapi_python_sdk-1.0.6.dist-info}/licenses/NOTICE +0 -0
- {webull_openapi_python_sdk-1.0.4.dist-info → webull_openapi_python_sdk-1.0.6.dist-info}/top_level.txt +0 -0
samples/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ =
|
|
1
|
+
__version__ = '1.0.6'
|
samples/data/data_client.py
CHANGED
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
# coding=utf-8
|
|
16
16
|
|
|
17
17
|
from webull.data.common.category import Category
|
|
18
|
+
from webull.data.common.contract_type import ContractType
|
|
18
19
|
from webull.data.common.timespan import Timespan
|
|
19
20
|
from webull.core.client import ApiClient
|
|
20
21
|
from webull.data.data_client import DataClient
|
|
@@ -34,6 +35,22 @@ if __name__ == '__main__':
|
|
|
34
35
|
if res.status_code == 200:
|
|
35
36
|
print('get_instrument:', res.json())
|
|
36
37
|
|
|
38
|
+
res = data_client.instrument.get_crypto_instrument()
|
|
39
|
+
if res.status_code == 200:
|
|
40
|
+
print('get_crypto_instrument(all):', res.json())
|
|
41
|
+
|
|
42
|
+
res = data_client.instrument.get_crypto_instrument("BTCUSD")
|
|
43
|
+
if res.status_code == 200:
|
|
44
|
+
print('get_crypto_instrument:', res.json())
|
|
45
|
+
|
|
46
|
+
res = data_client.crypto_market_data.get_crypto_snapshot("BTCUSD")
|
|
47
|
+
if res.status_code == 200:
|
|
48
|
+
print('get_crypto_snapshot:', res.json())
|
|
49
|
+
|
|
50
|
+
res = data_client.crypto_market_data.get_crypto_history_bar("BTCUSD", Category.US_CRYPTO.name, Timespan.M1.name)
|
|
51
|
+
if res.status_code == 200:
|
|
52
|
+
print('get_crypto_history_bar:', res.json())
|
|
53
|
+
|
|
37
54
|
res = data_client.market_data.get_snapshot('AAPL', Category.US_STOCK.name, extend_hour_required=True, overnight_required=True)
|
|
38
55
|
if res.status_code == 200:
|
|
39
56
|
print('get_snapshot:', res.json())
|
|
@@ -54,4 +71,30 @@ if __name__ == '__main__':
|
|
|
54
71
|
if res.status_code == 200:
|
|
55
72
|
print('get_quotes:', res.json())
|
|
56
73
|
|
|
74
|
+
res = data_client.futures_market_data.get_futures_depth("SILZ5", Category.US_FUTURES.name, depth=1)
|
|
75
|
+
if res.status_code == 200:
|
|
76
|
+
print('get_futures_depth:', res.json())
|
|
77
|
+
|
|
78
|
+
res = data_client.futures_market_data.get_futures_history_bars('SILZ5,6BM6', Category.US_FUTURES.name, Timespan.M1.name)
|
|
79
|
+
if res.status_code == 200:
|
|
80
|
+
print('get_futures_history_bars:', res.json())
|
|
81
|
+
|
|
82
|
+
res = data_client.futures_market_data.get_futures_tick("SILZ5", Category.US_FUTURES.name, count=10)
|
|
83
|
+
if res.status_code == 200:
|
|
84
|
+
print('get_futures_tick:', res.json())
|
|
85
|
+
|
|
86
|
+
res = data_client.futures_market_data.get_futures_snapshot("SILZ5,6BM6", Category.US_FUTURES.name)
|
|
87
|
+
if res.status_code == 200:
|
|
88
|
+
print('get_futures_snapshot:', res.json())
|
|
57
89
|
|
|
90
|
+
res = data_client.instrument.get_futures_products(Category.US_FUTURES.name)
|
|
91
|
+
if res.status_code == 200:
|
|
92
|
+
print('get_futures_products:', res.json())
|
|
93
|
+
|
|
94
|
+
res = data_client.instrument.get_futures_instrument("ESZ5", Category.US_FUTURES.name)
|
|
95
|
+
if res.status_code == 200:
|
|
96
|
+
print('get_futures_instrument:', res.json())
|
|
97
|
+
|
|
98
|
+
res = data_client.instrument.get_futures_instrument_by_code("ES", Category.US_FUTURES.name, ContractType.MONTHLY.name)
|
|
99
|
+
if res.status_code == 200:
|
|
100
|
+
print('get_futures_instrument_by_code:', res.json())
|
|
@@ -0,0 +1,420 @@
|
|
|
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 json
|
|
16
|
+
import uuid
|
|
17
|
+
from time import sleep
|
|
18
|
+
|
|
19
|
+
from webull.core.client import ApiClient
|
|
20
|
+
from webull.trade.trade_client import TradeClient
|
|
21
|
+
|
|
22
|
+
optional_api_endpoint = "<api_endpoint>"
|
|
23
|
+
your_app_key = "<your_app_key>"
|
|
24
|
+
your_app_secret = "<your_app_secret>"
|
|
25
|
+
region_id = "<region_id>"
|
|
26
|
+
account_id = "<your_account_id>"
|
|
27
|
+
api_client = ApiClient(your_app_key, your_app_secret, region_id)
|
|
28
|
+
api_client.add_endpoint(region_id, optional_api_endpoint)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
if __name__ == '__main__':
|
|
32
|
+
trade_client = TradeClient(api_client)
|
|
33
|
+
|
|
34
|
+
res = trade_client.account_v2.get_account_list()
|
|
35
|
+
if res.status_code == 200:
|
|
36
|
+
print('get account list:', res.json())
|
|
37
|
+
|
|
38
|
+
res = trade_client.account_v2.get_account_balance(account_id)
|
|
39
|
+
if res.status_code == 200:
|
|
40
|
+
print('get account balance res:', res.json())
|
|
41
|
+
|
|
42
|
+
res = trade_client.account_v2.get_account_position(account_id)
|
|
43
|
+
if res.status_code == 200:
|
|
44
|
+
print('get account position res:', res.json())
|
|
45
|
+
|
|
46
|
+
# ============================================================
|
|
47
|
+
# Equity Order Example
|
|
48
|
+
# ============================================================
|
|
49
|
+
|
|
50
|
+
# normal equity order
|
|
51
|
+
normal_equity_client_order_id = uuid.uuid4().hex
|
|
52
|
+
print('client order id:', normal_equity_client_order_id)
|
|
53
|
+
new_normal_equity_orders = [
|
|
54
|
+
{
|
|
55
|
+
"combo_type": "NORMAL",
|
|
56
|
+
"client_order_id": normal_equity_client_order_id,
|
|
57
|
+
"symbol": "AAPL",
|
|
58
|
+
"instrument_type": "EQUITY",
|
|
59
|
+
"market": "US",
|
|
60
|
+
"order_type": "LIMIT",
|
|
61
|
+
"limit_price": "188",
|
|
62
|
+
"quantity": "1",
|
|
63
|
+
"support_trading_session": "N",
|
|
64
|
+
"side": "BUY",
|
|
65
|
+
"time_in_force": "DAY",
|
|
66
|
+
"entrust_type": "QTY"
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
res = trade_client.order_v3.preview_order(account_id, new_normal_equity_orders)
|
|
71
|
+
if res.status_code == 200:
|
|
72
|
+
print('preview normal equity order res:', res.json())
|
|
73
|
+
|
|
74
|
+
res = trade_client.order_v3.place_order(account_id, new_normal_equity_orders)
|
|
75
|
+
if res.status_code == 200:
|
|
76
|
+
print('place normal equity order res:', res.json())
|
|
77
|
+
sleep(3)
|
|
78
|
+
|
|
79
|
+
replace_normal_equity_orders = [
|
|
80
|
+
{
|
|
81
|
+
"client_order_id": normal_equity_client_order_id,
|
|
82
|
+
"quantity": "100",
|
|
83
|
+
"limit_price": "200"
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
res = trade_client.order_v3.replace_order(account_id, replace_normal_equity_orders)
|
|
87
|
+
if res.status_code == 200:
|
|
88
|
+
print('replace normal equity order res:', res.json())
|
|
89
|
+
sleep(3)
|
|
90
|
+
|
|
91
|
+
res = trade_client.order_v3.cancel_order(account_id, normal_equity_client_order_id)
|
|
92
|
+
if res.status_code == 200:
|
|
93
|
+
print('cancel normal equity order res:', res.json())
|
|
94
|
+
|
|
95
|
+
res = trade_client.order_v3.get_order_open(account_id=account_id)
|
|
96
|
+
if res.status_code == 200:
|
|
97
|
+
print("order_open_res=" + json.dumps(res.json(), indent=4))
|
|
98
|
+
|
|
99
|
+
res = trade_client.order_v3.get_order_history(account_id)
|
|
100
|
+
if res.status_code == 200:
|
|
101
|
+
print('get order history res:', res.json())
|
|
102
|
+
|
|
103
|
+
res = trade_client.order_v3.get_order_detail(account_id, normal_equity_client_order_id)
|
|
104
|
+
if res.status_code == 200:
|
|
105
|
+
print('get order detail res:', res.json())
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
# combo equity order
|
|
110
|
+
master_equity_client_order_id = uuid.uuid4().hex
|
|
111
|
+
stop_profit_equity_client_order_id = uuid.uuid4().hex
|
|
112
|
+
stop_loss_equity_client_order_id = uuid.uuid4().hex
|
|
113
|
+
print('normal_equity_master_client_order_id:', master_equity_client_order_id)
|
|
114
|
+
print('stop_profit_equity_client_order_id:', stop_profit_equity_client_order_id)
|
|
115
|
+
print('stop_loss_equity_client_order_id:', stop_loss_equity_client_order_id)
|
|
116
|
+
new_combo_orders = [
|
|
117
|
+
{
|
|
118
|
+
"client_order_id": master_equity_client_order_id,
|
|
119
|
+
"combo_type": "MASTER",
|
|
120
|
+
"symbol": "F",
|
|
121
|
+
"instrument_type": "EQUITY",
|
|
122
|
+
"market": "US",
|
|
123
|
+
"order_type": "LIMIT",
|
|
124
|
+
"quantity": "1",
|
|
125
|
+
"support_trading_session": "N",
|
|
126
|
+
"limit_price": "10.5",
|
|
127
|
+
"side": "BUY",
|
|
128
|
+
"entrust_type": "QTY",
|
|
129
|
+
"time_in_force": "DAY"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"client_order_id": stop_profit_equity_client_order_id,
|
|
133
|
+
"combo_type": "STOP_PROFIT",
|
|
134
|
+
"symbol": "F",
|
|
135
|
+
"instrument_type": "EQUITY",
|
|
136
|
+
"market": "US",
|
|
137
|
+
"order_type": "LIMIT",
|
|
138
|
+
"quantity": "1",
|
|
139
|
+
"support_trading_session": "N",
|
|
140
|
+
"limit_price": "11.5",
|
|
141
|
+
"side": "SELL",
|
|
142
|
+
"entrust_type": "QTY",
|
|
143
|
+
"time_in_force": "DAY"
|
|
144
|
+
},
|
|
145
|
+
{
|
|
146
|
+
"client_order_id": stop_loss_equity_client_order_id,
|
|
147
|
+
"combo_type": "STOP_LOSS",
|
|
148
|
+
"symbol": "F",
|
|
149
|
+
"instrument_type": "EQUITY",
|
|
150
|
+
"market": "US",
|
|
151
|
+
"order_type": "STOP_LOSS",
|
|
152
|
+
"quantity": "1",
|
|
153
|
+
"support_trading_session": "N",
|
|
154
|
+
"stop_price": "10",
|
|
155
|
+
"side": "SELL",
|
|
156
|
+
"entrust_type": "QTY",
|
|
157
|
+
"time_in_force": "DAY"
|
|
158
|
+
}
|
|
159
|
+
]
|
|
160
|
+
|
|
161
|
+
res = trade_client.order_v3.preview_order(account_id, new_combo_orders)
|
|
162
|
+
if res.status_code == 200:
|
|
163
|
+
print('preview combo equity order res:', res.json())
|
|
164
|
+
|
|
165
|
+
res = trade_client.order_v3.place_order(account_id, new_combo_orders)
|
|
166
|
+
if res.status_code == 200:
|
|
167
|
+
print('place combo equity order res:', res.json())
|
|
168
|
+
sleep(3)
|
|
169
|
+
|
|
170
|
+
replace_combo_orders = [
|
|
171
|
+
{
|
|
172
|
+
"client_order_id": master_equity_client_order_id,
|
|
173
|
+
"quantity": "2"
|
|
174
|
+
},
|
|
175
|
+
{
|
|
176
|
+
"client_order_id": stop_profit_equity_client_order_id,
|
|
177
|
+
"quantity": "2"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"client_order_id": stop_loss_equity_client_order_id,
|
|
181
|
+
"quantity": "2"
|
|
182
|
+
}
|
|
183
|
+
]
|
|
184
|
+
res = trade_client.order_v3.replace_order(account_id, replace_combo_orders)
|
|
185
|
+
if res.status_code == 200:
|
|
186
|
+
print('replace combo equity order res:', res.json())
|
|
187
|
+
sleep(3)
|
|
188
|
+
|
|
189
|
+
res = trade_client.order_v3.cancel_order(account_id, master_equity_client_order_id)
|
|
190
|
+
if res.status_code == 200:
|
|
191
|
+
print('cancel master equity order res:', res.json())
|
|
192
|
+
|
|
193
|
+
res = trade_client.order_v3.get_order_history(account_id)
|
|
194
|
+
if res.status_code == 200:
|
|
195
|
+
print('get order history res:', res.json())
|
|
196
|
+
|
|
197
|
+
res = trade_client.order_v3.get_order_open(account_id=account_id)
|
|
198
|
+
if res.status_code == 200:
|
|
199
|
+
print("order_open_res=" + json.dumps(res.json(), indent=4))
|
|
200
|
+
|
|
201
|
+
res = trade_client.order_v3.get_order_detail(account_id, master_equity_client_order_id)
|
|
202
|
+
if res.status_code == 200:
|
|
203
|
+
print('get master order detail res:', res.json())
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
# ============================================================
|
|
209
|
+
# Option Order Example
|
|
210
|
+
# ============================================================
|
|
211
|
+
|
|
212
|
+
# normal option order
|
|
213
|
+
normal_option_client_order_id = uuid.uuid4().hex
|
|
214
|
+
new_normal_option_orders = [
|
|
215
|
+
{
|
|
216
|
+
"client_order_id": normal_option_client_order_id,
|
|
217
|
+
"combo_type": "NORMAL",
|
|
218
|
+
"order_type": "LIMIT",
|
|
219
|
+
"quantity": "1",
|
|
220
|
+
"limit_price": "21.25",
|
|
221
|
+
"option_strategy": "SINGLE",
|
|
222
|
+
"side": "BUY",
|
|
223
|
+
"time_in_force": "GTC",
|
|
224
|
+
"entrust_type": "QTY",
|
|
225
|
+
"legs": [
|
|
226
|
+
{
|
|
227
|
+
"side": "BUY",
|
|
228
|
+
"quantity": "1",
|
|
229
|
+
"symbol": "TSLA",
|
|
230
|
+
"strike_price": "400",
|
|
231
|
+
"option_expire_date": "2025-12-26",
|
|
232
|
+
"instrument_type": "OPTION",
|
|
233
|
+
"option_type": "CALL",
|
|
234
|
+
"market": "US"
|
|
235
|
+
}
|
|
236
|
+
]
|
|
237
|
+
}
|
|
238
|
+
]
|
|
239
|
+
|
|
240
|
+
# preview
|
|
241
|
+
res = trade_client.order_v3.preview_order(account_id, new_normal_option_orders)
|
|
242
|
+
if res.status_code == 200:
|
|
243
|
+
print("preview normal option order res:" + json.dumps(res.json(), indent=4))
|
|
244
|
+
|
|
245
|
+
# place
|
|
246
|
+
res = trade_client.order_v3.place_order(account_id, new_normal_option_orders)
|
|
247
|
+
if res.status_code == 200:
|
|
248
|
+
print("place normal option order res:" + json.dumps(res.json(), indent=4))
|
|
249
|
+
sleep(3)
|
|
250
|
+
|
|
251
|
+
# replace for Webull HK
|
|
252
|
+
# replace_normal_option_orders = [
|
|
253
|
+
# {
|
|
254
|
+
# "client_order_id": normal_option_client_order_id,
|
|
255
|
+
# "quantity": "2",
|
|
256
|
+
# "limit_price": "11.3"
|
|
257
|
+
# }
|
|
258
|
+
# ]
|
|
259
|
+
# res = trade_client.order_v3.replace_option(account_id, replace_normal_option_orders)
|
|
260
|
+
# if res.status_code == 200:
|
|
261
|
+
# print("replace normal option order res:" + json.dumps(res.json(), indent=4))
|
|
262
|
+
# sleep(5)
|
|
263
|
+
|
|
264
|
+
# replace for Webull US
|
|
265
|
+
res = trade_client.order_v3.get_order_detail(account_id, normal_option_client_order_id)
|
|
266
|
+
if res.status_code == 200:
|
|
267
|
+
print('get normal option order detail res:', res.json())
|
|
268
|
+
data = res.json() or {}
|
|
269
|
+
leg_id = (
|
|
270
|
+
data.get("orders", [{}])[0]
|
|
271
|
+
.get("legs", [{}])[0]
|
|
272
|
+
.get("id")
|
|
273
|
+
)
|
|
274
|
+
print('get normal option order detail id :', leg_id)
|
|
275
|
+
|
|
276
|
+
# If it is a multi-leg option, you need to manually match it to the corresponding sub-leg orderId.
|
|
277
|
+
if leg_id:
|
|
278
|
+
replace_normal_option_orders = [
|
|
279
|
+
{
|
|
280
|
+
"client_order_id": normal_option_client_order_id,
|
|
281
|
+
"quantity": "2",
|
|
282
|
+
"limit_price": "21.3",
|
|
283
|
+
"legs": [
|
|
284
|
+
{
|
|
285
|
+
"id": leg_id,
|
|
286
|
+
"quantity": "2"
|
|
287
|
+
}
|
|
288
|
+
]
|
|
289
|
+
}
|
|
290
|
+
]
|
|
291
|
+
res = trade_client.order_v3.replace_order(account_id, replace_normal_option_orders)
|
|
292
|
+
if res.status_code == 200:
|
|
293
|
+
print("replace normal option order res:" + json.dumps(res.json(), indent=4))
|
|
294
|
+
sleep(3)
|
|
295
|
+
|
|
296
|
+
# cancel
|
|
297
|
+
res = trade_client.order_v3.cancel_order(account_id, normal_option_client_order_id)
|
|
298
|
+
if res.status_code == 200:
|
|
299
|
+
print("cancel normal option order res:" + json.dumps(res.json(), indent=4))
|
|
300
|
+
|
|
301
|
+
res = trade_client.order_v3.get_order_history(account_id)
|
|
302
|
+
if res.status_code == 200:
|
|
303
|
+
print('get order history res:', res.json())
|
|
304
|
+
|
|
305
|
+
res = trade_client.order_v3.get_order_open(account_id=account_id)
|
|
306
|
+
if res.status_code == 200:
|
|
307
|
+
print("order_open_res=" + json.dumps(res.json(), indent=4))
|
|
308
|
+
|
|
309
|
+
res = trade_client.order_v3.get_order_detail(account_id, normal_option_client_order_id)
|
|
310
|
+
if res.status_code == 200:
|
|
311
|
+
print('get option order detail res:', res.json())
|
|
312
|
+
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
# ============================================================
|
|
316
|
+
# Crypto Order Example
|
|
317
|
+
# For the cryptocurrency example, please use a cryptocurrency account ID.
|
|
318
|
+
# ============================================================
|
|
319
|
+
|
|
320
|
+
# normal crypto order
|
|
321
|
+
normal_crypto_client_order_id = uuid.uuid4().hex
|
|
322
|
+
print('client order id:', normal_crypto_client_order_id)
|
|
323
|
+
new_normal_crypto_orders = [
|
|
324
|
+
{
|
|
325
|
+
"combo_type": "NORMAL",
|
|
326
|
+
"client_order_id": normal_crypto_client_order_id,
|
|
327
|
+
"symbol": "BTCUSD",
|
|
328
|
+
"instrument_type": "CRYPTO",
|
|
329
|
+
"market": "US",
|
|
330
|
+
"order_type": "LIMIT",
|
|
331
|
+
"limit_price": "80000",
|
|
332
|
+
"quantity": "0.003",
|
|
333
|
+
"side": "BUY",
|
|
334
|
+
"time_in_force": "DAY",
|
|
335
|
+
"entrust_type": "QTY"
|
|
336
|
+
}
|
|
337
|
+
]
|
|
338
|
+
|
|
339
|
+
res = trade_client.order_v3.place_order(account_id, new_normal_crypto_orders)
|
|
340
|
+
if res.status_code == 200:
|
|
341
|
+
print('place normal crypto order res:', res.json())
|
|
342
|
+
sleep(3)
|
|
343
|
+
|
|
344
|
+
res = trade_client.order_v3.cancel_order(account_id, normal_crypto_client_order_id)
|
|
345
|
+
if res.status_code == 200:
|
|
346
|
+
print('cancel normal crypto order res:', res.json())
|
|
347
|
+
|
|
348
|
+
res = trade_client.order_v3.get_order_open(account_id=account_id)
|
|
349
|
+
if res.status_code == 200:
|
|
350
|
+
print("order_open_res=" + json.dumps(res.json(), indent=4))
|
|
351
|
+
|
|
352
|
+
res = trade_client.order_v3.get_order_history(account_id)
|
|
353
|
+
if res.status_code == 200:
|
|
354
|
+
print('get order history res:', res.json())
|
|
355
|
+
|
|
356
|
+
res = trade_client.order_v3.get_order_detail(account_id, normal_crypto_client_order_id)
|
|
357
|
+
if res.status_code == 200:
|
|
358
|
+
print('get order detail res:', res.json())
|
|
359
|
+
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
# ============================================================
|
|
363
|
+
# Futures Order Example
|
|
364
|
+
# ============================================================
|
|
365
|
+
|
|
366
|
+
# normal futures order
|
|
367
|
+
normal_futures_client_order_id = uuid.uuid4().hex
|
|
368
|
+
print('futures client order id:', normal_futures_client_order_id)
|
|
369
|
+
new_normal_futures_orders = [
|
|
370
|
+
{
|
|
371
|
+
"combo_type": "NORMAL",
|
|
372
|
+
"client_order_id": normal_futures_client_order_id,
|
|
373
|
+
"symbol": "ESZ5",
|
|
374
|
+
"instrument_type": "FUTURES",
|
|
375
|
+
"market": "US",
|
|
376
|
+
"order_type": "LIMIT",
|
|
377
|
+
"limit_price": "4500",
|
|
378
|
+
"quantity": "1",
|
|
379
|
+
"side": "BUY",
|
|
380
|
+
"time_in_force": "DAY",
|
|
381
|
+
"entrust_type": "QTY"
|
|
382
|
+
}
|
|
383
|
+
]
|
|
384
|
+
res = trade_client.order_v3.place_order(account_id, new_normal_futures_orders)
|
|
385
|
+
if res.status_code == 200:
|
|
386
|
+
print('place normal futures order res:', res.json())
|
|
387
|
+
sleep(3)
|
|
388
|
+
|
|
389
|
+
# normal futures order replace
|
|
390
|
+
replace_normal_futures_orders = [
|
|
391
|
+
{
|
|
392
|
+
"client_order_id": normal_futures_client_order_id,
|
|
393
|
+
"quantity": "2",
|
|
394
|
+
"limit_price": "4550"
|
|
395
|
+
}
|
|
396
|
+
]
|
|
397
|
+
res = trade_client.order_v3.replace_order(account_id, replace_normal_futures_orders)
|
|
398
|
+
if res.status_code == 200:
|
|
399
|
+
print('replace normal futures order res:', res.json())
|
|
400
|
+
sleep(3)
|
|
401
|
+
|
|
402
|
+
# normal futures order cancel
|
|
403
|
+
res = trade_client.order_v3.cancel_order(account_id, normal_futures_client_order_id)
|
|
404
|
+
if res.status_code == 200:
|
|
405
|
+
print('cancel normal futures order res:', res.json())
|
|
406
|
+
|
|
407
|
+
# get futures order detail
|
|
408
|
+
res = trade_client.order_v3.get_order_detail(account_id, normal_futures_client_order_id)
|
|
409
|
+
if res.status_code == 200:
|
|
410
|
+
print('get futures order detail res:', res.json())
|
|
411
|
+
|
|
412
|
+
# get futures open orders
|
|
413
|
+
res = trade_client.order_v3.get_order_open(account_id, page_size=10)
|
|
414
|
+
if res.status_code == 200:
|
|
415
|
+
print("order_open_res=" + json.dumps(res.json(), indent=4))
|
|
416
|
+
|
|
417
|
+
# get futures order history
|
|
418
|
+
res = trade_client.order_v3.get_order_history(account_id, page_size=10)
|
|
419
|
+
if res.status_code == 200:
|
|
420
|
+
print('get order history res:', res.json())
|
|
@@ -37,7 +37,9 @@ if __name__ == '__main__':
|
|
|
37
37
|
|
|
38
38
|
# Create EventsClient instance
|
|
39
39
|
trade_events_client = TradeEventsClient(your_app_key, your_app_secret, region_id)
|
|
40
|
-
# For non production environment, you need to set the domain name of the subscription service through
|
|
40
|
+
# [Important] For non production environment, you need to set the domain name of the subscription service through events client. For example, the domain name of the UAT environment is set here
|
|
41
|
+
# [Important] For non production environment, you need to set the domain name of the subscription service through events client. For example, the domain name of the UAT environment is set here
|
|
42
|
+
# [Important] For non production environment, you need to set the domain name of the subscription service through events client. For example, the domain name of the UAT environment is set here
|
|
41
43
|
# trade_events_client = TradeEventsClient(your_app_key, your_app_secret, region_id, host=optional_api_endpoint)
|
|
42
44
|
trade_events_client.on_log = _on_log
|
|
43
45
|
|
webull/__init__.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ =
|
|
1
|
+
__version__ = '1.0.6'
|
webull/core/__init__.py
CHANGED
webull/core/data/endpoints.json
CHANGED
webull/data/__init__.py
CHANGED
webull/data/common/category.py
CHANGED
|
@@ -20,7 +20,8 @@ class Category(EasyEnum):
|
|
|
20
20
|
US_STOCK = (1, 'US STOCK')
|
|
21
21
|
US_OPTION = (2, 'US OPTION')
|
|
22
22
|
HK_STOCK = (3, 'HK STOCK')
|
|
23
|
-
CRYPTO = (4, 'CRYPTO')
|
|
24
23
|
US_ETF = (5, 'US ETF')
|
|
25
24
|
HK_ETF = (6, 'HK ETF')
|
|
26
|
-
CN_STOCK = (7, "CN STOCK")
|
|
25
|
+
CN_STOCK = (7, "CN STOCK")
|
|
26
|
+
US_CRYPTO = (8, "US CRYPTO")
|
|
27
|
+
US_FUTURES = (12, "US FUTURES")
|
|
@@ -0,0 +1,20 @@
|
|
|
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
|
+
# coding=utf-8
|
|
16
|
+
|
|
17
|
+
from webull.core.common.easy_enum import EasyEnum
|
|
18
|
+
class ContractType(EasyEnum):
|
|
19
|
+
MONTHLY = (1, "MONTHLY")
|
|
20
|
+
MAIN = (2, "MAIN")
|
webull/data/data_client.py
CHANGED
|
@@ -18,6 +18,8 @@ import logging
|
|
|
18
18
|
import sys
|
|
19
19
|
|
|
20
20
|
from webull.core.http.initializer.client_initializer import ClientInitializer
|
|
21
|
+
from webull.data.quotes.crypto_market_data import CryptoMarketData
|
|
22
|
+
from webull.data.quotes.futures_market_data import FuturesMarketData
|
|
21
23
|
from webull.data.quotes.instrument import Instrument
|
|
22
24
|
from webull.data.quotes.market_data import MarketData
|
|
23
25
|
|
|
@@ -28,6 +30,8 @@ class DataClient:
|
|
|
28
30
|
ClientInitializer.initializer(api_client)
|
|
29
31
|
self.instrument = Instrument(api_client)
|
|
30
32
|
self.market_data = MarketData(api_client)
|
|
33
|
+
self.crypto_market_data = CryptoMarketData(api_client)
|
|
34
|
+
self.futures_market_data = FuturesMarketData(api_client)
|
|
31
35
|
|
|
32
36
|
def _init_logger(self, api_client):
|
|
33
37
|
# No logger configured, using default console and local file logging.
|
|
@@ -0,0 +1,58 @@
|
|
|
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
|
+
from webull.data.common.category import Category
|
|
15
|
+
from webull.data.request.get_crypto_historical_bars_request import GetCryptoHistoricalBarsRequest
|
|
16
|
+
from webull.data.request.get_crypto_snapshot_request import GetCryptoSnapshotRequest
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CryptoMarketData:
|
|
20
|
+
def __init__(self, api_client):
|
|
21
|
+
self.client = api_client
|
|
22
|
+
|
|
23
|
+
def get_crypto_history_bar(self, symbols, category, timespan, count='200', real_time_required=None):
|
|
24
|
+
"""
|
|
25
|
+
Returns to Instrument in the window aggregated data.
|
|
26
|
+
According to the last N K-lines of the stock code, it supports various granularity K-lines such as m1 and m5.
|
|
27
|
+
Currently, only the K-line with the previous weight is provided for the daily K-line and above,
|
|
28
|
+
and only the un-weighted K-line is provided for the minute K.
|
|
29
|
+
|
|
30
|
+
:param symbols: List of security codes (e.g., single: 00700; multiple: BTCUSD,ETHUSD or ['BTCUSD','ETHUSD']).
|
|
31
|
+
:param category: Security type. Fixed value: "US_CRYPTO"
|
|
32
|
+
:param timespan: K-line time granularity
|
|
33
|
+
:param count: The number of lines: the default is 200, and the maximum limit is 1200
|
|
34
|
+
:param real_time_required: Returns the latest trade quote data. By default, the most recent market data is returned.
|
|
35
|
+
By default, only intraday candlestick data is returned.
|
|
36
|
+
"""
|
|
37
|
+
crypto_history_bar_request = GetCryptoHistoricalBarsRequest()
|
|
38
|
+
crypto_history_bar_request.set_symbols(symbols)
|
|
39
|
+
crypto_history_bar_request.set_category(category)
|
|
40
|
+
crypto_history_bar_request.set_timespan(timespan)
|
|
41
|
+
crypto_history_bar_request.set_count(count)
|
|
42
|
+
crypto_history_bar_request.set_real_time_required(real_time_required)
|
|
43
|
+
response = self.client.get_response(crypto_history_bar_request)
|
|
44
|
+
return response
|
|
45
|
+
|
|
46
|
+
def get_crypto_snapshot(self, symbols, category=Category.US_CRYPTO.name):
|
|
47
|
+
"""
|
|
48
|
+
Query the latest crypto market snapshots in batches according to the security code list.
|
|
49
|
+
|
|
50
|
+
:param symbols: List of security codes (e.g., single: 00700; multiple: BTCUSD,ETHUSD or ['BTCUSD','ETHUSD']).
|
|
51
|
+
Up to 20 symbols can be subscribed per request.
|
|
52
|
+
:param category: Security type. Fixed value: "US_CRYPTO".
|
|
53
|
+
"""
|
|
54
|
+
crypto_snapshot_request = GetCryptoSnapshotRequest()
|
|
55
|
+
crypto_snapshot_request.set_symbols(symbols)
|
|
56
|
+
crypto_snapshot_request.set_category(category)
|
|
57
|
+
response = self.client.get_response(crypto_snapshot_request)
|
|
58
|
+
return response
|