|
1 | 1 | import pandas as pd
|
2 | 2 | import streamlit as st
|
| 3 | +import logging |
| 4 | +from typing import Optional |
3 | 5 | from driftpy.constants.spot_markets import mainnet_spot_market_configs
|
4 | 6 |
|
5 | 7 | from lib.api import fetch_api_data
|
6 | 8 |
|
| 9 | +# Configure logging |
| 10 | +logging.basicConfig(level=logging.INFO) |
| 11 | +logger = logging.getLogger(__name__) |
7 | 12 |
|
8 | 13 | def format_authority(authority: str) -> str:
|
9 | 14 | """Format authority to show first and last 4 chars"""
|
10 | 15 | return f"{authority[:4]}...{authority[-4:]}"
|
11 | 16 |
|
| 17 | +def get_market_symbol(market_index: int) -> str: |
| 18 | + """ |
| 19 | + Get market symbol for a given market index with validation. |
| 20 | + |
| 21 | + Args: |
| 22 | + market_index: The market index to look up |
| 23 | + |
| 24 | + Returns: |
| 25 | + str: Formatted string with market index and symbol |
| 26 | + """ |
| 27 | + try: |
| 28 | + if not isinstance(market_index, (int, float)): |
| 29 | + raise ValueError(f"Invalid market index type: {type(market_index)}") |
| 30 | + |
| 31 | + market_index = int(market_index) |
| 32 | + valid_indices = [x.market_index for x in mainnet_spot_market_configs] |
| 33 | + |
| 34 | + if market_index not in valid_indices: |
| 35 | + logger.error(f"Invalid market index: {market_index}. Valid indices: {valid_indices}") |
| 36 | + return f"{market_index} (Unknown)" |
| 37 | + |
| 38 | + return f"{market_index} ({mainnet_spot_market_configs[market_index].symbol})" |
| 39 | + except Exception as e: |
| 40 | + logger.error(f"Error processing market index {market_index}: {str(e)}") |
| 41 | + return f"{market_index} (Error)" |
12 | 42 |
|
13 | 43 | def deposits_page():
|
14 |
| - params = st.query_params |
15 |
| - market_index = int(params.get("market_index", 0)) |
16 |
| - |
17 |
| - radio_option = st.radio( |
18 |
| - "Aggregate by", |
19 |
| - ["All", "By Market"], |
20 |
| - index=0, |
21 |
| - ) |
22 |
| - col1, col2 = st.columns([2, 2]) |
23 |
| - |
24 |
| - if radio_option == "All": |
25 |
| - market_index = 0 |
26 |
| - else: |
27 |
| - with col2: |
28 |
| - market_index = st.selectbox( |
29 |
| - "Market index", |
30 |
| - [x.market_index for x in mainnet_spot_market_configs], |
31 |
| - index=[x.market_index for x in mainnet_spot_market_configs].index( |
32 |
| - market_index |
33 |
| - ), |
34 |
| - format_func=lambda x: f"{x} ({mainnet_spot_market_configs[int(x)].symbol})", |
35 |
| - ) |
36 |
| - st.query_params.update({"market_index": str(market_index)}) |
37 |
| - |
38 |
| - if radio_option == "All": |
39 |
| - result = fetch_api_data( |
40 |
| - "deposits", |
41 |
| - "deposits", |
42 |
| - params={"market_index": None}, |
43 |
| - retry=True, |
44 |
| - ) |
45 |
| - else: |
46 |
| - result = fetch_api_data( |
47 |
| - "deposits", |
48 |
| - "deposits", |
49 |
| - params={"market_index": market_index}, |
50 |
| - retry=True, |
51 |
| - ) |
52 |
| - |
53 |
| - if result is None: |
54 |
| - st.error("No deposits found") |
55 |
| - return |
56 |
| - |
57 |
| - df = pd.DataFrame(result["deposits"]) |
58 |
| - total_number_of_deposited = sum([x["balance"] for x in result["deposits"]]) |
59 |
| - |
60 |
| - exclude_vaults = st.checkbox("Exclude Vaults", value=True) |
61 |
| - |
62 |
| - if exclude_vaults: |
63 |
| - df = df[~df["authority"].isin(result["vaults"])] |
64 |
| - |
65 |
| - with col1: |
66 |
| - min_balance = st.number_input( |
67 |
| - "Minimum Balance", |
68 |
| - min_value=0.0, |
69 |
| - max_value=float(df["balance"].max()), |
70 |
| - value=0.0, |
71 |
| - step=0.1, |
| 44 | + try: |
| 45 | + params = st.query_params |
| 46 | + market_index = int(params.get("market_index", 0)) |
| 47 | + |
| 48 | + radio_option = st.radio( |
| 49 | + "Aggregate by", |
| 50 | + ["All", "By Market"], |
| 51 | + index=0, |
72 | 52 | )
|
| 53 | + col1, col2 = st.columns([2, 2]) |
| 54 | + |
| 55 | + if radio_option == "All": |
| 56 | + market_index = 0 |
| 57 | + else: |
| 58 | + valid_indices = [x.market_index for x in mainnet_spot_market_configs] |
| 59 | + if market_index not in valid_indices: |
| 60 | + logger.warning(f"Invalid market index in params: {market_index}. Defaulting to first available market.") |
| 61 | + market_index = valid_indices[0] if valid_indices else 0 |
| 62 | + |
| 63 | + with col2: |
| 64 | + market_index = st.selectbox( |
| 65 | + "Market index", |
| 66 | + valid_indices, |
| 67 | + index=valid_indices.index(market_index) if market_index in valid_indices else 0, |
| 68 | + format_func=lambda x: get_market_symbol(x), |
| 69 | + ) |
| 70 | + st.query_params.update({"market_index": str(market_index)}) |
| 71 | + |
| 72 | + # Log API request parameters |
| 73 | + logger.info(f"Fetching deposits for market_index: {market_index if radio_option == 'By Market' else 'All'}") |
| 74 | + |
| 75 | + if radio_option == "All": |
| 76 | + result = fetch_api_data( |
| 77 | + "deposits", |
| 78 | + "deposits", |
| 79 | + params={"market_index": None}, |
| 80 | + retry=True, |
| 81 | + ) |
| 82 | + else: |
| 83 | + result = fetch_api_data( |
| 84 | + "deposits", |
| 85 | + "deposits", |
| 86 | + params={"market_index": market_index}, |
| 87 | + retry=True, |
| 88 | + ) |
73 | 89 |
|
74 |
| - tabs = st.tabs(["By Position", "By Authority"]) |
| 90 | + if result is None: |
| 91 | + logger.error("API returned no deposits data") |
| 92 | + st.error("No deposits found") |
| 93 | + return |
| 94 | + |
| 95 | + df = pd.DataFrame(result["deposits"]) |
| 96 | + if df.empty: |
| 97 | + logger.warning("Empty deposits dataframe created") |
| 98 | + st.warning("No deposits data available") |
| 99 | + return |
| 100 | + |
| 101 | + total_number_of_deposited = sum([x["balance"] for x in result["deposits"]]) |
| 102 | + |
| 103 | + exclude_vaults = st.checkbox("Exclude Vaults", value=True) |
| 104 | + |
| 105 | + if exclude_vaults: |
| 106 | + original_len = len(df) |
| 107 | + df = df[~df["authority"].isin(result["vaults"])] |
| 108 | + logger.info(f"Excluded {original_len - len(df)} vault entries") |
| 109 | + |
| 110 | + with col1: |
| 111 | + min_balance = st.number_input( |
| 112 | + "Minimum Balance", |
| 113 | + min_value=0.0, |
| 114 | + max_value=float(df["balance"].max()), |
| 115 | + value=0.0, |
| 116 | + step=0.1, |
| 117 | + ) |
75 | 118 |
|
76 |
| - with tabs[0]: |
77 |
| - filtered_df = df[df["balance"] >= min_balance] |
78 |
| - st.write(f"Total deposits value: **${filtered_df['value'].sum():,.2f}**") |
79 |
| - st.write(f"Number of depositor user accounts: **{len(filtered_df):,}**") |
| 119 | + tabs = st.tabs(["By Position", "By Authority"]) |
80 | 120 |
|
81 |
| - csv = filtered_df.to_csv(index=False) |
82 |
| - st.download_button( |
83 |
| - "Download All Deposits CSV", |
84 |
| - csv, |
85 |
| - "all_deposits.csv", |
86 |
| - "text/csv", |
87 |
| - key="download-all-deposits", |
88 |
| - ) |
89 |
| - filtered_df["market_index"] = filtered_df["market_index"].map( |
90 |
| - lambda x: f"{x} ({mainnet_spot_market_configs[x].symbol})" |
91 |
| - ) |
| 121 | + with tabs[0]: |
| 122 | + filtered_df = df[df["balance"] >= min_balance] |
| 123 | + st.write(f"Total deposits value: **${filtered_df['value'].sum():,.2f}**") |
| 124 | + st.write(f"Number of depositor user accounts: **{len(filtered_df):,}**") |
92 | 125 |
|
93 |
| - st.dataframe( |
94 |
| - filtered_df.sort_values("value", ascending=False), |
95 |
| - column_config={ |
96 |
| - "authority": st.column_config.TextColumn( |
97 |
| - "Authority", |
98 |
| - help="Account authority", |
99 |
| - ), |
100 |
| - "user_account": st.column_config.TextColumn( |
101 |
| - "User Account", |
102 |
| - help="User account address", |
103 |
| - ), |
104 |
| - "value": st.column_config.NumberColumn( |
105 |
| - "Value (USD)", |
106 |
| - step=0.01, |
107 |
| - ), |
108 |
| - "balance": st.column_config.NumberColumn( |
109 |
| - "Balance (USD)", |
110 |
| - step=0.01, |
111 |
| - ), |
112 |
| - }, |
113 |
| - hide_index=True, |
114 |
| - ) |
| 126 | + csv = filtered_df.to_csv(index=False) |
| 127 | + st.download_button( |
| 128 | + "Download All Deposits CSV", |
| 129 | + csv, |
| 130 | + "all_deposits.csv", |
| 131 | + "text/csv", |
| 132 | + key="download-all-deposits", |
| 133 | + ) |
| 134 | + |
| 135 | + # Safely map market indices to symbols |
| 136 | + filtered_df["market_index"] = filtered_df["market_index"].map(get_market_symbol) |
| 137 | + |
| 138 | + st.dataframe( |
| 139 | + filtered_df.sort_values("value", ascending=False), |
| 140 | + column_config={ |
| 141 | + "authority": st.column_config.TextColumn( |
| 142 | + "Authority", |
| 143 | + help="Account authority", |
| 144 | + ), |
| 145 | + "user_account": st.column_config.TextColumn( |
| 146 | + "User Account", |
| 147 | + help="User account address", |
| 148 | + ), |
| 149 | + "value": st.column_config.NumberColumn( |
| 150 | + "Value (USD)", |
| 151 | + step=0.01, |
| 152 | + ), |
| 153 | + "balance": st.column_config.NumberColumn( |
| 154 | + "Balance (USD)", |
| 155 | + step=0.01, |
| 156 | + ), |
| 157 | + }, |
| 158 | + hide_index=True, |
| 159 | + ) |
115 | 160 |
|
116 |
| - with tabs[1]: |
117 |
| - grouped_df = ( |
118 |
| - df.groupby("authority") |
119 |
| - .agg({"value": "sum", "balance": "sum", "user_account": "count"}) |
120 |
| - .reset_index() |
121 |
| - ) |
122 |
| - grouped_df = grouped_df[grouped_df["value"] >= min_balance] |
123 |
| - st.write(f"Total deposits value: **${grouped_df['value'].sum():,.2f}**") |
124 |
| - st.write(f"Total number of authorities with deposits: **{len(grouped_df):,}**") |
125 |
| - grouped_df = grouped_df.rename(columns={"user_account": "num_accounts"}) |
126 |
| - grouped_df = grouped_df.sort_values("value", ascending=False) |
127 |
| - grouped_df.drop(columns=["balance"], inplace=True) |
128 |
| - |
129 |
| - csv_grouped = grouped_df.to_csv(index=False) |
130 |
| - st.download_button( |
131 |
| - "Download Authority Summary CSV", |
132 |
| - csv_grouped, |
133 |
| - "deposits_by_authority.csv", |
134 |
| - "text/csv", |
135 |
| - key="download-grouped-deposits", |
136 |
| - ) |
| 161 | + with tabs[1]: |
| 162 | + grouped_df = ( |
| 163 | + df.groupby("authority") |
| 164 | + .agg({"value": "sum", "balance": "sum", "user_account": "count"}) |
| 165 | + .reset_index() |
| 166 | + ) |
| 167 | + grouped_df = grouped_df[grouped_df["value"] >= min_balance] |
| 168 | + st.write(f"Total deposits value: **${grouped_df['value'].sum():,.2f}**") |
| 169 | + st.write(f"Total number of authorities with deposits: **{len(grouped_df):,}**") |
| 170 | + grouped_df = grouped_df.rename(columns={"user_account": "num_accounts"}) |
| 171 | + grouped_df = grouped_df.sort_values("value", ascending=False) |
| 172 | + grouped_df.drop(columns=["balance"], inplace=True) |
| 173 | + |
| 174 | + csv_grouped = grouped_df.to_csv(index=False) |
| 175 | + st.download_button( |
| 176 | + "Download Authority Summary CSV", |
| 177 | + csv_grouped, |
| 178 | + "deposits_by_authority.csv", |
| 179 | + "text/csv", |
| 180 | + key="download-grouped-deposits", |
| 181 | + ) |
137 | 182 |
|
138 |
| - st.dataframe( |
139 |
| - grouped_df, |
140 |
| - column_config={ |
141 |
| - "authority": st.column_config.TextColumn( |
142 |
| - "Authority", |
143 |
| - help="Account authority", |
144 |
| - ), |
145 |
| - "value": st.column_config.NumberColumn( |
146 |
| - "Total Value (USD)", |
147 |
| - step=0.01, |
148 |
| - ), |
149 |
| - "num_accounts": st.column_config.NumberColumn( |
150 |
| - "Number of Accounts", |
151 |
| - step=1, |
152 |
| - ), |
153 |
| - }, |
154 |
| - hide_index=True, |
155 |
| - ) |
| 183 | + st.dataframe( |
| 184 | + grouped_df, |
| 185 | + column_config={ |
| 186 | + "authority": st.column_config.TextColumn( |
| 187 | + "Authority", |
| 188 | + help="Account authority", |
| 189 | + ), |
| 190 | + "value": st.column_config.NumberColumn( |
| 191 | + "Total Value (USD)", |
| 192 | + step=0.01, |
| 193 | + ), |
| 194 | + "num_accounts": st.column_config.NumberColumn( |
| 195 | + "Number of Accounts", |
| 196 | + step=1, |
| 197 | + ), |
| 198 | + }, |
| 199 | + hide_index=True, |
| 200 | + ) |
| 201 | + except Exception as e: |
| 202 | + logger.exception("Unexpected error in deposits_page") |
| 203 | + st.error(f"An unexpected error occurred: {str(e)}") |
0 commit comments