Skip to content

Commit 461096c

Browse files
committed
- refactored pnl and vaults pages to improve page load UX, retry logic, auto-refresh, and include slot age info box on page.
- enhanced backend apis to support this new functionality. - renamed vault and pnl files to reflect _page and _api respectively. - updated required helper/app files to match new names/functions.
1 parent ba29615 commit 461096c

File tree

9 files changed

+144
-60
lines changed

9 files changed

+144
-60
lines changed

backend/api/__init__.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,12 @@
88
market_recommender_api,
99
metadata,
1010
open_interest_api,
11-
pnl,
11+
pnl_api,
1212
positions,
1313
price_shock,
1414
snapshot,
1515
ucache,
1616
user_retention_explorer_api,
1717
user_retention_summary_api,
18-
vaults,
18+
vaults_api,
1919
)

backend/api/pnl.py renamed to backend/api/pnl_api.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
@router.get("/top_pnl")
1010
def get_top_pnl(request: BackendRequest, limit: int = 1000):
1111
vat: Vat = request.state.backend_state.vat
12+
last_oracle_slot = getattr(request.state.backend_state, "last_oracle_slot", 0)
1213

1314
pnl_data = []
1415
for user in vat.users.values():
@@ -32,4 +33,4 @@ def get_top_pnl(request: BackendRequest, limit: int = 1000):
3233
continue
3334

3435
pnl_data.sort(key=lambda x: x["total_pnl"], reverse=True)
35-
return pnl_data[:limit]
36+
return {"pnl": pnl_data[:limit], "slot": last_oracle_slot}

backend/api/vaults.py renamed to backend/api/vaults_api.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ async def get_vault_data(request: BackendRequest):
2020

2121
client = await create_vault_client()
2222
analytics = await client.calculate_analytics()
23+
last_oracle_slot = getattr(request.state.backend_state, "last_oracle_slot", 0)
2324

2425
vaults = []
2526
print(analytics["vaults"][0])
@@ -60,5 +61,6 @@ async def get_vault_data(request: BackendRequest):
6061
vault_depositors[vault_info["pubkey"]] = serializable_depositors
6162

6263
return {
63-
"data": {"analytics": serializable_analytics, "depositors": vault_depositors}
64+
"data": {"analytics": serializable_analytics, "depositors": vault_depositors},
65+
"slot": last_oracle_slot,
6466
}

backend/app.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717
market_recommender_api,
1818
metadata,
1919
open_interest_api,
20-
pnl,
20+
pnl_api,
2121
positions,
2222
price_shock,
2323
snapshot,
2424
ucache,
25-
vaults,
25+
vaults_api,
2626
high_leverage_api,
2727
user_retention_summary_api,
2828
user_retention_explorer_api,
@@ -88,8 +88,8 @@ async def lifespan(app: FastAPI):
8888
app.include_router(snapshot.router, prefix="/api/snapshot", tags=["snapshot"])
8989
app.include_router(ucache.router, prefix="/api/ucache", tags=["ucache"])
9090
app.include_router(deposits_api.router, prefix="/api/deposits", tags=["deposits"])
91-
app.include_router(pnl.router, prefix="/api/pnl", tags=["pnl"])
92-
app.include_router(vaults.router, prefix="/api/vaults", tags=["vaults"])
91+
app.include_router(pnl_api.router, prefix="/api/pnl", tags=["pnl"])
92+
app.include_router(vaults_api.router, prefix="/api/vaults", tags=["vaults"])
9393
app.include_router(positions.router, prefix="/api/positions", tags=["positions"])
9494
app.include_router(market_recommender_api.router, prefix="/api/market-recommender", tags=["market-recommender"])
9595
app.include_router(open_interest_api.router, prefix="/api/open-interest", tags=["open-interest"])

src/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,10 +15,10 @@
1515
from page.liquidation_curves_page import liquidation_curves_page
1616
from page.market_inspector import market_inspector_page
1717
from page.orderbook import orderbook_page
18-
from page.pnl import pnl_page
18+
from page.pnl_page import pnl_page
1919
from page.price_shock import price_shock_cached_page
2020
from page.swap import show as swap_page
21-
from page.vaults import vaults_page
21+
from page.vaults_page import vaults_page
2222
from page.welcome import welcome_page
2323
from page.market_recommender_page import market_recommender_page
2424
from page.open_interest_page import open_interest_page

src/page/pnl.py

Lines changed: 0 additions & 40 deletions
This file was deleted.

