Skip to content

Commit e89e831

Browse files
committed
Add cancel_all, cancel_market_orders and post_only support
- Add _cancel_all_global() using http_client.cancel_all() - Add _cancel_market_orders() using http_client.cancel_market_orders() - Enable post_only orders with GTC/GTD time in force - Pass post_only parameter to post_order() and PostOrdersArgs - Update tests for new post_only behavior (GTC/GTD required) - Add tests for cancel_all_global and cancel_market_orders
1 parent 90059c0 commit e89e831

File tree

2 files changed

+434
-12
lines changed

2 files changed

+434
-12
lines changed

nautilus_trader/adapters/polymarket/execution.py

Lines changed: 105 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -958,6 +958,92 @@ async def _cancel_all_orders(self, command: CancelAllOrders) -> None:
958958
finally:
959959
await self._retry_manager_pool.release(retry_manager)
960960

961+
async def _cancel_all_global(self) -> None:
962+
"""
963+
Cancel all orders for this API key using Polymarket's cancel_all endpoint.
964+
965+
This cancels ALL orders across all markets and strategies. Use with caution as
966+
it cannot be filtered by instrument or strategy.
967+
968+
"""
969+
self._log.info("Canceling ALL orders globally via Polymarket cancel_all endpoint")
970+
971+
retry_manager = await self._retry_manager_pool.acquire()
972+
try:
973+
response: JSON | None = await retry_manager.run(
974+
"cancel_all_global",
975+
[],
976+
asyncio.to_thread,
977+
self._http_client.cancel_all,
978+
)
979+
if not response or not retry_manager.result:
980+
self._log.error(f"Failed to cancel all orders: {retry_manager.message}")
981+
else:
982+
canceled = response.get("canceled", [])
983+
not_canceled = response.get("not_canceled", {})
984+
self._log.info(
985+
f"Cancel all result: {len(canceled)} canceled, "
986+
f"{len(not_canceled)} not canceled",
987+
)
988+
for order_id, reason in not_canceled.items():
989+
self._log.warning(f"Order {order_id} not canceled: {reason}")
990+
finally:
991+
await self._retry_manager_pool.release(retry_manager)
992+
993+
async def _cancel_market_orders(
994+
self,
995+
instrument_id: InstrumentId | None = None,
996+
asset_id: str = "",
997+
) -> None:
998+
"""
999+
Cancel orders for a specific market using Polymarket's cancel_market_orders
1000+
endpoint.
1001+
1002+
Parameters
1003+
----------
1004+
instrument_id : InstrumentId, optional
1005+
The instrument ID to derive the market (condition_id).
1006+
asset_id : str, optional
1007+
The specific asset ID (token_id) to cancel orders for.
1008+
1009+
"""
1010+
from nautilus_trader.adapters.polymarket.common.symbol import get_polymarket_condition_id
1011+
from nautilus_trader.adapters.polymarket.common.symbol import get_polymarket_token_id
1012+
1013+
market = ""
1014+
if instrument_id is not None:
1015+
market = get_polymarket_condition_id(instrument_id)
1016+
if not asset_id:
1017+
asset_id = get_polymarket_token_id(instrument_id)
1018+
1019+
self._log.info(
1020+
f"Canceling orders for market={market or 'ALL'}, asset_id={asset_id or 'ALL'}",
1021+
)
1022+
1023+
retry_manager = await self._retry_manager_pool.acquire()
1024+
try:
1025+
response: JSON | None = await retry_manager.run(
1026+
"cancel_market_orders",
1027+
[instrument_id] if instrument_id else [],
1028+
asyncio.to_thread,
1029+
self._http_client.cancel_market_orders,
1030+
market,
1031+
asset_id,
1032+
)
1033+
if not response or not retry_manager.result:
1034+
self._log.error(f"Failed to cancel market orders: {retry_manager.message}")
1035+
else:
1036+
canceled = response.get("canceled", [])
1037+
not_canceled = response.get("not_canceled", {})
1038+
self._log.info(
1039+
f"Cancel market orders result: {len(canceled)} canceled, "
1040+
f"{len(not_canceled)} not canceled",
1041+
)
1042+
for order_id, reason in not_canceled.items():
1043+
self._log.warning(f"Order {order_id} not canceled: {reason}")
1044+
finally:
1045+
await self._retry_manager_pool.release(retry_manager)
1046+
9611047
async def _submit_order(self, command: SubmitOrder) -> None:
9621048
await self._maintain_active_market(command.instrument_id)
9631049

