feat: add OI history deque, cold start init, and derived features to bot runtime

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
21in7
2026-03-04 20:17:37 +09:00
parent ffa6e443c1
commit 448b3e016b
4 changed files with 143 additions and 6 deletions

View File

@@ -227,6 +227,42 @@ async def test_process_candle_fetches_oi_and_funding(config, sample_df):
assert "funding_rate" in call_kwargs
def test_bot_has_oi_history_deque(config):
"""봇이 OI 히스토리 deque를 가져야 한다."""
from collections import deque
with patch("src.bot.BinanceFuturesClient"):
bot = TradingBot(config)
assert isinstance(bot._oi_history, deque)
assert bot._oi_history.maxlen == 5
@pytest.mark.asyncio
async def test_init_oi_history_fills_deque(config):
"""_init_oi_history가 deque를 채워야 한다."""
with patch("src.bot.BinanceFuturesClient"):
bot = TradingBot(config)
bot.exchange = AsyncMock()
bot.exchange.get_oi_history = AsyncMock(return_value=[0.01, -0.02, 0.03, -0.01, 0.02])
await bot._init_oi_history()
assert len(bot._oi_history) == 5
@pytest.mark.asyncio
async def test_fetch_microstructure_returns_4_tuple(config):
"""_fetch_market_microstructure가 4-tuple을 반환해야 한다."""
with patch("src.bot.BinanceFuturesClient"):
bot = TradingBot(config)
bot.exchange = AsyncMock()
bot.exchange.get_open_interest = AsyncMock(return_value=5000000.0)
bot.exchange.get_funding_rate = AsyncMock(return_value=0.0001)
bot._prev_oi = 4900000.0
bot._oi_history.extend([0.01, -0.02, 0.03, -0.01])
bot._latest_ret_1 = 0.01
result = await bot._fetch_market_microstructure()
assert len(result) == 4
def test_calc_oi_change_first_candle_returns_zero(config):
"""첫 캔들은 0.0을 반환하고 _prev_oi를 설정한다."""
with patch("src.bot.BinanceFuturesClient"):

View File

@@ -113,3 +113,43 @@ async def test_get_funding_rate_error_returns_none(exchange):
)
result = await exchange.get_funding_rate()
assert result is None
@pytest.mark.asyncio
async def test_get_oi_history_returns_changes(exchange):
"""get_oi_history()가 OI 변화율 리스트를 반환하는지 확인."""
exchange.client.futures_open_interest_hist = MagicMock(
return_value=[
{"sumOpenInterest": "1000000"},
{"sumOpenInterest": "1010000"},
{"sumOpenInterest": "1005000"},
{"sumOpenInterest": "1020000"},
{"sumOpenInterest": "1015000"},
{"sumOpenInterest": "1030000"},
]
)
result = await exchange.get_oi_history(limit=5)
assert len(result) == 5
assert isinstance(result[0], float)
# 첫 번째 변화율: (1010000 - 1000000) / 1000000 = 0.01
assert abs(result[0] - 0.01) < 1e-6
@pytest.mark.asyncio
async def test_get_oi_history_error_returns_empty(exchange):
"""API 오류 시 빈 리스트 반환 확인."""
exchange.client.futures_open_interest_hist = MagicMock(
side_effect=Exception("API error")
)
result = await exchange.get_oi_history(limit=5)
assert result == []
@pytest.mark.asyncio
async def test_get_oi_history_insufficient_data_returns_empty(exchange):
"""데이터가 부족하면 빈 리스트 반환 확인."""
exchange.client.futures_open_interest_hist = MagicMock(
return_value=[{"sumOpenInterest": "1000000"}]
)
result = await exchange.get_oi_history(limit=5)
assert result == []