Skip to content

Commit 9d9830c

Browse files
author
方佳
committed
Merge branch 'main_merge_20260110' into 'main'
feat: Add batch order, snapshots support pre-market, after-market, and night trading sessions. See merge request webull/webull-openapi-python-sdk!12
2 parents 1c0befd + ea151d2 commit 9d9830c

11 files changed

Lines changed: 208 additions & 32 deletions

File tree

samples/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.0.7'
1+
__version__ = '1.0.8'

samples/trade/trade_client_v3.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -208,7 +208,38 @@
208208
if res.status_code == 200:
209209
print('get master order detail res:', res.json())
210210

211-
211+
# batch place order
212+
batch_place_orders = [
213+
{
214+
"combo_type": "NORMAL",
215+
"client_order_id": uuid.uuid4().hex,
216+
"instrument_type": "EQUITY",
217+
"market": "US",
218+
"symbol": "AAPL",
219+
"order_type": "MARKET",
220+
"entrust_type": "QTY",
221+
"support_trading_session": "CORE",
222+
"time_in_force": "DAY",
223+
"side": "BUY",
224+
"quantity": "1"
225+
},
226+
{
227+
"combo_type": "NORMAL",
228+
"client_order_id": uuid.uuid4().hex,
229+
"instrument_type": "EQUITY",
230+
"market": "US",
231+
"symbol": "TESL",
232+
"order_type": "MARKET",
233+
"entrust_type": "QTY",
234+
"support_trading_session": "CORE",
235+
"time_in_force": "DAY",
236+
"side": "BUY",
237+
"quantity": "1"
238+
}
239+
]
240+
res = trade_client.order_v3.batch_place_order(account_id, batch_place_orders)
241+
if res.status_code == 200:
242+
print('batch place normal equity order res:', res.json())
212243

213244

214245
# ============================================================

webull/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.0.7'
1+
__version__ = '1.0.8'

webull/core/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
__version__ = '1.0.7'
1+
__version__ = '1.0.8'
22

33
import logging
44

webull/data/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
# coding=utf-8
22

3-
__version__ = '1.0.7'
3+
__version__ = '1.0.8'

webull/data/quotes/subscribe/message.proto

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,22 @@ message Snapshot {
1818
string volume = 8;
1919
string change = 9;
2020
string change_ratio = 10;
21+
22+
string ext_trade_time = 11;
23+
string ext_price = 12;
24+
string ext_high = 13;
25+
string ext_low = 14;
26+
string ext_volume = 15;
27+
string ext_change = 16;
28+
string ext_change_ratio = 17;
29+
30+
string ovn_trade_time = 18;
31+
string ovn_price = 19;
32+
string ovn_high = 20;
33+
string ovn_low = 21;
34+
string ovn_volume = 22;
35+
string ovn_change = 23;
36+
string ovn_change_ratio = 24;
2137
}
2238

