diff --git a/cschwabpy/SchwabAsyncClient.py b/cschwabpy/SchwabAsyncClient.py index f43b353..3e737d9 100644 --- a/cschwabpy/SchwabAsyncClient.py +++ b/cschwabpy/SchwabAsyncClient.py @@ -213,6 +213,23 @@ async def get_instruments_async( if not self.__keep_client_alive: await client.aclose() + async def cancel_order_async( + self, account_number_hash: AccountNumberWithHashID, order_id: int + ) -> bool: + """Cancel an order by order ID.""" + await self._ensure_valid_access_token() + target_url = f"{SCHWAB_TRADER_API_BASE_URL}/accounts/{account_number_hash.hashValue}/orders/{order_id}" + client = httpx.AsyncClient() if self.__client is None else self.__client + try: + response = await client.delete( + url=target_url, params={}, headers=self.__auth_header() + ) + + return response.status_code == 200 + finally: + if not self.__keep_client_alive: + await client.aclose() + async def place_order_async( self, account_number_hash: AccountNumberWithHashID, order: Order ) -> int: diff --git a/cschwabpy/SchwabClient.py b/cschwabpy/SchwabClient.py index e2762fb..4539dc5 100644 --- a/cschwabpy/SchwabClient.py +++ b/cschwabpy/SchwabClient.py @@ -215,6 +215,23 @@ def get_instruments( if not self.__keep_client_alive: client.close() + def cancel_order( + self, account_number_hash: AccountNumberWithHashID, order_id: int + ) -> bool: + """Cancel an order by order ID.""" + self._ensure_valid_access_token() + target_url = f"{SCHWAB_TRADER_API_BASE_URL}/accounts/{account_number_hash.hashValue}/orders/{order_id}" + client = httpx.Client() if self.__client is None else self.__client + try: + response = client.delete( + url=target_url, params={}, headers=self.__auth_header() + ) + + return response.status_code == 200 + finally: + if not self.__keep_client_alive: + client.close() + def place_order( self, account_number_hash: AccountNumberWithHashID, order: Order ) -> int: diff --git a/tests/test_models.py b/tests/test_models.py index 95d77c3..e6bca15 100644 --- a/tests/test_models.py +++ b/tests/test_models.py @@ -234,6 +234,44 @@ async def test_place_order(httpx_mock: HTTPXMock): assert new_order_id2 == order_id +@pytest.mark.asyncio +async def test_cancel_order(httpx_mock: HTTPXMock): + mocked_token = mock_tokens() + token_store = LocalTokenStore() + if os.path.exists(Path(token_store.token_output_path)): + os.remove(token_store.token_output_path) # clean up before test + + order_id = 1000847830245 + token_store.save_tokens(mocked_token) + httpx_mock.add_response(status_code=200) + async with httpx.AsyncClient() as client: + cschwab_client = SchwabAsyncClient( + app_client_id="fake_id", + app_secret="fake_secret", + token_store=token_store, + tokens=mocked_token, + http_client=client, + ) + is_canceled = await cschwab_client.cancel_order_async( + account_number_hash=mock_account(), order_id=order_id + ) + assert is_canceled + + with httpx.Client() as client2: + cschwab_client = SchwabClient( + app_client_id="fake_id", + app_secret="fake_secret", + token_store=token_store, + http_client=client2, + ) + + is_canceled2 = cschwab_client.cancel_order( + account_number_hash=mock_account(), + order_id=order_id, + ) + assert is_canceled2 + + @pytest.mark.asyncio async def test_get_order_by_id(httpx_mock: HTTPXMock): json_mock = get_mock_response()["filled_order"]