SDKs and Tools
Introduction
Webull provides SDKs in both Python and Java for professional clients, and also offers a web-based institutional Portal for clients to log in, allowing them to view account funds, positions, orders, and more. The Portal also supports subscriptions to advanced paid market data.
SDK Installation
- Python
- Java
Install via pip(Python version 3.8 through 3.11 is required)
pip3 install --upgrade webull-openapi-python-sdk
JDK 8 or above needs to be installed.
<dependency>
<groupId>com.webull.openapi</groupId>
<artifactId>webull-openapi-java-sdk</artifactId>
<version>1.0.3</version>
</dependency>
API Host
The HTTP API address is used for standard HTTP requests.
The trading message push address is used for real-time push notifications such as order status updates.
The market data message push address is used for real-time market data updates.
Test Environment
HTTP API: api.sandbox.webull.hk
Trading message push: events-api.sandbox.webull.hk
Market data message push: data-api.sandbox.webull.hk
Production Environment
HTTP API: api.webull.hk
Trading message push: events-api.webull.hk
Market data message push: data-api.webull.hk
Calling the Test API
How to obtain Test Environment App Key and App Secret:
- Individual
- Institution
You may use the test accounts provided in the Test Accounts table for testing.
First, you need to open an account on the Webull Portal in the test environment. After account opening, you must apply for API access and contact a Webull product manager for review. Once approved, register your application to generate the App Key and App Secret.
For detailed steps, refer to API Application.
Trade Request Example
- 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
# PRD env host: api.webull.hk
# Test env host: 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",
}
# This is an optional feature; you can still make a request without setting it.
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))
# order detail
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))
# Options
# For option order inquiries, please use the V2 query interface: 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"
}
]
}
]
# preview
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)
# place
# This is an optional feature; you can still make a request without setting it.
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)
# replace
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)
# cancel
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;
}
}
Market Data Example(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
# PRD env host: api.webull.hk
# Test env host: 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);
}
}
Market Data Example(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>"
# PRD env host: api.webull.hk
# Test env host: api.sandbox.webull.hk
optional_api_endpoint = "</optional_quotes_endpoint>"
# PRD env host: data-api.webull.hk
# Test env host: 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)
# set connect success callback func
data_streaming_client.on_connect_success = my_connect_success_func
# set quotes receiving callback func
data_streaming_client.on_quotes_message = my_quotes_message_func
# set subscribe success callback func
data_streaming_client.on_subscribe_success = my_subscribe_success_func
# the sync mode, blocking in current thread
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.");
}
}
Market Data Example(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>"
# PRD env host: api.webull.hk
# Test env host: api.sandbox.webull.hk
optional_api_endpoint = "</optional_quotes_endpoint>"
# PRD env host: data-api.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)
# set connect success callback func
data_streaming_client.on_connect_success = my_connect_success_func
# set quotes receiving callback func
data_streaming_client.on_quotes_message = my_quotes_message_func
# set subscribe success callback func
data_streaming_client.on_subscribe_success = my_subscribe_success_func
# the async mode, processing in another thread
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)
# subscribe
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 Test Accounts
The test account information for individual API integration is as follows:
| No. | Test Account ID | Test App Key | Test Secret Key |
|---|---|---|---|
| 1 | V4H6R3L4VRI33UQ4TGR2NM1VI9 | 4b2b7acd2bf0d30d8aea173fceefa238 | 840b4353a6a31ce3ab91e2f99a510272 |
| 2 | OGG4RRLC6EDE98HI920KRBVSKB | 42bd186fb65ea76de309d69cf12f024e | 29feb64b59d6b1b6b2d2aa8cea8a1b8d |
| 3 | 2DHSQ9B1DMPBFPMPFU2R5SDPB8 | 64fc722617af8b5ebb746f50a910e91f | a268416fc681d438533f9e9316bab576 |
Feedback and Communication
-
Troubleshoot issues or submit a ticket at our Support Center
-
You can contact our staff via the Webull API service email address: webull-api-support@webull.com
-
Official WhatsApp group