Skip to content

Commit

Permalink
Merge pull request #159 from CarmineOptions/feat/save-nostra-liquidab…
Browse files Browse the repository at this point in the history
…le-debt-to-the-db

Feat/save nostra liquidable debt to the db
  • Loading branch information
lukaspetrasek committed Jul 13, 2024
2 parents 9ffe7ae + 19b5c21 commit 893c6d9
Show file tree
Hide file tree
Showing 7 changed files with 279 additions and 132 deletions.
5 changes: 3 additions & 2 deletions data_handler/db/crud.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import uuid
from typing import List, Optional, Type, TypeVar

from sqlalchemy import create_engine, func, select, and_, desc
from sqlalchemy import create_engine, func, select, and_, desc, Subquery
from sqlalchemy.exc import SQLAlchemyError
from sqlalchemy.orm import scoped_session, sessionmaker, Session, aliased, Query

Expand Down Expand Up @@ -93,9 +93,10 @@ def delete_object(self, model: Type[Base] = None, obj_id: uuid = None) -> None:
finally:
db.close()

def _get_subquery(self):
def _get_subquery(self) -> Subquery:
"""
Returns subquery for loan state last blocks query
:return: Subquery
"""
session = self.Session()
return (
Expand Down
284 changes: 196 additions & 88 deletions data_handler/handlers/liquidable_debt/debt_handlers.py
Original file line number Diff line number Diff line change
@@ -1,66 +1,30 @@
import pandas as pd
import uuid
import asyncio
import time
import os
from decimal import Decimal
from copy import deepcopy
from typing import Iterable

import requests
from typing import Iterable, Type

from db.crud import DBConnector
from db.models import LiquidableDebt, LoanState

from handlers.state import State, InterestRateModels, LoanEntity
from handlers.liquidable_debt.bases import Collector
from handlers.liquidable_debt.collectors import GoogleCloudDataCollector
from handlers.liquidable_debt.exceptions import ProtocolExistenceError
from handlers.liquidable_debt.values import (GS_BUCKET_URL, GS_BUCKET_NAME, LendingProtocolNames,
LOCAL_STORAGE_PATH, COLLATERAL_FIELD_NAME, PROTOCOL_FIELD_NAME,
DEBT_FIELD_NAME, USER_FIELD_NAME, RISK_ADJUSTED_COLLATERAL_USD_FIELD_NAME,
HEALTH_FACTOR_FIELD_NAME, DEBT_USD_FIELD_NAME, FIELDS_TO_VALIDATE,
LIQUIDABLE_DEBT_FIELD_NAME, PRICE_FIELD_NAME,
MYSWAP_VALUE, JEDISWAP_VALUE, POOL_SPLIT_VALUE, ROW_ID_FIELD_NAME)
from handlers.loan_states.zklend.events import ZkLendState, ZkLendLoanEntity, TokenSettings
from db.models import LoanState

from handler_tools.constants import ProtocolIDs
from handlers.state import State, LoanEntity
from handlers.liquidable_debt.values import (LendingProtocolNames, COLLATERAL_FIELD_NAME,
DEBT_FIELD_NAME, LIQUIDABLE_DEBT_FIELD_NAME, PRICE_FIELD_NAME)
from handlers.liquidable_debt.utils import Prices
from handlers.settings import TOKEN_PAIRS
from handlers.helpers import TokenValues, get_range, get_collateral_token_range
from handler_tools.constants import AvailableProtocolID


class DBLiquidableDebtDataHandler:
class BaseDBLiquidableDebtDataHandler:
"""
A handler that collects data from the DB,
A base handler that collects data from the DB,
computes the liquidable debt and stores it in the database.
:cvar AVAILABLE_PROTOCOLS: A list of all available protocols.
:cvar CONNECTOR: Connector used to connect to the database.
:cvar INTEREST_MODEL_VALUES_URL: URL for interests model values.
"""
AVAILABLE_PROTOCOLS = [item.value for item in LendingProtocolNames]
CONNECTOR = DBConnector()

def __init__(
self,
loan_state_class: State,
loan_entity_class: LoanEntity,
):
self.state_class = loan_state_class
self.loan_entity_class = loan_entity_class

def fetch_data(self, protocol_name: str) -> tuple:
"""
Prepares the data for the given protocol.
:param protocol_name: Protocol name.
:return: tuple
"""
loan_data = self.get_loans_from_db(protocol_name=protocol_name)
interest_rate_models = self.get_interest_rate_models_from_db(
protocol_id=protocol_name
)

return loan_data, interest_rate_models
def __init__(self, *args, **kwargs):
self.db_connector = DBConnector()

@staticmethod
def get_prices_range(collateral_token_name: str, current_price: Decimal) -> Iterable[Decimal]:
Expand All @@ -77,18 +41,15 @@ def get_prices_range(collateral_token_name: str, current_price: Decimal) -> Iter

return get_range(Decimal(0), current_price * Decimal("1.3"), Decimal(current_price / 100))

def calculate_liquidable_debt(self, protocol_name: str = None) -> dict:
def initialize_loan_entities(self, state: State, data: dict = None) -> State:
"""
Calculates liquidable debt based on data provided and updates an existing data.
Data to calculate liquidable debt for:
:param protocol_name: str
:return: A dictionary of the ready liquidable debt data.
Initializes the loan entities in a state instance.
:param state: State
:param data: dict
:return: State
"""
data, interest_rate_models = self.fetch_data(protocol_name=protocol_name)
state = self.state_class()

for instance in data:
loan_entity = ZkLendLoanEntity()
loan_entity = self.loan_entity_class()

loan_entity.debt = TokenValues(values=instance.debt)
loan_entity.collateral = TokenValues(values=instance.collateral)
Expand All @@ -99,6 +60,53 @@ def calculate_liquidable_debt(self, protocol_name: str = None) -> dict:
}
)

