nado-protocol 0.1.6__py3-none-any.whl → 0.2.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.
- nado_protocol/indexer_client/query.py +23 -0
- nado_protocol/indexer_client/types/models.py +1 -1
- nado_protocol/indexer_client/types/query.py +35 -0
- nado_protocol/utils/__init__.py +2 -0
- nado_protocol/utils/balance.py +249 -0
- nado_protocol/utils/margin_manager.py +841 -0
- nado_protocol/utils/math.py +26 -0
- nado_protocol/utils/order.py +23 -29
- {nado_protocol-0.1.6.dist-info → nado_protocol-0.2.0.dist-info}/METADATA +1 -1
- {nado_protocol-0.1.6.dist-info → nado_protocol-0.2.0.dist-info}/RECORD +12 -10
- {nado_protocol-0.1.6.dist-info → nado_protocol-0.2.0.dist-info}/entry_points.txt +1 -0
- {nado_protocol-0.1.6.dist-info → nado_protocol-0.2.0.dist-info}/WHEEL +0 -0
|
@@ -49,6 +49,8 @@ from nado_protocol.indexer_client.types.query import (
|
|
|
49
49
|
IndexerMerkleProofsData,
|
|
50
50
|
IndexerInterestAndFundingParams,
|
|
51
51
|
IndexerInterestAndFundingData,
|
|
52
|
+
IndexerAccountSnapshotsParams,
|
|
53
|
+
IndexerAccountSnapshotsData,
|
|
52
54
|
IndexerTickersData,
|
|
53
55
|
IndexerPerpContractsData,
|
|
54
56
|
IndexerHistoricalTradesData,
|
|
@@ -459,3 +461,24 @@ class IndexerQueryClient:
|
|
|
459
461
|
if max_trade_id is not None:
|
|
460
462
|
url += f"&max_trade_id={max_trade_id}"
|
|
461
463
|
return ensure_data_type(self._query_v2(url), list)
|
|
464
|
+
|
|
465
|
+
def get_multi_subaccount_snapshots(
|
|
466
|
+
self, params: IndexerAccountSnapshotsParams
|
|
467
|
+
) -> IndexerAccountSnapshotsData:
|
|
468
|
+
"""
|
|
469
|
+
Retrieves subaccount snapshots at specified timestamps.
|
|
470
|
+
Each snapshot is a view of the subaccount's balances at that point in time,
|
|
471
|
+
with tracked variables for interest, funding, etc.
|
|
472
|
+
|
|
473
|
+
Args:
|
|
474
|
+
params (IndexerAccountSnapshotsParams): Parameters specifying subaccounts,
|
|
475
|
+
timestamps, and whether to include isolated positions.
|
|
476
|
+
|
|
477
|
+
Returns:
|
|
478
|
+
IndexerAccountSnapshotsData: Dict mapping subaccount hex -> timestamp -> snapshot data.
|
|
479
|
+
Each snapshot contains balances with trackedVars including netEntryUnrealized.
|
|
480
|
+
"""
|
|
481
|
+
return ensure_data_type(
|
|
482
|
+
self.query(IndexerAccountSnapshotsParams.parse_obj(params)).data,
|
|
483
|
+
IndexerAccountSnapshotsData,
|
|
484
|
+
)
|
|
@@ -182,7 +182,7 @@ class IndexerEventTrackedData(NadoBaseModel):
|
|
|
182
182
|
net_funding_cumulative: str
|
|
183
183
|
net_entry_unrealized: str
|
|
184
184
|
net_entry_cumulative: str
|
|
185
|
-
|
|
185
|
+
quote_volume_cumulative: str
|
|
186
186
|
|
|
187
187
|
|
|
188
188
|
class IndexerEvent(IndexerBaseModel, IndexerEventTrackedData):
|
|
@@ -49,6 +49,7 @@ class IndexerQueryType(StrEnum):
|
|
|
49
49
|
REFERRAL_CODE = "referral_code"
|
|
50
50
|
SUBACCOUNTS = "subaccounts"
|
|
51
51
|
USDC_PRICE = "usdc_price"
|
|
52
|
+
ACCOUNT_SNAPSHOTS = "account_snapshots"
|
|
52
53
|
# TODO: revise once this endpoint is live
|
|
53
54
|
TOKEN_MERKLE_PROOFS = "token_merkle_proofs"
|
|
54
55
|
FOUNDATION_REWARDS_MERKLE_PROOFS = "foundation_rewards_merkle_proofs"
|
|
@@ -292,6 +293,17 @@ class IndexerInterestAndFundingParams(NadoBaseModel):
|
|
|
292
293
|
limit: int
|
|
293
294
|
|
|
294
295
|
|
|
296
|
+
class IndexerAccountSnapshotsParams(NadoBaseModel):
|
|
297
|
+
"""
|
|
298
|
+
Parameters for querying account snapshots.
|
|
299
|
+
"""
|
|
300
|
+
|
|
301
|
+
subaccounts: list[str]
|
|
302
|
+
timestamps: list[int]
|
|
303
|
+
isolated: Optional[bool] = None
|
|
304
|
+
active: Optional[bool] = None
|
|
305
|
+
|
|
306
|
+
|
|
295
307
|
IndexerParams = Union[
|
|
296
308
|
IndexerSubaccountHistoricalOrdersParams,
|
|
297
309
|
IndexerHistoricalOrdersByDigestParams,
|
|
@@ -314,6 +326,7 @@ IndexerParams = Union[
|
|
|
314
326
|
IndexerTokenMerkleProofsParams,
|
|
315
327
|
IndexerFoundationRewardsMerkleProofsParams,
|
|
316
328
|
IndexerInterestAndFundingParams,
|
|
329
|
+
IndexerAccountSnapshotsParams,
|
|
317
330
|
]
|
|
318
331
|
|
|
319
332
|
|
|
@@ -487,6 +500,14 @@ class IndexerInterestAndFundingRequest(NadoBaseModel):
|
|
|
487
500
|
interest_and_funding: IndexerInterestAndFundingParams
|
|
488
501
|
|
|
489
502
|
|
|
503
|
+
class IndexerAccountSnapshotsRequest(NadoBaseModel):
|
|
504
|
+
"""
|
|
505
|
+
Request object for querying account snapshots.
|
|
506
|
+
"""
|
|
507
|
+
|
|
508
|
+
account_snapshots: IndexerAccountSnapshotsParams
|
|
509
|
+
|
|
510
|
+
|
|
490
511
|
IndexerRequest = Union[
|
|
491
512
|
IndexerHistoricalOrdersRequest,
|
|
492
513
|
IndexerMatchesRequest,
|
|
@@ -508,6 +529,7 @@ IndexerRequest = Union[
|
|
|
508
529
|
IndexerTokenMerkleProofsRequest,
|
|
509
530
|
IndexerFoundationRewardsMerkleProofsRequest,
|
|
510
531
|
IndexerInterestAndFundingRequest,
|
|
532
|
+
IndexerAccountSnapshotsRequest,
|
|
511
533
|
]
|
|
512
534
|
|
|
513
535
|
|
|
@@ -681,6 +703,14 @@ class IndexerInterestAndFundingData(NadoBaseModel):
|
|
|
681
703
|
IndexerLiquidationFeedData = list[IndexerLiquidatableAccount]
|
|
682
704
|
|
|
683
705
|
|
|
706
|
+
class IndexerAccountSnapshotsData(NadoBaseModel):
|
|
707
|
+
"""
|
|
708
|
+
Data object for subaccount snapshots grouped by subaccount and timestamp.
|
|
709
|
+
"""
|
|
710
|
+
|
|
711
|
+
snapshots: Dict[str, Dict[str, list[IndexerEvent]]]
|
|
712
|
+
|
|
713
|
+
|
|
684
714
|
IndexerResponseData = Union[
|
|
685
715
|
IndexerHistoricalOrdersData,
|
|
686
716
|
IndexerMatchesData,
|
|
@@ -702,6 +732,7 @@ IndexerResponseData = Union[
|
|
|
702
732
|
IndexerInterestAndFundingData,
|
|
703
733
|
IndexerLiquidationFeedData,
|
|
704
734
|
IndexerFundingRatesData,
|
|
735
|
+
IndexerAccountSnapshotsData,
|
|
705
736
|
]
|
|
706
737
|
|
|
707
738
|
|
|
@@ -809,6 +840,10 @@ def to_indexer_request(params: IndexerParams) -> IndexerRequest:
|
|
|
809
840
|
IndexerInterestAndFundingRequest,
|
|
810
841
|
IndexerQueryType.INTEREST_AND_FUNDING.value,
|
|
811
842
|
),
|
|
843
|
+
IndexerAccountSnapshotsParams: (
|
|
844
|
+
IndexerAccountSnapshotsRequest,
|
|
845
|
+
IndexerQueryType.ACCOUNT_SNAPSHOTS.value,
|
|
846
|
+
),
|
|
812
847
|
}
|
|
813
848
|
|
|
814
849
|
RequestClass, field_name = indexer_request_mapping[type(params)]
|
nado_protocol/utils/__init__.py
CHANGED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Balance Value Calculation Utilities
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from decimal import Decimal
|
|
6
|
+
from typing import Union
|
|
7
|
+
from nado_protocol.engine_client.types.models import (
|
|
8
|
+
SpotProduct,
|
|
9
|
+
PerpProduct,
|
|
10
|
+
SpotProductBalance,
|
|
11
|
+
PerpProductBalance,
|
|
12
|
+
)
|
|
13
|
+
from nado_protocol.utils.math import from_x18
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def calculate_spot_balance_value(
|
|
17
|
+
amount: Union[Decimal, float, str], oracle_price: Union[Decimal, float, str]
|
|
18
|
+
) -> Decimal:
|
|
19
|
+
"""
|
|
20
|
+
Calculate the quote value of a spot balance.
|
|
21
|
+
|
|
22
|
+
Formula: amount * oracle_price
|
|
23
|
+
|
|
24
|
+
This is used for:
|
|
25
|
+
- Calculating health contributions
|
|
26
|
+
- Determining deposits vs borrows
|
|
27
|
+
- Portfolio value calculations
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
amount: Token amount (can be negative for borrows)
|
|
31
|
+
oracle_price: Oracle price in quote currency
|
|
32
|
+
|
|
33
|
+
Returns:
|
|
34
|
+
Value in quote currency (positive for deposits, negative for borrows)
|
|
35
|
+
|
|
36
|
+
Example:
|
|
37
|
+
>>> calculate_spot_balance_value(100, 2000) # 100 ETH at $2000
|
|
38
|
+
Decimal('200000')
|
|
39
|
+
>>> calculate_spot_balance_value(-50, 2000) # 50 ETH borrowed
|
|
40
|
+
Decimal('-100000')
|
|
41
|
+
"""
|
|
42
|
+
amount_dec = Decimal(str(amount))
|
|
43
|
+
price_dec = Decimal(str(oracle_price))
|
|
44
|
+
return amount_dec * price_dec
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def calculate_perp_balance_notional_value(
|
|
48
|
+
amount: Union[Decimal, float, str], oracle_price: Union[Decimal, float, str]
|
|
49
|
+
) -> Decimal:
|
|
50
|
+
"""
|
|
51
|
+
Calculate the notional value of a perp position.
|
|
52
|
+
|
|
53
|
+
Formula: abs(amount * oracle_price)
|
|
54
|
+
|
|
55
|
+
This represents the total size of the position in quote currency terms,
|
|
56
|
+
regardless of direction (long or short).
|
|
57
|
+
|
|
58
|
+
Args:
|
|
59
|
+
amount: Position size (positive for long, negative for short)
|
|
60
|
+
oracle_price: Oracle price in quote currency
|
|
61
|
+
|
|
62
|
+
Returns:
|
|
63
|
+
Absolute notional value in quote currency
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
>>> calculate_perp_balance_notional_value(10, 50000) # 10 BTC long
|
|
67
|
+
Decimal('500000')
|
|
68
|
+
>>> calculate_perp_balance_notional_value(-10, 50000) # 10 BTC short
|
|
69
|
+
Decimal('500000')
|
|
70
|
+
"""
|
|
71
|
+
amount_dec = Decimal(str(amount))
|
|
72
|
+
price_dec = Decimal(str(oracle_price))
|
|
73
|
+
return abs(amount_dec * price_dec)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def calculate_perp_balance_value(
|
|
77
|
+
amount: Union[Decimal, float, str],
|
|
78
|
+
oracle_price: Union[Decimal, float, str],
|
|
79
|
+
v_quote_balance: Union[Decimal, float, str],
|
|
80
|
+
) -> Decimal:
|
|
81
|
+
"""
|
|
82
|
+
Calculate the true quote value of a perp balance (unrealized PnL).
|
|
83
|
+
|
|
84
|
+
Formula: (amount * oracle_price) + v_quote_balance
|
|
85
|
+
|
|
86
|
+
The v_quote_balance represents:
|
|
87
|
+
- Unrealized PnL from price changes
|
|
88
|
+
- Accumulated funding payments
|
|
89
|
+
- Entry cost adjustments
|
|
90
|
+
|
|
91
|
+
This value is what would be added to your balance if the position were closed.
|
|
92
|
+
|
|
93
|
+
Args:
|
|
94
|
+
amount: Position size
|
|
95
|
+
oracle_price: Oracle price in quote currency
|
|
96
|
+
v_quote_balance: Virtual quote balance (unsettled PnL)
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Total value in quote currency (can be positive or negative)
|
|
100
|
+
|
|
101
|
+
Example:
|
|
102
|
+
>>> # Long 10 BTC at $50k, now at $51k, with funding
|
|
103
|
+
>>> calculate_perp_balance_value(10, 51000, -500000)
|
|
104
|
+
Decimal('10000') # $10k profit
|
|
105
|
+
"""
|
|
106
|
+
amount_dec = Decimal(str(amount))
|
|
107
|
+
price_dec = Decimal(str(oracle_price))
|
|
108
|
+
v_quote_dec = Decimal(str(v_quote_balance))
|
|
109
|
+
return (amount_dec * price_dec) + v_quote_dec
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def parse_spot_balance_value(
|
|
113
|
+
balance: SpotProductBalance, product: SpotProduct
|
|
114
|
+
) -> Decimal:
|
|
115
|
+
"""
|
|
116
|
+
Parse spot balance value from raw SDK types.
|
|
117
|
+
|
|
118
|
+
This is a convenience function that extracts values from the SDK types
|
|
119
|
+
and calls calculate_spot_balance_value.
|
|
120
|
+
|
|
121
|
+
Args:
|
|
122
|
+
balance: Spot balance from subaccount info
|
|
123
|
+
product: Spot product information
|
|
124
|
+
|
|
125
|
+
Returns:
|
|
126
|
+
Balance value in quote currency
|
|
127
|
+
"""
|
|
128
|
+
amount = Decimal(from_x18(int(balance.balance.amount)))
|
|
129
|
+
oracle_price = Decimal(from_x18(int(product.oracle_price_x18)))
|
|
130
|
+
return calculate_spot_balance_value(amount, oracle_price)
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def parse_perp_balance_notional_value(
|
|
134
|
+
balance: PerpProductBalance, product: PerpProduct
|
|
135
|
+
) -> Decimal:
|
|
136
|
+
"""
|
|
137
|
+
Parse perp notional value from raw SDK types.
|
|
138
|
+
|
|
139
|
+
Args:
|
|
140
|
+
balance: Perp balance from subaccount info
|
|
141
|
+
product: Perp product information
|
|
142
|
+
|
|
143
|
+
Returns:
|
|
144
|
+
Notional value in quote currency
|
|
145
|
+
"""
|
|
146
|
+
amount = Decimal(from_x18(int(balance.balance.amount)))
|
|
147
|
+
oracle_price = Decimal(from_x18(int(product.oracle_price_x18)))
|
|
148
|
+
return calculate_perp_balance_notional_value(amount, oracle_price)
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def parse_perp_balance_value(
|
|
152
|
+
balance: PerpProductBalance, product: PerpProduct
|
|
153
|
+
) -> Decimal:
|
|
154
|
+
"""
|
|
155
|
+
Parse perp balance value (unrealized PnL) from raw SDK types.
|
|
156
|
+
|
|
157
|
+
Args:
|
|
158
|
+
balance: Perp balance from subaccount info
|
|
159
|
+
product: Perp product information
|
|
160
|
+
|
|
161
|
+
Returns:
|
|
162
|
+
Balance value in quote currency
|
|
163
|
+
"""
|
|
164
|
+
amount = Decimal(from_x18(int(balance.balance.amount)))
|
|
165
|
+
oracle_price = Decimal(from_x18(int(product.oracle_price_x18)))
|
|
166
|
+
v_quote = Decimal(from_x18(int(balance.balance.v_quote_balance)))
|
|
167
|
+
return calculate_perp_balance_value(amount, oracle_price, v_quote)
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def calculate_total_spot_deposits_and_borrows(
|
|
171
|
+
balances: list[tuple[SpotProductBalance, SpotProduct]]
|
|
172
|
+
) -> tuple[Decimal, Decimal]:
|
|
173
|
+
"""
|
|
174
|
+
Calculate total spot deposits and borrows across all balances.
|
|
175
|
+
|
|
176
|
+
Args:
|
|
177
|
+
balances: List of (balance, product) tuples
|
|
178
|
+
|
|
179
|
+
Returns:
|
|
180
|
+
Tuple of (total_deposits, total_borrows) in quote currency
|
|
181
|
+
Both values are positive (borrows is absolute value)
|
|
182
|
+
|
|
183
|
+
Example:
|
|
184
|
+
>>> balances = [(usdt_balance, usdt_product), (eth_balance, eth_product)]
|
|
185
|
+
>>> deposits, borrows = calculate_total_spot_deposits_and_borrows(balances)
|
|
186
|
+
>>> deposits # Total deposits
|
|
187
|
+
Decimal('10000')
|
|
188
|
+
>>> borrows # Total borrows (absolute value)
|
|
189
|
+
Decimal('5000')
|
|
190
|
+
"""
|
|
191
|
+
total_deposits = Decimal(0)
|
|
192
|
+
total_borrows = Decimal(0)
|
|
193
|
+
|
|
194
|
+
for balance, product in balances:
|
|
195
|
+
value = parse_spot_balance_value(balance, product)
|
|
196
|
+
if value > 0:
|
|
197
|
+
total_deposits += value
|
|
198
|
+
else:
|
|
199
|
+
total_borrows += abs(value)
|
|
200
|
+
|
|
201
|
+
return total_deposits, total_borrows
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
def calculate_total_perp_notional(
|
|
205
|
+
balances: list[tuple[PerpProductBalance, PerpProduct]]
|
|
206
|
+
) -> Decimal:
|
|
207
|
+
"""
|
|
208
|
+
Calculate total notional value across all perp positions.
|
|
209
|
+
|
|
210
|
+
Args:
|
|
211
|
+
balances: List of (balance, product) tuples
|
|
212
|
+
|
|
213
|
+
Returns:
|
|
214
|
+
Total notional value in quote currency
|
|
215
|
+
|
|
216
|
+
Example:
|
|
217
|
+
>>> balances = [(btc_perp_balance, btc_perp_product)]
|
|
218
|
+
>>> total = calculate_total_perp_notional(balances)
|
|
219
|
+
>>> total
|
|
220
|
+
Decimal('500000') # Total position size
|
|
221
|
+
"""
|
|
222
|
+
total = Decimal(0)
|
|
223
|
+
for balance, product in balances:
|
|
224
|
+
total += parse_perp_balance_notional_value(balance, product)
|
|
225
|
+
return total
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
def calculate_total_perp_value(
|
|
229
|
+
balances: list[tuple[PerpProductBalance, PerpProduct]]
|
|
230
|
+
) -> Decimal:
|
|
231
|
+
"""
|
|
232
|
+
Calculate total unrealized PnL across all perp positions.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
balances: List of (balance, product) tuples
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Total unrealized PnL in quote currency (can be positive or negative)
|
|
239
|
+
|
|
240
|
+
Example:
|
|
241
|
+
>>> balances = [(btc_perp_balance, btc_perp_product)]
|
|
242
|
+
>>> total_pnl = calculate_total_perp_value(balances)
|
|
243
|
+
>>> total_pnl
|
|
244
|
+
Decimal('10000') # $10k unrealized profit
|
|
245
|
+
"""
|
|
246
|
+
total = Decimal(0)
|
|
247
|
+
for balance, product in balances:
|
|
248
|
+
total += parse_perp_balance_value(balance, product)
|
|
249
|
+
return total
|