Skip to content

Commit dc127e1

Browse files
author
方佳
committed
Merge branch 'main_old_260129_merge' into 'main'
feat: Add event contracts and US OpenAPI algorithm orders See merge request webull/webull-openapi-python-sdk!15
2 parents f84ef8a + 1d202eb commit dc127e1

28 files changed

Lines changed: 773 additions & 32 deletions

‎samples/data/data_client_event.py‎

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+
15+
# coding=utf-8
16+
17+
from webull.data.common.category import Category
18+
from webull.core.client import ApiClient
19+
from webull.data.data_client import DataClient
20+
21+
optional_api_endpoint = "<api_endpoint>"
22+
your_app_key = "<your_app_key>"
23+
your_app_secret = "<your_app_secret>"
24+
region_id = "<region_id>"
25+
# The token_dir parameter can be used to specify the directory for storing the 2FA token. Both absolute and relative paths are supported and this option has the highest priority.
26+
# Alternatively, the storage directory can be configured via an environment variable with the key WEBULL_OPENAPI_TOKEN_DIR, which also supports both absolute and relative paths.
27+
# If neither is specified, the default configuration will be used, and the token will be stored at conf/token.txt under the current working directory.
28+
# token_dir = "<your_token_dir>"
29+
# api_client.set_token_dir(token_dir)
30+
31+
api_client = ApiClient(your_app_key, your_app_secret, region_id)
32+
api_client.add_endpoint(region_id, optional_api_endpoint)
33+
34+
35+
if __name__ == '__main__':
36+
data_client = DataClient(api_client)
37+
38+
res = data_client.instrument.get_event_series("ECONOMICS")
39+
if res.status_code == 200:
40+
print('get_event_series:', res.json())
41+
42+
res = data_client.instrument.get_event_instrument("KXGDP")
43+
if res.status_code == 200:
44+
print('get_event_instrument:', res.json())
45+
46+
res = data_client.event_market_data.get_event_snapshot("KXRATECUTCOUNT-25DEC31-T10", Category.US_EVENT.name)
47+
if res.status_code == 200:
48+
print('get_event_snapshot:', res.json())
49+
50+
res = data_client.event_market_data.get_event_depth("KXRATECUTCOUNT-25DEC31-T10", Category.US_EVENT.name)
51+
if res.status_code == 200:
52+
print('get_event_depth:', res.json())
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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 uuid
19+
from logging.handlers import TimedRotatingFileHandler
20+
21+
from webull.data.common.category import Category
22+
from webull.data.common.subscribe_type import SubscribeType
23+
from webull.data.data_streaming_client import DataStreamingClient
24+
25+
your_app_key = "</your_app_key>"
26+
your_app_secret = "</your_app_secret>"
27+
optional_api_endpoint = "</optional_quotes_endpoint>"
28+
optional_quotes_endpoint = "</optional_quotes_endpoint>"
29+
region_id = '<region_id>'
30+
# The token_dir parameter can be used to specify the directory for storing the 2FA token. Both absolute and relative paths are supported and this option has the highest priority.
31+
# Alternatively, the storage directory can be configured via an environment variable with the key WEBULL_OPENAPI_TOKEN_DIR, which also supports both absolute and relative paths.
32+
# If neither is specified, the default configuration will be used, and the token will be stored at conf/token.txt under the current working directory.
33+
# token_dir = "<your_token_dir>"
34+
# data_streaming_client.set_token_dir(token_dir)
35+
36+
session_id = uuid.uuid4().hex
37+
data_streaming_client = DataStreamingClient(your_app_key, your_app_secret, region_id, session_id,
38+
http_host=optional_api_endpoint,
39+
mqtt_host=optional_quotes_endpoint)
40+
41+
if __name__ == '__main__':
42+
def my_connect_success_func(client, api_client, quotes_session_id):
43+
print("connect success with session_id:%s" % quotes_session_id)
44+
# subscribe
45+
symbols = ['KXNHLGAME-26JAN21ANACOL-ANA', 'KXNBAGAME-26JAN20LACCHI-LAC']
46+
sub_types = [SubscribeType.QUOTE.name, SubscribeType.SNAPSHOT.name]
47+
client.subscribe(symbols, Category.US_EVENT.name, sub_types)
48+
49+
def my_quotes_message_func(client, topic, quotes):
50+
print("receive message: topic:%s, quotes:%s" % (topic, quotes))
51+
52+
def my_subscribe_success_func(client, api_client, quotes_session_id):
53+
print("subscribe success with session_id:%s" % quotes_session_id)
54+
55+
56+
# set connect success callback func
57+
data_streaming_client.on_connect_success = my_connect_success_func
58+
# set quotes receiving callback func
59+
data_streaming_client.on_quotes_message = my_quotes_message_func
60+
# set subscribe success callback func
61+
data_streaming_client.on_subscribe_success = my_subscribe_success_func
62+
63+
# the sync mode, blocking in current thread
64+
data_streaming_client.connect_and_loop_forever()
65+
66+

