SDKs and Tools
簡介
Webull為專業客戶提供Python和Java的軟體開發套件(SDK),同時也提供一個基於網頁的機構投資人入口,方便客戶登入後查閱帳戶資金、持倉、訂單等資訊。該入口亦支援訂閱高級付費市場數據
SDK安裝
- Python
- Java
透過pip安裝(需 Python 3.8至3.11版本)
pip3 install --upgrade webull-openapi-python-sdk
需安裝 JDK 8 或更高版本
<dependency>
<groupId>com.webull.openapi</groupId>
<artifactId>webull-openapi-java-sdk</artifactId>
<version>1.0.3</version>
</dependency>
API位址說明
注意
HTTP API 位址用於標準的 HTTP 請求
交易消息推送位址用於即時推送通知,例如訂單狀態更新
行情數據消息推送位址用於即時行情數據更新
測試環境
HTTP API: api.sandbox.webull.hk
Trading message push: events-api.sandbox.webull.hk
Market data message push: data-api.sandbox.webull.hk
生產環境
HTTP API: api.webull.hk
Trading message push: events-api.webull.hk
Market data message push: data-api.webull.hk
呼叫測試 API
如何獲取測試環境的 App Key 和 App Secret:
- Individual
- Institution
您可以使用測試帳戶表中提供的測試帳戶進行測試。
首先,您需要在測試環境中於Webull Portal開設帳戶。開設帳戶後,您需要申請API訪問。一旦審核通過,註冊您的申請以生成App Key和App Secret。
詳情步驟,請參考API申請。
交易請求範例
- Python
- Java
import json
import unittest
import uuid
from time import sleep
from webull.core.client import ApiClient
from webull.data.common.category import Category
from webull.trade.trade_client import TradeClient
# 生產環境: api.webull.hk
# 測試環境: api.sandbox.webull.hk
optional_api_endpoint = "<api_endpoint>"
your_app_key = "<your_app_key>"
your_app_secret = "<your_app_secret>"
region_id = "hk"
account_id = "<your_account_id>"
api_client = ApiClient(your_app_key, your_app_secret, region_id)
api_client.add_endpoint(region_id, optional_api_endpoint)
if __name__ == '__main__':
trade_client = TradeClient(api_client)
res = trade_client.account_v2.get_account_list()
if res.status_code == 200:
print("account_list=" + json.dumps(res.json(), indent=4))
res = trade_client.account_v2.get_account_balance(account_id)
if res.status_code == 200:
print("account_balance=" + json.dumps(res.json(), indent=4))
res = trade_client.account_v2.get_account_position(account_id)
if res.status_code == 200:
print("account_position=" + json.dumps(res.json(), indent=4))
preview_orders = {
"symbol": "AAPL",
"instrument_type": "EQUITY",
"market": "US",
"order_type": "MARKET",
"quantity": "1",
"support_trading_session": "CORE",
"side": "BUY",
"time_in_force": "DAY",
"entrust_type": "QTY"
}
res = trade_client.order_v2.preview_order(account_id=account_id, preview_orders=preview_orders)
if res.status_code == 200:
print("preview_res=" + json.dumps(res.json(), indent=4))
client_order_id = uuid.uuid4().hex
new_orders = {
"client_order_id": client_order_id,
"symbol": "AAPL",
"instrument_type": "EQUITY",
"market": "US",
"order_type": "LIMIT",
"limit_price": "188",
"quantity": "1",
"support_trading_session": "CORE",
"side": "BUY",
"time_in_force": "DAY",
"entrust_type": "QTY",
}
# 可選的,沒有也可以發起請求
custom_headers_map = {"category": Category.US_STOCK.name}
trade_client.order_v2.add_custom_headers(custom_headers_map)
res = trade_client.order_v2.place_order(account_id=account_id, new_orders=new_orders)
trade_client.order_v2.remove_custom_headers()
if res.status_code == 200:
print("place_order_res=" + json.dumps(res.json(), indent=4))
sleep(5)
modify_orders = {
"client_order_id": client_order_id,
"quantity": "100",
"limit_price": "200"
}
res = trade_client.order_v2.replace_order(account_id=account_id, modify_orders=modify_orders)
if res.status_code == 200:
print("replace_order_res=" + json.dumps(res.json(), indent=4))
sleep(5)
res = trade_client.order_v2.cancel_order_v2(account_id=account_id, client_order_id=client_order_id)
if res.status_code == 200:
print("cancel_order_res=" + json.dumps(res.json(), indent=4))
res = trade_client.order_v2.get_order_history_request(account_id=account_id)
if res.status_code == 200:
print("order_history_res=" + json.dumps(res.json(), indent=4))
# 訂單明細
res = trade_client.order_v2.get_order_detail(account_id=account_id, client_order_id=client_order_id)
if res.status_code == 200:
print("order detail=" + json.dumps(res.json(), indent=4))
# 期權
# 對於期權,請使用 V2 查詢介面:api.order_v2.get_order_detail(account_id, client_order_id)。
client_order_id = uuid.uuid4().hex
option_new_orders = [
{
"client_order_id": client_order_id,
"combo_type": "NORMAL",
"order_type": "LIMIT",
"quantity": "1",
"limit_price": "11.25",
"option_strategy": "SINGLE",
"side": "BUY",
"time_in_force": "GTC",
"entrust_type": "QTY",
"orders": [
{
"side": "BUY",
"quantity": "1",
"symbol": "AAPL",
"strike_price": "249.0",
"init_exp_date": "2025-08-15",
"instrument_type": "OPTION",
"option_type": "CALL",
"market": "US"
}
]
}
]
# 預估訂單
res = trade_client.order_v2.preview_option(account_id, option_new_orders)
if res.status_code == 200:
print("preview option=" + json.dumps(res.json(), indent=4))
sleep(5)
#下單
# 可選的,沒有也可以發起請求
custom_headers_map = {"category": Category.US_OPTION.name}
trade_client.order_v2.add_custom_headers(custom_headers_map)
res = trade_client.order_v2.place_option(account_id, option_new_orders)
trade_client.order_v2.remove_custom_headers()
if res.status_code == 200:
print("place option=" + json.dumps(res.json(), indent=4))
sleep(5)
#修改訂單
option_modify_orders = [
{
"client_order_id": client_order_id,
"quantity": "2",
"limit_price": "11.3",
"orders": [
{
"client_order_id": client_order_id,
"quantity": "2"
}
]
}
]
res = trade_client.order_v2.replace_option(account_id, option_modify_orders)
if res.status_code == 200:
print("replace option=" + json.dumps(res.json(), indent=4))
sleep(5)
# 取消訂單
res = trade_client.order_v2.cancel_option(account_id, client_order_id)
if res.status_code == 200:
print("cancel option=" + json.dumps(res.json(), indent=4))
package com.webull.openapi;
import com.webull.openapi.core.common.Region;
import com.webull.openapi.core.common.dict.*;
import com.webull.openapi.core.http.HttpApiConfig;
import com.webull.openapi.core.logger.Logger;
import com.webull.openapi.core.logger.LoggerFactory;
import com.webull.openapi.core.utils.GUID;
import com.webull.openapi.trade.TradeClientV2;
import com.webull.openapi.trade.request.v2.*;
import com.webull.openapi.trade.response.v2.OrderHistory;
import com.webull.openapi.trade.response.v2.TradeOrderResponse;
import java.util.ArrayList;
import java.util.List;
public class OrderTradeClient {
private static final Logger logger = LoggerFactory.getLogger(OrderTradeClient.class);
public static void main(String[] args) throws InterruptedException {
OrderTradeClient orderTradeClient = new OrderTradeClient();
HttpApiConfig apiConfig = HttpApiConfig.builder()
.appKey("<your_app_key>") //<your_app_key>
.appSecret("<your_app_secret>") //<your_app_secret>
.regionId("hk") //<your_region_id> @see com.webull.openapi.core.common.Region
.endpoint("<webull_api_host>") //PRD env host: api.webull.hk. Test env host: api.sandbox.webull.hk
.build();
TradeClientV2 apiService = new TradeClientV2(apiConfig);
// Use getAccountList interface to get account info
String accountId = "#{accountId}"; //<your_account_id> from by Account Api
String clientOrderId = GUID.get();
//stock
// build place order params
TradeOrder tradeOrder = orderTradeClient.buildPlaceStockParams(clientOrderId);
// place order
TradeOrderResponse placeOrderResp = apiService.placeOrder(accountId,tradeOrder);
logger.info("Place order response: {}", placeOrderResp);
// get order detail
OrderHistory orderDetail = apiService.getOrderDetails(accountId,clientOrderId);
logger.info("Order details response: {}", orderDetail);
Thread.sleep(2000);
// replace order
TradeOrder modifyTradeOrder = orderTradeClient.buildReplaceOrderParams(clientOrderId);
TradeOrderResponse modifyOrderResponse = apiService.replaceOrder(accountId, modifyTradeOrder);
logger.info("Order modify response: {}", modifyOrderResponse);
// query order detail after replace order
OrderHistory orderDetail1 = apiService.getOrderDetails(accountId, clientOrderId);
logger.info("Order orderDetail response after replace order: {}", orderDetail1);
// cancel order
TradeOrder cancelOrder = new TradeOrder();
cancelOrder.setClientOrderId(clientOrderId);
TradeOrderResponse cancelOrderResponse = apiService.cancelOrder(accountId, cancelOrder);
logger.info("Order cancel order response: {}", cancelOrderResponse);
// query order detail after cancel order
OrderHistory orderDetail2 = apiService.getOrderDetails(accountId, clientOrderId);
logger.info("Order orderDetail response after cancel: {}", orderDetail2.getOrders().get(0).getStatus());
//option
clientOrderId = GUID.get();
// build place option order params
OptionOrder optionOrder = orderTradeClient.buildOptionPlaceParams(clientOrderId);
TradeOrderResponse tradeOrderResponse = apiService.placeOption(accountId, optionOrder);
logger.info("Place option order response: {}", tradeOrderResponse);
// get option order detail
OrderHistory orderDetails = apiService.getOrderDetails(accountId, clientOrderId);
logger.info("Order details response: {}", orderDetails);
OptionOrder replaceOptionOrder = orderTradeClient.buildReplaceOptionPlaceParams(clientOrderId);
TradeOrderResponse replaceResponse = apiService.replaceOption(accountId, replaceOptionOrder);
logger.info("Replace option order response: {}", replaceResponse);
// get option order detail
OrderHistory orderDetails2= apiService.getOrderDetails(accountId, clientOrderId);
logger.info("Order details response: {}", orderDetails2);
// cancel order
OptionOrder cancelOption = new OptionOrder();
cancelOption.setClientOrderId(clientOrderId);
TradeOrderResponse orderResponse = apiService.cancelOption(accountId, cancelOption);
logger.info("Option order cancel response: {}", orderResponse);
}
/**
* build your place order object
*
* @param clientOrderId
* @return
*/
private TradeOrder buildPlaceStockParams(String clientOrderId) {
TradeOrder tradeOrder = new TradeOrder();
List<TradeOrderItem> newOrders = new ArrayList<>();
TradeOrderItem placeOne = new TradeOrderItem();
placeOne.setClientOrderId(clientOrderId);
// WebullUS need set combo_type, because WebullUS support combo order
placeOne.setComboType(ComboType.NORMAL.name());
newOrders.add(placeOne);
placeOne.setSymbol("AAPL");
placeOne.setInstrumentType(InstrumentSuperType.EQUITY.name());
placeOne.setMarket(Region.us.name().toUpperCase());
placeOne.setOrderType(OrderType.LIMIT.name());
placeOne.setQuantity("1");
placeOne.setLimitPrice("100");
placeOne.setSupportTradingSession("Y");
placeOne.setSide(OrderSide.BUY.name());
placeOne.setTimeInForce(OrderTIF.DAY.name());
placeOne.setEntrustType(EntrustType.QTY.name());
tradeOrder.setNewOrders(newOrders);
return tradeOrder;
}
/**
* build your replace order params
* @param clientOrderId
* @return replace order object
*/
private TradeOrder buildReplaceOrderParams(String clientOrderId) {
TradeOrder replaceTradeOrder = new TradeOrder();
List<TradeOrderItem> modifyOrders = new ArrayList<>();
TradeOrderItem modifyOne = new TradeOrderItem();
modifyOne.setClientOrderId(clientOrderId);
modifyOne.setLimitPrice("25");
modifyOne.setQuantity("2");
modifyOrders.add(modifyOne);
replaceTradeOrder.setModifyOrders(modifyOrders);
return replaceTradeOrder;
}
/**
* build your option stock place params
* @param clientOrderId
* @return option order place params
*/
private OptionOrder buildOptionPlaceParams(String clientOrderId) {
// Options
OptionOrderItemLeg optionOrderItemLeg = new OptionOrderItemLeg();
optionOrderItemLeg.setSide(OrderSide.BUY.name());
optionOrderItemLeg.setQuantity("10");
optionOrderItemLeg.setSymbol("AAPL");
optionOrderItemLeg.setStrikePrice("280");
optionOrderItemLeg.setOptionExpireDate("2025-12-19");
optionOrderItemLeg.setInstrumentType(InstrumentSuperType.OPTION.name());
optionOrderItemLeg.setOptionType(OptionType.CALL.name());
optionOrderItemLeg.setMarket(Markets.US.name());
List<OptionOrderItemLeg> optionOrderItemLegList = new ArrayList<>();
optionOrderItemLegList.add(optionOrderItemLeg);
OptionOrderItem optionOrderItem = new OptionOrderItem();
optionOrderItem.setClientOrderId(clientOrderId);
optionOrderItem.setComboType(ComboType.NORMAL.name());
optionOrderItem.setOptionStrategy(OptionStrategy.SINGLE.name());
optionOrderItem.setSide(OrderSide.BUY.name());
optionOrderItem.setOrderType(OrderType.LIMIT.name());
optionOrderItem.setTimeInForce(OrderTIF.GTC.name());
optionOrderItem.setLimitPrice("20.5");
optionOrderItem.setQuantity("1");
optionOrderItem.setEntrustType(EntrustType.QTY.name());
optionOrderItem.setLegs(optionOrderItemLegList);
List<OptionOrderItem> optionOrderItemList = new ArrayList<>();
optionOrderItemList.add(optionOrderItem);
OptionOrder optionOrder = new OptionOrder();
optionOrder.setNewOrders(optionOrderItemList);
return optionOrder;
}
/**
* build your option stock place params
* @param clientOrderId
* @return option order place params
*/
private OptionOrder buildReplaceOptionPlaceParams(String clientOrderId) {
OptionOrder replaceOptionOrder = new OptionOrder();
replaceOptionOrder.setClientOrderId(clientOrderId);
List<OptionOrderItem> modifyOrders = new ArrayList<>();
replaceOptionOrder.setModifyOrders(modifyOrders);
OptionOrderItem optionOrderItem = new OptionOrderItem();
modifyOrders.add(optionOrderItem);
optionOrderItem.setClientOrderId(clientOrderId);
optionOrderItem.setQuantity("2");
return replaceOptionOrder;
}
}
行情範例(Http)
- Python
- Java
from webull.data.common.category import Category
from webull.data.common.timespan import Timespan
from webull.core.client import ApiClient
from webull.data.data_client import DataClient
# 生產環境: api.webull.hk
# 測試環境: api.sandbox.webull.hk
optional_api_endpoint = "<api_endpoint>"
your_app_key = "<your_app_key>"
your_app_secret = "<your_app_secret>"
region_id = "hk"
api_client = ApiClient(your_app_key, your_app_secret, region_id)
api_client.add_endpoint(region_id, optional_api_endpoint)
if __name__ == '__main__':
data_client = DataClient(api_client)
trading_sessions = ["PRE", "RTH", "ATH", "OVN"]
res = data_client.instrument.get_instrument("AAPL", Category.US_STOCK.name)
if res.status_code == 200:
print('get_instrument:', res.json())
res = data_client.market_data.get_snapshot('AAPL', Category.US_STOCK.name, extend_hour_required=True, overnight_required=True)
if res.status_code == 200:
print('get_snapshot:', res.json())
res = data_client.market_data.get_history_bar('AAPL', Category.US_STOCK.name, Timespan.M1.name)
if res.status_code == 200:
print('get_history_bar:', res.json())
res = data_client.market_data.get_batch_history_bar(['AAPL', 'TSLA'], Category.US_STOCK.name, Timespan.M1.name, 1)
if res.status_code == 200:
print('get_batch_history_bar:', res.json())
res = data_client.market_data.get_tick("AAPL", Category.US_STOCK.name, trading_sessions=trading_sessions)
if res.status_code == 200:
print('get_tick:', res.json())
res = data_client.market_data.get_quotes("AAPL", Category.US_STOCK.name, depth=1, overnight_required=True)
if res.status_code == 200:
print('get_quotes:', res.json())
package com.webull.openapi;
import com.webull.openapi.core.common.Region;
import com.webull.openapi.core.common.dict.Category;
import com.webull.openapi.core.common.dict.Timespan;
import com.webull.openapi.core.http.HttpApiConfig;
import com.webull.openapi.core.logger.Logger;
import com.webull.openapi.core.logger.LoggerFactory;
import com.webull.openapi.data.DataClient;
import com.webull.openapi.data.quotes.domain.*;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class MarketDataHttpDemo {
private static final Logger logger = LoggerFactory.getLogger(OrderTradeClient.class);
public static void main(String[] args) {
HttpApiConfig apiConfig = HttpApiConfig.builder()
.appKey("<your_app_key>") //<your_app_key>
.appSecret("<your_app_secret>") //<your_app_secret>
.regionId("hk") //<your_region_id> @see com.webull.openapi.core.common.Region
.endpoint("<webull_api_host>") //PRD env host: api.webull.hk. Test env host: api.sandbox.webull.hk
.build();
DataClient dataClient = new DataClient(apiConfig);
Set<String> symbols = new HashSet<>();
symbols.add("AAPL");
symbols.add("TSLA");
// instrument
List<Instrument> instruments = dataClient.getInstruments(symbols, Category.US_STOCK.name());
logger.info("GetInstruments response: {}", instruments);
//snapshot
List<Snapshot> snapshots = dataClient.getSnapshots(symbols, Category.US_STOCK.name(), true, true);
logger.info("GetSnapshots response: {}", snapshots);
//history bar
List<Bar> bars = dataClient.getBars("AAPL", Category.US_STOCK.name(), Timespan.M5.name(),10);
logger.info("GetBars response: {}", bars);
//batch history bars
BatchBarResponse batchBarResponse = dataClient.getBatchBars(new ArrayList<>(symbols), Category.US_STOCK.name(), Timespan.M5.name(),10);
logger.info("GetBatchBars response: {}", batchBarResponse);
// get tick
Tick tick = dataClient.getTicks("AAPL", Category.US_STOCK.name());
logger.info("GetTicks response: {}", tick);
//get quote
Quote quote = dataClient.getQuote("AAPL", Category.US_STOCK.name(), "1", true);
logger.info("GetQuote response: {}", quote);
}
}
行情範例(mqtt sync)
- Python
- Java
import logging
import uuid
from logging.handlers import TimedRotatingFileHandler
from webull.data.common.category import Category
from webull.data.common.subscribe_type import SubscribeType
from webull.data.data_streaming_client import DataStreamingClient
your_app_key = "</your_app_key>"
your_app_secret = "</your_app_secret>"
# 生產環境: api.webull.hk
# 測試環境: api.sandbox.webull.hk
optional_api_endpoint = "</optional_quotes_endpoint>"
# 生產環境: data-api.webull.hk
# 測試環境: data-api.sandbox.webull.hk
optional_quotes_endpoint = "</optional_quotes_endpoint>"
region_id = 'hk'
session_id = uuid.uuid4().hex
data_streaming_client = DataStreamingClient(your_app_key, your_app_secret, region_id, session_id,
http_host=optional_api_endpoint,
mqtt_host=optional_quotes_endpoint)
if __name__ == '__main__':
def my_connect_success_func(client, api_client, quotes_session_id):
print("connect success with session_id:%s" % quotes_session_id)
# 訂閱清單
symbols = ['00700']
sub_types = [SubscribeType.QUOTE.name, SubscribeType.SNAPSHOT.name, SubscribeType.TICK.name]
client.subscribe( symbols, Category.HK_STOCK.name, sub_types)
def my_quotes_message_func(client, topic, quotes):
print("receive message: topic:%s, quotes:%s" % (topic, quotes))
def my_subscribe_success_func(client, api_client, quotes_session_id):
print("subscribe success with session_id:%s" % quotes_session_id)
# 設定連接成功回調函數
data_streaming_client.on_connect_success = my_connect_success_func
# 設定行情接收回調函數
data_streaming_client.on_quotes_message = my_quotes_message_func
# 設定訂閱成功回調函數
data_streaming_client.on_subscribe_success = my_subscribe_success_func
# 同步模式,當前線程受阻
data_streaming_client.connect_and_loop_forever()
package com.webull.openapi;
import com.webull.openapi.core.common.Region;
import com.webull.openapi.core.common.dict.Category;
import com.webull.openapi.core.common.dict.SubscribeType;
import com.webull.openapi.core.execption.ClientException;
import com.webull.openapi.core.execption.ServerException;
import com.webull.openapi.core.logger.Logger;
import com.webull.openapi.core.logger.LoggerFactory;
import com.webull.openapi.core.serialize.JsonSerializer;
import com.webull.openapi.core.utils.GUID;
import com.webull.openapi.data.quotes.subsribe.IDataStreamingClient;
import com.webull.openapi.data.quotes.subsribe.message.MarketData;
import java.util.HashSet;
import java.util.Set;
public class MarketDataMqttSyncDemo {
private static final Logger logger = LoggerFactory.getLogger(MarketDataMqttSyncDemo.class);
private static final String APP_KEY = "<your_app_key>";
private static final String APP_SECRET = "<your_app_secret>";
// PRD env host: data-api.webull.hk. Test env host: data-api.sandbox.webull.hk
private static final String DATA_API_HOST = "<webull_api_host>";
//PRD env host: api.webull.hk. Test env host: api.sandbox.webull.hk
private static final String HTTP_API_HOST = "<webull_data_host>";
public static void main(String[] args) {
Set<String> symbols = new HashSet<>();
symbols.add("AAPL");
Set<String> subTypes = new HashSet<>();
subTypes.add(SubscribeType.SNAPSHOT.name());
subTypes.add(SubscribeType.QUOTE.name());
subTypes.add(SubscribeType.TICK.name());
try (IDataStreamingClient client = IDataStreamingClient.builder()
.appKey(APP_KEY)
.appSecret(APP_SECRET)
.sessionId(GUID.get())
.regionId(Region.hk.name())
.http_host(HTTP_API_HOST)
.mqtt_host(DATA_API_HOST)
.onMessage(MarketDataMqttSyncDemo::handleMarketData)
.addSubscription(symbols, Category.US_STOCK.name(), subTypes, "1", false)
.build()) {
// subscribe blocking.
subscribeBlocking(client);
} catch (ClientException ex) {
logger.error("Client error", ex);
} catch (ServerException ex) {
logger.error("Sever error", ex);
} catch (Exception ex) {
logger.error("Unknown error", ex);
}
}
private static void handleMarketData(MarketData marketData) {
// your code...
logger.info("Received market data: {}", JsonSerializer.toJson(marketData));
}
private static void subscribeBlocking(IDataStreamingClient client) {
client.connectBlocking();
logger.info("Connect completed.");
client.subscribeBlocking();
logger.info("Subscribe completed.");
}
}
行情範例(mqtt async)
- Python
- Java
import time
import uuid
from webull.data.common.category import Category
from webull.data.common.subscribe_type import SubscribeType
from webull.data.data_streaming_client import DataStreamingClient
your_app_key = "</your_app_key>"
your_app_secret = "</your_app_secret>"
# 生產環境: api.webull.hk
# 測試環境: api.sandbox.webull.hk
optional_api_endpoint = "</optional_quotes_endpoint>"
# 生產環境: data-api.webull.hk
# 測試環境: data-api.sandbox.webull.hk
optional_quotes_endpoint = "</optional_quotes_endpoint>"
region_id = 'hk'
session_id = uuid.uuid4().hex
data_streaming_client = DataStreamingClient(your_app_key, your_app_secret, region_id, session_id,
http_host=optional_api_endpoint,
mqtt_host=optional_quotes_endpoint)
if __name__ == '__main__':
def my_connect_success_func(client, api_client, quotes_session_id):
print("connect success with session_id:%s" % quotes_session_id)
# subscribe
symbols = ['00700']
sub_types = [SubscribeType.QUOTE.name, SubscribeType.SNAPSHOT.name, SubscribeType.TICK.name]
client.subscribe(symbols, Category.HK_STOCK.name, sub_types)
def my_quotes_message_func(client, topic, quotes):
print("receive message: topic:%s, quotes:%s" % (topic, quotes))
def my_subscribe_success_func(client, api_client, quotes_session_id):
print("subscribe success with session_id:%s" % quotes_session_id)
# 設定連接成功回調函數
data_streaming_client.on_connect_success = my_connect_success_func
# 設定行情接收回調函數
data_streaming_client.on_quotes_message = my_quotes_message_func
# 設定訂閱成功回調函數
data_streaming_client.on_subscribe_success = my_subscribe_success_func
# 異步模式,當前線程未受阻
data_streaming_client.connect_and_loop_start()
ticker = 60
print("will remove subscription after %s seconds..." % ticker)
time.sleep(ticker)
subscribe_success = data_streaming_client.get_subscribe_success()
quotes_session_id = data_streaming_client.get_session_id()
if subscribe_success:
print("start remove subscription...")
data_streaming_client.unsubscribe(unsubscribe_all=True)
print("remove subscription finish")
else:
print("Do not remove subscription, subscribe_success:%s", subscribe_success)
start_time = time.time()
wait_time = 1
while True:
elapsed = int(time.time() - start_time)
if elapsed >= ticker:
print("Wait completed, start subscribing...")
break
print("Waiting {} seconds before subscription... (elapsed {}s / {}s)".format(wait_time, elapsed, ticker))
time.sleep(wait_time)
# 訂閱
connect_success = data_streaming_client.get_connect_success()
if connect_success:
symbols = ['00700']
sub_types = [SubscribeType.QUOTE.name, SubscribeType.SNAPSHOT.name, SubscribeType.TICK.name]
data_streaming_client.subscribe(symbols, Category.HK_STOCK.name, sub_types)
print("add subscription...")
else:
print("Do not add subscription, connect_success:%s", connect_success)
print("will stop processing after %s seconds" % ticker)
time.sleep(ticker)
data_streaming_client.loop_stop()
print("processing done")
package com.webull.openapi.demo;
import com.webull.openapi.core.common.Region;
import com.webull.openapi.core.common.dict.Category;
import com.webull.openapi.core.common.dict.SubscribeType;
import com.webull.openapi.core.execption.ClientException;
import com.webull.openapi.core.execption.ServerException;
import com.webull.openapi.core.logger.Logger;
import com.webull.openapi.core.logger.LoggerFactory;
import com.webull.openapi.core.serialize.JsonSerializer;
import com.webull.openapi.core.utils.GUID;
import com.webull.openapi.data.quotes.subsribe.IDataStreamingClient;
import com.webull.openapi.data.quotes.subsribe.message.MarketData;
import java.util.HashSet;
import java.util.Set;
public class MarketDataMqttAsyncDemo {
private static final Logger logger = LoggerFactory.getLogger(MarketDataMqttAsyncDemo.class);
private static final String APP_KEY = "<your_app_key>";
private static final String APP_SECRET = "<your_app_secret>";
// PRD env host: data-api.webull.hk. Test env host: data-api.sandbox.webull.hk
private static final String DATA_API_HOST = "<webull_api_host>";
//PRD env host: api.webull.hk. Test env host: api.sandbox.webull.hk
private static final String HTTP_API_HOST = "<webull_data_host>";
public static void main(String[] args) {
Set<String> symbols = new HashSet<>();
symbols.add("AAPL");
Set<String> subTypes = new HashSet<>();
subTypes.add(SubscribeType.SNAPSHOT.name());
subTypes.add(SubscribeType.QUOTE.name());
subTypes.add(SubscribeType.TICK.name());
String category = Category.US_STOCK.name();
String depth = "1";
boolean overnightRequired = false;
try (IDataStreamingClient client = IDataStreamingClient.builder()
.appKey(APP_KEY)
.appSecret(APP_SECRET)
.sessionId(GUID.get())
.regionId(Region.hk.name())
.http_host(HTTP_API_HOST)
.mqtt_host(DATA_API_HOST)
.onMessage(MarketDataMqttAsyncDemo::handleMarketData)
.addSubscription(symbols, category, subTypes, depth, overnightRequired)
.build()) {
// connect
client.connectBlocking();
// subscribe asynchronously.
client.subscribeAsync();
// waiting to unsubscribe
long ticker = 30;
int waitTime = 1;
long startTime = System.currentTimeMillis();
while (true) {
long elapsed = (System.currentTimeMillis() - startTime) / 1000;
if (elapsed >= ticker) {
logger.info("Wait completed, start remove subscription...");
break;
}
logger.info("Waiting {} seconds before remove subscription... (elapsed {}s / {}s)", waitTime, elapsed, ticker);
Thread.sleep(waitTime * 1000L);
}
client.removeSubscriptionAsync(symbols, category, subTypes);
logger.info("Asynchronous call to cancel subscription succeeded.");
// waiting to subscribe
startTime = System.currentTimeMillis();
while (true) {
long elapsed = (System.currentTimeMillis() - startTime) / 1000;
if (elapsed >= ticker) {
logger.info("Wait completed, start subscribing...");
break;
}
logger.info("Waiting {} seconds before subscription... (elapsed {}s / {}s)", waitTime, elapsed, ticker);
Thread.sleep(waitTime * 1000L);
}
client.addSubscriptionAsync(symbols, category, subTypes, depth, overnightRequired);
logger.info("Asynchronous call to subscribe succeeded.");
// waiting to disconnect
startTime = System.currentTimeMillis();
while (true) {
long elapsed = (System.currentTimeMillis() - startTime) / 1000;
if (elapsed >= ticker) {
logger.info("Wait completed, start disconnect...");
break;
}
logger.info("Waiting {} seconds before disconnect... (elapsed {}s / {}s)", waitTime, elapsed, ticker);
Thread.sleep(waitTime * 1000L);
}
client.disconnectAsync();
logger.info("Asynchronous call to disconnect succeeded.");
} catch (ClientException ex) {
logger.error("Client error", ex);
} catch (ServerException ex) {
logger.error("Sever error", ex);
} catch (Exception ex) {
logger.error("Unknown error", ex);
}
}
private static void handleMarketData(MarketData marketData) {
// your code...
logger.info("Received market data: {}", JsonSerializer.toJson(marketData));
}
}
Sandbox環境測試帳號
個人对接API測試帳號信息如下:
| No. | Test Account ID | Test App Key | Test Secret Key |
|---|---|---|---|
| 1 | V4H6R3L4VRI33UQ4TGR2NM1VI9 | 4b2b7acd2bf0d30d8aea173fceefa238 | 840b4353a6a31ce3ab91e2f99a510272 |
| 2 | OGG4RRLC6EDE98HI920KRBVSKB | 42bd186fb65ea76de309d69cf12f024e | 29feb64b59d6b1b6b2d2aa8cea8a1b8d |
| 3 | 2DHSQ9B1DMPBFPMPFU2R5SDPB8 | 64fc722617af8b5ebb746f50a910e91f | a268416fc681d438533f9e9316bab576 |
意見反饋與交流
- 透過幫助中心聯繫我們:幫助中心
- 您可以通过Webull API服務郵箱地址聯繫我們的工作人員:webull-api-support@webull.com
- 官方WhatsApp群组
