Skip to content

Commit 7e446b0

Browse files
committed
Enhance health API endpoints for position retrieval
- Updated the `get_most_levered_perp_positions_above_1m`, `get_largest_spot_borrows`, and `get_most_levered_spot_borrows_above_1m` functions to accept `number_of_positions` and `market_index` as optional parameters, allowing for more flexible queries. - Improved documentation for these endpoints to clarify the new parameters and their usage. - Added error handling for positions with missing market prices and zero collateral, ensuring that errors are logged and included in the response. - Adjusted the logic to return the specified number of positions instead of a fixed count, enhancing the utility of the API. - Updated response structure to include error details for positions where applicable, improving the clarity of the API's output.
1 parent ba3bc6a commit 7e446b0

File tree

1 file changed

+127
-39
lines changed

1 file changed

+127
-39
lines changed

backend/api/health.py

Lines changed: 127 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -194,15 +194,21 @@ def get_largest_perp_positions(request: BackendRequest, number_of_positions: int
194194

195195

196196
@router.get("/most_levered_perp_positions_above_1m")
197-
def get_most_levered_perp_positions_above_1m(request: BackendRequest):
197+
def get_most_levered_perp_positions_above_1m(request: BackendRequest, number_of_positions: int = 10, market_index: int = None):
198198
"""
199-
Get the top 10 most leveraged perpetual positions with value above $1 million.
199+
Get the most leveraged perpetual positions with value above $1 million.
200200
201201
This endpoint calculates the leverage of each perpetual position with a value
202-
over $1 million and returns the top 10 most leveraged positions.
202+
over $1 million and returns the most leveraged positions, limited by number_of_positions.
203+
Results can be filtered by market_index if provided.
204+
205+
Args:
206+
request: The backend request object
207+
number_of_positions: Maximum number of positions to return (default: 10)
208+
market_index: Optional market index to filter by
203209
204210
Returns:
205-
dict: A dictionary containing lists of data for the top 10 leveraged positions:
211+
dict: A dictionary containing lists of data for the top leveraged positions:
206212
- Market Index (list[int]): The market indices of the top positions
207213
- Value (list[str]): The formatted dollar values of the positions
208214
- Base Asset Amount (list[str]): The formatted base asset amounts
@@ -223,6 +229,10 @@ def get_most_levered_perp_positions_above_1m(request: BackendRequest):
223229
continue
224230
if total_collateral > 0:
225231
for position in user.get_user_account().perp_positions:
232+
# Skip if filtering by market_index
233+
if market_index is not None and position.market_index != market_index:
234+
continue
235+
226236
if position.base_asset_amount > 0:
227237
market_price = vat.perp_oracles.get(position.market_index)
228238
if market_price is not None:
@@ -240,7 +250,7 @@ def get_most_levered_perp_positions_above_1m(request: BackendRequest):
240250
leverage,
241251
)
242252

243-
if len(top_positions) < 10:
253+
if len(top_positions) < number_of_positions:
244254
heapq.heappush(top_positions, heap_item)
245255
else:
246256
heapq.heappushpop(top_positions, heap_item)
@@ -264,15 +274,21 @@ def get_most_levered_perp_positions_above_1m(request: BackendRequest):
264274

265275

266276
@router.get("/largest_spot_borrows")
267-
def get_largest_spot_borrows(request: BackendRequest):
277+
def get_largest_spot_borrows(request: BackendRequest, number_of_positions: int = 10, market_index: int = None):
268278
"""
269-
Get the top 10 largest spot borrowing positions by value.
279+
Get the largest spot borrowing positions by value.
270280
271281
This endpoint retrieves the largest spot borrowing positions across all users,
272-
calculated based on the current market prices.
282+
calculated based on the current market prices. Results can be limited by
283+
number_of_positions and filtered by market_index if provided.
284+
285+
Args:
286+
request: The backend request object
287+
number_of_positions: Maximum number of positions to return (default: 10)
288+
market_index: Optional market index to filter by
273289
274290
Returns:
275-
dict: A dictionary containing lists of data for the top 10 borrowing positions:
291+
dict: A dictionary containing lists of data for the top borrowing positions:
276292
- Market Index (list[int]): The market indices of the top borrows
277293
- Value (list[str]): The formatted dollar values of the borrows
278294
- Scaled Balance (list[str]): The formatted scaled balances of the borrows
@@ -283,6 +299,10 @@ def get_largest_spot_borrows(request: BackendRequest):
283299

284300
for user in vat.users.values():
285301
for position in user.get_user_account().spot_positions:
302+
# Skip if filtering by market_index
303+
if market_index is not None and position.market_index != market_index:
304+
continue
305+
286306
if position.scaled_balance > 0 and is_variant(
287307
position.balance_type, "Borrow"
288308
):
@@ -299,7 +319,7 @@ def get_largest_spot_borrows(request: BackendRequest):
299319
position.scaled_balance / SPOT_BALANCE_PRECISION,
300320
)
301321

