Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions examples/market_prices.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,8 +37,8 @@ def main() -> None:
"buyPrice": buy_price,
"midpoint": midpoint,
"spread": spread,
"lastTradePrice": last_trade.price,
"lastTradeSide": last_trade.side,
"lastTradePrice": last_trade.price if last_trade is not None else "N/A",
"lastTradeSide": last_trade.side if last_trade is not None else "N/A",
}
)

Expand Down
19 changes: 16 additions & 3 deletions src/polymarket/_internal/actions/clob.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
from collections.abc import Sequence
from decimal import Decimal
from typing import Annotated, cast
from typing import Annotated, Literal, cast

from pydantic import BeforeValidator, TypeAdapter, ValidationError, field_validator

Expand Down Expand Up @@ -47,6 +47,16 @@ def _parse_spread(cls, value: object) -> object:
return parse_decimal_string(value)


class _LastTradePriceResponse(BaseModel):
price: Decimal
side: OrderSide | Literal[""]

@field_validator("price", mode="before")
@classmethod
def _parse_price(cls, value: object) -> object:
return parse_decimal_string(value)


_PRICE_HISTORY_INTERVALS: frozenset[str] = frozenset({"max", "1w", "1d", "6h", "1h"})
_VALID_ORDER_SIDES: frozenset[str] = frozenset({"BUY", "SELL"})

Expand Down Expand Up @@ -200,8 +210,11 @@ def build_last_trade_price_request(*, token_id: str) -> tuple[str, dict[str, str
return "/last-trade-price", {"token_id": _require_string_token_id(token_id)}


def parse_last_trade_price(data: object) -> LastTradePrice:
return LastTradePrice.parse_response(data)
def parse_last_trade_price(data: object) -> LastTradePrice | None:
response = _LastTradePriceResponse.parse_response(data)
if response.side == "":
return None
return LastTradePrice(price=response.price, side=response.side)


def build_last_trade_prices_request(
Expand Down
13 changes: 10 additions & 3 deletions src/polymarket/clients/async_public.py
Original file line number Diff line number Diff line change
Expand Up @@ -1273,8 +1273,11 @@ async def get_spreads(self, *, token_ids: Sequence[str]) -> dict[TokenId, Decima
path, body = _clob_actions.build_spreads_request(token_ids=token_ids)
return _clob_actions.parse_spreads(await self._ctx.clob.post_json(path, json=body))

async def get_last_trade_price(self, *, token_id: str) -> LastTradePrice:
"""Get the most recent trade price for a token."""
async def get_last_trade_price(self, *, token_id: str) -> LastTradePrice | None:
"""Get the most recent trade price for a token.

Returns ``None`` when the token has not traded.
"""
path, params = _clob_actions.build_last_trade_price_request(token_id=token_id)
return _clob_actions.parse_last_trade_price(
await self._ctx.clob.get_json(path, params=params)
Expand All @@ -1283,7 +1286,11 @@ async def get_last_trade_price(self, *, token_id: str) -> LastTradePrice:
async def get_last_trade_prices(
self, *, token_ids: Sequence[str]
) -> tuple[LastTradePriceForToken, ...]:
"""Get the most recent trade prices for multiple tokens."""
"""Get the most recent trade prices for multiple tokens.

Tokens without trades are omitted. Match returned entries by ``token_id``;
the result is not positionally aligned with ``token_ids``.
"""
path, body = _clob_actions.build_last_trade_prices_request(token_ids=token_ids)
return _clob_actions.parse_last_trade_prices(
await self._ctx.clob.post_json(path, json=body)
Expand Down
13 changes: 10 additions & 3 deletions src/polymarket/clients/async_secure.py
Original file line number Diff line number Diff line change
Expand Up @@ -1867,8 +1867,11 @@ async def get_spreads(self, *, token_ids: Sequence[str]) -> dict[TokenId, Decima
path, body = _clob_actions.build_spreads_request(token_ids=token_ids)
return _clob_actions.parse_spreads(await self._ctx.clob.post_json(path, json=body))

async def get_last_trade_price(self, *, token_id: str) -> LastTradePrice:
"""Get the most recent trade price for a token."""
async def get_last_trade_price(self, *, token_id: str) -> LastTradePrice | None:
"""Get the most recent trade price for a token.

Returns ``None`` when the token has not traded.
"""
path, params = _clob_actions.build_last_trade_price_request(token_id=token_id)
return _clob_actions.parse_last_trade_price(
await self._ctx.clob.get_json(path, params=params)
Expand All @@ -1877,7 +1880,11 @@ async def get_last_trade_price(self, *, token_id: str) -> LastTradePrice:
async def get_last_trade_prices(
self, *, token_ids: Sequence[str]
) -> tuple[LastTradePriceForToken, ...]:
"""Get the most recent trade prices for multiple tokens."""
"""Get the most recent trade prices for multiple tokens.

Tokens without trades are omitted. Match returned entries by ``token_id``;
the result is not positionally aligned with ``token_ids``.
"""
path, body = _clob_actions.build_last_trade_prices_request(token_ids=token_ids)
return _clob_actions.parse_last_trade_prices(
await self._ctx.clob.post_json(path, json=body)
Expand Down
13 changes: 10 additions & 3 deletions src/polymarket/clients/public.py
Original file line number Diff line number Diff line change
Expand Up @@ -1069,15 +1069,22 @@ def get_spreads(self, *, token_ids: Sequence[str]) -> dict[TokenId, Decimal]:
path, body = _clob_actions.build_spreads_request(token_ids=token_ids)
return _clob_actions.parse_spreads(self._ctx.clob.post_json(path, json=body))

def get_last_trade_price(self, *, token_id: str) -> LastTradePrice:
"""Get the most recent trade price for a token."""
def get_last_trade_price(self, *, token_id: str) -> LastTradePrice | None:
"""Get the most recent trade price for a token.

Returns ``None`` when the token has not traded.
"""
path, params = _clob_actions.build_last_trade_price_request(token_id=token_id)
return _clob_actions.parse_last_trade_price(self._ctx.clob.get_json(path, params=params))

def get_last_trade_prices(
self, *, token_ids: Sequence[str]
) -> tuple[LastTradePriceForToken, ...]:
"""Get the most recent trade prices for multiple tokens."""
"""Get the most recent trade prices for multiple tokens.

Tokens without trades are omitted. Match returned entries by ``token_id``;
the result is not positionally aligned with ``token_ids``.
"""
path, body = _clob_actions.build_last_trade_prices_request(token_ids=token_ids)
return _clob_actions.parse_last_trade_prices(self._ctx.clob.post_json(path, json=body))

Expand Down
13 changes: 10 additions & 3 deletions src/polymarket/clients/secure.py
Original file line number Diff line number Diff line change
Expand Up @@ -1393,15 +1393,22 @@ def get_spreads(self, *, token_ids: Sequence[str]) -> dict[TokenId, Decimal]:
path, body = _clob_actions.build_spreads_request(token_ids=token_ids)
return _clob_actions.parse_spreads(self._ctx.clob.post_json(path, json=body))

def get_last_trade_price(self, *, token_id: str) -> LastTradePrice:
"""Get the most recent trade price for a token."""
def get_last_trade_price(self, *, token_id: str) -> LastTradePrice | None:
"""Get the most recent trade price for a token.

Returns ``None`` when the token has not traded.
"""
path, params = _clob_actions.build_last_trade_price_request(token_id=token_id)
return _clob_actions.parse_last_trade_price(self._ctx.clob.get_json(path, params=params))

def get_last_trade_prices(
self, *, token_ids: Sequence[str]
) -> tuple[LastTradePriceForToken, ...]:
"""Get the most recent trade prices for multiple tokens."""
"""Get the most recent trade prices for multiple tokens.

Tokens without trades are omitted. Match returned entries by ``token_id``;
the result is not positionally aligned with ``token_ids``.
"""
path, body = _clob_actions.build_last_trade_prices_request(token_ids=token_ids)
return _clob_actions.parse_last_trade_prices(self._ctx.clob.post_json(path, json=body))

Expand Down
2 changes: 1 addition & 1 deletion tests/integration/test_clob_reads.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,7 +152,7 @@ async def run() -> dict[TokenId, Decimal]:

@pytest.mark.integration
def test_async_get_last_trade_price_returns_model(active_clob_token: TokenId) -> None:
async def run() -> LastTradePrice:
async def run() -> LastTradePrice | None:
async with AsyncPublicClient() as client:
return await client.get_last_trade_price(token_id=active_clob_token)

Expand Down
10 changes: 9 additions & 1 deletion tests/unit/test_clob_actions.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
parse_spreads,
)
from polymarket.errors import UnexpectedResponseError, UserInputError
from polymarket.models import OrderSide, PriceRequest, TokenId
from polymarket.models import LastTradePrice, OrderSide, PriceRequest, TokenId


def test_build_midpoint_request_targets_midpoint_path_with_token_id() -> None:
Expand Down Expand Up @@ -496,10 +496,18 @@ def test_build_last_trade_price_request_targets_last_trade_price_path() -> None:
def test_parse_last_trade_price_returns_model() -> None:
result = parse_last_trade_price({"price": "0.53", "side": "BUY"})

assert result is not None
assert result.price == Decimal("0.53")
assert result.side == "BUY"


def test_parse_last_trade_price_returns_none_without_trades() -> None:
result = parse_last_trade_price({"price": "0.5", "side": ""})

assert_type(result, LastTradePrice | None)
assert result is None


def test_parse_last_trade_price_rejects_numeric_price() -> None:
with pytest.raises(UnexpectedResponseError):
parse_last_trade_price({"price": 0.53, "side": "BUY"})
Expand Down
3 changes: 2 additions & 1 deletion tests/unit/test_clob_transport.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,13 +230,14 @@ async def run() -> dict[TokenId, Decimal]:
def test_async_get_last_trade_price_returns_model() -> None:
captured: list[httpx.Request] = []

async def run() -> LastTradePrice:
async def run() -> LastTradePrice | None:
async with AsyncPublicClient() as client:
_install_async_clob(client, _clob_handler(captured, {"price": "0.53", "side": "BUY"}))
return await client.get_last_trade_price(token_id="123")

result = asyncio.run(run())

assert result is not None
assert result.price == Decimal("0.53")
assert result.side == "BUY"
assert urlparse(str(captured[0].url)).path == "/last-trade-price"
Expand Down
Loading