‎samples/trade/trade_client_v3.py‎

Lines changed: 64 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -454,4 +454,67 @@
454454
# get futures order history
455455
res = trade_client.order_v3.get_order_history(account_id, page_size=10)
456456
if res.status_code == 200:
457-
print('get order history res:', res.json())
457+
print('get order history res:', res.json())
458+
459+
# ============================================================
460+
# Algo Order Example
461+
# ============================================================
462+
alog_client_order_id = uuid.uuid4().hex
463+
print('client order id:', alog_client_order_id)
464+
new_normal_equity_orders = [
465+
{
466+
"combo_type": "NORMAL",
467+
"client_order_id": alog_client_order_id,
468+
"symbol": "AAPL",
469+
"instrument_type": "EQUITY",
470+
"market": "US",
471+
"order_type": "LIMIT",
472+
"limit_price": "188",
473+
"quantity": "1",
474+
"support_trading_session": "N",
475+
"side": "BUY",
476+
"time_in_force": "DAY",
477+
"entrust_type": "QTY",
478+
"algo_start_time": "16:00:00",
479+
"algo_end_time": "23:00:00",
480+
"target_vol_percent": "10",
481+
"algo_type": "POV"
482+
}
483+
]
484+
485+
res = trade_client.order_v3.preview_order(account_id, new_normal_equity_orders)
486+
if res.status_code == 200:
487+
print('preview algo order res:', res.json())
488+
489+
res = trade_client.order_v3.place_order(account_id, new_normal_equity_orders)
490+
if res.status_code == 200:
491+
print('place algo order res:', res.json())
492+
sleep(3)
493+
494+
replace_normal_equity_orders = [
495+
{
496+
"client_order_id": alog_client_order_id,
497+
"quantity": "100",
498+
"limit_price": "200"
499+
}
500+
]
501+
res = trade_client.order_v3.replace_order(account_id, replace_normal_equity_orders)
502+
if res.status_code == 200:
503+
print('replace algo order res:', res.json())
504+
sleep(3)
505+
506+
res = trade_client.order_v3.cancel_order(account_id, alog_client_order_id)
507+
if res.status_code == 200:
508+
print('cancel algo order res:', res.json())
509+
510+
res = trade_client.order_v3.get_order_open(account_id=account_id)
511+
if res.status_code == 200:
512+
print("order_open_res=" + json.dumps(res.json(), indent=4))
513+
514+
res = trade_client.order_v3.get_order_history(account_id)
515+
if res.status_code == 200:
516+
print('get order history res:', res.json())
517+
518+
res = trade_client.order_v3.get_order_detail(account_id, alog_client_order_id)
519+
if res.status_code == 200:
520+
print('get order detail res:', res.json())
Lines changed: 102 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,102 @@
1+
# Copyright 2022 Webull
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import json
16+
import uuid
17+
from time import sleep
18+
19+
from webull.core.client import ApiClient
20+
from webull.trade.trade_client import TradeClient
21+
22+
optional_api_endpoint = "<api_endpoint>"
23+
your_app_key = "<your_app_key>"
24+
your_app_secret = "<your_app_secret>"
25+
region_id = "<region_id>"
26+
account_id = "<your_account_id>"
27+
# The token_dir parameter can be used to specify the directory for storing the 2FA token. Both absolute and relative paths are supported and this option has the highest priority.
28+
# Alternatively, the storage directory can be configured via an environment variable with the key WEBULL_OPENAPI_TOKEN_DIR, which also supports both absolute and relative paths.
29+
# If neither is specified, the default configuration will be used, and the token will be stored at conf/token.txt under the current working directory.
30+
# token_dir = "<your_token_dir>"
31+
# api_client.set_token_dir(token_dir)
32+
33+
api_client = ApiClient(your_app_key, your_app_secret, region_id)
34+
api_client.add_endpoint(region_id, optional_api_endpoint)
35+
36+
37+
if __name__ == '__main__':
38+
trade_client = TradeClient(api_client)
39+
40+
res = trade_client.account_v2.get_account_list()
41+
if res.status_code == 200:
42+
print('get account list:', res.json())
43+
44+
res = trade_client.account_v2.get_account_balance(account_id)
45+
if res.status_code == 200:
46+
print('get account balance res:', res.json())
47+
48+
res = trade_client.account_v2.get_account_position(account_id)
49+
if res.status_code == 200:
50+
print('get account position res:', res.json())
51+
52+
# normal event order
53+
normal_event_client_order_id = uuid.uuid4().hex
54+
print('event client order id:', normal_event_client_order_id)
55+
new_normal_event_orders = [
56+
{
57+
"combo_type": "NORMAL",
58+
"client_order_id": normal_event_client_order_id,
59+
"instrument_type": "EVENT",
60+
"market": "US",
61+
"symbol": "KXRATECUTCOUNT-25DEC31-T9",
62+
"order_type": "LIMIT",
63+
"entrust_type": "QTY",
64+
"time_in_force": "DAY",
65+
"side": "BUY",
66+
"quantity": "1",
67+
"limit_price": "0.1",
68+
"event_outcome": "yes"
69+
}
70+
]
71+
72+
res = trade_client.order_v3.preview_order(account_id, new_normal_event_orders)
73+
if res.status_code == 200:
74+
print('preview normal event order res:', res.json())
75+
76+
res = trade_client.order_v3.place_order(account_id, new_normal_event_orders)
77+
if res.status_code == 200:
78+
print('place normal event order res:', res.json())
79+
sleep(3)
80+
81+
# normal event order replace
82+
replace_normal_event_orders = [
83+
{
84+
"client_order_id": normal_event_client_order_id,
85+
"quantity": "10",
86+
"limit_price": "0.1"
87+
}
88+
]
89+
res = trade_client.order_v3.replace_order(account_id, replace_normal_event_orders)
90+
if res.status_code == 200:
91+
print('replace normal event order res:', res.json())
92+
sleep(3)
93+
94+
# normal event order cancel
95+
res = trade_client.order_v3.cancel_order(account_id, normal_event_client_order_id)
96+
if res.status_code == 200:
97+
print('cancel normal event order res:', res.json())
98+
99+
# get event order detail
100+
res = trade_client.order_v3.get_order_detail(account_id, normal_event_client_order_id)
101+
if res.status_code == 200:
102+
print('get event order detail res:', res.json())

