Skip to content

Commit

Permalink
Merge pull request #368 from CarmineOptions/feat/build-loans-with-low…
Browse files Browse the repository at this point in the history
…-health-factor-chart

loans with low health factor chard added
  • Loading branch information
djeck1432 authored Jan 25, 2025
2 parents 0034505 + 4bec382 commit d7c068f
Show file tree
Hide file tree
Showing 19 changed files with 535 additions and 100 deletions.
2 changes: 0 additions & 2 deletions apps/dashboard_app/charts/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
"""
Imports the Dashboard class from the main module in the dashboard_app.charts package.
"""

from dashboard_app.charts.main import Dashboard
6 changes: 6 additions & 0 deletions apps/dashboard_app/charts/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from dataclasses import dataclass


@dataclass
class ChartsHeaders:
low_health_factor_loans: str = "Loans with low health factor"
92 changes: 80 additions & 12 deletions apps/dashboard_app/charts/main.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
"""
This module defines the Dashboard class for rendering a DeRisk dashboard using Streamlit.
"""

import pandas as pd
import streamlit as st

from dashboard_app.charts.main_chart_figure import get_main_chart_figure
from dashboard_app.charts.utils import process_liquidity
from dashboard_app.helpers.settings import (
COLLATERAL_TOKENS,
DEBT_TOKENS,
STABLECOIN_BUNDLE_NAME,
from data_handler.handlers.loan_states.abstractions import State
from shared.helpers import (
extract_token_addresses,
fetch_token_symbols_from_set_of_loan_addresses,
update_loan_data_with_symbols,
)

from helpers.settings import COLLATERAL_TOKENS, DEBT_TOKENS, STABLECOIN_BUNDLE_NAME

from .constants import ChartsHeaders
from .main_chart_figure import get_main_chart_figure
from .utils import (
get_protocol_data_mappings,
process_liquidity,
transform_loans_data,
transform_main_chart_data,
)
Expand All @@ -23,16 +26,16 @@ class Dashboard:
"""
A class representing a dashboard for managing protocol names.
"""

PROTOCOL_NAMES = [
"zkLend",
# "Nostra Alpha",
# "Nostra Mainnet",
]

def __init__(self, zklend_state):
def __init__(self, state: State):
"""
Initialize the dashboard.
:param zklend_state: ZkLendState
"""
# Set the page configuration
st.set_page_config(
Expand All @@ -49,6 +52,7 @@ def __init__(self, zklend_state):
self.current_pair = None
self.stable_coin_pair = None
self.collateral_token_price = 0
self.state = state

def load_sidebar(self):
"""
Expand Down Expand Up @@ -93,9 +97,10 @@ def load_main_chart(self):
current_pair=self.current_pair,
stable_coin_pair=self.stable_coin_pair,
protocols=self.PROTOCOL_NAMES,
state=self.state,
)
loans_data = transform_loans_data(
protocol_loans_data_mapping, self.PROTOCOL_NAMES
loans_data = ( # TODO: remove unused `loans_data` variable or use it
transform_loans_data(protocol_loans_data_mapping, self.PROTOCOL_NAMES)
)
main_chart_data = transform_main_chart_data(
protocol_main_chart_data_mapping, self.current_pair, self.PROTOCOL_NAMES
Expand Down Expand Up @@ -128,10 +133,73 @@ def load_main_chart(self):
)
st.plotly_chart(figure_or_data=figure, use_container_width=True)

def load_loans_with_low_health_factor_chart(self):
"""
Gererate a chart that shows loans with low health factor.
"""
(
protocol_main_chart_data_mapping,
protocol_loans_data_mapping,
) = get_protocol_data_mappings(
current_pair=self.current_pair,
stable_coin_pair=self.stable_coin_pair,
protocols=self.PROTOCOL_NAMES,
state=self.state,
)
loans_data = transform_loans_data(
protocol_loans_data_mapping, self.PROTOCOL_NAMES
)

token_addresses = extract_token_addresses(loans_data)
if token_addresses:
token_symbols = fetch_token_symbols_from_set_of_loan_addresses(
token_addresses
)
loans_data = update_loan_data_with_symbols(loans_data, token_symbols)

st.header(ChartsHeaders.low_health_factor_loans)

col1, _ = st.columns([1, 3])
with col1:
# TODO: remove this line when debugging is done
debt_usd_lower_bound, debt_usd_upper_bound = st.slider(
label="Select range of USD borrowings",
min_value=0,
max_value=int(loans_data["Debt (USD)"].max()),
value=(0, int(loans_data["Debt (USD)"].max()) or 1), # FIXME remove 1
)

try:
st.dataframe(
loans_data[
(loans_data["Health factor"] > 0) # TODO: debug the negative HFs
& loans_data["Debt (USD)"].between(
debt_usd_lower_bound, debt_usd_upper_bound
)
]
.sort_values("Health factor")
.iloc[:20],
use_container_width=True,
)

except TypeError:
st.dataframe(
pd.DataFrame(
{
"price": [0],
"debt_token_supply": [0],
"collateral_token_price": [0],
"Ekubo_debt_token_supply": [0],
}
),
use_container_width=True,
)

def run(self):
"""
This function executes/runs the load_sidebar() and load_main_chart() function.
"""
# Load sidebar with protocol settings
self.load_sidebar()
self.load_main_chart()
self.load_loans_with_low_health_factor_chart()
11 changes: 5 additions & 6 deletions apps/dashboard_app/charts/main_chart_figure.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,12 @@
import pandas as pd
import plotly.express
import plotly.graph_objs

from shared.amms import SwapAmm
from shared.state import State
from shared.types import Prices

from dashboard_app.helpers.settings import TOKEN_SETTINGS
from dashboard_app.helpers.tools import (
from helpers.settings import TOKEN_SETTINGS
from helpers.tools import (
get_collateral_token_range,
get_custom_data,
get_prices,
Expand All @@ -32,7 +31,7 @@ def get_main_chart_data(
) -> pd.DataFrame:
"""
Generates financial chart data based on token prices, liquidity, and debt information.
Takes five parameters and
Takes five parameters and
Returns: A DataFrame containing calculated token prices and liquidable debt data.
"""
collateral_token_underlying_address = get_underlying_address(
Expand Down Expand Up @@ -93,7 +92,7 @@ def compute_supply_at_price(collateral_token_price: float):
supplies_and_totals = data["collateral_token_price"].apply(compute_supply_at_price)
for amm in AMMS:
data[f"{amm}_debt_token_supply"] = supplies_and_totals.apply(
lambda x, amm=amm: x[0][amm]
lambda x, amm: x[0][amm]
)
data["debt_token_supply"] = supplies_and_totals.apply(lambda x: x[1])

Expand Down Expand Up @@ -166,7 +165,7 @@ def get_main_chart_figure(
xaxis_title=f"{collateral_token} Price (USD)",
yaxis_title="Volume (USD)",
legend_title="Legend",
yaxis2={"overlaying": 'y', "side": 'left', "matches": 'y'},
yaxis2={"overlaying": "y", "side": "left", "matches": "y"},
)

# Add the vertical line and shaded region for the current price
Expand Down
73 changes: 51 additions & 22 deletions apps/dashboard_app/charts/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,24 @@

import pandas as pd
import streamlit as st

from dashboard_app.helpers.ekubo import EkuboLiquidity
from dashboard_app.helpers.settings import (
from data_handler.handlers.liquidable_debt.utils import Prices
from data_handler.handlers.loan_states.abstractions import State
from shared.amms import SwapAmm
from shared.constants import PAIRS
from shared.tests.test_swap_amm import swap_amm

from helpers.ekubo import EkuboLiquidity
from helpers.loans_table import get_loans_table_data
from helpers.settings import (
COLLATERAL_TOKENS,
DEBT_TOKENS,
STABLECOIN_BUNDLE_NAME,
TOKEN_SETTINGS,
UNDERLYING_SYMBOLS_TO_UNDERLYING_ADDRESSES,
)
from dashboard_app.helpers.tools import get_prices
from helpers.tools import get_main_chart_data, get_prices

logger = logging.getLogger(__name__)


def process_liquidity(
Expand Down Expand Up @@ -84,17 +92,17 @@ def create_stablecoin_bundle(data: dict[str, pd.DataFrame]) -> dict[str, pd.Data
Creates a stablecoin bundle by merging relevant DataFrames for collateral tokens and debt
tokens.
For each collateral token specified in `src.settings.COLLATERAL_TOKENS`, this function finds
For each collateral token specified in `src.settings.COLLATERAL_TOKENS`, this function finds
the relevant stablecoin pairs from the provided `data` dictionary and merges the corresponding
Dataframes based on the 'collateral_token_price' column. It combines the debt and liquidity
data for multiple stablecoin pairs and adds the result back to the `data` dictionary under a
Dataframes based on the 'collateral_token_price' column. It combines the debt and liquidity
data for multiple stablecoin pairs and adds the result back to the `data` dictionary under a
new key.
Parameters:
data (dict[str, pandas.DataFrame]): A dictionary where the keys are token pairs and the values
are corresponding DataFrames containing price and supply data.
Returns: dict[str, pandas.DataFrame]:
Returns: dict[str, pandas.DataFrame]:
The updated dictionary with the newly created stablecoin bundle added.
"""

Expand Down Expand Up @@ -155,37 +163,58 @@ def create_stablecoin_bundle(data: dict[str, pd.DataFrame]) -> dict[str, pd.Data
return data


@st.cache_data(ttl=300)
def get_data(
protocol_name: str,
) -> tuple[dict[str, pd.DataFrame], dict[str, pd.DataFrame]]:
protocol_name: str, state: State
) -> tuple[dict[str, pd.DataFrame], pd.DataFrame]:
"""
Load loan data and main chart data for the specified protocol.
:param protocol_name: Protocol name.
:param state: State to load data for.
:return: DataFrames containing loan data and main chart data.
"""
return load_data(protocol=protocol_name)
# directory = f"{protocol_name.lower().replace(' ', '_')}_data"
main_chart_data = {}
current_prices = Prices()

for pair in PAIRS:
collateral_token_underlying_symbol, debt_token_underlying_symbol = pair.split(
"-"
)
try:
main_chart_data[pair] = get_main_chart_data(
state=state,
prices=current_prices,
swap_amm=SwapAmm(),
collateral_token_underlying_symbol=collateral_token_underlying_symbol,
debt_token_underlying_symbol=debt_token_underlying_symbol,
)
except Exception:
main_chart_data[pair] = pd.DataFrame()

loans_data = get_loans_table_data(state=state, prices=current_prices)
return main_chart_data, loans_data


def get_protocol_data_mappings(
current_pair: str, stable_coin_pair: str, protocols: list[str]
) -> tuple[dict[str, dict], dict[str, dict]]:
current_pair: str, stable_coin_pair: str, protocols: list[str], state: State
) -> tuple[dict[str, pd.DataFrame], dict[str, dict]]:
"""
Get protocol data mappings for main chart data and loans data.
:param current_pair: The current pair for which data is to be fetched.
:param stable_coin_pair: The stable coin pair to check against.
:param protocols: List of protocols for which data is to be fetched.
:param state: State to load data for.
:return: tuple of dictionaries containing:
- protocol_main_chart_data: Mapping of protocol names to their main chart data.
- protocol_loans_data: Mapping of protocol names to their loans data.
"""

protocol_main_chart_data: dict[str, dict] = {}
protocol_main_chart_data: dict[str, pd.DataFrame] = {}
protocol_loans_data: dict[str, dict] = {}

for protocol_name in protocols:
main_chart_data, loans_data = get_data(protocol_name)
main_chart_data, loans_data = get_data(state=state, protocol_name=protocol_name)
protocol_loans_data[protocol_name] = loans_data

if current_pair == stable_coin_pair:
Expand Down Expand Up @@ -251,9 +280,9 @@ def transform_main_chart_data(
main_chart_data[f"liquidable_debt_{protocol}"] = protocol_main_chart_data[
"liquidable_debt"
]
main_chart_data[f"liquidable_debt_at_interval_{protocol}"] = (
protocol_main_chart_data["liquidable_debt_at_interval"]
)
main_chart_data[
f"liquidable_debt_at_interval_{protocol}"
] = protocol_main_chart_data["liquidable_debt_at_interval"]
else:
main_chart_data["liquidable_debt"] += protocol_main_chart_data[
"liquidable_debt"
Expand All @@ -264,8 +293,8 @@ def transform_main_chart_data(
main_chart_data[f"liquidable_debt_{protocol}"] = protocol_main_chart_data[
"liquidable_debt"
]
main_chart_data[f"liquidable_debt_at_interval_{protocol}"] = (
protocol_main_chart_data["liquidable_debt_at_interval"]
)
main_chart_data[
f"liquidable_debt_at_interval_{protocol}"
] = protocol_main_chart_data["liquidable_debt_at_interval"]

return main_chart_data
18 changes: 13 additions & 5 deletions apps/dashboard_app/dashboard.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,22 @@
"""

import logging
from dashboard_app.charts import Dashboard
from dashboard_app.helpers.load_data import DashboardDataHandler

from charts.main import Dashboard
from helpers.load_data import DashboardDataHandler

if __name__ == "__main__":
logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)
dashboard_data_handler = DashboardDataHandler()
dashboard_data_handler.load_data()
(
zklend_state,
general_stats,
supply_stats,
collateral_stats,
debt_stats,
utilization_stats,
) = dashboard_data_handler.load_data()

# dashboard = Dashboard()
# dashboard.run()
dashboard = Dashboard(zklend_state)
dashboard.run()
1 change: 1 addition & 0 deletions apps/dashboard_app/data_conector.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class DataConnector:
"""
Handles database connection and fetches data.
"""

REQUIRED_VARS = ("DB_USER", "DB_PASSWORD", "DB_HOST", "DB_PORT", "DB_NAME")
ZKLEND_SQL_QUERY = """
SELECT
Expand Down
15 changes: 15 additions & 0 deletions apps/dashboard_app/dev/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
FROM python:3.10

WORKDIR /app

RUN pip install poetry
COPY dashboard_app/pyproject.toml dashboard_app/poetry.lock* ./
RUN poetry config virtualenvs.create false \
&& poetry install --no-interaction --no-ansi --no-root

RUN touch ./__init__.py
COPY dashboard_app/ .
COPY data_handler/ ./data_handler/
COPY shared/ ./shared/

CMD ["streamlit", "run", "dashboard.py"]
Loading

0 comments on commit d7c068f

Please sign in to comment.