ddx-python 1.0.4__cp310-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.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.
Files changed (106) hide show
  1. ddx/.gitignore +1 -0
  2. ddx/__init__.py +58 -0
  3. ddx/_rust/__init__.pyi +2685 -0
  4. ddx/_rust/common/__init__.pyi +17 -0
  5. ddx/_rust/common/accounting.pyi +6 -0
  6. ddx/_rust/common/enums.pyi +3 -0
  7. ddx/_rust/common/requests/__init__.pyi +23 -0
  8. ddx/_rust/common/requests/intents.pyi +19 -0
  9. ddx/_rust/common/specs.pyi +17 -0
  10. ddx/_rust/common/state/__init__.pyi +41 -0
  11. ddx/_rust/common/state/keys.pyi +29 -0
  12. ddx/_rust/common/transactions.pyi +7 -0
  13. ddx/_rust/decimal.pyi +3 -0
  14. ddx/_rust/h256.pyi +3 -0
  15. ddx/_rust.abi3.so +0 -0
  16. ddx/app_config/ethereum/addresses.json +526 -0
  17. ddx/auditor/README.md +32 -0
  18. ddx/auditor/__init__.py +0 -0
  19. ddx/auditor/auditor_driver.py +1043 -0
  20. ddx/auditor/websocket_message.py +54 -0
  21. ddx/common/__init__.py +0 -0
  22. ddx/common/epoch_params.py +28 -0
  23. ddx/common/fill_context.py +141 -0
  24. ddx/common/logging.py +184 -0
  25. ddx/common/market_aware_account.py +259 -0
  26. ddx/common/market_specs.py +64 -0
  27. ddx/common/trade_mining_params.py +19 -0
  28. ddx/common/transaction_utils.py +85 -0
  29. ddx/common/transactions/__init__.py +0 -0
  30. ddx/common/transactions/advance_epoch.py +91 -0
  31. ddx/common/transactions/advance_settlement_epoch.py +63 -0
  32. ddx/common/transactions/all_price_checkpoints.py +84 -0
  33. ddx/common/transactions/cancel.py +76 -0
  34. ddx/common/transactions/cancel_all.py +88 -0
  35. ddx/common/transactions/complete_fill.py +103 -0
  36. ddx/common/transactions/disaster_recovery.py +96 -0
  37. ddx/common/transactions/event.py +48 -0
  38. ddx/common/transactions/fee_distribution.py +119 -0
  39. ddx/common/transactions/funding.py +292 -0
  40. ddx/common/transactions/futures_expiry.py +123 -0
  41. ddx/common/transactions/genesis.py +108 -0
  42. ddx/common/transactions/inner/__init__.py +0 -0
  43. ddx/common/transactions/inner/adl_outcome.py +25 -0
  44. ddx/common/transactions/inner/fill.py +232 -0
  45. ddx/common/transactions/inner/liquidated_position.py +41 -0
  46. ddx/common/transactions/inner/liquidation_entry.py +41 -0
  47. ddx/common/transactions/inner/liquidation_fill.py +118 -0
  48. ddx/common/transactions/inner/outcome.py +32 -0
  49. ddx/common/transactions/inner/trade_fill.py +292 -0
  50. ddx/common/transactions/insurance_fund_update.py +138 -0
  51. ddx/common/transactions/insurance_fund_withdraw.py +100 -0
  52. ddx/common/transactions/liquidation.py +353 -0
  53. ddx/common/transactions/partial_fill.py +125 -0
  54. ddx/common/transactions/pnl_realization.py +120 -0
  55. ddx/common/transactions/post.py +72 -0
  56. ddx/common/transactions/post_order.py +95 -0
  57. ddx/common/transactions/price_checkpoint.py +97 -0
  58. ddx/common/transactions/signer_registered.py +62 -0
  59. ddx/common/transactions/specs_update.py +61 -0
  60. ddx/common/transactions/strategy_update.py +158 -0
  61. ddx/common/transactions/tradable_product_update.py +98 -0
  62. ddx/common/transactions/trade_mining.py +147 -0
  63. ddx/common/transactions/trader_update.py +131 -0
  64. ddx/common/transactions/withdraw.py +90 -0
  65. ddx/common/transactions/withdraw_ddx.py +74 -0
  66. ddx/common/utils.py +176 -0
  67. ddx/config.py +17 -0
  68. ddx/derivadex_client.py +270 -0
  69. ddx/models/__init__.py +0 -0
  70. ddx/models/base.py +132 -0
  71. ddx/py.typed +0 -0
  72. ddx/realtime_client/__init__.py +2 -0
  73. ddx/realtime_client/config.py +2 -0
  74. ddx/realtime_client/models/__init__.py +611 -0
  75. ddx/realtime_client/realtime_client.py +646 -0
  76. ddx/rest_client/__init__.py +0 -0
  77. ddx/rest_client/clients/__init__.py +0 -0
  78. ddx/rest_client/clients/base_client.py +60 -0
  79. ddx/rest_client/clients/market_client.py +1243 -0
  80. ddx/rest_client/clients/on_chain_client.py +439 -0
  81. ddx/rest_client/clients/signed_client.py +292 -0
  82. ddx/rest_client/clients/system_client.py +843 -0
  83. ddx/rest_client/clients/trade_client.py +357 -0
  84. ddx/rest_client/constants/__init__.py +0 -0
  85. ddx/rest_client/constants/endpoints.py +66 -0
  86. ddx/rest_client/contracts/__init__.py +0 -0
  87. ddx/rest_client/contracts/checkpoint/__init__.py +560 -0
  88. ddx/rest_client/contracts/ddx/__init__.py +1949 -0
  89. ddx/rest_client/contracts/dummy_token/__init__.py +1014 -0
  90. ddx/rest_client/contracts/i_collateral/__init__.py +1414 -0
  91. ddx/rest_client/contracts/i_stake/__init__.py +696 -0
  92. ddx/rest_client/exceptions/__init__.py +0 -0
  93. ddx/rest_client/exceptions/exceptions.py +32 -0
  94. ddx/rest_client/http/__init__.py +0 -0
  95. ddx/rest_client/http/http_client.py +336 -0
  96. ddx/rest_client/models/__init__.py +0 -0
  97. ddx/rest_client/models/market.py +693 -0
  98. ddx/rest_client/models/signed.py +61 -0
  99. ddx/rest_client/models/system.py +311 -0
  100. ddx/rest_client/models/trade.py +185 -0
  101. ddx/rest_client/utils/__init__.py +0 -0
  102. ddx/rest_client/utils/encryption_utils.py +26 -0
  103. ddx/utils/__init__.py +0 -0
  104. ddx_python-1.0.4.dist-info/METADATA +63 -0
  105. ddx_python-1.0.4.dist-info/RECORD +106 -0
  106. ddx_python-1.0.4.dist-info/WHEEL +5 -0
