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.
Files changed (295) hide show
  1. samples/__init__.py +1 -0
  2. samples/data/__init__.py +1 -0
  3. samples/data/data_client.py +57 -0
  4. samples/data/data_streaming_client.py +86 -0
  5. samples/data/data_streaming_client_async.py +101 -0
  6. samples/trade/__init__.py +0 -0
  7. samples/trade/trade_client.py +163 -0
  8. samples/trade/trade_client_v2.py +181 -0
  9. samples/trade/trade_event_client.py +47 -0
  10. webull/__init__.py +1 -0
  11. webull/core/__init__.py +12 -0
  12. webull/core/auth/__init__.py +0 -0
  13. webull/core/auth/algorithm/__init__.py +0 -0
  14. webull/core/auth/algorithm/sha_hmac1.py +65 -0
  15. webull/core/auth/algorithm/sha_hmac256.py +75 -0
  16. webull/core/auth/composer/__init__.py +0 -0
  17. webull/core/auth/composer/default_signature_composer.py +125 -0
  18. webull/core/auth/credentials.py +46 -0
  19. webull/core/auth/signers/__init__.py +0 -0
  20. webull/core/auth/signers/app_key_signer.py +72 -0
  21. webull/core/auth/signers/signer.py +48 -0
  22. webull/core/auth/signers/signer_factory.py +58 -0
  23. webull/core/cache/__init__.py +225 -0
  24. webull/core/client.py +410 -0
  25. webull/core/common/__init__.py +0 -0
  26. webull/core/common/api_type.py +19 -0
  27. webull/core/common/easy_enum.py +35 -0
  28. webull/core/common/region.py +7 -0
  29. webull/core/compat.py +85 -0
  30. webull/core/context/__init__.py +0 -0
  31. webull/core/context/request_context_holder.py +33 -0
  32. webull/core/data/endpoints.json +22 -0
  33. webull/core/data/retry_config.json +15 -0
  34. webull/core/endpoint/__init__.py +8 -0
  35. webull/core/endpoint/chained_endpoint_resolver.py +57 -0
  36. webull/core/endpoint/default_endpoint_resolver.py +60 -0
  37. webull/core/endpoint/local_config_regional_endpoint_resolver.py +77 -0
  38. webull/core/endpoint/resolver_endpoint_request.py +46 -0
  39. webull/core/endpoint/user_customized_endpoint_resolver.py +55 -0
  40. webull/core/exception/__init__.py +0 -0
  41. webull/core/exception/error_code.py +23 -0
  42. webull/core/exception/error_msg.py +21 -0
  43. webull/core/exception/exceptions.py +53 -0
  44. webull/core/headers.py +57 -0
  45. webull/core/http/__init__.py +0 -0
  46. webull/core/http/initializer/__init__.py +0 -0
  47. webull/core/http/initializer/client_initializer.py +79 -0
  48. webull/core/http/initializer/token/__init__.py +0 -0
  49. webull/core/http/initializer/token/bean/__init__.py +0 -0
  50. webull/core/http/initializer/token/bean/access_token.py +40 -0
  51. webull/core/http/initializer/token/bean/check_token_request.py +44 -0
  52. webull/core/http/initializer/token/bean/create_token_request.py +45 -0
  53. webull/core/http/initializer/token/bean/refresh_token_request.py +44 -0
  54. webull/core/http/initializer/token/token_manager.py +208 -0
  55. webull/core/http/initializer/token/token_operation.py +72 -0
  56. webull/core/http/method_type.py +43 -0
  57. webull/core/http/protocol_type.py +43 -0
  58. webull/core/http/request.py +121 -0
  59. webull/core/http/response.py +166 -0
  60. webull/core/request.py +278 -0
  61. webull/core/retry/__init__.py +0 -0
  62. webull/core/retry/backoff_strategy.py +102 -0
  63. webull/core/retry/retry_condition.py +214 -0
  64. webull/core/retry/retry_policy.py +63 -0
  65. webull/core/retry/retry_policy_context.py +51 -0
  66. webull/core/utils/__init__.py +0 -0
  67. webull/core/utils/common.py +62 -0
  68. webull/core/utils/data.py +25 -0
  69. webull/core/utils/desensitize.py +33 -0
  70. webull/core/utils/validation.py +49 -0
  71. webull/core/vendored/__init__.py +0 -0
  72. webull/core/vendored/requests/__init__.py +94 -0
  73. webull/core/vendored/requests/__version__.py +28 -0
  74. webull/core/vendored/requests/_internal_utils.py +56 -0
  75. webull/core/vendored/requests/adapters.py +539 -0
  76. webull/core/vendored/requests/api.py +166 -0
  77. webull/core/vendored/requests/auth.py +307 -0
  78. webull/core/vendored/requests/certs.py +34 -0
  79. webull/core/vendored/requests/compat.py +85 -0
  80. webull/core/vendored/requests/cookies.py +555 -0
  81. webull/core/vendored/requests/exceptions.py +136 -0
  82. webull/core/vendored/requests/help.py +134 -0
  83. webull/core/vendored/requests/hooks.py +48 -0
  84. webull/core/vendored/requests/models.py +960 -0
  85. webull/core/vendored/requests/packages/__init__.py +17 -0
  86. webull/core/vendored/requests/packages/certifi/__init__.py +17 -0
  87. webull/core/vendored/requests/packages/certifi/__main__.py +16 -0
  88. webull/core/vendored/requests/packages/certifi/cacert.pem +4433 -0
  89. webull/core/vendored/requests/packages/certifi/core.py +51 -0
  90. webull/core/vendored/requests/packages/chardet/__init__.py +53 -0
  91. webull/core/vendored/requests/packages/chardet/big5freq.py +400 -0
  92. webull/core/vendored/requests/packages/chardet/big5prober.py +61 -0
  93. webull/core/vendored/requests/packages/chardet/chardistribution.py +247 -0
  94. webull/core/vendored/requests/packages/chardet/charsetgroupprober.py +120 -0
  95. webull/core/vendored/requests/packages/chardet/charsetprober.py +159 -0
  96. webull/core/vendored/requests/packages/chardet/cli/__init__.py +1 -0
  97. webull/core/vendored/requests/packages/chardet/cli/chardetect.py +99 -0
  98. webull/core/vendored/requests/packages/chardet/codingstatemachine.py +102 -0
  99. webull/core/vendored/requests/packages/chardet/compat.py +48 -0
  100. webull/core/vendored/requests/packages/chardet/cp949prober.py +63 -0
  101. webull/core/vendored/requests/packages/chardet/enums.py +90 -0
  102. webull/core/vendored/requests/packages/chardet/escprober.py +115 -0
  103. webull/core/vendored/requests/packages/chardet/escsm.py +260 -0
  104. webull/core/vendored/requests/packages/chardet/eucjpprober.py +106 -0
  105. webull/core/vendored/requests/packages/chardet/euckrfreq.py +209 -0
  106. webull/core/vendored/requests/packages/chardet/euckrprober.py +61 -0
  107. webull/core/vendored/requests/packages/chardet/euctwfreq.py +401 -0
  108. webull/core/vendored/requests/packages/chardet/euctwprober.py +60 -0
  109. webull/core/vendored/requests/packages/chardet/gb2312freq.py +297 -0
  110. webull/core/vendored/requests/packages/chardet/gb2312prober.py +60 -0
  111. webull/core/vendored/requests/packages/chardet/hebrewprober.py +306 -0
  112. webull/core/vendored/requests/packages/chardet/jisfreq.py +339 -0
  113. webull/core/vendored/requests/packages/chardet/jpcntx.py +247 -0
  114. webull/core/vendored/requests/packages/chardet/langbulgarianmodel.py +242 -0
  115. webull/core/vendored/requests/packages/chardet/langcyrillicmodel.py +347 -0
  116. webull/core/vendored/requests/packages/chardet/langgreekmodel.py +239 -0
  117. webull/core/vendored/requests/packages/chardet/langhebrewmodel.py +214 -0
  118. webull/core/vendored/requests/packages/chardet/langhungarianmodel.py +239 -0
  119. webull/core/vendored/requests/packages/chardet/langthaimodel.py +213 -0
  120. webull/core/vendored/requests/packages/chardet/langturkishmodel.py +207 -0
  121. webull/core/vendored/requests/packages/chardet/latin1prober.py +159 -0
  122. webull/core/vendored/requests/packages/chardet/mbcharsetprober.py +105 -0
  123. webull/core/vendored/requests/packages/chardet/mbcsgroupprober.py +68 -0
  124. webull/core/vendored/requests/packages/chardet/mbcssm.py +586 -0
  125. webull/core/vendored/requests/packages/chardet/sbcharsetprober.py +146 -0
  126. webull/core/vendored/requests/packages/chardet/sbcsgroupprober.py +87 -0
  127. webull/core/vendored/requests/packages/chardet/sjisprober.py +106 -0
  128. webull/core/vendored/requests/packages/chardet/universaldetector.py +300 -0
  129. webull/core/vendored/requests/packages/chardet/utf8prober.py +96 -0
  130. webull/core/vendored/requests/packages/chardet/version.py +23 -0
  131. webull/core/vendored/requests/packages/urllib3/__init__.py +114 -0
  132. webull/core/vendored/requests/packages/urllib3/_collections.py +346 -0
  133. webull/core/vendored/requests/packages/urllib3/connection.py +405 -0
  134. webull/core/vendored/requests/packages/urllib3/connectionpool.py +910 -0
  135. webull/core/vendored/requests/packages/urllib3/contrib/__init__.py +0 -0
  136. webull/core/vendored/requests/packages/urllib3/contrib/_appengine_environ.py +44 -0
  137. webull/core/vendored/requests/packages/urllib3/contrib/_securetransport/__init__.py +0 -0
  138. webull/core/vendored/requests/packages/urllib3/contrib/_securetransport/bindings.py +607 -0
  139. webull/core/vendored/requests/packages/urllib3/contrib/_securetransport/low_level.py +360 -0
  140. webull/core/vendored/requests/packages/urllib3/contrib/appengine.py +303 -0
  141. webull/core/vendored/requests/packages/urllib3/contrib/ntlmpool.py +125 -0
  142. webull/core/vendored/requests/packages/urllib3/contrib/pyopenssl.py +484 -0
  143. webull/core/vendored/requests/packages/urllib3/contrib/securetransport.py +818 -0
  144. webull/core/vendored/requests/packages/urllib3/contrib/socks.py +206 -0
  145. webull/core/vendored/requests/packages/urllib3/exceptions.py +260 -0
  146. webull/core/vendored/requests/packages/urllib3/fields.py +192 -0
  147. webull/core/vendored/requests/packages/urllib3/filepost.py +112 -0
  148. webull/core/vendored/requests/packages/urllib3/packages/__init__.py +19 -0
  149. webull/core/vendored/requests/packages/urllib3/packages/backports/__init__.py +0 -0
  150. webull/core/vendored/requests/packages/urllib3/packages/backports/makefile.py +67 -0
  151. webull/core/vendored/requests/packages/urllib3/packages/ordered_dict.py +273 -0
  152. webull/core/vendored/requests/packages/urllib3/packages/six.py +882 -0
  153. webull/core/vendored/requests/packages/urllib3/packages/socks.py +887 -0
  154. webull/core/vendored/requests/packages/urllib3/packages/ssl_match_hostname/__init__.py +19 -0
  155. webull/core/vendored/requests/packages/urllib3/packages/ssl_match_hostname/_implementation.py +170 -0
  156. webull/core/vendored/requests/packages/urllib3/poolmanager.py +467 -0
  157. webull/core/vendored/requests/packages/urllib3/request.py +164 -0
  158. webull/core/vendored/requests/packages/urllib3/response.py +721 -0
  159. webull/core/vendored/requests/packages/urllib3/util/__init__.py +68 -0
  160. webull/core/vendored/requests/packages/urllib3/util/connection.py +148 -0
  161. webull/core/vendored/requests/packages/urllib3/util/queue.py +35 -0
  162. webull/core/vendored/requests/packages/urllib3/util/request.py +132 -0
  163. webull/core/vendored/requests/packages/urllib3/util/response.py +101 -0
  164. webull/core/vendored/requests/packages/urllib3/util/retry.py +426 -0
  165. webull/core/vendored/requests/packages/urllib3/util/selectors.py +601 -0
  166. webull/core/vendored/requests/packages/urllib3/util/ssl_.py +396 -0
  167. webull/core/vendored/requests/packages/urllib3/util/timeout.py +256 -0
  168. webull/core/vendored/requests/packages/urllib3/util/url.py +252 -0
  169. webull/core/vendored/requests/packages/urllib3/util/wait.py +164 -0
  170. webull/core/vendored/requests/packages.py +28 -0
  171. webull/core/vendored/requests/sessions.py +750 -0
  172. webull/core/vendored/requests/status_codes.py +105 -0
  173. webull/core/vendored/requests/structures.py +119 -0
  174. webull/core/vendored/requests/utils.py +916 -0
  175. webull/core/vendored/six.py +905 -0
  176. webull/data/__init__.py +3 -0
  177. webull/data/common/__init__.py +0 -0
  178. webull/data/common/category.py +26 -0
  179. webull/data/common/connect_ack.py +29 -0
  180. webull/data/common/direction.py +25 -0
  181. webull/data/common/exchange_code.py +33 -0
  182. webull/data/common/exercise_style.py +22 -0
  183. webull/data/common/expiration_cycle.py +26 -0
  184. webull/data/common/instrument_status.py +23 -0
  185. webull/data/common/option_type.py +20 -0
  186. webull/data/common/subscribe_type.py +22 -0
  187. webull/data/common/timespan.py +29 -0
  188. webull/data/data_client.py +35 -0
  189. webull/data/data_streaming_client.py +89 -0
  190. webull/data/internal/__init__.py +0 -0
  191. webull/data/internal/default_retry_policy.py +84 -0
  192. webull/data/internal/exceptions.py +60 -0
  193. webull/data/internal/quotes_client.py +314 -0
  194. webull/data/internal/quotes_decoder.py +40 -0
  195. webull/data/internal/quotes_payload_decoder.py +35 -0
  196. webull/data/internal/quotes_topic.py +36 -0
  197. webull/data/quotes/__init__.py +0 -0
  198. webull/data/quotes/instrument.py +33 -0
  199. webull/data/quotes/market_data.py +187 -0
  200. webull/data/quotes/market_streaming_data.py +66 -0
  201. webull/data/quotes/subscribe/__init__.py +0 -0
  202. webull/data/quotes/subscribe/ask_bid_result.py +49 -0
  203. webull/data/quotes/subscribe/basic_result.py +45 -0
  204. webull/data/quotes/subscribe/broker_result.py +33 -0
  205. webull/data/quotes/subscribe/message_pb2.py +37 -0
  206. webull/data/quotes/subscribe/order_result.py +30 -0
  207. webull/data/quotes/subscribe/payload_type.py +19 -0
  208. webull/data/quotes/subscribe/quote_decoder.py +28 -0
  209. webull/data/quotes/subscribe/quote_result.py +47 -0
  210. webull/data/quotes/subscribe/snapshot_decoder.py +30 -0
  211. webull/data/quotes/subscribe/snapshot_result.py +69 -0
  212. webull/data/quotes/subscribe/tick_decoder.py +29 -0
  213. webull/data/quotes/subscribe/tick_result.py +47 -0
  214. webull/data/request/__init__.py +0 -0
  215. webull/data/request/get_batch_historical_bars_request.py +43 -0
  216. webull/data/request/get_corp_action_request.py +47 -0
  217. webull/data/request/get_eod_bars_request.py +32 -0
  218. webull/data/request/get_historical_bars_request.py +43 -0
  219. webull/data/request/get_instruments_request.py +30 -0
  220. webull/data/request/get_quotes_request.py +35 -0
  221. webull/data/request/get_snapshot_request.py +38 -0
  222. webull/data/request/get_tick_request.py +37 -0
  223. webull/data/request/subscribe_request.py +43 -0
  224. webull/data/request/unsubscribe_request.py +42 -0
  225. webull/trade/__init__.py +2 -0
  226. webull/trade/common/__init__.py +0 -0
  227. webull/trade/common/account_type.py +22 -0
  228. webull/trade/common/category.py +29 -0
  229. webull/trade/common/combo_ticker_type.py +23 -0
  230. webull/trade/common/combo_type.py +31 -0
  231. webull/trade/common/currency.py +24 -0
  232. webull/trade/common/forbid_reason.py +27 -0
  233. webull/trade/common/instrument_type.py +27 -0
  234. webull/trade/common/markets.py +27 -0
  235. webull/trade/common/order_entrust_type.py +21 -0
  236. webull/trade/common/order_side.py +23 -0
  237. webull/trade/common/order_status.py +25 -0
  238. webull/trade/common/order_tif.py +24 -0
  239. webull/trade/common/order_type.py +30 -0
  240. webull/trade/common/trade_policy.py +22 -0
  241. webull/trade/common/trading_date_type.py +24 -0
  242. webull/trade/common/trailing_type.py +23 -0
  243. webull/trade/events/__init__.py +0 -0
  244. webull/trade/events/default_retry_policy.py +64 -0
  245. webull/trade/events/events_pb2.py +43 -0
  246. webull/trade/events/events_pb2_grpc.py +66 -0
  247. webull/trade/events/signature_composer.py +61 -0
  248. webull/trade/events/types.py +21 -0
  249. webull/trade/request/__init__.py +0 -0
  250. webull/trade/request/cancel_order_request.py +28 -0
  251. webull/trade/request/get_account_balance_request.py +28 -0
  252. webull/trade/request/get_account_positions_request.py +30 -0
  253. webull/trade/request/get_account_profile_request.py +26 -0
  254. webull/trade/request/get_app_subscriptions.py +28 -0
  255. webull/trade/request/get_open_orders_request.py +30 -0
  256. webull/trade/request/get_order_detail_request.py +27 -0
  257. webull/trade/request/get_today_orders_request.py +31 -0
  258. webull/trade/request/get_trade_calendar_request.py +30 -0
  259. webull/trade/request/get_trade_instrument_detail_request.py +24 -0
  260. webull/trade/request/get_trade_security_detail_request.py +42 -0
  261. webull/trade/request/get_tradeable_instruments_request.py +27 -0
  262. webull/trade/request/palce_order_request.py +91 -0
  263. webull/trade/request/place_order_request_v2.py +58 -0
  264. webull/trade/request/replace_order_request.py +73 -0
  265. webull/trade/request/replace_order_request_v2.py +38 -0
  266. webull/trade/request/v2/__init__.py +0 -0
  267. webull/trade/request/v2/cancel_option_request.py +28 -0
  268. webull/trade/request/v2/cancel_order_request.py +28 -0
  269. webull/trade/request/v2/get_account_balance_request.py +28 -0
  270. webull/trade/request/v2/get_account_list.py +23 -0
  271. webull/trade/request/v2/get_account_positions_request.py +24 -0
  272. webull/trade/request/v2/get_order_detail_request.py +26 -0
  273. webull/trade/request/v2/get_order_history_request.py +35 -0
  274. webull/trade/request/v2/palce_order_request.py +87 -0
  275. webull/trade/request/v2/place_option_request.py +64 -0
  276. webull/trade/request/v2/preview_option_request.py +28 -0
  277. webull/trade/request/v2/preview_order_request.py +59 -0
  278. webull/trade/request/v2/replace_option_request.py +28 -0
  279. webull/trade/request/v2/replace_order_request.py +57 -0
  280. webull/trade/trade/__init__.py +0 -0
  281. webull/trade/trade/account_info.py +83 -0
  282. webull/trade/trade/order_operation.py +246 -0
  283. webull/trade/trade/trade_calendar.py +37 -0
  284. webull/trade/trade/trade_instrument.py +72 -0
  285. webull/trade/trade/v2/__init__.py +0 -0
  286. webull/trade/trade/v2/account_info_v2.py +55 -0
  287. webull/trade/trade/v2/order_operation_v2.py +206 -0
  288. webull/trade/trade_client.py +43 -0
  289. webull/trade/trade_events_client.py +233 -0
  290. webull_openapi_python_sdk-1.0.0.dist-info/METADATA +28 -0
  291. webull_openapi_python_sdk-1.0.0.dist-info/RECORD +295 -0
  292. webull_openapi_python_sdk-1.0.0.dist-info/WHEEL +5 -0
  293. webull_openapi_python_sdk-1.0.0.dist-info/licenses/LICENSE +202 -0
  294. webull_openapi_python_sdk-1.0.0.dist-info/licenses/NOTICE +56 -0
  295. webull_openapi_python_sdk-1.0.0.dist-info/top_level.txt +2 -0