@@ -981,17 +1067,18 @@ async def _submit_order(self, command: SubmitOrder) -> None:
9811067
)
9821068
return
9831069

984-
if order.is_post_only:
1070+
# post_only orders only supported with GTC or GTD time_in_force
1071+
if order.is_post_only and order.time_in_force not in (TimeInForce.GTC, TimeInForce.GTD):
9851072
self._log.error(
9861073
f"Cannot submit order {order.client_order_id}: "
987-
"Post-only orders not supported on Polymarket",
1074+
"Post-only orders require GTC or GTD time in force",
9881075
LogColor.RED,
9891076
)
9901077
self.generate_order_denied(
9911078
strategy_id=order.strategy_id,
9921079
instrument_id=order.instrument_id,
9931080
client_order_id=order.client_order_id,
994-
reason="POST_ONLY_NOT_SUPPORTED",
1081+
reason="POST_ONLY_REQUIRES_GTC_OR_GTD",
9951082
ts_event=self._clock.timestamp_ns(),
9961083
)
9971084
return
@@ -1043,8 +1130,8 @@ def _validate_order_for_batch(self, order: Order) -> str | None:
10431130
if order.is_reduce_only:
10441131
return "REDUCE_ONLY_NOT_SUPPORTED"
10451132

1046-
if order.is_post_only:
1047-
return "POST_ONLY_NOT_SUPPORTED"
1133+
if order.is_post_only and order.time_in_force not in (TimeInForce.GTC, TimeInForce.GTD):
1134+
return "POST_ONLY_REQUIRES_GTC_OR_GTD"
10481135

10491136
if order.time_in_force not in VALID_POLYMARKET_TIME_IN_FORCE:
10501137
return "UNSUPPORTED_TIME_IN_FORCE"
@@ -1156,7 +1243,11 @@ async def _sign_orders_for_batch(
11561243

11571244
order_type = convert_tif_to_polymarket_order_type(order.time_in_force)
11581245
signed_orders_args.append(
1159-
PostOrdersArgs(order=signed_order, orderType=order_type),
1246+
PostOrdersArgs(
1247+
order=signed_order,
1248+
orderType=order_type,
1249+
postOnly=order.is_post_only,
1250+
),
11601251
)
11611252

11621253
interval = self._clock.timestamp() - signing_start
@@ -1356,9 +1447,14 @@ async def _submit_limit_order(self, command: SubmitOrder, instrument) -> None:
13561447
ts_event=self._clock.timestamp_ns(),
13571448
)
13581449

1359-
await self._post_signed_order(order, signed_order)
1450+
await self._post_signed_order(order, signed_order, post_only=order.is_post_only)
13601451

1361-
async def _post_signed_order(self, order: Order, signed_order) -> None:
1452+
async def _post_signed_order(
1453+
self,
1454+
order: Order,
1455+
signed_order,
1456+
post_only: bool = False,
1457+
) -> None:
13621458
retry_manager = await self._retry_manager_pool.acquire()
13631459
try:
13641460
response: JSON | None = await retry_manager.run(
@@ -1368,6 +1464,7 @@ async def _post_signed_order(self, order: Order, signed_order) -> None:
13681464
self._http_client.post_order,
13691465
signed_order,
13701466
convert_tif_to_polymarket_order_type(order.time_in_force),
1467+
post_only,
13711468
)
13721469
if not response or not response.get("success"):
13731470
self.generate_order_rejected(

0 commit comments

Comments
 (0)