2339
message Quote {

webull/data/quotes/subscribe/message_pb2.py

Lines changed: 12 additions & 12 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

webull/data/quotes/subscribe/snapshot_result.py

Lines changed: 78 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,40 @@
2121
class SnapshotResult:
2222
def __init__(self, pb_snapshot):
2323
self.basic = BasicResult(pb_snapshot.basic)
24+
self.last_trade_time = int(pb_snapshot.trade_time) if pb_snapshot.trade_time else None
25+
self.price = Decimal(pb_snapshot.price) if pb_snapshot.price else None
2426
self.open = Decimal(pb_snapshot.open) if pb_snapshot.open else None
2527
self.high = Decimal(pb_snapshot.high) if pb_snapshot.high else None
2628
self.low = Decimal(pb_snapshot.low) if pb_snapshot.low else None
27-
self.price = Decimal(pb_snapshot.price) if pb_snapshot.price else None
28-
self.pre_close = Decimal(
29-
pb_snapshot.pre_close) if pb_snapshot.pre_close else None
30-
self.volume = Decimal(
31-
pb_snapshot.volume) if pb_snapshot.volume else None
32-
self.change = Decimal(
33-
pb_snapshot.change) if pb_snapshot.change else None
34-
self.change_ratio = Decimal(
35-
pb_snapshot.change_ratio) if pb_snapshot.change_ratio else None
29+
self.pre_close = Decimal(pb_snapshot.pre_close) if pb_snapshot.pre_close else None
30+
self.close = Decimal(pb_snapshot.open) if pb_snapshot.open else None
31+
self.volume = Decimal(pb_snapshot.volume) if pb_snapshot.volume else None
32+
self.change = Decimal(pb_snapshot.change) if pb_snapshot.change else None
33+
self.change_ratio = Decimal(pb_snapshot.change_ratio) if pb_snapshot.change_ratio else None
34+
self.ext_trade_time = int(pb_snapshot.ext_trade_time) if pb_snapshot.ext_trade_time else None
35+
self.ext_price = Decimal(pb_snapshot.ext_price) if pb_snapshot.ext_price else None
36+
self.ext_high = Decimal(pb_snapshot.ext_high) if pb_snapshot.ext_high else None
37+
self.ext_low = Decimal(pb_snapshot.ext_low) if pb_snapshot.ext_low else None
38+
self.ext_volume = Decimal(pb_snapshot.ext_volume) if pb_snapshot.ext_volume else None
39+
self.ext_change = Decimal(pb_snapshot.ext_change) if pb_snapshot.ext_change else None
40+
self.ext_change_ratio = Decimal(pb_snapshot.ext_change_ratio) if pb_snapshot.ext_change_ratio else None
41+
self.ovn_trade_time = int(pb_snapshot.ovn_trade_time) if pb_snapshot.ovn_trade_time else None
42+
self.ovn_price = Decimal(pb_snapshot.ovn_price) if pb_snapshot.ovn_price else None
43+
self.ovn_high = Decimal(pb_snapshot.ovn_high) if pb_snapshot.ovn_high else None
44+
self.ovn_low = Decimal(pb_snapshot.ovn_low) if pb_snapshot.ovn_low else None
45+
self.ovn_volume = Decimal(pb_snapshot.ovn_volume) if pb_snapshot.ovn_volume else None
46+
self.ovn_change = Decimal(pb_snapshot.ovn_change) if pb_snapshot.ovn_change else None
47+
self.ovn_change_ratio = Decimal(pb_snapshot.ovn_change_ratio) if pb_snapshot.ovn_change_ratio else None
3648

3749
def get_basic(self):
3850
return self.basic
3951

52+
def get_last_trade_time(self):
53+
return self.last_trade_time
54+
55+
def get_price(self):
56+
return self.price
57+
4058
def get_open(self):
4159
return self.open
4260

@@ -46,12 +64,12 @@ def get_high(self):
4664
def get_low(self):
4765
return self.low
4866

49-
def get_price(self):
50-
return self.price
51-
5267
def get_pre_close(self):
5368
return self.pre_close
5469

70+
def get_close(self):
71+
return self.close
72+
5573
def get_volume(self):
5674
return self.volume
5775

@@ -61,9 +79,55 @@ def get_change(self):
6179
def get_change_ratio(self):
6280
return self.change_ratio
6381

82+
def get_ext_trade_time(self):
83+
return self.ext_trade_time
84+
85+
def get_ext_price(self):
86+
return self.ext_price
87+
88+
def get_ext_high(self):
89+
return self.ext_high
90+
91+
def get_ext_low(self):
92+
return self.ext_low
93+
94+
def get_ext_volume(self):
95+
return self.ext_volume
96+
97+
def get_ext_change(self):
98+
return self.ext_change
99+
100+
def get_ext_change_ratio(self):
101+
return self.ext_change_ratio
102+
103+
def get_ovn_trade_time(self):
104+
return self.ovn_trade_time
105+
106+
def get_ovn_price(self):
107+
return self.ovn_price
108+
109+
def get_ovn_high(self):
110+
return self.ovn_high
111+
112+
def get_ovn_low(self):
113+
return self.ovn_low
114+
115+
def get_ovn_volume(self):
116+
return self.ovn_volume
117+
118+
def get_ovn_change(self):
119+
return self.ovn_change
120+
121+
def get_ovn_change_ratio(self):
122+
return self.ovn_change_ratio
123+
64124
def __repr__(self):
65-
return "%s, open:%s, high:%s, low:%s, price:%s, pre_close:%s, volume:%s, change:%s, change_ratio:%s" \
66-
% (self.basic, self.open, self.high, self.low, self.price, self.pre_close, self.volume, self.change, self.change_ratio)
125+
attrs = ['last_trade_time', 'price', 'open', 'high', 'low', 'pre_close', 'close', 'volume', 'change', 'change_ratio']
126+
ext_attrs = [f"ext_{name}" for name in ['trade_time', 'price', 'high', 'low', 'volume', 'change', 'change_ratio']]
127+
ovn_attrs = [f"ovn_{name}" for name in ['trade_time', 'price', 'high', 'low', 'volume', 'change', 'change_ratio']]
128+
all_attrs = attrs + ext_attrs + ovn_attrs
129+
attr_str = ', '.join(f"{name}:{getattr(self, name)}" for name in all_attrs)
130+
return f"{self.basic}, {attr_str}"
67131

68132
def __str__(self):
69133
return self.__repr__()

webull/trade/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1 @@
1-
__version__ = '1.0.7'
1+
__version__ = '1.0.8'
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
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 json
15+
# coding=utf-8
16+
17+
from webull.core.request import ApiRequest
18+
19+
20+
class BatchPlaceOrderRequest(ApiRequest):
21+
def __init__(self):
22+
super().__init__("/openapi/trade/order/batch-place", version='v2', method="POST", body_params={})
23+
24+
def set_account_id(self, account_id):
25+
self.add_body_params("account_id", account_id)
26+
27+
def set_batch_orders(self, batch_orders):
28+
self.add_body_params("batch_orders", batch_orders)
29+
30+
def add_custom_headers_from_order(self, batch_orders):
31+
if not batch_orders:
32+
return
33+
34+
if isinstance(batch_orders, list) and batch_orders[0]:
35+
first_order = batch_orders[0]
36+
leg_list = first_order.get("legs")
37+
if leg_list is not None and isinstance(leg_list, list):
38+
for sub_leg in leg_list:
39+
if (sub_leg and isinstance(sub_leg, dict)
40+
and sub_leg.get("instrument_type") == "OPTION"):
41+
instrument_type = sub_leg.get("instrument_type")
42+
market = sub_leg.get("market")
43+
category = market + "_" + instrument_type
44+
if category is not None:
45+
self.add_header("category", category)
46+
return
47+
48+
market = first_order.get("market")
49+
instrument_type = first_order.get("instrument_type")
50+
category = market + "_" + instrument_type
51+
if category is not None:
52+
self.add_header("category", category)

0 commit comments

Comments
 (0)