diff --git a/README.md b/README.md index e482467..62891b4 100644 --- a/README.md +++ b/README.md @@ -402,6 +402,23 @@ do not amend them. | `portfolio.positions(params?)` | Get trading positions | | `portfolio.activities(params?)` | Get activity history | +Filter activity history by time and reward type: + +```python +activities = client.portfolio.activities( + { + "startTime": "2026-10-01T00:00:00Z", + "endTime": "2026-10-02T00:00:00Z", + "types": ["ACTIVITY_TYPE_TAKER_FEE_REBATE", "ACTIVITY_TYPE_LIQUIDITY_PROGRAM"], + } +) +``` + +Both time filters are optional RFC 3339 strings and include their endpoints. +When supplied together, `startTime` must be before `endTime`. For later pages, +pass `nextCursor` as `cursor` with the same filters until `eof` is true. +The async client accepts the same parameters with `await`. + ### Account (Authenticated) | Method | Description | diff --git a/polymarket_us/types/portfolio.py b/polymarket_us/types/portfolio.py index 7eabed6..7e1199e 100644 --- a/polymarket_us/types/portfolio.py +++ b/polymarket_us/types/portfolio.py @@ -14,6 +14,8 @@ "ACTIVITY_TYPE_ACCOUNT_WITHDRAWAL", "ACTIVITY_TYPE_REFERRAL_BONUS", "ACTIVITY_TYPE_TRANSFER", + "ACTIVITY_TYPE_TAKER_FEE_REBATE", + "ACTIVITY_TYPE_LIQUIDITY_PROGRAM", ] SortOrder = Literal["SORT_ORDER_DESCENDING", "SORT_ORDER_ASCENDING"] @@ -122,6 +124,8 @@ class GetActivitiesParams(TypedDict, total=False): marketSlug: str types: list[ActivityType] sortOrder: SortOrder + startTime: str + endTime: str class GetActivitiesResponse(TypedDict, total=False): diff --git a/pyproject.toml b/pyproject.toml index f66243f..48a8cdc 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "polymarket-us" -version = "2.4.1" +version = "2.4.2" description = "Polymarket US Python SDK" readme = "README.md" license = "MIT" diff --git a/tests/test_activity_history.py b/tests/test_activity_history.py new file mode 100644 index 0000000..d755ca6 --- /dev/null +++ b/tests/test_activity_history.py @@ -0,0 +1,67 @@ +"""Activity filters pass through both clients without losing timestamp or type values.""" + +from copy import deepcopy + +import pytest +from pytest_httpx import HTTPXMock + +from polymarket_us import AsyncPolymarketUS, PolymarketUS +from polymarket_us.types import GetActivitiesParams, GetActivitiesResponse +from tests.types.activity_history import activities, params + +TEST_SECRET_KEY = "nWGxne/9WmC6hEr0kuwsxERJxWl7MmkZcDusAxyuf2A=" + +QUERIES: list[GetActivitiesParams | None] = [ + None, + { + "startTime": "2026-10-01T00:00:00.123456789Z", + "types": ["ACTIVITY_TYPE_TAKER_FEE_REBATE"], + }, + { + "endTime": "2026-10-02T00:00:00.123456789+05:30", + "types": ["ACTIVITY_TYPE_LIQUIDITY_PROGRAM"], + }, + params, +] + + +@pytest.mark.parametrize("asynchronous", [False, True], ids=["sync", "async"]) +@pytest.mark.parametrize("query", QUERIES, ids=["omitted", "start-only", "end-only", "combined"]) +async def test_activity_history_filters( + httpx_mock: HTTPXMock, asynchronous: bool, query: GetActivitiesParams | None +) -> None: + response: GetActivitiesResponse = { + "activities": activities, + "nextCursor": "next+/=cursor", + "eof": False, + } + httpx_mock.add_response(json=response) + original_query = deepcopy(query) + + if asynchronous: + async with AsyncPolymarketUS( + key_id="offline", secret_key=TEST_SECRET_KEY, api_base_url="https://api.test" + ) as client: + result = await client.portfolio.activities(query) + else: + with PolymarketUS( + key_id="offline", secret_key=TEST_SECRET_KEY, api_base_url="https://api.test" + ) as sync_client: + result = sync_client.portfolio.activities(query) + + assert result == response + assert query == original_query + requests = httpx_mock.get_requests() + assert len(requests) == 1 + request = requests[0] + assert request.method == "GET" + assert request.url.host == "api.test" + assert request.url.path == "/v1/portfolio/activities" + assert request.content == b"" + assert request.headers["X-PM-Access-Key"] == "offline" + expected = original_query or {} + assert set(request.url.params) == set(expected) + for key, value in expected.items(): + assert request.url.params.get_list(key) == ( + value if isinstance(value, list) else [str(value)] + ) diff --git a/tests/types/activity_history.py b/tests/types/activity_history.py new file mode 100644 index 0000000..d2c44a5 --- /dev/null +++ b/tests/types/activity_history.py @@ -0,0 +1,27 @@ +"""Typed consumers of activity time filters and reward categories.""" + +from polymarket_us import AsyncPolymarketUS, PolymarketUS +from polymarket_us.types import Activity, ActivityType, GetActivitiesParams, GetActivitiesResponse + +reward_types: list[ActivityType] = [ + "ACTIVITY_TYPE_TAKER_FEE_REBATE", + "ACTIVITY_TYPE_LIQUIDITY_PROGRAM", +] +params: GetActivitiesParams = { + "startTime": "2026-10-01T00:00:00.123456789Z", + "endTime": "2026-10-02T00:00:00.123456789+00:00", + "types": reward_types, + "limit": 2, + "cursor": "opaque+/=cursor", + "marketSlug": "example-market", + "sortOrder": "SORT_ORDER_ASCENDING", +} +activities: list[Activity] = [{"type": value} for value in reward_types] + + +def history(client: PolymarketUS) -> GetActivitiesResponse: + return client.portfolio.activities(params) + + +async def async_history(client: AsyncPolymarketUS) -> GetActivitiesResponse: + return await client.portfolio.activities(params)