-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
fd20217
commit dd19182
Showing
6 changed files
with
478 additions
and
0 deletions.
There are no files selected for viewing
Empty file.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
from backend.state import BackendRequest | ||
from backend.state import BackendState | ||
from backend.utils.matrix import get_matrix | ||
from backend.utils.user_metrics import get_usermap_df | ||
from driftpy.drift_client import DriftClient | ||
from driftpy.pickle.vat import Vat | ||
from fastapi import APIRouter | ||
|
||
|
||
router = APIRouter() | ||
|
||
|
||
@router.get("/matrix/{mode}/{perp_market_index}") | ||
async def get_asset_liability_matrix( | ||
request: BackendRequest, mode: int, perp_market_index: int | ||
): | ||
backend_state: BackendState = request.state.backend_state | ||
vat: Vat = backend_state.vat | ||
drift_client: DriftClient = backend_state.dc | ||
|
||
res, df = await get_matrix(drift_client, vat, mode, perp_market_index) | ||
|
||
return { | ||
"res": res.to_dict(), | ||
"df": df.to_dict(), | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,356 @@ | ||
import heapq | ||
|
||
from backend.state import BackendRequest | ||
from driftpy.constants import BASE_PRECISION | ||
from driftpy.constants import PRICE_PRECISION | ||
from driftpy.constants import SPOT_BALANCE_PRECISION | ||
from driftpy.pickle.vat import Vat | ||
from driftpy.types import is_variant | ||
from fastapi import APIRouter | ||
import pandas as pd | ||
|
||
|
||
router = APIRouter() | ||
|
||
|
||
def to_financial(num: float): | ||
""" | ||
Helper function to format a number to a financial format. | ||
""" | ||
num_str = str(num) | ||
decimal_pos = num_str.find(".") | ||
if decimal_pos != -1: | ||
return float(num_str[: decimal_pos + 3]) | ||
return num | ||
|
||
|
||
@router.get("/health_distribution") | ||
def get_account_health_distribution(request: BackendRequest): | ||
""" | ||
Get the distribution of account health across different ranges. | ||
This endpoint calculates the distribution of account health for all users, | ||
categorizing them into health ranges and summing up the total collateral | ||
in each range. | ||
Returns: | ||
list[dict]: A list of dictionaries containing the health distribution data. | ||
Each dictionary has the following keys: | ||
- Health Range (str): The health percentage range (e.g., '0-10%') | ||
- Counts (int): The number of accounts in this range | ||
- Notional Values (float): The total collateral value in this range | ||
""" | ||
vat: Vat = request.state.backend_state.vat | ||
health_notional_distributions = { | ||
"0-10%": 0, | ||
"10-20%": 0, | ||
"20-30%": 0, | ||
"30-40%": 0, | ||
"40-50%": 0, | ||
"50-60%": 0, | ||
"60-70%": 0, | ||
"70-80%": 0, | ||
"80-90%": 0, | ||
"90-100%": 0, | ||
} | ||
health_counts = { | ||
"0-10%": 0, | ||
"10-20%": 0, | ||
"20-30%": 0, | ||
"30-40%": 0, | ||
"40-50%": 0, | ||
"50-60%": 0, | ||
"60-70%": 0, | ||
"70-80%": 0, | ||
"80-90%": 0, | ||
"90-100%": 0, | ||
} | ||
|
||
for user in vat.users.values(): | ||
total_collateral = user.get_total_collateral() / PRICE_PRECISION | ||
current_health = user.get_health() | ||
match current_health: | ||
case _ if current_health < 10: | ||
health_notional_distributions["0-10%"] += total_collateral | ||
health_counts["0-10%"] += 1 | ||
case _ if current_health < 20: | ||
health_notional_distributions["10-20%"] += total_collateral | ||
health_counts["10-20%"] += 1 | ||
case _ if current_health < 30: | ||
health_notional_distributions["20-30%"] += total_collateral | ||
health_counts["20-30%"] += 1 | ||
case _ if current_health < 40: | ||
health_notional_distributions["30-40%"] += total_collateral | ||
health_counts["30-40%"] += 1 | ||
case _ if current_health < 50: | ||
health_notional_distributions["40-50%"] += total_collateral | ||
health_counts["40-50%"] += 1 | ||
case _ if current_health < 60: | ||
health_notional_distributions["50-60%"] += total_collateral | ||
health_counts["50-60%"] += 1 | ||
case _ if current_health < 70: | ||
health_notional_distributions["60-70%"] += total_collateral | ||
health_counts["60-70%"] += 1 | ||
case _ if current_health < 80: | ||
health_notional_distributions["70-80%"] += total_collateral | ||
health_counts["70-80%"] += 1 | ||
case _ if current_health < 90: | ||
health_notional_distributions["80-90%"] += total_collateral | ||
health_counts["80-90%"] += 1 | ||
case _: | ||
health_notional_distributions["90-100%"] += total_collateral | ||
health_counts["90-100%"] += 1 | ||
df = pd.DataFrame( | ||
{ | ||
"Health Range": list(health_counts.keys()), | ||
"Counts": list(health_counts.values()), | ||
"Notional Values": list(health_notional_distributions.values()), | ||
} | ||
) | ||
|
||
return df.to_dict(orient="records") | ||
|
||
|
||
@router.get("/largest_perp_positions") | ||
def get_largest_perp_positions(request: BackendRequest): | ||
""" | ||
Get the top 10 largest perpetual positions by value. | ||
This endpoint retrieves the largest perpetual positions across all users, | ||
calculated based on the current market prices. | ||
Returns: | ||
dict: A dictionary containing lists of data for the top 10 positions: | ||
- Market Index (list[int]): The market indices of the top positions | ||
- Value (list[str]): The formatted dollar values of the positions | ||
- Base Asset Amount (list[str]): The formatted base asset amounts | ||
- Public Key (list[str]): The public keys of the position holders | ||
""" | ||
vat: Vat = request.state.backend_state.vat | ||
top_positions: list[tuple[float, str, int, float]] = [] | ||
|
||
for user in vat.users.values(): | ||
for position in user.get_user_account().perp_positions: | ||
if position.base_asset_amount > 0: | ||
market_price = vat.perp_oracles.get(position.market_index) | ||
if market_price is not None: | ||
market_price_ui = market_price.price / PRICE_PRECISION | ||
base_asset_value = ( | ||
abs(position.base_asset_amount) / BASE_PRECISION | ||
) * market_price_ui | ||
heap_item = ( | ||
to_financial(base_asset_value), | ||
user.user_public_key, | ||
position.market_index, | ||
position.base_asset_amount / BASE_PRECISION, | ||
) | ||
|
||
if len(top_positions) < 10: | ||
heapq.heappush(top_positions, heap_item) | ||
else: | ||
heapq.heappushpop(top_positions, heap_item) | ||
|
||
positions = sorted( | ||
(value, pubkey, market_idx, amt) | ||
for value, pubkey, market_idx, amt in top_positions | ||
) | ||
|
||
positions.reverse() | ||
|
||
data = { | ||
"Market Index": [pos[2] for pos in positions], | ||
"Value": [f"${pos[0]:,.2f}" for pos in positions], | ||
"Base Asset Amount": [f"{pos[3]:,.2f}" for pos in positions], | ||
"Public Key": [pos[1] for pos in positions], | ||
} | ||
|
||
return data | ||
|
||
|
||
@router.get("/most_levered_perp_positions_above_1m") | ||
def get_most_levered_perp_positions_above_1m(request: BackendRequest): | ||
""" | ||
Get the top 10 most leveraged perpetual positions with value above $1 million. | ||
This endpoint calculates the leverage of each perpetual position with a value | ||
over $1 million and returns the top 10 most leveraged positions. | ||
Returns: | ||
dict: A dictionary containing lists of data for the top 10 leveraged positions: | ||
- Market Index (list[int]): The market indices of the top positions | ||
- Value (list[str]): The formatted dollar values of the positions | ||
- Base Asset Amount (list[str]): The formatted base asset amounts | ||
- Leverage (list[str]): The formatted leverage ratios | ||
- Public Key (list[str]): The public keys of the position holders | ||
""" | ||
vat: Vat = request.state.backend_state.vat | ||
top_positions: list[tuple[float, str, int, float, float]] = [] | ||
|
||
for user in vat.users.values(): | ||
total_collateral = user.get_total_collateral() / PRICE_PRECISION | ||
if total_collateral > 0: | ||
for position in user.get_user_account().perp_positions: | ||
if position.base_asset_amount > 0: | ||
market_price = vat.perp_oracles.get(position.market_index) | ||
if market_price is not None: | ||
market_price_ui = market_price.price / PRICE_PRECISION | ||
base_asset_value = ( | ||
abs(position.base_asset_amount) / BASE_PRECISION | ||
) * market_price_ui | ||
leverage = base_asset_value / total_collateral | ||
if base_asset_value > 1_000_000: | ||
heap_item = ( | ||
to_financial(base_asset_value), | ||
user.user_public_key, | ||
position.market_index, | ||
position.base_asset_amount / BASE_PRECISION, | ||
leverage, | ||
) | ||
|
||
if len(top_positions) < 10: | ||
heapq.heappush(top_positions, heap_item) | ||
else: | ||
heapq.heappushpop(top_positions, heap_item) | ||
|
||
positions = sorted( | ||
top_positions, # We can sort directly the heap result | ||
key=lambda x: x[ | ||
4 | ||
], # Sort by leverage, which is the fifth element in your tuple | ||
) | ||
|
||
positions.reverse() | ||
|
||
data = { | ||
"Market Index": [pos[2] for pos in positions], | ||
"Value": [f"${pos[0]:,.2f}" for pos in positions], | ||
"Base Asset Amount": [f"{pos[3]:,.2f}" for pos in positions], | ||
"Leverage": [f"{pos[4]:,.2f}" for pos in positions], | ||
"Public Key": [pos[1] for pos in positions], | ||
} | ||
|
||
return data | ||
|
||
|
||
@router.get("/largest_spot_borrows") | ||
def get_largest_spot_borrows(request: BackendRequest): | ||
""" | ||
Get the top 10 largest spot borrowing positions by value. | ||
This endpoint retrieves the largest spot borrowing positions across all users, | ||
calculated based on the current market prices. | ||
Returns: | ||
dict: A dictionary containing lists of data for the top 10 borrowing positions: | ||
- Market Index (list[int]): The market indices of the top borrows | ||
- Value (list[str]): The formatted dollar values of the borrows | ||
- Scaled Balance (list[str]): The formatted scaled balances of the borrows | ||
- Public Key (list[str]): The public keys of the borrowers | ||
""" | ||
vat: Vat = request.state.backend_state.vat | ||
top_borrows: list[tuple[float, str, int, float]] = [] | ||
|
||
for user in vat.users.values(): | ||
for position in user.get_user_account().spot_positions: | ||
if position.scaled_balance > 0 and is_variant( | ||
position.balance_type, "Borrow" | ||
): | ||
market_price = vat.spot_oracles.get(position.market_index) | ||
if market_price is not None: | ||
market_price_ui = market_price.price / PRICE_PRECISION | ||
borrow_value = ( | ||
position.scaled_balance / SPOT_BALANCE_PRECISION | ||
) * market_price_ui | ||
heap_item = ( | ||
to_financial(borrow_value), | ||
user.user_public_key, | ||
position.market_index, | ||
position.scaled_balance / SPOT_BALANCE_PRECISION, | ||
) | ||
|
||
if len(top_borrows) < 10: | ||
heapq.heappush(top_borrows, heap_item) | ||
else: | ||
heapq.heappushpop(top_borrows, heap_item) | ||
|
||
borrows = sorted( | ||
(value, pubkey, market_idx, amt) | ||
for value, pubkey, market_idx, amt in top_borrows | ||
) | ||
|
||
borrows.reverse() | ||
|
||
data = { | ||
"Market Index": [pos[2] for pos in borrows], | ||
"Value": [f"${pos[0]:,.2f}" for pos in borrows], | ||
"Scaled Balance": [f"{pos[3]:,.2f}" for pos in borrows], | ||
"Public Key": [pos[1] for pos in borrows], | ||
} | ||
|
||
return data | ||
|
||
|
||
@router.get("/most_levered_spot_borrows_above_1m") | ||
def get_most_levered_spot_borrows_above_1m(request: BackendRequest): | ||
""" | ||
Get the top 10 most leveraged spot borrowing positions with value above $750,000. | ||
This endpoint calculates the leverage of each spot borrowing position with a value | ||
over $750,000 and returns the top 10 most leveraged positions. | ||
Returns: | ||
dict: A dictionary containing lists of data for the top 10 leveraged borrowing positions: | ||
- Market Index (list[int]): The market indices of the top borrows | ||
- Value (list[str]): The formatted dollar values of the borrows | ||
- Scaled Balance (list[str]): The formatted scaled balances of the borrows | ||
- Leverage (list[str]): The formatted leverage ratios | ||
- Public Key (list[str]): The public keys of the borrowers | ||
""" | ||
vat: Vat = request.state.backend_state.vat | ||
top_borrows: list[tuple[float, str, int, float, float]] = [] | ||
|
||
for user in vat.users.values(): | ||
total_collateral = user.get_total_collateral() / PRICE_PRECISION | ||
if total_collateral > 0: | ||
for position in user.get_user_account().spot_positions: | ||
if ( | ||
is_variant(position.balance_type, "Borrow") | ||
and position.scaled_balance > 0 | ||
): | ||
market_price = vat.spot_oracles.get(position.market_index) | ||
if market_price is not None: | ||
market_price_ui = market_price.price / PRICE_PRECISION | ||
borrow_value = ( | ||
position.scaled_balance / SPOT_BALANCE_PRECISION | ||
) * market_price_ui | ||
leverage = borrow_value / total_collateral | ||
if borrow_value > 750_000: | ||
heap_item = ( | ||
to_financial(borrow_value), | ||
user.user_public_key, | ||
position.market_index, | ||
position.scaled_balance / SPOT_BALANCE_PRECISION, | ||
leverage, | ||
) | ||
|
||
if len(top_borrows) < 10: | ||
heapq.heappush(top_borrows, heap_item) | ||
else: | ||
heapq.heappushpop(top_borrows, heap_item) | ||
|
||
borrows = sorted( | ||
top_borrows, | ||
key=lambda x: x[4], | ||
) | ||
|
||
borrows.reverse() | ||
|
||
data = { | ||
"Market Index": [pos[2] for pos in borrows], | ||
"Value": [f"${pos[0]:,.2f}" for pos in borrows], | ||
"Scaled Balance": [f"{pos[3]:,.2f}" for pos in borrows], | ||
"Leverage": [f"{pos[4]:,.2f}" for pos in borrows], | ||
"Public Key": [pos[1] for pos in borrows], | ||
} | ||
|
||
return data |
Oops, something went wrong.