@@ -0,0 +1,314 @@
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
+ import logging
18
+ import sys
19
+ import threading
20
+ import time
21
+ import uuid
22
+ from logging.handlers import TimedRotatingFileHandler
23
+
24
+ import paho.mqtt.client as mqttc
25
+
26
+ import webull.core.exception.error_code as error_code
27
+ from webull.core.client import ApiClient
28
+ from webull.core.common import api_type
29
+ from webull.core.endpoint.default_endpoint_resolver import DefaultEndpointResolver
30
+ from webull.core.endpoint.resolver_endpoint_request import ResolveEndpointRequest
31
+ from webull.core.exception.exceptions import ClientException
32
+ from webull.core.http.initializer.client_initializer import ClientInitializer
33
+ from webull.core.retry.retry_condition import RetryCondition
34
+ from webull.data.common.connect_ack import ConnectAck
35
+ from webull.data.internal.default_retry_policy import DefaultQuotesRetryPolicy, QuotesRetryPolicyContext
36
+ from webull.data.internal.exceptions import ConnectException, LoopException
37
+ from webull.data.internal.quotes_decoder import QuotesDecoder
38
+
39
+ DEFAULT_REGION_ID = "us"
40
+
41
+ LOG_INFO = mqttc.MQTT_LOG_INFO
42
+ LOG_NOTICE = mqttc.MQTT_LOG_NOTICE
43
+ LOG_WARNING = mqttc.MQTT_LOG_WARNING
44
+ LOG_ERR = mqttc.MQTT_LOG_ERR
45
+ LOG_DEBUG = mqttc.MQTT_LOG_DEBUG
46
+
47
+ logger = logging.getLogger(__name__)
48
+
49
+ class QuotesClient(mqttc.Client):
50
+ LOG_FORMAT = '%(thread)d %(threadName)s %(asctime)s %(name)s %(levelname)s %(message)s'
51
+ def __init__(self, app_key, app_secret, region_id, session_id,
52
+ http_host=None,
53
+ mqtt_host=None,
54
+ mqtt_port=1883,
55
+ tls_enable=True,
56
+ transport="tcp",
57
+ retry_policy=None,
58
+ downgrade_message=None):
59
+ self._endpoint_resolver = DefaultEndpointResolver(self)
60
+ self._client_id = session_id
61
+ self._app_key = app_key
62
+ self._app_secret = app_secret
63
+ self._region_id = region_id
64
+ self._out_api_message_mutex = threading.Lock()
65
+ self._quotes_session_id = session_id
66
+ self._quotes_subscribe = None
67
+ self._quotes_unsubscribe = None
68
+ self._on_quotes_message = None
69
+ self._http_host = http_host
70
+ self._mqtt_host = mqtt_host
71
+ self._mqtt_port = mqtt_port
72
+ self._quotes_decoder = QuotesDecoder()
73
+
74
+ api_client = ApiClient(app_key, app_secret, region_id)
75
+ if http_host:
76
+ api_client.add_endpoint(region_id, http_host)
77
+ self._api_client = api_client
78
+
79
+ def _quotes_message(client, userdata, message):
80
+ decoded = client._quotes_decoder.decode(message)
81
+ if decoded:
82
+ client._easy_log(
83
+ LOG_DEBUG, 'decoded message topic: %s, payload: %s', decoded[0], decoded[1])
84
+ _on_quotes_message = client._on_quotes_message
85
+ no_callback_topic = ['echo','notice']
86
+ if _on_quotes_message and decoded[0] not in no_callback_topic:
87
+ _on_quotes_message(client, decoded[0], decoded[1])
88
+ else:
89
+ client._easy_log(
90
+ LOG_ERR, 'unexpected decoding for message topic: %s', message.topic)
91
+
92
+ def _quotes_on_connect(client, userdata, flags, rc):
93
+ if rc == 0:
94
+ with self._callback_mutex:
95
+ _quotes_subscribe = self._quotes_subscribe
96
+ if _quotes_subscribe:
97
+ with self._out_api_message_mutex:
98
+ try:
99
+ _quotes_subscribe(
100
+ client, self._api_client, self._quotes_session_id)
101
+ except Exception as e:
102
+ self._easy_log(
103
+ LOG_ERR, 'Caught exception in on_quotes_subscribe: %s', e)
104
+ raise
105
+ else:
106
+ raise ClientException(
107
+ error_code.SDK_INVALID_PARAMETER, "on_quotes_subscribe func must be set")
108
+ else:
109
+ error_msg = ''
110
+ ack = ConnectAck.from_code(rc)
111
+ if ack is not None:
112
+ error_msg = ack.value[1]
113
+ raise ConnectException(rc, error_msg)
114
+
115
+ self._quotes_message = _quotes_message
116
+ self._quotes_on_connect = _quotes_on_connect
117
+ mqttc.Client.__init__(self, self._client_id, transport=transport, reconnect_on_failure=False)
118
+ self.username_pw_set(self._app_key, uuid.uuid4().hex)
119
+ if tls_enable:
120
+ self.tls_set()
121
+ if retry_policy:
122
+ self._retry_policy = retry_policy
123
+ else:
124
+ self._retry_policy = DefaultQuotesRetryPolicy()
125
+ self._mqtt_host = mqtt_host
126
+
127
+ @property
128
+ def on_quotes_subscribe(self):
129
+ return self._quotes_subscribe
130
+
131
+ @on_quotes_subscribe.setter
132
+ def on_quotes_subscribe(self, func):
133
+ with self._callback_mutex:
134
+ self._quotes_subscribe = func
135
+
136
+ @property
137
+ def on_quotes_unsubscribe(self):
138
+ return self._quotes_unsubscribe
139
+
140
+ @on_quotes_unsubscribe.setter
141
+ def on_quotes_unsubscribe(self, func):
142
+ with self._callback_mutex:
143
+ self._quotes_unsubscribe = func
144
+
145
+ @property
146
+ def quotes_session_id(self):
147
+ return self._quotes_session_id
148
+
149
+ @property
150
+ def api_client(self):
151
+ return self._api_client
152
+
153
+ @property
154
+ def on_quotes_message(self):
155
+ return self._on_quotes_message
156
+
157
+ @on_quotes_message.setter
158
+ def on_quotes_message(self, func):
159
+ with self._callback_mutex:
160
+ self._on_quotes_message = func
161
+
162
+ def register_payload_decoder(self, type, decoder):
163
+ with self._callback_mutex:
164
+ self._quotes_decoder.register_payload_decoder(type, decoder)
165
+
166
+ def _quotes_connect(self, host, port):
167
+ self.on_message = self._quotes_message
168
+ self.on_connect = self._quotes_on_connect
169
+ if not host:
170
+ endpoint_request = ResolveEndpointRequest(
171
+ self._region_id, api_type=api_type.QUOTES)
172
+ endpoint = self._endpoint_resolver.resolve(endpoint_request)
173
+ _host = endpoint
174
+ else:
175
+ _host = host
176
+ try:
177
+ return super().connect(_host, port)
178
+ except Exception as e:
179
+ self._easy_log(
180
+ LOG_ERR, 'Caught exception in connect: %s, host: %s, port: %s, ssl: %s', e, _host, port, self._ssl)
181
+ raise e
182
+
183
+ def connect_and_loop_forever(self, timeout=1, logger_enable=True, customer_logger=None):
184
+ self._init_logger(logger_enable, customer_logger)
185
+ self._init_client()
186
+ retry_policy_context = QuotesRetryPolicyContext(None, 0, None)
187
+ retries = 0
188
+ final_exception = None
189
+ while True:
190
+ if self._thread_terminate is True:
191
+ self._easy_log(LOG_WARNING,
192
+ 'exited due to thread terminated')
193
+ self._sock_close()
194
+ return
195
+ try:
196
+ self._quotes_connect(self._mqtt_host, self._mqtt_port)
197
+ loop_ret = super().loop_forever(timeout)
198
+ # loop_ret != 0 means unexpected error returned from server, should be retry in future
199
+ if loop_ret != 0:
200
+ raise LoopException(loop_ret)
201
+ else:
202
+ self._easy_log(LOG_WARNING, 'exited normally')
203
+ return
204
+ except ConnectException as connect_exception:
205
+ final_exception = connect_exception
206
+ retry_policy_context = QuotesRetryPolicyContext(
207
+ None, retries, connect_exception.error_code)
208
+ self._easy_log(LOG_ERR,
209
+ 'connect exception:%s', connect_exception)
210
+ except Exception as exception:
211
+ final_exception = exception
212
+ retry_policy_context = QuotesRetryPolicyContext(
213
+ exception, retries, None)
214
+ self._easy_log(LOG_ERR, 'exception:%s', exception)
215
+ retryable = self._retry_policy.should_retry(retry_policy_context)
216
+ if retryable & RetryCondition.NO_RETRY:
217
+ self._easy_log(
218
+ LOG_ERR, 'processing will stopped due to not be retryable, retry_context:%s', retry_policy_context)
219
+ break
220
+ retry_policy_context.retryable = retryable
221
+ time_to_sleep = self._retry_policy.compute_delay_before_next_retry(
222
+ retry_policy_context)
223
+ self._easy_log(LOG_INFO, "next retry will be started in %s ms, retry_context:%s",
224
+ time_to_sleep, retry_policy_context)
225
+ time.sleep(time_to_sleep / 1000.0)
226
+ retries += 1
227
+ retry_policy_context.retries_attempted = retries
228
+ self._sock_close()
229
+ if final_exception:
230
+ raise final_exception
231
+
232
+ def connect_and_loop_async(self, timeout=1, thread_daemon=False, logger_enable=True, customer_logger=None):
233
+ if self._thread is not None:
234
+ return mqttc.MQTT_ERR_INVAL
235
+ self._sockpairR, self._sockpairW = mqttc._socketpair_compat()
236
+ self._thread_terminate = False
237
+ self._thread = threading.Thread(
238
+ target=self.connect_and_loop_forever, name="Thread-Async-Quotes-Client", args=(timeout,))
239
+ self._thread.daemon = True
240
+ self._thread.daemon = thread_daemon
241
+ self._thread.start()
242
+
243
+ def connect_and_loop_start(self, timeout=1, logger_enable=True, customer_logger=None):
244
+ self.connect_and_loop_async(timeout, True, logger_enable, customer_logger)
245
+
246
+ def loop_wait(self):
247
+ if self._thread is None:
248
+ return mqttc.MQTT_ERR_INVAL
249
+ if threading.current_thread() != self._thread:
250
+ self._thread.join()
251
+
252
+ def loop_stop(self):
253
+ return super().loop_stop()
254
+
255
+ def set_stream_logger(self, log_level=logging.DEBUG, logger_name='webull.data', stream=None,
256
+ format_string=None):
257
+ if format_string is None:
258
+ format_string = self.LOG_FORMAT
259
+
260
+ # http
261
+ self.api_client.set_stream_logger(log_level, 'webull.core', stream, format_string)
262
+
263
+ # mqtt
264
+ log = logging.getLogger(logger_name)
265
+ log.setLevel(log_level)
266
+ ch = logging.StreamHandler(stream)
267
+ ch.setLevel(log_level)
268
+ formatter = logging.Formatter(format_string)
269
+ ch.setFormatter(formatter)
270
+ log.addHandler(ch)
271
+
272
+ def set_file_logger(self, path, log_level=logging.DEBUG, logger_name='webull.data', format_string=None, when='H', interval=1, backup_count=72):
273
+ if format_string is None:
274
+ format_string = self.LOG_FORMAT
275
+
276
+ # http
277
+ self.api_client.set_file_logger(path, log_level, 'webull.core', format_string, when, interval, backup_count)
278
+
279
+ # mqtt
280
+ log = logging.getLogger(logger_name)
281
+ log.setLevel(log_level)
282
+ handler = TimedRotatingFileHandler(
283
+ filename=path,
284
+ when=when,
285
+ interval=interval,
286
+ backupCount=backup_count,
287
+ encoding='utf-8'
288
+ )
289
+ formatter = logging.Formatter(format_string)
290
+ handler.setFormatter(formatter)
291
+ log.addHandler(handler)
292
+
293
+ def _init_logger(self, logger_enable=True, customer_logger=None):
294
+
295
+ if logger_enable is not True:
296
+ return
297
+
298
+ if customer_logger:
299
+ customer_api_logger = logging.getLogger("webull.core")
300
+ customer_api_logger.setLevel(customer_logger.level)
301
+ for handler in customer_logger.handlers:
302
+ customer_api_logger.addHandler(handler)
303
+ self.api_client.set_logger(customer_api_logger)
304
+ self.enable_logger(customer_logger)
305
+ else:
306
+ log_format = '%(thread)d %(asctime)s %(name)s %(levelname)s %(message)s'
307
+ log_file_path = 'webull_data_streaming_sdk.log'
308
+ self.set_stream_logger(stream=sys.stdout, logger_name='webull.data', log_level=logging.INFO, format_string=log_format)
309
+ self.set_file_logger(path=log_file_path, logger_name='webull.data', log_level=logging.INFO, format_string=log_format)
310
+ self.enable_logger(logging.getLogger('webull.data'))
311
+
312
+
313
+ def _init_client(self):
314
+ ClientInitializer.initializer(self.api_client)
@@ -0,0 +1,40 @@
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.data.internal.quotes_payload_decoder import Utf8Decoder
18
+
19
+ class QuotesDecoder:
20
+ def __init__(self):
21
+ self._payload_decoders = {}
22
+ self._default_decoder = Utf8Decoder()
23
+
24
+ def register_payload_decoder(self, payload_type, decoder):
25
+ self._payload_decoders[payload_type] = decoder
26
+
27
+ def decode(self, message):
28
+ quotes_topic = message.topic
29
+ if quotes_topic:
30
+ payload = message.payload
31
+ decoded_payload = self.decode_payload(quotes_topic, payload)
32
+ return (quotes_topic, decoded_payload)
33
+ return None
34
+
35
+ def decode_payload(self, quotes_topic, payload):
36
+ decoder = self._payload_decoders.get(quotes_topic)
37
+ if decoder:
38
+ return decoder.parse(payload)
39
+ else:
40
+ return self._default_decoder.parse(payload)
@@ -0,0 +1,35 @@
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 abc
17
+ from webull.core.vendored.six import add_metaclass
18
+
19
+
20
+ @add_metaclass(abc.ABCMeta)
21
+ class BaseQuotesPayloadDecoder(object):
22
+ def __init__(self):
23
+ pass
24
+
25
+ @abc.abstractclassmethod
26
+ def parse(self, payload):
27
+ pass
28
+
29
+
30
+ class Utf8Decoder(BaseQuotesPayloadDecoder):
31
+ def __init__(self):
32
+ super().__init__()
33
+
34
+ def parse(self, payload):
35
+ return str(payload.decode("utf-8"))
@@ -0,0 +1,36 @@
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
+ class QuotesTopic(object):
18
+ def __init__(self, instrument_id, data_type, interval):
19
+ self.instrument_id = instrument_id
20
+ self.data_type = int(data_type)
21
+ self.interval = int(interval)
22
+
23
+ def get_instrument_id(self):
24
+ return self.instrument_id
25
+
26
+ def get_data_type(self):
27
+ return self.data_type
28
+
29
+ def get_interval(self):
30
+ return self.interval
31
+
32
+ def __repr__(self):
33
+ return "%s-%s-%s" % (self.instrument_id, self.data_type, self.interval)
34
+
35
+ def __str__(self):
36
+ return self.__repr__()
File without changes
@@ -0,0 +1,33 @@
1
+ # Copyright 2022 Webull
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+
15
+ from webull.data.request.get_instruments_request import GetInstrumentsRequest
16
+
17
+
18
+ class Instrument:
19
+ def __init__(self, api_client):
20
+ self.client = api_client
21
+
22
+ def get_instrument(self, symbols, category):
23
+ """
24
+ Query the underlying information according to the security symbol list and security type.
25
+
26
+ :param symbols: Securities symbol, such as: 00700,00981.
27
+ :param category: Security type, enumeration.
28
+ """
29
+ instruments_request = GetInstrumentsRequest()
30
+ instruments_request.set_symbols(symbols)
31
+ instruments_request.set_category(category)
32
+ response = self.client.get_response(instruments_request)
33
+ return response
@@ -0,0 +1,187 @@
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.request.get_batch_historical_bars_request import BatchHistoricalBarsRequest
15
+ from webull.data.request.get_corp_action_request import GetCorpActionRequest
16
+ from webull.data.request.get_eod_bars_request import GetEodBarsRequest
17
+ from webull.data.request.get_historical_bars_request import GetHistoricalBarsRequest
18
+ from webull.data.request.get_quotes_request import GetQuotesRequest
19
+ from webull.data.request.get_snapshot_request import GetSnapshotRequest
20
+ from webull.data.request.get_tick_request import GetTickRequest
21
+ from webull.data.request.subscribe_request import SubscribeRequest
22
+ from webull.data.request.unsubscribe_request import UnsubcribeRequest
23
+
24
+
25
+ class MarketData:
26
+ def __init__(self, api_client):
27
+ self.client = api_client
28
+
29
+ def get_history_bar(self, symbol, category, timespan, count='200', real_time_required=None, trading_sessions=None):
30
+ """
31
+ Returns to Instrument in the window aggregated data.
32
+ According to the last N K-lines of the stock code, it supports various granularity K-lines such as m1 and m5.
33
+ Currently, only the K-line with the previous weight is provided for the daily K-line and above,
34
+ and only the un-weighted K-line is provided for the minute K.
35
+
36
+ :param symbol: Securities code
37
+ :param category: Security type, enumeration.
38
+ :param timespan: K-line time granularity
39
+ :param count: The number of lines: the default is 200, and the maximum limit is 1200
40
+ :param real_time_required: Returns the latest trade quote data. By default, the most recent market data is returned.
41
+ :param trading_sessions: Specify trading session, multiple selections are allowed
42
+ By default, only intraday candlestick data is returned.
43
+ """
44
+ history_bar_request = GetHistoricalBarsRequest()
45
+ history_bar_request.set_symbol(symbol)
46
+ history_bar_request.set_category(category)
47
+ history_bar_request.set_timespan(timespan)
48
+ history_bar_request.set_count(count)
49
+ history_bar_request.set_real_time_required(real_time_required)
50
+ history_bar_request.set_trading_sessions(trading_sessions)
51
+ response = self.client.get_response(history_bar_request)
52
+ return response
53
+
54
+ def get_batch_history_bar(self, symbols, category, timespan, count='200', real_time_required=None, trading_sessions=None):
55
+ """
56
+ Batch query K-line data for multiple symbols, returning aggregated data within the window.
57
+ According to the last N K-lines of the stock code, it supports various granularity K-lines such as m1 and m5.
58
+ Currently, only the K-line with the previous weight is provided for the daily K-line and above,
59
+ and only the un-weighted K-line is provided for the minute K.
60
+
61
+ :param symbols: List of security codes
62
+ :param category: Security type, enumeration
63
+ :param timespan: K-line interval
64
+ :param count: Number of K-lines to return, default is 200, maximum is 1200
65
+ :param real_time_required: Returns the latest trade quote data. By default, the most recent market data is returned.
66
+ :param trading_sessions: Specify trading session, multiple selections are allowed
67
+ By default, only intraday candlestick data is returned.
68
+ """
69
+ history_bar_request = BatchHistoricalBarsRequest()
70
+ history_bar_request.set_symbols(symbols)
71
+ history_bar_request.set_category(category)
72
+ history_bar_request.set_timespan(timespan)
73
+ history_bar_request.set_count(count)
74
+ history_bar_request.set_real_time_required(real_time_required)
75
+ history_bar_request.set_trading_sessions(trading_sessions)
76
+ response = self.client.get_response(history_bar_request)
77
+ return response
78
+
79
+ def get_snapshot(self, symbols, category, extend_hour_required=None, overnight_required=None):
80
+ """
81
+ Query the latest stock market snapshots in batches according to the stock code list.
82
+
83
+ :param symbols: List of security codes; for example: single: 00700 multiple: 00700,00981;
84
+ For each request,up to 100 symbols can be subscribed; Under the authority of Hong Kong stock BMP,
85
+ a single request supports up to 20 symbols
86
+ :param category: Security type, enumeration.
87
+ :param extend_hour_required: Whether to include pre-market and after-hours sessions, the default is not included
88
+ :param overnight_required: Whether to include the night session, the default is not included
89
+ """
90
+ snapshot_request = GetSnapshotRequest()
91
+ snapshot_request.set_symbols(symbols)
92
+ snapshot_request.set_category(category)
93
+ snapshot_request.set_extend_hour_required(extend_hour_required)
94
+ snapshot_request.set_overnight_required(overnight_required)
95
+ response = self.client.get_response(snapshot_request)
96
+ return response
97
+
98
+ def get_quotes(self, symbol, category, depth=None, overnight_required=None):
99
+ """
100
+ Query the depth quote of securities according to the stock code list.
101
+
102
+ :param symbol: Securities code
103
+ :param category: Security type, enumeration.
104
+ :param depth: Retrieve bid/ask depth
105
+ Level 1 contains only the top 1 bid/ask level.
106
+ Level 2 becomes effective, with 10 levels by default. For U.S. stocks, Level 2 supports up to 50 levels.
107
+ :param overnight_required: Whether to include the night session, the default is not included
108
+ """
109
+ quote_request = GetQuotesRequest()
110
+ quote_request.set_symbol(symbol)
111
+ quote_request.set_category(category)
112
+ quote_request.set_depth(depth)
113
+ quote_request.set_overnight_required(overnight_required)
114
+ response = self.client.get_response(quote_request)
115
+ return response
116
+
117
+ def get_tick(self, symbol, category, count='200', trading_sessions=None):
118
+ """
119
+ Query tick-by-tick transaction of securities according to the stock code list.
120
+
121
+ :param symbol: Securities code
122
+ :param category: Security type, enumeration.
123
+ :param count: The number of lines: the default is 30, and the maximum limit is 1000
124
+ :param trading_sessions: Specify trading session, multiple selections are allowed
125
+ """
126
+ tick_request = GetTickRequest()
127
+ tick_request.set_symbol(symbol)
128
+ tick_request.set_category(category)
129
+ tick_request.set_count(count)
130
+ tick_request.set_trading_sessions(trading_sessions)
131
+ response = self.client.get_response(tick_request)
132
+ return response
133
+
134
+ def get_eod_bar(self, instrument_ids, date=None, count='1'):
135
+ """
136
+ Only for Webull JP
137
+
138
+ Query end-of-day market information according to instrument_id.
139
+
140
+ :param instrument_ids: Instrument id collection, such as: 913256135,913303964.
141
+ Multiple instrument_ids should be separated by ,.
142
+ A single query supports up to 200 instrument_id
143
+ :param date: UTC time. Time format: yyyy-MM-dd, and the default check is conducted on the latest date
144
+ :param count: With “date” as the deadline, the end-of-day market data of the last “count” trading days:
145
+ the default is 1, and the maximum limit is 800
146
+ """
147
+ eod_bar_request = GetEodBarsRequest()
148
+ eod_bar_request.set_instrument_ids(instrument_ids)
149
+ if date is not None:
150
+ eod_bar_request.set_date(date)
151
+ eod_bar_request.set_count(count)
152
+ response = self.client.get_response(eod_bar_request)
153
+ return response
154
+
155
+ def get_corp_action(self, instrument_ids, event_types, start_date=None, end_date=None, page_number=None,
156
+ page_size=None, last_update_time=None):
157
+ """
158
+ Only for Webull JP
159
+ Supports the query of the corporate events for stock splits and reverse stock split,
160
+ including past and upcoming events.
161
+
162
+ :param instrument_ids: Instrument id collection, such as: 913256135,913303964.
163
+ Multiple instrument_ids should be separated by ,.
164
+ A single query supports up to 100 instrument_id
165
+ :param event_types: Event type collection. Multiple event_types should be separated by ,
166
+ :param start_date: Event start date, UTC time.Time format: yyyy-MM-dd
167
+ :param end_date: Event end date, UTC time.Time format: yyyy-MM-dd
168
+ :param page_number: The initial value, if not passed, the first page will be searched by default
169
+ :param page_size: Number of entries per page: default value is 20, and maximum value is 200.
170
+ Integers can be filled
171
+ :param last_update_time: Incremental update time, UTC time. Time format: yyyy-MM-dd HH:mm:ss
172
+ """
173
+ eod_corp_action_request = GetCorpActionRequest()
174
+ eod_corp_action_request.set_instrument_ids(instrument_ids)
175
+ eod_corp_action_request.set_event_types(event_types)
176
+ if start_date is not None:
177
+ eod_corp_action_request.set_start_date(start_date)
178
+ if end_date is not None:
179
+ eod_corp_action_request.set_end_date(end_date)
180
+ if page_number is not None:
181
+ eod_corp_action_request.set_page_number(page_number)
182
+ if page_size is not None:
183
+ eod_corp_action_request.set_page_size(page_size)
184
+ if last_update_time is not None:
185
+ eod_corp_action_request.set_last_update_time(last_update_time)
186
+ response = self.client.get_response(eod_corp_action_request)
187
+ return response