Skip to content

[Bug]: list_market_positions stops after one page because /v1/market-positions returns grouped items #284

Description

@mayamika

SDK package and version

polymarket-client==0.7.1 (same code in 0.7.0)

Runtime

Python 3.14.5, uv 0.12.6

What happened?

/v1/market-positions returns one entry per outcome token with the rows nested inside, and the SDK models it that way — items are MetaMarketPosition (_internal/actions/data.py:330-359).

The generic offset paginator then decides whether a page is full by counting top-level items:

# _internal/pagination.py:104-129
has_more = len(items) >= page_size
next_cursor = encode_offset_cursor(...) if has_more else None

But len(items) here is the number of outcomes, not the number of rows: 2 for a binary market. At the endpoint maximum page_size=500 the check is 2 >= 500, so the first page always reports has_more=False and next_cursor=None.

The rows are reachable: limit and offset do apply inside each group. But with next_cursor=None there is no second page to ask for, so iter_items() and to_pandas(limit=None) return the first page and report no truncation. Async behaves the same.

Among offset-paginated specs this is the only grouped response; the flat ones are unaffected. It looks like a leftover from #192/#205: len(items) >= page_size is correct for flat responses, but here the item count is decoupled from the row count.

Minimal reproduction

from unittest.mock import patch

from polymarket._internal.actions.data import list_market_positions_spec
from polymarket._internal.dispatch import sync_paginate_offset

TOTAL = 1200  # positions per outcome, above the endpoint maximum of 500


class GroupedTransport:
    def __init__(self) -> None:
        self.calls = []

    def get_json(self, path, params=None):
        self.calls.append(dict(params or {}))
        offset, limit = int(params["offset"]), int(params["limit"])
        window = range(offset, min(offset + limit, TOTAL))
        return [
            {"token": token, "positions": [{"size": "1"} for _ in window]}
            for token in ("111", "222")
        ]


transport = GroupedTransport()
spec = list_market_positions_spec(market="0x" + "ab" * 32, status="OPEN")

with patch("polymarket._internal.dispatch._sync_transport_for", return_value=transport):
    groups = list(sync_paginate_offset(object(), spec, page_size=500).iter_items())

print(len(transport.calls))                    # 1, expected 3
print([len(g.positions) for g in groups])      # [500, 500], expected [1200, 1200]

Expected behavior, actual behavior, or logs

Expected: three requests (offset=0, 500, 1000) and all 1200 positions per outcome.

Actual: one request, 700 rows per outcome dropped silently, no truncation marker.

Possible fix: let OffsetPaginatedSpec carry a row-count function for the fullness check — len(items) by default, sum(len(g.positions or ()) for g in items) for this endpoint.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions