Skip to content
Merged
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
74 changes: 43 additions & 31 deletions sdks/python/pmxt/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
import urllib.error
from abc import ABC
from datetime import datetime
from typing import List, Optional, Dict, Any, Literal, Union
from typing import Callable, List, Optional, Dict, Any, Literal, Union

# Add generated client to path
_GENERATED_PATH = os.path.join(os.path.dirname(__file__), "..", "generated")
Expand Down Expand Up @@ -46,6 +46,10 @@
EventFilterFunction,
SubscribedAddressSnapshot,
FirehoseEvent,
MatchResult,
EventMatchResult,
PriceComparison,
ArbitrageOpportunity,
)
from .constants import LOCAL_URL, resolve_pmxt_base_url
from .errors import PmxtError, from_server_error
Expand All @@ -62,7 +66,6 @@
'price_change_24h': 'priceChange24h',
'unrealized_pnl': 'unrealizedPnL',
'realized_pnl': 'realizedPnL',
'dt': 'datetime',
}


Expand All @@ -74,6 +77,15 @@ def _snake_to_camel(name: str) -> str:
return parts[0] + ''.join(p.title() for p in parts[1:])


def _convert_params_to_camel(params: Dict[str, Any]) -> Dict[str, Any]:
"""Convert snake_case param keys to camelCase for the sidecar wire format.

Keys that are already camelCase or single-word pass through unchanged.
Nested dicts/lists are left as-is -- only top-level keys are converted.
"""
return {_snake_to_camel(k): v for k, v in params.items()}