return state

def fetch_data(self, protocol_name: ProtocolIDs | str) -> tuple:
"""
Prepares the data for the given protocol.
:param protocol_name: Protocol name.
:return: tuple
"""
loan_data = self.db_connector.get_loans(
model=LoanState,
protocol=protocol_name
)
interest_rate_models = self.db_connector.get_last_interest_rate_record_by_protocol_id(
protocol_id=protocol_name
)

return loan_data, interest_rate_models


class ZkLendDBLiquidableDebtDataHandler(BaseDBLiquidableDebtDataHandler):
"""
A zkLend handler that collects data from the DB,
computes the liquidable debt and stores it in the database.
:cvar AVAILABLE_PROTOCOLS: A list of all available protocols.
"""

def __init__(
self,
loan_state_class: Type[State],
loan_entity_class: Type[LoanEntity],
):
super().__init__()
self.state_class = loan_state_class
self.loan_entity_class = loan_entity_class

def calculate_liquidable_debt(self, protocol_name: str = None) -> list:
"""
Calculates liquidable debt based on data provided and updates an existing data.
Data to calculate liquidable debt for:
:param protocol_name: str
:return: A dictionary of the ready liquidable debt data.
"""
data, interest_rate_models = self.fetch_data(protocol_name=protocol_name)
state = self.state_class()
state = self.initialize_loan_entities(state=state, data=data)

# Set up collateral and debt interest rate models
state.collateral_interest_rate_models = TokenValues(
values=interest_rate_models.collateral
Expand All @@ -115,7 +123,7 @@ def calculate_liquidable_debt(self, protocol_name: str = None) -> dict:
current_price=current_prices.prices.values["STRK"]
)

result_data = dict()
result_data = list()
# Go through first hypothetical prices and then through the debts
for hypothetical_price in hypothetical_collateral_token_prices:
liquidable_debt = state.compute_liquidable_debt_at_price(
Expand All @@ -126,44 +134,144 @@ def calculate_liquidable_debt(self, protocol_name: str = None) -> dict:
)

if liquidable_debt > Decimal("0"):
result_data.update({
f"{uuid.uuid4()}": {
LIQUIDABLE_DEBT_FIELD_NAME: liquidable_debt,
PRICE_FIELD_NAME: hypothetical_price,
COLLATERAL_FIELD_NAME: "STRK",
PROTOCOL_FIELD_NAME: "zkLend",
DEBT_FIELD_NAME: "USDC",
}
})
result_data.append({
LIQUIDABLE_DEBT_FIELD_NAME: liquidable_debt,
PRICE_FIELD_NAME: hypothetical_price,
COLLATERAL_FIELD_NAME: "STRK",
DEBT_FIELD_NAME: "USDC",
}
)

return result_data

@classmethod
def get_interest_rate_models_from_db(cls, protocol_id: str) -> dict:
"""
Returns interest rate models data from DB.
:param protocol_id: str
:return: dict
"""
return cls.CONNECTOR.get_last_interest_rate_record_by_protocol_id(protocol_id=protocol_id)

