diff --git a/README.md b/README.md index 76edfb7..d33e42b 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # Webull OpenAPI Python SDK +Note: This is the new version of the Webull SDK, currently applicable only to Webull Hong Kong or Webull US customers. Webull OpenAPI aims to provide quantitative trading investors with convenient, fast and secure services. Webull aims to help every quant traders achieve flexible and changeable trading or market strategies. diff --git a/samples/__init__.py b/samples/__init__.py index 3f39079..668c344 100644 --- a/samples/__init__.py +++ b/samples/__init__.py @@ -1 +1 @@ -__version__ = '2.0.1' +__version__ = '2.0.2' diff --git a/samples/data/data_streaming_client_event.py b/samples/data/data_streaming_client_event.py index 1ab1652..98f6763 100644 --- a/samples/data/data_streaming_client_event.py +++ b/samples/data/data_streaming_client_event.py @@ -43,7 +43,7 @@ def my_connect_success_func(client, api_client, quotes_session_id): print("connect success with session_id:%s" % quotes_session_id) # subscribe symbols = ['KXNHLGAME-26JAN21ANACOL-ANA', 'KXNBAGAME-26JAN20LACCHI-LAC'] - sub_types = [SubscribeType.QUOTE.name, SubscribeType.SNAPSHOT.name] + sub_types = [SubscribeType.QUOTE.name, SubscribeType.SNAPSHOT.name, SubscribeType.TICK.name] client.subscribe(symbols, Category.US_EVENT.name, sub_types) def my_quotes_message_func(client, topic, quotes): diff --git a/samples/trade/trade_client_v3.py b/samples/trade/trade_client_v3.py index ab61f82..51d89fb 100644 --- a/samples/trade/trade_client_v3.py +++ b/samples/trade/trade_client_v3.py @@ -247,6 +247,7 @@ # ============================================================ # normal option order + # position_intent: Currently, only the US market is supported, and only options orders are allowed. normal_option_client_order_id = uuid.uuid4().hex new_normal_option_orders = [ { @@ -259,6 +260,7 @@ "side": "BUY", "time_in_force": "GTC", "entrust_type": "QTY", + "position_intent": "BUY_TO_OPEN", "legs": [ { "side": "BUY", diff --git a/webull/__init__.py b/webull/__init__.py index 3f39079..668c344 100644 --- a/webull/__init__.py +++ b/webull/__init__.py @@ -1 +1 @@ -__version__ = '2.0.1' +__version__ = '2.0.2' diff --git a/webull/core/__init__.py b/webull/core/__init__.py index 9769a68..fa855e6 100644 --- a/webull/core/__init__.py +++ b/webull/core/__init__.py @@ -1,4 +1,4 @@ -__version__ = '2.0.1' +__version__ = '2.0.2' import logging diff --git a/webull/core/auth/composer/default_signature_composer.py b/webull/core/auth/composer/default_signature_composer.py index 092d4be..24a0019 100644 --- a/webull/core/auth/composer/default_signature_composer.py +++ b/webull/core/auth/composer/default_signature_composer.py @@ -87,7 +87,7 @@ def _build_sign_string(sign_params, uri, body_string): if string_to_sign: string_to_sign = string_to_sign + PARAMS_JOIN + PARAMS_JOIN.join(sorted_array) else: - string_to_sign = PARAM_KV_JOIN.join(sorted_array) + string_to_sign = PARAMS_JOIN.join(sorted_array) if body_string: string_to_sign = string_to_sign + PARAMS_JOIN + body_string # All characters except alphabetic characters, digits, -, ., _, ~ will be encoded as %XX. diff --git a/webull/data/__init__.py b/webull/data/__init__.py index 9dacdb0..0ea29a8 100644 --- a/webull/data/__init__.py +++ b/webull/data/__init__.py @@ -1,3 +1,3 @@ # coding=utf-8 -__version__ = '2.0.1' +__version__ = '2.0.2' diff --git a/webull/data/data_streaming_client.py b/webull/data/data_streaming_client.py index ccb8f6b..cd362fc 100644 --- a/webull/data/data_streaming_client.py +++ b/webull/data/data_streaming_client.py @@ -18,8 +18,9 @@ from webull.data.quotes.market_streaming_data import MarketDataStreaming from webull.data.quotes.subscribe.event_depth_decoder import EventDepthDecoder from webull.data.quotes.subscribe.event_snapshot_decoder import EventSnapshotDecoder +from webull.data.quotes.subscribe.event_tick_decoder import EventTickDecoder from webull.data.quotes.subscribe.payload_type import PAYLOAD_TYPE_QUOTE, PAYLOAD_TYPE_SHAPSHOT, PAYLOAD_TYPE_TICK, \ - PAYLOAD_TYPE_EVENT_SHAPSHOT, PAYLOAD_TYPE_EVENT_DEPTH + PAYLOAD_TYPE_EVENT_SHAPSHOT, PAYLOAD_TYPE_EVENT_DEPTH, PAYLOAD_TYPE_EVENT_TICK from webull.data.quotes.subscribe.quote_decoder import QuoteDecoder from webull.data.quotes.subscribe.snapshot_decoder import SnapshotDecoder from webull.data.quotes.subscribe.tick_decoder import TickDecoder @@ -44,6 +45,7 @@ def __init__(self, app_key, app_secret, region_id, session_id, http_host=None, self.register_payload_decoder(PAYLOAD_TYPE_TICK, TickDecoder()) self.register_payload_decoder(PAYLOAD_TYPE_EVENT_DEPTH, EventDepthDecoder()) self.register_payload_decoder(PAYLOAD_TYPE_EVENT_SHAPSHOT, EventSnapshotDecoder()) + self.register_payload_decoder(PAYLOAD_TYPE_EVENT_TICK, EventTickDecoder()) @property def on_connect_success(self): diff --git a/webull/data/quotes/subscribe/event_tick_decoder.py b/webull/data/quotes/subscribe/event_tick_decoder.py new file mode 100644 index 0000000..fccb7de --- /dev/null +++ b/webull/data/quotes/subscribe/event_tick_decoder.py @@ -0,0 +1,30 @@ +# Copyright 2022 Webull +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# coding=utf-8 + +from webull.data.quotes.subscribe.message_pb2 import EventTick +from webull.data.quotes.subscribe.event_tick_result import EventTickResult + +from webull.data.internal.quotes_payload_decoder import BaseQuotesPayloadDecoder + + + +class EventTickDecoder(BaseQuotesPayloadDecoder): + def __init__(self): + super().__init__() + + def parse(self, payload): + eventTick = EventTick() + eventTick.ParseFromString(payload) + return EventTickResult(eventTick) diff --git a/webull/data/quotes/subscribe/event_tick_result.py b/webull/data/quotes/subscribe/event_tick_result.py new file mode 100644 index 0000000..3361b1e --- /dev/null +++ b/webull/data/quotes/subscribe/event_tick_result.py @@ -0,0 +1,56 @@ +# Copyright 2022 Webull +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# coding=utf-8 + +from decimal import Decimal +from webull.data.quotes.subscribe.basic_result import BasicResult + + +class EventTickResult: + def __init__(self, pb_event_tick): + self.basic = BasicResult(pb_event_tick.basic) + self.time = pb_event_tick.time + self.yes_price = Decimal(pb_event_tick.yes_price) if pb_event_tick.yes_price else None + self.no_price = Decimal(pb_event_tick.no_price) if pb_event_tick.no_price else None + self.volume = pb_event_tick.volume + self.side = pb_event_tick.side + self.trade_id = pb_event_tick.trade_id + + def get_basic(self): + return self.basic + + def get_time(self): + return self.time + + def get_yes_price(self): + return self.yes_price + + def get_no_price(self): + return self.no_price + + def get_volume(self): + return self.volume + + def get_side(self): + return self.side + + def get_trade_id(self): + return self.trade_id + + def __repr__(self): + return "basic: %s,time: %s,yes_price: %s,no_price:%s,volume:%s,side:%s,trade_id:%s" % (self.basic, self.time, self.yes_price, self.no_price, self.volume, self.side, self.trade_id) + + def __str__(self): + return self.__repr__() diff --git a/webull/data/quotes/subscribe/message.proto b/webull/data/quotes/subscribe/message.proto index 8931bd0..9c2ef60 100644 --- a/webull/data/quotes/subscribe/message.proto +++ b/webull/data/quotes/subscribe/message.proto @@ -92,4 +92,14 @@ message EventQuote { message EventAskBid { string price = 1; string size = 2; +} + +message EventTick { + Basic basic = 1; + string yes_price = 2; + string no_price = 3; + string volume = 4; + string side = 5; + string trade_id = 6; + string time = 7; } \ No newline at end of file diff --git a/webull/data/quotes/subscribe/message_pb2.py b/webull/data/quotes/subscribe/message_pb2.py index 701dc05..56b4f72 100644 --- a/webull/data/quotes/subscribe/message_pb2.py +++ b/webull/data/quotes/subscribe/message_pb2.py @@ -13,7 +13,7 @@ -DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rmessage.proto\"Z\n\x05\x42\x61sic\x12\x0e\n\x06symbol\x18\x01 \x01(\t\x12\x15\n\rinstrument_id\x18\x02 \x01(\t\x12\x11\n\ttimestamp\x18\x03 \x01(\t\x12\x17\n\x0ftrading_session\x18\x04 \x01(\t\"\xd6\x03\n\x08Snapshot\x12\x15\n\x05\x62\x61sic\x18\x01 \x01(\x0b\x32\x06.Basic\x12\x12\n\ntrade_time\x18\x02 \x01(\t\x12\r\n\x05price\x18\x03 \x01(\t\x12\x0c\n\x04open\x18\x04 \x01(\t\x12\x0c\n\x04high\x18\x05 \x01(\t\x12\x0b\n\x03low\x18\x06 \x01(\t\x12\x11\n\tpre_close\x18\x07 \x01(\t\x12\x0e\n\x06volume\x18\x08 \x01(\t\x12\x0e\n\x06\x63hange\x18\t \x01(\t\x12\x14\n\x0c\x63hange_ratio\x18\n \x01(\t\x12\x16\n\x0e\x65xt_trade_time\x18\x0b \x01(\t\x12\x11\n\text_price\x18\x0c \x01(\t\x12\x10\n\x08\x65xt_high\x18\r \x01(\t\x12\x0f\n\x07\x65xt_low\x18\x0e \x01(\t\x12\x12\n\next_volume\x18\x0f \x01(\t\x12\x12\n\next_change\x18\x10 \x01(\t\x12\x18\n\x10\x65xt_change_ratio\x18\x11 \x01(\t\x12\x16\n\x0eovn_trade_time\x18\x12 \x01(\t\x12\x11\n\tovn_price\x18\x13 \x01(\t\x12\x10\n\x08ovn_high\x18\x14 \x01(\t\x12\x0f\n\x07ovn_low\x18\x15 \x01(\t\x12\x12\n\novn_volume\x18\x16 \x01(\t\x12\x12\n\novn_change\x18\x17 \x01(\t\x12\x18\n\x10ovn_change_ratio\x18\x18 \x01(\t\"L\n\x05Quote\x12\x15\n\x05\x62\x61sic\x18\x01 \x01(\x0b\x32\x06.Basic\x12\x15\n\x04\x61sks\x18\x02 \x03(\x0b\x32\x07.AskBid\x12\x15\n\x04\x62ids\x18\x03 \x03(\x0b\x32\x07.AskBid\"X\n\x04Tick\x12\x15\n\x05\x62\x61sic\x18\x01 \x01(\x0b\x32\x06.Basic\x12\x0c\n\x04time\x18\x02 \x01(\t\x12\r\n\x05price\x18\x03 \x01(\t\x12\x0e\n\x06volume\x18\x04 \x01(\t\x12\x0c\n\x04side\x18\x05 \x01(\t\"U\n\x06\x41skBid\x12\r\n\x05price\x18\x01 \x01(\t\x12\x0c\n\x04size\x18\x02 \x01(\t\x12\x15\n\x05order\x18\x03 \x03(\x0b\x32\x06.Order\x12\x17\n\x06\x62roker\x18\x04 \x03(\x0b\x32\x07.Broker\"#\n\x05Order\x12\x0c\n\x04mpid\x18\x01 \x01(\t\x12\x0c\n\x04size\x18\x02 \x01(\t\"#\n\x06\x42roker\x12\x0b\n\x03\x62id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\"\x8d\x02\n\rEventSnapshot\x12\x15\n\x05\x62\x61sic\x18\x01 \x01(\x0b\x32\x06.Basic\x12\r\n\x05price\x18\x02 \x01(\t\x12\x0e\n\x06volume\x18\x03 \x01(\t\x12\x17\n\x0flast_trade_time\x18\x04 \x01(\t\x12\x15\n\ropen_interest\x18\x05 \x01(\t\x12\x0f\n\x07yes_ask\x18\x06 \x01(\t\x12\x0f\n\x07yes_bid\x18\x07 \x01(\t\x12\x14\n\x0cyes_ask_size\x18\x08 \x01(\t\x12\x14\n\x0cyes_bid_size\x18\t \x01(\t\x12\x0e\n\x06no_ask\x18\n \x01(\t\x12\x0e\n\x06no_bid\x18\x0b \x01(\t\x12\x13\n\x0bno_ask_size\x18\x0c \x01(\t\x12\x13\n\x0bno_bid_size\x18\r \x01(\t\"b\n\nEventQuote\x12\x15\n\x05\x62\x61sic\x18\x01 \x01(\x0b\x32\x06.Basic\x12\x1e\n\x08yes_bids\x18\x02 \x03(\x0b\x32\x0c.EventAskBid\x12\x1d\n\x07no_bids\x18\x03 \x03(\x0b\x32\x0c.EventAskBid\"*\n\x0b\x45ventAskBid\x12\r\n\x05price\x18\x01 \x01(\t\x12\x0c\n\x04size\x18\x02 \x01(\tb\x06proto3') +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\rmessage.proto\"Z\n\x05\x42\x61sic\x12\x0e\n\x06symbol\x18\x01 \x01(\t\x12\x15\n\rinstrument_id\x18\x02 \x01(\t\x12\x11\n\ttimestamp\x18\x03 \x01(\t\x12\x17\n\x0ftrading_session\x18\x04 \x01(\t\"\xd6\x03\n\x08Snapshot\x12\x15\n\x05\x62\x61sic\x18\x01 \x01(\x0b\x32\x06.Basic\x12\x12\n\ntrade_time\x18\x02 \x01(\t\x12\r\n\x05price\x18\x03 \x01(\t\x12\x0c\n\x04open\x18\x04 \x01(\t\x12\x0c\n\x04high\x18\x05 \x01(\t\x12\x0b\n\x03low\x18\x06 \x01(\t\x12\x11\n\tpre_close\x18\x07 \x01(\t\x12\x0e\n\x06volume\x18\x08 \x01(\t\x12\x0e\n\x06\x63hange\x18\t \x01(\t\x12\x14\n\x0c\x63hange_ratio\x18\n \x01(\t\x12\x16\n\x0e\x65xt_trade_time\x18\x0b \x01(\t\x12\x11\n\text_price\x18\x0c \x01(\t\x12\x10\n\x08\x65xt_high\x18\r \x01(\t\x12\x0f\n\x07\x65xt_low\x18\x0e \x01(\t\x12\x12\n\next_volume\x18\x0f \x01(\t\x12\x12\n\next_change\x18\x10 \x01(\t\x12\x18\n\x10\x65xt_change_ratio\x18\x11 \x01(\t\x12\x16\n\x0eovn_trade_time\x18\x12 \x01(\t\x12\x11\n\tovn_price\x18\x13 \x01(\t\x12\x10\n\x08ovn_high\x18\x14 \x01(\t\x12\x0f\n\x07ovn_low\x18\x15 \x01(\t\x12\x12\n\novn_volume\x18\x16 \x01(\t\x12\x12\n\novn_change\x18\x17 \x01(\t\x12\x18\n\x10ovn_change_ratio\x18\x18 \x01(\t\"L\n\x05Quote\x12\x15\n\x05\x62\x61sic\x18\x01 \x01(\x0b\x32\x06.Basic\x12\x15\n\x04\x61sks\x18\x02 \x03(\x0b\x32\x07.AskBid\x12\x15\n\x04\x62ids\x18\x03 \x03(\x0b\x32\x07.AskBid\"X\n\x04Tick\x12\x15\n\x05\x62\x61sic\x18\x01 \x01(\x0b\x32\x06.Basic\x12\x0c\n\x04time\x18\x02 \x01(\t\x12\r\n\x05price\x18\x03 \x01(\t\x12\x0e\n\x06volume\x18\x04 \x01(\t\x12\x0c\n\x04side\x18\x05 \x01(\t\"U\n\x06\x41skBid\x12\r\n\x05price\x18\x01 \x01(\t\x12\x0c\n\x04size\x18\x02 \x01(\t\x12\x15\n\x05order\x18\x03 \x03(\x0b\x32\x06.Order\x12\x17\n\x06\x62roker\x18\x04 \x03(\x0b\x32\x07.Broker\"#\n\x05Order\x12\x0c\n\x04mpid\x18\x01 \x01(\t\x12\x0c\n\x04size\x18\x02 \x01(\t\"#\n\x06\x42roker\x12\x0b\n\x03\x62id\x18\x01 \x01(\t\x12\x0c\n\x04name\x18\x02 \x01(\t\"\x8d\x02\n\rEventSnapshot\x12\x15\n\x05\x62\x61sic\x18\x01 \x01(\x0b\x32\x06.Basic\x12\r\n\x05price\x18\x02 \x01(\t\x12\x0e\n\x06volume\x18\x03 \x01(\t\x12\x17\n\x0flast_trade_time\x18\x04 \x01(\t\x12\x15\n\ropen_interest\x18\x05 \x01(\t\x12\x0f\n\x07yes_ask\x18\x06 \x01(\t\x12\x0f\n\x07yes_bid\x18\x07 \x01(\t\x12\x14\n\x0cyes_ask_size\x18\x08 \x01(\t\x12\x14\n\x0cyes_bid_size\x18\t \x01(\t\x12\x0e\n\x06no_ask\x18\n \x01(\t\x12\x0e\n\x06no_bid\x18\x0b \x01(\t\x12\x13\n\x0bno_ask_size\x18\x0c \x01(\t\x12\x13\n\x0bno_bid_size\x18\r \x01(\t\"b\n\nEventQuote\x12\x15\n\x05\x62\x61sic\x18\x01 \x01(\x0b\x32\x06.Basic\x12\x1e\n\x08yes_bids\x18\x02 \x03(\x0b\x32\x0c.EventAskBid\x12\x1d\n\x07no_bids\x18\x03 \x03(\x0b\x32\x0c.EventAskBid\"*\n\x0b\x45ventAskBid\x12\r\n\x05price\x18\x01 \x01(\t\x12\x0c\n\x04size\x18\x02 \x01(\t\"\x85\x01\n\tEventTick\x12\x15\n\x05\x62\x61sic\x18\x01 \x01(\x0b\x32\x06.Basic\x12\x11\n\tyes_price\x18\x02 \x01(\t\x12\x10\n\x08no_price\x18\x03 \x01(\t\x12\x0e\n\x06volume\x18\x04 \x01(\t\x12\x0c\n\x04side\x18\x05 \x01(\t\x12\x10\n\x08trade_id\x18\x06 \x01(\t\x12\x0c\n\x04time\x18\x07 \x01(\tb\x06proto3') _globals = globals() _builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) @@ -40,4 +40,6 @@ _globals['_EVENTQUOTE']._serialized_end=1281 _globals['_EVENTASKBID']._serialized_start=1283 _globals['_EVENTASKBID']._serialized_end=1325 + _globals['_EVENTTICK']._serialized_start=1328 + _globals['_EVENTTICK']._serialized_end=1461 # @@protoc_insertion_point(module_scope) diff --git a/webull/data/quotes/subscribe/payload_type.py b/webull/data/quotes/subscribe/payload_type.py index b0179ce..68cb5de 100644 --- a/webull/data/quotes/subscribe/payload_type.py +++ b/webull/data/quotes/subscribe/payload_type.py @@ -18,4 +18,5 @@ PAYLOAD_TYPE_SHAPSHOT = 'snapshot' PAYLOAD_TYPE_TICK = 'tick' PAYLOAD_TYPE_EVENT_DEPTH = 'event-quote' -PAYLOAD_TYPE_EVENT_SHAPSHOT = 'event-snapshot' \ No newline at end of file +PAYLOAD_TYPE_EVENT_SHAPSHOT = 'event-snapshot' +PAYLOAD_TYPE_EVENT_TICK = 'event-tick' \ No newline at end of file diff --git a/webull/trade/__init__.py b/webull/trade/__init__.py index 6df8ed0..f11d20d 100644 --- a/webull/trade/__init__.py +++ b/webull/trade/__init__.py @@ -1 +1 @@ -__version__ = '1.1.0' \ No newline at end of file +__version__ = '2.0.2' \ No newline at end of file diff --git a/webull/trade/common/position_intent.py b/webull/trade/common/position_intent.py new file mode 100644 index 0000000..f01e351 --- /dev/null +++ b/webull/trade/common/position_intent.py @@ -0,0 +1,24 @@ +# Copyright 2022 Webull +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# coding=utf-8 + +from webull.core.common.easy_enum import EasyEnum + + +class OrderStatus(EasyEnum): + BUY_TO_OPEN = (1, "BUY_TO_OPEN") + BUY_TO_CLOSE = (2, "BUY_TO_CLOSE") + SELL_TO_OPEN = (3, "SELL_TO_OPEN") + SELL_TO_CLOSE = (4, "SELL_TO_CLOSE") diff --git a/webull/trade/trade_events_client.py b/webull/trade/trade_events_client.py index 1c88697..7ae0561 100644 --- a/webull/trade/trade_events_client.py +++ b/webull/trade/trade_events_client.py @@ -68,8 +68,12 @@ def __init__(self, app_key, app_secret, region_id=DEFAULT_REGION_ID, host=None, self._on_log = None def _build_request(self, app_key, app_secret, accounts): + if self._region_id == 'us': + subscribeType = 3 + else: + subscribeType = 1 request = pb.SubscribeRequest( - subscribeType= 3, # only 1、2 allowed now + subscribeType= subscribeType, # 1、2、3 allowed now timestamp=int(time.time() * 1000), # millis accounts=accounts, )