polynode 0.10.4__tar.gz → 0.10.5__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.
- {polynode-0.10.4 → polynode-0.10.5}/PKG-INFO +27 -1
- {polynode-0.10.4 → polynode-0.10.5}/README.md +26 -0
- polynode-0.10.5/polynode/_version.py +1 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/client.py +300 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/engine.py +1 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/orderbook_state.py +8 -2
- {polynode-0.10.4 → polynode-0.10.5}/pyproject.toml +1 -1
- polynode-0.10.5/tests/test_client.py +137 -0
- {polynode-0.10.4 → polynode-0.10.5}/tests/test_orderbook.py +15 -1
- polynode-0.10.4/polynode/_version.py +0 -1
- polynode-0.10.4/tests/test_client.py +0 -45
- {polynode-0.10.4 → polynode-0.10.5}/.gitignore +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/__init__.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/cache/__init__.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/errors.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/orderbook.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/redemption_watcher.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/short_form.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/subscription.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/testing.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/V2_ORDER_FLOW.md +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/__init__.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/clob_api.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/constants.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/cosigner.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/eip712.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/escrow.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/onboarding.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/position_management.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/privy.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/relayer.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/signer.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/sqlite_backend.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/trader.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/trading/types.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/types/__init__.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/types/enums.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/types/events.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/types/orderbook.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/types/rest.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/types/short_form.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/types/ws.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/polynode/ws.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/tests/__init__.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/tests/conftest.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/tests/test_trading.py +0 -0
- {polynode-0.10.4 → polynode-0.10.5}/tests/test_types.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: polynode
|
|
3
|
-
Version: 0.10.
|
|
3
|
+
Version: 0.10.5
|
|
4
4
|
Summary: Python SDK for the PolyNode real-time prediction market data platform
|
|
5
5
|
Project-URL: Homepage, https://polynode.dev
|
|
6
6
|
Project-URL: Documentation, https://docs.polynode.dev
|
|
@@ -59,6 +59,32 @@ with PolyNode(api_key="pn_live_...") as pn:
|
|
|
59
59
|
settlements = pn.recent_settlements(count=5)
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
### Sports and Online Context
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from polynode import PolyNode
|
|
66
|
+
|
|
67
|
+
with PolyNode(api_key="pn_live_...") as pn:
|
|
68
|
+
state = pn.sports_game_state(
|
|
69
|
+
"nba-cle-nyk-2026-05-31",
|
|
70
|
+
price_limit_tokens=20,
|
|
71
|
+
)
|
|
72
|
+
|
|
73
|
+
context = pn.sports_game_context(
|
|
74
|
+
"nba-cle-nyk-2026-05-31",
|
|
75
|
+
sources=["online"],
|
|
76
|
+
query_set="injuries",
|
|
77
|
+
max_queries=2,
|
|
78
|
+
max_per_query=5,
|
|
79
|
+
include_state=True,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
web = pn.search_online(
|
|
83
|
+
"Cavaliers Knicks injury news",
|
|
84
|
+
max_results=5,
|
|
85
|
+
)
|
|
86
|
+
```
|
|
87
|
+
|
|
62
88
|
### Async REST
|
|
63
89
|
|
|
64
90
|
```python
|
|
@@ -26,6 +26,32 @@ with PolyNode(api_key="pn_live_...") as pn:
|
|
|
26
26
|
settlements = pn.recent_settlements(count=5)
|
|
27
27
|
```
|
|
28
28
|
|
|
29
|
+
### Sports and Online Context
|
|
30
|
+
|
|
31
|
+
```python
|
|
32
|
+
from polynode import PolyNode
|
|
33
|
+
|
|
34
|
+
with PolyNode(api_key="pn_live_...") as pn:
|
|
35
|
+
state = pn.sports_game_state(
|
|
36
|
+
"nba-cle-nyk-2026-05-31",
|
|
37
|
+
price_limit_tokens=20,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
context = pn.sports_game_context(
|
|
41
|
+
"nba-cle-nyk-2026-05-31",
|
|
42
|
+
sources=["online"],
|
|
43
|
+
query_set="injuries",
|
|
44
|
+
max_queries=2,
|
|
45
|
+
max_per_query=5,
|
|
46
|
+
include_state=True,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
web = pn.search_online(
|
|
50
|
+
"Cavaliers Knicks injury news",
|
|
51
|
+
max_results=5,
|
|
52
|
+
)
|
|
53
|
+
```
|
|
54
|
+
|
|
29
55
|
### Async REST
|
|
30
56
|
|
|
31
57
|
```python
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.10.5"
|
|
@@ -32,6 +32,14 @@ from .types.rest import (
|
|
|
32
32
|
from .types.enums import CandleResolution, MarketSortField
|
|
33
33
|
|
|
34
34
|
|
|
35
|
+
def _csv_param(value: Any) -> Any:
|
|
36
|
+
if value is None or isinstance(value, str):
|
|
37
|
+
return value
|
|
38
|
+
if isinstance(value, (list, tuple, set)):
|
|
39
|
+
return ",".join(str(item) for item in value)
|
|
40
|
+
return value
|
|
41
|
+
|
|
42
|
+
|
|
35
43
|
class PolyNode:
|
|
36
44
|
"""Synchronous PolyNode REST client."""
|
|
37
45
|
|
|
@@ -187,6 +195,152 @@ class PolyNode:
|
|
|
187
195
|
self._fetch("/v1/search", query={"q": query, "limit": limit, "include_inactive": include_inactive})
|
|
188
196
|
)
|
|
189
197
|
|
|
198
|
+
def search_online(
|
|
199
|
+
self,
|
|
200
|
+
query: str,
|
|
201
|
+
*,
|
|
202
|
+
max_results: int | None = None,
|
|
203
|
+
engines: str | list[str] | tuple[str, ...] | set[str] | None = None,
|
|
204
|
+
language: str | None = None,
|
|
205
|
+
time_range: str | None = None,
|
|
206
|
+
safe_search: int | None = None,
|
|
207
|
+
cache_ttl_seconds: int | None = None,
|
|
208
|
+
) -> dict:
|
|
209
|
+
return self._fetch(
|
|
210
|
+
"/v2/search/online",
|
|
211
|
+
query={
|
|
212
|
+
"q": query,
|
|
213
|
+
"max_results": max_results,
|
|
214
|
+
"engines": _csv_param(engines),
|
|
215
|
+
"language": language,
|
|
216
|
+
"time_range": time_range,
|
|
217
|
+
"safe_search": safe_search,
|
|
218
|
+
"cache_ttl_seconds": cache_ttl_seconds,
|
|
219
|
+
},
|
|
220
|
+
)
|
|
221
|
+
|
|
222
|
+
# ── Sports ──
|
|
223
|
+
|
|
224
|
+
def sports_leagues(self) -> dict:
|
|
225
|
+
return self._fetch("/v2/sports/leagues")
|
|
226
|
+
|
|
227
|
+
def sports_teams(self, league: str, *, limit: int | None = None, offset: int | None = None) -> dict:
|
|
228
|
+
return self._fetch(
|
|
229
|
+
f"/v2/sports/leagues/{quote(league, safe='')}/teams",
|
|
230
|
+
query={"limit": limit, "offset": offset},
|
|
231
|
+
)
|
|
232
|
+
|
|
233
|
+
def sports_games(
|
|
234
|
+
self,
|
|
235
|
+
league: str,
|
|
236
|
+
*,
|
|
237
|
+
status: str | None = None,
|
|
238
|
+
limit: int | None = None,
|
|
239
|
+
offset: int | None = None,
|
|
240
|
+
sort: str | None = None,
|
|
241
|
+
direction: str | None = None,
|
|
242
|
+
) -> dict:
|
|
243
|
+
return self._fetch(
|
|
244
|
+
f"/v2/sports/leagues/{quote(league, safe='')}/games",
|
|
245
|
+
query={"status": status, "limit": limit, "offset": offset, "sort": sort, "direction": direction},
|
|
246
|
+
)
|
|
247
|
+
|
|
248
|
+
def sports_game(self, slug: str) -> dict:
|
|
249
|
+
return self._fetch(f"/v2/sports/games/{quote(slug, safe='')}")
|
|
250
|
+
|
|
251
|
+
def sports_game_state(
|
|
252
|
+
self,
|
|
253
|
+
slug: str,
|
|
254
|
+
*,
|
|
255
|
+
include_history: bool | None = None,
|
|
256
|
+
price_limit_tokens: int | None = None,
|
|
257
|
+
history_limit_tokens: int | None = None,
|
|
258
|
+
start_ts: int | str | None = None,
|
|
259
|
+
end_ts: int | str | None = None,
|
|
260
|
+
interval: str | None = None,
|
|
261
|
+
fidelity: int | None = None,
|
|
262
|
+
) -> dict:
|
|
263
|
+
return self._fetch(
|
|
264
|
+
f"/v2/sports/games/{quote(slug, safe='')}/state",
|
|
265
|
+
query={
|
|
266
|
+
"include_history": include_history,
|
|
267
|
+
"price_limit_tokens": price_limit_tokens,
|
|
268
|
+
"history_limit_tokens": history_limit_tokens,
|
|
269
|
+
"start_ts": start_ts,
|
|
270
|
+
"end_ts": end_ts,
|
|
271
|
+
"interval": interval,
|
|
272
|
+
"fidelity": fidelity,
|
|
273
|
+
},
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
def sports_game_context(
|
|
277
|
+
self,
|
|
278
|
+
slug: str,
|
|
279
|
+
*,
|
|
280
|
+
sources: str | list[str] | tuple[str, ...] | set[str] | None = None,
|
|
281
|
+
query_set: str | None = None,
|
|
282
|
+
max_queries: int | None = None,
|
|
283
|
+
max_per_query: int | None = None,
|
|
284
|
+
include_state: bool | None = None,
|
|
285
|
+
price_limit_tokens: int | None = None,
|
|
286
|
+
) -> dict:
|
|
287
|
+
return self._fetch(
|
|
288
|
+
f"/v2/sports/games/{quote(slug, safe='')}/context",
|
|
289
|
+
query={
|
|
290
|
+
"sources": _csv_param(sources),
|
|
291
|
+
"query_set": query_set,
|
|
292
|
+
"max_queries": max_queries,
|
|
293
|
+
"max_per_query": max_per_query,
|
|
294
|
+
"include_state": include_state,
|
|
295
|
+
"price_limit_tokens": price_limit_tokens,
|
|
296
|
+
},
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
def sports_game_prices(self, slug: str, *, limit_tokens: int | None = None) -> dict:
|
|
300
|
+
return self._fetch(
|
|
301
|
+
f"/v2/sports/games/{quote(slug, safe='')}/prices",
|
|
302
|
+
query={"limit_tokens": limit_tokens},
|
|
303
|
+
)
|
|
304
|
+
|
|
305
|
+
def sports_game_history(
|
|
306
|
+
self,
|
|
307
|
+
slug: str,
|
|
308
|
+
*,
|
|
309
|
+
start_ts: int | str | None = None,
|
|
310
|
+
end_ts: int | str | None = None,
|
|
311
|
+
interval: str | None = None,
|
|
312
|
+
fidelity: int | None = None,
|
|
313
|
+
limit_tokens: int | None = None,
|
|
314
|
+
) -> dict:
|
|
315
|
+
return self._fetch(
|
|
316
|
+
f"/v2/sports/games/{quote(slug, safe='')}/history",
|
|
317
|
+
query={
|
|
318
|
+
"start_ts": start_ts,
|
|
319
|
+
"end_ts": end_ts,
|
|
320
|
+
"interval": interval,
|
|
321
|
+
"fidelity": fidelity,
|
|
322
|
+
"limit_tokens": limit_tokens,
|
|
323
|
+
},
|
|
324
|
+
)
|
|
325
|
+
|
|
326
|
+
def sports_market_types(self) -> dict:
|
|
327
|
+
return self._fetch("/v2/sports/market-types")
|
|
328
|
+
|
|
329
|
+
def sports_series(self, *, status: str | None = None, limit: int | None = None) -> dict:
|
|
330
|
+
return self._fetch("/v2/sports/series", query={"status": status, "limit": limit})
|
|
331
|
+
|
|
332
|
+
def sports_tournaments(self) -> dict:
|
|
333
|
+
return self._fetch("/v2/sports/tournaments")
|
|
334
|
+
|
|
335
|
+
def sports_live(self) -> dict:
|
|
336
|
+
return self._fetch("/v2/sports/live")
|
|
337
|
+
|
|
338
|
+
def sports_summary(self) -> dict:
|
|
339
|
+
return self._fetch("/v2/sports/summary")
|
|
340
|
+
|
|
341
|
+
def sports_search(self, query: str, *, limit: int | None = None) -> dict:
|
|
342
|
+
return self._fetch("/v2/sports/search", query={"q": query, "limit": limit})
|
|
343
|
+
|
|
190
344
|
# ── Pricing ──
|
|
191
345
|
|
|
192
346
|
def candles(
|
|
@@ -490,6 +644,152 @@ class AsyncPolyNode:
|
|
|
490
644
|
await self._fetch("/v1/search", query={"q": query, "limit": limit, "include_inactive": include_inactive})
|
|
491
645
|
)
|
|
492
646
|
|
|
647
|
+
async def search_online(
|
|
648
|
+
self,
|
|
649
|
+
query: str,
|
|
650
|
+
*,
|
|
651
|
+
max_results: int | None = None,
|
|
652
|
+
engines: str | list[str] | tuple[str, ...] | set[str] | None = None,
|
|
653
|
+
language: str | None = None,
|
|
654
|
+
time_range: str | None = None,
|
|
655
|
+
safe_search: int | None = None,
|
|
656
|
+
cache_ttl_seconds: int | None = None,
|
|
657
|
+
) -> dict:
|
|
658
|
+
return await self._fetch(
|
|
659
|
+
"/v2/search/online",
|
|
660
|
+
query={
|
|
661
|
+
"q": query,
|
|
662
|
+
"max_results": max_results,
|
|
663
|
+
"engines": _csv_param(engines),
|
|
664
|
+
"language": language,
|
|
665
|
+
"time_range": time_range,
|
|
666
|
+
"safe_search": safe_search,
|
|
667
|
+
"cache_ttl_seconds": cache_ttl_seconds,
|
|
668
|
+
},
|
|
669
|
+
)
|
|
670
|
+
|
|
671
|
+
# ── Sports ──
|
|
672
|
+
|
|
673
|
+
async def sports_leagues(self) -> dict:
|
|
674
|
+
return await self._fetch("/v2/sports/leagues")
|
|
675
|
+
|
|
676
|
+
async def sports_teams(self, league: str, *, limit: int | None = None, offset: int | None = None) -> dict:
|
|
677
|
+
return await self._fetch(
|
|
678
|
+
f"/v2/sports/leagues/{quote(league, safe='')}/teams",
|
|
679
|
+
query={"limit": limit, "offset": offset},
|
|
680
|
+
)
|
|
681
|
+
|
|
682
|
+
async def sports_games(
|
|
683
|
+
self,
|
|
684
|
+
league: str,
|
|
685
|
+
*,
|
|
686
|
+
status: str | None = None,
|
|
687
|
+
limit: int | None = None,
|
|
688
|
+
offset: int | None = None,
|
|
689
|
+
sort: str | None = None,
|
|
690
|
+
direction: str | None = None,
|
|
691
|
+
) -> dict:
|
|
692
|
+
return await self._fetch(
|
|
693
|
+
f"/v2/sports/leagues/{quote(league, safe='')}/games",
|
|
694
|
+
query={"status": status, "limit": limit, "offset": offset, "sort": sort, "direction": direction},
|
|
695
|
+
)
|
|
696
|
+
|
|
697
|
+
async def sports_game(self, slug: str) -> dict:
|
|
698
|
+
return await self._fetch(f"/v2/sports/games/{quote(slug, safe='')}")
|
|
699
|
+
|
|
700
|
+
async def sports_game_state(
|
|
701
|
+
self,
|
|
702
|
+
slug: str,
|
|
703
|
+
*,
|
|
704
|
+
include_history: bool | None = None,
|
|
705
|
+
price_limit_tokens: int | None = None,
|
|
706
|
+
history_limit_tokens: int | None = None,
|
|
707
|
+
start_ts: int | str | None = None,
|
|
708
|
+
end_ts: int | str | None = None,
|
|
709
|
+
interval: str | None = None,
|
|
710
|
+
fidelity: int | None = None,
|
|
711
|
+
) -> dict:
|
|
712
|
+
return await self._fetch(
|
|
713
|
+
f"/v2/sports/games/{quote(slug, safe='')}/state",
|
|
714
|
+
query={
|
|
715
|
+
"include_history": include_history,
|
|
716
|
+
"price_limit_tokens": price_limit_tokens,
|
|
717
|
+
"history_limit_tokens": history_limit_tokens,
|
|
718
|
+
"start_ts": start_ts,
|
|
719
|
+
"end_ts": end_ts,
|
|
720
|
+
"interval": interval,
|
|
721
|
+
"fidelity": fidelity,
|
|
722
|
+
},
|
|
723
|
+
)
|
|
724
|
+
|
|
725
|
+
async def sports_game_context(
|
|
726
|
+
self,
|
|
727
|
+
slug: str,
|
|
728
|
+
*,
|
|
729
|
+
sources: str | list[str] | tuple[str, ...] | set[str] | None = None,
|
|
730
|
+
query_set: str | None = None,
|
|
731
|
+
max_queries: int | None = None,
|
|
732
|
+
max_per_query: int | None = None,
|
|
733
|
+
include_state: bool | None = None,
|
|
734
|
+
price_limit_tokens: int | None = None,
|
|
735
|
+
) -> dict:
|
|
736
|
+
return await self._fetch(
|
|
737
|
+
f"/v2/sports/games/{quote(slug, safe='')}/context",
|
|
738
|
+
query={
|
|
739
|
+
"sources": _csv_param(sources),
|
|
740
|
+
"query_set": query_set,
|
|
741
|
+
"max_queries": max_queries,
|
|
742
|
+
"max_per_query": max_per_query,
|
|
743
|
+
"include_state": include_state,
|
|
744
|
+
"price_limit_tokens": price_limit_tokens,
|
|
745
|
+
},
|
|
746
|
+
)
|
|
747
|
+
|
|
748
|
+
async def sports_game_prices(self, slug: str, *, limit_tokens: int | None = None) -> dict:
|
|
749
|
+
return await self._fetch(
|
|
750
|
+
f"/v2/sports/games/{quote(slug, safe='')}/prices",
|
|
751
|
+
query={"limit_tokens": limit_tokens},
|
|
752
|
+
)
|
|
753
|
+
|
|
754
|
+
async def sports_game_history(
|
|
755
|
+
self,
|
|
756
|
+
slug: str,
|
|
757
|
+
*,
|
|
758
|
+
start_ts: int | str | None = None,
|
|
759
|
+
end_ts: int | str | None = None,
|
|
760
|
+
interval: str | None = None,
|
|
761
|
+
fidelity: int | None = None,
|
|
762
|
+
limit_tokens: int | None = None,
|
|
763
|
+
) -> dict:
|
|
764
|
+
return await self._fetch(
|
|
765
|
+
f"/v2/sports/games/{quote(slug, safe='')}/history",
|
|
766
|
+
query={
|
|
767
|
+
"start_ts": start_ts,
|
|
768
|
+
"end_ts": end_ts,
|
|
769
|
+
"interval": interval,
|
|
770
|
+
"fidelity": fidelity,
|
|
771
|
+
"limit_tokens": limit_tokens,
|
|
772
|
+
},
|
|
773
|
+
)
|
|
774
|
+
|
|
775
|
+
async def sports_market_types(self) -> dict:
|
|
776
|
+
return await self._fetch("/v2/sports/market-types")
|
|
777
|
+
|
|
778
|
+
async def sports_series(self, *, status: str | None = None, limit: int | None = None) -> dict:
|
|
779
|
+
return await self._fetch("/v2/sports/series", query={"status": status, "limit": limit})
|
|
780
|
+
|
|
781
|
+
async def sports_tournaments(self) -> dict:
|
|
782
|
+
return await self._fetch("/v2/sports/tournaments")
|
|
783
|
+
|
|
784
|
+
async def sports_live(self) -> dict:
|
|
785
|
+
return await self._fetch("/v2/sports/live")
|
|
786
|
+
|
|
787
|
+
async def sports_summary(self) -> dict:
|
|
788
|
+
return await self._fetch("/v2/sports/summary")
|
|
789
|
+
|
|
790
|
+
async def sports_search(self, query: str, *, limit: int | None = None) -> dict:
|
|
791
|
+
return await self._fetch("/v2/sports/search", query={"q": query, "limit": limit})
|
|
792
|
+
|
|
493
793
|
# ── Pricing ──
|
|
494
794
|
|
|
495
795
|
async def candles(
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
|
-
from .types.orderbook import BookSnapshot, BookUpdate, OrderbookLevel, PriceChange
|
|
5
|
+
from .types.orderbook import BookSnapshot, BookUpdate, OrderbookLevel, PriceChange, PriceChangeAsset
|
|
6
6
|
|
|
7
7
|
|
|
8
8
|
class LocalOrderbook:
|
|
@@ -10,6 +10,7 @@ class LocalOrderbook:
|
|
|
10
10
|
|
|
11
11
|
def __init__(self) -> None:
|
|
12
12
|
self._books: dict[str, tuple[list[OrderbookLevel], list[OrderbookLevel]]] = {}
|
|
13
|
+
self._last_prices: dict[str, PriceChangeAsset] = {}
|
|
13
14
|
|
|
14
15
|
def apply_snapshot(self, snap: BookSnapshot) -> None:
|
|
15
16
|
bids = sorted(list(snap.bids), key=lambda l: float(l.price), reverse=True)
|
|
@@ -25,11 +26,15 @@ class LocalOrderbook:
|
|
|
25
26
|
self._books[update.asset_id] = (new_bids, new_asks)
|
|
26
27
|
|
|
27
28
|
def apply_price_change(self, change: PriceChange) -> None:
|
|
28
|
-
|
|
29
|
+
for asset in change.assets:
|
|
30
|
+
self._last_prices[asset.asset_id] = asset
|
|
29
31
|
|
|
30
32
|
def get_book(self, asset_id: str) -> tuple[list[OrderbookLevel], list[OrderbookLevel]] | None:
|
|
31
33
|
return self._books.get(asset_id)
|
|
32
34
|
|
|
35
|
+
def get_last_price(self, asset_id: str) -> PriceChangeAsset | None:
|
|
36
|
+
return self._last_prices.get(asset_id)
|
|
37
|
+
|
|
33
38
|
def get_best_bid(self, asset_id: str) -> OrderbookLevel | None:
|
|
34
39
|
book = self._books.get(asset_id)
|
|
35
40
|
if book and book[0]:
|
|
@@ -55,6 +60,7 @@ class LocalOrderbook:
|
|
|
55
60
|
|
|
56
61
|
def clear(self) -> None:
|
|
57
62
|
self._books.clear()
|
|
63
|
+
self._last_prices.clear()
|
|
58
64
|
|
|
59
65
|
|
|
60
66
|
def _apply_deltas(
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
"""Tests for the sync REST client."""
|
|
2
|
+
|
|
3
|
+
import pytest
|
|
4
|
+
from polynode import PolyNode
|
|
5
|
+
from polynode.types.rest import StatusResponse, MarketsResponse, SettlementsResponse
|
|
6
|
+
|
|
7
|
+
TEST_KEY = "pn_live_test_session_tracking_51eca107e9b347b589f5b0a04f98eb1d"
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@pytest.fixture
|
|
11
|
+
def pn():
|
|
12
|
+
client = PolyNode(api_key=TEST_KEY)
|
|
13
|
+
yield client
|
|
14
|
+
client.close()
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_healthz(pn):
|
|
18
|
+
result = pn.healthz()
|
|
19
|
+
assert result == "ok" or "ok" in str(result).lower()
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def test_status(pn):
|
|
23
|
+
result = pn.status()
|
|
24
|
+
assert isinstance(result, StatusResponse)
|
|
25
|
+
assert result.uptime_seconds > 0
|
|
26
|
+
assert result.state.market_count > 0
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def test_markets(pn):
|
|
30
|
+
result = pn.markets(count=3)
|
|
31
|
+
assert isinstance(result, MarketsResponse)
|
|
32
|
+
assert result.count > 0
|
|
33
|
+
assert len(result.markets) <= 3
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_recent_settlements(pn):
|
|
37
|
+
result = pn.recent_settlements(count=1)
|
|
38
|
+
assert isinstance(result, SettlementsResponse)
|
|
39
|
+
assert result.count >= 0
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def test_search(pn):
|
|
43
|
+
result = pn.search("election", limit=3)
|
|
44
|
+
assert result.query == "election"
|
|
45
|
+
assert result.count >= 0
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def test_search_online_builds_expected_path():
|
|
49
|
+
client = PolyNode(api_key=TEST_KEY)
|
|
50
|
+
calls = []
|
|
51
|
+
|
|
52
|
+
def fake_fetch(path, **kwargs):
|
|
53
|
+
calls.append((path, kwargs))
|
|
54
|
+
return {"ok": True}
|
|
55
|
+
|
|
56
|
+
client._fetch = fake_fetch # type: ignore[method-assign]
|
|
57
|
+
try:
|
|
58
|
+
assert client.search_online(
|
|
59
|
+
"Cavaliers Knicks injury news",
|
|
60
|
+
max_results=5,
|
|
61
|
+
engines=["brave", "bing"],
|
|
62
|
+
language="en-US",
|
|
63
|
+
time_range="day",
|
|
64
|
+
safe_search=0,
|
|
65
|
+
cache_ttl_seconds=300,
|
|
66
|
+
)["ok"]
|
|
67
|
+
finally:
|
|
68
|
+
client.close()
|
|
69
|
+
|
|
70
|
+
assert calls[0][0] == "/v2/search/online"
|
|
71
|
+
assert calls[0][1]["query"] == {
|
|
72
|
+
"q": "Cavaliers Knicks injury news",
|
|
73
|
+
"max_results": 5,
|
|
74
|
+
"engines": "brave,bing",
|
|
75
|
+
"language": "en-US",
|
|
76
|
+
"time_range": "day",
|
|
77
|
+
"safe_search": 0,
|
|
78
|
+
"cache_ttl_seconds": 300,
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_sports_methods_build_expected_paths():
|
|
83
|
+
client = PolyNode(api_key=TEST_KEY)
|
|
84
|
+
calls = []
|
|
85
|
+
|
|
86
|
+
def fake_fetch(path, **kwargs):
|
|
87
|
+
calls.append((path, kwargs))
|
|
88
|
+
return {"ok": True}
|
|
89
|
+
|
|
90
|
+
client._fetch = fake_fetch # type: ignore[method-assign]
|
|
91
|
+
try:
|
|
92
|
+
assert client.sports_leagues()["ok"]
|
|
93
|
+
assert client.sports_games("nba", status="active", limit=2)["ok"]
|
|
94
|
+
assert client.sports_game("nba-nyk-cle-2026-05-25")["ok"]
|
|
95
|
+
assert client.sports_game_state(
|
|
96
|
+
"nba-nyk-cle-2026-05-25",
|
|
97
|
+
include_history=True,
|
|
98
|
+
price_limit_tokens=4,
|
|
99
|
+
history_limit_tokens=2,
|
|
100
|
+
interval="1d",
|
|
101
|
+
)["ok"]
|
|
102
|
+
assert client.sports_game_context(
|
|
103
|
+
"nba-nyk-cle-2026-05-25",
|
|
104
|
+
sources=["x", "online"],
|
|
105
|
+
query_set="injuries",
|
|
106
|
+
max_queries=2,
|
|
107
|
+
max_per_query=3,
|
|
108
|
+
include_state=True,
|
|
109
|
+
price_limit_tokens=4,
|
|
110
|
+
)["ok"]
|
|
111
|
+
assert client.sports_game_prices("nba-nyk-cle-2026-05-25", limit_tokens=4)["ok"]
|
|
112
|
+
assert client.sports_game_history("nba-nyk-cle-2026-05-25", interval="1d", limit_tokens=2)["ok"]
|
|
113
|
+
finally:
|
|
114
|
+
client.close()
|
|
115
|
+
|
|
116
|
+
assert calls[0][0] == "/v2/sports/leagues"
|
|
117
|
+
assert calls[1][0] == "/v2/sports/leagues/nba/games"
|
|
118
|
+
assert calls[1][1]["query"] == {"status": "active", "limit": 2, "offset": None, "sort": None, "direction": None}
|
|
119
|
+
assert calls[2][0] == "/v2/sports/games/nba-nyk-cle-2026-05-25"
|
|
120
|
+
assert calls[3][0] == "/v2/sports/games/nba-nyk-cle-2026-05-25/state"
|
|
121
|
+
assert calls[3][1]["query"]["include_history"] is True
|
|
122
|
+
assert calls[3][1]["query"]["price_limit_tokens"] == 4
|
|
123
|
+
assert calls[3][1]["query"]["history_limit_tokens"] == 2
|
|
124
|
+
assert calls[4][0] == "/v2/sports/games/nba-nyk-cle-2026-05-25/context"
|
|
125
|
+
assert calls[4][1]["query"] == {
|
|
126
|
+
"sources": "x,online",
|
|
127
|
+
"query_set": "injuries",
|
|
128
|
+
"max_queries": 2,
|
|
129
|
+
"max_per_query": 3,
|
|
130
|
+
"include_state": True,
|
|
131
|
+
"price_limit_tokens": 4,
|
|
132
|
+
}
|
|
133
|
+
assert calls[5][0] == "/v2/sports/games/nba-nyk-cle-2026-05-25/prices"
|
|
134
|
+
assert calls[5][1]["query"] == {"limit_tokens": 4}
|
|
135
|
+
assert calls[6][0] == "/v2/sports/games/nba-nyk-cle-2026-05-25/history"
|
|
136
|
+
assert calls[6][1]["query"]["interval"] == "1d"
|
|
137
|
+
assert calls[6][1]["query"]["limit_tokens"] == 2
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"""Tests for LocalOrderbook state management."""
|
|
2
2
|
|
|
3
3
|
from polynode.orderbook_state import LocalOrderbook
|
|
4
|
-
from polynode.types.orderbook import BookSnapshot, BookUpdate, OrderbookLevel
|
|
4
|
+
from polynode.types.orderbook import BookSnapshot, BookUpdate, OrderbookLevel, PriceChange, PriceChangeAsset
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
def test_apply_snapshot():
|
|
@@ -57,3 +57,17 @@ def test_spread():
|
|
|
57
57
|
spread = ob.get_spread("t1")
|
|
58
58
|
assert spread is not None
|
|
59
59
|
assert abs(spread - 0.10) < 0.001
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
def test_apply_price_change_tracks_last_price():
|
|
63
|
+
ob = LocalOrderbook()
|
|
64
|
+
change = PriceChange(
|
|
65
|
+
market="m1",
|
|
66
|
+
assets=[PriceChangeAsset(asset_id="t1", price="0.57", outcome="Yes")],
|
|
67
|
+
)
|
|
68
|
+
ob.apply_price_change(change)
|
|
69
|
+
|
|
70
|
+
last = ob.get_last_price("t1")
|
|
71
|
+
assert last is not None
|
|
72
|
+
assert last.price == "0.57"
|
|
73
|
+
assert last.outcome == "Yes"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.9.1"
|
|
@@ -1,45 +0,0 @@
|
|
|
1
|
-
"""Tests for the sync REST client."""
|
|
2
|
-
|
|
3
|
-
import pytest
|
|
4
|
-
from polynode import PolyNode
|
|
5
|
-
from polynode.types.rest import StatusResponse, MarketsResponse, SettlementsResponse
|
|
6
|
-
|
|
7
|
-
TEST_KEY = "pn_live_test_session_tracking_51eca107e9b347b589f5b0a04f98eb1d"
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
@pytest.fixture
|
|
11
|
-
def pn():
|
|
12
|
-
client = PolyNode(api_key=TEST_KEY)
|
|
13
|
-
yield client
|
|
14
|
-
client.close()
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def test_healthz(pn):
|
|
18
|
-
result = pn.healthz()
|
|
19
|
-
assert result == "ok" or "ok" in str(result).lower()
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
def test_status(pn):
|
|
23
|
-
result = pn.status()
|
|
24
|
-
assert isinstance(result, StatusResponse)
|
|
25
|
-
assert result.uptime_seconds > 0
|
|
26
|
-
assert result.state.market_count > 0
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
def test_markets(pn):
|
|
30
|
-
result = pn.markets(count=3)
|
|
31
|
-
assert isinstance(result, MarketsResponse)
|
|
32
|
-
assert result.count > 0
|
|
33
|
-
assert len(result.markets) <= 3
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
def test_recent_settlements(pn):
|
|
37
|
-
result = pn.recent_settlements(count=1)
|
|
38
|
-
assert isinstance(result, SettlementsResponse)
|
|
39
|
-
assert result.count >= 0
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
def test_search(pn):
|
|
43
|
-
result = pn.search("election", limit=3)
|
|
44
|
-
assert result.query == "election"
|
|
45
|
-
assert result.count >= 0
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|