Skip to content

Commit f025a52

Browse files
author
方佳
committed
Merge branch 'main_merge_260529' into 'main'
feat: Add option interface and add support for Webull AU and TH See merge request webull/webull-openapi-python-sdk!31
2 parents 54011a9 + 96483c8 commit f025a52

18 files changed

Lines changed: 654 additions & 19 deletions

‎README.md‎

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# Webull OpenAPI Python SDK
22

3-
Note: This is the new version of the Webull SDK, currently applicable only to Webull Hong Kong, Webull US, or Webull JP, Webull SG, or Webull TH, or Webull AU customers.
3+
Note: This is the new version of the Webull SDK, currently applicable only to Webull Hong Kong, Webull US, Webull JP, Webull SG, Webull TH, Webull AU, Webull MY, or Webull UK customers.
44

55
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.
66

@@ -26,7 +26,8 @@ Subscription to real-time information: Subscribe to order status changes, market
2626
| Webull SG | https://www.webull.com.sg/open-api-management|
2727
| Webull TH | https://www.webull.co.th/open-api-management |
2828
| Webull AU | https://www.webull.com.au/open-api-management |
29-
29+
| Webull MY | https://www.webull.com.my/open-api-management |
30+
| Webull UK | https://www.webull-uk.com/open-api-management |
3031
- Requires Python 3.7 through 3.11.
3132

3233
## Interface Protocol
@@ -49,3 +50,5 @@ The bottom layer of Webull OpenAPI provides three protocols, HTTP / GRPC / MQTT,
4950
| Webull SG | https://developer.webull.com.sg/apis/docs |
5051
| Webull TH | https://developer.webull.co.th/apis/docs |
5152
| Webull AU | https://developer.webull.com.au/apis/docs |
53+
| Webull MY | https://developer.webull.com.my/apis/docs |
54+
| Webull UK | https://developer.webull-uk.com/apis/docs |

‎samples/__init__.py‎

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

‎samples/data/data_client.py‎

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
your_app_key = "<your_app_key>"
2525
your_app_secret = "<your_app_secret>"
2626
region_id = "<region_id>"
27+
2728
# 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.
2829
# 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.
2930
# 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.
@@ -145,3 +146,21 @@
145146
res = data_client.market_data.get_noii_snapshot("AAPL", Category.US_STOCK.name, "PRE_CLOSE")
146147
if res.status_code == 200:
147148
print('get_noii_snapshot (closing):', res.json())
149+
150+
# Option market data - Historical bars
151+
# Rate limit: 1 request per second per App Key
152+
res = data_client.option_market_data.get_option_history_bars('AAPL260522C00300000', Category.US_OPTION.name, Timespan.M1.name)
153+
if res.status_code == 200:
154+
print('get_option_history_bars:', res.json())
155+
156+
# Option market data - Tick
157+
# Rate limit: 60 requests per minute
158+
res = data_client.option_market_data.get_option_tick("AAPL260522C00300000", Category.US_OPTION.name, count=30)
159+
if res.status_code == 200:
160+
print('get_option_tick:', res.json())
161+
162+
# Option market data - Snapshot
163+
# Rate limit: 60 requests per minute
164+
res = data_client.option_market_data.get_option_snapshot("AAPL260522C00300000", Category.US_OPTION.name)
165+
if res.status_code == 200:
166+
print('get_option_snapshot:', res.json())

‎samples/trade/trade_client_v3.py‎

