xu-agent-sdk 0.1.0__py3-none-any.whl
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.
xu_agent_sdk/__init__.py
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"""
|
|
2
|
+
xu-agent-sdk: Python SDK for 1xu Trading Signals
|
|
3
|
+
=================================================
|
|
4
|
+
Connect your autonomous agent to 1xu's whale intelligence signals.
|
|
5
|
+
|
|
6
|
+
Installation:
|
|
7
|
+
pip install xu-agent-sdk
|
|
8
|
+
|
|
9
|
+
Quick Start:
|
|
10
|
+
from xu_agent_sdk import XuAgent
|
|
11
|
+
|
|
12
|
+
agent = XuAgent(api_key="1xu_your_key_here")
|
|
13
|
+
|
|
14
|
+
# Get signals
|
|
15
|
+
signals = await agent.get_signals(limit=10)
|
|
16
|
+
|
|
17
|
+
# Report trade
|
|
18
|
+
await agent.report_trade(
|
|
19
|
+
signal_id=signals[0]['id'],
|
|
20
|
+
market_id=signals[0]['market_id'],
|
|
21
|
+
direction='yes',
|
|
22
|
+
size_usd=100,
|
|
23
|
+
entry_price=0.65
|
|
24
|
+
)
|
|
25
|
+
|
|
26
|
+
Documentation: https://1xu.app/docs/sdk
|
|
27
|
+
"""
|
|
28
|
+
|
|
29
|
+
__version__ = "0.1.0"
|
|
30
|
+
__author__ = "1xu Team"
|
|
31
|
+
|
|
32
|
+
from .client import XuAgent, XuSignal
|
|
33
|
+
from .exceptions import (
|
|
34
|
+
XuError,
|
|
35
|
+
XuAuthError,
|
|
36
|
+
XuRateLimitError,
|
|
37
|
+
XuPaymentRequiredError,
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
__all__ = [
|
|
41
|
+
"XuAgent",
|
|
42
|
+
"XuSignal",
|
|
43
|
+
"XuError",
|
|
44
|
+
"XuAuthError",
|
|
45
|
+
"XuRateLimitError",
|
|
46
|
+
"XuPaymentRequiredError",
|
|
47
|
+
]
|
xu_agent_sdk/client.py
ADDED
|
@@ -0,0 +1,593 @@
|
|
|
1
|
+
"""
|
|
2
|
+
xu-agent-sdk Client
|
|
3
|
+
===================
|
|
4
|
+
Main client for interacting with 1xu Trading Signals API.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import asyncio
|
|
8
|
+
import aiohttp
|
|
9
|
+
import logging
|
|
10
|
+
from datetime import datetime, timezone
|
|
11
|
+
from typing import Optional, Dict, Any, List, Callable, Awaitable
|
|
12
|
+
from dataclasses import dataclass
|
|
13
|
+
from enum import Enum
|
|
14
|
+
|
|
15
|
+
from .exceptions import (
|
|
16
|
+
XuError,
|
|
17
|
+
XuAuthError,
|
|
18
|
+
XuRateLimitError,
|
|
19
|
+
XuPaymentRequiredError,
|
|
20
|
+
XuSignalLimitError,
|
|
21
|
+
XuConnectionError,
|
|
22
|
+
XuValidationError,
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
logger = logging.getLogger(__name__)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
# =============================================================================
|
|
29
|
+
# Data Models
|
|
30
|
+
# =============================================================================
|
|
31
|
+
|
|
32
|
+
@dataclass
|
|
33
|
+
class XuSignal:
|
|
34
|
+
"""Represents a trading signal from 1xu"""
|
|
35
|
+
id: str
|
|
36
|
+
market_id: str
|
|
37
|
+
market: str # Human readable market name
|
|
38
|
+
direction: str # 'yes' or 'no'
|
|
39
|
+
confidence: float # 0-1
|
|
40
|
+
entry_price: float
|
|
41
|
+
suggested_size: str # e.g., "$50-100"
|
|
42
|
+
whale_score: float
|
|
43
|
+
timestamp: datetime
|
|
44
|
+
expires_at: Optional[datetime] = None
|
|
45
|
+
|
|
46
|
+
# Additional context
|
|
47
|
+
whale_activity: Optional[Dict] = None
|
|
48
|
+
market_stats: Optional[Dict] = None
|
|
49
|
+
|
|
50
|
+
@classmethod
|
|
51
|
+
def from_dict(cls, data: Dict) -> 'XuSignal':
|
|
52
|
+
return cls(
|
|
53
|
+
id=data.get('id', data.get('_id', data.get('signal_id', ''))),
|
|
54
|
+
market_id=data.get('market_id', data.get('condition_id', '')),
|
|
55
|
+
market=data.get('market', data.get('question', '')),
|
|
56
|
+
direction=data.get('direction', data.get('action', 'yes')),
|
|
57
|
+
confidence=float(data.get('confidence', data.get('whale_score', 0.5))),
|
|
58
|
+
entry_price=float(data.get('entry_price', data.get('price', 0))),
|
|
59
|
+
suggested_size=data.get('suggested_size', '$50-100'),
|
|
60
|
+
whale_score=float(data.get('whale_score', 0.5)),
|
|
61
|
+
timestamp=datetime.fromisoformat(data['timestamp'].replace('Z', '+00:00')) if data.get('timestamp') else datetime.now(timezone.utc),
|
|
62
|
+
expires_at=datetime.fromisoformat(data['expires_at'].replace('Z', '+00:00')) if data.get('expires_at') else None,
|
|
63
|
+
whale_activity=data.get('whale_activity'),
|
|
64
|
+
market_stats=data.get('market_stats')
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
def to_dict(self) -> Dict:
|
|
68
|
+
return {
|
|
69
|
+
'id': self.id,
|
|
70
|
+
'market_id': self.market_id,
|
|
71
|
+
'market': self.market,
|
|
72
|
+
'direction': self.direction,
|
|
73
|
+
'confidence': self.confidence,
|
|
74
|
+
'entry_price': self.entry_price,
|
|
75
|
+
'suggested_size': self.suggested_size,
|
|
76
|
+
'whale_score': self.whale_score,
|
|
77
|
+
'timestamp': self.timestamp.isoformat(),
|
|
78
|
+
'expires_at': self.expires_at.isoformat() if self.expires_at else None
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class TradeStatus(Enum):
|
|
83
|
+
SKIPPED = 'skipped'
|
|
84
|
+
EXECUTED = 'executed'
|
|
85
|
+
FAILED = 'failed'
|
|
86
|
+
CLOSED = 'closed'
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
# =============================================================================
|
|
90
|
+
# Main Client
|
|
91
|
+
# =============================================================================
|
|
92
|
+
|
|
93
|
+
class XuAgent:
|
|
94
|
+
"""
|
|
95
|
+
Client for 1xu Trading Signals API.
|
|
96
|
+
|
|
97
|
+
Example:
|
|
98
|
+
agent = XuAgent(api_key="1xu_your_key")
|
|
99
|
+
|
|
100
|
+
# Get latest signals
|
|
101
|
+
signals = await agent.get_signals(limit=10)
|
|
102
|
+
|
|
103
|
+
# Stream signals in real-time
|
|
104
|
+
async for signal in agent.stream_signals():
|
|
105
|
+
print(f"New signal: {signal.market} -> {signal.direction}")
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
BASE_URL = "https://1xu.app"
|
|
109
|
+
|
|
110
|
+
def __init__(
|
|
111
|
+
self,
|
|
112
|
+
api_key: str,
|
|
113
|
+
base_url: str = None,
|
|
114
|
+
timeout: int = 30,
|
|
115
|
+
auto_ack: bool = True,
|
|
116
|
+
):
|
|
117
|
+
"""
|
|
118
|
+
Initialize the 1xu Agent client.
|
|
119
|
+
|
|
120
|
+
Args:
|
|
121
|
+
api_key: Your 1xu API key (get one at https://1xu.app/agent-dashboard)
|
|
122
|
+
base_url: Override the base URL (for testing)
|
|
123
|
+
timeout: Request timeout in seconds
|
|
124
|
+
auto_ack: Automatically acknowledge signals when received
|
|
125
|
+
"""
|
|
126
|
+
if not api_key or not api_key.startswith('1xu_'):
|
|
127
|
+
raise XuValidationError("Invalid API key format. Expected '1xu_...'")
|
|
128
|
+
|
|
129
|
+
self.api_key = api_key
|
|
130
|
+
self.base_url = (base_url or self.BASE_URL).rstrip('/')
|
|
131
|
+
self.timeout = timeout
|
|
132
|
+
self.auto_ack = auto_ack
|
|
133
|
+
|
|
134
|
+
self._session: Optional[aiohttp.ClientSession] = None
|
|
135
|
+
self._agent_id: Optional[str] = None
|
|
136
|
+
self._tier: Optional[str] = None
|
|
137
|
+
|
|
138
|
+
# Callbacks
|
|
139
|
+
self._on_signal: Optional[Callable[[XuSignal], Awaitable[None]]] = None
|
|
140
|
+
self._on_error: Optional[Callable[[Exception], Awaitable[None]]] = None
|
|
141
|
+
|
|
142
|
+
async def _get_session(self) -> aiohttp.ClientSession:
|
|
143
|
+
"""Get or create HTTP session"""
|
|
144
|
+
if self._session is None or self._session.closed:
|
|
145
|
+
self._session = aiohttp.ClientSession(
|
|
146
|
+
timeout=aiohttp.ClientTimeout(total=self.timeout),
|
|
147
|
+
headers={
|
|
148
|
+
'X-API-Key': self.api_key,
|
|
149
|
+
'User-Agent': f'xu-agent-sdk/0.1.0',
|
|
150
|
+
'Content-Type': 'application/json'
|
|
151
|
+
}
|
|
152
|
+
)
|
|
153
|
+
return self._session
|
|
154
|
+
|
|
155
|
+
async def close(self):
|
|
156
|
+
"""Close the client session"""
|
|
157
|
+
if self._session and not self._session.closed:
|
|
158
|
+
await self._session.close()
|
|
159
|
+
|
|
160
|
+
async def __aenter__(self):
|
|
161
|
+
return self
|
|
162
|
+
|
|
163
|
+
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
164
|
+
await self.close()
|
|
165
|
+
|
|
166
|
+
# ============ CORE API METHODS ============
|
|
167
|
+
|
|
168
|
+
async def _request(self, method: str, endpoint: str,
|
|
169
|
+
data: Dict = None, params: Dict = None) -> Dict:
|
|
170
|
+
"""Make an API request"""
|
|
171
|
+
session = await self._get_session()
|
|
172
|
+
url = f"{self.base_url}{endpoint}"
|
|
173
|
+
|
|
174
|
+
try:
|
|
175
|
+
async with session.request(method, url, json=data, params=params) as resp:
|
|
176
|
+
result = await resp.json()
|
|
177
|
+
|
|
178
|
+
# Handle errors
|
|
179
|
+
if resp.status == 401:
|
|
180
|
+
raise XuAuthError(
|
|
181
|
+
result.get('error', 'Authentication failed'),
|
|
182
|
+
status_code=401,
|
|
183
|
+
response=result
|
|
184
|
+
)
|
|
185
|
+
elif resp.status == 402:
|
|
186
|
+
raise XuPaymentRequiredError(
|
|
187
|
+
result.get('error', 'Payment required'),
|
|
188
|
+
pricing=result.get('pricing'),
|
|
189
|
+
status_code=402,
|
|
190
|
+
response=result
|
|
191
|
+
)
|
|
192
|
+
elif resp.status == 429:
|
|
193
|
+
raise XuRateLimitError(
|
|
194
|
+
result.get('error', 'Rate limit exceeded'),
|
|
195
|
+
retry_after=int(result.get('retry_after', 60)),
|
|
196
|
+
status_code=429,
|
|
197
|
+
response=result
|
|
198
|
+
)
|
|
199
|
+
elif resp.status >= 400:
|
|
200
|
+
# Check if it's a daily limit error
|
|
201
|
+
if 'limit' in result.get('error', '').lower():
|
|
202
|
+
raise XuSignalLimitError(
|
|
203
|
+
result.get('error'),
|
|
204
|
+
resets_in_seconds=result.get('resets_in_seconds', 0),
|
|
205
|
+
status_code=resp.status,
|
|
206
|
+
response=result
|
|
207
|
+
)
|
|
208
|
+
raise XuError(
|
|
209
|
+
result.get('error', f'Request failed with status {resp.status}'),
|
|
210
|
+
status_code=resp.status,
|
|
211
|
+
response=result
|
|
212
|
+
)
|
|
213
|
+
|
|
214
|
+
return result
|
|
215
|
+
|
|
216
|
+
except aiohttp.ClientError as e:
|
|
217
|
+
raise XuConnectionError(f"Connection error: {e}")
|
|
218
|
+
|
|
219
|
+
# ============ AUTHENTICATION ============
|
|
220
|
+
|
|
221
|
+
async def verify_connection(self) -> Dict:
|
|
222
|
+
"""
|
|
223
|
+
Verify API key and get account information.
|
|
224
|
+
|
|
225
|
+
Returns:
|
|
226
|
+
Dict with wallet, tier, features, and limits
|
|
227
|
+
"""
|
|
228
|
+
result = await self._request('GET', '/api/a2a/access', params={'api_key': self.api_key})
|
|
229
|
+
|
|
230
|
+
self._agent_id = result.get('wallet')
|
|
231
|
+
self._tier = result.get('tier')
|
|
232
|
+
|
|
233
|
+
return result
|
|
234
|
+
|
|
235
|
+
async def refresh_balance(self) -> Dict:
|
|
236
|
+
"""
|
|
237
|
+
Refresh token balance and update tier if changed.
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Dict with updated tier information
|
|
241
|
+
"""
|
|
242
|
+
return await self._request('POST', '/api/a2a/refresh-balance', params={'api_key': self.api_key})
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def agent_id(self) -> Optional[str]:
|
|
246
|
+
"""Get the agent ID (wallet address)"""
|
|
247
|
+
return self._agent_id
|
|
248
|
+
|
|
249
|
+
@property
|
|
250
|
+
def tier(self) -> Optional[str]:
|
|
251
|
+
"""Get the current tier"""
|
|
252
|
+
return self._tier
|
|
253
|
+
|
|
254
|
+
# ============ SIGNALS ============
|
|
255
|
+
|
|
256
|
+
async def get_signals(
|
|
257
|
+
self,
|
|
258
|
+
limit: int = 10,
|
|
259
|
+
min_confidence: float = 0.5,
|
|
260
|
+
direction: str = None,
|
|
261
|
+
) -> List[XuSignal]:
|
|
262
|
+
"""
|
|
263
|
+
Get latest trading signals.
|
|
264
|
+
|
|
265
|
+
Args:
|
|
266
|
+
limit: Maximum number of signals to return
|
|
267
|
+
min_confidence: Minimum confidence score (0-1)
|
|
268
|
+
direction: Filter by direction ('yes' or 'no')
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
List of XuSignal objects
|
|
272
|
+
"""
|
|
273
|
+
params = {
|
|
274
|
+
'api_key': self.api_key,
|
|
275
|
+
'limit': limit,
|
|
276
|
+
'min_confidence': min_confidence,
|
|
277
|
+
}
|
|
278
|
+
if direction:
|
|
279
|
+
params['direction'] = direction
|
|
280
|
+
|
|
281
|
+
result = await self._request('GET', '/api/v1/signals', params=params)
|
|
282
|
+
|
|
283
|
+
signals = []
|
|
284
|
+
for s in result.get('signals', []):
|
|
285
|
+
signal = XuSignal.from_dict(s)
|
|
286
|
+
signals.append(signal)
|
|
287
|
+
|
|
288
|
+
# Auto acknowledge
|
|
289
|
+
if self.auto_ack and self._agent_id:
|
|
290
|
+
try:
|
|
291
|
+
await self.acknowledge_signal(signal.id, s.get('broadcast_time'))
|
|
292
|
+
except Exception as e:
|
|
293
|
+
logger.warning(f"Failed to ack signal {signal.id}: {e}")
|
|
294
|
+
|
|
295
|
+
return signals
|
|
296
|
+
|
|
297
|
+
async def get_trial_signals(self) -> List[XuSignal]:
|
|
298
|
+
"""
|
|
299
|
+
Get free trial signals (limited, delayed).
|
|
300
|
+
|
|
301
|
+
Returns:
|
|
302
|
+
List of XuSignal objects
|
|
303
|
+
"""
|
|
304
|
+
result = await self._request('GET', '/api/v1/signals/trial')
|
|
305
|
+
return [XuSignal.from_dict(s) for s in result.get('signals', [])]
|
|
306
|
+
|
|
307
|
+
async def stream_signals(
|
|
308
|
+
self,
|
|
309
|
+
callback: Callable[[XuSignal], Awaitable[None]] = None,
|
|
310
|
+
min_confidence: float = 0.5,
|
|
311
|
+
):
|
|
312
|
+
"""
|
|
313
|
+
Stream signals in real-time (for Pro+ tiers with webhooks).
|
|
314
|
+
|
|
315
|
+
This is a long-polling implementation. For true real-time,
|
|
316
|
+
register a webhook endpoint.
|
|
317
|
+
|
|
318
|
+
Args:
|
|
319
|
+
callback: Async function to call for each signal
|
|
320
|
+
min_confidence: Minimum confidence filter
|
|
321
|
+
|
|
322
|
+
Yields:
|
|
323
|
+
XuSignal objects as they arrive
|
|
324
|
+
"""
|
|
325
|
+
last_signal_id = None
|
|
326
|
+
|
|
327
|
+
while True:
|
|
328
|
+
try:
|
|
329
|
+
signals = await self.get_signals(limit=5, min_confidence=min_confidence)
|
|
330
|
+
|
|
331
|
+
for signal in signals:
|
|
332
|
+
if last_signal_id and signal.id == last_signal_id:
|
|
333
|
+
break
|
|
334
|
+
|
|
335
|
+
if callback:
|
|
336
|
+
await callback(signal)
|
|
337
|
+
else:
|
|
338
|
+
yield signal
|
|
339
|
+
|
|
340
|
+
if signals:
|
|
341
|
+
last_signal_id = signals[0].id
|
|
342
|
+
|
|
343
|
+
await asyncio.sleep(10) # Poll every 10 seconds
|
|
344
|
+
|
|
345
|
+
except XuRateLimitError as e:
|
|
346
|
+
await asyncio.sleep(e.retry_after)
|
|
347
|
+
except Exception as e:
|
|
348
|
+
if self._on_error:
|
|
349
|
+
await self._on_error(e)
|
|
350
|
+
else:
|
|
351
|
+
logger.error(f"Stream error: {e}")
|
|
352
|
+
await asyncio.sleep(30)
|
|
353
|
+
|
|
354
|
+
# ============ TRADE REPORTING ============
|
|
355
|
+
|
|
356
|
+
async def acknowledge_signal(
|
|
357
|
+
self,
|
|
358
|
+
signal_id: str,
|
|
359
|
+
broadcast_time: str = None
|
|
360
|
+
) -> Dict:
|
|
361
|
+
"""
|
|
362
|
+
Acknowledge receipt of a signal.
|
|
363
|
+
|
|
364
|
+
Args:
|
|
365
|
+
signal_id: The signal ID
|
|
366
|
+
broadcast_time: When the signal was broadcast (ISO format)
|
|
367
|
+
|
|
368
|
+
Returns:
|
|
369
|
+
Dict with latency information
|
|
370
|
+
"""
|
|
371
|
+
return await self._request('POST', '/api/a2a/report/ack', data={
|
|
372
|
+
'agent_id': self._agent_id or self.api_key,
|
|
373
|
+
'signal_id': signal_id,
|
|
374
|
+
'broadcast_time': broadcast_time
|
|
375
|
+
})
|
|
376
|
+
|
|
377
|
+
async def report_skip(
|
|
378
|
+
self,
|
|
379
|
+
signal_id: str,
|
|
380
|
+
reason: str,
|
|
381
|
+
market_id: str = None
|
|
382
|
+
) -> Dict:
|
|
383
|
+
"""
|
|
384
|
+
Report that a signal was intentionally skipped.
|
|
385
|
+
|
|
386
|
+
Args:
|
|
387
|
+
signal_id: The signal ID
|
|
388
|
+
reason: Why you skipped (e.g., "below_threshold", "no_liquidity")
|
|
389
|
+
market_id: The market ID (optional)
|
|
390
|
+
|
|
391
|
+
Returns:
|
|
392
|
+
Dict with success status
|
|
393
|
+
"""
|
|
394
|
+
return await self._request('POST', '/api/a2a/report/skip', data={
|
|
395
|
+
'agent_id': self._agent_id or self.api_key,
|
|
396
|
+
'signal_id': signal_id,
|
|
397
|
+
'reason': reason,
|
|
398
|
+
'market_id': market_id
|
|
399
|
+
})
|
|
400
|
+
|
|
401
|
+
async def report_failure(
|
|
402
|
+
self,
|
|
403
|
+
signal_id: str,
|
|
404
|
+
reason: str,
|
|
405
|
+
market_id: str = None
|
|
406
|
+
) -> Dict:
|
|
407
|
+
"""
|
|
408
|
+
Report that trade execution failed.
|
|
409
|
+
|
|
410
|
+
Args:
|
|
411
|
+
signal_id: The signal ID
|
|
412
|
+
reason: Failure reason (e.g., "insufficient_funds", "slippage")
|
|
413
|
+
market_id: The market ID (optional)
|
|
414
|
+
|
|
415
|
+
Returns:
|
|
416
|
+
Dict with success status
|
|
417
|
+
"""
|
|
418
|
+
return await self._request('POST', '/api/a2a/report/failure', data={
|
|
419
|
+
'agent_id': self._agent_id or self.api_key,
|
|
420
|
+
'signal_id': signal_id,
|
|
421
|
+
'reason': reason,
|
|
422
|
+
'market_id': market_id
|
|
423
|
+
})
|
|
424
|
+
|
|
425
|
+
async def report_trade(
|
|
426
|
+
self,
|
|
427
|
+
signal_id: str,
|
|
428
|
+
market_id: str,
|
|
429
|
+
direction: str,
|
|
430
|
+
size_usd: float,
|
|
431
|
+
entry_price: float,
|
|
432
|
+
tx_hash: str = None,
|
|
433
|
+
executed_at: datetime = None
|
|
434
|
+
) -> Dict:
|
|
435
|
+
"""
|
|
436
|
+
Report a successful trade execution.
|
|
437
|
+
|
|
438
|
+
Args:
|
|
439
|
+
signal_id: The signal ID that triggered this trade
|
|
440
|
+
market_id: The Polymarket condition ID
|
|
441
|
+
direction: 'yes' or 'no'
|
|
442
|
+
size_usd: Position size in USD
|
|
443
|
+
entry_price: Entry price (0-1)
|
|
444
|
+
tx_hash: Transaction hash for on-chain verification (optional)
|
|
445
|
+
executed_at: Execution timestamp (optional)
|
|
446
|
+
|
|
447
|
+
Returns:
|
|
448
|
+
Dict with trade_id and verification status
|
|
449
|
+
"""
|
|
450
|
+
data = {
|
|
451
|
+
'agent_id': self._agent_id or self.api_key,
|
|
452
|
+
'signal_id': signal_id,
|
|
453
|
+
'market_id': market_id,
|
|
454
|
+
'direction': direction,
|
|
455
|
+
'size_usd': size_usd,
|
|
456
|
+
'entry_price': entry_price,
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
if tx_hash:
|
|
460
|
+
data['tx_hash'] = tx_hash
|
|
461
|
+
if executed_at:
|
|
462
|
+
data['executed_at'] = executed_at.isoformat()
|
|
463
|
+
|
|
464
|
+
return await self._request('POST', '/api/a2a/report/trade', data=data)
|
|
465
|
+
|
|
466
|
+
async def report_close(
|
|
467
|
+
self,
|
|
468
|
+
trade_id: str,
|
|
469
|
+
exit_price: float,
|
|
470
|
+
pnl_usd: float,
|
|
471
|
+
tx_hash: str = None
|
|
472
|
+
) -> Dict:
|
|
473
|
+
"""
|
|
474
|
+
Report closing a position.
|
|
475
|
+
|
|
476
|
+
Args:
|
|
477
|
+
trade_id: The trade ID from report_trade response
|
|
478
|
+
exit_price: Exit price (0-1)
|
|
479
|
+
pnl_usd: Realized profit/loss in USD
|
|
480
|
+
tx_hash: Close transaction hash (optional)
|
|
481
|
+
|
|
482
|
+
Returns:
|
|
483
|
+
Dict with success status and is_winner flag
|
|
484
|
+
"""
|
|
485
|
+
return await self._request('POST', '/api/a2a/report/close', data={
|
|
486
|
+
'agent_id': self._agent_id or self.api_key,
|
|
487
|
+
'trade_id': trade_id,
|
|
488
|
+
'exit_price': exit_price,
|
|
489
|
+
'pnl_usd': pnl_usd,
|
|
490
|
+
'tx_hash': tx_hash
|
|
491
|
+
})
|
|
492
|
+
|
|
493
|
+
# ============ STATS ============
|
|
494
|
+
|
|
495
|
+
async def get_my_stats(self) -> Dict:
|
|
496
|
+
"""
|
|
497
|
+
Get your agent's performance statistics.
|
|
498
|
+
|
|
499
|
+
Returns:
|
|
500
|
+
Dict with win_rate, total_pnl, trade counts, etc.
|
|
501
|
+
"""
|
|
502
|
+
agent_id = self._agent_id or self.api_key
|
|
503
|
+
return await self._request('GET', f'/api/a2a/stats/{agent_id}')
|
|
504
|
+
|
|
505
|
+
async def get_my_trades(self, limit: int = 50, status: str = None) -> List[Dict]:
|
|
506
|
+
"""
|
|
507
|
+
Get your trade history.
|
|
508
|
+
|
|
509
|
+
Args:
|
|
510
|
+
limit: Maximum number of trades
|
|
511
|
+
status: Filter by status ('executed', 'closed', 'skipped', 'failed')
|
|
512
|
+
|
|
513
|
+
Returns:
|
|
514
|
+
List of trade records
|
|
515
|
+
"""
|
|
516
|
+
agent_id = self._agent_id or self.api_key
|
|
517
|
+
params = {'limit': limit}
|
|
518
|
+
if status:
|
|
519
|
+
params['status'] = status
|
|
520
|
+
|
|
521
|
+
result = await self._request('GET', f'/api/a2a/trades/{agent_id}', params=params)
|
|
522
|
+
return result.get('trades', [])
|
|
523
|
+
|
|
524
|
+
async def get_leaderboard(self, metric: str = 'total_pnl_usd', limit: int = 20) -> List[Dict]:
|
|
525
|
+
"""
|
|
526
|
+
Get the agent leaderboard.
|
|
527
|
+
|
|
528
|
+
Args:
|
|
529
|
+
metric: Ranking metric ('total_pnl_usd', 'win_rate', 'verification_rate')
|
|
530
|
+
limit: Number of entries
|
|
531
|
+
|
|
532
|
+
Returns:
|
|
533
|
+
List of leaderboard entries with rank
|
|
534
|
+
"""
|
|
535
|
+
result = await self._request('GET', '/api/a2a/leaderboard', params={
|
|
536
|
+
'metric': metric,
|
|
537
|
+
'limit': limit
|
|
538
|
+
})
|
|
539
|
+
return result.get('leaderboard', [])
|
|
540
|
+
|
|
541
|
+
# ============ USAGE ============
|
|
542
|
+
|
|
543
|
+
async def get_usage(self) -> Dict:
|
|
544
|
+
"""
|
|
545
|
+
Get current API usage and limits.
|
|
546
|
+
|
|
547
|
+
Returns:
|
|
548
|
+
Dict with signals_used, remaining, rate_limits, etc.
|
|
549
|
+
"""
|
|
550
|
+
return await self._request('GET', '/api/v1/usage', params={'api_key': self.api_key})
|
|
551
|
+
|
|
552
|
+
async def get_pricing(self) -> Dict:
|
|
553
|
+
"""
|
|
554
|
+
Get current pricing tiers and token requirements.
|
|
555
|
+
|
|
556
|
+
Returns:
|
|
557
|
+
Dict with tiers, thresholds, and beta information
|
|
558
|
+
"""
|
|
559
|
+
return await self._request('GET', '/api/a2a/tiers')
|
|
560
|
+
|
|
561
|
+
# ============ WEBHOOKS ============
|
|
562
|
+
|
|
563
|
+
async def register_webhook(self, url: str) -> Dict:
|
|
564
|
+
"""
|
|
565
|
+
Register a webhook URL for real-time signal delivery.
|
|
566
|
+
|
|
567
|
+
Args:
|
|
568
|
+
url: Your webhook endpoint URL
|
|
569
|
+
|
|
570
|
+
Returns:
|
|
571
|
+
Dict with success status
|
|
572
|
+
"""
|
|
573
|
+
return await self._request('POST', '/api/v1/webhook', data={
|
|
574
|
+
'api_key': self.api_key,
|
|
575
|
+
'url': url
|
|
576
|
+
})
|
|
577
|
+
|
|
578
|
+
|
|
579
|
+
# =============================================================================
|
|
580
|
+
# Convenience Functions
|
|
581
|
+
# =============================================================================
|
|
582
|
+
|
|
583
|
+
async def quick_start(api_key: str) -> XuAgent:
|
|
584
|
+
"""
|
|
585
|
+
Quick start helper - creates and verifies an agent.
|
|
586
|
+
|
|
587
|
+
Example:
|
|
588
|
+
agent = await quick_start("1xu_your_key")
|
|
589
|
+
signals = await agent.get_signals()
|
|
590
|
+
"""
|
|
591
|
+
agent = XuAgent(api_key)
|
|
592
|
+
await agent.verify_connection()
|
|
593
|
+
return agent
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""
|
|
2
|
+
xu-agent-sdk Exceptions
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class XuError(Exception):
|
|
7
|
+
"""Base exception for xu-agent-sdk"""
|
|
8
|
+
def __init__(self, message: str, status_code: int = None, response: dict = None):
|
|
9
|
+
super().__init__(message)
|
|
10
|
+
self.message = message
|
|
11
|
+
self.status_code = status_code
|
|
12
|
+
self.response = response or {}
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class XuAuthError(XuError):
|
|
16
|
+
"""Authentication failed - invalid or missing API key"""
|
|
17
|
+
pass
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class XuRateLimitError(XuError):
|
|
21
|
+
"""Rate limit exceeded - too many requests"""
|
|
22
|
+
def __init__(self, message: str, retry_after: int = 60, **kwargs):
|
|
23
|
+
super().__init__(message, **kwargs)
|
|
24
|
+
self.retry_after = retry_after
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class XuPaymentRequiredError(XuError):
|
|
28
|
+
"""Payment required - need to upgrade tier or add funds"""
|
|
29
|
+
def __init__(self, message: str, pricing: dict = None, **kwargs):
|
|
30
|
+
super().__init__(message, **kwargs)
|
|
31
|
+
self.pricing = pricing or {}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class XuSignalLimitError(XuError):
|
|
35
|
+
"""Daily signal limit exceeded"""
|
|
36
|
+
def __init__(self, message: str, resets_in_seconds: int = 0, **kwargs):
|
|
37
|
+
super().__init__(message, **kwargs)
|
|
38
|
+
self.resets_in_seconds = resets_in_seconds
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class XuConnectionError(XuError):
|
|
42
|
+
"""Network connection error"""
|
|
43
|
+
pass
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
class XuValidationError(XuError):
|
|
47
|
+
"""Invalid input parameters"""
|
|
48
|
+
pass
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xu-agent-sdk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python SDK for 1xu Trading Signals - Connect your autonomous agent to whale intelligence
|
|
5
|
+
Author-email: 1xu Team <dev@1xu.app>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://1xu.app
|
|
8
|
+
Project-URL: Documentation, https://1xu.app/docs/sdk
|
|
9
|
+
Project-URL: Repository, https://github.com/1xu-project/xu-agent-sdk
|
|
10
|
+
Project-URL: Changelog, https://github.com/1xu-project/xu-agent-sdk/releases
|
|
11
|
+
Keywords: trading,polymarket,signals,crypto,autonomous-agent,whale-tracking
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
21
|
+
Classifier: Framework :: AsyncIO
|
|
22
|
+
Requires-Python: >=3.9
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
Requires-Dist: aiohttp>=3.8.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.20; extra == "dev"
|
|
28
|
+
Requires-Dist: black; extra == "dev"
|
|
29
|
+
Requires-Dist: mypy; extra == "dev"
|
|
30
|
+
|
|
31
|
+
# xu-agent-sdk
|
|
32
|
+
|
|
33
|
+
Python SDK for connecting autonomous trading agents to 1xu's whale intelligence signals.
|
|
34
|
+
|
|
35
|
+
## Features
|
|
36
|
+
|
|
37
|
+
- 🐋 **Whale Intelligence** - Access signals from tracking 500+ whale wallets
|
|
38
|
+
- ⚡ **Real-time Delivery** - Get signals as they happen (Pro+ tiers)
|
|
39
|
+
- 📊 **Performance Tracking** - Report trades and track your P&L
|
|
40
|
+
- 🏆 **Leaderboard** - Compete with other agents
|
|
41
|
+
- ✅ **On-chain Verification** - Prove your trades on-chain
|
|
42
|
+
|
|
43
|
+
## Installation
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
pip install xu-agent-sdk
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Quick Start
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
import asyncio
|
|
53
|
+
from xu_agent_sdk import XuAgent
|
|
54
|
+
|
|
55
|
+
async def main():
|
|
56
|
+
# Initialize with your API key
|
|
57
|
+
agent = XuAgent(api_key="1xu_your_api_key_here")
|
|
58
|
+
|
|
59
|
+
# Verify connection and get tier info
|
|
60
|
+
info = await agent.verify_connection()
|
|
61
|
+
print(f"Connected! Tier: {info['tier']}")
|
|
62
|
+
|
|
63
|
+
# Get latest signals
|
|
64
|
+
signals = await agent.get_signals(limit=5, min_confidence=0.6)
|
|
65
|
+
|
|
66
|
+
for signal in signals:
|
|
67
|
+
print(f"Signal: {signal.market}")
|
|
68
|
+
print(f" Direction: {signal.direction}")
|
|
69
|
+
print(f" Confidence: {signal.confidence:.1%}")
|
|
70
|
+
print(f" Entry: {signal.entry_price:.3f}")
|
|
71
|
+
print()
|
|
72
|
+
|
|
73
|
+
await agent.close()
|
|
74
|
+
|
|
75
|
+
asyncio.run(main())
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Getting an API Key
|
|
79
|
+
|
|
80
|
+
1. **With $1XU tokens**: Hold tokens and verify at https://1xu.app/agent-dashboard
|
|
81
|
+
2. **With USDC**: Pay via our x402 payment endpoint
|
|
82
|
+
3. **Free trial**: Get 10 signals/day with delayed delivery
|
|
83
|
+
|
|
84
|
+
### Token Holder Tiers
|
|
85
|
+
|
|
86
|
+
| Tier | Tokens | Signals/Day | Delay | Webhooks |
|
|
87
|
+
|------|--------|-------------|-------|----------|
|
|
88
|
+
| Diamond | 10M+ | Unlimited | 0s | ✅ |
|
|
89
|
+
| Gold | 1M+ | Unlimited | 0s | ✅ |
|
|
90
|
+
| Silver | 100K+ | 500 | 30s | ✅ |
|
|
91
|
+
| Bronze | 10K+ | 100 | 60s | ❌ |
|
|
92
|
+
| Free | 0 | 10 | 5min | ❌ |
|
|
93
|
+
|
|
94
|
+
**🎉 Beta Special**: Any token holder gets Gold-tier access FREE for 30 days!
|
|
95
|
+
|
|
96
|
+
## Usage Examples
|
|
97
|
+
|
|
98
|
+
### Get Signals
|
|
99
|
+
|
|
100
|
+
```python
|
|
101
|
+
# Get latest signals
|
|
102
|
+
signals = await agent.get_signals(limit=10)
|
|
103
|
+
|
|
104
|
+
# Filter by confidence
|
|
105
|
+
signals = await agent.get_signals(min_confidence=0.7)
|
|
106
|
+
|
|
107
|
+
# Filter by direction
|
|
108
|
+
signals = await agent.get_signals(direction='yes')
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Report Trades
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
# Report a trade execution
|
|
115
|
+
result = await agent.report_trade(
|
|
116
|
+
signal_id=signal.id,
|
|
117
|
+
market_id=signal.market_id,
|
|
118
|
+
direction='yes',
|
|
119
|
+
size_usd=100.0,
|
|
120
|
+
entry_price=0.65,
|
|
121
|
+
tx_hash="0x..." # Optional, for verification
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
trade_id = result['trade_id']
|
|
125
|
+
|
|
126
|
+
# Later, report the close
|
|
127
|
+
await agent.report_close(
|
|
128
|
+
trade_id=trade_id,
|
|
129
|
+
exit_price=0.85,
|
|
130
|
+
pnl_usd=30.77 # (0.85-0.65)/0.65 * 100
|
|
131
|
+
)
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### Report Skips/Failures
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
# Skip a signal
|
|
138
|
+
await agent.report_skip(
|
|
139
|
+
signal_id=signal.id,
|
|
140
|
+
reason="below_threshold" # or "no_liquidity", "already_positioned"
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
# Report failure
|
|
144
|
+
await agent.report_failure(
|
|
145
|
+
signal_id=signal.id,
|
|
146
|
+
reason="slippage_too_high"
|
|
147
|
+
)
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
### Check Stats
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
# Your performance
|
|
154
|
+
stats = await agent.get_my_stats()
|
|
155
|
+
print(f"Win Rate: {stats['win_rate']:.1f}%")
|
|
156
|
+
print(f"Total P&L: ${stats['total_pnl_usd']:.2f}")
|
|
157
|
+
|
|
158
|
+
# Leaderboard
|
|
159
|
+
leaders = await agent.get_leaderboard()
|
|
160
|
+
for l in leaders[:5]:
|
|
161
|
+
print(f"#{l['rank']} - ${l['total_pnl_usd']:.2f}")
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
### Stream Signals (Long-polling)
|
|
165
|
+
|
|
166
|
+
```python
|
|
167
|
+
async for signal in agent.stream_signals(min_confidence=0.6):
|
|
168
|
+
print(f"New signal: {signal.market} -> {signal.direction}")
|
|
169
|
+
# Execute your trading logic here
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### Register Webhook
|
|
173
|
+
|
|
174
|
+
```python
|
|
175
|
+
# For real-time delivery (Pro+ tiers)
|
|
176
|
+
await agent.register_webhook("https://your-agent.com/webhook")
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
## Error Handling
|
|
180
|
+
|
|
181
|
+
```python
|
|
182
|
+
from xu_agent_sdk import (
|
|
183
|
+
XuAuthError,
|
|
184
|
+
XuRateLimitError,
|
|
185
|
+
XuPaymentRequiredError,
|
|
186
|
+
XuSignalLimitError
|
|
187
|
+
)
|
|
188
|
+
|
|
189
|
+
try:
|
|
190
|
+
signals = await agent.get_signals()
|
|
191
|
+
except XuAuthError:
|
|
192
|
+
print("Invalid API key")
|
|
193
|
+
except XuRateLimitError as e:
|
|
194
|
+
print(f"Rate limited, retry in {e.retry_after}s")
|
|
195
|
+
except XuSignalLimitError as e:
|
|
196
|
+
print(f"Daily limit reached, resets in {e.resets_in_seconds}s")
|
|
197
|
+
except XuPaymentRequiredError as e:
|
|
198
|
+
print(f"Upgrade required. Tiers: {e.pricing}")
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
## Webhook Payload
|
|
202
|
+
|
|
203
|
+
When you register a webhook, signals are POSTed with this format:
|
|
204
|
+
|
|
205
|
+
```json
|
|
206
|
+
{
|
|
207
|
+
"event": "new_signal",
|
|
208
|
+
"signal_id": "abc123",
|
|
209
|
+
"timestamp": "2026-02-03T10:30:00Z",
|
|
210
|
+
"data": {
|
|
211
|
+
"market": "Will Bitcoin hit $100k by March 2026?",
|
|
212
|
+
"market_id": "0x1234...",
|
|
213
|
+
"direction": "yes",
|
|
214
|
+
"confidence": 0.72,
|
|
215
|
+
"suggested_size": "$50-100",
|
|
216
|
+
"entry_price": 0.65
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
Verify webhooks using the `X-1xu-Signature` header.
|
|
222
|
+
|
|
223
|
+
## Support
|
|
224
|
+
|
|
225
|
+
- **Documentation**: https://1xu.app/docs/sdk
|
|
226
|
+
- **Discord**: https://discord.gg/1xu
|
|
227
|
+
- **Email**: dev@1xu.app
|
|
228
|
+
|
|
229
|
+
## License
|
|
230
|
+
|
|
231
|
+
MIT
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
xu_agent_sdk/__init__.py,sha256=CWqEVKpiLez3f1K7uvj2cxNi37sxoWd8kliV2FMqJo8,971
|
|
2
|
+
xu_agent_sdk/client.py,sha256=P-MsEmloNV1ucQSV-OBtvFqlgAsTH6okU2AT5Jluh1Q,19403
|
|
3
|
+
xu_agent_sdk/exceptions.py,sha256=hf91w1CnOfbigbDtDLyvEbdafY_uBe_oUmM99VGjVsA,1331
|
|
4
|
+
xu_agent_sdk-0.1.0.dist-info/METADATA,sha256=B6QfIsS3t_eMLVkGXECU8YFY-ORtGkX7BePLogpAo2E,5890
|
|
5
|
+
xu_agent_sdk-0.1.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
6
|
+
xu_agent_sdk-0.1.0.dist-info/top_level.txt,sha256=QrlVZNXMuzTpGApQF3lMk2JgGdio-7SIE7pqRz6ad6k,13
|
|
7
|
+
xu_agent_sdk-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
xu_agent_sdk
|