@classmethod
def get_loans_from_db(cls, protocol_name: str) -> dict:
class NostraAlphaDBLiquidableDebtDataHandler(BaseDBLiquidableDebtDataHandler):
"""
A Nostra_alpha handler that collects data from the DB,
computes the liquidable debt and stores it in the database.
:cvar AVAILABLE_PROTOCOLS: A list of all available protocols.
"""

def __init__(
self,
loan_state_class: Type[State],
loan_entity_class: Type[LoanEntity],
):
super().__init__()
self.state_class = loan_state_class
self.loan_entity_class = loan_entity_class

def calculate_liquidable_debt(self, protocol_name: str = None) -> list:
"""
Gets the data from the database based on the protocol name.
:param protocol_name: The protocol name.
:return: The data from the database.
Calculates liquidable debt based on data provided and updates an existing data.
Data to calculate liquidable debt for:
:param protocol_name: str
:return: A dictionary of the ready liquidable debt data.
"""
return cls.CONNECTOR.get_loans(
model=LoanState,
protocol=protocol_name
data, interest_rate_models = self.fetch_data(protocol_name=protocol_name)
state = self.state_class()
state = self.initialize_loan_entities(state=state, data=data)

# Set up collateral and debt interest rate models
state.collateral_interest_rate_models = TokenValues(
values=interest_rate_models.collateral
)
state.debt_interest_rate_models = TokenValues(
values=interest_rate_models.debt
)

@classmethod
def write_to_db(cls, data: LiquidableDebt = None) -> None:
current_prices = Prices()
asyncio.run(current_prices.get_lp_token_prices())

hypothetical_collateral_token_prices = self.get_prices_range(
collateral_token_name="STRK",
current_price=current_prices.prices.values["STRK"]
)

result_data = list()
# Go through first hypothetical prices and then through the debts
for hypothetical_price in hypothetical_collateral_token_prices:
liquidable_debt = state.compute_liquidable_debt_at_price(
prices=TokenValues(values=current_prices.prices.values),
collateral_token="STRK",
collateral_token_price=hypothetical_price,
debt_token="USDC",
)

if liquidable_debt > Decimal("0"):
result_data.append({
LIQUIDABLE_DEBT_FIELD_NAME: liquidable_debt,
PRICE_FIELD_NAME: hypothetical_price,
COLLATERAL_FIELD_NAME: "STRK",
DEBT_FIELD_NAME: "USDC",
}
)

return result_data


class NostraMainnetDBLiquidableDebtDataHandler(BaseDBLiquidableDebtDataHandler):
"""
A Nostra_mainnet handler that collects data from the DB,
computes the liquidable debt and stores it in the database.
:cvar AVAILABLE_PROTOCOLS: A list of all available protocols.
"""

def __init__(
self,
loan_state_class: Type[State],
loan_entity_class: Type[LoanEntity],
):
super().__init__()
self.state_class = loan_state_class
self.loan_entity_class = loan_entity_class

def calculate_liquidable_debt(self, protocol_name: str = None) -> list:
"""
Writes the data into the database.
:param data: A dictionary of the parsed data.
:return: None
Calculates liquidable debt based on data provided and updates an existing data.
Data to calculate liquidable debt for:
:param protocol_name: str
:return: A dictionary of the ready liquidable debt data.
"""
cls.CONNECTOR.write_to_db(data)
data, interest_rate_models = self.fetch_data(protocol_name=protocol_name)
state = self.state_class()
state = self.initialize_loan_entities(state=state, data=data)

# Set up collateral and debt interest rate models
state.collateral_interest_rate_models = TokenValues(
values=interest_rate_models.collateral
)
state.debt_interest_rate_models = TokenValues(
values=interest_rate_models.debt
)

current_prices = Prices()
asyncio.run(current_prices.get_lp_token_prices())

hypothetical_collateral_token_prices = self.get_prices_range(
collateral_token_name="STRK",
current_price=current_prices.prices.values["STRK"]
)

result_data = list()
# Go through first hypothetical prices and then through the debts
for hypothetical_price in hypothetical_collateral_token_prices:
liquidable_debt = state.compute_liquidable_debt_at_price(
prices=TokenValues(values=current_prices.prices.values),
collateral_token="STRK",
collateral_token_price=hypothetical_price,
debt_token="USDC",
)

if liquidable_debt > Decimal("0"):
result_data.append({
LIQUIDABLE_DEBT_FIELD_NAME: liquidable_debt,
PRICE_FIELD_NAME: hypothetical_price,
COLLATERAL_FIELD_NAME: "STRK",
DEBT_FIELD_NAME: "USDC",
}
)

return result_data
Loading

0 comments on commit 893c6d9

Please sign in to comment.