oxarchive 0.5.4__tar.gz → 0.6.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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oxarchive
3
- Version: 0.5.4
3
+ Version: 0.6.0
4
4
  Summary: Official Python SDK for 0xarchive - Hyperliquid Historical Data API
5
5
  Project-URL: Homepage, https://0xarchive.io
6
6
  Project-URL: Documentation, https://0xarchive.io/docs/sdks
@@ -406,6 +406,66 @@ candles = await client.hyperliquid.candles.ahistory("BTC", start=..., end=..., i
406
406
  | `1d` | 1 day |
407
407
  | `1w` | 1 week |
408
408
 
409
+ ### Data Quality Monitoring
410
+
411
+ Monitor data coverage, incidents, latency, and SLA compliance across all exchanges.
412
+
413
+ ```python
414
+ # Get overall system health status
415
+ status = client.data_quality.status()
416
+ print(f"System status: {status.status}")
417
+ for exchange, info in status.exchanges.items():
418
+ print(f" {exchange}: {info.status}")
419
+
420
+ # Get data coverage summary for all exchanges
421
+ coverage = client.data_quality.coverage()
422
+ for exchange in coverage.exchanges:
423
+ print(f"{exchange.exchange}:")
424
+ for dtype, info in exchange.data_types.items():
425
+ print(f" {dtype}: {info.total_records:,} records, {info.completeness}% complete")
426
+
427
+ # Get symbol-specific coverage with gap detection
428
+ btc = client.data_quality.symbol_coverage("hyperliquid", "BTC")
429
+ oi = btc.data_types["open_interest"]
430
+ print(f"BTC OI completeness: {oi.completeness}%")
431
+ print(f"Gaps found: {len(oi.gaps)}")
432
+ for gap in oi.gaps[:5]:
433
+ print(f" {gap.duration_minutes} min gap: {gap.start} -> {gap.end}")
434
+
435
+ # List incidents with filtering
436
+ result = client.data_quality.list_incidents(status="open")
437
+ for incident in result.incidents:
438
+ print(f"[{incident.severity}] {incident.title}")
439
+
440
+ # Get latency metrics
441
+ latency = client.data_quality.latency()
442
+ for exchange, metrics in latency.exchanges.items():
443
+ print(f"{exchange}: OB lag {metrics.data_freshness.orderbook_lag_ms}ms")
444
+
445
+ # Get SLA compliance metrics for a specific month
446
+ sla = client.data_quality.sla(year=2026, month=1)
447
+ print(f"Period: {sla.period}")
448
+ print(f"Uptime: {sla.actual.uptime}% ({sla.actual.uptime_status})")
449
+ print(f"API P99: {sla.actual.api_latency_p99_ms}ms ({sla.actual.latency_status})")
450
+
451
+ # Async versions available for all methods
452
+ status = await client.data_quality.astatus()
453
+ coverage = await client.data_quality.acoverage()
454
+ ```
455
+
456
+ #### Data Quality Endpoints
457
+
458
+ | Method | Description |
459
+ |--------|-------------|
460
+ | `status()` | Overall system health and per-exchange status |
461
+ | `coverage()` | Data coverage summary for all exchanges |
462
+ | `exchange_coverage(exchange)` | Coverage details for a specific exchange |
463
+ | `symbol_coverage(exchange, symbol)` | Coverage with gap detection for specific symbol |
464
+ | `list_incidents(...)` | List incidents with filtering and pagination |
465
+ | `get_incident(incident_id)` | Get specific incident details |
466
+ | `latency()` | Current latency metrics (WebSocket, REST, data freshness) |
467
+ | `sla(year, month)` | SLA compliance metrics for a specific month |
468
+
409
469
  ### Legacy API (Deprecated)
410
470
 
411
471
  The following legacy methods are deprecated and will be removed in v2.0. They default to Hyperliquid data:
@@ -369,6 +369,66 @@ candles = await client.hyperliquid.candles.ahistory("BTC", start=..., end=..., i
369
369
  | `1d` | 1 day |
370
370
  | `1w` | 1 week |
371
371
 
372
+ ### Data Quality Monitoring
373
+
374
+ Monitor data coverage, incidents, latency, and SLA compliance across all exchanges.
375
+
376
+ ```python
377
+ # Get overall system health status
378
+ status = client.data_quality.status()
379
+ print(f"System status: {status.status}")
380
+ for exchange, info in status.exchanges.items():
381
+ print(f" {exchange}: {info.status}")
382
+
383
+ # Get data coverage summary for all exchanges
384
+ coverage = client.data_quality.coverage()
385
+ for exchange in coverage.exchanges:
386
+ print(f"{exchange.exchange}:")
387
+ for dtype, info in exchange.data_types.items():
388
+ print(f" {dtype}: {info.total_records:,} records, {info.completeness}% complete")
389
+
390
+ # Get symbol-specific coverage with gap detection
391
+ btc = client.data_quality.symbol_coverage("hyperliquid", "BTC")
392
+ oi = btc.data_types["open_interest"]
393
+ print(f"BTC OI completeness: {oi.completeness}%")
394
+ print(f"Gaps found: {len(oi.gaps)}")
395
+ for gap in oi.gaps[:5]:
396
+ print(f" {gap.duration_minutes} min gap: {gap.start} -> {gap.end}")
397
+
398
+ # List incidents with filtering
399
+ result = client.data_quality.list_incidents(status="open")
400
+ for incident in result.incidents:
401
+ print(f"[{incident.severity}] {incident.title}")
402
+
403
+ # Get latency metrics
404
+ latency = client.data_quality.latency()
405
+ for exchange, metrics in latency.exchanges.items():
406
+ print(f"{exchange}: OB lag {metrics.data_freshness.orderbook_lag_ms}ms")
407
+
408
+ # Get SLA compliance metrics for a specific month
409
+ sla = client.data_quality.sla(year=2026, month=1)
410
+ print(f"Period: {sla.period}")
411
+ print(f"Uptime: {sla.actual.uptime}% ({sla.actual.uptime_status})")
412
+ print(f"API P99: {sla.actual.api_latency_p99_ms}ms ({sla.actual.latency_status})")
413
+
414
+ # Async versions available for all methods
415
+ status = await client.data_quality.astatus()
416
+ coverage = await client.data_quality.acoverage()
417
+ ```
418
+
419
+ #### Data Quality Endpoints
420
+
421
+ | Method | Description |
422
+ |--------|-------------|
423
+ | `status()` | Overall system health and per-exchange status |
424
+ | `coverage()` | Data coverage summary for all exchanges |
425
+ | `exchange_coverage(exchange)` | Coverage details for a specific exchange |
426
+ | `symbol_coverage(exchange, symbol)` | Coverage with gap detection for specific symbol |
427
+ | `list_incidents(...)` | List incidents with filtering and pagination |
428
+ | `get_incident(incident_id)` | Get specific incident details |
429
+ | `latency()` | Current latency metrics (WebSocket, REST, data freshness) |
430
+ | `sla(year, month)` | SLA compliance metrics for a specific month |
431
+
372
432
  ### Legacy API (Deprecated)
373
433
 
374
434
  The following legacy methods are deprecated and will be removed in v2.0. They default to Hyperliquid data:
@@ -68,7 +68,7 @@ except ImportError:
68
68
  OxArchiveWs = None # type: ignore
69
69
  WsOptions = None # type: ignore
70
70
 
71
- __version__ = "0.5.4"
71
+ __version__ = "0.6.0"
72
72
 
73
73
  __all__ = [
74
74
  # Client
@@ -12,6 +12,7 @@ from .resources import (
12
12
  InstrumentsResource,
13
13
  FundingResource,
14
14
  OpenInterestResource,
15
+ DataQualityResource,
15
16
  )
16
17
 
17
18
  DEFAULT_BASE_URL = "https://api.0xarchive.io"
@@ -92,6 +93,10 @@ class Client:
92
93
  self.lighter = LighterClient(self._http)
93
94
  """Lighter.xyz exchange data (August 2025+)"""
94
95
 
96
+ # Data quality monitoring (cross-exchange)
97
+ self.data_quality = DataQualityResource(self._http)
98
+ """Data quality metrics: status, coverage, incidents, latency, SLA"""
99
+
95
100
  # Legacy resource namespaces (deprecated - use client.hyperliquid.* instead)
96
101
  # These will be removed in v2.0
97
102
  # Note: Using /v1/hyperliquid base path for backward compatibility
@@ -7,6 +7,7 @@ from .funding import FundingResource
7
7
  from .openinterest import OpenInterestResource
8
8
  from .candles import CandlesResource
9
9
  from .liquidations import LiquidationsResource
10
+ from .data_quality import DataQualityResource
10
11
 
11
12
  __all__ = [
12
13
  "OrderBookResource",
@@ -17,4 +18,5 @@ __all__ = [
17
18
  "OpenInterestResource",
18
19
  "CandlesResource",
19
20
  "LiquidationsResource",
21
+ "DataQualityResource",
20
22
  ]
@@ -0,0 +1,336 @@
1
+ """Data quality API resource."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from datetime import datetime
6
+ from typing import Literal, Optional
7
+
8
+ from ..http import HttpClient
9
+ from ..types import (
10
+ CoverageResponse,
11
+ ExchangeCoverage,
12
+ Incident,
13
+ IncidentsResponse,
14
+ LatencyResponse,
15
+ SlaResponse,
16
+ StatusResponse,
17
+ SymbolCoverageResponse,
18
+ Timestamp,
19
+ )
20
+
21
+
22
+ class DataQualityResource:
23
+ """
24
+ Data quality API resource.
25
+
26
+ Provides endpoints for monitoring data quality, coverage, incidents, and SLA metrics.
27
+
28
+ Example:
29
+ >>> # Get system status
30
+ >>> status = client.data_quality.status()
31
+ >>> print(f"System status: {status.status}")
32
+ >>>
33
+ >>> # Get coverage for all exchanges
34
+ >>> coverage = client.data_quality.coverage()
35
+ >>>
36
+ >>> # Get symbol-specific coverage with gap detection
37
+ >>> btc = client.data_quality.symbol_coverage("hyperliquid", "BTC")
38
+ >>> print(f"BTC completeness: {btc.data_types['orderbook'].completeness}%")
39
+ >>> for gap in btc.data_types['orderbook'].gaps[:5]:
40
+ ... print(f"Gap: {gap.start} - {gap.end} ({gap.duration_minutes} min)")
41
+ """
42
+
43
+ def __init__(self, http: HttpClient, base_path: str = "/v1/data-quality"):
44
+ self._http = http
45
+ self._base_path = base_path
46
+
47
+ def _convert_timestamp(self, ts: Optional[Timestamp]) -> Optional[int]:
48
+ """Convert timestamp to Unix milliseconds."""
49
+ if ts is None:
50
+ return None
51
+ if isinstance(ts, int):
52
+ return ts
53
+ if isinstance(ts, datetime):
54
+ return int(ts.timestamp() * 1000)
55
+ if isinstance(ts, str):
56
+ try:
57
+ dt = datetime.fromisoformat(ts.replace("Z", "+00:00"))
58
+ return int(dt.timestamp() * 1000)
59
+ except ValueError:
60
+ return int(ts)
61
+ return None
62
+
63
+ # =========================================================================
64
+ # Status Endpoints
65
+ # =========================================================================
66
+
67
+ def status(self) -> StatusResponse:
68
+ """
69
+ Get overall system health status.
70
+
71
+ Returns:
72
+ StatusResponse with overall status, per-exchange status,
73
+ per-data-type status, and active incident count.
74
+
75
+ Example:
76
+ >>> status = client.data_quality.status()
77
+ >>> print(f"Overall: {status.status}")
78
+ >>> for exchange, info in status.exchanges.items():
79
+ ... print(f"{exchange}: {info.status}")
80
+ """
81
+ data = self._http.get(f"{self._base_path}/status")
82
+ return StatusResponse.model_validate(data)
83
+
84
+ async def astatus(self) -> StatusResponse:
85
+ """Async version of status()."""
86
+ data = await self._http.aget(f"{self._base_path}/status")
87
+ return StatusResponse.model_validate(data)
88
+
89
+ # =========================================================================
90
+ # Coverage Endpoints
91
+ # =========================================================================
92
+
93
+ def coverage(self) -> CoverageResponse:
94
+ """
95
+ Get data coverage summary for all exchanges.
96
+
97
+ Returns:
98
+ CoverageResponse with coverage info for all exchanges and data types.
99
+
100
+ Example:
101
+ >>> coverage = client.data_quality.coverage()
102
+ >>> for exchange in coverage.exchanges:
103
+ ... print(f"{exchange.exchange}:")
104
+ ... for dtype, info in exchange.data_types.items():
105
+ ... print(f" {dtype}: {info.total_records} records")
106
+ """
107
+ data = self._http.get(f"{self._base_path}/coverage")
108
+ return CoverageResponse.model_validate(data)
109
+
110
+ async def acoverage(self) -> CoverageResponse:
111
+ """Async version of coverage()."""
112
+ data = await self._http.aget(f"{self._base_path}/coverage")
113
+ return CoverageResponse.model_validate(data)
114
+
115
+ def exchange_coverage(self, exchange: str) -> ExchangeCoverage:
116
+ """
117
+ Get data coverage for a specific exchange.
118
+
119
+ Args:
120
+ exchange: Exchange name ('hyperliquid' or 'lighter')
121
+
122
+ Returns:
123
+ ExchangeCoverage with coverage info for all data types on this exchange.
124
+
125
+ Example:
126
+ >>> hl = client.data_quality.exchange_coverage("hyperliquid")
127
+ >>> print(f"Orderbook earliest: {hl.data_types['orderbook'].earliest}")
128
+ """
129
+ data = self._http.get(f"{self._base_path}/coverage/{exchange.lower()}")
130
+ return ExchangeCoverage.model_validate(data)
131
+
132
+ async def aexchange_coverage(self, exchange: str) -> ExchangeCoverage:
133
+ """Async version of exchange_coverage()."""
134
+ data = await self._http.aget(f"{self._base_path}/coverage/{exchange.lower()}")
135
+ return ExchangeCoverage.model_validate(data)
136
+
137
+ def symbol_coverage(self, exchange: str, symbol: str) -> SymbolCoverageResponse:
138
+ """
139
+ Get data coverage for a specific symbol on an exchange.
140
+
141
+ Includes gap detection showing periods where data may be missing.
142
+
143
+ Args:
144
+ exchange: Exchange name ('hyperliquid' or 'lighter')
145
+ symbol: Symbol name (e.g., 'BTC', 'ETH')
146
+
147
+ Returns:
148
+ SymbolCoverageResponse with per-data-type coverage including gaps.
149
+
150
+ Example:
151
+ >>> btc = client.data_quality.symbol_coverage("hyperliquid", "BTC")
152
+ >>> oi = btc.data_types["open_interest"]
153
+ >>> print(f"OI completeness: {oi.completeness}%")
154
+ >>> print(f"Gaps found: {len(oi.gaps)}")
155
+ >>> for gap in oi.gaps[:3]:
156
+ ... print(f" {gap.duration_minutes} min gap at {gap.start}")
157
+ """
158
+ data = self._http.get(
159
+ f"{self._base_path}/coverage/{exchange.lower()}/{symbol.upper()}"
160
+ )
161
+ return SymbolCoverageResponse.model_validate(data)
162
+
163
+ async def asymbol_coverage(self, exchange: str, symbol: str) -> SymbolCoverageResponse:
164
+ """Async version of symbol_coverage()."""
165
+ data = await self._http.aget(
166
+ f"{self._base_path}/coverage/{exchange.lower()}/{symbol.upper()}"
167
+ )
168
+ return SymbolCoverageResponse.model_validate(data)
169
+
170
+ # =========================================================================
171
+ # Incidents Endpoints
172
+ # =========================================================================
173
+
174
+ def list_incidents(
175
+ self,
176
+ *,
177
+ status: Optional[Literal["open", "investigating", "identified", "monitoring", "resolved"]] = None,
178
+ exchange: Optional[str] = None,
179
+ since: Optional[Timestamp] = None,
180
+ limit: Optional[int] = None,
181
+ offset: Optional[int] = None,
182
+ ) -> IncidentsResponse:
183
+ """
184
+ List incidents with filtering and pagination.
185
+
186
+ Args:
187
+ status: Filter by incident status
188
+ exchange: Filter by exchange
189
+ since: Only show incidents starting after this timestamp
190
+ limit: Maximum results per page (default: 20, max: 100)
191
+ offset: Pagination offset
192
+
193
+ Returns:
194
+ IncidentsResponse with list of incidents and pagination info.
195
+
196
+ Example:
197
+ >>> # Get all open incidents
198
+ >>> result = client.data_quality.list_incidents(status="open")
199
+ >>> for incident in result.incidents:
200
+ ... print(f"{incident.severity}: {incident.title}")
201
+ """
202
+ data = self._http.get(
203
+ f"{self._base_path}/incidents",
204
+ params={
205
+ "status": status,
206
+ "exchange": exchange,
207
+ "since": self._convert_timestamp(since),
208
+ "limit": limit,
209
+ "offset": offset,
210
+ },
211
+ )
212
+ return IncidentsResponse.model_validate(data)
213
+
214
+ async def alist_incidents(
215
+ self,
216
+ *,
217
+ status: Optional[Literal["open", "investigating", "identified", "monitoring", "resolved"]] = None,
218
+ exchange: Optional[str] = None,
219
+ since: Optional[Timestamp] = None,
220
+ limit: Optional[int] = None,
221
+ offset: Optional[int] = None,
222
+ ) -> IncidentsResponse:
223
+ """Async version of list_incidents()."""
224
+ data = await self._http.aget(
225
+ f"{self._base_path}/incidents",
226
+ params={
227
+ "status": status,
228
+ "exchange": exchange,
229
+ "since": self._convert_timestamp(since),
230
+ "limit": limit,
231
+ "offset": offset,
232
+ },
233
+ )
234
+ return IncidentsResponse.model_validate(data)
235
+
236
+ def get_incident(self, incident_id: str) -> Incident:
237
+ """
238
+ Get a specific incident by ID.
239
+
240
+ Args:
241
+ incident_id: The incident ID
242
+
243
+ Returns:
244
+ Incident details.
245
+
246
+ Example:
247
+ >>> incident = client.data_quality.get_incident("inc_123")
248
+ >>> print(f"Status: {incident.status}")
249
+ >>> print(f"Root cause: {incident.root_cause}")
250
+ """
251
+ data = self._http.get(f"{self._base_path}/incidents/{incident_id}")
252
+ return Incident.model_validate(data)
253
+
254
+ async def aget_incident(self, incident_id: str) -> Incident:
255
+ """Async version of get_incident()."""
256
+ data = await self._http.aget(f"{self._base_path}/incidents/{incident_id}")
257
+ return Incident.model_validate(data)
258
+
259
+ # =========================================================================
260
+ # Latency Endpoints
261
+ # =========================================================================
262
+
263
+ def latency(self) -> LatencyResponse:
264
+ """
265
+ Get current latency metrics for all exchanges.
266
+
267
+ Returns:
268
+ LatencyResponse with WebSocket, REST API, and data freshness metrics.
269
+
270
+ Example:
271
+ >>> latency = client.data_quality.latency()
272
+ >>> for exchange, metrics in latency.exchanges.items():
273
+ ... print(f"{exchange}:")
274
+ ... if metrics.websocket:
275
+ ... print(f" WS current: {metrics.websocket.current_ms}ms")
276
+ ... print(f" OB lag: {metrics.data_freshness.orderbook_lag_ms}ms")
277
+ """
278
+ data = self._http.get(f"{self._base_path}/latency")
279
+ return LatencyResponse.model_validate(data)
280
+
281
+ async def alatency(self) -> LatencyResponse:
282
+ """Async version of latency()."""
283
+ data = await self._http.aget(f"{self._base_path}/latency")
284
+ return LatencyResponse.model_validate(data)
285
+
286
+ # =========================================================================
287
+ # SLA Endpoints
288
+ # =========================================================================
289
+
290
+ def sla(
291
+ self,
292
+ *,
293
+ year: Optional[int] = None,
294
+ month: Optional[int] = None,
295
+ ) -> SlaResponse:
296
+ """
297
+ Get SLA compliance metrics for a specific month.
298
+
299
+ Args:
300
+ year: Year (defaults to current year)
301
+ month: Month 1-12 (defaults to current month)
302
+
303
+ Returns:
304
+ SlaResponse with SLA targets, actual metrics, and compliance status.
305
+
306
+ Example:
307
+ >>> sla = client.data_quality.sla(year=2026, month=1)
308
+ >>> print(f"Period: {sla.period}")
309
+ >>> print(f"Uptime: {sla.actual.uptime}% ({sla.actual.uptime_status})")
310
+ >>> print(f"Completeness: {sla.actual.data_completeness.overall}%")
311
+ >>> print(f"API P99: {sla.actual.api_latency_p99_ms}ms")
312
+ """
313
+ data = self._http.get(
314
+ f"{self._base_path}/sla",
315
+ params={
316
+ "year": year,
317
+ "month": month,
318
+ },
319
+ )
320
+ return SlaResponse.model_validate(data)
321
+
322
+ async def asla(
323
+ self,
324
+ *,
325
+ year: Optional[int] = None,
326
+ month: Optional[int] = None,
327
+ ) -> SlaResponse:
328
+ """Async version of sla()."""
329
+ data = await self._http.aget(
330
+ f"{self._base_path}/sla",
331
+ params={
332
+ "year": year,
333
+ "month": month,
334
+ },
335
+ )
336
+ return SlaResponse.model_validate(data)
@@ -593,3 +593,353 @@ class CursorResponse(BaseModel, Generic[T]):
593
593
  # Type alias for timestamp parameters
594
594
  Timestamp = Union[int, str, datetime]
595
595
  """Timestamp can be Unix ms (int), ISO string, or datetime object."""
596
+
597
+
598
+ # =============================================================================
599
+ # Data Quality Types
600
+ # =============================================================================
601
+
602
+
603
+ class SystemStatus(BaseModel):
604
+ """System status values: operational, degraded, outage, maintenance."""
605
+
606
+ status: Literal["operational", "degraded", "outage", "maintenance"]
607
+
608
+
609
+ class ExchangeStatus(BaseModel):
610
+ """Status of a single exchange."""
611
+
612
+ status: Literal["operational", "degraded", "outage", "maintenance"]
613
+ """Current status."""
614
+
615
+ last_data_at: Optional[datetime] = None
616
+ """Timestamp of last received data."""
617
+
618
+ latency_ms: Optional[int] = None
619
+ """Current latency in milliseconds."""
620
+
621
+
622
+ class DataTypeStatus(BaseModel):
623
+ """Status of a data type (orderbook, fills, etc.)."""
624
+
625
+ status: Literal["operational", "degraded", "outage", "maintenance"]
626
+ """Current status."""
627
+
628
+ completeness_24h: float
629
+ """Data completeness over last 24 hours (0-100)."""
630
+
631
+
632
+ class StatusResponse(BaseModel):
633
+ """Overall system status response."""
634
+
635
+ status: Literal["operational", "degraded", "outage", "maintenance"]
636
+ """Overall system status."""
637
+
638
+ updated_at: datetime
639
+ """When this status was computed."""
640
+
641
+ exchanges: dict[str, ExchangeStatus]
642
+ """Per-exchange status."""
643
+
644
+ data_types: dict[str, DataTypeStatus]
645
+ """Per-data-type status."""
646
+
647
+ active_incidents: int
648
+ """Number of active incidents."""
649
+
650
+
651
+ class DataTypeCoverage(BaseModel):
652
+ """Coverage information for a specific data type."""
653
+
654
+ earliest: datetime
655
+ """Earliest available data timestamp."""
656
+
657
+ latest: datetime
658
+ """Latest available data timestamp."""
659
+
660
+ total_records: int
661
+ """Total number of records."""
662
+
663
+ symbols: int
664
+ """Number of symbols with data."""
665
+
666
+ resolution: Optional[str] = None
667
+ """Data resolution (e.g., '1.2s', '1m')."""
668
+
669
+ lag: Optional[str] = None
670
+ """Current data lag."""
671
+
672
+ completeness: float
673
+ """Completeness percentage (0-100)."""
674
+
675
+
676
+ class ExchangeCoverage(BaseModel):
677
+ """Coverage for a single exchange."""
678
+
679
+ exchange: str
680
+ """Exchange name."""
681
+
682
+ data_types: dict[str, DataTypeCoverage]
683
+ """Coverage per data type."""
684
+
685
+
686
+ class CoverageResponse(BaseModel):
687
+ """Overall coverage response."""
688
+
689
+ exchanges: list[ExchangeCoverage]
690
+ """Coverage for all exchanges."""
691
+
692
+
693
+ class CoverageGap(BaseModel):
694
+ """Gap information for per-symbol coverage."""
695
+
696
+ start: datetime
697
+ """Start of the gap (last data before gap)."""
698
+
699
+ end: datetime
700
+ """End of the gap (first data after gap)."""
701
+
702
+ duration_minutes: int
703
+ """Duration of the gap in minutes."""
704
+
705
+
706
+ class SymbolDataTypeCoverage(BaseModel):
707
+ """Coverage for a specific symbol and data type."""
708
+
709
+ earliest: datetime
710
+ """Earliest available data timestamp."""
711
+
712
+ latest: datetime
713
+ """Latest available data timestamp."""
714
+
715
+ total_records: int
716
+ """Total number of records."""
717
+
718
+ completeness: float
719
+ """Completeness percentage (0-100)."""
720
+
721
+ gaps: list[CoverageGap]
722
+ """Detected data gaps."""
723
+
724
+
725
+ class SymbolCoverageResponse(BaseModel):
726
+ """Per-symbol coverage response."""
727
+
728
+ exchange: str
729
+ """Exchange name."""
730
+
731
+ symbol: str
732
+ """Symbol name."""
733
+
734
+ data_types: dict[str, SymbolDataTypeCoverage]
735
+ """Coverage per data type."""
736
+
737
+
738
+ class Incident(BaseModel):
739
+ """Data quality incident."""
740
+
741
+ id: str
742
+ """Unique incident ID."""
743
+
744
+ status: str
745
+ """Status: open, investigating, identified, monitoring, resolved."""
746
+
747
+ severity: str
748
+ """Severity: minor, major, critical."""
749
+
750
+ exchange: Optional[str] = None
751
+ """Affected exchange (if specific to one)."""
752
+
753
+ data_types: list[str]
754
+ """Affected data types."""
755
+
756
+ symbols_affected: list[str]
757
+ """Affected symbols."""
758
+
759
+ started_at: datetime
760
+ """When the incident started."""
761
+
762
+ resolved_at: Optional[datetime] = None
763
+ """When the incident was resolved."""
764
+
765
+ duration_minutes: Optional[int] = None
766
+ """Total duration in minutes."""
767
+
768
+ title: str
769
+ """Incident title."""
770
+
771
+ description: Optional[str] = None
772
+ """Detailed description."""
773
+
774
+ root_cause: Optional[str] = None
775
+ """Root cause analysis."""
776
+
777
+ resolution: Optional[str] = None
778
+ """Resolution details."""
779
+
780
+ records_affected: Optional[int] = None
781
+ """Number of records affected."""
782
+
783
+ records_recovered: Optional[int] = None
784
+ """Number of records recovered."""
785
+
786
+
787
+ class Pagination(BaseModel):
788
+ """Pagination info for incident list."""
789
+
790
+ total: int
791
+ """Total number of incidents."""
792
+
793
+ limit: int
794
+ """Page size limit."""
795
+
796
+ offset: int
797
+ """Current offset."""
798
+
799
+
800
+ class IncidentsResponse(BaseModel):
801
+ """Incidents list response."""
802
+
803
+ incidents: list[Incident]
804
+ """List of incidents."""
805
+
806
+ pagination: Pagination
807
+ """Pagination info."""
808
+
809
+
810
+ class WebSocketLatency(BaseModel):
811
+ """WebSocket latency metrics."""
812
+
813
+ current_ms: int
814
+ """Current latency."""
815
+
816
+ avg_1h_ms: int
817
+ """1-hour average latency."""
818
+
819
+ avg_24h_ms: int
820
+ """24-hour average latency."""
821
+
822
+ p99_24h_ms: Optional[int] = None
823
+ """24-hour P99 latency."""
824
+
825
+
826
+ class ApiLatency(BaseModel):
827
+ """REST API latency metrics."""
828
+
829
+ current_ms: int
830
+ """Current latency."""
831
+
832
+ avg_1h_ms: int
833
+ """1-hour average latency."""
834
+
835
+ avg_24h_ms: int
836
+ """24-hour average latency."""
837
+
838
+
839
+ class DataFreshness(BaseModel):
840
+ """Data freshness metrics (lag from source)."""
841
+
842
+ orderbook_lag_ms: Optional[int] = None
843
+ """Orderbook data lag."""
844
+
845
+ fills_lag_ms: Optional[int] = None
846
+ """Fills/trades data lag."""
847
+
848
+ funding_lag_ms: Optional[int] = None
849
+ """Funding rate data lag."""
850
+
851
+ oi_lag_ms: Optional[int] = None
852
+ """Open interest data lag."""
853
+
854
+
855
+ class ExchangeLatency(BaseModel):
856
+ """Latency metrics for a single exchange."""
857
+
858
+ websocket: Optional[WebSocketLatency] = None
859
+ """WebSocket latency metrics."""
860
+
861
+ rest_api: Optional[ApiLatency] = None
862
+ """REST API latency metrics."""
863
+
864
+ data_freshness: DataFreshness
865
+ """Data freshness metrics."""
866
+
867
+
868
+ class LatencyResponse(BaseModel):
869
+ """Overall latency response."""
870
+
871
+ measured_at: datetime
872
+ """When these metrics were measured."""
873
+
874
+ exchanges: dict[str, ExchangeLatency]
875
+ """Per-exchange latency metrics."""
876
+
877
+
878
+ class SlaTargets(BaseModel):
879
+ """SLA targets."""
880
+
881
+ uptime: float
882
+ """Uptime target percentage."""
883
+
884
+ data_completeness: float
885
+ """Data completeness target percentage."""
886
+
887
+ api_latency_p99_ms: int
888
+ """API P99 latency target in milliseconds."""
889
+
890
+
891
+ class CompletenessMetrics(BaseModel):
892
+ """Completeness metrics per data type."""
893
+
894
+ orderbook: float
895
+ """Orderbook completeness percentage."""
896
+
897
+ fills: float
898
+ """Fills completeness percentage."""
899
+
900
+ funding: float
901
+ """Funding rate completeness percentage."""
902
+
903
+ overall: float
904
+ """Overall completeness percentage."""
905
+
906
+
907
+ class SlaActual(BaseModel):
908
+ """Actual SLA metrics."""
909
+
910
+ uptime: float
911
+ """Actual uptime percentage."""
912
+
913
+ uptime_status: str
914
+ """'met' or 'missed'."""
915
+
916
+ data_completeness: CompletenessMetrics
917
+ """Actual completeness metrics."""
918
+
919
+ completeness_status: str
920
+ """'met' or 'missed'."""
921
+
922
+ api_latency_p99_ms: int
923
+ """Actual API P99 latency."""
924
+
925
+ latency_status: str
926
+ """'met' or 'missed'."""
927
+
928
+
929
+ class SlaResponse(BaseModel):
930
+ """SLA compliance response."""
931
+
932
+ period: str
933
+ """Period covered (e.g., '2026-01')."""
934
+
935
+ sla_targets: SlaTargets
936
+ """Target SLA metrics."""
937
+
938
+ actual: SlaActual
939
+ """Actual SLA metrics."""
940
+
941
+ incidents_this_period: int
942
+ """Number of incidents in this period."""
943
+
944
+ total_downtime_minutes: int
945
+ """Total downtime in minutes."""
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "oxarchive"
7
- version = "0.5.4"
7
+ version = "0.6.0"
8
8
  description = "Official Python SDK for 0xarchive - Hyperliquid Historical Data API"
9
9
  readme = "README.md"
10
10
  license = "MIT"
File without changes
File without changes