Lines changed: 328 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -529,3 +529,331 @@
529529
res = trade_client.order_v3.get_order_detail(account_id, alog_client_order_id)
530530
if res.status_code == 200:
531531
print('get order detail res:', res.json())
532+
533+
# ============================================================
534+
# Stop Loss Order Example (HK Market)
535+
# ============================================================
536+
stop_loss_client_order_id = uuid.uuid4().hex
537+
print('stop loss client order id:', stop_loss_client_order_id)
538+
new_stop_loss_orders = [
539+
{
540+
"combo_type": "NORMAL",
541+
"client_order_id": stop_loss_client_order_id,
542+
"symbol": "00700",
543+
"instrument_type": "EQUITY",
544+
"market": "HK",
545+
"order_type": "STOP_LOSS",
546+
"quantity": "200",
547+
"stop_price": "560.35",
548+
"support_trading_session": "CORE",
549+
"side": "BUY",
550+
"time_in_force": "DAY",
551+
"entrust_type": "QTY",
552+
"trigger_price_type": "PRICE"
553+
}
554+
]
555+
556+
res = trade_client.order_v3.place_order(account_id, new_stop_loss_orders)
557+
if res.status_code == 200:
558+
print('place stop loss order res:', res.json())
559+
sleep(5)
560+
561+
replace_stop_loss_orders = [
562+
{
563+
"client_order_id": stop_loss_client_order_id,
564+
"stop_price": "560.37",
565+
"quantity": "200"
566+
}
567+
]
568+
res = trade_client.order_v3.replace_order(account_id, replace_stop_loss_orders)
569+
if res.status_code == 200:
570+
print('replace stop loss order res:', res.json())
571+
sleep(5)
572+
573+
res = trade_client.order_v3.cancel_order(account_id, stop_loss_client_order_id)
574+
if res.status_code == 200:
575+
print('cancel stop loss order res:', res.json())
576+
577+
res = trade_client.order_v3.get_order_history(account_id)
578+
if res.status_code == 200:
579+
print('get order history res:', res.json())
580+
581+
res = trade_client.order_v3.get_order_detail(account_id, stop_loss_client_order_id)
582+
if res.status_code == 200:
583+
print('get stop loss order detail res:', res.json())
584+
585+
# ============================================================
586+
# Stop Loss Limit Order Example (HK Market)
587+
# ============================================================
588+
stop_loss_limit_client_order_id = uuid.uuid4().hex
589+
print('stop loss limit client order id:', stop_loss_limit_client_order_id)
590+
new_stop_loss_limit_orders = [
591+
{
592+
"combo_type": "NORMAL",
593+
"client_order_id": stop_loss_limit_client_order_id,
594+
"symbol": "00700",
595+
"instrument_type": "EQUITY",
596+
"market": "HK",
597+
"order_type": "STOP_LOSS_LIMIT",
598+
"quantity": "200",
599+
"stop_price": "560.35",
600+
"limit_price": "562.21",
601+
"support_trading_session": "CORE",
602+
"side": "BUY",
603+
"time_in_force": "DAY",
604+
"entrust_type": "QTY",
605+
"trigger_price_type": "PRICE"
606+
}
607+
]
608+
609+
res = trade_client.order_v3.place_order(account_id, new_stop_loss_limit_orders)
610+
if res.status_code == 200:
611+
print('place stop loss limit order res:', res.json())
612+
sleep(5)
613+
614+
replace_stop_loss_limit_orders = [
615+
{
616+
"client_order_id": stop_loss_limit_client_order_id,
617+
"stop_price": "560.39",
618+
"limit_price": "562.69",
619+
"quantity": "200"
620+
}
621+
]
622+
res = trade_client.order_v3.replace_order(account_id, replace_stop_loss_limit_orders)
623+
if res.status_code == 200:
624+
print('replace stop loss limit order res:', res.json())
625+
sleep(5)
626+
627+
res = trade_client.order_v3.cancel_order(account_id, stop_loss_limit_client_order_id)
628+
if res.status_code == 200:
629+
print('cancel stop loss limit order res:', res.json())
630+
631+
res = trade_client.order_v3.get_order_history(account_id)
632+
if res.status_code == 200:
633+
print('get order history res:', res.json())
634+
635+
res = trade_client.order_v3.get_order_detail(account_id, stop_loss_limit_client_order_id)
636+
if res.status_code == 200:
637+
print('get stop loss limit order detail res:', res.json())
638+
639+
# ============================================================
640+
# Touch (Market) Order Example (HK Market)
641+
# ============================================================
642+
touch_mkt_client_order_id = uuid.uuid4().hex
643+
print('touch mkt client order id:', touch_mkt_client_order_id)
644+
new_touch_mkt_orders = [
645+
{
646+
"combo_type": "NORMAL",
647+
"client_order_id": touch_mkt_client_order_id,
648+
"symbol": "00700",
649+
"instrument_type": "EQUITY",
650+
"market": "HK",
651+
"order_type": "TOUCH_MKT",
652+
"quantity": "200",
653+
"stop_price": "540.35",
654+
"support_trading_session": "CORE",
655+
"side": "BUY",
656+
"time_in_force": "DAY",
657+
"entrust_type": "QTY",
658+
"trigger_price_type": "PRICE"
659+
}
660+
]
661+
662+
res = trade_client.order_v3.place_order(account_id, new_touch_mkt_orders)
663+
if res.status_code == 200:
664+
print('place touch mkt order res:', res.json())
665+
sleep(5)
666+
667+
replace_touch_mkt_orders = [
668+
{
669+
"client_order_id": touch_mkt_client_order_id,
670+
"stop_price": "540.67",
671+
"quantity": "200",
672+
"trigger_price_type": "PRICE"
673+
}
674+
]
675+
res = trade_client.order_v3.replace_order(account_id, replace_touch_mkt_orders)
676+
if res.status_code == 200:
677+
print('replace touch mkt order res:', res.json())
678+
sleep(5)
679+
680+
res = trade_client.order_v3.cancel_order(account_id, touch_mkt_client_order_id)
681+
if res.status_code == 200:
682+
print('cancel touch mkt order res:', res.json())
683+
684+
res = trade_client.order_v3.get_order_history(account_id)
685+
if res.status_code == 200:
686+
print('get order history res:', res.json())
687+
688+
res = trade_client.order_v3.get_order_detail(account_id, touch_mkt_client_order_id)
689+
if res.status_code == 200:
690+
print('get touch mkt order detail res:', res.json())
691+
692+
# ============================================================
693+
# Touch Limit Order Example (HK Market)
694+
# ============================================================
695+
touch_limit_client_order_id = uuid.uuid4().hex
696+
print('touch limit client order id:', touch_limit_client_order_id)
697+
new_touch_limit_orders = [
698+
{
699+
"combo_type": "NORMAL",
700+
"client_order_id": touch_limit_client_order_id,
701+
"symbol": "00700",
702+
"instrument_type": "EQUITY",
703+
"market": "HK",
704+
"order_type": "TOUCH_MKT",
705+
"quantity": "200",
706+
"stop_price": "540.35",
707+
"limit_price": "535.35",
708+
"support_trading_session": "CORE",
709+
"side": "BUY",
710+
"time_in_force": "DAY",
711+
"entrust_type": "QTY",
712+
"trigger_price_type": "PRICE"
713+
}
714+
]
715+
716+
res = trade_client.order_v3.place_order(account_id, new_touch_limit_orders)
717+
if res.status_code == 200:
718+
print('place touch limit order res:', res.json())
719+
sleep(5)
720+
721+
replace_touch_limit_orders = [
722+
{
723+
"client_order_id": touch_limit_client_order_id,
724+
"stop_price": "540.67",
725+
"limit_price": "536.67",
726+
"quantity": "200",
727+
"trigger_price_type": "PRICE"
728+
}
729+
]
730+
res = trade_client.order_v3.replace_order(account_id, replace_touch_limit_orders)
731+
if res.status_code == 200:
732+
print('replace touch limit order res:', res.json())
733+
sleep(5)
734+
735+
res = trade_client.order_v3.cancel_order(account_id, touch_limit_client_order_id)
736+
if res.status_code == 200:
737+
print('cancel touch limit order res:', res.json())
738+
739+
res = trade_client.order_v3.get_order_history(account_id)
740+
if res.status_code == 200:
741+
print('get order history res:', res.json())
742+
743+
res = trade_client.order_v3.get_order_detail(account_id, touch_limit_client_order_id)
744+
if res.status_code == 200:
745+
print('get touch limit order detail res:', res.json())
746+
747+
# ============================================================
748+
# Trailing Stop Loss Order Example (HK Market)
749+
# ============================================================
750+
trailing_stop_loss_client_order_id = uuid.uuid4().hex
751+
print('trailing stop loss client order id:', trailing_stop_loss_client_order_id)
752+
new_trailing_stop_loss_orders = [
753+
{
754+
"combo_type": "NORMAL",
755+
"client_order_id": trailing_stop_loss_client_order_id,
756+
"symbol": "00700",
757+
"instrument_type": "EQUITY",
758+
"market": "HK",
759+
"order_type": "TRAILING_STOP_LOSS",
760+
"quantity": "200",
761+
"trailing_stop_step": "0.1",
762+
"trailing_type": "PERCENTAGE",
763+
"support_trading_session": "CORE",
764+
"side": "BUY",
765+
"time_in_force": "DAY",
766+
"entrust_type": "QTY",
767+
"trigger_price_type": "PRICE"
768+
}
769+
]
770+
771+
res = trade_client.order_v3.place_order(account_id, new_trailing_stop_loss_orders)
772+
if res.status_code == 200:
773+
print('place trailing stop loss order res:', res.json())
774+
sleep(5)
775+
776+
replace_trailing_stop_loss_orders = [
777+
{
778+
"client_order_id": trailing_stop_loss_client_order_id,
779+
"stop_price": "560.47",
780+
"quantity": "200",
781+
"trailing_stop_step": "0.15",
782+
"trailing_type": "PERCENTAGE",
783+
"trigger_price_type": "PRICE"
784+
}
785+
]
786+
res = trade_client.order_v3.replace_order(account_id, replace_trailing_stop_loss_orders)
787+
if res.status_code == 200:
788+
print('replace trailing stop loss order res:', res.json())
789+
sleep(5)
790+
791+
res = trade_client.order_v3.cancel_order(account_id, trailing_stop_loss_client_order_id)
792+
if res.status_code == 200:
793+
print('cancel trailing stop loss order res:', res.json())
794+
795+
res = trade_client.order_v3.get_order_history(account_id)
796+
if res.status_code == 200:
797+
print('get order history res:', res.json())
798+
799+
res = trade_client.order_v3.get_order_detail(account_id, trailing_stop_loss_client_order_id)
800+
if res.status_code == 200:
801+
print('get trailing stop loss order detail res:', res.json())
802+
803+
# ============================================================
804+
# Trailing Stop Loss Limit Order Example (HK Market)
805+
# ============================================================
806+
trailing_stop_loss_limit_client_order_id = uuid.uuid4().hex
807+
print('trailing stop loss limit client order id:', trailing_stop_loss_limit_client_order_id)
808+
new_trailing_stop_loss_limit_orders = [
809+
{
810+
"combo_type": "NORMAL",
811+
"client_order_id": trailing_stop_loss_limit_client_order_id,
812+
"symbol": "00700",
813+
"instrument_type": "EQUITY",
814+
"market": "HK",
815+
"order_type": "TRAILING_STOP_LOSS_LIMIT",
816+
"quantity": "200",
817+
"trailing_stop_step": "0.1",
818+
"trailing_limit_price": "580",
819+
"trailing_type": "PERCENTAGE",
820+
"support_trading_session": "CORE",
821+
"side": "BUY",
822+
"time_in_force": "DAY",
823+
"entrust_type": "QTY",
824+
"trigger_price_type": "PRICE"
825+
}
826+
]
827+
828+
res = trade_client.order_v3.place_order(account_id, new_trailing_stop_loss_limit_orders)
829+
if res.status_code == 200:
830+
print('place trailing stop loss limit order res:', res.json())
831+
sleep(5)
832+
833+
replace_trailing_stop_loss_limit_orders = [
834+
{
835+
"client_order_id": trailing_stop_loss_limit_client_order_id,
836+
"stop_price": "560.77",
837+
"quantity": "200",
838+
"trailing_stop_step": "0.15",
839+
"trailing_limit_price": "581",
840+
"trailing_type": "PERCENTAGE",
841+
"trigger_price_type": "PRICE"
842+
}
843+
]
844+
res = trade_client.order_v3.replace_order(account_id, replace_trailing_stop_loss_limit_orders)
845+
if res.status_code == 200:
846+
print('replace trailing stop loss limit order res:', res.json())
847+
sleep(5)
848+
849+
res = trade_client.order_v3.cancel_order(account_id, trailing_stop_loss_limit_client_order_id)
850+
if res.status_code == 200:
851+
print('cancel trailing stop loss limit order res:', res.json())
852+
853+
res = trade_client.order_v3.get_order_history(account_id)
854+
if res.status_code == 200:
855+
print('get order history res:', res.json())
856+
857+
res = trade_client.order_v3.get_order_detail(account_id, trailing_stop_loss_limit_client_order_id)
858+
if res.status_code == 200:
859+
print('get trailing stop loss limit order detail res:', res.json())

0 commit comments

Comments
 (0)