src/page/pnl_page.py

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import pandas as pd
2+
import streamlit as st
3+
import time
4+
5+
from lib.api import fetch_api_data
6+
from src.utils import get_current_slot
7+
8+
RETRY_DELAY_SECONDS = 5
9+
10+
11+
def is_processing(result):
12+
"""Checks if the API result indicates backend processing."""
13+
return isinstance(result, dict) and result.get("result") == "processing"
14+
15+
16+
def has_error(result):
17+
"""Checks if the API result indicates an error."""
18+
return result is None or (
19+
isinstance(result, dict) and result.get("result") == "error"
20+
) or ("pnl" not in result if isinstance(result, dict) else False)
21+
22+
23+
def pnl_page():
24+
st.title("Top PnL by User (All Time)")
25+
response = fetch_api_data("pnl", "top_pnl", retry=False)
26+
27+
if is_processing(response):
28+
st.info(
29+
f"Backend is processing PnL data. Auto-refreshing in {RETRY_DELAY_SECONDS} seconds..."
30+
)
31+
with st.spinner("Please wait..."):
32+
time.sleep(RETRY_DELAY_SECONDS)
33+
st.rerun()
34+
return
35+
36+
if has_error(response):
37+
error_msg = (
38+
response["message"]
39+
if isinstance(response, dict) and "message" in response
40+
else "Could not connect or fetch PnL data."
41+
)
42+
st.error(f"Failed to fetch PnL data: {error_msg}")
43+
if st.button("Retry"):
44+
st.rerun()
45+
return
46+
47+
pnl_data = response["pnl"]
48+
if not pnl_data:
49+
st.info("No PnL data available.")
50+
return
51+
52+
slot = response.get("slot", 0)
53+
current_slot = get_current_slot()
54+
if slot > 0 and current_slot > 0:
55+
slot_age = current_slot - slot
56+
st.info(f"Data from slot {slot}, which is {slot_age} slots old.")
57+
58+
df = pd.DataFrame(pnl_data)
59+
for col in ["realized_pnl", "unrealized_pnl", "total_pnl"]:
60+
df[col] = df[col].map("${:,.2f}".format)
61+
62+
csv = df.to_csv(index=False)
63+
st.download_button(
64+
"Download PnL Data CSV", csv, "top_pnl.csv", "text/csv", key="download-pnl"
65+
)
66+
st.dataframe(
67+
df,
68+
height=650,
69+
column_config={
70+
"authority": st.column_config.TextColumn(
71+
"Authority",
72+
help="Authority address",
73+
),
74+
"user_key": st.column_config.TextColumn(
75+
"User Account",
76+
help="User account address",
77+
),
78+
"realized_pnl": st.column_config.NumberColumn("All Time Realized PnL"),
79+
"unrealized_pnl": st.column_config.NumberColumn("Unrealized PnL"),
80+
"total_pnl": st.column_config.NumberColumn("Total PnL"),
81+
},
82+
hide_index=True,
83+
)

src/page/vaults.py renamed to src/page/vaults_page.py

Lines changed: 47 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,23 @@
11
import pandas as pd
22
import streamlit as st
3+
import time
34

45
from lib.api import fetch_api_data
6+
from src.utils import get_current_slot
7+
8+
RETRY_DELAY_SECONDS = 10
9+
10+
11+
def is_processing(result):
12+
"""Checks if the API result indicates backend processing."""
13+
return isinstance(result, dict) and result.get("result") == "processing"
14+
15+
16+
def has_error(result):
17+
"""Checks if the API result indicates an error."""
18+
return result is None or (
19+
isinstance(result, dict) and result.get("result") == "error"
20+
) or ("data" not in result if isinstance(result, dict) else False)
521

622

723
def vaults_page():
@@ -11,15 +27,37 @@ def vaults_page():
1127
"This page may be out of date up to 30 minutes."
1228
)
1329

14-
with st.spinner("Loading vault data..."):
15-
response = fetch_api_data("vaults", "data", retry=True, max_wait_time=60)
16-
if not response or "data" not in response:
17-
st.error("Failed to load vault data")
18-
return
19-
20-
data = response["data"]
21-
analytics = data["analytics"]
22-
all_depositors = data["depositors"]
30+
response = fetch_api_data("vaults", "data", retry=False)
31+
32+
if is_processing(response):
33+
st.info(
34+
f"Backend is processing vault data. Auto-refreshing in {RETRY_DELAY_SECONDS} seconds..."
35+
)
36+
with st.spinner("Please wait..."):
37+
time.sleep(RETRY_DELAY_SECONDS)
38+
st.rerun()
39+
return
40+
41+
if has_error(response):
42+
error_msg = "Invalid response from backend."
43+
if isinstance(response, dict) and "message" in response:
44+
error_msg = response["message"]
45+
elif has_error(response):
46+
error_msg = "Could not connect or fetch vault data."
47+
st.error(f"Failed to load vault data: {error_msg}")
48+
if st.button("Retry"):
49+
st.rerun()
50+
return
51+
52+
slot = response.get("slot", 0)
53+
current_slot = get_current_slot()
54+
if slot > 0 and current_slot > 0:
55+
slot_age = current_slot - slot
56+
st.info(f"Data from slot {slot}, which is {slot_age} slots old.")
57+
58+
data = response["data"]
59+
analytics = data["analytics"]
60+
all_depositors = data["depositors"]
2361

2462
unique_depositors = set()
2563
for depositors in all_depositors.values():

src/page/welcome.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ def welcome_page():
3535
label="💰 **Deposits** - Track total deposits across all the protocol",
3636
)
3737
st.page_link(
38-
StreamlitPage("page/pnl.py", url_path="pnl"),
38+
StreamlitPage("page/pnl_page.py", url_path="pnl"),
3939
label="💸 **PnL** - Track top trader PnLs",
4040
)
4141

0 commit comments

Comments
 (0)