webull-openapi-python-sdk 1.0.0__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 -0
- samples/data/__init__.py +1 -0
- samples/data/data_client.py +57 -0
- samples/data/data_streaming_client.py +86 -0
- samples/data/data_streaming_client_async.py +101 -0
- samples/trade/__init__.py +0 -0
- samples/trade/trade_client.py +163 -0
- samples/trade/trade_client_v2.py +181 -0
- samples/trade/trade_event_client.py +47 -0
- webull/__init__.py +1 -0
- webull/core/__init__.py +12 -0
- webull/core/auth/__init__.py +0 -0
- webull/core/auth/algorithm/__init__.py +0 -0
- webull/core/auth/algorithm/sha_hmac1.py +65 -0
- webull/core/auth/algorithm/sha_hmac256.py +75 -0
- webull/core/auth/composer/__init__.py +0 -0
- webull/core/auth/composer/default_signature_composer.py +125 -0
- webull/core/auth/credentials.py +46 -0
- webull/core/auth/signers/__init__.py +0 -0
- webull/core/auth/signers/app_key_signer.py +72 -0
- webull/core/auth/signers/signer.py +48 -0
- webull/core/auth/signers/signer_factory.py +58 -0
- webull/core/cache/__init__.py +225 -0
- webull/core/client.py +410 -0
- webull/core/common/__init__.py +0 -0
- webull/core/common/api_type.py +19 -0
- webull/core/common/easy_enum.py +35 -0
- webull/core/common/region.py +7 -0
- webull/core/compat.py +85 -0
- webull/core/context/__init__.py +0 -0
- webull/core/context/request_context_holder.py +33 -0
- webull/core/data/endpoints.json +22 -0
- webull/core/data/retry_config.json +15 -0
- webull/core/endpoint/__init__.py +8 -0
- webull/core/endpoint/chained_endpoint_resolver.py +57 -0
- webull/core/endpoint/default_endpoint_resolver.py +60 -0
- webull/core/endpoint/local_config_regional_endpoint_resolver.py +77 -0
- webull/core/endpoint/resolver_endpoint_request.py +46 -0
- webull/core/endpoint/user_customized_endpoint_resolver.py +55 -0
- webull/core/exception/__init__.py +0 -0
- webull/core/exception/error_code.py +23 -0
- webull/core/exception/error_msg.py +21 -0
- webull/core/exception/exceptions.py +53 -0
- webull/core/headers.py +57 -0
- webull/core/http/__init__.py +0 -0
- webull/core/http/initializer/__init__.py +0 -0
- webull/core/http/initializer/client_initializer.py +79 -0
- webull/core/http/initializer/token/__init__.py +0 -0
- webull/core/http/initializer/token/bean/__init__.py +0 -0
- webull/core/http/initializer/token/bean/access_token.py +40 -0
- webull/core/http/initializer/token/bean/check_token_request.py +44 -0
- webull/core/http/initializer/token/bean/create_token_request.py +45 -0
- webull/core/http/initializer/token/bean/refresh_token_request.py +44 -0
- webull/core/http/initializer/token/token_manager.py +208 -0
- webull/core/http/initializer/token/token_operation.py +72 -0
- webull/core/http/method_type.py +43 -0
- webull/core/http/protocol_type.py +43 -0
- webull/core/http/request.py +121 -0
- webull/core/http/response.py +166 -0
- webull/core/request.py +278 -0
- webull/core/retry/__init__.py +0 -0
- webull/core/retry/backoff_strategy.py +102 -0
- webull/core/retry/retry_condition.py +214 -0
- webull/core/retry/retry_policy.py +63 -0
- webull/core/retry/retry_policy_context.py +51 -0
- webull/core/utils/__init__.py +0 -0
- webull/core/utils/common.py +62 -0
- webull/core/utils/data.py +25 -0
- webull/core/utils/desensitize.py +33 -0
- webull/core/utils/validation.py +49 -0
- webull/core/vendored/__init__.py +0 -0
- webull/core/vendored/requests/__init__.py +94 -0
- webull/core/vendored/requests/__version__.py +28 -0
- webull/core/vendored/requests/_internal_utils.py +56 -0
- webull/core/vendored/requests/adapters.py +539 -0
- webull/core/vendored/requests/api.py +166 -0
- webull/core/vendored/requests/auth.py +307 -0
- webull/core/vendored/requests/certs.py +34 -0
- webull/core/vendored/requests/compat.py +85 -0
- webull/core/vendored/requests/cookies.py +555 -0
- webull/core/vendored/requests/exceptions.py +136 -0
- webull/core/vendored/requests/help.py +134 -0
- webull/core/vendored/requests/hooks.py +48 -0
- webull/core/vendored/requests/models.py +960 -0
- webull/core/vendored/requests/packages/__init__.py +17 -0
- webull/core/vendored/requests/packages/certifi/__init__.py +17 -0
- webull/core/vendored/requests/packages/certifi/__main__.py +16 -0
- webull/core/vendored/requests/packages/certifi/cacert.pem +4433 -0
- webull/core/vendored/requests/packages/certifi/core.py +51 -0
- webull/core/vendored/requests/packages/chardet/__init__.py +53 -0
- webull/core/vendored/requests/packages/chardet/big5freq.py +400 -0
- webull/core/vendored/requests/packages/chardet/big5prober.py +61 -0
- webull/core/vendored/requests/packages/chardet/chardistribution.py +247 -0
- webull/core/vendored/requests/packages/chardet/charsetgroupprober.py +120 -0
- webull/core/vendored/requests/packages/chardet/charsetprober.py +159 -0
- webull/core/vendored/requests/packages/chardet/cli/__init__.py +1 -0
- webull/core/vendored/requests/packages/chardet/cli/chardetect.py +99 -0
- webull/core/vendored/requests/packages/chardet/codingstatemachine.py +102 -0
- webull/core/vendored/requests/packages/chardet/compat.py +48 -0
- webull/core/vendored/requests/packages/chardet/cp949prober.py +63 -0
- webull/core/vendored/requests/packages/chardet/enums.py +90 -0
- webull/core/vendored/requests/packages/chardet/escprober.py +115 -0
- webull/core/vendored/requests/packages/chardet/escsm.py +260 -0
- webull/core/vendored/requests/packages/chardet/eucjpprober.py +106 -0
- webull/core/vendored/requests/packages/chardet/euckrfreq.py +209 -0
- webull/core/vendored/requests/packages/chardet/euckrprober.py +61 -0
- webull/core/vendored/requests/packages/chardet/euctwfreq.py +401 -0
- webull/core/vendored/requests/packages/chardet/euctwprober.py +60 -0
- webull/core/vendored/requests/packages/chardet/gb2312freq.py +297 -0
- webull/core/vendored/requests/packages/chardet/gb2312prober.py +60 -0
- webull/core/vendored/requests/packages/chardet/hebrewprober.py +306 -0
- webull/core/vendored/requests/packages/chardet/jisfreq.py +339 -0
- webull/core/vendored/requests/packages/chardet/jpcntx.py +247 -0
- webull/core/vendored/requests/packages/chardet/langbulgarianmodel.py +242 -0
- webull/core/vendored/requests/packages/chardet/langcyrillicmodel.py +347 -0
- webull/core/vendored/requests/packages/chardet/langgreekmodel.py +239 -0
- webull/core/vendored/requests/packages/chardet/langhebrewmodel.py +214 -0
- webull/core/vendored/requests/packages/chardet/langhungarianmodel.py +239 -0
- webull/core/vendored/requests/packages/chardet/langthaimodel.py +213 -0
- webull/core/vendored/requests/packages/chardet/langturkishmodel.py +207 -0
- webull/core/vendored/requests/packages/chardet/latin1prober.py +159 -0
- webull/core/vendored/requests/packages/chardet/mbcharsetprober.py +105 -0
- webull/core/vendored/requests/packages/chardet/mbcsgroupprober.py +68 -0
- webull/core/vendored/requests/packages/chardet/mbcssm.py +586 -0
- webull/core/vendored/requests/packages/chardet/sbcharsetprober.py +146 -0
- webull/core/vendored/requests/packages/chardet/sbcsgroupprober.py +87 -0
- webull/core/vendored/requests/packages/chardet/sjisprober.py +106 -0
- webull/core/vendored/requests/packages/chardet/universaldetector.py +300 -0
- webull/core/vendored/requests/packages/chardet/utf8prober.py +96 -0
- webull/core/vendored/requests/packages/chardet/version.py +23 -0
- webull/core/vendored/requests/packages/urllib3/__init__.py +114 -0
- webull/core/vendored/requests/packages/urllib3/_collections.py +346 -0
- webull/core/vendored/requests/packages/urllib3/connection.py +405 -0
- webull/core/vendored/requests/packages/urllib3/connectionpool.py +910 -0
- webull/core/vendored/requests/packages/urllib3/contrib/__init__.py +0 -0
- webull/core/vendored/requests/packages/urllib3/contrib/_appengine_environ.py +44 -0
- webull/core/vendored/requests/packages/urllib3/contrib/_securetransport/__init__.py +0 -0
- webull/core/vendored/requests/packages/urllib3/contrib/_securetransport/bindings.py +607 -0
- webull/core/vendored/requests/packages/urllib3/contrib/_securetransport/low_level.py +360 -0
- webull/core/vendored/requests/packages/urllib3/contrib/appengine.py +303 -0
- webull/core/vendored/requests/packages/urllib3/contrib/ntlmpool.py +125 -0
- webull/core/vendored/requests/packages/urllib3/contrib/pyopenssl.py +484 -0
- webull/core/vendored/requests/packages/urllib3/contrib/securetransport.py +818 -0
- webull/core/vendored/requests/packages/urllib3/contrib/socks.py +206 -0
- webull/core/vendored/requests/packages/urllib3/exceptions.py +260 -0
- webull/core/vendored/requests/packages/urllib3/fields.py +192 -0
- webull/core/vendored/requests/packages/urllib3/filepost.py +112 -0
- webull/core/vendored/requests/packages/urllib3/packages/__init__.py +19 -0
- webull/core/vendored/requests/packages/urllib3/packages/backports/__init__.py +0 -0
- webull/core/vendored/requests/packages/urllib3/packages/backports/makefile.py +67 -0
- webull/core/vendored/requests/packages/urllib3/packages/ordered_dict.py +273 -0
- webull/core/vendored/requests/packages/urllib3/packages/six.py +882 -0
- webull/core/vendored/requests/packages/urllib3/packages/socks.py +887 -0
- webull/core/vendored/requests/packages/urllib3/packages/ssl_match_hostname/__init__.py +19 -0
- webull/core/vendored/requests/packages/urllib3/packages/ssl_match_hostname/_implementation.py +170 -0
- webull/core/vendored/requests/packages/urllib3/poolmanager.py +467 -0
- webull/core/vendored/requests/packages/urllib3/request.py +164 -0
- webull/core/vendored/requests/packages/urllib3/response.py +721 -0
- webull/core/vendored/requests/packages/urllib3/util/__init__.py +68 -0
- webull/core/vendored/requests/packages/urllib3/util/connection.py +148 -0
- webull/core/vendored/requests/packages/urllib3/util/queue.py +35 -0
- webull/core/vendored/requests/packages/urllib3/util/request.py +132 -0
- webull/core/vendored/requests/packages/urllib3/util/response.py +101 -0
- webull/core/vendored/requests/packages/urllib3/util/retry.py +426 -0
- webull/core/vendored/requests/packages/urllib3/util/selectors.py +601 -0
- webull/core/vendored/requests/packages/urllib3/util/ssl_.py +396 -0
- webull/core/vendored/requests/packages/urllib3/util/timeout.py +256 -0
- webull/core/vendored/requests/packages/urllib3/util/url.py +252 -0
- webull/core/vendored/requests/packages/urllib3/util/wait.py +164 -0
- webull/core/vendored/requests/packages.py +28 -0
- webull/core/vendored/requests/sessions.py +750 -0
- webull/core/vendored/requests/status_codes.py +105 -0
- webull/core/vendored/requests/structures.py +119 -0
- webull/core/vendored/requests/utils.py +916 -0
- webull/core/vendored/six.py +905 -0
- webull/data/__init__.py +3 -0
- webull/data/common/__init__.py +0 -0
- webull/data/common/category.py +26 -0
- webull/data/common/connect_ack.py +29 -0
- webull/data/common/direction.py +25 -0
- webull/data/common/exchange_code.py +33 -0
- webull/data/common/exercise_style.py +22 -0
- webull/data/common/expiration_cycle.py +26 -0
- webull/data/common/instrument_status.py +23 -0
- webull/data/common/option_type.py +20 -0
- webull/data/common/subscribe_type.py +22 -0
- webull/data/common/timespan.py +29 -0
- webull/data/data_client.py +35 -0
- webull/data/data_streaming_client.py +89 -0
- webull/data/internal/__init__.py +0 -0
- webull/data/internal/default_retry_policy.py +84 -0
- webull/data/internal/exceptions.py +60 -0
- webull/data/internal/quotes_client.py +314 -0
- webull/data/internal/quotes_decoder.py +40 -0
- webull/data/internal/quotes_payload_decoder.py +35 -0
- webull/data/internal/quotes_topic.py +36 -0
- webull/data/quotes/__init__.py +0 -0
- webull/data/quotes/instrument.py +33 -0
- webull/data/quotes/market_data.py +187 -0
- webull/data/quotes/market_streaming_data.py +66 -0
- webull/data/quotes/subscribe/__init__.py +0 -0
- webull/data/quotes/subscribe/ask_bid_result.py +49 -0
- webull/data/quotes/subscribe/basic_result.py +45 -0
- webull/data/quotes/subscribe/broker_result.py +33 -0
- webull/data/quotes/subscribe/message_pb2.py +37 -0
- webull/data/quotes/subscribe/order_result.py +30 -0
- webull/data/quotes/subscribe/payload_type.py +19 -0
- webull/data/quotes/subscribe/quote_decoder.py +28 -0
- webull/data/quotes/subscribe/quote_result.py +47 -0
- webull/data/quotes/subscribe/snapshot_decoder.py +30 -0
- webull/data/quotes/subscribe/snapshot_result.py +69 -0
- webull/data/quotes/subscribe/tick_decoder.py +29 -0
- webull/data/quotes/subscribe/tick_result.py +47 -0
- webull/data/request/__init__.py +0 -0
- webull/data/request/get_batch_historical_bars_request.py +43 -0
- webull/data/request/get_corp_action_request.py +47 -0
- webull/data/request/get_eod_bars_request.py +32 -0
- webull/data/request/get_historical_bars_request.py +43 -0
- webull/data/request/get_instruments_request.py +30 -0
- webull/data/request/get_quotes_request.py +35 -0
- webull/data/request/get_snapshot_request.py +38 -0
- webull/data/request/get_tick_request.py +37 -0
- webull/data/request/subscribe_request.py +43 -0
- webull/data/request/unsubscribe_request.py +42 -0
- webull/trade/__init__.py +2 -0
- webull/trade/common/__init__.py +0 -0
- webull/trade/common/account_type.py +22 -0
- webull/trade/common/category.py +29 -0
- webull/trade/common/combo_ticker_type.py +23 -0
- webull/trade/common/combo_type.py +31 -0
- webull/trade/common/currency.py +24 -0
- webull/trade/common/forbid_reason.py +27 -0
- webull/trade/common/instrument_type.py +27 -0
- webull/trade/common/markets.py +27 -0
- webull/trade/common/order_entrust_type.py +21 -0
- webull/trade/common/order_side.py +23 -0
- webull/trade/common/order_status.py +25 -0
- webull/trade/common/order_tif.py +24 -0
- webull/trade/common/order_type.py +30 -0
- webull/trade/common/trade_policy.py +22 -0
- webull/trade/common/trading_date_type.py +24 -0
- webull/trade/common/trailing_type.py +23 -0
- webull/trade/events/__init__.py +0 -0
- webull/trade/events/default_retry_policy.py +64 -0
- webull/trade/events/events_pb2.py +43 -0
- webull/trade/events/events_pb2_grpc.py +66 -0
- webull/trade/events/signature_composer.py +61 -0
- webull/trade/events/types.py +21 -0
- webull/trade/request/__init__.py +0 -0
- webull/trade/request/cancel_order_request.py +28 -0
- webull/trade/request/get_account_balance_request.py +28 -0
- webull/trade/request/get_account_positions_request.py +30 -0
- webull/trade/request/get_account_profile_request.py +26 -0
- webull/trade/request/get_app_subscriptions.py +28 -0
- webull/trade/request/get_open_orders_request.py +30 -0
- webull/trade/request/get_order_detail_request.py +27 -0
- webull/trade/request/get_today_orders_request.py +31 -0
- webull/trade/request/get_trade_calendar_request.py +30 -0
- webull/trade/request/get_trade_instrument_detail_request.py +24 -0
- webull/trade/request/get_trade_security_detail_request.py +42 -0
- webull/trade/request/get_tradeable_instruments_request.py +27 -0
- webull/trade/request/palce_order_request.py +91 -0
- webull/trade/request/place_order_request_v2.py +58 -0
- webull/trade/request/replace_order_request.py +73 -0
- webull/trade/request/replace_order_request_v2.py +38 -0
- webull/trade/request/v2/__init__.py +0 -0
- webull/trade/request/v2/cancel_option_request.py +28 -0
- webull/trade/request/v2/cancel_order_request.py +28 -0
- webull/trade/request/v2/get_account_balance_request.py +28 -0
- webull/trade/request/v2/get_account_list.py +23 -0
- webull/trade/request/v2/get_account_positions_request.py +24 -0
- webull/trade/request/v2/get_order_detail_request.py +26 -0
- webull/trade/request/v2/get_order_history_request.py +35 -0
- webull/trade/request/v2/palce_order_request.py +87 -0
- webull/trade/request/v2/place_option_request.py +64 -0
- webull/trade/request/v2/preview_option_request.py +28 -0
- webull/trade/request/v2/preview_order_request.py +59 -0
- webull/trade/request/v2/replace_option_request.py +28 -0
- webull/trade/request/v2/replace_order_request.py +57 -0
- webull/trade/trade/__init__.py +0 -0
- webull/trade/trade/account_info.py +83 -0
- webull/trade/trade/order_operation.py +246 -0
- webull/trade/trade/trade_calendar.py +37 -0
- webull/trade/trade/trade_instrument.py +72 -0
- webull/trade/trade/v2/__init__.py +0 -0
- webull/trade/trade/v2/account_info_v2.py +55 -0
- webull/trade/trade/v2/order_operation_v2.py +206 -0
- webull/trade/trade_client.py +43 -0
- webull/trade/trade_events_client.py +233 -0
- webull_openapi_python_sdk-1.0.0.dist-info/METADATA +28 -0
- webull_openapi_python_sdk-1.0.0.dist-info/RECORD +295 -0
- webull_openapi_python_sdk-1.0.0.dist-info/WHEEL +5 -0
- webull_openapi_python_sdk-1.0.0.dist-info/licenses/LICENSE +202 -0
- webull_openapi_python_sdk-1.0.0.dist-info/licenses/NOTICE +56 -0
- webull_openapi_python_sdk-1.0.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,206 @@
|
|
|
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
|
+
# coding=utf-8
|
|
15
|
+
from webull.core.context.request_context_holder import RequestContextHolder
|
|
16
|
+
from webull.trade.request.v2.cancel_option_request import CancelOptionRequest
|
|
17
|
+
from webull.trade.request.v2.cancel_order_request import CancelOrderRequest
|
|
18
|
+
from webull.trade.request.v2.get_order_detail_request import OrderDetailRequest
|
|
19
|
+
from webull.trade.request.v2.get_order_history_request import OrderHistoryRequest
|
|
20
|
+
from webull.trade.request.v2.palce_order_request import PlaceOrderRequest
|
|
21
|
+
from webull.trade.request.v2.place_option_request import PlaceOptionRequest
|
|
22
|
+
from webull.trade.request.v2.preview_option_request import PreviewOptionRequest
|
|
23
|
+
from webull.trade.request.v2.preview_order_request import PreviewOrderRequest
|
|
24
|
+
from webull.trade.request.v2.replace_option_request import ReplaceOptionRequest
|
|
25
|
+
from webull.trade.request.v2.replace_order_request import ReplaceOrderRequest
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class OrderOperationV2:
|
|
29
|
+
def __init__(self, api_client):
|
|
30
|
+
self.client = api_client
|
|
31
|
+
|
|
32
|
+
def preview_order(self, account_id, preview_orders):
|
|
33
|
+
"""
|
|
34
|
+
This interface is currently available only to individual brokerage customers in Webull Japan
|
|
35
|
+
and institutional brokerage clients in Webull Hong Kong. It is not yet available to
|
|
36
|
+
Webull US brokerage customers, but support will be introduced progressively in the future.
|
|
37
|
+
"""
|
|
38
|
+
preview_order_request = PreviewOrderRequest()
|
|
39
|
+
preview_order_request.set_account_id(account_id)
|
|
40
|
+
preview_order_request.set_new_orders(preview_orders)
|
|
41
|
+
preview_order_request.finalize_order()
|
|
42
|
+
response = self.client.get_response(preview_order_request)
|
|
43
|
+
return response
|
|
44
|
+
|
|
45
|
+
def place_order(self, account_id, new_orders):
|
|
46
|
+
"""
|
|
47
|
+
This interface is currently available only to individual brokerage customers in Webull Japan
|
|
48
|
+
and institutional brokerage clients in Webull Hong Kong. It is not yet available to
|
|
49
|
+
Webull US brokerage customers, but support will be introduced progressively in the future.
|
|
50
|
+
"""
|
|
51
|
+
place_order_req = PlaceOrderRequest()
|
|
52
|
+
place_order_req.set_account_id(account_id)
|
|
53
|
+
place_order_req.set_new_orders(new_orders)
|
|
54
|
+
place_order_req.finalize_order()
|
|
55
|
+
place_order_req.add_custom_headers_from_order(new_orders)
|
|
56
|
+
place_order_req.add_custom_headers_from_context()
|
|
57
|
+
response = self.client.get_response(place_order_req)
|
|
58
|
+
return response
|
|
59
|
+
|
|
60
|
+
def replace_order(self, account_id, modify_orders):
|
|
61
|
+
"""
|
|
62
|
+
This interface is currently available only to individual brokerage customers in Webull Japan
|
|
63
|
+
and institutional brokerage clients in Webull Hong Kong. It is not yet available to
|
|
64
|
+
Webull US brokerage customers, but support will be introduced progressively in the future.
|
|
65
|
+
"""
|
|
66
|
+
replace_order_request = ReplaceOrderRequest()
|
|
67
|
+
replace_order_request.set_account_id(account_id)
|
|
68
|
+
replace_order_request.set_modify_orders(modify_orders)
|
|
69
|
+
replace_order_request.finalize_order()
|
|
70
|
+
response = self.client.get_response(replace_order_request)
|
|
71
|
+
return response
|
|
72
|
+
|
|
73
|
+
def cancel_order_v2(self, account_id, client_order_id):
|
|
74
|
+
"""
|
|
75
|
+
This interface is currently available only to individual brokerage customers in Webull Japan
|
|
76
|
+
and institutional brokerage clients in Webull Hong Kong. It is not yet available to
|
|
77
|
+
Webull US brokerage customers, but support will be introduced progressively in the future.
|
|
78
|
+
"""
|
|
79
|
+
cancel_order_request = CancelOrderRequest()
|
|
80
|
+
cancel_order_request.set_account_id(account_id)
|
|
81
|
+
cancel_order_request.set_client_order_id(client_order_id)
|
|
82
|
+
response = self.client.get_response(cancel_order_request)
|
|
83
|
+
return response
|
|
84
|
+
|
|
85
|
+
def get_order_detail(self, account_id, client_order_id):
|
|
86
|
+
"""
|
|
87
|
+
This interface is exclusively available for Webull Hong Kong brokerage clients.
|
|
88
|
+
Currently, it does not support Webull Japan or Webull U.S. clients,
|
|
89
|
+
but support will be gradually introduced in the future.
|
|
90
|
+
"""
|
|
91
|
+
order_detail_request = OrderDetailRequest()
|
|
92
|
+
order_detail_request.set_account_id(account_id)
|
|
93
|
+
order_detail_request.set_client_order_id(client_order_id)
|
|
94
|
+
response = self.client.get_response(order_detail_request)
|
|
95
|
+
return response
|
|
96
|
+
|
|
97
|
+
def get_order_history_request(self, account_id, page_size=None, start_date=None, end_date=None, last_client_order_id=None):
|
|
98
|
+
"""
|
|
99
|
+
Historical orders, query the records of the past 7 days. If they are group orders, will be returned together,
|
|
100
|
+
and the number of orders returned on one page may exceed the page_size.
|
|
101
|
+
|
|
102
|
+
:param account_id: Account ID
|
|
103
|
+
:param page_size: Limit the number of records per query to 10 by default.
|
|
104
|
+
:param start_date: Start date (if empty, the default is the last 7 days), in the format of yyyy-MM-dd.
|
|
105
|
+
:param end_date: End date (if empty, the default is the last 7 days), in the format of yyyy-MM-dd.
|
|
106
|
+
:param last_client_order_id: The last order ID from the previous response. For the first page query,
|
|
107
|
+
this parameter is not required.
|
|
108
|
+
"""
|
|
109
|
+
order_history_request = OrderHistoryRequest()
|
|
110
|
+
order_history_request.set_account_id(account_id=account_id)
|
|
111
|
+
if page_size:
|
|
112
|
+
order_history_request.set_page_size(page_size=page_size)
|
|
113
|
+
if start_date:
|
|
114
|
+
order_history_request.set_start_date(start_date=start_date)
|
|
115
|
+
if end_date:
|
|
116
|
+
order_history_request.set_end_date(end_date=end_date)
|
|
117
|
+
if last_client_order_id:
|
|
118
|
+
order_history_request.set_last_client_order_id(last_client_order_id=last_client_order_id)
|
|
119
|
+
response = self.client.get_response(order_history_request)
|
|
120
|
+
return response
|
|
121
|
+
def query_order_detail(self, account_id, client_order_id):
|
|
122
|
+
"""
|
|
123
|
+
This interface is currently available only to individual and institutional clients
|
|
124
|
+
of Webull Hong Kong brokerages. It is not yet supported for clients of Webull US
|
|
125
|
+
and Webull Japan brokerages, but support will be gradually introduced in the future.
|
|
126
|
+
Paging query pending orders.
|
|
127
|
+
|
|
128
|
+
:param account_id: Account ID
|
|
129
|
+
:param client_order_id: The 3rd party order ID.
|
|
130
|
+
"""
|
|
131
|
+
order_detail_request = OrderDetailRequest()
|
|
132
|
+
order_detail_request.set_account_id(account_id)
|
|
133
|
+
order_detail_request.set_client_order_id(client_order_id)
|
|
134
|
+
response = self.client.get_response(order_detail_request)
|
|
135
|
+
return response
|
|
136
|
+
|
|
137
|
+
def preview_option(self, account_id, new_orders):
|
|
138
|
+
"""
|
|
139
|
+
This interface is currently available only to individual and institutional clients
|
|
140
|
+
of Webull Hong Kong brokerages. It is not yet supported for clients of Webull US
|
|
141
|
+
and Webull Japan brokerages, but support will be gradually introduced in the future.
|
|
142
|
+
"""
|
|
143
|
+
preview_option_request = PreviewOptionRequest()
|
|
144
|
+
preview_option_request.set_new_orders(new_orders)
|
|
145
|
+
preview_option_request.set_account_id(account_id)
|
|
146
|
+
response = self.client.get_response(preview_option_request)
|
|
147
|
+
return response
|
|
148
|
+
|
|
149
|
+
def place_option(self, account_id, new_orders):
|
|
150
|
+
"""
|
|
151
|
+
This interface is currently available only to individual and institutional clients
|
|
152
|
+
of Webull Hong Kong brokerages. It is not yet supported for clients of Webull US
|
|
153
|
+
and Webull Japan brokerages, but support will be gradually introduced in the future.
|
|
154
|
+
"""
|
|
155
|
+
place_option_request = PlaceOptionRequest()
|
|
156
|
+
place_option_request.set_new_orders(new_orders)
|
|
157
|
+
place_option_request.set_account_id(account_id)
|
|
158
|
+
place_option_request.add_custom_headers_from_order(new_orders)
|
|
159
|
+
place_option_request.add_custom_headers_from_context()
|
|
160
|
+
response = self.client.get_response(place_option_request)
|
|
161
|
+
return response
|
|
162
|
+
|
|
163
|
+
def replace_option(self, account_id, modify_orders):
|
|
164
|
+
"""
|
|
165
|
+
This interface is currently available only to individual and institutional clients
|
|
166
|
+
of Webull Hong Kong brokerages. It is not yet supported for clients of Webull US
|
|
167
|
+
and Webull Japan brokerages, but support will be gradually introduced in the future.
|
|
168
|
+
"""
|
|
169
|
+
replace_option_request = ReplaceOptionRequest()
|
|
170
|
+
replace_option_request.set_modify_orders(modify_orders)
|
|
171
|
+
replace_option_request.set_account_id(account_id)
|
|
172
|
+
response = self.client.get_response(replace_option_request)
|
|
173
|
+
return response
|
|
174
|
+
|
|
175
|
+
def cancel_option(self, account_id, client_order_id):
|
|
176
|
+
"""
|
|
177
|
+
This interface is currently available only to individual and institutional clients
|
|
178
|
+
of Webull Hong Kong brokerages. It is not yet supported for clients of Webull US
|
|
179
|
+
and Webull Japan brokerages, but support will be gradually introduced in the future.
|
|
180
|
+
"""
|
|
181
|
+
cancel_option_request = CancelOptionRequest()
|
|
182
|
+
cancel_option_request.set_client_order_id(client_order_id)
|
|
183
|
+
cancel_option_request.set_account_id(account_id)
|
|
184
|
+
response = self.client.get_response(cancel_option_request)
|
|
185
|
+
return response
|
|
186
|
+
|
|
187
|
+
def add_custom_headers(self, headers_map: dict):
|
|
188
|
+
"""
|
|
189
|
+
This is an optional feature; you can still make a request without setting it.
|
|
190
|
+
If set, you can specify certain headers to perform specific operations.
|
|
191
|
+
Note: If you set a header, call remove_custom_headers to clean up the header after the request is completed.
|
|
192
|
+
|
|
193
|
+
Currently supported header keys and functions:
|
|
194
|
+
Key:category {See Also: category}
|
|
195
|
+
Function: Frequency limit rules, please refer to the document for details. currently only supports Hong Kong
|
|
196
|
+
"""
|
|
197
|
+
if not headers_map or len(headers_map) == 0:
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
RequestContextHolder.get().update(headers_map)
|
|
201
|
+
|
|
202
|
+
def remove_custom_headers(self):
|
|
203
|
+
"""
|
|
204
|
+
Clearing headers after the request is completed.
|
|
205
|
+
"""
|
|
206
|
+
RequestContextHolder.clear()
|
|
@@ -0,0 +1,43 @@
|
|
|
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
|
+
import logging
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
from webull.core.http.initializer.client_initializer import ClientInitializer
|
|
18
|
+
from webull.trade.trade.account_info import Account
|
|
19
|
+
from webull.trade.trade.order_operation import OrderOperation
|
|
20
|
+
from webull.trade.trade.trade_calendar import TradeCalendar
|
|
21
|
+
from webull.trade.trade.trade_instrument import TradeInstrument
|
|
22
|
+
from webull.trade.trade.v2.account_info_v2 import AccountV2
|
|
23
|
+
from webull.trade.trade.v2.order_operation_v2 import OrderOperationV2
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class TradeClient:
|
|
27
|
+
def __init__(self, api_client):
|
|
28
|
+
self._init_logger(api_client)
|
|
29
|
+
ClientInitializer.initializer(api_client)
|
|
30
|
+
self.account = Account(api_client)
|
|
31
|
+
self.account_v2 = AccountV2(api_client)
|
|
32
|
+
self.order = OrderOperation(api_client)
|
|
33
|
+
self.order_v2 = OrderOperationV2(api_client)
|
|
34
|
+
self.trade_instrument = TradeInstrument(api_client)
|
|
35
|
+
self.trade_calendar = TradeCalendar(api_client)
|
|
36
|
+
|
|
37
|
+
def _init_logger(self, api_client):
|
|
38
|
+
# No logger configured, using default console and local file logging.
|
|
39
|
+
if not getattr(api_client, '_stream_logger_set', False) and not getattr(api_client, '_file_logger_set', False):
|
|
40
|
+
log_format = '%(thread)d %(asctime)s %(name)s %(levelname)s %(message)s'
|
|
41
|
+
log_file_path = 'webull_trade_sdk.log'
|
|
42
|
+
api_client.set_stream_logger(stream=sys.stdout, format_string=log_format)
|
|
43
|
+
api_client.set_file_logger(path=log_file_path, log_level=logging.INFO, format_string=log_format)
|
|
@@ -0,0 +1,233 @@
|
|
|
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
|
+
import json
|
|
17
|
+
import logging
|
|
18
|
+
import threading
|
|
19
|
+
import time
|
|
20
|
+
|
|
21
|
+
import grpc
|
|
22
|
+
from webull.core.common import api_type
|
|
23
|
+
from webull.core.endpoint.default_endpoint_resolver import \
|
|
24
|
+
DefaultEndpointResolver
|
|
25
|
+
from webull.core.endpoint.resolver_endpoint_request import \
|
|
26
|
+
ResolveEndpointRequest
|
|
27
|
+
from webull.core.retry.retry_condition import RetryCondition
|
|
28
|
+
|
|
29
|
+
import webull.trade.events.events_pb2 as pb
|
|
30
|
+
import webull.trade.events.events_pb2_grpc as pb_grpc
|
|
31
|
+
from webull.trade.events.default_retry_policy import (
|
|
32
|
+
DefaultSubscribeRetryPolicy, SubscribeRetryPolicyContext)
|
|
33
|
+
from webull.trade.events.signature_composer import calc_signature
|
|
34
|
+
|
|
35
|
+
# contentTypes
|
|
36
|
+
JSON = "application/json"
|
|
37
|
+
TEXT = "text/plain"
|
|
38
|
+
|
|
39
|
+
DEFAULT_REGION_ID = "us"
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TradeEventsClient():
|
|
43
|
+
def __init__(self, app_key, app_secret, region_id=DEFAULT_REGION_ID, host=None, port=443, tls_enable=True, retry_policy=None):
|
|
44
|
+
self._app_key = app_key
|
|
45
|
+
self._app_secret = app_secret
|
|
46
|
+
self._region_id = region_id
|
|
47
|
+
self._tls_enable = tls_enable
|
|
48
|
+
self._endpoint_resolver = DefaultEndpointResolver(self)
|
|
49
|
+
if not host:
|
|
50
|
+
endpoint_request = ResolveEndpointRequest(
|
|
51
|
+
self._region_id, api_type=api_type.EVENTS)
|
|
52
|
+
endpoint = self._endpoint_resolver.resolve(endpoint_request)
|
|
53
|
+
self._host = endpoint
|
|
54
|
+
else:
|
|
55
|
+
self._host = host
|
|
56
|
+
self._port = port
|
|
57
|
+
if retry_policy:
|
|
58
|
+
self._retry_policy = retry_policy
|
|
59
|
+
else:
|
|
60
|
+
self._retry_policy = DefaultSubscribeRetryPolicy()
|
|
61
|
+
|
|
62
|
+
self._logger = None
|
|
63
|
+
self._in_callback_mutex = threading.Lock()
|
|
64
|
+
self._callback_mutex = threading.RLock()
|
|
65
|
+
# callbacks
|
|
66
|
+
self._on_connect = None
|
|
67
|
+
self._on_events_message = None
|
|
68
|
+
self._on_log = None
|
|
69
|
+
|
|
70
|
+
def _build_request(self, app_key, app_secret, accounts):
|
|
71
|
+
request = pb.SubscribeRequest(
|
|
72
|
+
subscribeType=1, # only 1 allowed now
|
|
73
|
+
timestamp=int(time.time() * 1000), # millis
|
|
74
|
+
accounts=accounts,
|
|
75
|
+
)
|
|
76
|
+
signature, metadata = calc_signature(app_key, app_secret, request)
|
|
77
|
+
return request, metadata
|
|
78
|
+
|
|
79
|
+
def _stream_processing(self, stub, accounts):
|
|
80
|
+
retry_policy_context = SubscribeRetryPolicyContext(None, 0, None)
|
|
81
|
+
retries = 0
|
|
82
|
+
final_exception = None
|
|
83
|
+
while True:
|
|
84
|
+
request, metadata = self._build_request(
|
|
85
|
+
self._app_key, self._app_secret, accounts)
|
|
86
|
+
try:
|
|
87
|
+
response_iterator = stub.Subscribe(
|
|
88
|
+
request=request, metadata=metadata)
|
|
89
|
+
for response in response_iterator:
|
|
90
|
+
self._easy_handler(response)
|
|
91
|
+
except grpc.RpcError as rpc_error:
|
|
92
|
+
state = rpc_error._state
|
|
93
|
+
final_exception = rpc_error
|
|
94
|
+
retry_policy_context = SubscribeRetryPolicyContext(
|
|
95
|
+
None, retries, state.code)
|
|
96
|
+
self._easy_log(logging.ERROR, "grpc error code:%s, error msg:%s, details:%s",
|
|
97
|
+
state.code, state.details, state.debug_error_string)
|
|
98
|
+
except Exception as exception:
|
|
99
|
+
final_exception = exception
|
|
100
|
+
retry_policy_context = SubscribeRetryPolicyContext(
|
|
101
|
+
exception, retries, None)
|
|
102
|
+
self._easy_log(logging.ERROR, "grpc exception:%s", exception)
|
|
103
|
+
retryable = self._retry_policy.should_retry(retry_policy_context)
|
|
104
|
+
if retryable & RetryCondition.NO_RETRY:
|
|
105
|
+
self._easy_log(
|
|
106
|
+
logging.ERROR, "processing will stopped due to not be retryable, retry_context:%s", retry_policy_context)
|
|
107
|
+
break
|
|
108
|
+
retry_policy_context.retryable = retryable
|
|
109
|
+
time_to_sleep = self._retry_policy.compute_delay_before_next_retry(
|
|
110
|
+
retry_policy_context)
|
|
111
|
+
self._easy_log(logging.INFO, "next retry will be started in %s ms, retry_context:%s",
|
|
112
|
+
time_to_sleep, retry_policy_context)
|
|
113
|
+
time.sleep(time_to_sleep / 1000.0)
|
|
114
|
+
retries += 1
|
|
115
|
+
retry_policy_context.retries_attempted = retries
|
|
116
|
+
if final_exception:
|
|
117
|
+
raise final_exception
|
|
118
|
+
|
|
119
|
+
@property
|
|
120
|
+
def on_connect(self):
|
|
121
|
+
return self._on_connect
|
|
122
|
+
|
|
123
|
+
@on_connect.setter
|
|
124
|
+
def on_connect(self, func):
|
|
125
|
+
with self._callback_mutex:
|
|
126
|
+
self._on_connect = func
|
|
127
|
+
|
|
128
|
+
@property
|
|
129
|
+
def on_events_message(self):
|
|
130
|
+
return self._on_events_message
|
|
131
|
+
|
|
132
|
+
@on_events_message.setter
|
|
133
|
+
def on_events_message(self, func):
|
|
134
|
+
with self._callback_mutex:
|
|
135
|
+
self._on_events_message = func
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def on_log(self):
|
|
139
|
+
return self._on_log
|
|
140
|
+
|
|
141
|
+
@on_log.setter
|
|
142
|
+
def on_log(self, func):
|
|
143
|
+
self._on_log = func
|
|
144
|
+
|
|
145
|
+
def _easy_log(self, level, fmt, *args):
|
|
146
|
+
if self.on_log is not None:
|
|
147
|
+
buf = fmt % args
|
|
148
|
+
try:
|
|
149
|
+
self.on_log(level, buf)
|
|
150
|
+
except Exception:
|
|
151
|
+
pass
|
|
152
|
+
if self._logger is not None:
|
|
153
|
+
self._logger.log(level, fmt, *args)
|
|
154
|
+
|
|
155
|
+
def enable_logger(self, logger=None):
|
|
156
|
+
if logger is None:
|
|
157
|
+
if self._logger is not None:
|
|
158
|
+
return
|
|
159
|
+
logger = logging.getLogger(__name__)
|
|
160
|
+
self._logger = logger
|
|
161
|
+
|
|
162
|
+
def disable_logger(self):
|
|
163
|
+
self._logger = None
|
|
164
|
+
|
|
165
|
+
def _handle_subscribe_success(self, response):
|
|
166
|
+
self._easy_log(
|
|
167
|
+
logging.INFO, "subscribe success, response:%s", response)
|
|
168
|
+
with self._callback_mutex:
|
|
169
|
+
on_connect = self.on_connect
|
|
170
|
+
if not on_connect:
|
|
171
|
+
return
|
|
172
|
+
with self._in_callback_mutex:
|
|
173
|
+
try:
|
|
174
|
+
on_connect(self, response.payload, response)
|
|
175
|
+
except Exception as err:
|
|
176
|
+
self._easy_log(
|
|
177
|
+
logging.ERROR, 'Caught exception in on_connect: %s', err)
|
|
178
|
+
raise err
|
|
179
|
+
|
|
180
|
+
def _handle_default(self, response, level):
|
|
181
|
+
self._easy_log(level, "response:%s", response)
|
|
182
|
+
|
|
183
|
+
def _handle_message(self, response):
|
|
184
|
+
self._easy_log(
|
|
185
|
+
logging.DEBUG, "message received, response:%s", response)
|
|
186
|
+
with self._callback_mutex:
|
|
187
|
+
on_events_message = self.on_events_message
|
|
188
|
+
if not on_events_message:
|
|
189
|
+
return
|
|
190
|
+
with self._in_callback_mutex:
|
|
191
|
+
content_type = response.contentType
|
|
192
|
+
_payload = response.payload
|
|
193
|
+
if JSON == content_type:
|
|
194
|
+
try:
|
|
195
|
+
_payload = json.loads(response.payload)
|
|
196
|
+
except Exception as err:
|
|
197
|
+
self._easy_log(
|
|
198
|
+
logging.ERROR, 'Caught exception in decode message: %s, %s', _payload, err)
|
|
199
|
+
raise err
|
|
200
|
+
try:
|
|
201
|
+
on_events_message(response.eventType,
|
|
202
|
+
response.subscribeType, _payload, response)
|
|
203
|
+
except Exception as err:
|
|
204
|
+
self._easy_log(
|
|
205
|
+
logging.ERROR, 'Caught exception in on_events_message: %s', err)
|
|
206
|
+
raise err
|
|
207
|
+
|
|
208
|
+
def _easy_handler(self, response):
|
|
209
|
+
event_type = response.eventType
|
|
210
|
+
if event_type == pb.SubscribeSuccess:
|
|
211
|
+
self._handle_subscribe_success(response)
|
|
212
|
+
elif event_type == pb.Ping:
|
|
213
|
+
self._handle_default(response, logging.DEBUG)
|
|
214
|
+
elif event_type == pb.AuthError:
|
|
215
|
+
self._handle_default(response, logging.FATAL)
|
|
216
|
+
elif event_type == pb.NumOfConnExceed:
|
|
217
|
+
self._handle_default(response, logging.FATAL)
|
|
218
|
+
elif event_type == pb.SubscribeExpired:
|
|
219
|
+
self._handle_default(response, logging.FATAL)
|
|
220
|
+
else:
|
|
221
|
+
self._handle_message(response)
|
|
222
|
+
|
|
223
|
+
def do_subscribe(self, accounts):
|
|
224
|
+
target = self._host + ":" + str(self._port)
|
|
225
|
+
if self._tls_enable:
|
|
226
|
+
ssl_channel_credentials = grpc.ssl_channel_credentials()
|
|
227
|
+
with grpc.secure_channel(target, ssl_channel_credentials) as channel:
|
|
228
|
+
stub = pb_grpc.EventServiceStub(channel)
|
|
229
|
+
self._stream_processing(stub, accounts)
|
|
230
|
+
else:
|
|
231
|
+
with grpc.insecure_channel(target) as channel:
|
|
232
|
+
stub = pb_grpc.EventServiceStub(channel)
|
|
233
|
+
self._stream_processing(stub, accounts)
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: webull-openapi-python-sdk
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Webull Python SDK.
|
|
5
|
+
Home-page:
|
|
6
|
+
Author: Webull
|
|
7
|
+
Author-email:
|
|
8
|
+
License: Apache License 2.0
|
|
9
|
+
Platform: any
|
|
10
|
+
Requires-Python: >=3.8,<3.12
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
License-File: NOTICE
|
|
14
|
+
Requires-Dist: jmespath<1.0.0,>=0.9.3
|
|
15
|
+
Requires-Dist: cryptography>=2.6.0
|
|
16
|
+
Requires-Dist: cachetools==5.2.0
|
|
17
|
+
Requires-Dist: paho-mqtt==1.6.1
|
|
18
|
+
Requires-Dist: protobuf==4.21.12
|
|
19
|
+
Requires-Dist: grpcio==1.51.1
|
|
20
|
+
Requires-Dist: grpcio-tools==1.51.1
|
|
21
|
+
Dynamic: author
|
|
22
|
+
Dynamic: description-content-type
|
|
23
|
+
Dynamic: license
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
Dynamic: platform
|
|
26
|
+
Dynamic: requires-dist
|
|
27
|
+
Dynamic: requires-python
|
|
28
|
+
Dynamic: summary
|