Skip to content

Commit

Permalink
Merge branch 'master' into feat/build-top-loans-chart
Browse files Browse the repository at this point in the history
  • Loading branch information
iamnovichek authored Jan 26, 2025
2 parents 545522e + f28c0ec commit 047e27c
Show file tree
Hide file tree
Showing 24 changed files with 1,462 additions and 4 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,4 @@ __pycache__
.vscode
.env
storage_credentials.json
env/
2 changes: 1 addition & 1 deletion apps/data_handler/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ WORKDIR /code
COPY data_handler/poetry.lock data_handler/pyproject.toml /code/
COPY data_handler/entrypoint.sh /code/entrypoint.sh

RUN pip install poetry \
RUN curl -sSL https://install.python-poetry.org | POETRY_HOME=/usr/local/ POETRY_VERSION=1.8.3 python3 -\
&& poetry config virtualenvs.create false \
&& poetry install --no-dev

Expand Down
1 change: 0 additions & 1 deletion apps/data_handler/handlers/loan_states/zklend/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -320,7 +320,6 @@ def process_withdrawal_event(self, event: pd.Series) -> None:

# Update the user's deposit and collateral values
self.loan_entities[user].deposit.increase_value(token=token, value=-raw_amount)
self.loan_entities[user].deposit.increase_value(token=token, value=-raw_amount)

