oxarchive 0.5.3__py3-none-any.whl → 0.6.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.
oxarchive/__init__.py CHANGED
@@ -31,6 +31,7 @@ from .types import (
31
31
  LighterInstrument,
32
32
  FundingRate,
33
33
  OpenInterest,
34
+ Liquidation,
34
35
  Candle,
35
36
  CandleInterval,
36
37
  OxArchiveError,
@@ -67,7 +68,7 @@ except ImportError:
67
68
  OxArchiveWs = None # type: ignore
68
69
  WsOptions = None # type: ignore
69
70
 
70
- __version__ = "0.5.3"
71
+ __version__ = "0.6.0"
71
72
 
72
73
  __all__ = [
73
74
  # Client
@@ -86,6 +87,7 @@ __all__ = [
86
87
  "LighterGranularity",
87
88
  "FundingRate",
88
89
  "OpenInterest",
90
+ "Liquidation",
89
91
  "Candle",
90
92
  "CandleInterval",
91
93
  "OxArchiveError",
oxarchive/client.py CHANGED
@@ -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)
oxarchive/types.py CHANGED
@@ -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."""
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: oxarchive
3
- Version: 0.5.3
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
@@ -229,8 +229,12 @@ while result.next_cursor:
229
229
  # Filter by side
230
230
  buys = client.hyperliquid.trades.list("BTC", start=..., end=..., side="buy")
231
231
 
232
- # Async version
232
+ # Get recent trades (Lighter only - has real-time data)
233
+ recent = client.lighter.trades.recent("BTC", limit=100)
234
+
235
+ # Async versions
233
236
  result = await client.hyperliquid.trades.alist("ETH", start=..., end=...)
237
+ recent = await client.lighter.trades.arecent("BTC", limit=100)
234
238
  ```
235
239
 
236
240
  ### Instruments
@@ -313,6 +317,44 @@ current = await client.hyperliquid.open_interest.acurrent("BTC")
313
317
  history = await client.hyperliquid.open_interest.ahistory("ETH", start=..., end=...)
314
318
  ```
315
319
 
320
+ ### Liquidations (Hyperliquid only)
321
+
322
+ Get historical liquidation events. Data available from May 2025 onwards.
323
+
324
+ ```python
325
+ # Get liquidation history for a coin
326
+ liquidations = client.hyperliquid.liquidations.history(
327
+ "BTC",
328
+ start="2025-06-01",
329
+ end="2025-06-02",
330
+ limit=100
331
+ )
332
+
333
+ # Paginate through all results
334
+ all_liquidations = list(liquidations.data)
335
+ while liquidations.next_cursor:
336
+ liquidations = client.hyperliquid.liquidations.history(
337
+ "BTC",
338
+ start="2025-06-01",
339
+ end="2025-06-02",
340
+ cursor=liquidations.next_cursor,
341
+ limit=1000
342
+ )
343
+ all_liquidations.extend(liquidations.data)
344
+
345
+ # Get liquidations for a specific user
346
+ user_liquidations = client.hyperliquid.liquidations.by_user(
347
+ "0x1234...",
348
+ start="2025-06-01",
349
+ end="2025-06-07",
350
+ coin="BTC" # optional filter
351
+ )
352
+
353
+ # Async versions
354
+ liquidations = await client.hyperliquid.liquidations.ahistory("BTC", start=..., end=...)
355
+ user_liquidations = await client.hyperliquid.liquidations.aby_user("0x...", start=..., end=...)
356
+ ```
357
+
316
358
  ### Candles (OHLCV)
317
359
 
318
360
  Get historical OHLCV candle data aggregated from trades.
@@ -364,6 +406,66 @@ candles = await client.hyperliquid.candles.ahistory("BTC", start=..., end=..., i
364
406
  | `1d` | 1 day |
365
407
  | `1w` | 1 week |
366
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
+
367
469
  ### Legacy API (Deprecated)
368
470
 
369
471
  The following legacy methods are deprecated and will be removed in v2.0. They default to Hyperliquid data:
@@ -632,7 +734,7 @@ Full type hint support with Pydantic models:
632
734
 
633
735
  ```python
634
736
  from oxarchive import Client, LighterGranularity
635
- from oxarchive.types import OrderBook, Trade, Instrument, LighterInstrument, FundingRate, OpenInterest
737
+ from oxarchive.types import OrderBook, Trade, Instrument, LighterInstrument, FundingRate, OpenInterest, Candle, Liquidation
636
738
  from oxarchive.resources.trades import CursorResponse
637
739
 
638
740
  client = Client(api_key="ox_your_api_key")
@@ -1,17 +1,18 @@
1
- oxarchive/__init__.py,sha256=-TIv-IqajJ0h3W4QyZsnBKb-PLc3hrRcsz54ynKA_hw,2738
2
- oxarchive/client.py,sha256=XWQ_VEBQy3UIAnmZQ-Z_FyzXnvMA3FITwtinBOf3o-Y,4453
1
+ oxarchive/__init__.py,sha256=7fzZru1_OgDwCR40kvydjYyeWA-NKMLHAo9UtwC9U3g,2776
2
+ oxarchive/client.py,sha256=I19WyNpNIeTT7qURQSz3UHuDayxxgXbyJRBS2x6Fxgs,4673
3
3
  oxarchive/exchanges.py,sha256=nTd0gRrgV2wDoptWWxwh38HXkCcunVNuNdNEwzNDsBM,2773
4
4
  oxarchive/http.py,sha256=SY_o9Ag8ADo1HI3i3uAKW1xwkYjPE75gRAjnMsddAGs,4211
5
- oxarchive/types.py,sha256=tfL6QG2WTBe4cdgk5TAbgpTRGAQALvhPTADH9umqd4g,15463
5
+ oxarchive/types.py,sha256=Vmw9P3HBl2WurzgfF8IDZtQfBF-zyXzB4u2o0EwOiuI,23519
6
6
  oxarchive/websocket.py,sha256=sS-kLDKv2qS77-61hXChRePjL_hz-URcALM9UW5zxXU,30609
7
- oxarchive/resources/__init__.py,sha256=JyNkR6dKBOGPQpYAuG6Qy1lDMCxJ68dXNLUAOua1Vs8,587
7
+ oxarchive/resources/__init__.py,sha256=u_0EUOSnWrbb4c88nqDcPetgG5DbKO9nCzNGqDbfgas,662
8
8
  oxarchive/resources/candles.py,sha256=GI7-YSNFckEd1i49W9mlrLn1cl955sY8ki0u12TuLgw,4449
9
+ oxarchive/resources/data_quality.py,sha256=YuSHvS88gJqbXzq4QOZXX1P1Jqcz1looOO8y1sMJgX4,12458
9
10
  oxarchive/resources/funding.py,sha256=ybMWkpoccrkdwnd6W3oHgsaor7cBcA2nkYy4CbjmHUg,4485
10
11
  oxarchive/resources/instruments.py,sha256=6q7rMdIaixXgFVXgwQsVd-YuO7RIXr1oGPT5jBsqI9A,3733
11
12
  oxarchive/resources/liquidations.py,sha256=kX3mEX2u6uEvgk1aKfL4U0JE9gOjJOwsLVpkIj84arE,6741
12
13
  oxarchive/resources/openinterest.py,sha256=whwo60KFNLGwvVrDmDYYc-rMZr35Fcizb5Iil-DSvD8,4553
13
14
  oxarchive/resources/orderbook.py,sha256=NzgKH45MBb2oAHyPmy6BSikaYyq0nM-8GFjur9LIRN8,6661
14
15
  oxarchive/resources/trades.py,sha256=RhOTbhqSSBAaekden6rLY8qJL-NDArGTqCempvUbX08,5801
15
- oxarchive-0.5.3.dist-info/METADATA,sha256=a134ZFK2iEmo5UPDAI2sw3ZcqpqjzVphjSKGUS2-4LA,17924
16
- oxarchive-0.5.3.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
17
- oxarchive-0.5.3.dist-info/RECORD,,
16
+ oxarchive-0.6.0.dist-info/METADATA,sha256=D5PP_-e1Llo-D9iuLRCvgNC8aWsv09yz2ZdQEbFfbT0,21581
17
+ oxarchive-0.6.0.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
18
+ oxarchive-0.6.0.dist-info/RECORD,,