302-
if len(top_borrows) < 10:
322+
if len(top_borrows) < number_of_positions:
303323
heapq.heappush(top_borrows, heap_item)
304324
else:
305325
heapq.heappushpop(top_borrows, heap_item)
@@ -322,73 +342,141 @@ def get_largest_spot_borrows(request: BackendRequest):
322342

323343

324344
@router.get("/most_levered_spot_borrows_above_1m")
325-
def get_most_levered_spot_borrows_above_1m(request: BackendRequest):
345+
def get_most_levered_spot_borrows_above_1m(request: BackendRequest, number_of_positions: int = 10, market_index: int = None):
326346
"""
327-
Get the top 10 most leveraged spot borrowing positions with value above $750,000.
347+
Get the most leveraged spot borrowing positions with value above $750,000.
328348
329349
This endpoint calculates the leverage of each spot borrowing position with a value
330-
over $750,000 and returns the top 10 most leveraged positions.
350+
over $750,000 and returns the most leveraged positions, limited by number_of_positions.
351+
Results can be filtered by market_index if provided.
352+
353+
Args:
354+
request: The backend request object
355+
number_of_positions: Maximum number of positions to return (default: 10)
356+
market_index: Optional market index to filter by
331357
332358
Returns:
333-
dict: A dictionary containing lists of data for the top 10 leveraged borrowing positions:
359+
dict: A dictionary containing lists of data for the leveraged borrowing positions:
334360
- Market Index (list[int]): The market indices of the top borrows
335361
- Value (list[str]): The formatted dollar values of the borrows
336362
- Scaled Balance (list[str]): The formatted scaled balances of the borrows
337363
- Leverage (list[str]): The formatted leverage ratios
338364
- Public Key (list[str]): The public keys of the borrowers
365+
- Error (list[str]): Error details if any (empty string if no error)
339366
"""
340367
vat: Vat = request.state.backend_state.vat
341-
top_borrows: list[tuple[float, str, int, float, float]] = []
368+
top_borrows: list[tuple[float, str, int, float, float, str]] = [] # Added error field
369+
error_positions = [] # Track positions with errors for logging
342370

