116 lines
3.7 KiB
Python
116 lines
3.7 KiB
Python
import pytest
|
|
from unittest.mock import AsyncMock, MagicMock, patch
|
|
from src.exchange import BinanceFuturesClient
|
|
from src.config import Config
|
|
import os
|
|
|
|
|
|
@pytest.fixture
|
|
def config():
|
|
os.environ.update({
|
|
"BINANCE_API_KEY": "test_key",
|
|
"BINANCE_API_SECRET": "test_secret",
|
|
"SYMBOL": "XRPUSDT",
|
|
"LEVERAGE": "10",
|
|
})
|
|
return Config()
|
|
|
|
|
|
@pytest.fixture
|
|
def client():
|
|
config = Config()
|
|
config.leverage = 10
|
|
c = BinanceFuturesClient.__new__(BinanceFuturesClient)
|
|
c.config = config
|
|
return c
|
|
|
|
|
|
@pytest.fixture
|
|
def exchange():
|
|
os.environ.update({
|
|
"BINANCE_API_KEY": "test_key",
|
|
"BINANCE_API_SECRET": "test_secret",
|
|
"SYMBOL": "XRPUSDT",
|
|
"LEVERAGE": "10",
|
|
})
|
|
config = Config()
|
|
c = BinanceFuturesClient.__new__(BinanceFuturesClient)
|
|
c.config = config
|
|
c.client = MagicMock()
|
|
return c
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_set_leverage(config):
|
|
with patch("src.exchange.Client") as MockClient:
|
|
mock_binance = MagicMock()
|
|
MockClient.return_value = mock_binance
|
|
mock_binance.futures_change_leverage.return_value = {"leverage": 10}
|
|
client = BinanceFuturesClient(config)
|
|
result = await client.set_leverage(10)
|
|
assert result is not None
|
|
|
|
|
|
def test_calculate_quantity_basic(client):
|
|
"""잔고 22, 비율 50%, 레버리지 10배 → 명목금액 110, XRP 가격 2.5 → 수량 44.0"""
|
|
qty = client.calculate_quantity(balance=22.0, price=2.5, leverage=10, margin_ratio=0.50)
|
|
# 명목금액 = 22 * 0.5 * 10 = 110, 수량 = 110 / 2.5 = 44.0
|
|
assert qty == pytest.approx(44.0, abs=0.1)
|
|
|
|
|
|
def test_calculate_quantity_min_notional(client):
|
|
"""명목금액이 최소(5 USDT) 미만이면 최소값으로 올림"""
|
|
qty = client.calculate_quantity(balance=1.0, price=2.5, leverage=1, margin_ratio=0.01)
|
|
# 명목금액 = 1 * 0.01 * 1 = 0.01 < 5 → 최소 5 USDT
|
|
assert qty * 2.5 >= 5.0
|
|
|
|
|
|
def test_calculate_quantity_zero_balance(client):
|
|
"""잔고 0이면 최소 명목금액 기반 수량 반환"""
|
|
qty = client.calculate_quantity(balance=0.0, price=2.5, leverage=10, margin_ratio=0.50)
|
|
assert qty > 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_open_interest(exchange):
|
|
"""get_open_interest()가 float을 반환하는지 확인."""
|
|
exchange.client.futures_open_interest = MagicMock(
|
|
return_value={"openInterest": "123456.789"}
|
|
)
|
|
result = await exchange.get_open_interest()
|
|
assert isinstance(result, float)
|
|
assert result == pytest.approx(123456.789)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_funding_rate(exchange):
|
|
"""get_funding_rate()가 float을 반환하는지 확인."""
|
|
exchange.client.futures_mark_price = MagicMock(
|
|
return_value={"lastFundingRate": "0.0001"}
|
|
)
|
|
result = await exchange.get_funding_rate()
|
|
assert isinstance(result, float)
|
|
assert result == pytest.approx(0.0001)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_open_interest_error_returns_none(exchange):
|
|
"""API 오류 시 None 반환 확인."""
|
|
from binance.exceptions import BinanceAPIException
|
|
exchange.client.futures_open_interest = MagicMock(
|
|
side_effect=BinanceAPIException(MagicMock(status_code=400), 400, '{"code":-1121,"msg":"Invalid symbol"}')
|
|
)
|
|
result = await exchange.get_open_interest()
|
|
assert result is None
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_get_funding_rate_error_returns_none(exchange):
|
|
"""API 오류 시 None 반환 확인."""
|
|
from binance.exceptions import BinanceAPIException
|
|
exchange.client.futures_mark_price = MagicMock(
|
|
side_effect=BinanceAPIException(MagicMock(status_code=400), 400, '{"code":-1121,"msg":"Invalid symbol"}')
|
|
)
|
|
result = await exchange.get_funding_rate()
|
|
assert result is None
|