diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 33c3193e..ccb1aeac 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -1,6 +1,6 @@ name: CI Workflow -on: [push, pull_request] +on: [push] jobs: diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 598f8a6b..4beaddbe 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -1,6 +1,6 @@ name: Pylint Check -on: [push, pull_request] +on: [push] jobs: lint: diff --git a/apps/web_app/order_books/__init__.py b/apps/web_app/order_books/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/web_app/order_books/abstractions.py b/apps/web_app/order_books/abstractions.py deleted file mode 100644 index 9403ab4f..00000000 --- a/apps/web_app/order_books/abstractions.py +++ /dev/null @@ -1,106 +0,0 @@ -from abc import ABC, abstractmethod -from datetime import datetime, timezone -from decimal import Decimal - -from order_books.constants import TOKEN_MAPPING - -from db.schemas import OrderBookModel - - -class OrderBookBase(ABC): - DEX: str = None - MIN_PRICE_RANGE = Decimal("0.1") - MAX_PRICE_RANGE = Decimal("1.90") - - def __init__(self, token_a: str, token_b: str): - self.token_a = token_a - self.token_b = token_b - self.asks = [] # List of tuples (price, quantity) - self.bids = [] # List of tuples (price, quantity) - self.timestamp = None - self.block = None - self.current_price = Decimal("0") - self.token_a_decimal = self.get_token_decimals(token_a) - self.token_b_decimal = self.get_token_decimals(token_b) - self.total_liquidity = Decimal("0") - - def get_token_decimals(self, token: str) -> Decimal: - """ - Get the token decimals - :return: tuple - The token decimals - """ - token_config = TOKEN_MAPPING.get(token) - if token_config: - return token_config.decimals - return Decimal("0") - - @abstractmethod - def fetch_price_and_liquidity(self) -> None: - """ - Fetches the price and liquidity data from the connector - """ - pass - - @abstractmethod - def _calculate_order_book(self, *args, **kwargs) -> tuple: - """ - Calculates order book data based on the liquidity data - """ - pass - - def calculate_price_range(self) -> tuple: - """ - Calculate the minimum and maximum price based on the current price. - :return: tuple - The minimum and maximum price range. - """ - min_price = self.current_price * self.MIN_PRICE_RANGE - max_price = self.current_price * self.MAX_PRICE_RANGE - return min_price, max_price - - @abstractmethod - def calculate_liquidity_amount(self, *args, **kwargs) -> Decimal: - """ - Calculates the liquidity amount based on the liquidity delta difference - """ - pass - - @staticmethod - def get_sqrt_ratio(tick: Decimal) -> Decimal: - """ - Get the square root ratio based on the tick. - :param tick: tick value - :return: square root ratio - """ - return (Decimal("1.000001").sqrt() ** tick) * (Decimal(2) ** 128) - - @abstractmethod - def tick_to_price(self, tick: Decimal) -> Decimal: - """ - Converts the tick value to price - """ - pass - - def get_order_book(self) -> dict: - """ - Returns the order book data - :return: dict - The order book data - """ - dt_now = datetime.now(timezone.utc) - - return { - "token_a": self.token_a, - "token_b": self.token_b, - "timestamp": int(dt_now.replace(tzinfo=timezone.utc).timestamp()), - "block": self.block, - "dex": self.DEX, - "asks": sorted(self.asks, key=lambda x: x[0]), - "bids": sorted(self.bids, key=lambda x: x[0]), - } - - def serialize(self) -> OrderBookModel: - """ - Serialize the order book data - :return: dict - The serialized order book data - """ - order_book_data = self.get_order_book() - return OrderBookModel(**order_book_data) diff --git a/apps/web_app/order_books/constants.py b/apps/web_app/order_books/constants.py deleted file mode 100644 index e058cf94..00000000 --- a/apps/web_app/order_books/constants.py +++ /dev/null @@ -1,41 +0,0 @@ -from dataclasses import dataclass - - -@dataclass -class TokenConfig: - name: str - decimals: int - - -TOKEN_MAPPING = { - "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7": TokenConfig( - name="ETH", decimals=18 - ), - "0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8": TokenConfig( - name="USDC", decimals=6 - ), - "0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8": TokenConfig( - name="USDT", decimals=6 - ), - "0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3": TokenConfig( - name="DAI", decimals=18 - ), - "0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac": TokenConfig( - name="wBTC", decimals=8 - ), - "0x04718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d": TokenConfig( - name="STRK", decimals=18 - ), - "0x0719b5092403233201aa822ce928bd4b551d0cdb071a724edd7dc5e5f57b7f34": TokenConfig( - name="UNO", decimals=18 - ), - "0x00585c32b625999e6e5e78645ff8df7a9001cf5cf3eb6b80ccdd16cb64bd3a34": TokenConfig( - name="ZEND", decimals=18 - ), - "0x042b8f0484674ca266ac5d08e4ac6a3fe65bd3129795def2dca5c34ecc5f96d2": TokenConfig( - name="wstETH", decimals=18 - ), - "0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49": TokenConfig( - name="LORDS", decimals=18 - ), -} diff --git a/apps/web_app/order_books/ekubo/README.md b/apps/web_app/order_books/ekubo/README.md deleted file mode 100644 index ee23b617..00000000 --- a/apps/web_app/order_books/ekubo/README.md +++ /dev/null @@ -1,34 +0,0 @@ -# Ekubo - -This folder contains all interaction with [Ekubo API](https://docs.ekubo.org/integration-guides/reference/ekubo-api/api-endpoints). - -# Endpoints Documentation: -- [Get Pool Liquidity](docs/pool_liquidity.md) - Detailed information about liquidity in different pools. -- [Get Pools](docs/pools.md) - Detailed information about various pools. -- [Get Token Prices](docs/token_prices.md) - Information on how to retrieve current token prices. -- [Get List of Tokens](docs/token_lists.md) - List of tokens with detailed information. - - -# How to run -In this folder: `ekubo` run next command -```bash -python main.py -``` - -# How to check quality of data -In this folder: `ekubo` run next command to see the histogram of the data -```bash -python manage.py histogram -``` - -## Histogram - -To run the script to see histogram, use the following command: -!!! Pay attention, you should run it from `web_app` folder -```sh -python histogram.py --type asks -``` -or -```sh -python histogram.py --type bids -``` \ No newline at end of file diff --git a/apps/web_app/order_books/ekubo/__init__.py b/apps/web_app/order_books/ekubo/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/web_app/order_books/ekubo/api_connector.py b/apps/web_app/order_books/ekubo/api_connector.py deleted file mode 100644 index 01cf3eb4..00000000 --- a/apps/web_app/order_books/ekubo/api_connector.py +++ /dev/null @@ -1,265 +0,0 @@ -import time - -from web_app.utils.abstractions import AbstractionAPIConnector - - -class EkuboAPIConnector(AbstractionAPIConnector): - API_URL = "https://mainnet-api.ekubo.org" - - @classmethod - def get_token_prices(cls, quote_token: str) -> dict: - """ - Get the prices of all other tokens in terms of the specified quote token. - - :param quote_token: The address of the quote token on StarkNet. - :type quote_token: str - :return: A dictionary containing the timestamp and a list of dictionaries for each token with price details. - Each token's dictionary includes its address, price, and trading volume. - :rtype: dict - - The response dictionary structure is as follows: - { - 'timestamp': int, # Unix timestamp in milliseconds when the data was recorded - 'prices': [ - { - 'token': str, # The address of the token on the blockchain - 'price': str, # The current price of the token expressed in terms of the quote token - 'k_volume': str # The trading volume for the token, expressed in the smallest unit counts - } - ] - } - - Example: - { - 'timestamp': 1715350510592, - 'prices': [ - { - 'token': '0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7', - 'price': '0.0481921', - 'k_volume': '1176822712207290630680641242' - } - ] - } - """ - endpoint = f"/price/{quote_token}" - return cls.send_get_request(endpoint) - - @classmethod - def get_pool_liquidity(cls, key_hash: str) -> list: - """ - Get the liquidity delta for each tick for the given pool key hash. The response includes an array - of objects, each containing details about the tick and the net liquidity delta difference. - - :param key_hash: The pool key hash in hexadecimal or decimal format. - :type key_hash: str - :return: An array of objects detailing the current liquidity chart for the pool. Each object - in the array includes the following: - - 'tick': The tick index as an integer. - - 'net_liquidity_delta_diff': The difference in net liquidity for the tick, represented as a string. - :rtype: list - - Example of returned data: - [ - { - "tick": -88719042, - "net_liquidity_delta_diff": "534939583228319557" - } - ] - - """ - endpoint = f"/pools/{key_hash}/liquidity" - return cls.send_get_request(endpoint) - - def get_list_tokens(self) -> list: - """ - Retrieves a list of tokens from the blockchain layer 2. Each token object - in the list provides comprehensive details - including its name, symbol, decimal precision, layer 2 address, and other attributes. - - :return: A list of dictionaries, each representing a token with the following attributes: - - 'name' (str): The name of the token, e.g., "Wrapped BTC". - - 'symbol' (str): The abbreviated symbol of the token, e.g., "WBTC". - - 'decimals' (int): The number of decimal places the token is divided into, e.g., 8. - - 'l2_token_address' (str): The address of the token on layer 2, in hexadecimal format. - - 'sort_order' (int): An integer specifying the order in which the token should be displayed - relative to others; lower numbers appear first. - - 'total_supply' (str): The total supply of the token, if known. This can be 'None' - if the total supply is not set or unlimited. - - 'logo_url' (str): A URL to the token's logo image. - - Example of returned data: - [ - { - "name": "Wrapped BTC", - "symbol": "WBTC", - "decimals": 8, - "l2_token_address": "0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac", - "sort_order": 0, - "total_supply": "None", - "logo_url": "https://imagedelivery.net/0xPAQaDtnQhBs8IzYRIlNg/7dcb2db2-a7a7-44af-660b-8262e057a100/logo" - } - ] - - """ - endpoint = "/tokens" - return self.send_get_request(endpoint) - - def get_pools(self) -> list: - """ - Retrieves a list of detailed information about various pools. Each entry in the list is a dictionary - that provides comprehensive details about a pool, including the tokens involved, fees, - and other relevant metrics. - - :return: A list of dictionaries, each containing detailed information about a pool. The structure of - each dictionary is as follows: - - 'key_hash': The unique identifier of the pool in hexadecimal format. (str) - - 'token0': The address of the first token in the pool on the blockchain. (str) - - 'token1': The address of the second token in the pool on the blockchain. (str) - - 'fee': The fee associated with the pool transactions, in hexadecimal format. (str) - - 'tick_spacing': The minimum granularity of price movements in the pool. (int) - - 'extension': Additional information or features related to the pool, in hexadecimal format. (str) - - 'sqrt_ratio': The square root of the current price ratio between token0 and token1, - in hexadecimal format. (str) - - 'tick': The current position of the pool in its price range. (int) - - 'liquidity': The total liquidity available in the pool. (str) - - 'lastUpdate': Dictionary containing details about the latest update event: - - 'event_id': Identifier of the last update event. (str) - :rtype: list - - Example of returned data: - [ - { - "key_hash": "0x34756a876aa3288b724dc967d43ee72d9b9cf390023775f0934305233767156", - "token0": "0xda114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3", - "token1": "0x49210ffc442172463f3177147c1aeaa36c51d152c1b0630f2364c300d4f48ee", - "fee": "0x20c49ba5e353f80000000000000000", - "tick_spacing": 1000, - "extension": "0x0", - "sqrt_ratio": "0x5c358a19c219f31e027d5ac98e8d5656", - "tick": -2042238, - "liquidity": "35067659406163360487", - "lastUpdate": { - "event_id": "2747597966999556" - } - } - ] - """ - endpoint = "/pools" - response = self.send_get_request(endpoint) - if isinstance(response, dict) and response.get("error"): - # handling too many requests - time.sleep(2) - response = self.send_get_request(endpoint) - return response - - def get_pool_states(self) -> list: - """ - Fetches the current state of all pools. - - The method sends a GET request to the endpoint "/pools" and retrieves a list of pool states. - Each pool state is represented as a dictionary with the following keys: - - - key_hash (str): A unique hash identifier for the pool. - - token0 (str): The address of the first token in the pool. - - token1 (str): The address of the second token in the pool. - - fee (str): The fee tier of the pool. - - tick_spacing (int): The tick spacing for the pool. - - extension (str): Extension field (details unspecified). - - sqrt_ratio (str): The square root of the price ratio (Q64.96 format). - - tick (int): The current tick of the pool. - - liquidity (str): The current liquidity in the pool. - - lastUpdate (dict): A dictionary containing the last update information: - - event_id (str): The event identifier for the last update. - - Returns: - list: A list of dictionaries, each representing the state of a pool. - """ - endpoint = "/pools" - return self.send_get_request(endpoint) - - def get_pair_liquidity(self, tokenA: str, tokenB: str) -> dict: - """ - Fetches the liquidity information for a specified token pair. - - The method sends a GET request to the endpoint "/tokens/{tokenA}/{tokenB}/liquidity" - to retrieve the liquidity data for the given token pair. The response is a dictionary - containing the following keys: - - - net_liquidity_delta_diff (str): The net liquidity delta difference for the token pair. - - tick (int): The current tick for the liquidity of the token pair. - - Args: - tokenA (str): The address of the first token. - tokenB (str): The address of the second token. - - Returns: - dict: A dictionary containing the liquidity information for the token pair. - """ - endpoint = f"/tokens/{tokenA}/{tokenB}/liquidity" - return self.send_get_request(endpoint) - - def get_pair_states(self, tokenA: str, tokenB: str) -> dict: - """ - Fetches the state information for a specified token pair. - - The method sends a GET request to the endpoint "/pair/{tokenA}/{tokenB}" - to retrieve the state data for the given token pair. The response is a dictionary - containing the following keys: - - - timestamp (int): The timestamp of the data. - - tvlByToken (list): A list of dictionaries containing the total value locked (TVL) by token: - - token (str): The address of the token. - - balance (str): The balance of the token in the pool. - - volumeByToken (list): A list of dictionaries containing the volume by token: - - token (str): The address of the token. - - volume (str): The trading volume of the token. - - fees (str): The fees generated by the token. - - revenueByToken (list): A list of dictionaries containing the revenue by token: - - token (str): The address of the token. - - revenue (str): The revenue generated by the token. - - tvlDeltaByTokenByDate (list): A list of dictionaries containing the TVL delta by token by date: - - token (str): The address of the token. - - date (str): The date of the TVL delta. - - delta (str): The change in TVL for the token. - - volumeByTokenByDate (list): A list of dictionaries containing the volume by token by date: - - token (str): The address of the token. - - date (str): The date of the volume data. - - volume (str): The trading volume of the token. - - fees (str): The fees generated by the token. - - revenueByTokenByDate (list): A list of dictionaries containing the revenue by token by date: - - token (str): The address of the token. - - date (str): The date of the revenue data. - - revenue (str): The revenue generated by the token. - - topPools (list): A list of dictionaries containing the top pools: - - fee (str): The fee tier of the pool. - - tick_spacing (int): The tick spacing of the pool. - - extension (str): Extension field (details unspecified). - - volume0_24h (str): The 24-hour trading volume of token0. - - volume1_24h (str): The 24-hour trading volume of token1. - - fees0_24h (str): The 24-hour trading fees for token0. - - fees1_24h (str): The 24-hour trading fees for token1. - - tvl0_total (str): The total TVL of token0. - - tvl1_total (str): The total TVL of token1. - - tvl0_delta_24h (str): The 24-hour delta in TVL for token0. - - tvl1_delta_24h (str): The 24-hour delta in TVL for token1. - - Args: - tokenA (str): The address of the first token. - tokenB (str): The address of the second token. - - Returns: - dict: A dictionary containing the state information for the token pair. - """ - endpoint = f"/pair/{tokenA}/{tokenB}" - return self.send_get_request(endpoint) - - def get_pair_price(self, base_token: str, quote_token: str) -> dict: - """ - Fetches the price information for a specified token pair. - :param base_token: Base token address - :param quote_token: Quote token address - :return: - {'price': '0.9528189037', 'timestamp': '2024-05-18T10:41:37.091Z'} - """ - endpoint = f"/price/{base_token}/{quote_token}" - return self.send_get_request(endpoint) diff --git a/apps/web_app/order_books/ekubo/docs/pool_liquidity.md b/apps/web_app/order_books/ekubo/docs/pool_liquidity.md deleted file mode 100644 index b145313e..00000000 --- a/apps/web_app/order_books/ekubo/docs/pool_liquidity.md +++ /dev/null @@ -1,32 +0,0 @@ -# Get Pool Liquidity - -### Class Method -Use this class method to retrieve liquidity data for each tick associated with a given pool key hash: -`Ekubo.api_connector.EkuboAPIConnector.get_pool_liquidity(key_hash)` -To get `key_hash`, use the `get_pools()` method. - -### Data Structure -This endpoint retrieves the liquidity delta for each tick for a specified pool key hash. The response includes an array of objects, each detailing specific liquidity information for a tick. Each object in the array contains: -- **tick**: The index of the tick within the pool's range. - - Type: `int` -- **net_liquidity_delta_diff**: The net change in liquidity at this tick, expressed as a difference. - - Type: `str` - -### Parameters -- **key_hash**: The unique identifier of the pool, can be in hexadecimal or decimal format. - - Type: `str` - -### Return -- Returns an array of dictionaries, each providing details on the liquidity state for each tick. - -### Example of Returned Data -```json -[ - { - "tick": -88719042, - "net_liquidity_delta_diff": "534939583228319557" - } -] -``` - - diff --git a/apps/web_app/order_books/ekubo/docs/pools.md b/apps/web_app/order_books/ekubo/docs/pools.md deleted file mode 100644 index 0f3bde93..00000000 --- a/apps/web_app/order_books/ekubo/docs/pools.md +++ /dev/null @@ -1,52 +0,0 @@ -# Get Pools - -### Class Method -Use this class method to retrieve detailed information about various pools: -`Ekubo.api_connector.EkuboAPIConnector.get_pools()` - -### Data Structure -This endpoint retrieves a list of dictionaries, each containing comprehensive details about a pool. The structure of each dictionary in the list is as follows: -- **key_hash**: The unique identifier of the pool in hexadecimal format. - - Type: `str` -- **token0**: The address of the first token in the pool on the blockchain. - - Type: `str` -- **token1**: The address of the second token in the pool on the blockchain. - - Type: `str` -- **fee**: The fee associated with the pool transactions, in hexadecimal format. - - Type: `str` -- **tick_spacing**: The minimum granularity of price movements in the pool. - - Type: `int` -- **extension**: Additional information or features related to the pool, in hexadecimal format. - - Type: `str` -- **sqrt_ratio**: The square root of the current price ratio between token0 and token1, in hexadecimal format. - - Type: `str` -- **tick**: The current position of the pool in its price range. - - Type: `int` -- **liquidity**: The total liquidity available in the pool. - - Type: `str` -- **lastUpdate**: A dictionary containing details about the latest update event: - - **event_id**: Identifier of the last update event. - - Type: `str` - -### Return -- The method returns a list of dictionaries. Each dictionary provides detailed information about a specific pool. - -### Example of Returned Data -```json -[ - { - "key_hash": "0x34756a876aa3288b724dc967d43ee72d9b9cf390023775f0934305233767156", - "token0": "0xda114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3", - "token1": "0x49210ffc442172463f3177147c1aeaa36c51d152c1b0630f2364c300d4f48ee", - "fee": "0x20c49ba5e353f80000000000000000", - "tick_spacing": 1000, - "extension": "0x0", - "sqrt_ratio": "0x5c358a19c219f31e027d5ac98e8d5656", - "tick": -2042238, - "liquidity": "35067659406163360487", - "lastUpdate": { - "event_id": "2747597966999556" - } - } -] -``` \ No newline at end of file diff --git a/apps/web_app/order_books/ekubo/docs/token_lists.md b/apps/web_app/order_books/ekubo/docs/token_lists.md deleted file mode 100644 index bb0dd8a0..00000000 --- a/apps/web_app/order_books/ekubo/docs/token_lists.md +++ /dev/null @@ -1,22 +0,0 @@ -# Get List of Tokens - -### Class Method -Retrieve a list of tokens by using: -`Ekubo.api_connector.EkuboAPIConnector.get_list_tokens()` - -### Data Structure -This endpoint provides a list of tokens with detailed information. Each entry in the returned list is a dictionary containing: -- **name**: The name of the token, such as "Wrapped BTC". - - Type: `str` -- **symbol**: The abbreviated symbol of the token, such as "WBTC". - - Type: `int` -- **decimals**: The number of decimal places the token is divided into. - - Type: `str` -- **l2_token_address**: The address of the token on layer 2, in hexadecimal format. - - Type: `str` -- **sort_order**: An integer specifying the display order relative to other tokens; lower numbers appear first. - - Type: `int` -- **total_supply**: The total supply of the token, if known. It can be 'None' if the total supply is not set or unlimited. - - Type: `str` -- **logo_url**: A URL to the token's logo image. - - Type: `str` \ No newline at end of file diff --git a/apps/web_app/order_books/ekubo/docs/token_prices.md b/apps/web_app/order_books/ekubo/docs/token_prices.md deleted file mode 100644 index 235e80c4..00000000 --- a/apps/web_app/order_books/ekubo/docs/token_prices.md +++ /dev/null @@ -1,26 +0,0 @@ -# Get Token Prices - -### Usage Example -To get the `quote_token`, first retrieve the list of tokens using `get_list_tokens()`, and then use a specific token's details from the list to query `get_token_prices(quote_token)` for its current prices. - -### Class Method -Use this class method to retrieve current token prices: -`Ekubo.api_connector.EkuboAPIConnector.get_token_prices(quote_token)` -`Quote token` can be fetched from `get_list_tokens` method. This field is `l2_token_address` - -### Data Structure -This endpoint returns the current price of a token in terms of another token or currency. The response includes the following details: -- **timestamp**: Time at which the data was recorded, in milliseconds since the Unix epoch. - - Type: `int` -- **prices**: A list of dictionaries detailing the price of each token. - - Type: `list` of `dict` - -Each dictionary in the `prices` list contains: -- **token**: The address of the token on the blockchain. - - Type: `str` -- **price**: The current price of the token, expressed in another token or currency. - - Type: `str` -- **k_volume**: Volume of transactions for the token within a specified period, expressed in smallest unit counts (e.g., wei for Ethereum). - - Type: `str` - - diff --git a/apps/web_app/order_books/ekubo/histogram.py b/apps/web_app/order_books/ekubo/histogram.py deleted file mode 100644 index bf901d7b..00000000 --- a/apps/web_app/order_books/ekubo/histogram.py +++ /dev/null @@ -1,159 +0,0 @@ -import argparse -from typing import List - -import matplotlib.pyplot as plt - -from web_app.order_books.ekubo.main import EkuboOrderBook - -TOKEN_A = "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" # ETH -TOKEN_B = "0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8" # USDC - - -def fetch_order_book_and_current_price(token_a: str, token_b: str) -> tuple: - """ - Fetch the order book and current price for the given token pair. - :param token_a: Base token contract address - :param token_b: Quote token contract address - :return: Tuple containing the order book and current price - """ - order_book = EkuboOrderBook(token_a, token_b) - order_book.fetch_price_and_liquidity() - return order_book.get_order_book(), order_book.current_price - - -class Histogram: - """ - A class to create and display histograms for order book data. - - Attributes: - fig (plt.Figure): The matplotlib figure object. - ax (plt.Axes): The matplotlib axes object. - ask_prices (List[float]): List of ask prices. - ask_quantities (List[float]): List of ask quantities. - bid_prices (List[float]): List of bid prices. - bid_quantities (List[float]): List of bid quantities. - current_price (float): The current market price. - """ - - def __init__(self): - """Initialize the Histogram with an empty plot and collect order book data.""" - self.fig, self.ax = plt.subplots() - self._collect_data() - - def add_label(self, quantity_name: str, price_name: str) -> None: - """ - Add labels to the histogram. - - Args: - quantity_name (str): The name of the quantity. - price_name (str): The name of the price. - """ - self.ax.set_xlabel(f"Quantity ({quantity_name})") - self.ax.set_ylabel(f"Price ({price_name})") - self.ax.legend() - - def _collect_data(self) -> None: - """Collect order book data and convert to float.""" - data, current_price = fetch_order_book_and_current_price(TOKEN_A, TOKEN_B) - - ask_prices, ask_quantities = zip(*data["asks"]) - bid_prices, bid_quantities = zip(*data["bids"]) - - self.ask_prices = [float(price) for price in ask_prices] - self.ask_quantities = [float(quantity) for quantity in ask_quantities] - self.bid_prices = [float(price) for price in bid_prices] - self.bid_quantities = [float(quantity) for quantity in bid_quantities] - - self.current_price = float(current_price) - - def add_current_price_line(self, max_quantity: float = 0) -> None: - """ - Add a line representing the current price to the histogram. - - Args: - max_quantity (float): The maximum quantity to set the width of the line. Defaults to 0. - """ - self.ax.barh( - [self.current_price], - max_quantity, - color="black", - height=20, - label="Current Price", - ) - min_price = min(min(self.bid_prices), min(self.ask_prices), self.current_price) - max_price = max(max(self.bid_prices), max(self.ask_prices), self.current_price) - self.ax.set_ylim(min_price - 100, max_price + 100) # Adding some buffer - - def add_asks(self) -> None: - """Add ask prices and quantities to the histogram.""" - self.ax.barh( - self.ask_prices, self.ask_quantities, color="red", label="Asks", height=15 - ) - - def add_bids(self) -> None: - """Add bid prices and quantities to the histogram.""" - self.ax.barh( - self.bid_prices, self.bid_quantities, color="green", label="Bids", height=15 - ) - - def add_total_box_quantity( - self, quantities_name: str, sum_quantities: float - ) -> None: - """ - Add a text box displaying the total quantity. - - Args: - quantities_name (str): The name of the quantities. - sum_quantities (float): The sum of the quantities. - """ - total_quantity = round(sum_quantities, 4) - textstr = f"Total {quantities_name} Quantity: {total_quantity}" - props = dict(boxstyle="round", facecolor="wheat", alpha=0.5) - self.ax.text( - 0.95, - 1.05, - textstr, - transform=self.ax.transAxes, - fontsize=10, - verticalalignment="top", - horizontalalignment="right", - bbox=props, - ) - - def show_asks(self) -> None: - """Display the asks histogram with the current price line and total quantity.""" - self.add_current_price_line(max(self.ask_quantities)) - self.add_asks() - self.add_label("ETH", "USDC") - self.add_total_box_quantity("ETH", sum(self.ask_quantities)) - plt.show() - - def show_bids(self) -> None: - """Display the bids histogram with the current price line and total quantity.""" - self.add_current_price_line(max(self.bid_quantities)) - self.add_bids() - self.add_label("USDC", "ETH") - self.add_total_box_quantity("USDC", sum(self.bid_quantities)) - plt.show() - - -def main(): - parser = argparse.ArgumentParser(description="Display order book histograms.") - parser.add_argument( - "--type", - choices=["asks", "bids"], - required=True, - help="Type of histogram to display: 'asks' or 'bids'.", - ) - - args = parser.parse_args() - - histogram = Histogram() - if args.type == "asks": - histogram.show_asks() - elif args.type == "bids": - histogram.show_bids() - - -if __name__ == "__main__": - main() diff --git a/apps/web_app/order_books/ekubo/main.py b/apps/web_app/order_books/ekubo/main.py deleted file mode 100644 index 206df4b3..00000000 --- a/apps/web_app/order_books/ekubo/main.py +++ /dev/null @@ -1,244 +0,0 @@ -import logging -from decimal import Decimal, getcontext - -import pandas as pd -from order_books.abstractions import OrderBookBase -from order_books.constants import TOKEN_MAPPING - -from web_app.order_books.ekubo.api_connector import EkuboAPIConnector - -getcontext().prec = 18 - - -class EkuboOrderBook(OrderBookBase): - DEX = "Ekubo" - - def __init__(self, token_a: str, token_b: str) -> None: - """ - Initialize the EkuboOrderBook object. - :param token_a: BaseToken contract address - :param token_b: QuoteToken contract address - """ - super().__init__(token_a, token_b) - self.connector = EkuboAPIConnector() - - def set_current_price(self) -> str: - """ - Get the current price of the pair. - :return: str - The current price of the pair. - """ - price_data = self.connector.get_pair_price(self.token_a, self.token_b) - self.current_price = Decimal(price_data.get("price", "0")) - - def fetch_price_and_liquidity(self) -> None: - """ - Fetch the current price and liquidity of the pair from the Ekubo API. - """ - # Get pool liquidity - pools = self.connector.get_pools() - df = pd.DataFrame(pools) - # filter pool data by token_a and token_b - pool_df = df.loc[ - (df["token0"] == self.token_a) & (df["token1"] == self.token_b) - ] - - # set current price - self.set_current_price() - for index, row in list(pool_df.iterrows()): - key_hash = row["key_hash"] - # Fetch pool liquidity data - pool_liquidity = int(row["liquidity"]) - self.block = row["lastUpdate"]["event_id"] - - liquidity_response = self.connector.get_pool_liquidity(key_hash) - liquidity_data = liquidity_response["data"] - liquidity_data = sorted(liquidity_data, key=lambda x: x["tick"]) - - self._calculate_order_book( - liquidity_data, - pool_liquidity, - row, - ) - - def _calculate_order_book( - self, - liquidity_data: list, - pool_liquidity: int, - row: pd.Series, - ) -> None: - """ - Calculate the order book based on the liquidity data. - :param liquidity_data: list - List of liquidity data - :param pool_liquidity: pool liquidity - :param row: pd.Series - Pool data - """ - min_price, max_price = self.calculate_price_range() - if not liquidity_data: - return - - self.add_asks(liquidity_data, row) - self.add_bids(liquidity_data, row) - - # Filter asks and bids by price range - self.asks = [ - (price, supply) - for price, supply in self.asks - if min_price < price < max_price - ] - self.bids = [ - (price, supply) - for price, supply in self.bids - if min_price < price < max_price - ] - - def add_asks(self, liquidity_data: list[dict], row: pd.Series) -> None: - """ - Add `asks` to the order book. - :param liquidity_data: list of dict with tick and net_liquidity_delta_diff - :param row: pool row data - """ - ask_ticks = [i for i in liquidity_data if i["tick"] >= row["tick"]] - if not ask_ticks: - return - - glob_liq = Decimal(row["liquidity"]) - - # Calculate for current tick (loops start with the next one) - next_tick = ask_ticks[0]["tick"] - prev_tick = next_tick - row["tick_spacing"] - - prev_sqrt = self._get_pure_sqrt_ratio(prev_tick) - next_sqrt = self._get_pure_sqrt_ratio(next_tick) - - supply = abs( - ((glob_liq / prev_sqrt) - (glob_liq / next_sqrt)) - / 10**self.token_a_decimal - ) - price = self.tick_to_price(prev_tick) - self.asks.append((price, supply)) - - for index, tick in enumerate(ask_ticks): - if index == 0: - continue - glob_liq += Decimal(ask_ticks[index - 1]["net_liquidity_delta_diff"]) - prev_tick = Decimal(ask_ticks[index - 1]["tick"]) - - curr_tick = Decimal(tick["tick"]) - - prev_sqrt = self._get_pure_sqrt_ratio(prev_tick) - next_sqrt = self._get_pure_sqrt_ratio(curr_tick) - - supply = abs( - ((glob_liq / prev_sqrt) - (glob_liq / next_sqrt)) - / 10**self.token_a_decimal - ) - price = self.tick_to_price(prev_tick) - self.asks.append((price, supply)) - - def add_bids(self, liquidity_data: list[dict], row: pd.Series) -> None: - """ - Add `bids` to the order book. - :param liquidity_data: liquidity data list of dict with tick and net_liquidity_delta_diff - :param row: pool row data - """ - bid_ticks = [i for i in liquidity_data if i["tick"] <= row["tick"]][::-1] - if not bid_ticks: - return - - glob_liq = Decimal(row["liquidity"]) - - next_tick = bid_ticks[0]["tick"] - prev_tick = next_tick + row["tick_spacing"] - - prev_sqrt = self._get_pure_sqrt_ratio(prev_tick) - next_sqrt = self._get_pure_sqrt_ratio(next_tick) - - supply = abs( - ((glob_liq * prev_sqrt) - (glob_liq * next_sqrt)) - / 10**self.token_b_decimal - ) - price = self.tick_to_price(prev_tick) - self.bids.append((price, supply)) - - for index, tick in enumerate(bid_ticks): - if index == 0: - continue - glob_liq -= Decimal(bid_ticks[index - 1]["net_liquidity_delta_diff"]) - prev_tick = Decimal(bid_ticks[index - 1]["tick"]) - curr_tick = Decimal(tick["tick"]) - - prev_sqrt = self._get_pure_sqrt_ratio(prev_tick) - next_sqrt = self._get_pure_sqrt_ratio(curr_tick) - - supply = ( - abs(((glob_liq * prev_sqrt) - (glob_liq * next_sqrt))) - / 10**self.token_b_decimal - ) - price = self.tick_to_price(prev_tick) - self.bids.append((price, supply)) - - def _get_pure_sqrt_ratio(self, tick: Decimal) -> Decimal: - """ - Get the square root ratio based on the tick. - :param tick: tick value - :return: square root ratio - """ - return Decimal("1.000001").sqrt() ** tick - - @staticmethod - def sort_ticks_by_asks_and_bids( - sorted_liquidity_data: list, current_tick: int - ) -> tuple[list, list]: - """ - Sort tick by ask and bid - :param sorted_liquidity_data: list - List of sorted liquidity data - :param current_tick: int - Current tick - :return: list - List of sorted liquidity data - """ - sorted_liquidity_data = sorted(sorted_liquidity_data, key=lambda x: x["tick"]) - ask_data, bid_data = [], [] - for sorted_data in sorted_liquidity_data: - if sorted_data["tick"] > current_tick: - ask_data.append(sorted_data) - else: - bid_data.append(sorted_data) - return ask_data, bid_data - - def calculate_liquidity_amount( - self, tick: Decimal, liquidity_pair_total: Decimal - ) -> Decimal: - """ - Calculate the liquidity amount based on the liquidity delta and sqrt ratio. - :param tick: Decimal - The sqrt ratio. - :param liquidity_pair_total: Decimal - The liquidity pair total. - :return: Decimal - The liquidity amount. - """ - sqrt_ratio = self.get_sqrt_ratio(tick) - liquidity_delta = liquidity_pair_total / (sqrt_ratio / Decimal(2**128)) - return liquidity_delta / 10**self.token_a_decimal - - def tick_to_price(self, tick: Decimal) -> Decimal: - """ - Convert tick to price. - :param tick: tick value - :return: price by tick - """ - sqrt_ratio = self.get_sqrt_ratio(tick) - # calculate price by formula price = (sqrt_ratio / (2 ** 128)) ** 2 * 10 ** (token_a_decimal - token_b_decimal) - price = ((sqrt_ratio / (Decimal(2) ** 128)) ** 2) * 10 ** ( - self.token_a_decimal - self.token_b_decimal - ) - return price - - -def debug_code() -> None: - """ - This function is used to test the EkuboOrderBook class. - """ - token_a = "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" # ETH - token_b = ( - "0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8" # USDC - ) - order_book = EkuboOrderBook(token_a, token_b) - order_book.fetch_price_and_liquidity() - print(order_book.get_order_book(), "\n") diff --git a/apps/web_app/order_books/haiko/README.md b/apps/web_app/order_books/haiko/README.md deleted file mode 100644 index 7d0443f7..00000000 --- a/apps/web_app/order_books/haiko/README.md +++ /dev/null @@ -1,23 +0,0 @@ -# Haiko - -This folder contains interactions with Haiko API, -[BlastAPI](https://docs.blastapi.io/blast-documentation/apis-documentation/core-api/starknet) and -[Haiko Smart Contract](https://starkscan.co/contract/0x0038925b0bcf4dce081042ca26a96300d9e181b910328db54a6c89e5451503f5) on Starknet. - -# Endpoints Documentation: -- [Get Haiko Markets](docs/markets.md) - Information about markets for different tokens. -- [Get Supported Tokens](docs/tokens.md) - All supported tokens on Haiko. -- [Get Block Information](docs/block_info.md) - Retrieve information about latest Haiko block. -- [Get USD Prices](docs/prices.md) - Retrieve USD prices for tokens on Haiko. - -# How to run -In this folder: `haiko` run next command -```bash -python main.py -``` - -# How to get report about supported tokens -In this folder: `haiko` run next command and check `reports` folder to see the report. -```bash -python report.py -``` diff --git a/apps/web_app/order_books/haiko/__init__.py b/apps/web_app/order_books/haiko/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/apps/web_app/order_books/haiko/api_connector.py b/apps/web_app/order_books/haiko/api_connector.py deleted file mode 100644 index 34e2a1d3..00000000 --- a/apps/web_app/order_books/haiko/api_connector.py +++ /dev/null @@ -1,148 +0,0 @@ -from web_app.utils.abstractions import AbstractionAPIConnector - - -class HaikoAPIConnector(AbstractionAPIConnector): - API_URL = "https://app.haiko.xyz/api/v1" - - @classmethod - def get_supported_tokens(cls, existing_only: bool = True) -> list[dict]: - """ - Get all tokens supported by Haiko. - :param existing_only: If True, return only tokens that are currently available on Haiko. - :return: List of all tokens supported by Haiko. - The response list structure is as follows: - [ - { - "address": "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", - "name": "Ether", - "symbol": "ETH", - "decimals": 18, - "rank": 7, - "coingeckoAddress": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" - } - ] - """ - if not isinstance(existing_only, bool): - raise ValueError("Existing only parameter must be a bool") - endpoint = f"/tokens?network=mainnet&existingOnly={existing_only}" - return cls.send_get_request(endpoint) # type: ignore - - @classmethod - def get_market_depth(cls, market_id: str) -> list[dict]: - """ - Get the market depth for a specific market. - :param market_id: The market ID in hexadecimal. - :return: List of market depth. - The response list structure is as follows: - [ - { - "price": "1.2072265814306946", - "liquidityCumulative": "4231256547876" - }, - ... - ] - """ - endpoint = f"/depth?network=mainnet&id={market_id}" - return cls.send_get_request(endpoint) # type: ignore - - @classmethod - def get_pair_markets(cls, token0: str, token1: str) -> list[dict]: - """ - Get Haiko markets for provided token pair. - :return: List of Haiko markets for a token pair. - The response list structure is as follows: - [ - { - "marketId": "0x581defc9a9b4e77fcb3ec274983e18ea30115727f7647f2eb1d23858292d873", - "baseToken": { - "address": "0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d", - "name": "Starknet Token", - "symbol": "STRK", - "decimals": 18, - "rank": 10 - }, - "quoteToken": { - "address": "0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8", - "name": "USD Coin", - "symbol": "USDC", - "decimals": 6, - "rank": 2 - }, - "width": 200, - "strategy": { - "address": "0x0", - "name": null, - "symbol": null, - "version": null - }, - "swapFeeRate": 100, - "feeController": "0x0", - "controller": "0x0", - "currLimit": -2744284, - "currSqrtPrice": "1.0987386319915646", - "currPrice": "1.2072265814306946", - "tvl": "580.7339", - "feeApy": 0.05661423233103435 - } - ] - """ - endpoint = f"/markets-by-pair?network=mainnet&token0={token0}&token1={token1}" - return cls.send_get_request(endpoint) # type: ignore - - @classmethod - def get_usd_prices(cls, token_a_name, token_b_name) -> dict: - """ - Get USD prices for the provided token pair. - :return: USD prices for the provided token pair. - The response dictionary structure is as follows: - { - "ETH": 3761.71, - "USDC": 0.999003 - } - """ - endpoint = f"/usd-prices?network=mainnet&tokens={token_a_name},{token_b_name}" - return cls.send_get_request(endpoint) # type: ignore - - -class HaikoBlastAPIConnector(AbstractionAPIConnector): - API_URL = "https://starknet-mainnet.blastapi.io" - PROJECT_ID = "a419bd5a-ec9e-40a7-93a4-d16467fb79b3" - - @classmethod - def _post_request_builder(cls, call_method: str, params: dict) -> dict: - """ - Build request body for Blast API POST request from common base. - :param call_method: BlastAPI method to call. - :param params: Parameters for the method. - :return: Response from BlastAPI. - """ - if not isinstance(call_method, str) or not isinstance(params, dict): - raise ValueError("Call method must be a string and params must be a dict.") - body = {"jsonrpc": "2.0", "method": call_method, "params": params, "id": 0} - endpoint = f"/{cls.PROJECT_ID}" - return cls.send_post_request(endpoint, json=body) - - @classmethod - def get_block_info(cls) -> dict: - """ - Get information about the latest block. - :return: Information about the latest block. - The response dictionary structure is as follows: - { - "jsonrpc": "2.0", - "result": { - "status": "ACCEPTED_ON_L2", - "block_hash": "0x18ec1a3931bb5a286f801a950e1153bd427d6d3811591cc01e6f074615a1f76", - "parent_hash": "0x413229e9996b3025feb6b276a33249fb0ff0f92d8aeea284deb35ea4093dea2", - "block_number": 4503, - "new_root": "0xc95a878188acf408e285027bd5e7674a88529b8c65ef6c1999b3569aea8bc8", - "timestamp": 1661246333, - "sequencer_address": "0x5dcd266a80b8a5f29f04d779c6b166b80150c24f2180a75e82427242dab20a9", - "transactions": ["0x6a19b22f4fe4018d4d60ff844770a5459534d0a69f850f3c9cdcf70a132df94", ...], - }, - "id": 0 - } - """ - return cls._post_request_builder( - "starknet_getBlockWithTxHashes", params={"block_id": "latest"} - ) diff --git a/apps/web_app/order_books/haiko/docs/block_info.md b/apps/web_app/order_books/haiko/docs/block_info.md deleted file mode 100644 index 45135af6..00000000 --- a/apps/web_app/order_books/haiko/docs/block_info.md +++ /dev/null @@ -1,29 +0,0 @@ -# Get Block Information - -### Class Method -Use this class method to retrieve information about block using Haiko project id: -`haiko.api_connector.HaikoBlastAPIConnector.get_block_info()` - -### Data Structure -This endpoint retrieves information about the latest Haiko block. The response includes an object containing the following details: block number and timestamp. - -### Return -- Returns dictionary with id, jsonrpc version and block information. - -### Example of Returned Data -```json -{ - "jsonrpc": "2.0", - "result": { - "status": "ACCEPTED_ON_L2", - "block_hash": "0x18ec1a3931bb5a286f801a950e1153bd427d6d3811591cc01e6f074615a1f76", - "parent_hash": "0x413229e9996b3025feb6b276a33249fb0ff0f92d8aeea284deb35ea4093dea2", - "block_number": 4503, - "new_root": "0xc95a878188acf408e285027bd5e7674a88529b8c65ef6c1999b3569aea8bc8", - "timestamp": 1661246333, - "sequencer_address": "0x5dcd266a80b8a5f29f04d779c6b166b80150c24f2180a75e82427242dab20a9", - "transactions": ["0x6a19b22f4fe4018d4d60ff844770a5459534d0a69f850f3c9cdcf70a132df94", "0x5fb5b63f0226ef426c81168d0235269398b63aa145ca6a3c47294caa691cfdc"] - }, - "id": 0 -} -``` diff --git a/apps/web_app/order_books/haiko/docs/depth.md b/apps/web_app/order_books/haiko/docs/depth.md deleted file mode 100644 index 503b4a75..00000000 --- a/apps/web_app/order_books/haiko/docs/depth.md +++ /dev/null @@ -1,31 +0,0 @@ -### Get depth of the market - -### Class Method -Use this class method to retrieve market depth: -`haiko.api_connector.HaikoAPIConnector.get_market_depth(market_id)` - -### Data Structure -This endpoint retrieves the liquidity for each price for a specified market. -The response includes an array of objects, each detailing liquidity information in a pool for a price. -Each object in the array contains: -- **price**: The index of the tick within the pool's range. - - Type: `str` -- **liquidityCumulative**: The current liquidity in a pool. - - Type: `str` - -### Parameters -- **market_id**: The identifier of the market in hexadecimal format. - - Type: `str` - -### Return -- Returns an array of dictionaries, each providing details on the liquidity state for each price. - -### Example of Returned Data -```json -[ - { - "price": "3750.5342", - "liquidityCumulative": "534939583228319557" - } -] -``` diff --git a/apps/web_app/order_books/haiko/docs/markets.md b/apps/web_app/order_books/haiko/docs/markets.md deleted file mode 100644 index b881d651..00000000 --- a/apps/web_app/order_books/haiko/docs/markets.md +++ /dev/null @@ -1,89 +0,0 @@ -# Get Markets - -### Class Method -Use this class method to retrieve detailed information about markets for tokens: -`haiko.api_connector.HaikoAPIConnector.get_token_markets(token0, token1)` - -### Data Structure -This endpoint retrieves a list of dictionaries, each containing details about a market. The structure of each dictionary in the list is as follows(only used and known info): -- **marketId**: The unique identifier of the market in hexadecimal format. - - Type: `str` -- **baseToken**: A dictionary containing details about the base token: - - **address**: The address of the base token in hexadecimal format. - - Type: `str` - - **symbol**: The symbol of the base token. - - Type: `str` - - **decimals**: The number of decimal places used to represent the base token. - - Type: `int` - - **name**: The name of the base token. - - Type: `str` - - **rank**: The rank of the base token on the market. - - Type: `int` -- **quoteToken**: A dictionary containing details about the quote token: - - **address**: The address of the quote token in hexadecimal format. - - Type: `str` - - **symbol**: The symbol of the quote token. - - Type: `str` - - **decimals**: The number of decimal places used to represent the quote token. - - Type: `int` - - **name**: The name of the quote token. - - Type: `str` - - **rank**: The rank of the quote token on the market. - - Type: `int` -- **width** The width of the tick on the market. - - Type: `int` -- **currLimit**: The current tick on the market. - - Type: `int` -- **currPrice**: The current price on the market. - - Type: `str` -- **currSqrtPrice**: The square root of the current price on the market. - - Type: `str` -- **tvl**: The total value locked in the market. - - Type: `str` - -### Parameters -- **token0**: The address of the base token in the hexadecimal format. - - Type: `str` -- **token1**: The address of the quote token in the hexadecimal format. - - Type: `str` - -### Return -- The method returns a list of dictionaries. Each dictionary provides detailed information about a specific market. - -### Example of Returned Data -```json -[ - { - "marketId": "0x581defc9a9b4e77fcb3ec274983e18ea30115727f7647f2eb1d23858292d873", - "baseToken": { - "address": "0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d", - "name": "Starknet Token", - "symbol": "STRK", - "decimals": 18, - "rank": 10 - }, - "quoteToken": { - "address": "0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8", - "name": "USD Coin", - "symbol": "USDC", - "decimals": 6, - "rank": 2 - }, - "width": 200, - "strategy": { - "address": "0x0", - "name": null, - "symbol": null, - "version": null - }, - "swapFeeRate": 100, - "feeController": "0x0", - "controller": "0x0", - "currLimit": -2744284, - "currSqrtPrice": "1.0987386319915646", - "currPrice": "1.2072265814306946", - "tvl": "580.7339", - "feeApy": 0.05661423233103435 - } -] -``` diff --git a/apps/web_app/order_books/haiko/docs/prices.md b/apps/web_app/order_books/haiko/docs/prices.md deleted file mode 100644 index d90acd93..00000000 --- a/apps/web_app/order_books/haiko/docs/prices.md +++ /dev/null @@ -1,29 +0,0 @@ -# Get USD Prices - -### Class Method -Use this class method to retrieve prices of tokens: -`haiko.api_connector.HaikoAPIConnector.get_usd_prices(token0_name, token1_name)` - -### Data Structure -This endpoint retrieves a dictionary containing the prices of tokens. The keys of the dictionary are: -- **token0_name**: The price of the first token. - - Type: `str` -- **token1_name**: The price of the second token. - - Type: `str` - -### Parameters -- **token0_name**: The name of the base token. - - Type: `str` -- **token1_name**: The name of the quote token. - - Type: `str` - -### Return -- The method returns a dictionary containing the prices of the specified tokens. - -### Example of Returned Data -```json -{ - "ETH": "232.54", - "USDC": "0.867987" -} -``` diff --git a/apps/web_app/order_books/haiko/docs/tokens.md b/apps/web_app/order_books/haiko/docs/tokens.md deleted file mode 100644 index 9ebedd29..00000000 --- a/apps/web_app/order_books/haiko/docs/tokens.md +++ /dev/null @@ -1,35 +0,0 @@ -# Get List of Supported Tokens - -### Class Method -Retrieve a list of tokens by using: -`haiko.api_connector.HaikoAPIConnector.get_supported_tokens()` - -### Data Structure -This endpoint provides a list of tokens with detailed information. Each entry in the returned list is a dictionary containing: -- **address**: The address of the token on layer 2, in hexadecimal format. - - Type: `str` -- **name**: The name of the token, such as "Wrapped BTC". - - Type: `str` -- **symbol**: The abbreviated symbol of the token, such as "WBTC". - - Type: `int` -- **decimals**: The number of decimal places the token is divided into. - - Type: `str` -- **rank**: The rank of the token on the market. - - Type: `int` - -### Return -The method returns a list of dictionaries, each containing detailed information about a token. - -### Example of Returned Data -```json -[ - { - "address": "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", - "name": "Ether", - "symbol": "ETH", - "decimals": 18, - "rank": 7, - "coingeckoAddress": "0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" - } -] -``` diff --git a/apps/web_app/order_books/haiko/logger.py b/apps/web_app/order_books/haiko/logger.py deleted file mode 100644 index 2f8bf5d4..00000000 --- a/apps/web_app/order_books/haiko/logger.py +++ /dev/null @@ -1,35 +0,0 @@ -import logging -from datetime import datetime -from pathlib import Path - - -def get_logger(path: str, echo: bool = False): - """ - Configure and get logger for haiko order book - :param path: path to log file - :param echo: write logs in terminal if set to True - :return: Configured logger - """ - path_dir = Path(path) - path_dir.mkdir(parents=True, exist_ok=True) - logger = logging.getLogger("Haiko_Logger") - logger.setLevel(logging.DEBUG) - log_path = path_dir / datetime.now().strftime("order_book_%Y%m%d_%H%M%S.log") - file_handler = logging.FileHandler(log_path) - file_handler.setLevel(logging.DEBUG) - - formatter = logging.Formatter( - "%(asctime)s - %(name)s - %(levelname)s - %(message)s" - ) - file_handler.setFormatter(formatter) - logger.addHandler(file_handler) - - if not echo: - return logger - - console_handler = logging.StreamHandler() - console_handler.setLevel(logging.DEBUG) - console_handler.setFormatter(formatter) - logger.addHandler(console_handler) - - return logger diff --git a/apps/web_app/order_books/haiko/main.py b/apps/web_app/order_books/haiko/main.py deleted file mode 100644 index f66d8616..00000000 --- a/apps/web_app/order_books/haiko/main.py +++ /dev/null @@ -1,272 +0,0 @@ -from decimal import Decimal - -from web_app.order_books.abstractions import OrderBookBase -from web_app.order_books.constants import TOKEN_MAPPING -from web_app.order_books.haiko.api_connector import ( - HaikoAPIConnector, - HaikoBlastAPIConnector, -) -from web_app.order_books.haiko.logger import get_logger - - -class HaikoOrderBook(OrderBookBase): - DEX = "Haiko" - - def __init__(self, token_a, token_b, apply_filtering: bool = False): - """ - Initialize the HaikoOrderBook object. - :param token_a: baseToken hexadecimal address - :param token_b: quoteToken hexadecimal address - :param apply_filtering: bool - If True apply min and max price filtering to the order book data - """ - super().__init__(token_a, token_b) - self.haiko_connector = HaikoAPIConnector() - self.blast_connector = HaikoBlastAPIConnector() - self.apply_filtering = apply_filtering - self.logger = get_logger("./logs", echo=True) - - self.token_a_price = Decimal(0) - self.token_b_price = Decimal(0) - - self._decimals_diff = 10 ** ( - self.token_a_decimal - self.token_b_decimal or self.token_a_decimal - ) - self._check_tokens_supported() - self._set_usd_prices() - - def _get_valid_tokens_addresses(self) -> tuple[str, str]: - """ - Get tokens' addresses without leading zeros. - :return: tuple - The token addresses tuple without leading zeros - """ - if not isinstance(self.token_a, str) or not isinstance(self.token_b, str): - raise ValueError("Token addresses must be strings.") - return hex(int(self.token_a, base=16)), hex(int(self.token_b, base=16)) - - def _set_usd_prices(self) -> None: - """Set USD prices for tokens based on Haiko API.""" - token_a_info = TOKEN_MAPPING.get(self.token_a) - token_b_info = TOKEN_MAPPING.get(self.token_b) - if not token_a_info or not token_b_info: - raise ValueError("Information about tokens isn't available.") - token_a_name = token_a_info.name - token_b_name = token_b_info.name - prices = self.haiko_connector.get_usd_prices(token_a_name, token_b_name) - self.token_a_price = Decimal(prices.get(token_a_name, 0)) - self.token_b_price = Decimal(prices.get(token_b_name, 0)) - if self.token_a_price == 0 or self.token_b_price == 0: - raise RuntimeError("Prices for tokens aren't available.") - - def _check_tokens_supported(self) -> None: - """Check if a pair of tokens is supported by Haiko""" - supported_tokens = self.haiko_connector.get_supported_tokens( - existing_only=False - ) - if isinstance(supported_tokens, dict) and supported_tokens.get("error"): - raise RuntimeError(f"Unexpected error from API: {supported_tokens}") - valid_tokens = self._get_valid_tokens_addresses() - supported_tokens_filtered = [ - token for token in supported_tokens if token["address"] in valid_tokens - ] - if len(supported_tokens_filtered) != 2: - raise ValueError("One of tokens isn't supported by Haiko") - - def set_current_price(self, current_price: Decimal) -> None: - """ - Set the current price based on the current tick. - :param current_price: Decimal - Current market price - """ - self.current_price = current_price - - def sort_asks_bids(self) -> None: - """Sort bids and asks data in correct order.""" - self.asks.sort(key=lambda ask: ask[0]) - self.bids.sort(key=lambda bid: bid[0], reverse=True) - - def fetch_price_and_liquidity(self) -> None: - tokens_markets = self._filter_markets_data( - self.haiko_connector.get_pair_markets(self.token_a, self.token_b) - ) - latest_block_info = self.blast_connector.get_block_info() - if latest_block_info.get("error"): - raise RuntimeError(f"Blast-api returned an error: {latest_block_info}") - - self.block = latest_block_info["result"]["block_number"] - self.timestamp = latest_block_info["result"]["timestamp"] - - if not tokens_markets: - message = "Markets for this pair isn't available for now" - self.logger.critical(f"Pair of tokens: {self.token_a}-{self.token_b}") - self.logger.critical(f"{message}\n") - return - - for market in tokens_markets: - market_id = market["marketId"] - market_depth_list = self.haiko_connector.get_market_depth(market_id) - if not market_depth_list: - self.logger.info(f"Market depth for market {market_id} is empty.") - continue - self._calculate_order_book(market_depth_list, Decimal(market["currPrice"])) - - self.sort_asks_bids() - self.current_price = max(tokens_markets, key=lambda x: Decimal(x["tvl"]))[ - "currPrice" - ] - - def _calculate_order_book( - self, market_ticks_liquidity: list, current_price: Decimal - ) -> None: - self.set_current_price(current_price) - price_range = self.calculate_price_range() - asks, bids = [], [] - for tick_info in market_ticks_liquidity: - tick_info["price"] = Decimal(tick_info["price"]) - tick_info["liquidityCumulative"] = Decimal(tick_info["liquidityCumulative"]) - if tick_info["price"] >= current_price: - asks.append(tick_info) - else: - bids.append(tick_info) - - if not asks or not bids: - return - - bids.sort(key=lambda x: x["price"], reverse=True) - asks.sort(key=lambda x: x["price"]) - self.add_bids(bids, price_range) - self.add_asks(asks, bids[0]["liquidityCumulative"], price_range) - - def add_asks( - self, market_asks: list[dict], pool_liquidity: Decimal, price_range: tuple - ) -> None: - """ - Add `asks` to the order book. - :param market_asks: list of dictionaries with price and liquidityCumulative - :param pool_liquidity: current liquidity in a pool - :param price_range: tuple of Decimal - minimal and maximal acceptable prices - """ - if not market_asks: - return - local_asks = [] - x = self._get_token_amount( - pool_liquidity, - self.current_price.sqrt(), - market_asks[0]["price"].sqrt(), - ) - local_asks.append((self.current_price, x)) - for index, ask in enumerate(market_asks): - if index == 0: - continue - current_price = Decimal(market_asks[index - 1]["price"]) - x = self._get_token_amount( - Decimal(market_asks[index - 1]["liquidityCumulative"]), - current_price.sqrt(), - Decimal(market_asks[index]["price"]).sqrt(), - ) - local_asks.append((current_price, x)) - if self.apply_filtering: - self.asks.extend( - [ask for ask in local_asks if price_range[0] < ask[0] < price_range[1]] - ) - return - self.asks.extend(local_asks) - - def add_bids(self, market_bids: list[dict], price_range: tuple) -> None: - """ - Add `bids` to the order book. - :param market_bids: list of dictionaries with price and liquidityCumulative - :param price_range: tuple of Decimal - minimal and maximal acceptable prices - """ - if not market_bids: - return - local_bids = [] - prev_price = Decimal(market_bids[0]["price"]) - y = self._get_token_amount( - Decimal(market_bids[0]["liquidityCumulative"]), - self.current_price.sqrt(), - prev_price.sqrt(), - is_ask=False, - ) - local_bids.append((prev_price, y)) - for index, bid in enumerate(market_bids[::-1]): - if index == 0: - continue - current_price = Decimal(market_bids[index - 1]["price"]) - y = self._get_token_amount( - Decimal(market_bids[index]["liquidityCumulative"]), - current_price.sqrt(), - Decimal(market_bids[index]["price"]).sqrt(), - is_ask=False, - ) - local_bids.append((current_price, y)) - if self.apply_filtering: - self.bids.extend( - [bid for bid in local_bids if price_range[0] < bid[0] < price_range[1]] - ) - return - self.bids.extend(local_bids) - - def _get_token_amount( - self, - current_liq: Decimal, - current_sqrt: Decimal, - next_sqrt: Decimal, - is_ask: bool = True, - ) -> Decimal: - """ - Calculate token amount based on liquidity data and current data processed(asks/bids). - :param current_liq: Decimal - Current price liquidity - :param current_sqrt: Decimal - Current square root of a price - :param next_sqrt: Decimal - Next square root of a price - :param is_ask: bool - True if an ask data - :return: Decimal - token amount - """ - if is_ask and (current_sqrt == 0 or next_sqrt == 0): - raise ValueError("Square root of prices for asks can't be zero.") - if not is_ask and next_sqrt == 0: - return abs(current_liq / current_sqrt) / self._decimals_diff - amount = abs(current_liq / next_sqrt - current_liq / current_sqrt) - return amount / self._decimals_diff - - def calculate_liquidity_amount(self, tick, liquidity_pair_total) -> Decimal: - sqrt_ratio = self.get_sqrt_ratio(tick) - liquidity_delta = liquidity_pair_total / (sqrt_ratio / Decimal(2**128)) - return liquidity_delta / 10**self.token_a_decimal - - def tick_to_price(self, tick: Decimal) -> Decimal: - return Decimal("1.00001") ** tick * ( - 10 ** (self.token_a_decimal - self.token_b_decimal) - ) - - def _filter_markets_data(self, all_markets_data: list) -> list: - """ - Filter markets for actual token pair. - :param all_markets_data: all supported markets provided bt Haiko - :return: list of markets data for actual token pair - """ - token_a_valid, token_b_valid = self._get_valid_tokens_addresses() - return list( - filter( - lambda market: market["baseToken"]["address"] == token_a_valid - and market["quoteToken"]["address"] == token_b_valid, - all_markets_data, - ) - ) - - -if __name__ == "__main__": - # token_0 = "0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d" # STRK - # token_1 = "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" # ETH - # token_0 = "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" # ETH - # token_1 = "0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8" # USDC - token_0 = ( - "0x042b8f0484674ca266ac5d08e4ac6a3fe65bd3129795def2dca5c34ecc5f96d2" # wstETH - ) - token_1 = "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" # ETH - # token_0 = "0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d" # STRK - # token_1 = "0x53c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8" # USDC - # token_0 = "0x07e2c010c0b381f347926d5a203da0335ef17aefee75a89292ef2b0f94924864" # wstETH - # token_1 = "0x49d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7" - # token_1 = "0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8" - order_book = HaikoOrderBook(token_0, token_1) - order_book.fetch_price_and_liquidity() - serialized_order_book = order_book.serialize() diff --git a/apps/web_app/order_books/haiko/report.py b/apps/web_app/order_books/haiko/report.py deleted file mode 100644 index dc76adcb..00000000 --- a/apps/web_app/order_books/haiko/report.py +++ /dev/null @@ -1,114 +0,0 @@ -import asyncio -import json -import logging -from datetime import datetime -from pathlib import Path - -from web_app.order_books.constants import TOKEN_MAPPING -from web_app.order_books.haiko.main import HaikoOrderBook - - -def filter_logs(logs_path: Path) -> None: - """ - Remove empty log files. - :param logs_path: Path to the directory with log files. - """ - for file in logs_path.iterdir(): - if file.stat().st_size == 0: - file.unlink() - - -def serialize_asks_bids(order_book: dict) -> None: - """ - Convert asks and bids to serializable format. - :param order_book: Order book data. - """ - order_book["asks"] = [[float(ask[0]), float(ask[1])] for ask in order_book["asks"]] - order_book["bids"] = [[float(bid[0]), float(bid[1])] for bid in order_book["bids"]] - - -def get_report() -> dict: - """ - Get report for all token pairs. - :return: Report with order books data. - The report structure is as follows: - { - "empty_pairs": [STRK-ETH, ...], - "ETH-USDC": { - "is_empty": False, - "order_book": { - "asks": [[price1, amount1], [price2, amount2], ...], - "bids": [[price1, amount1], [price2, amount2], ...] - } - }, - ... - } - """ - report = {"empty_pairs": []} - all_tokens = set(TOKEN_MAPPING.keys()) - - for base_token in TOKEN_MAPPING: - current_tokens = all_tokens - {base_token} - for quote_token in current_tokens: - try: - order_book = HaikoOrderBook(base_token, quote_token) - except ValueError: - logging.log( - logging.ERROR, f"Pair of tokens: {base_token}-{quote_token}" - ) - logging.log(logging.ERROR, "One of the tokens isn't supported by Haiko") - continue - except RuntimeError as e: - logging.log( - logging.ERROR, f"Pair of tokens: {base_token}-{quote_token}" - ) - logging.log(logging.ERROR, e) - continue - - try: - base_token_name = TOKEN_MAPPING[base_token].name - quote_token_name = TOKEN_MAPPING[quote_token].name - order_book.fetch_price_and_liquidity() - token_pair = f"{base_token_name}-{quote_token_name}" - report[token_pair] = {"is_empty": False, "order_book": {}} - entry = order_book.get_order_book() - serialize_asks_bids(entry) - report[token_pair]["order_book"] = entry - if not order_book.bids and not order_book.asks: - report[token_pair]["is_empty"] = True - report["empty_pairs"].append(token_pair) - except KeyError: - order_book.logger.error(f"Pair of tokens: {base_token}-{quote_token}") - order_book.logger.error( - "One of the tokens doesn't present in tokens mapping" - ) - except RuntimeError as e: - order_book.logger.error(f"Pair of tokens: {base_token}-{quote_token}") - order_book.logger.error(e) - except Exception as e: - order_book.logger.error(f"Pair of tokens: {base_token}-{quote_token}") - order_book.logger.error(f"Unexpected error: {e}") - - return report - - -def write_report(report: dict, path: str | Path) -> None: - """ - Write report to a json file. - :param report: Report data. - :param path: Path to the file. - """ - try: - with open(path, mode="w", encoding="UTF-8") as file: - json.dump(report, file, indent=4) - except IOError as e: - print(f"Error writing report: {e}") - - -if __name__ == "__main__": - report_data = get_report() - reports_dir = Path("./reports") - reports_dir.mkdir(parents=True, exist_ok=True) - report_path = reports_dir / datetime.now().strftime("report_%Y%m%d_%H%M%S.json") - filter_logs(reports_dir) - write_report(report_data, report_path) diff --git a/apps/web_app/utils/helpers.py b/apps/web_app/utils/helpers.py deleted file mode 100644 index a94e95ec..00000000 --- a/apps/web_app/utils/helpers.py +++ /dev/null @@ -1,76 +0,0 @@ -import logging -import os -from decimal import Decimal -from typing import Iterator, Union - -import google.cloud.storage -import pandas as pd -from utils.exceptions import TokenValidationError -from utils.settings import TOKEN_SETTINGS - - -class TokenValues: - """A class that holds all token values""" - - def __init__( - self, - values: dict[str, Union[bool, Decimal]] | None = None, - init_value: Decimal = Decimal("0"), - ) -> None: - if values: - self._validate_token_values(values) - self.values: dict[str, Decimal] = values - else: - self.values: dict[str, Decimal] = { - token: init_value for token in TOKEN_SETTINGS - } - - @staticmethod - def _validate_token_values(token_values: dict[str, Union[bool, Decimal]]) -> None: - """ - Validate's token_values keys - :param token_values: dict[str, Union[bool, Decimal]] - :return: None - """ - if set(token_values.keys()) != set(TOKEN_SETTINGS.keys()): - raise TokenValidationError( - "Token values keys do not match with TOKEN_SETTINGS keys" - ) - - -MAX_ROUNDING_ERRORS: TokenValues = TokenValues( - values={ - "ETH": Decimal("0.5e13"), - "wBTC": Decimal("1e2"), - "USDC": Decimal("1e4"), - "DAI": Decimal("1e16"), - "USDT": Decimal("1e4"), - "wstETH": Decimal("0.5e13"), - "LORDS": Decimal("0.5e13"), - "STRK": Decimal("0.5e13"), - }, -) - - -class Portfolio(TokenValues): - """A class that describes holdings of tokens.""" - - MAX_ROUNDING_ERRORS: TokenValues = MAX_ROUNDING_ERRORS - - def __init__(self) -> None: - super().__init__(init_value=Decimal("0")) - - -def get_symbol(address: str) -> str: - """ - Returns the symbol of a given address. - :param address: the address of the symbol - :return: str - """ - address_int = int(address, base=16) - - for symbol, settings in TOKEN_SETTINGS.items(): - if int(settings.address, base=16) == address_int: - return symbol - - raise KeyError(f"Address = {address} does not exist in the symbol table.") diff --git a/apps/web_app/utils/settings.py b/apps/web_app/utils/settings.py deleted file mode 100644 index 4ca849aa..00000000 --- a/apps/web_app/utils/settings.py +++ /dev/null @@ -1,53 +0,0 @@ -from dataclasses import dataclass -from decimal import Decimal - - -@dataclass -class TokenSettings: - symbol: str - decimal_factor: Decimal - address: str - - -TOKEN_SETTINGS: dict[str, TokenSettings] = { - "ETH": TokenSettings( - symbol="ETH", - decimal_factor=Decimal("1e18"), - address="0x049d36570d4e46f48e99674bd3fcc84644ddd6b96f7c741b1562b82f9e004dc7", - ), - "wBTC": TokenSettings( - symbol="wBTC", - decimal_factor=Decimal("1e8"), - address="0x03fe2b97c1fd336e750087d68b9b867997fd64a2661ff3ca5a7c771641e8e7ac", - ), - "USDC": TokenSettings( - symbol="USDC", - decimal_factor=Decimal("1e6"), - address="0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8", - ), - "DAI": TokenSettings( - symbol="DAI", - decimal_factor=Decimal("1e18"), - address="0x00da114221cb83fa859dbdb4c44beeaa0bb37c7537ad5ae66fe5e0efd20e6eb3", - ), - "USDT": TokenSettings( - symbol="USDT", - decimal_factor=Decimal("1e6"), - address="0x068f5c6a61780768455de69077e07e89787839bf8166decfbf92b645209c0fb8", - ), - "wstETH": TokenSettings( - symbol="wstETH", - decimal_factor=Decimal("1e18"), - address="0x042b8f0484674ca266ac5d08e4ac6a3fe65bd3129795def2dca5c34ecc5f96d2", - ), - "LORDS": TokenSettings( - symbol="LORDS", - decimal_factor=Decimal("1e18"), - address="0x0124aeb495b947201f5fac96fd1138e326ad86195b98df6dec9009158a533b49", - ), - "STRK": TokenSettings( - symbol="STRK", - decimal_factor=Decimal("1e18"), - address="0x4718f5a0fc34cc1af16a1cdee98ffb20c31f5cd61d6ab07201858f4287c938d", - ), -} diff --git a/apps/web_app/utils/state.py b/apps/web_app/utils/state.py deleted file mode 100644 index 4c1249f8..00000000 --- a/apps/web_app/utils/state.py +++ /dev/null @@ -1,153 +0,0 @@ -from abc import ABC -from collections import defaultdict -from dataclasses import dataclass -from decimal import Decimal - -from shared.types import Portfolio, TokenValues -from .settings import TOKEN_SETTINGS as BASE_TOKEN_SETTINGS -from .settings import TokenSettings as BaseTokenSettings - - -@dataclass -class SpecificTokenSettings: - collateral_factor: Decimal - debt_factor: Decimal - - -@dataclass -class TokenSettings(SpecificTokenSettings, BaseTokenSettings): - pass - - -LOAN_ENTITY_SPECIFIC_TOKEN_SETTINGS: dict[str, SpecificTokenSettings] = { - "ETH": SpecificTokenSettings( - collateral_factor=Decimal("1"), debt_factor=Decimal("1") - ), - "wBTC": SpecificTokenSettings( - collateral_factor=Decimal("1"), debt_factor=Decimal("1") - ), - "USDC": SpecificTokenSettings( - collateral_factor=Decimal("1"), debt_factor=Decimal("1") - ), - "DAI": SpecificTokenSettings( - collateral_factor=Decimal("1"), debt_factor=Decimal("1") - ), - "USDT": SpecificTokenSettings( - collateral_factor=Decimal("1"), debt_factor=Decimal("1") - ), - "wstETH": SpecificTokenSettings( - collateral_factor=Decimal("1"), debt_factor=Decimal("1") - ), - "LORDS": SpecificTokenSettings( - collateral_factor=Decimal("1"), debt_factor=Decimal("1") - ), - "STRK": SpecificTokenSettings( - collateral_factor=Decimal("1"), debt_factor=Decimal("1") - ), -} -TOKEN_SETTINGS: dict[str, TokenSettings] = { - token: TokenSettings( - symbol=BASE_TOKEN_SETTINGS[token].symbol, - decimal_factor=BASE_TOKEN_SETTINGS[token].decimal_factor, - address=BASE_TOKEN_SETTINGS[token].address, - collateral_factor=LOAN_ENTITY_SPECIFIC_TOKEN_SETTINGS[token].collateral_factor, - debt_factor=LOAN_ENTITY_SPECIFIC_TOKEN_SETTINGS[token].debt_factor, - ) - for token in BASE_TOKEN_SETTINGS -} - - -class InterestRateModels(TokenValues): - """ - A class that describes the state of the interest rate indices which help transform face amounts into raw amounts. - Raw amount is the amount that would have been accumulated into the face amount if it were deposited at genesis. - """ - - def __init__(self) -> None: - super().__init__(init_value=Decimal("1")) - - -class LoanEntity(ABC): - """ - A class that describes and entity which can hold collateral, borrow debt and be liquidable. For example, on - Starknet, such an entity is the user in case of zkLend, Nostra Alpha and Nostra Mainnet, or an individual loan in - case od Hashstack V0 and Hashstack V1. - """ - - TOKEN_SETTINGS: dict[str, TokenSettings] = TOKEN_SETTINGS - - def __init__(self) -> None: - self.collateral: Portfolio = Portfolio() - self.debt: Portfolio = Portfolio() - - def compute_collateral_usd( - self, - risk_adjusted: bool, - collateral_interest_rate_models: InterestRateModels, - prices: TokenValues, - ) -> Decimal: - """ - Compute's collateral usd of interest - :param risk_adjusted: bool - :param collateral_interest_rate_models: InterestRateModels - :param prices: TokenValues - :return: Decimal - """ - return sum( - token_amount - / self.TOKEN_SETTINGS[token].decimal_factor - * ( - self.TOKEN_SETTINGS[token].collateral_factor - if risk_adjusted - else Decimal("1") - ) - * collateral_interest_rate_models.values[token] - * prices.values[token] - for token, token_amount in self.collateral.values.items() - ) - - def compute_debt_usd( - self, - risk_adjusted: bool, - debt_interest_rate_models: InterestRateModels, - prices: TokenValues, - ) -> Decimal: - """ - Compute's debt usd of interest - :param risk_adjusted: bool - :param debt_interest_rate_models: InterestRateModels - :param prices: TokenValues - :return: Decimal - """ - return sum( - token_amount - / self.TOKEN_SETTINGS[token].decimal_factor - / ( - self.TOKEN_SETTINGS[token].debt_factor - if risk_adjusted - else Decimal("1") - ) - * debt_interest_rate_models.values[token] - * prices.values[token] - for token, token_amount in self.debt.values.items() - ) - - -class State(ABC): - """ - A class that describes the state of all loan entities of the given lending protocol. - """ - - EVENTS_METHODS_MAPPING: dict[str, str] = {} - - def __init__( - self, - loan_entity_class: LoanEntity, - verbose_user: str | None = None, - ) -> None: - self.loan_entity_class: LoanEntity = loan_entity_class - self.verbose_user: str | None = verbose_user - self.loan_entities: defaultdict = defaultdict(self.loan_entity_class) - self.collateral_interest_rate_models: InterestRateModels = InterestRateModels() - self.debt_interest_rate_models: InterestRateModels = InterestRateModels() - self.last_block_number: int = 0 diff --git a/apps/web_app/utils/zklend.py b/apps/web_app/utils/zklend.py deleted file mode 100644 index 7f992fc8..00000000 --- a/apps/web_app/utils/zklend.py +++ /dev/null @@ -1,138 +0,0 @@ -from dataclasses import dataclass -from decimal import Decimal - -from .helpers import Portfolio, TokenValues -from .settings import TOKEN_SETTINGS as BASE_TOKEN_SETTINGS -from .settings import TokenSettings as BaseTokenSettings -from .state import InterestRateModels, LoanEntity, State -from shared.constants import ProtocolIDs - -# IT GIVES `ModuleNotFoundError` THAT'S WHY I COMMENTED OUT IT -# from data_handler.handlers.loan_states.zklend.fetch_zklend_specific_token_settings import ZKLEND_SPECIFIC_TOKEN_SETTINGS - -ADDRESS: str = "0x04c0a5193d58f74fbace4b74dcf65481e734ed1714121bdc571da345540efa05" - - -@dataclass -class ZkLendSpecificTokenSettings: - collateral_factor: Decimal - debt_factor: Decimal - liquidation_bonus: Decimal - protocol_token_address: str - - -@dataclass -class TokenSettings(ZkLendSpecificTokenSettings, BaseTokenSettings): - pass - - -# IT GIVES `ModuleNotFoundError` THAT'S WHY I COMMENTED OUT IT -# TOKEN_SETTINGS: dict[str, TokenSettings] = { -# token: TokenSettings( -# symbol=BASE_TOKEN_SETTINGS[token].symbol, -# decimal_factor=BASE_TOKEN_SETTINGS[token].decimal_factor, -# address=BASE_TOKEN_SETTINGS[token].address, -# collateral_factor=ZKLEND_SPECIFIC_TOKEN_SETTINGS[token].collateral_factor, -# debt_factor=ZKLEND_SPECIFIC_TOKEN_SETTINGS[token].debt_factor, -# liquidation_bonus=ZKLEND_SPECIFIC_TOKEN_SETTINGS[token].liquidation_bonus, -# protocol_token_address=ZKLEND_SPECIFIC_TOKEN_SETTINGS[ -# token -# ].protocol_token_address, -# ) -# for token in BASE_TOKEN_SETTINGS -# } - -# Keys are values of the "key_name" column in the database, values are the respective method names. -EVENTS_METHODS_MAPPING: dict[str, str] = { - "AccumulatorsSync": "process_accumulators_sync_event", - "zklend::market::Market::AccumulatorsSync": "process_accumulators_sync_event", - "Deposit": "process_deposit_event", - "zklend::market::Market::Deposit": "process_deposit_event", - "CollateralEnabled": "process_collateral_enabled_event", - "zklend::market::Market::CollateralEnabled": "process_collateral_enabled_event", - "CollateralDisabled": "process_collateral_disabled_event", - "zklend::market::Market::CollateralDisabled": "process_collateral_disabled_event", - "Withdrawal": "process_withdrawal_event", - "zklend::market::Market::Withdrawal": "process_withdrawal_event", - "Borrowing": "process_borrowing_event", - "zklend::market::Market::Borrowing": "process_borrowing_event", - "Repayment": "process_repayment_event", - "zklend::market::Market::Repayment": "process_repayment_event", - "Liquidation": "process_liquidation_event", - "zklend::market::Market::Liquidation": "process_liquidation_event", -} - - -class ZkLendLoanEntity(LoanEntity): - """ - A class that describes the zkLend loan entity. On top of the abstract `LoanEntity`, it implements the `deposit` and - `collateral_enabled` attributes in order to help with accounting for the changes in collateral. This is because - under zkLend, collateral is the amount deposited that is specificaly flagged with `collateral_enabled` set to True - for the given token. To properly account for the changes in collateral, we must hold the information about the - given token's deposits being enabled as collateral or not and the amount of the deposits. We keep all balances in raw - amounts. - """ - - TOKEN_SETTINGS: dict[str, TokenSettings] = ... - - def __init__(self) -> None: - super().__init__() - self.deposit: Portfolio = Portfolio() - self.collateral_enabled: TokenValues = TokenValues(init_value=False) - - def compute_health_factor( - self, - standardized: bool, - collateral_interest_rate_models: InterestRateModels | None = None, - debt_interest_rate_models: InterestRateModels | None = None, - prices: TokenValues | None = TokenValues(), - risk_adjusted_collateral_usd: Decimal | None = None, - debt_usd: Decimal | None = None, - ) -> Decimal: - """ - Compute's health ratio factor for zkLend loans. - :param standardized: InterestRateModels | None = None - :param collateral_interest_rate_models: InterestRateModels | None = None - :param debt_interest_rate_models: InterestRateModels | None = None - :param prices: TokenValues | None = None - :param risk_adjusted_collateral_usd: Decimal | None = None - :param debt_usd: Decimal | None = None - :return: Decimal - """ - if risk_adjusted_collateral_usd is None: - risk_adjusted_collateral_usd = self.compute_collateral_usd( - collateral_interest_rate_models=collateral_interest_rate_models, - prices=prices, - risk_adjusted=True, - ) - - if debt_usd is None: - debt_usd = self.compute_debt_usd( - debt_interest_rate_models=debt_interest_rate_models, - prices=prices, - risk_adjusted=False, - ) - - if debt_usd == Decimal("0"): - return Decimal("Inf") - - return risk_adjusted_collateral_usd / debt_usd - - -class ZkLendState(State): - """ - A class that describes the state of all zkLend loan entities. It implements methods for correct processing of every - relevant event. - """ - - PROTOCOL_NAME: str = ProtocolIDs.ZKLEND.value - EVENTS_METHODS_MAPPING: dict[str, str] = EVENTS_METHODS_MAPPING - - def __init__( - self, - verbose_user: str | None = None, - ) -> None: - super().__init__( - loan_entity_class=ZkLendLoanEntity, - verbose_user=verbose_user, - )