polymarklib 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- polymarklib-0.1.0/PKG-INFO +5 -0
- polymarklib-0.1.0/README.md +0 -0
- polymarklib-0.1.0/pyproject.toml +12 -0
- polymarklib-0.1.0/setup.cfg +4 -0
- polymarklib-0.1.0/src/polymarklib/__init__.py +1 -0
- polymarklib-0.1.0/src/polymarklib/config.py +20 -0
- polymarklib-0.1.0/src/polymarklib/markets.py +140 -0
- polymarklib-0.1.0/src/polymarklib/order.py +63 -0
- polymarklib-0.1.0/src/polymarklib/users.py +57 -0
- polymarklib-0.1.0/src/polymarklib.egg-info/PKG-INFO +5 -0
- polymarklib-0.1.0/src/polymarklib.egg-info/SOURCES.txt +12 -0
- polymarklib-0.1.0/src/polymarklib.egg-info/dependency_links.txt +1 -0
- polymarklib-0.1.0/src/polymarklib.egg-info/requires.txt +1 -0
- polymarklib-0.1.0/src/polymarklib.egg-info/top_level.txt +1 -0
|
File without changes
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "polymarklib"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
requires-python = ">=3.10"
|
|
9
|
+
dependencies = ["requests>=2.31.0"]
|
|
10
|
+
|
|
11
|
+
[tool.setuptools.packages.find]
|
|
12
|
+
where = ["src"]
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
|
|
4
|
+
# API ENDPOINTS
|
|
5
|
+
@dataclass(frozen=True)
|
|
6
|
+
class Endpoints:
|
|
7
|
+
"""
|
|
8
|
+
Endpoints dataclass - containing all the polymarket API endpoints
|
|
9
|
+
such as GAMMA, CLOB, etc.
|
|
10
|
+
"""
|
|
11
|
+
gamma: str = os.getenv(
|
|
12
|
+
"POLYMARKET_GAMMA_API",
|
|
13
|
+
"https://gamma-api.polymarket.com",
|
|
14
|
+
)
|
|
15
|
+
clob: str = os.getenv(
|
|
16
|
+
"POLYMARKET_CLOB_API",
|
|
17
|
+
"https://clob.polymarket.com"
|
|
18
|
+
)
|
|
19
|
+
|
|
20
|
+
ENDPOINTS = Endpoints()
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
import json
|
|
3
|
+
from typing import Tuple, Any
|
|
4
|
+
from dataclasses import dataclass
|
|
5
|
+
|
|
6
|
+
from config import ENDPOINTS
|
|
7
|
+
|
|
8
|
+
@dataclass(frozen=True)
|
|
9
|
+
class Market:
|
|
10
|
+
slug: str
|
|
11
|
+
question: str | None
|
|
12
|
+
outcomes: tuple[str, ...]
|
|
13
|
+
clob_token_ids: tuple[str, ...]
|
|
14
|
+
raw: dict[str, Any]
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def token_map(self) -> dict[str, str]:
|
|
18
|
+
# validates lengths via zip truncation, and returns token_map
|
|
19
|
+
if len(self.outcomes) != len(self.clob_token_ids):
|
|
20
|
+
raise ValueError(
|
|
21
|
+
f"Outcome/token length mismatch: "
|
|
22
|
+
f"{len(self.outcomes)} vs {len(self.clob_token_ids)}"
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
return dict(zip(self.outcomes, self.clob_token_ids))
|
|
26
|
+
|
|
27
|
+
def fetch_quotes(self) -> dict[str, dict[str, float]]:
|
|
28
|
+
token_map = self.token_map
|
|
29
|
+
quotes = {}
|
|
30
|
+
for outcome, token in token_map.items():
|
|
31
|
+
bid, ask = fetch_quote(token)
|
|
32
|
+
quotes[outcome] = {"bid": bid, "ask": ask}
|
|
33
|
+
|
|
34
|
+
return quotes
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def from_gamma(gamma_resp: dict) -> "Market":
|
|
38
|
+
try:
|
|
39
|
+
outcomes_raw = gamma_resp["outcomes"]
|
|
40
|
+
clob_tokens_raw = gamma_resp["clobTokenIds"]
|
|
41
|
+
except KeyError as e:
|
|
42
|
+
raise KeyError(f"Missing expected Gamma field: {e}") from e
|
|
43
|
+
|
|
44
|
+
try:
|
|
45
|
+
outcomes = json.loads(outcomes_raw)
|
|
46
|
+
clob_tokens = json.loads(clob_tokens_raw)
|
|
47
|
+
except json.JSONDecodeError as e:
|
|
48
|
+
raise ValueError("JSON fields malformed") from e
|
|
49
|
+
|
|
50
|
+
if len(outcomes) != len(clob_tokens):
|
|
51
|
+
raise ValueError(
|
|
52
|
+
f"Outcome/token length mismatch: "
|
|
53
|
+
f"{len(outcomes)} vs {len(clob_tokens)}"
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
return Market(
|
|
57
|
+
slug=str(gamma_resp.get("slug", "")),
|
|
58
|
+
question=gamma_resp.get("question"),
|
|
59
|
+
outcomes=tuple(str(x) for x in outcomes),
|
|
60
|
+
clob_token_ids=tuple(str(x) for x in clob_tokens),
|
|
61
|
+
raw=gamma_resp,
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def fetch_market_by_slug(slug: str, timeout: float = 15.0) -> Market:
|
|
67
|
+
"""
|
|
68
|
+
Fetch a market by its unique slug.
|
|
69
|
+
|
|
70
|
+
Args:
|
|
71
|
+
timeout: seconds timeout allowed
|
|
72
|
+
slug: the slug of the market
|
|
73
|
+
|
|
74
|
+
Returns:
|
|
75
|
+
Parsed JSON response
|
|
76
|
+
|
|
77
|
+
Raises:
|
|
78
|
+
requests.HTTPError: if non-200 response
|
|
79
|
+
requests.RequestException: on network failure
|
|
80
|
+
ValueError: if response is not valid JSON
|
|
81
|
+
|
|
82
|
+
"""
|
|
83
|
+
url = f"{ENDPOINTS.gamma}/markets/slug/{slug}"
|
|
84
|
+
|
|
85
|
+
resp = requests.get(url, timeout=timeout)
|
|
86
|
+
resp.raise_for_status()
|
|
87
|
+
|
|
88
|
+
data = resp.json()
|
|
89
|
+
|
|
90
|
+
return Market.from_gamma(data)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def fetch_quote(token_id: str, timeout: float = 15.0) -> Tuple[float, float]:
|
|
95
|
+
"""
|
|
96
|
+
Fetches a price quote for a specific token ID
|
|
97
|
+
|
|
98
|
+
Arguments
|
|
99
|
+
token_id: the CLOB token representing the market position
|
|
100
|
+
timeout: timeout for the http request
|
|
101
|
+
|
|
102
|
+
Returns
|
|
103
|
+
(bid, ask) for that token
|
|
104
|
+
|
|
105
|
+
Raises
|
|
106
|
+
requests.HTTPError: if non-200 response
|
|
107
|
+
requests.RequestException: on network failure
|
|
108
|
+
ValueError: if response is not valid JSON
|
|
109
|
+
KeyError: if price key not in JSON response
|
|
110
|
+
"""
|
|
111
|
+
base = f"{ENDPOINTS.clob}/price"
|
|
112
|
+
|
|
113
|
+
bid_resp = requests.get(base, params={"token_id": token_id, "side": "sell"}, timeout=timeout)
|
|
114
|
+
ask_resp = requests.get(base, params={"token_id": token_id, "side": "buy"}, timeout=timeout)
|
|
115
|
+
|
|
116
|
+
bid_resp.raise_for_status()
|
|
117
|
+
ask_resp.raise_for_status()
|
|
118
|
+
|
|
119
|
+
try:
|
|
120
|
+
bid_json = bid_resp.json()
|
|
121
|
+
ask_json = ask_resp.json()
|
|
122
|
+
except json.JSONDecodeError as e:
|
|
123
|
+
raise ValueError(f"Response was not valid JSON") from e
|
|
124
|
+
|
|
125
|
+
try:
|
|
126
|
+
bid_raw = bid_json["price"]
|
|
127
|
+
ask_raw = ask_json["price"]
|
|
128
|
+
except KeyError as e:
|
|
129
|
+
raise KeyError(f"Missing price field: {e}") from e
|
|
130
|
+
|
|
131
|
+
if bid_raw is None or ask_raw is None:
|
|
132
|
+
raise ValueError(f"Price was null (bid={bid_raw}, ask={ask_raw})")
|
|
133
|
+
|
|
134
|
+
try:
|
|
135
|
+
bid = float(bid_raw)
|
|
136
|
+
ask = float(ask_raw)
|
|
137
|
+
except (TypeError, ValueError) as e:
|
|
138
|
+
raise ValueError(f"Price was not numeric (bid={bid_raw}, ask={ask_raw})") from e
|
|
139
|
+
|
|
140
|
+
return bid, ask
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from py_clob_client import ClobClient
|
|
4
|
+
from py_clob_client.clob_types import *
|
|
5
|
+
from py_clob_client.exceptions import PolyApiException
|
|
6
|
+
from py_clob_client.order_builder.constants import BUY, SELL
|
|
7
|
+
|
|
8
|
+
from config import ENDPOINTS
|
|
9
|
+
|
|
10
|
+
class Spender:
|
|
11
|
+
def __init__(self, *, wallet_address: str, signature_type: int, private_key: str | None = None, allow_live: bool = False):
|
|
12
|
+
if not allow_live:
|
|
13
|
+
raise RuntimeError(
|
|
14
|
+
"Live trading disabled. Pass allow_live=True to enable"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
if private_key is None:
|
|
18
|
+
private_key = os.getenv("POLYMARKET_PRIVATE_KEY")
|
|
19
|
+
|
|
20
|
+
if not private_key:
|
|
21
|
+
raise ValueError("No private key provided")
|
|
22
|
+
|
|
23
|
+
self.auth_client = ClobClient(
|
|
24
|
+
ENDPOINTS.clob,
|
|
25
|
+
key=private_key,
|
|
26
|
+
chain_id=137,
|
|
27
|
+
signature_type=signature_type,
|
|
28
|
+
funder=wallet_address
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
creds = self.auth_client.derive_api_key()
|
|
32
|
+
self.auth_client.set_api_creds(creds)
|
|
33
|
+
|
|
34
|
+
def get_balance(self):
|
|
35
|
+
balance = self.auth_client.get_balance_allowance(BalanceAllowanceParams(asset_type=AssetType.COLLATERAL))
|
|
36
|
+
usdc_balance = int(balance['balance']) / 1e6
|
|
37
|
+
return usdc_balance
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def place_order(self, * token_id: str, amount: float, order_type: OrderType, side: str) -> dict[str, Any]:
|
|
41
|
+
"""
|
|
42
|
+
Places a real polymarket bet
|
|
43
|
+
:param order_type: e.g FOK
|
|
44
|
+
:param token_id: the order ID
|
|
45
|
+
:param amount: the amount in USDC
|
|
46
|
+
:return:
|
|
47
|
+
"""
|
|
48
|
+
if side not in (BUY, SELL):
|
|
49
|
+
raise ValueError("Side must be BUY or SELL")
|
|
50
|
+
|
|
51
|
+
market_order = MarketOrderArgs(
|
|
52
|
+
token_id=token_id,
|
|
53
|
+
amount=amount,
|
|
54
|
+
side=side,
|
|
55
|
+
order_type=order_type
|
|
56
|
+
)
|
|
57
|
+
signed = self.auth_client.create_market_order(market_order)
|
|
58
|
+
|
|
59
|
+
try:
|
|
60
|
+
resp = self.auth_client.post_order(signed, order_type)
|
|
61
|
+
return {"ok": True, "resp": resp}
|
|
62
|
+
except PolyApiException as e:
|
|
63
|
+
return {"ok": False, "reason": "", "error": str(e)}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import requests
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
@dataclass(frozen=True)
|
|
6
|
+
class Action:
|
|
7
|
+
user_id: str
|
|
8
|
+
event_type: str
|
|
9
|
+
usdc_size: float
|
|
10
|
+
price: float
|
|
11
|
+
side: str
|
|
12
|
+
asset: str
|
|
13
|
+
title: str
|
|
14
|
+
slug: str
|
|
15
|
+
timestamp: int
|
|
16
|
+
|
|
17
|
+
class UsersClient:
|
|
18
|
+
def __init__(self, session=None):
|
|
19
|
+
self.session = session
|
|
20
|
+
|
|
21
|
+
def fetch_activity(self, user_id: str, limit: int = 100, timeout: int = 15) -> tuple[Action, ...]:
|
|
22
|
+
"""
|
|
23
|
+
"""
|
|
24
|
+
s = self.session or requests
|
|
25
|
+
|
|
26
|
+
url = "https://data-api.polymarket.com/activity"
|
|
27
|
+
params = {
|
|
28
|
+
"user": user_id,
|
|
29
|
+
"limit": limit,
|
|
30
|
+
"sortBy": "TIMESTAMP",
|
|
31
|
+
"sortDirection": "DESC"
|
|
32
|
+
}
|
|
33
|
+
resp = s.get(url, params=params, timeout=timeout)
|
|
34
|
+
resp.raise_for_status()
|
|
35
|
+
|
|
36
|
+
try:
|
|
37
|
+
data: list[dict] = resp.json()
|
|
38
|
+
except ValueError as e:
|
|
39
|
+
raise ValueError("Response was not valid JSON") from e
|
|
40
|
+
|
|
41
|
+
activity: list[Action] = []
|
|
42
|
+
for entry in data:
|
|
43
|
+
action: Action = Action(
|
|
44
|
+
user_id=user_id,
|
|
45
|
+
event_type=entry["type"],
|
|
46
|
+
usdc_size=float(entry["size"]),
|
|
47
|
+
price=float(entry["price"]),
|
|
48
|
+
side=entry["side"],
|
|
49
|
+
asset=entry["asset"],
|
|
50
|
+
title=entry["title"],
|
|
51
|
+
slug=entry["slug"],
|
|
52
|
+
timestamp=int(entry["timestamp"])
|
|
53
|
+
)
|
|
54
|
+
activity.append(action)
|
|
55
|
+
|
|
56
|
+
return tuple(activity)
|
|
57
|
+
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
README.md
|
|
2
|
+
pyproject.toml
|
|
3
|
+
src/polymarklib/__init__.py
|
|
4
|
+
src/polymarklib/config.py
|
|
5
|
+
src/polymarklib/markets.py
|
|
6
|
+
src/polymarklib/order.py
|
|
7
|
+
src/polymarklib/users.py
|
|
8
|
+
src/polymarklib.egg-info/PKG-INFO
|
|
9
|
+
src/polymarklib.egg-info/SOURCES.txt
|
|
10
|
+
src/polymarklib.egg-info/dependency_links.txt
|
|
11
|
+
src/polymarklib.egg-info/requires.txt
|
|
12
|
+
src/polymarklib.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
requests>=2.31.0
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
polymarklib
|