From 20eed09ffdef72428b28fedd98d204f3d9e5df17 Mon Sep 17 00:00:00 2001 From: Alex Kwiatkowski Date: Thu, 25 Jun 2026 23:53:36 -0700 Subject: [PATCH] fix: trade search Gateway endpoint - /trades/search -> /Trade/search --- src/project_x_py/client/trading.py | 26 +++++++++----- tests/client/test_trading.py | 19 ++++++----- tests/client/test_trading_legacy.py | 53 ++++++++++++++++------------- tests/conftest.py | 2 +- 4 files changed, 57 insertions(+), 43 deletions(-) diff --git a/src/project_x_py/client/trading.py b/src/project_x_py/client/trading.py index 9761784..7f7448d 100644 --- a/src/project_x_py/client/trading.py +++ b/src/project_x_py/client/trading.py @@ -257,20 +257,28 @@ async def search_trades( if start_date is None: start_date = end_date - timedelta(days=30) - # Prepare parameters - params = { + # Prepare request payload for Gateway API + payload = { "accountId": account_id, - "startDate": start_date.isoformat(), - "endDate": end_date.isoformat(), - "limit": limit, + "startTimestamp": start_date.isoformat(), + "endTimestamp": end_date.isoformat(), } if contract_id: - params["contractId"] = contract_id + payload["contractId"] = contract_id - response = await self._make_request("GET", "/trades/search", params=params) + response = await self._make_request("POST", "/Trade/search", data=payload) - if not response or not isinstance(response, list): + if response is None: + return [] + + if isinstance(response, list): + trades_data = response + elif isinstance(response, dict): + if not response.get("success", False): + return [] + trades_data = response.get("trades", []) + else: return [] - return [Trade(**trade) for trade in response] + return [Trade(**trade) for trade in trades_data[:limit]] diff --git a/tests/client/test_trading.py b/tests/client/test_trading.py index 6a48467..0ed63fe 100644 --- a/tests/client/test_trading.py +++ b/tests/client/test_trading.py @@ -275,13 +275,14 @@ async def test_search_trades_with_filters( # Check request parameters last_call = mock_httpx_client.request.call_args_list[-1] - params = last_call[1]["params"] + data = last_call[1]["json"] - assert params["accountId"] == 12345 - assert params["startDate"] == start_date.isoformat() - assert params["endDate"] == end_date.isoformat() - assert params["limit"] == 50 - assert params["contractId"] == "MGC" + assert last_call[1]["method"] == "POST" + assert last_call[1]["url"].endswith("/Trade/search") + assert data["accountId"] == 12345 + assert data["startTimestamp"] == start_date.isoformat() + assert data["endTimestamp"] == end_date.isoformat() + assert data["contractId"] == "MGC" @pytest.mark.asyncio async def test_search_trades_empty( @@ -346,14 +347,14 @@ async def test_search_trades_date_defaults( # Check default date parameters last_call = mock_httpx_client.request.call_args_list[-1] - params = last_call[1]["params"] + data = last_call[1]["json"] # Should have start date 30 days ago start_date = datetime.datetime.fromisoformat( - params["startDate"].replace("Z", "+00:00") + data["startTimestamp"].replace("Z", "+00:00") ) end_date = datetime.datetime.fromisoformat( - params["endDate"].replace("Z", "+00:00") + data["endTimestamp"].replace("Z", "+00:00") ) date_diff = end_date - start_date diff --git a/tests/client/test_trading_legacy.py b/tests/client/test_trading_legacy.py index a9815c6..204698e 100644 --- a/tests/client/test_trading_legacy.py +++ b/tests/client/test_trading_legacy.py @@ -275,7 +275,9 @@ async def test_search_trades_success(self, trading_client): simulated=False, ) - mock_response = [ + mock_response = { + "success": True, + "trades": [ { "id": 1, "accountId": 12345, @@ -304,7 +306,8 @@ async def test_search_trades_success(self, trading_client): "voided": False, "orderId": 101, }, - ] + ], + } trading_client._make_request.return_value = mock_response trades = await trading_client.search_trades() @@ -344,13 +347,12 @@ async def test_search_trades_with_date_range(self, trading_client): # Verify the request parameters trading_client._make_request.assert_called_once_with( - "GET", - "/trades/search", - params={ + "POST", + "/Trade/search", + data={ "accountId": 12345, - "startDate": start_date.isoformat(), - "endDate": end_date.isoformat(), - "limit": 100, + "startTimestamp": start_date.isoformat(), + "endTimestamp": end_date.isoformat(), }, ) @@ -366,7 +368,9 @@ async def test_search_trades_with_contract_filter(self, trading_client): simulated=False, ) - mock_response = [ + mock_response = { + "success": True, + "trades": [ { "id": 1, "accountId": 12345, @@ -380,7 +384,8 @@ async def test_search_trades_with_contract_filter(self, trading_client): "voided": False, "orderId": 102, } - ] + ], + } trading_client._make_request.return_value = mock_response trades = await trading_client.search_trades(contract_id="MNQ", limit=50) @@ -390,8 +395,8 @@ async def test_search_trades_with_contract_filter(self, trading_client): # Verify contract_id was included in request call_args = trading_client._make_request.call_args - assert call_args[1]["params"]["contractId"] == "MNQ" - assert call_args[1]["params"]["limit"] == 50 + assert call_args[1]["data"]["contractId"] == "MNQ" + assert len(trades) <= 50 @pytest.mark.asyncio async def test_search_trades_custom_account_id(self, trading_client): @@ -406,7 +411,7 @@ async def test_search_trades_custom_account_id(self, trading_client): # Verify the request used custom account ID call_args = trading_client._make_request.call_args - assert call_args[1]["params"]["accountId"] == custom_account_id + assert call_args[1]["data"]["accountId"] == custom_account_id @pytest.mark.asyncio async def test_search_trades_no_account(self, trading_client): @@ -441,10 +446,10 @@ async def test_search_trades_default_dates(self, trading_client): # Verify date range is approximately 30 days call_args = trading_client._make_request.call_args - params = call_args[1]["params"] + data = call_args[1]["data"] - start_date = datetime.datetime.fromisoformat(params["startDate"]) - end_date = datetime.datetime.fromisoformat(params["endDate"]) + start_date = datetime.datetime.fromisoformat(data["startTimestamp"]) + end_date = datetime.datetime.fromisoformat(data["endTimestamp"]) date_diff = end_date - start_date assert 29 <= date_diff.days <= 31 @@ -476,10 +481,10 @@ async def test_search_trades_with_start_date_only(self, trading_client): # Verify end_date defaulted to now call_args = trading_client._make_request.call_args - params = call_args[1]["params"] + data = call_args[1]["data"] - assert params["startDate"] == start_date.isoformat() - end_date = datetime.datetime.fromisoformat(params["endDate"]) + assert data["startTimestamp"] == start_date.isoformat() + end_date = datetime.datetime.fromisoformat(data["endTimestamp"]) assert end_date == mock_now @pytest.mark.asyncio @@ -503,10 +508,10 @@ async def test_search_trades_with_end_date_only(self, trading_client): # Verify start_date is 30 days before end_date call_args = trading_client._make_request.call_args - params = call_args[1]["params"] + data = call_args[1]["data"] - start_date = datetime.datetime.fromisoformat(params["startDate"]) - assert params["endDate"] == end_date.isoformat() + start_date = datetime.datetime.fromisoformat(data["startTimestamp"]) + assert data["endTimestamp"] == end_date.isoformat() date_diff = end_date - start_date assert 29 <= date_diff.days <= 31 @@ -559,8 +564,8 @@ async def test_search_trades_invalid_response_type(self, trading_client): simulated=False, ) - # Invalid response type (dict instead of list) - trading_client._make_request.return_value = {"trades": []} + # Invalid response type (string instead of list/dict) + trading_client._make_request.return_value = "invalid response" trades = await trading_client.search_trades() diff --git a/tests/conftest.py b/tests/conftest.py index d515822..390ee29 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -215,7 +215,7 @@ def mock_trades_data(): @pytest.fixture def mock_trades_response(mock_response, mock_trades_data): """Create a mock trades response.""" - return mock_response(json_data=mock_trades_data) + return mock_response(json_data={"success": True, "trades": mock_trades_data}) @pytest.fixture