1
+ from driftpy .constants import BASE_PRECISION , PRICE_PRECISION , QUOTE_SPOT_MARKET_INDEX , SPOT_BALANCE_PRECISION
2
+ from driftpy .pickle .vat import Vat
3
+ from driftpy .types import is_variant
4
+ from fastapi import APIRouter
5
+ import logging
6
+ from collections import defaultdict
7
+
8
+ from backend .state import BackendRequest
9
+
10
+ router = APIRouter ()
11
+ logger = logging .getLogger (__name__ )
12
+
13
+
14
+ def format_number (number : float , decimals : int = 4 , use_commas : bool = True ) -> str :
15
+ """Format a number with proper decimal places and optional comma separators"""
16
+ if abs (number ) >= 1e6 :
17
+ # For large numbers, use millions format
18
+ return f"{ number / 1e6 :,.{decimals }f} M"
19
+ elif abs (number ) >= 1e3 and use_commas :
20
+ return f"{ number :,.{decimals }f} "
21
+ else :
22
+ return f"{ number :.{decimals }f} "
23
+
24
+
25
+ async def _get_aggregated_positions (vat : Vat ) -> dict :
26
+ """
27
+ Get aggregated positions for all users in the system.
28
+
29
+ Args:
30
+ vat: The Vat instance containing user and market data
31
+
32
+ Returns:
33
+ dict: Dictionary containing aggregated position data including:
34
+ - total_unique_authorities: Number of unique user authorities
35
+ - total_sub_accounts: Total number of sub-accounts
36
+ - total_net_value: Total net value across all accounts
37
+ - perp_markets: Dictionary of perpetual market aggregates
38
+ - spot_markets: Dictionary of spot market aggregates
39
+ - errors: List of any errors encountered during processing
40
+ """
41
+ # Initialize aggregation containers
42
+ perp_aggregates = defaultdict (lambda : {
43
+ "market_name" : "" ,
44
+ "total_long_usd" : 0.0 ,
45
+ "total_short_usd" : 0.0 ,
46
+ "total_lp_shares" : 0 ,
47
+ "current_price" : None , # Changed to None as default
48
+ "unique_users" : set (),
49
+ "errors" : [] # Track errors per market
50
+ })
51
+
52
+ spot_aggregates = defaultdict (lambda : {
53
+ "market_name" : "" ,
54
+ "total_deposits_native" : 0.0 ,
55
+ "total_borrows_native" : 0.0 ,
56
+ "total_deposits_usd" : 0.0 ,
57
+ "total_borrows_usd" : 0.0 ,
58
+ "token_price" : None , # Changed to None as default
59
+ "decimals" : 0 ,
60
+ "unique_users" : set (),
61
+ "errors" : [] # Track errors per market
62
+ })
63
+
64
+ total_net_value = 0.0
65
+ total_sub_accounts = 0
66
+ unique_authorities = set ()
67
+ global_errors = [] # Track global errors
68
+
69
+ # Log the available oracles for debugging
70
+ logger .info (f"Available perp oracles: { list (vat .perp_oracles .keys ())} " )
71
+ logger .info (f"Available spot oracles: { list (vat .spot_oracles .keys ())} " )
72
+
73
+ user_count = sum (1 for _ in vat .users .values ())
74
+ logger .info (f"Processing { user_count } users" )
75
+
76
+ # Process all users
77
+ for user in vat .users .values ():
78
+ try :
79
+ user_account = user .get_user_account ()
80
+ authority = str (user_account .authority )
81
+ unique_authorities .add (authority )
82
+ total_sub_accounts += 1
83
+
84
+ try :
85
+ total_net_value += user .get_net_usd_value () / 1e6
86
+ except Exception as e :
87
+ logger .warning (f"Error calculating net value for user { authority } : { str (e )} " )
88
+
89
+ # Process perpetual positions
90
+ perp_positions = user .get_active_perp_positions ()
91
+ for position in perp_positions :
92
+ try :
93
+ market = user .drift_client .get_perp_market_account (position .market_index )
94
+ market_name = bytes (market .name ).decode ('utf-8' ).strip ('\x00 ' )
95
+
96
+ agg = perp_aggregates [position .market_index ]
97
+ agg ["market_name" ] = market_name
98
+
99
+ oracle_price_data = vat .perp_oracles .get (position .market_index )
100
+ if oracle_price_data is None :
101
+ error_msg = f"Oracle not found for perp market { position .market_index } "
102
+ if error_msg not in agg ["errors" ]:
103
+ agg ["errors" ].append (error_msg )
104
+ logger .warning (error_msg )
105
+ continue
106
+
107
+ agg ["current_price" ] = oracle_price_data .price / PRICE_PRECISION
108
+
109
+ position_value = abs (user .get_perp_position_value (
110
+ position .market_index ,
111
+ oracle_price_data ,
112
+ include_open_orders = True
113
+ ) / PRICE_PRECISION )
114
+
115
+ base_asset_amount = position .base_asset_amount / BASE_PRECISION
116
+ if base_asset_amount > 0 :
117
+ agg ["total_long_usd" ] += position_value
118
+ else :
119
+ agg ["total_short_usd" ] += position_value
120
+
121
+ agg ["total_lp_shares" ] += position .lp_shares / BASE_PRECISION
122
+ agg ["unique_users" ].add (authority )
123
+ except Exception as e :
124
+ error_msg = f"Error processing perp position for market { position .market_index } : { str (e )} "
125
+ logger .warning (error_msg )
126
+ if error_msg not in perp_aggregates [position .market_index ]["errors" ]:
127
+ perp_aggregates [position .market_index ]["errors" ].append (error_msg )
128
+
129
+ # Process spot positions
130
+ spot_positions = user .get_active_spot_positions ()
131
+ for position in spot_positions :
132
+ try :
133
+ market = user .drift_client .get_spot_market_account (position .market_index )
134
+ market_name = bytes (market .name ).decode ('utf-8' ).strip ('\x00 ' )
135
+
136
+ agg = spot_aggregates [position .market_index ]
137
+ agg ["market_name" ] = market_name
138
+ agg ["decimals" ] = market .decimals
139
+
140
+ token_amount = user .get_token_amount (position .market_index )
141
+ formatted_amount = token_amount / (10 ** market .decimals )
142
+
143
+ if position .market_index == QUOTE_SPOT_MARKET_INDEX :
144
+ token_price = 1.0
145
+ token_value = abs (formatted_amount )
146
+ else :
147
+ oracle_price_data = vat .spot_oracles .get (position .market_index )
148
+ if oracle_price_data is None :
149
+ error_msg = f"Oracle not found for spot market { position .market_index } "
150
+ if error_msg not in agg ["errors" ]:
151
+ agg ["errors" ].append (error_msg )
152
+ logger .warning (error_msg )
153
+ continue
154
+
155
+ token_price = oracle_price_data .price / PRICE_PRECISION
156
+ if token_amount < 0 :
157
+ token_value = abs (user .get_spot_market_liability_value (
158
+ market_index = position .market_index ,
159
+ include_open_orders = True
160
+ ) / PRICE_PRECISION )
161
+ else :
162
+ token_value = abs (user .get_spot_market_asset_value (
163
+ market_index = position .market_index ,
164
+ include_open_orders = True
165
+ ) / PRICE_PRECISION )
166
+
167
+ agg ["token_price" ] = token_price
168
+
169
+ if token_amount > 0 :
170
+ agg ["total_deposits_native" ] += formatted_amount
171
+ agg ["total_deposits_usd" ] += token_value
172
+ else :
173
+ agg ["total_borrows_native" ] += abs (formatted_amount )
174
+ agg ["total_borrows_usd" ] += token_value
175
+
176
+ agg ["unique_users" ].add (authority )
177
+ except Exception as e :
178
+ error_msg = f"Error processing spot position for market { position .market_index } : { str (e )} "
179
+ logger .warning (error_msg )
180
+ if error_msg not in spot_aggregates [position .market_index ]["errors" ]:
181
+ spot_aggregates [position .market_index ]["errors" ].append (error_msg )
182
+
183
+ except Exception as e :
184
+ error_msg = f"Error processing user { authority } : { str (e )} "
185
+ logger .warning (error_msg )
186
+ global_errors .append (error_msg )
187
+ continue
188
+
189
+ # Convert sets to counts for JSON serialization
190
+ for agg in perp_aggregates .values ():
191
+ agg ["unique_users" ] = len (agg ["unique_users" ])
192
+ for agg in spot_aggregates .values ():
193
+ agg ["unique_users" ] = len (agg ["unique_users" ])
194
+
195
+ # Sort markets by index
196
+ sorted_perp_markets = dict (sorted (perp_aggregates .items (), key = lambda x : int (x [0 ])))
197
+ sorted_spot_markets = dict (sorted (spot_aggregates .items (), key = lambda x : int (x [0 ])))
198
+
199
+ return {
200
+ "total_unique_authorities" : len (unique_authorities ),
201
+ "total_sub_accounts" : total_sub_accounts ,
202
+ "total_net_value" : total_net_value ,
203
+ "perp_markets" : sorted_perp_markets ,
204
+ "spot_markets" : sorted_spot_markets ,
205
+ "global_errors" : global_errors
206
+ }
207
+
208
+
209
+ @router .get ("/aggregated" )
210
+ async def get_aggregated_positions (request : BackendRequest ):
211
+ """
212
+ Get aggregated positions across all users in the Drift Protocol.
213
+
214
+ This endpoint calculates and returns aggregated position data including:
215
+ - Total unique authorities and sub-accounts
216
+ - Total net value across all accounts
217
+ - Per-market aggregates for both perpetual and spot markets
218
+ - Position values, user counts, and market details
219
+ - Error tracking for missing oracles or calculation issues
220
+
221
+ Returns:
222
+ dict: Aggregated position data across all markets and users
223
+ """
224
+ return await _get_aggregated_positions (request .state .backend_state .vat )
0 commit comments