‎samples/trade/trade_event_client.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414

1515
import logging
1616

17-
from webull.trade.events.types import ORDER_STATUS_CHANGED, EVENT_TYPE_ORDER
17+
from webull.trade.events.types import ORDER_STATUS_CHANGED, EVENT_TYPE_ORDER, EVENT_TYPE_POSITION, \
18+
POSITION_STATUS_CHANGED
1819
from webull.trade.trade_events_client import TradeEventsClient
1920

2021
your_app_key = "<your_app_key>"
@@ -32,6 +33,8 @@ def my_on_events_message(event_type, subscribe_type, payload, raw_message):
3233
if EVENT_TYPE_ORDER == event_type and ORDER_STATUS_CHANGED == subscribe_type:
3334
print('----request_id:%s----' % payload['request_id'])
3435
print(payload)
36+
if EVENT_TYPE_POSITION == event_type and POSITION_STATUS_CHANGED == subscribe_type:
37+
print('event payload:%s' % payload)
3538

3639
if __name__ == '__main__':
3740

‎webull/__init__.py‎

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

‎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.9'
1+
__version__ = '1.1.0'
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.9'
3+
__version__ = '1.1.0'

‎webull/data/common/category.py‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,3 +25,4 @@ class Category(EasyEnum):
2525
CN_STOCK = (7, "CN STOCK")
2626
US_CRYPTO = (8, "US CRYPTO")
2727
US_FUTURES = (12, "US FUTURES")
28+
US_EVENT = (13, "US EVENT")

‎webull/data/data_client.py‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@
1919

2020
from webull.core.http.initializer.client_initializer import ClientInitializer
2121
from webull.data.quotes.crypto_market_data import CryptoMarketData
22+
from webull.data.quotes.event_market_data import EventMarketData
2223
from webull.data.quotes.futures_market_data import FuturesMarketData
2324
from webull.data.quotes.instrument import Instrument
2425
from webull.data.quotes.market_data import MarketData
@@ -32,6 +33,7 @@ def __init__(self, api_client):
3233
self.market_data = MarketData(api_client)
3334
self.crypto_market_data = CryptoMarketData(api_client)
3435
self.futures_market_data = FuturesMarketData(api_client)
36+
self.event_market_data = EventMarketData(api_client)
3537

3638
def _init_logger(self, api_client):
3739
# No logger configured, using default console and local file logging.

0 commit comments

Comments
 (0)