if self.loan_entities[user].collateral_enabled[token]:
self.loan_entities[user].collateral.increase_value(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,7 +118,7 @@ def test_process_withdrawal_event(self, zklend_state, sample_event, mock_portfol
mock_parsed_data.amount = int(1e18)

with patch(
"data_handler.handler_tools.data_parser.zklend.ZklendDataParser.parse_withdrawal_event",
"data_handler.handlers.loan_states.zklend.events.ZklendDataParser.parse_withdrawal_event",
return_value=mock_parsed_data,
):
user = mock_parsed_data.user
Expand All @@ -130,7 +130,6 @@ def test_process_withdrawal_event(self, zklend_state, sample_event, mock_portfol
loan_entity.extra_info = MagicMock()

zklend_state.loan_entities[user] = loan_entity

zklend_state.process_withdrawal_event(sample_event)

mock_portfolio.increase_value.assert_called()
Expand Down
Empty file.
162 changes: 162 additions & 0 deletions apps/data_handler/tests/order_book/test_ekubo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
"""Tests for the EkuboOrderBook class."""
from decimal import Decimal
from unittest.mock import Mock, patch, MagicMock

import pandas as pd
import pytest
from data_handler.handlers.order_books.ekubo.main import EkuboOrderBook

@pytest.fixture
def mock_connector():
"""Create a mock EkuboAPIConnector."""
with patch('data_handler.handlers.order_books.ekubo.main.EkuboAPIConnector') as mock:
connector = mock.return_value
connector.get_pair_price.return_value = {"price": "1.5"}
connector.get_pools.return_value = [{
"token0": "0x1",
"token1": "0x2",
"key_hash": "0xabc",
"liquidity": "1000000",
"lastUpdate": {"event_id": "123"},
"tick": 100,
"tick_spacing": 10
}]
connector.get_pool_liquidity.return_value = {
"data": [
{"tick": 90, "net_liquidity_delta_diff": "100"},
{"tick": 100, "net_liquidity_delta_diff": "200"},
{"tick": 110, "net_liquidity_delta_diff": "300"}
]
}
yield connector

@pytest.fixture
def order_book(mock_connector):
"""Create an EkuboOrderBook instance with mocked connector."""
book = EkuboOrderBook("0x1", "0x2")
# Set decimals for token precision
book.token_a_decimal = 18
book.token_b_decimal = 18
return book

def test_initialization(order_book):
"""Test EkuboOrderBook initialization."""
assert order_book.DEX == "Ekubo"
assert order_book.token_a == "0x1"
assert order_book.token_b == "0x2"
assert order_book.asks == []
assert order_book.bids == []

def test_set_current_price(order_book):
"""Test setting current price."""
order_book.set_current_price()
assert order_book.current_price == Decimal("1.5")

def test_fetch_price_and_liquidity(order_book):
"""Test fetching price and liquidity data."""
order_book.fetch_price_and_liquidity()
assert order_book.block == "123"
assert len(order_book.asks) > 0
assert len(order_book.bids) > 0

def test_calculate_order_book(order_book):
"""Test order book calculation."""
# Create test data with ticks both above and below the current tick (100)
liquidity_data = [
{"tick": 90, "net_liquidity_delta_diff": "100"},
{"tick": 95, "net_liquidity_delta_diff": "150"},
{"tick": 100, "net_liquidity_delta_diff": "200"},
{"tick": 105, "net_liquidity_delta_diff": "250"},
{"tick": 110, "net_liquidity_delta_diff": "300"}
]

# Create a pandas Series for the row
row_data = {
"tick": 100,
"tick_spacing": 5,
"liquidity": "1000000"
}
row = pd.Series(row_data)

# Set current price for price range calculation
order_book.current_price = Decimal("1.5")

# Calculate order book
order_book._calculate_order_book(liquidity_data, 1000000, row)

# Verify that both asks and bids are populated
assert len(order_book.asks) > 0, "Asks should not be empty"
assert len(order_book.bids) > 0, "Bids should not be empty"

# Verify the structure of asks and bids
for price, supply in order_book.asks:
assert isinstance(price, Decimal), "Price should be Decimal"
assert isinstance(supply, Decimal), "Supply should be Decimal"
assert price > 0, "Price should be positive"
assert supply > 0, "Supply should be positive"

for price, supply in order_book.bids:
assert isinstance(price, Decimal), "Price should be Decimal"
assert isinstance(supply, Decimal), "Supply should be Decimal"
assert price > 0, "Price should be positive"
assert supply > 0, "Supply should be positive"

def test_get_pure_sqrt_ratio(order_book):
"""Test square root ratio calculation."""
result = order_book._get_pure_sqrt_ratio(Decimal("1"))
assert isinstance(result, Decimal)
assert result > 0

def test_sort_ticks_by_asks_and_bids():
"""Test sorting ticks into asks and bids."""
liquidity_data = [
{"tick": 90},
{"tick": 100},
{"tick": 110}
]
current_tick = 100
asks, bids = EkuboOrderBook.sort_ticks_by_asks_and_bids(liquidity_data, current_tick)
assert len(asks) == 1
assert len(bids) == 2
assert all(tick["tick"] > current_tick for tick in asks)
assert all(tick["tick"] <= current_tick for tick in bids)

def test_calculate_liquidity_amount(order_book):
"""Test liquidity amount calculation."""
# Set token decimals for proper calculation
order_book.token_a_decimal = 18
order_book.token_b_decimal = 18

tick = Decimal("100")
liquidity_total = Decimal("1000000")
result = order_book.calculate_liquidity_amount(tick, liquidity_total)
assert isinstance(result, Decimal)
assert result > 0

@pytest.mark.parametrize("tick,expected_sign", [
(1000, 1), # positive tick
(-1000, -1), # negative tick
(0, 0) # zero tick
])
def test_tick_edge_cases(order_book, tick, expected_sign):
"""Test tick calculations with edge cases."""
result = order_book._get_pure_sqrt_ratio(Decimal(tick))
assert isinstance(result, Decimal)
assert result > 0 # sqrt ratio should always be positive
if expected_sign > 0:
assert result > 1
elif expected_sign < 0:
assert result < 1

def test_empty_liquidity_data(order_book):
"""Test behavior with empty liquidity data."""
row_data = {
"tick": 100,
"tick_spacing": 5,
"liquidity": "1000000"
}
row = pd.Series(row_data)

order_book._calculate_order_book([], 1000000, row)
assert len(order_book.asks) == 0
assert len(order_book.bids) == 0
Empty file.
109 changes: 109 additions & 0 deletions apps/data_handler/tests/order_book/test_myswap.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
""" Test Cases for MySwap"""

import asyncio
import math
from decimal import Decimal
from unittest.mock import AsyncMock, Mock, patch

import pytest

from ...handlers.order_books.myswap.main import MySwapOrderBook

MAX_MYSWAP_TICK = Decimal("1774532")


@pytest.fixture
def order_book():
"""Mocks"""
obj = MySwapOrderBook(
base_token="0x12345", quote_token="0x67890", apply_filtering=True
)
obj.base_token = "0x12345"
obj.quote_token = "0x67890"
return obj


def test_order_book_initialization(order_book):
"""Test that the order book is initialized correctly."""
assert order_book.base_token == "0x12345"
assert order_book.quote_token == "0x67890"
assert order_book.apply_filtering is True


def test_price_to_tick(order_book):
"""Test conversion of price to tick."""
order_book._decimals_diff = Decimal(1)
price = Decimal("1.0001")
tick = order_book._price_to_tick(price)
expected_tick = (
round(
Decimal(math.log(price / (Decimal(2**128) * order_book._decimals_diff)))
/ Decimal(math.log(Decimal("1.0001")))
)
+ MAX_MYSWAP_TICK
)
assert tick == expected_tick


@patch.object(MySwapOrderBook, "_get_clean_addresses", return_value=("0x123", "0x456"))
def test_filter_pools_data(mock_get_clean_addresses, order_book):
"""Test that the pool data is correctly filtered."""
all_pools = {
"pools": [
{"token0": {"address": "0x123"}, "token1": {"address": "0x456"}},
{"token0": {"address": "0x999"}, "token1": {"address": "0x888"}},
]
}
filtered = order_book._filter_pools_data(all_pools)
assert len(filtered) == 1
assert filtered[0]["token0"]["address"] == "0x123"


@patch.object(
MySwapOrderBook, "_filter_pools_data", return_value=[{"poolkey": "test_pool"}]
)
@patch.object(MySwapOrderBook, "_calculate_order_book", new_callable=AsyncMock)
def test_async_fetch_price_and_liquidity(
mock_calculate_order_book, mock_filter_pools, order_book
):
"""Test the async fetching of price and liquidity."""
order_book.connector = Mock()
order_book.connector.get_pools_data = Mock(return_value={"pools": []})

async def run_test():
await order_book._async_fetch_price_and_liquidity()

asyncio.run(run_test())
mock_calculate_order_book.assert_awaited_once_with("test_pool")


@patch.object(MySwapOrderBook, "_get_clean_addresses", return_value=("0x123", "0x456"))
def test_filter_pools_data_no_match(mock_get_clean_addresses, order_book):
"""Test filtering when no pools match."""
all_pools = {
"pools": [
{"token0": {"address": "0x999"}, "token1": {"address": "0x888"}},
]
}
filtered = order_book._filter_pools_data(all_pools)
assert len(filtered) == 0


def test_price_to_tick_invalid(order_book):
"""Test invalid price leading to ValueError."""
order_book._decimals_diff = Decimal(1)
with pytest.raises(ValueError):
order_book._price_to_tick(Decimal(0))


@patch.object(MySwapOrderBook, "_filter_pools_data", return_value=[])
def test_async_fetch_price_and_liquidity_no_pools(mock_filter_pools, order_book):
"""Test the async fetching when no pools are available."""
order_book.connector = Mock()
order_book.connector.get_pools_data = Mock(return_value={"pools": []})

async def run_test():
await order_book._async_fetch_price_and_liquidity()

asyncio.run(run_test())
mock_filter_pools.assert_called_once()
Loading

0 comments on commit 047e27c

Please sign in to comment.