@@ -0,0 +1,693 @@
1
+ from datetime import datetime
2
+ from typing import List, Literal, Optional, Dict
3
+ from ddx._rust.common.state import BookOrder
4
+ from pydantic import Field, ConfigDict, field_validator
5
+ from ddx.models.base import (
6
+ CamelModel,
7
+ CancelRejection,
8
+ HexStr,
9
+ OrderRejection,
10
+ OrderUpdateReason,
11
+ PositionSide,
12
+ StrategyUpdateReason,
13
+ TraderUpdateReason,
14
+ WithdrawDDXRejection,
15
+ WithdrawRejection,
16
+ validate_decimal_str,
17
+ TradeSide,
18
+ OrderType,
19
+ )
20
+ from dataclasses import dataclass
21
+ from itertools import groupby
22
+ from operator import attrgetter
23
+
24
+ from ddx._rust.decimal import Decimal
25
+
26
+
27
+ # Mark Price History Models
28
+ class MarkPrice(CamelModel):
29
+ """Mark price data model."""
30
+
31
+ global_ordinal: int = Field(..., ge=0)
32
+ epoch_id: int = Field(..., ge=0)
33
+ symbol: str
34
+ price: str
35
+ funding_rate: str
36
+ created_at: datetime
37
+
38
+ @field_validator("price")
39
+ @classmethod
40
+ def validate_nonnegative_decimals(cls, v, info):
41
+ return validate_decimal_str(v, f"MarkPrice.{info.field_name}", nonnegative=True)
42
+
43
+ @field_validator("funding_rate")
44
+ @classmethod
45
+ def validate_decimals(cls, v, info):
46
+ return validate_decimal_str(
47
+ v, f"MarkPrice.{info.field_name}", nonnegative=False
48
+ )
49
+
50
+
51
+ class MarkPriceHistoryResponse(CamelModel):
52
+ """Response model for mark price history endpoint."""
53
+
54
+ next_global_ordinal: Optional[int] = Field(None, ge=0)
55
+ value: List[MarkPrice]
56
+ success: bool
57
+ timestamp: int
58
+
59
+
60
+ # Order Book L3 Models
61
+ class OrderBookL3Entry(CamelModel):
62
+ """L3 order book entry model."""
63
+
64
+ book_ordinal: int = Field(..., ge=0)
65
+ order_hash: HexStr
66
+ symbol: str
67
+ side: TradeSide
68
+ original_amount: str
69
+ amount: str
70
+ price: str
71
+ trader_address: HexStr
72
+ strategy_id_hash: HexStr
73
+
74
+ def raw_book_order(self) -> BookOrder:
75
+ return BookOrder(
76
+ self.side.raw_order_side(),
77
+ Decimal(self.amount),
78
+ Decimal(self.price),
79
+ self.trader_address,
80
+ self.strategy_id_hash,
81
+ self.book_ordinal,
82
+ 0,
83
+ )
84
+
85
+ @field_validator("original_amount", "amount", "price")
86
+ @classmethod
87
+ def validate_nonnegative_decimals(cls, v, info):
88
+ return validate_decimal_str(
89
+ v, f"OrderBookL3.{info.field_name}", nonnegative=True
90
+ )
91
+
92
+
93
+ class OrderBookL3Response(CamelModel):
94
+ """Response model for L3 order book endpoint."""
95
+
96
+ value: List[OrderBookL3Entry]
97
+ success: bool
98
+ timestamp: int
99
+
100
+
101
+ # Order Update History Models
102
+ class OrderIntent(CamelModel):
103
+ """Order intent data model."""
104
+
105
+ epoch_id: int = Field(..., ge=0)
106
+ order_hash: HexStr
107
+ symbol: str
108
+ side: TradeSide
109
+ amount: str
110
+ price: str
111
+ trader_address: HexStr
112
+ strategy_id_hash: HexStr
113
+ order_type: OrderType
114
+ stop_price: str
115
+ nonce: str
116
+ signature: HexStr
117
+ created_at: datetime
118
+
119
+ @field_validator("amount", "price", "stop_price")
120
+ @classmethod
121
+ def validate_nonnegative_decimals(cls, v, info):
122
+ return validate_decimal_str(
123
+ v, f"OrderIntent.{info.field_name}", nonnegative=True
124
+ )
125
+
126
+
127
+ class OrderUpdate(CamelModel):
128
+ """Order update data model."""
129
+
130
+ global_ordinal: int = Field(..., ge=0)
131
+ epoch_id: int = Field(..., ge=0)
132
+ tx_ordinal: int = Field(..., ge=0)
133
+ ordinal: int = Field(..., ge=0)
134
+ order_rejection: Optional[OrderRejection] = None
135
+ cancel_rejection: Optional[CancelRejection] = None
136
+ reason: OrderUpdateReason
137
+ amount: Optional[str] = None
138
+ quote_asset_amount: Optional[str] = None
139
+ symbol: str
140
+ price: Optional[str] = None
141
+ maker_fee_collateral: Optional[str] = None
142
+ maker_fee_ddx: Optional[str] = Field(default=None, alias="makerFeeDDX")
143
+ maker_realized_pnl: Optional[str] = None
144
+ taker_order_intent: Optional[OrderIntent] = None
145
+ taker_fee_collateral: Optional[str] = None
146
+ taker_fee_ddx: Optional[str] = Field(default=None, alias="takerFeeDDX")
147
+ taker_realized_pnl: Optional[str] = None
148
+ liquidated_trader_address: Optional[HexStr] = None
149
+ liquidated_strategy_id_hash: Optional[HexStr] = None
150
+ maker_order_intent: OrderIntent
151
+ created_at: datetime
152
+
153
+ @field_validator(
154
+ "amount",
155
+ "quote_asset_amount",
156
+ "price",
157
+ "maker_fee_collateral",
158
+ "maker_fee_ddx",
159
+ "taker_fee_collateral",
160
+ "taker_fee_ddx",
161
+ )
162
+ @classmethod
163
+ def validate_nonnegative_optional_decimals(cls, v, info):
164
+ if v is None:
165
+ return v
166
+ return validate_decimal_str(
167
+ v, f"OrderUpdate.{info.field_name}", nonnegative=True
168
+ )
169
+
170
+ @field_validator("maker_realized_pnl", "taker_realized_pnl")
171
+ @classmethod
172
+ def validate_optional_decimals(cls, v, info):
173
+ if v is None:
174
+ return v
175
+ return validate_decimal_str(
176
+ v, f"OrderUpdate.{info.field_name}", nonnegative=False
177
+ )
178
+
179
+
180
+ class OrderUpdateHistoryResponse(CamelModel):
181
+ """Response model for order update history endpoint."""
182
+
183
+ next_global_ordinal: Optional[int] = Field(None, ge=0)
184
+ value: List[OrderUpdate]
185
+ success: bool
186
+ timestamp: int
187
+
188
+
189
+ # Strategy Update History Models
190
+ class StrategyPositionUpdate(CamelModel):
191
+ """Strategy position update data within a strategy update."""
192
+
193
+ symbol: str
194
+ side: Optional[PositionSide] = None
195
+ avg_entry_price: Optional[str] = Field(
196
+ None, description="New average entry price after RealizedPnl strategy update"
197
+ )
198
+ realized_pnl: str = Field(
199
+ ..., description="Realized PnL after RealizedPnl or ADL strategy update"
200
+ )
201
+
202
+ @field_validator("avg_entry_price")
203
+ @classmethod
204
+ def validate_nonnegative_decimals(cls, v, info):
205
+ return validate_decimal_str(
206
+ v, f"StrategyPositionUpdate.{info.field_name}", nonnegative=True
207
+ )
208
+
209
+ @field_validator("realized_pnl")
210
+ @classmethod
211
+ def validate_decimals(cls, v, info):
212
+ return validate_decimal_str(
213
+ v, f"StrategyPositionUpdate.{info.field_name}", nonnegative=False
214
+ )
215
+
216
+
217
+ class StrategyUpdate(CamelModel):
218
+ """Strategy update data model."""
219
+
220
+ global_ordinal: int = Field(..., ge=0)
221
+ epoch_id: int = Field(..., ge=0)
222
+ tx_ordinal: int = Field(..., ge=0)
223
+ ordinal: int = Field(..., ge=0)
224
+ withdraw_rejection: Optional[WithdrawRejection] = None
225
+ reason: StrategyUpdateReason
226
+ trader_address: HexStr
227
+ strategy_id_hash: HexStr
228
+ collateral_address: HexStr
229
+ collateral_symbol: Literal["USDC"]
230
+ amount: Optional[str] = None
231
+ new_avail_collateral: Optional[str] = None
232
+ new_locked_collateral: Optional[str] = None
233
+ block_number: Optional[int] = Field(None, ge=0)
234
+ positions: Optional[List[StrategyPositionUpdate]] = None
235
+ created_at: datetime
236
+
237
+ @field_validator("amount", "new_avail_collateral", "new_locked_collateral")
238
+ @classmethod
239
+ def validate_nonnegative_optional_decimals(cls, v, info):
240
+ if v is None:
241
+ return v
242
+ return validate_decimal_str(
243
+ v, f"StrategyUpdate.{info.field_name}", nonnegative=True
244
+ )
245
+
246
+
247
+ class StrategyUpdateHistoryResponse(CamelModel):
248
+ """Response model for strategy update history endpoint."""
249
+
250
+ next_global_ordinal: Optional[int] = Field(None, ge=0)
251
+ value: List[StrategyUpdate]
252
+ success: bool
253
+ timestamp: int
254
+
255
+
256
+ # Ticker Models
257
+ class Ticker(CamelModel):
258
+ """Market ticker data model."""
259
+
260
+ symbol: str
261
+ high_price_24h: str
262
+ low_price_24h: str
263
+ prev_price_24h: str
264
+ last_price: str
265
+ mark_price: str
266
+ index_price: str
267
+ next_funding_time: datetime
268
+ volume_24h: str
269
+ amount_24h: Optional[str] = None
270
+ funding_rate: str
271
+ open_interest: str
272
+ open_interest_value: str
273
+
274
+
275
+ class TickersResponse(CamelModel):
276
+ """Response model for tickers endpoint."""
277
+
278
+ value: List[Ticker]
279
+ success: bool
280
+ timestamp: int
281
+
282
+
283
+ # Trader Update History Models
284
+ class TraderUpdate(CamelModel):
285
+ """Trader update data model."""
286
+
287
+ global_ordinal: int = Field(..., ge=0)
288
+ epoch_id: int = Field(..., ge=0)
289
+ tx_ordinal: int = Field(..., ge=0)
290
+ ordinal: int = Field(..., ge=0)
291
+ withdraw_ddx_rejection: Optional[WithdrawDDXRejection] = Field(
292
+ default=None, alias="withdrawDDXRejection"
293
+ )
294
+ reason: TraderUpdateReason
295
+ trader_address: HexStr
296
+ amount: Optional[str] = None
297
+ new_avail_ddx_balance: Optional[str] = Field(
298
+ default=None, alias="newAvailDDXBalance"
299
+ )
300
+ new_locked_ddx_balance: Optional[str] = Field(
301
+ default=None, alias="newLockedDDXBalance"
302
+ )
303
+ pay_fees_in_ddx: Optional[bool] = Field(default=None, alias="payFeesInDDX")
304
+ block_number: Optional[int] = None
305
+ created_at: datetime
306
+
307
+ @field_validator("new_avail_ddx_balance", "new_locked_ddx_balance")
308
+ @classmethod
309
+ def validate_nonnegative_optional_decimals(cls, v, info):
310
+ if v is None:
311
+ return v
312
+ return validate_decimal_str(
313
+ v, f"TraderUpdate.{info.field_name}", nonnegative=True
314
+ )
315
+
316
+ @field_validator("amount")
317
+ @classmethod
318
+ def validate_optional_decimals(cls, v, info):
319
+ if v is None:
320
+ return v
321
+ return validate_decimal_str(
322
+ v, f"TraderUpdate.{info.field_name}", nonnegative=False
323
+ )
324
+
325
+
326
+ class TraderUpdateHistoryResponse(CamelModel):
327
+ """Response model for trader update history endpoint."""
328
+
329
+ next_global_ordinal: Optional[int] = Field(None, ge=0)
330
+ value: List[TraderUpdate]
331
+ success: bool
332
+ timestamp: int
333
+
334
+
335
+ # Balance Aggregation Models
336
+ class BalanceAggregation(CamelModel):
337
+ """Balance aggregation data model."""
338
+
339
+ trader: HexStr
340
+ strategy_id_hash: HexStr
341
+ amount: str
342
+ timestamp: int
343
+
344
+ @field_validator("amount")
345
+ @classmethod
346
+ def validate_decimals(cls, v, info):
347
+ return validate_decimal_str(
348
+ v, f"BalanceAggregation.{info.field_name}", nonnegative=False
349
+ )
350
+
351
+
352
+ class BalanceAggregationResponse(CamelModel):
353
+ """Response model for balance aggregation endpoint."""
354
+
355
+ value: List[BalanceAggregation]
356
+ success: bool
357
+ timestamp: int
358
+
359
+
360
+ # Fees Aggregation Models
361
+ class FeesAggregation(CamelModel):
362
+ """Fees aggregation data model."""
363
+
364
+ model_config = CamelModel.model_config | ConfigDict(extra="allow")
365
+
366
+ timestamp: int
367
+
368
+ def get_fees_value(self, fee_symbol: Optional[str] = None) -> Optional[str]:
369
+ """Get fees value for a specific fee symbol (e.g., 'USDC', 'DDX')."""
370
+ return getattr(self, f"fees_{fee_symbol}", None)
371
+
372
+
373
+ class FeesAggregationResponse(CamelModel):
374
+ """Response model for fees aggregation endpoint."""
375
+
376
+ next_lookback_timestamp: Optional[int] = Field(None, ge=0)
377
+ value: List[FeesAggregation]
378
+ success: bool
379
+ timestamp: int
380
+
381
+
382
+ # Funding Rate Comparison Models
383
+ class FundingRateComparison(CamelModel):
384
+ """Funding rate comparison data model."""
385
+
386
+ symbol: str
387
+ derivadex_funding_rate: str
388
+ binance_funding_rate: str
389
+ derivadex_binance_arbitrage: str
390
+ bybit_funding_rate: str
391
+ derivadex_bybit_arbitrage: str
392
+ hyperliquid_funding_rate: str
393
+ derivadex_hyperliquid_arbitrage: str
394
+
395
+ @field_validator(
396
+ "derivadex_funding_rate",
397
+ "binance_funding_rate",
398
+ "bybit_funding_rate",
399
+ "derivadex_binance_arbitrage",
400
+ "derivadex_bybit_arbitrage",
401
+ "hyperliquid_funding_rate",
402
+ "derivadex_hyperliquid_arbitrage",
403
+ )
404
+ @classmethod
405
+ def validate_decimals(cls, v, info):
406
+ return validate_decimal_str(
407
+ v, f"FundingRateComparison.{info.field_name}", nonnegative=False
408
+ )
409
+
410
+
411
+ class FundingRateComparisonResponse(CamelModel):
412
+ """Response model for funding rate comparison aggregation endpoint."""
413
+
414
+ value: List[FundingRateComparison]
415
+ success: bool
416
+ timestamp: int
417
+
418
+
419
+ # Top Traders Models
420
+ class TopTrader(CamelModel):
421
+ """Top trader data model."""
422
+
423
+ trader: HexStr
424
+ volume: Optional[str] = None
425
+ realized_pnl: Optional[str] = None
426
+ account_value: Optional[str] = None
427
+
428
+ @field_validator("volume", "account_value")
429
+ @classmethod
430
+ def validate_nonnegative_optional_decimals(cls, v, info):
431
+ if v is None:
432
+ return v
433
+ return validate_decimal_str(v, f"TopTrader.{info.field_name}", nonnegative=True)
434
+
435
+ @field_validator("realized_pnl")
436
+ @classmethod
437
+ def validate_optional_decimals(cls, v, info):
438
+ if v is None:
439
+ return v
440
+ return validate_decimal_str(
441
+ v, f"TopTrader.{info.field_name}", nonnegative=False
442
+ )
443
+
444
+
445
+ class TopTradersAggregationResponse(CamelModel):
446
+ """Response model for top traders endpoint."""
447
+
448
+ value: List[TopTrader]
449
+ next_cursor: Optional[int] = None
450
+ success: bool
451
+ timestamp: int
452
+
453
+
454
+ # Volume Aggregation Models
455
+ class VolumeAggregation(CamelModel):
456
+ """Volume aggregation data model."""
457
+
458
+ model_config = CamelModel.model_config | ConfigDict(extra="allow")
459
+
460
+ timestamp: int
461
+
462
+ def get_volume_value(self, field_name: str) -> Optional[str]:
463
+ """Get value for a specific volume field."""
464
+ return getattr(self, f"volume_{field_name}", None)
465
+
466
+
467
+ class VolumeAggregationResponse(CamelModel):
468
+ """Response model for volume aggregation endpoint."""
469
+
470
+ model_config = CamelModel.model_config | ConfigDict(extra="allow")
471
+
472
+ next_lookback_timestamp: Optional[int] = Field(None, ge=0)
473
+ value: List[VolumeAggregation]
474
+ success: bool
475
+ timestamp: int
476
+
477
+
478
+ # Funding Rate History Models
479
+ class FundingRateHistory(CamelModel):
480
+ """Funding rate history data model."""
481
+
482
+ epoch_id: int = Field(..., ge=0)
483
+ tx_ordinal: int = Field(..., ge=0)
484
+ symbol: str
485
+ funding_rate: str
486
+ created_at: datetime
487
+
488
+ @field_validator("funding_rate")
489
+ @classmethod
490
+ def validate_decimals(cls, v, info):
491
+ return validate_decimal_str(
492
+ v, f"FundingRateHistory.{info.field_name}", nonnegative=False
493
+ )
494
+
495
+
496
+ class FundingRateHistoryResponse(CamelModel):
497
+ """Response model for funding rate history endpoint."""
498
+
499
+ value: List[FundingRateHistory]
500
+ next_epoch: Optional[int] = Field(None, ge=0)
501
+ next_tx_ordinal: Optional[int] = Field(None, ge=0)
502
+ next_ordinal: Optional[int] = Field(None, ge=0)
503
+ success: bool
504
+ timestamp: int
505
+
506
+
507
+ # Open Interest History Models
508
+ class OpenInterestHistory(CamelModel):
509
+ """Open interest history data model."""
510
+
511
+ symbol: str
512
+ amount: str
513
+ created_at: datetime
514
+
515
+ @field_validator("amount")
516
+ @classmethod
517
+ def validate_nonnegative_decimals(cls, v, info):
518
+ return validate_decimal_str(
519
+ v, f"OpenInterestHistory.{info.field_name}", nonnegative=True
520
+ )
521
+
522
+
523
+ class OpenInterestHistoryResponse(CamelModel):
524
+ """Response model for open interest history endpoint."""
525
+
526
+ value: List[OpenInterestHistory]
527
+ success: bool
528
+ timestamp: int
529
+
530
+
531
+ # Order Book L2 Models
532
+ class OrderBookL2Entry(CamelModel):
533
+ """L2 order book item model."""
534
+
535
+ symbol: str
536
+ amount: str
537
+ price: str
538
+ side: TradeSide
539
+
540
+ @field_validator("amount", "price")
541
+ @classmethod
542
+ def validate_nonnegative_decimals(cls, v, info):
543
+ return validate_decimal_str(
544
+ v, f"OrderBookL2.{info.field_name}", nonnegative=True
545
+ )
546
+
547
+
548
+ class OrderBookL2Response(CamelModel):
549
+ """Response model for L2 order book endpoint."""
550
+
551
+ value: List[OrderBookL2Entry]
552
+ success: bool
553
+ timestamp: int
554
+
555
+
556
+ @dataclass
557
+ class OrderBook:
558
+ """
559
+ Complete order book for a single market.
560
+
561
+ Attributes
562
+ ----------
563
+ symbol : str
564
+ The symbol of the market
565
+ bids : List[OrderBookL2Item]
566
+ List of bid orders, sorted by price (descending)
567
+ asks : List[OrderBookL2Item]
568
+ List of ask orders, sorted by price (ascending)
569
+ timestamp : int
570
+ Timestamp of the order book snapshot
571
+ """
572
+
573
+ symbol: str
574
+ bids: List[OrderBookL2Entry]
575
+ asks: List[OrderBookL2Entry]
576
+ timestamp: int
577
+
578
+ @classmethod
579
+ def from_order_book_l2_items(
580
+ cls, symbol: str, order_book_l2_items: List[OrderBookL2Entry], timestamp: int
581
+ ) -> "OrderBook":
582
+ """
583
+ Create instance from a list of entries for a single symbol.
584
+
585
+ Parameters
586
+ ----------
587
+ symbol : str
588
+ The market symbol
589
+ order_book_l2_items : List[OrderBookL2Item]
590
+ List of order book entries for this symbol
591
+ timestamp : int
592
+ Timestamp of the order book snapshot
593
+
594
+ Returns
595
+ -------
596
+ OrderBook
597
+ Initialized instance
598
+ """
599
+ # Filter and sort bids (descending)
600
+ bids = [e for e in order_book_l2_items if e.side == TradeSide.BID]
601
+ bids.sort(key=lambda x: Decimal(x.price), reverse=True)
602
+
603
+ # Filter and sort asks (ascending)
604
+ asks = [e for e in order_book_l2_items if e.side == TradeSide.ASK]
605
+ asks.sort(key=lambda x: Decimal(x.price))
606
+
607
+ return cls(symbol=symbol, bids=bids, asks=asks, timestamp=timestamp)
608
+
609
+ @classmethod
610
+ def from_response(
611
+ cls, response: OrderBookL2Response, symbol: Optional[str] = None
612
+ ) -> Dict[str, "OrderBook"]:
613
+ """
614
+ Create OrderBook instance(s) from response data.
615
+
616
+ Parameters
617
+ ----------
618
+ response : OrderBookL2Response
619
+ Parsed response from the API
620
+ symbol : Optional[str]
621
+ If provided, only return order book for this symbol
622
+
623
+ Returns
624
+ -------
625
+ Dict[str, OrderBook]
626
+ Dictionary mapping symbols to their respective order books
627
+ """
628
+ if not response.value:
629
+ return {}
630
+
631
+ # If specific symbol requested, filter first
632
+ items = response.value
633
+ if symbol:
634
+ items = [item for item in items if item.symbol == symbol]
635
+ if not items:
636
+ return {}
637
+
638
+ # Group entries by symbol
639
+ items_sorted = sorted(items, key=attrgetter("symbol"))
640
+ grouped = groupby(items_sorted, key=attrgetter("symbol"))
641
+
642
+ # Create order books for each symbol
643
+ order_books = {}
644
+ for sym, entries in grouped:
645
+ entry_list = list(entries)
646
+ if entry_list: # Only create order book if there are entries
647
+ order_books[sym] = cls.from_order_book_l2_items(
648
+ sym, entry_list, response.timestamp
649
+ )
650
+
651
+ return order_books
652
+
653
+
654
+ class PriceCheckpoint(CamelModel):
655
+ """Price checkpoint data model."""
656
+
657
+ epoch_id: int = Field(..., ge=0)
658
+ tx_ordinal: int = Field(..., ge=0)
659
+ index_price_hash: HexStr
660
+ symbol: str
661
+ index_price: str
662
+ mark_price: str
663
+ time: int = Field(..., ge=0)
664
+ ema: Optional[str] = None
665
+ price_ordinal: int = Field(..., ge=0)
666
+ created_at: datetime
667
+
668
+ @field_validator("index_price", "mark_price")
669
+ @classmethod
670
+ def validate_nonnegative_decimals(cls, v, info):
671
+ return validate_decimal_str(
672
+ v, f"PriceCheckpoint.{info.field_name}", nonnegative=True
673
+ )
674
+
675
+ @field_validator("ema")
676
+ @classmethod
677
+ def validate_optional_decimals(cls, v, info):
678
+ if v is None:
679
+ return v
680
+ return validate_decimal_str(
681
+ v, f"PriceCheckpoint.{info.field_name}", nonnegative=False
682
+ )
683
+
684
+
685
+ class PriceCheckpointHistoryResponse(CamelModel):
686
+ """Response model for price checkpoint history endpoint."""
687
+
688
+ value: List[PriceCheckpoint]
689
+ next_epoch: Optional[int] = Field(None, ge=0)
690
+ next_tx_ordinal: Optional[int] = Field(None, ge=0)
691
+ next_ordinal: Optional[int] = Field(None, ge=0)
692
+ success: bool
693
+ timestamp: int