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.
- ddx/.gitignore +1 -0
- ddx/__init__.py +58 -0
- ddx/_rust/__init__.pyi +2685 -0
- ddx/_rust/common/__init__.pyi +17 -0
- ddx/_rust/common/accounting.pyi +6 -0
- ddx/_rust/common/enums.pyi +3 -0
- ddx/_rust/common/requests/__init__.pyi +23 -0
- ddx/_rust/common/requests/intents.pyi +19 -0
- ddx/_rust/common/specs.pyi +17 -0
- ddx/_rust/common/state/__init__.pyi +41 -0
- ddx/_rust/common/state/keys.pyi +29 -0
- ddx/_rust/common/transactions.pyi +7 -0
- ddx/_rust/decimal.pyi +3 -0
- ddx/_rust/h256.pyi +3 -0
- ddx/_rust.abi3.so +0 -0
- ddx/app_config/ethereum/addresses.json +526 -0
- ddx/auditor/README.md +32 -0
- ddx/auditor/__init__.py +0 -0
- ddx/auditor/auditor_driver.py +1043 -0
- ddx/auditor/websocket_message.py +54 -0
- ddx/common/__init__.py +0 -0
- ddx/common/epoch_params.py +28 -0
- ddx/common/fill_context.py +141 -0
- ddx/common/logging.py +184 -0
- ddx/common/market_aware_account.py +259 -0
- ddx/common/market_specs.py +64 -0
- ddx/common/trade_mining_params.py +19 -0
- ddx/common/transaction_utils.py +85 -0
- ddx/common/transactions/__init__.py +0 -0
- ddx/common/transactions/advance_epoch.py +91 -0
- ddx/common/transactions/advance_settlement_epoch.py +63 -0
- ddx/common/transactions/all_price_checkpoints.py +84 -0
- ddx/common/transactions/cancel.py +76 -0
- ddx/common/transactions/cancel_all.py +88 -0
- ddx/common/transactions/complete_fill.py +103 -0
- ddx/common/transactions/disaster_recovery.py +96 -0
- ddx/common/transactions/event.py +48 -0
- ddx/common/transactions/fee_distribution.py +119 -0
- ddx/common/transactions/funding.py +292 -0
- ddx/common/transactions/futures_expiry.py +123 -0
- ddx/common/transactions/genesis.py +108 -0
- ddx/common/transactions/inner/__init__.py +0 -0
- ddx/common/transactions/inner/adl_outcome.py +25 -0
- ddx/common/transactions/inner/fill.py +232 -0
- ddx/common/transactions/inner/liquidated_position.py +41 -0
- ddx/common/transactions/inner/liquidation_entry.py +41 -0
- ddx/common/transactions/inner/liquidation_fill.py +118 -0
- ddx/common/transactions/inner/outcome.py +32 -0
- ddx/common/transactions/inner/trade_fill.py +292 -0
- ddx/common/transactions/insurance_fund_update.py +138 -0
- ddx/common/transactions/insurance_fund_withdraw.py +100 -0
- ddx/common/transactions/liquidation.py +353 -0
- ddx/common/transactions/partial_fill.py +125 -0
- ddx/common/transactions/pnl_realization.py +120 -0
- ddx/common/transactions/post.py +72 -0
- ddx/common/transactions/post_order.py +95 -0
- ddx/common/transactions/price_checkpoint.py +97 -0
- ddx/common/transactions/signer_registered.py +62 -0
- ddx/common/transactions/specs_update.py +61 -0
- ddx/common/transactions/strategy_update.py +158 -0
- ddx/common/transactions/tradable_product_update.py +98 -0
- ddx/common/transactions/trade_mining.py +147 -0
- ddx/common/transactions/trader_update.py +131 -0
- ddx/common/transactions/withdraw.py +90 -0
- ddx/common/transactions/withdraw_ddx.py +74 -0
- ddx/common/utils.py +176 -0
- ddx/config.py +17 -0
- ddx/derivadex_client.py +270 -0
- ddx/models/__init__.py +0 -0
- ddx/models/base.py +132 -0
- ddx/py.typed +0 -0
- ddx/realtime_client/__init__.py +2 -0
- ddx/realtime_client/config.py +2 -0
- ddx/realtime_client/models/__init__.py +611 -0
- ddx/realtime_client/realtime_client.py +646 -0
- ddx/rest_client/__init__.py +0 -0
- ddx/rest_client/clients/__init__.py +0 -0
- ddx/rest_client/clients/base_client.py +60 -0
- ddx/rest_client/clients/market_client.py +1243 -0
- ddx/rest_client/clients/on_chain_client.py +439 -0
- ddx/rest_client/clients/signed_client.py +292 -0
- ddx/rest_client/clients/system_client.py +843 -0
- ddx/rest_client/clients/trade_client.py +357 -0
- ddx/rest_client/constants/__init__.py +0 -0
- ddx/rest_client/constants/endpoints.py +66 -0
- ddx/rest_client/contracts/__init__.py +0 -0
- ddx/rest_client/contracts/checkpoint/__init__.py +560 -0
- ddx/rest_client/contracts/ddx/__init__.py +1949 -0
- ddx/rest_client/contracts/dummy_token/__init__.py +1014 -0
- ddx/rest_client/contracts/i_collateral/__init__.py +1414 -0
- ddx/rest_client/contracts/i_stake/__init__.py +696 -0
- ddx/rest_client/exceptions/__init__.py +0 -0
- ddx/rest_client/exceptions/exceptions.py +32 -0
- ddx/rest_client/http/__init__.py +0 -0
- ddx/rest_client/http/http_client.py +336 -0
- ddx/rest_client/models/__init__.py +0 -0
- ddx/rest_client/models/market.py +693 -0
- ddx/rest_client/models/signed.py +61 -0
- ddx/rest_client/models/system.py +311 -0
- ddx/rest_client/models/trade.py +185 -0
- ddx/rest_client/utils/__init__.py +0 -0
- ddx/rest_client/utils/encryption_utils.py +26 -0
- ddx/utils/__init__.py +0 -0
- ddx_python-1.0.4.dist-info/METADATA +63 -0
- ddx_python-1.0.4.dist-info/RECORD +106 -0
- 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
|