def _auto_convert(cls, raw: Dict[str, Any], **overrides):
"""Auto-map camelCase raw dict to snake_case dataclass fields.

Expand Down Expand Up @@ -739,7 +751,7 @@ def fetch_markets(self, params: Optional[dict] = None, **kwargs) -> List[Unified
if kwargs:
params = {**(params or {}), **kwargs}
if params is not None:
args.append(params)
args.append(_convert_params_to_camel(params))
body: dict = {"args": args}
creds = self._get_credentials_dict()
if creds:
Expand All @@ -762,7 +774,7 @@ def fetch_markets_paginated(self, params: Optional[dict] = None, **kwargs) -> Pa
if kwargs:
params = {**(params or {}), **kwargs}
if params is not None:
args.append(params)
args.append(_convert_params_to_camel(params))
body: dict = {"args": args}
creds = self._get_credentials_dict()
if creds:
Expand All @@ -789,7 +801,7 @@ def fetch_events(self, params: Optional[dict] = None, **kwargs) -> List[UnifiedE
if kwargs:
params = {**(params or {}), **kwargs}
if params is not None:
args.append(params)
args.append(_convert_params_to_camel(params))
body: dict = {"args": args}
creds = self._get_credentials_dict()
if creds:
Expand All @@ -812,7 +824,7 @@ def fetch_market(self, params: Optional[dict] = None, **kwargs) -> UnifiedMarket
if kwargs:
params = {**(params or {}), **kwargs}
if params is not None:
args.append(params)
args.append(_convert_params_to_camel(params))
body: dict = {"args": args}
creds = self._get_credentials_dict()
if creds:
Expand All @@ -835,7 +847,7 @@ def fetch_event(self, params: Optional[dict] = None, **kwargs) -> UnifiedEvent:
if kwargs:
params = {**(params or {}), **kwargs}
if params is not None:
args.append(params)
args.append(_convert_params_to_camel(params))
body: dict = {"args": args}
creds = self._get_credentials_dict()
if creds:
Expand Down Expand Up @@ -1132,7 +1144,7 @@ def close(self) -> None:
except ApiException as e:
raise self._parse_api_exception(e) from None

def fetch_market_matches(self, params: Optional[dict] = None, **kwargs) -> List[Any]:
def fetch_market_matches(self, params: Optional[dict] = None, **kwargs) -> List[MatchResult]:
try:
args = []
if kwargs:
Expand All @@ -1155,7 +1167,7 @@ def fetch_market_matches(self, params: Optional[dict] = None, **kwargs) -> List[
except ApiException as e:
raise self._parse_api_exception(e) from None

def fetch_matches(self, params: dict, **kwargs) -> List[Any]:
def fetch_matches(self, params: dict, **kwargs) -> List[MatchResult]:
try:
args = []
if kwargs:
Expand All @@ -1177,7 +1189,7 @@ def fetch_matches(self, params: dict, **kwargs) -> List[Any]:
except ApiException as e:
raise self._parse_api_exception(e) from None

def fetch_event_matches(self, params: Optional[dict] = None, **kwargs) -> List[Any]:
def fetch_event_matches(self, params: Optional[dict] = None, **kwargs) -> List[EventMatchResult]:
try:
args = []
if kwargs:
Expand All @@ -1200,7 +1212,7 @@ def fetch_event_matches(self, params: Optional[dict] = None, **kwargs) -> List[A
except ApiException as e:
raise self._parse_api_exception(e) from None

def compare_market_prices(self, params: dict, **kwargs) -> List[Any]:
def compare_market_prices(self, params: dict, **kwargs) -> List[PriceComparison]:
try:
args = []
if kwargs:
Expand All @@ -1222,7 +1234,7 @@ def compare_market_prices(self, params: dict, **kwargs) -> List[Any]:
except ApiException as e:
raise self._parse_api_exception(e) from None

def fetch_related_markets(self, params: dict, **kwargs) -> List[Any]:
def fetch_related_markets(self, params: dict, **kwargs) -> List[MatchResult]:
try:
args = []
if kwargs:
Expand All @@ -1244,7 +1256,7 @@ def fetch_related_markets(self, params: dict, **kwargs) -> List[Any]:
except ApiException as e:
raise self._parse_api_exception(e) from None

def fetch_matched_markets(self, params: Optional[dict] = None, **kwargs) -> List[Any]:
def fetch_matched_markets(self, params: Optional[dict] = None, **kwargs) -> List[MatchResult]:
try:
args = []
if kwargs:
Expand All @@ -1267,7 +1279,7 @@ def fetch_matched_markets(self, params: Optional[dict] = None, **kwargs) -> List
except ApiException as e:
raise self._parse_api_exception(e) from None

def fetch_matched_prices(self, params: Optional[dict] = None, **kwargs) -> List[Any]:
def fetch_matched_prices(self, params: Optional[dict] = None, **kwargs) -> List[PriceComparison]:
try:
args = []
if kwargs:
Expand All @@ -1290,7 +1302,7 @@ def fetch_matched_prices(self, params: Optional[dict] = None, **kwargs) -> List[
except ApiException as e:
raise self._parse_api_exception(e) from None

def fetch_hedges(self, params: dict, **kwargs) -> List[Any]:
def fetch_hedges(self, params: dict, **kwargs) -> List[PriceComparison]:
try:
args = []
if kwargs:
Expand All @@ -1312,7 +1324,7 @@ def fetch_hedges(self, params: dict, **kwargs) -> List[Any]:
except ApiException as e:
raise self._parse_api_exception(e) from None

def fetch_arbitrage(self, params: Optional[dict] = None, **kwargs) -> List[Any]:
def fetch_arbitrage(self, params: Optional[dict] = None, **kwargs) -> List[ArbitrageOpportunity]:
try:
args = []
if kwargs:
Expand Down Expand Up @@ -2054,7 +2066,7 @@ def watch_trades(
def watch_address(
self,
address: str,
types: list[str] = None,
types: Optional[List[str]] = None,
) -> SubscribedAddressSnapshot:
"""
Watch real-time updates of a public wallet via WebSocket.
Expand Down Expand Up @@ -2140,7 +2152,7 @@ def unwatch_address(
except ApiException as e:
raise self._parse_api_exception(e) from None

def watch_prices(self, market_address: str, callback: Optional[Any] = None) -> Any:
def watch_prices(self, market_address: str, callback: Optional[Callable[[Dict[str, Any]], None]] = None) -> Dict[str, Any]:
"""
Watch real-time AMM price updates via WebSocket.

Expand Down Expand Up @@ -2176,7 +2188,7 @@ def watch_prices(self, market_address: str, callback: Optional[Any] = None) -> A
except ApiException as e:
raise self._parse_api_exception(e) from None

def watch_user_positions(self, callback: Optional[Any] = None) -> List[Position]:
def watch_user_positions(self, callback: Optional[Callable[[Dict[str, Any]], None]] = None) -> List[Position]:
"""
Watch real-time user position updates via WebSocket.
Requires API key authentication.
Expand Down Expand Up @@ -2213,7 +2225,7 @@ def watch_user_positions(self, callback: Optional[Any] = None) -> List[Position]
except ApiException as e:
raise self._parse_api_exception(e) from None

def watch_user_transactions(self, callback: Optional[Any] = None) -> Any:
def watch_user_transactions(self, callback: Optional[Callable[[Dict[str, Any]], None]] = None) -> Dict[str, Any]:
"""
Watch real-time user transaction updates via WebSocket.
Requires API key authentication.
Expand Down Expand Up @@ -2326,7 +2338,7 @@ def create_order(
market_id: Optional[str] = None,
outcome_id: Optional[str] = None,
side: Literal["buy", "sell"] = "buy",
type: Literal["market", "limit"] = "market",
order_type: Literal["market", "limit"] = "market",
amount: float = 0,
price: Optional[float] = None,
fee: Optional[int] = None,
Expand All @@ -2344,7 +2356,7 @@ def create_order(
market_id: Market ID (or use outcome instead)
outcome_id: Outcome ID (or use outcome instead)
side: Order side (buy/sell)
type: Order type (market/limit)
order_type: Order type (market/limit)
amount: Number of contracts
price: Limit price (required for limit orders, 0.0-1.0)
fee: Optional fee rate (e.g., 1000 for 0.1%)
Expand All @@ -2359,7 +2371,7 @@ def create_order(
... market_id="663583",
... outcome_id="10991849...",
... side="buy",
... type="limit",
... order_type="limit",
... amount=10,
... price=0.55
... )
Expand All @@ -2368,15 +2380,15 @@ def create_order(
>>> order = exchange.create_order(
... outcome=market.yes,
... side="buy",
... type="market",
... order_type="market",
... amount=10,
... )
"""
if self.is_hosted:
if self.exchange_name == "sor" and self.private_key:
return self._execute_sor_order(
market_id=market_id, outcome_id=outcome_id, side=side,
type=type, amount=amount, price=price, outcome=outcome,
type=order_type, amount=amount, price=price, outcome=outcome,
)
raise PmxtError(
"Trade execution is not available through the hosted API. "
Expand Down Expand Up @@ -2405,7 +2417,7 @@ def create_order(
"marketId": market_id,
"outcomeId": outcome_id,
"side": side,
"type": type,
"type": order_type,
"amount": amount,
}
if price is not None:
Expand Down Expand Up @@ -2443,7 +2455,7 @@ def build_order(
market_id: Optional[str] = None,
outcome_id: Optional[str] = None,
side: Literal["buy", "sell"] = "buy",
type: Literal["market", "limit"] = "market",
order_type: Literal["market", "limit"] = "market",
amount: float = 0,
price: Optional[float] = None,
fee: Optional[int] = None,
Expand All @@ -2463,7 +2475,7 @@ def build_order(
market_id: Market ID (or use outcome instead)
outcome_id: Outcome ID (or use outcome instead)
side: Order side (buy/sell)
type: Order type (market/limit)
order_type: Order type (market/limit)
amount: Number of contracts
price: Limit price (required for limit orders, 0.0-1.0)
fee: Optional fee rate (e.g., 1000 for 0.1%)
Expand All @@ -2478,7 +2490,7 @@ def build_order(
... market_id="663583",
... outcome_id="10991849...",
... side="buy",
... type="limit",
... order_type="limit",
... amount=10,
... price=0.55
... )
Expand All @@ -2489,7 +2501,7 @@ def build_order(
>>> built = exchange.build_order(
... outcome=market.yes,
... side="buy",
... type="market",
... order_type="market",
... amount=10
... )
"""
Expand Down Expand Up @@ -2521,7 +2533,7 @@ def build_order(
"marketId": market_id,
"outcomeId": outcome_id,
"side": side,
"type": type,
"type": order_type,
"amount": amount,
}
if price is not None:
Expand Down
2 changes: 1 addition & 1 deletion sdks/python/pmxt/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -274,7 +274,7 @@ class OrderBook:
timestamp: Optional[int] = None
"""Unix timestamp (milliseconds)"""

dt: Optional[str] = None
datetime: Optional[str] = None
"""ISO 8601 datetime string (CCXT-compatible)"""


Expand Down
Loading