343371
for user in vat.users.values():
372+
user_collateral = 0
373+
collateral_error = ""
374+
344375
try:
345-
total_collateral = user.get_total_collateral() / PRICE_PRECISION
376+
user_collateral = user.get_total_collateral() / PRICE_PRECISION
346377
except Exception as e:
347-
print(
348-
f"==> Error from get_most_levered_spot_borrows_above_1m [{user.user_public_key}] ",
349-
e,
378+
collateral_error = f"Collateral error: {str(e)}"
379+
logger.warning(
380+
f"Error calculating collateral for user [{user.user_public_key}]: {str(e)}"
350381
)
351-
raise e
352-
if total_collateral > 0:
353-
for position in user.get_user_account().spot_positions:
354-
if (
355-
is_variant(position.balance_type, "Borrow")
356-
and position.scaled_balance > 0
357-
):
358-
market_price = vat.spot_oracles.get(position.market_index)
359-
if market_price is not None:
382+
383+
for position in user.get_user_account().spot_positions:
384+
# Skip if filtering by market_index
385+
if market_index is not None and position.market_index != market_index:
386+
continue
387+
388+
if is_variant(position.balance_type, "Borrow") and position.scaled_balance > 0:
389+
position_error = collateral_error # Start with any collateral error
390+
market_price = vat.spot_oracles.get(position.market_index)
391+
392+
if market_price is None:
393+
oracle_error = f"Oracle for market {position.market_index} not found"
394+
position_error = oracle_error if not position_error else f"{position_error}; {oracle_error}"
395+
logger.warning(f"{oracle_error} for user [{user.user_public_key}]")
396+
397+
# Add position with error
398+
borrow_value = 0 # Default value when price is unknown
399+
scaled_balance = position.scaled_balance / SPOT_BALANCE_PRECISION
400+
leverage = 0 # Default leverage when calculation is impossible
401+
402+
error_positions.append({
403+
"market_index": position.market_index,
404+
"public_key": user.user_public_key,
405+
"scaled_balance": scaled_balance,
406+
"error": position_error
407+
})
408+
409+
# If we don't have enough items yet, add this one with error
410+
if len(top_borrows) < number_of_positions:
411+
heap_item = (
412+
borrow_value, # Will be sorted last due to 0 value
413+
user.user_public_key,
414+
position.market_index,
415+
scaled_balance,
416+
leverage,
417+
position_error,
418+
)
419+
heapq.heappush(top_borrows, heap_item)
420+
else:
421+
try:
360422
market_price_ui = market_price.price / PRICE_PRECISION
361423
borrow_value = (
362424
position.scaled_balance / SPOT_BALANCE_PRECISION
363425
) * market_price_ui
364-
leverage = borrow_value / total_collateral
426+
427+
if user_collateral > 0:
428+
leverage = borrow_value / user_collateral
429+
else:
430+
leverage = float('inf') # Infinite leverage when collateral is 0
431+
if not position_error:
432+
position_error = "Zero collateral"
433+
365434
if borrow_value > 750_000:
366435
heap_item = (
367436
to_financial(borrow_value),
368437
user.user_public_key,
369438
position.market_index,
370439
position.scaled_balance / SPOT_BALANCE_PRECISION,
371440
leverage,
441+
position_error, # Empty string if no error
372442
)
373443

374-
if len(top_borrows) < 10:
444+
if len(top_borrows) < number_of_positions:
375445
heapq.heappush(top_borrows, heap_item)
376446
else:
377447
heapq.heappushpop(top_borrows, heap_item)
448+
except Exception as e:
449+
calc_error = f"Calculation error: {str(e)}"
450+
position_error = calc_error if not position_error else f"{position_error}; {calc_error}"
451+
logger.warning(
452+
f"Error processing borrow position for user [{user.user_public_key}] market [{position.market_index}]: {str(e)}"
453+
)
454+
455+
# Add error position
456+
error_positions.append({
457+
"market_index": position.market_index,
458+
"public_key": user.user_public_key,
459+
"scaled_balance": position.scaled_balance / SPOT_BALANCE_PRECISION,
460+
"error": position_error
461+
})
462+
463+
# Log all error positions for debugging
464+
if error_positions:
465+
logger.warning(f"Found {len(error_positions)} positions with errors: {error_positions}")
378466

379-
borrows = sorted(
467+
positions = sorted(
380468
top_borrows,
381-
key=lambda x: x[4],
469+
key=lambda x: x[4] if not x[5] else float('-inf'), # Sort error positions first
470+
reverse=True,
382471
)
383472

384-
borrows.reverse()
385-
386473
data = {
387-
"Market Index": [pos[2] for pos in borrows],
388-
"Value": [f"${pos[0]:,.2f}" for pos in borrows],
389-
"Scaled Balance": [f"{pos[3]:,.2f}" for pos in borrows],
390-
"Leverage": [f"{pos[4]:,.2f}" for pos in borrows],
391-
"Public Key": [pos[1] for pos in borrows],
474+
"Market Index": [pos[2] for pos in positions],
475+
"Value": [f"${pos[0]:,.2f}" if not pos[5] else "N/A" for pos in positions],
476+
"Scaled Balance": [f"{pos[3]:,.2f}" for pos in positions],
477+
"Leverage": [f"{pos[4]:,.2f}" if not pos[5] and pos[4] != float('inf') else "∞" if pos[4] == float('inf') else "N/A" for pos in positions],
478+
"Public Key": [pos[1] for pos in positions],
479+
"Error": [pos[5] for pos in positions],
392480
}
393481

394482
return data

0 commit comments

Comments
 (0)