predigy-edge-sdk 2.0.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.
- predigy_edge_sdk-2.0.0/PKG-INFO +289 -0
- predigy_edge_sdk-2.0.0/README.md +264 -0
- predigy_edge_sdk-2.0.0/edge_sdk/__init__.py +35 -0
- predigy_edge_sdk-2.0.0/edge_sdk/_http.py +69 -0
- predigy_edge_sdk-2.0.0/edge_sdk/client.py +253 -0
- predigy_edge_sdk-2.0.0/edge_sdk/compliance.py +156 -0
- predigy_edge_sdk-2.0.0/edge_sdk/exceptions.py +42 -0
- predigy_edge_sdk-2.0.0/edge_sdk/lp.py +80 -0
- predigy_edge_sdk-2.0.0/edge_sdk/parlay.py +36 -0
- predigy_edge_sdk-2.0.0/edge_sdk/py.typed +0 -0
- predigy_edge_sdk-2.0.0/edge_sdk/retail.py +70 -0
- predigy_edge_sdk-2.0.0/edge_sdk/types.py +338 -0
- predigy_edge_sdk-2.0.0/edge_sdk/webhook.py +52 -0
- predigy_edge_sdk-2.0.0/predigy_edge_sdk.egg-info/PKG-INFO +289 -0
- predigy_edge_sdk-2.0.0/predigy_edge_sdk.egg-info/SOURCES.txt +25 -0
- predigy_edge_sdk-2.0.0/predigy_edge_sdk.egg-info/dependency_links.txt +1 -0
- predigy_edge_sdk-2.0.0/predigy_edge_sdk.egg-info/requires.txt +6 -0
- predigy_edge_sdk-2.0.0/predigy_edge_sdk.egg-info/top_level.txt +1 -0
- predigy_edge_sdk-2.0.0/pyproject.toml +42 -0
- predigy_edge_sdk-2.0.0/setup.cfg +4 -0
- predigy_edge_sdk-2.0.0/tests/test_client.py +153 -0
- predigy_edge_sdk-2.0.0/tests/test_compliance.py +119 -0
- predigy_edge_sdk-2.0.0/tests/test_lp.py +123 -0
- predigy_edge_sdk-2.0.0/tests/test_parlay.py +80 -0
- predigy_edge_sdk-2.0.0/tests/test_retail.py +117 -0
- predigy_edge_sdk-2.0.0/tests/test_webhook.py +55 -0
- predigy_edge_sdk-2.0.0/tests/test_webhook_events.py +58 -0
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: predigy-edge-sdk
|
|
3
|
+
Version: 2.0.0
|
|
4
|
+
Summary: Official Python SDK for the EDGE by Predigy prediction market API (retail, parlay, compliance/MICS, LP)
|
|
5
|
+
Author: Predigy LLC
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://edge-by-predigy.netlify.app
|
|
8
|
+
Project-URL: Repository, https://github.com/predigy/edge
|
|
9
|
+
Project-URL: Documentation, https://edge-production-7b77.up.railway.app/docs
|
|
10
|
+
Project-URL: Bug Tracker, https://github.com/predigy/edge/issues
|
|
11
|
+
Keywords: edge,predigy,prediction-market,lmsr,sdk,retail,parlay,compliance,mics,liquidity-provider
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
Requires-Dist: httpx>=0.25.0
|
|
21
|
+
Requires-Dist: pydantic>=2.0.0
|
|
22
|
+
Provides-Extra: dev
|
|
23
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
24
|
+
Requires-Dist: pytest-asyncio>=0.23; extra == "dev"
|
|
25
|
+
|
|
26
|
+
# EDGE Python SDK
|
|
27
|
+
|
|
28
|
+
Official Python SDK for the [EDGE by Predigy](https://edge-by-predigy.netlify.app) prediction market API.
|
|
29
|
+
|
|
30
|
+
EDGE is a prediction market pricing engine that integrates with sportsbook platforms. This SDK provides a fully-typed async client for all API operations.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## Installation
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install edge-sdk
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
**Requirements:** Python 3.11+
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
## Quick Start
|
|
45
|
+
|
|
46
|
+
```python
|
|
47
|
+
import asyncio
|
|
48
|
+
from edge_sdk import EdgeClient
|
|
49
|
+
|
|
50
|
+
async def main():
|
|
51
|
+
async with EdgeClient(
|
|
52
|
+
base_url="https://edge-production-7b77.up.railway.app",
|
|
53
|
+
api_key="your-api-key",
|
|
54
|
+
) as client:
|
|
55
|
+
# List open markets
|
|
56
|
+
result = await client.list_markets(status="OPEN")
|
|
57
|
+
for market in result.markets:
|
|
58
|
+
print(f"{market.title}: YES={market.prices.yes:.1%}, NO={market.prices.no:.1%}")
|
|
59
|
+
|
|
60
|
+
# Get a quote before trading
|
|
61
|
+
quote = await client.get_quote("mkt_abc123", side="YES", amount=50.0)
|
|
62
|
+
print(f"Cost: ${quote.total_cost:.2f} for {quote.contracts:.1f} contracts")
|
|
63
|
+
|
|
64
|
+
# Execute the trade
|
|
65
|
+
trade = await client.execute_trade("mkt_abc123", side="YES", amount=50.0)
|
|
66
|
+
print(f"Trade {trade.trade_id} executed! New balance: ${trade.new_balance:.2f}")
|
|
67
|
+
|
|
68
|
+
asyncio.run(main())
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## Authentication
|
|
74
|
+
|
|
75
|
+
Every request requires an API key passed in the `X-API-Key` header. The SDK handles this automatically:
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
client = EdgeClient(
|
|
79
|
+
base_url="https://edge-production-7b77.up.railway.app",
|
|
80
|
+
api_key="your-api-key",
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
You receive your API key when your operator account is created by the Predigy team.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## API Reference
|
|
89
|
+
|
|
90
|
+
### Markets
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
# List markets with optional filters
|
|
94
|
+
markets = await client.list_markets(status="OPEN", category="NBA", limit=10)
|
|
95
|
+
|
|
96
|
+
# Get a single market by external ID
|
|
97
|
+
market = await client.get_market("mkt_abc123")
|
|
98
|
+
|
|
99
|
+
# Create a new market (admin)
|
|
100
|
+
market = await client.create_market(
|
|
101
|
+
title="Lakers vs Celtics — Lakers Win",
|
|
102
|
+
category="NBA",
|
|
103
|
+
description="Will the Lakers win tonight's game?",
|
|
104
|
+
b_base=5000.0,
|
|
105
|
+
initial_price_yes=0.55,
|
|
106
|
+
)
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Quotes and Trades
|
|
110
|
+
|
|
111
|
+
```python
|
|
112
|
+
# Get a price quote (does not execute a trade)
|
|
113
|
+
quote = await client.get_quote("mkt_abc123", side="YES", amount=100.0)
|
|
114
|
+
print(f"Contracts: {quote.contracts}")
|
|
115
|
+
print(f"Avg price: ${quote.avg_fill_price:.4f}")
|
|
116
|
+
print(f"Fee: ${quote.fee:.2f} ({quote.fee_rate:.2%})")
|
|
117
|
+
print(f"Total cost: ${quote.total_cost:.2f}")
|
|
118
|
+
|
|
119
|
+
# Execute a trade
|
|
120
|
+
trade = await client.execute_trade("mkt_abc123", side="YES", amount=100.0)
|
|
121
|
+
|
|
122
|
+
# Execute with slippage protection
|
|
123
|
+
trade = await client.execute_trade(
|
|
124
|
+
"mkt_abc123", side="YES", amount=100.0,
|
|
125
|
+
max_avg_price=0.60, # Reject if avg price exceeds $0.60
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
# Sell (cash out) contracts from an existing position
|
|
129
|
+
sell = await client.sell_position("mkt_abc123", side="YES", contracts=50.0)
|
|
130
|
+
print(f"Net payout: ${sell.net_payout:.2f}")
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Portfolio
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
portfolio = await client.get_portfolio()
|
|
137
|
+
print(f"Balance: ${portfolio.balance:.2f}")
|
|
138
|
+
print(f"Unrealized P&L: ${portfolio.total_unrealized_pnl:.2f}")
|
|
139
|
+
|
|
140
|
+
for pos in portfolio.positions:
|
|
141
|
+
print(f" {pos.market_title} ({pos.side}): {pos.contracts} contracts, P&L: ${pos.unrealized_pnl:.2f}")
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
### Admin Operations
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
# Get platform statistics
|
|
148
|
+
stats = await client.get_stats()
|
|
149
|
+
print(f"Total markets: {stats.total_markets}")
|
|
150
|
+
print(f"Total volume: ${stats.total_volume:,.2f}")
|
|
151
|
+
|
|
152
|
+
# Settle a market
|
|
153
|
+
result = await client.settle_market("mkt_abc123", outcome="YES")
|
|
154
|
+
|
|
155
|
+
# Reset sandbox data
|
|
156
|
+
await client.reset_sandbox()
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
### Webhooks
|
|
160
|
+
|
|
161
|
+
```python
|
|
162
|
+
# Register a webhook endpoint
|
|
163
|
+
webhook = await client.create_webhook(
|
|
164
|
+
url="https://your-app.com/webhook",
|
|
165
|
+
events=["trade.executed", "market.settled"],
|
|
166
|
+
description="Production trade notifications",
|
|
167
|
+
)
|
|
168
|
+
print(f"Webhook ID: {webhook.webhook.external_id}")
|
|
169
|
+
print(f"Secret: {webhook.secret}") # Store this — shown only once!
|
|
170
|
+
|
|
171
|
+
# List webhooks
|
|
172
|
+
webhooks = await client.list_webhooks()
|
|
173
|
+
|
|
174
|
+
# Delete a webhook
|
|
175
|
+
await client.delete_webhook("whk_abc123")
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
### Health Check
|
|
179
|
+
|
|
180
|
+
```python
|
|
181
|
+
health = await client.health_check()
|
|
182
|
+
print(health) # {"status": "healthy"}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
---
|
|
186
|
+
|
|
187
|
+
## Error Handling
|
|
188
|
+
|
|
189
|
+
The SDK raises typed exceptions for different error scenarios:
|
|
190
|
+
|
|
191
|
+
```python
|
|
192
|
+
from edge_sdk.exceptions import (
|
|
193
|
+
EdgeAPIError, # Base class for all API errors
|
|
194
|
+
EdgeAuthError, # 401 — Invalid or missing API key
|
|
195
|
+
EdgeRateLimitError, # 429 — Too many requests
|
|
196
|
+
EdgeValidationError,# 422 — Invalid request data
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
try:
|
|
200
|
+
trade = await client.execute_trade("mkt_abc123", side="YES", amount=100.0)
|
|
201
|
+
except EdgeAuthError as e:
|
|
202
|
+
print(f"Authentication failed: {e.detail}")
|
|
203
|
+
except EdgeRateLimitError as e:
|
|
204
|
+
print(f"Rate limited. Retry after {e.retry_after} seconds")
|
|
205
|
+
except EdgeValidationError as e:
|
|
206
|
+
print(f"Invalid request: {e.detail}")
|
|
207
|
+
except EdgeAPIError as e:
|
|
208
|
+
print(f"API error {e.status_code}: {e.detail}")
|
|
209
|
+
print(f"Request ID: {e.request_id}") # Useful for support
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
All exceptions include a `request_id` field that you can reference when contacting support.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Webhook Verification
|
|
217
|
+
|
|
218
|
+
When receiving webhook deliveries, verify the HMAC-SHA256 signature to ensure the payload is authentic:
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from edge_sdk import verify_signature
|
|
222
|
+
|
|
223
|
+
# In your webhook handler (e.g., FastAPI)
|
|
224
|
+
@app.post("/webhook")
|
|
225
|
+
async def handle_webhook(request: Request):
|
|
226
|
+
body = await request.body()
|
|
227
|
+
signature = request.headers.get("X-Edge-Signature", "")
|
|
228
|
+
|
|
229
|
+
if not verify_signature(body, signature, WEBHOOK_SECRET):
|
|
230
|
+
raise HTTPException(401, "Invalid signature")
|
|
231
|
+
|
|
232
|
+
event = json.loads(body)
|
|
233
|
+
print(f"Received event: {event['event_type']}")
|
|
234
|
+
# Process event...
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
The `X-Edge-Signature` header format is `sha256=<hex_digest>`.
|
|
238
|
+
|
|
239
|
+
**Webhook event types:**
|
|
240
|
+
- `trade.executed` — A trade was placed
|
|
241
|
+
- `market.created` — A new market was created
|
|
242
|
+
- `market.settled` — A market was settled with an outcome
|
|
243
|
+
- `market.suspended` — A market was suspended
|
|
244
|
+
- `surge.activated` — Surge pricing was triggered
|
|
245
|
+
- `liquidity.adjusted` — Dynamic liquidity parameter changed
|
|
246
|
+
|
|
247
|
+
---
|
|
248
|
+
|
|
249
|
+
## Advanced Usage
|
|
250
|
+
|
|
251
|
+
### Custom HTTP Client
|
|
252
|
+
|
|
253
|
+
You can provide your own `httpx.AsyncClient` for custom timeouts, proxies, or connection pooling:
|
|
254
|
+
|
|
255
|
+
```python
|
|
256
|
+
import httpx
|
|
257
|
+
|
|
258
|
+
custom_client = httpx.AsyncClient(
|
|
259
|
+
base_url="https://edge-production-7b77.up.railway.app",
|
|
260
|
+
timeout=60.0,
|
|
261
|
+
headers={"X-API-Key": "your-api-key", "Content-Type": "application/json"},
|
|
262
|
+
limits=httpx.Limits(max_connections=20),
|
|
263
|
+
)
|
|
264
|
+
|
|
265
|
+
client = EdgeClient(
|
|
266
|
+
base_url="https://edge-production-7b77.up.railway.app",
|
|
267
|
+
api_key="your-api-key",
|
|
268
|
+
http_client=custom_client,
|
|
269
|
+
)
|
|
270
|
+
```
|
|
271
|
+
|
|
272
|
+
### Type Safety
|
|
273
|
+
|
|
274
|
+
The SDK is fully typed with Pydantic models. All responses are validated and provide IDE autocompletion. The `py.typed` marker (PEP 561) enables type checking in tools like mypy and pyright.
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## Links
|
|
279
|
+
|
|
280
|
+
- **API Documentation:** [docs/API.md](../../docs/API.md)
|
|
281
|
+
- **Integration Guide:** [docs/EDGE_INTEGRATION_GUIDE.md](../../docs/EDGE_INTEGRATION_GUIDE.md)
|
|
282
|
+
- **Live API (Swagger):** [edge-production-7b77.up.railway.app/docs](https://edge-production-7b77.up.railway.app/docs)
|
|
283
|
+
- **Frontend Demo:** [edge-by-predigy.netlify.app](https://edge-by-predigy.netlify.app)
|
|
284
|
+
|
|
285
|
+
---
|
|
286
|
+
|
|
287
|
+
## License
|
|
288
|
+
|
|
289
|
+
Proprietary. Copyright Predigy LLC. All rights reserved.
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
# EDGE Python SDK
|
|
2
|
+
|
|
3
|
+
Official Python SDK for the [EDGE by Predigy](https://edge-by-predigy.netlify.app) prediction market API.
|
|
4
|
+
|
|
5
|
+
EDGE is a prediction market pricing engine that integrates with sportsbook platforms. This SDK provides a fully-typed async client for all API operations.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Installation
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install edge-sdk
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
**Requirements:** Python 3.11+
|
|
16
|
+
|
|
17
|
+
---
|
|
18
|
+
|
|
19
|
+
## Quick Start
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
import asyncio
|
|
23
|
+
from edge_sdk import EdgeClient
|
|
24
|
+
|
|
25
|
+
async def main():
|
|
26
|
+
async with EdgeClient(
|
|
27
|
+
base_url="https://edge-production-7b77.up.railway.app",
|
|
28
|
+
api_key="your-api-key",
|
|
29
|
+
) as client:
|
|
30
|
+
# List open markets
|
|
31
|
+
result = await client.list_markets(status="OPEN")
|
|
32
|
+
for market in result.markets:
|
|
33
|
+
print(f"{market.title}: YES={market.prices.yes:.1%}, NO={market.prices.no:.1%}")
|
|
34
|
+
|
|
35
|
+
# Get a quote before trading
|
|
36
|
+
quote = await client.get_quote("mkt_abc123", side="YES", amount=50.0)
|
|
37
|
+
print(f"Cost: ${quote.total_cost:.2f} for {quote.contracts:.1f} contracts")
|
|
38
|
+
|
|
39
|
+
# Execute the trade
|
|
40
|
+
trade = await client.execute_trade("mkt_abc123", side="YES", amount=50.0)
|
|
41
|
+
print(f"Trade {trade.trade_id} executed! New balance: ${trade.new_balance:.2f}")
|
|
42
|
+
|
|
43
|
+
asyncio.run(main())
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Authentication
|
|
49
|
+
|
|
50
|
+
Every request requires an API key passed in the `X-API-Key` header. The SDK handles this automatically:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
client = EdgeClient(
|
|
54
|
+
base_url="https://edge-production-7b77.up.railway.app",
|
|
55
|
+
api_key="your-api-key",
|
|
56
|
+
)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
You receive your API key when your operator account is created by the Predigy team.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## API Reference
|
|
64
|
+
|
|
65
|
+
### Markets
|
|
66
|
+
|
|
67
|
+
```python
|
|
68
|
+
# List markets with optional filters
|
|
69
|
+
markets = await client.list_markets(status="OPEN", category="NBA", limit=10)
|
|
70
|
+
|
|
71
|
+
# Get a single market by external ID
|
|
72
|
+
market = await client.get_market("mkt_abc123")
|
|
73
|
+
|
|
74
|
+
# Create a new market (admin)
|
|
75
|
+
market = await client.create_market(
|
|
76
|
+
title="Lakers vs Celtics — Lakers Win",
|
|
77
|
+
category="NBA",
|
|
78
|
+
description="Will the Lakers win tonight's game?",
|
|
79
|
+
b_base=5000.0,
|
|
80
|
+
initial_price_yes=0.55,
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### Quotes and Trades
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
# Get a price quote (does not execute a trade)
|
|
88
|
+
quote = await client.get_quote("mkt_abc123", side="YES", amount=100.0)
|
|
89
|
+
print(f"Contracts: {quote.contracts}")
|
|
90
|
+
print(f"Avg price: ${quote.avg_fill_price:.4f}")
|
|
91
|
+
print(f"Fee: ${quote.fee:.2f} ({quote.fee_rate:.2%})")
|
|
92
|
+
print(f"Total cost: ${quote.total_cost:.2f}")
|
|
93
|
+
|
|
94
|
+
# Execute a trade
|
|
95
|
+
trade = await client.execute_trade("mkt_abc123", side="YES", amount=100.0)
|
|
96
|
+
|
|
97
|
+
# Execute with slippage protection
|
|
98
|
+
trade = await client.execute_trade(
|
|
99
|
+
"mkt_abc123", side="YES", amount=100.0,
|
|
100
|
+
max_avg_price=0.60, # Reject if avg price exceeds $0.60
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
# Sell (cash out) contracts from an existing position
|
|
104
|
+
sell = await client.sell_position("mkt_abc123", side="YES", contracts=50.0)
|
|
105
|
+
print(f"Net payout: ${sell.net_payout:.2f}")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### Portfolio
|
|
109
|
+
|
|
110
|
+
```python
|
|
111
|
+
portfolio = await client.get_portfolio()
|
|
112
|
+
print(f"Balance: ${portfolio.balance:.2f}")
|
|
113
|
+
print(f"Unrealized P&L: ${portfolio.total_unrealized_pnl:.2f}")
|
|
114
|
+
|
|
115
|
+
for pos in portfolio.positions:
|
|
116
|
+
print(f" {pos.market_title} ({pos.side}): {pos.contracts} contracts, P&L: ${pos.unrealized_pnl:.2f}")
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
### Admin Operations
|
|
120
|
+
|
|
121
|
+
```python
|
|
122
|
+
# Get platform statistics
|
|
123
|
+
stats = await client.get_stats()
|
|
124
|
+
print(f"Total markets: {stats.total_markets}")
|
|
125
|
+
print(f"Total volume: ${stats.total_volume:,.2f}")
|
|
126
|
+
|
|
127
|
+
# Settle a market
|
|
128
|
+
result = await client.settle_market("mkt_abc123", outcome="YES")
|
|
129
|
+
|
|
130
|
+
# Reset sandbox data
|
|
131
|
+
await client.reset_sandbox()
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Webhooks
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
# Register a webhook endpoint
|
|
138
|
+
webhook = await client.create_webhook(
|
|
139
|
+
url="https://your-app.com/webhook",
|
|
140
|
+
events=["trade.executed", "market.settled"],
|
|
141
|
+
description="Production trade notifications",
|
|
142
|
+
)
|
|
143
|
+
print(f"Webhook ID: {webhook.webhook.external_id}")
|
|
144
|
+
print(f"Secret: {webhook.secret}") # Store this — shown only once!
|
|
145
|
+
|
|
146
|
+
# List webhooks
|
|
147
|
+
webhooks = await client.list_webhooks()
|
|
148
|
+
|
|
149
|
+
# Delete a webhook
|
|
150
|
+
await client.delete_webhook("whk_abc123")
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
### Health Check
|
|
154
|
+
|
|
155
|
+
```python
|
|
156
|
+
health = await client.health_check()
|
|
157
|
+
print(health) # {"status": "healthy"}
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
---
|
|
161
|
+
|
|
162
|
+
## Error Handling
|
|
163
|
+
|
|
164
|
+
The SDK raises typed exceptions for different error scenarios:
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
from edge_sdk.exceptions import (
|
|
168
|
+
EdgeAPIError, # Base class for all API errors
|
|
169
|
+
EdgeAuthError, # 401 — Invalid or missing API key
|
|
170
|
+
EdgeRateLimitError, # 429 — Too many requests
|
|
171
|
+
EdgeValidationError,# 422 — Invalid request data
|
|
172
|
+
)
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
trade = await client.execute_trade("mkt_abc123", side="YES", amount=100.0)
|
|
176
|
+
except EdgeAuthError as e:
|
|
177
|
+
print(f"Authentication failed: {e.detail}")
|
|
178
|
+
except EdgeRateLimitError as e:
|
|
179
|
+
print(f"Rate limited. Retry after {e.retry_after} seconds")
|
|
180
|
+
except EdgeValidationError as e:
|
|
181
|
+
print(f"Invalid request: {e.detail}")
|
|
182
|
+
except EdgeAPIError as e:
|
|
183
|
+
print(f"API error {e.status_code}: {e.detail}")
|
|
184
|
+
print(f"Request ID: {e.request_id}") # Useful for support
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
All exceptions include a `request_id` field that you can reference when contacting support.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Webhook Verification
|
|
192
|
+
|
|
193
|
+
When receiving webhook deliveries, verify the HMAC-SHA256 signature to ensure the payload is authentic:
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
from edge_sdk import verify_signature
|
|
197
|
+
|
|
198
|
+
# In your webhook handler (e.g., FastAPI)
|
|
199
|
+
@app.post("/webhook")
|
|
200
|
+
async def handle_webhook(request: Request):
|
|
201
|
+
body = await request.body()
|
|
202
|
+
signature = request.headers.get("X-Edge-Signature", "")
|
|
203
|
+
|
|
204
|
+
if not verify_signature(body, signature, WEBHOOK_SECRET):
|
|
205
|
+
raise HTTPException(401, "Invalid signature")
|
|
206
|
+
|
|
207
|
+
event = json.loads(body)
|
|
208
|
+
print(f"Received event: {event['event_type']}")
|
|
209
|
+
# Process event...
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
The `X-Edge-Signature` header format is `sha256=<hex_digest>`.
|
|
213
|
+
|
|
214
|
+
**Webhook event types:**
|
|
215
|
+
- `trade.executed` — A trade was placed
|
|
216
|
+
- `market.created` — A new market was created
|
|
217
|
+
- `market.settled` — A market was settled with an outcome
|
|
218
|
+
- `market.suspended` — A market was suspended
|
|
219
|
+
- `surge.activated` — Surge pricing was triggered
|
|
220
|
+
- `liquidity.adjusted` — Dynamic liquidity parameter changed
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
## Advanced Usage
|
|
225
|
+
|
|
226
|
+
### Custom HTTP Client
|
|
227
|
+
|
|
228
|
+
You can provide your own `httpx.AsyncClient` for custom timeouts, proxies, or connection pooling:
|
|
229
|
+
|
|
230
|
+
```python
|
|
231
|
+
import httpx
|
|
232
|
+
|
|
233
|
+
custom_client = httpx.AsyncClient(
|
|
234
|
+
base_url="https://edge-production-7b77.up.railway.app",
|
|
235
|
+
timeout=60.0,
|
|
236
|
+
headers={"X-API-Key": "your-api-key", "Content-Type": "application/json"},
|
|
237
|
+
limits=httpx.Limits(max_connections=20),
|
|
238
|
+
)
|
|
239
|
+
|
|
240
|
+
client = EdgeClient(
|
|
241
|
+
base_url="https://edge-production-7b77.up.railway.app",
|
|
242
|
+
api_key="your-api-key",
|
|
243
|
+
http_client=custom_client,
|
|
244
|
+
)
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
### Type Safety
|
|
248
|
+
|
|
249
|
+
The SDK is fully typed with Pydantic models. All responses are validated and provide IDE autocompletion. The `py.typed` marker (PEP 561) enables type checking in tools like mypy and pyright.
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Links
|
|
254
|
+
|
|
255
|
+
- **API Documentation:** [docs/API.md](../../docs/API.md)
|
|
256
|
+
- **Integration Guide:** [docs/EDGE_INTEGRATION_GUIDE.md](../../docs/EDGE_INTEGRATION_GUIDE.md)
|
|
257
|
+
- **Live API (Swagger):** [edge-production-7b77.up.railway.app/docs](https://edge-production-7b77.up.railway.app/docs)
|
|
258
|
+
- **Frontend Demo:** [edge-by-predigy.netlify.app](https://edge-by-predigy.netlify.app)
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## License
|
|
263
|
+
|
|
264
|
+
Proprietary. Copyright Predigy LLC. All rights reserved.
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"""
|
|
2
|
+
EDGE by Predigy — Official Python SDK
|
|
3
|
+
|
|
4
|
+
Usage:
|
|
5
|
+
from edge_sdk import EdgeClient
|
|
6
|
+
|
|
7
|
+
async with EdgeClient(base_url="...", api_key="...") as client:
|
|
8
|
+
markets = await client.list_markets() # 1.x flat
|
|
9
|
+
ticket = await client.retail.mint_ticket(...) # 2.x grouped
|
|
10
|
+
parlay = await client.parlay.create_parlay(...) # 2.x grouped
|
|
11
|
+
report = await client.compliance.daily_results(...) # 2.x grouped
|
|
12
|
+
lp = await client.lp.designate_lp(...) # 2.x grouped
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
__version__ = "2.0.0"
|
|
16
|
+
|
|
17
|
+
from edge_sdk.client import EdgeClient
|
|
18
|
+
from edge_sdk.compliance import ComplianceClient
|
|
19
|
+
from edge_sdk.lp import LPClient
|
|
20
|
+
from edge_sdk.parlay import ParlayClient
|
|
21
|
+
from edge_sdk.retail import RetailClient
|
|
22
|
+
from edge_sdk.webhook import verify_signature
|
|
23
|
+
from edge_sdk.exceptions import EdgeAPIError, EdgeAuthError, EdgeRateLimitError
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"EdgeClient",
|
|
27
|
+
"RetailClient",
|
|
28
|
+
"ParlayClient",
|
|
29
|
+
"ComplianceClient",
|
|
30
|
+
"LPClient",
|
|
31
|
+
"verify_signature",
|
|
32
|
+
"EdgeAPIError",
|
|
33
|
+
"EdgeAuthError",
|
|
34
|
+
"EdgeRateLimitError",
|
|
35
|
+
]
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Internal HTTP client for EdgeClient and all 2.0 sub-clients.
|
|
3
|
+
|
|
4
|
+
Kept in a leading-underscore module so it stays private. Sub-clients
|
|
5
|
+
receive an instance via constructor injection. Same status-code -> typed
|
|
6
|
+
error mapping the 1.x EdgeClient._request used, now shareable.
|
|
7
|
+
"""
|
|
8
|
+
from __future__ import annotations
|
|
9
|
+
|
|
10
|
+
from typing import Any
|
|
11
|
+
|
|
12
|
+
import httpx
|
|
13
|
+
|
|
14
|
+
from edge_sdk.exceptions import (
|
|
15
|
+
EdgeAPIError,
|
|
16
|
+
EdgeAuthError,
|
|
17
|
+
EdgeRateLimitError,
|
|
18
|
+
EdgeValidationError,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class HttpClient:
|
|
23
|
+
def __init__(self, client: httpx.AsyncClient) -> None:
|
|
24
|
+
self._client = client
|
|
25
|
+
|
|
26
|
+
@property
|
|
27
|
+
def raw(self) -> httpx.AsyncClient:
|
|
28
|
+
"""Raw httpx client — exposed so EdgeClient.close() can reach it."""
|
|
29
|
+
return self._client
|
|
30
|
+
|
|
31
|
+
async def request(
|
|
32
|
+
self,
|
|
33
|
+
method: str,
|
|
34
|
+
path: str,
|
|
35
|
+
json: dict | None = None,
|
|
36
|
+
params: dict | None = None,
|
|
37
|
+
) -> Any:
|
|
38
|
+
"""Make an authenticated API request with typed-error mapping."""
|
|
39
|
+
if params:
|
|
40
|
+
params = {k: v for k, v in params.items() if v is not None}
|
|
41
|
+
|
|
42
|
+
response = await self._client.request(method, path, json=json, params=params)
|
|
43
|
+
request_id = response.headers.get("x-request-id")
|
|
44
|
+
|
|
45
|
+
if response.status_code == 401:
|
|
46
|
+
raise EdgeAuthError(self._detail(response), request_id)
|
|
47
|
+
if response.status_code == 429:
|
|
48
|
+
retry_after = response.headers.get("retry-after")
|
|
49
|
+
raise EdgeRateLimitError(
|
|
50
|
+
self._detail(response),
|
|
51
|
+
int(retry_after) if retry_after else None,
|
|
52
|
+
request_id,
|
|
53
|
+
)
|
|
54
|
+
if response.status_code == 422:
|
|
55
|
+
raise EdgeValidationError(self._detail(response), request_id)
|
|
56
|
+
if response.status_code >= 400:
|
|
57
|
+
raise EdgeAPIError(response.status_code, self._detail(response), request_id)
|
|
58
|
+
|
|
59
|
+
if response.status_code == 204:
|
|
60
|
+
return None
|
|
61
|
+
return response.json()
|
|
62
|
+
|
|
63
|
+
@staticmethod
|
|
64
|
+
def _detail(response: httpx.Response) -> str:
|
|
65
|
+
try:
|
|
66
|
+
body = response.json()
|
|
67
|
+
return body.get("detail", response.reason_phrase or "Unknown error")
|
|
68
|
+
except Exception:
|
|
69
|
+
return response.reason_phrase or "Unknown error"
|