feat: 봇 시작 시 과거 캔들 200개 프리로드 (즉시 신호 계산 가능)
Made-with: Cursor
This commit is contained in:
@@ -45,11 +45,37 @@ class KlineStream:
|
|||||||
df.set_index("timestamp", inplace=True)
|
df.set_index("timestamp", inplace=True)
|
||||||
return df
|
return df
|
||||||
|
|
||||||
|
async def _preload_history(self, client: AsyncClient, limit: int = 200):
|
||||||
|
"""REST API로 과거 캔들 데이터를 버퍼에 미리 채운다."""
|
||||||
|
logger.info(f"과거 캔들 {limit}개 로드 중...")
|
||||||
|
loop = asyncio.get_event_loop()
|
||||||
|
klines = await loop.run_in_executor(
|
||||||
|
None,
|
||||||
|
lambda: client.futures_klines(
|
||||||
|
symbol=self.symbol.upper(),
|
||||||
|
interval=self.interval,
|
||||||
|
limit=limit,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
# 마지막 캔들은 아직 닫히지 않았을 수 있으므로 제외
|
||||||
|
for k in klines[:-1]:
|
||||||
|
self.buffer.append({
|
||||||
|
"timestamp": k[0],
|
||||||
|
"open": float(k[1]),
|
||||||
|
"high": float(k[2]),
|
||||||
|
"low": float(k[3]),
|
||||||
|
"close": float(k[4]),
|
||||||
|
"volume": float(k[5]),
|
||||||
|
"is_closed": True,
|
||||||
|
})
|
||||||
|
logger.info(f"과거 캔들 {len(self.buffer)}개 로드 완료 — 즉시 신호 계산 가능")
|
||||||
|
|
||||||
async def start(self, api_key: str, api_secret: str):
|
async def start(self, api_key: str, api_secret: str):
|
||||||
client = await AsyncClient.create(
|
client = await AsyncClient.create(
|
||||||
api_key=api_key,
|
api_key=api_key,
|
||||||
api_secret=api_secret,
|
api_secret=api_secret,
|
||||||
)
|
)
|
||||||
|
await self._preload_history(client)
|
||||||
bm = BinanceSocketManager(client)
|
bm = BinanceSocketManager(client)
|
||||||
stream_name = f"{self.symbol}@kline_{self.interval}"
|
stream_name = f"{self.symbol}@kline_{self.interval}"
|
||||||
logger.info(f"WebSocket 스트림 시작: {stream_name}")
|
logger.info(f"WebSocket 스트림 시작: {stream_name}")
|
||||||
|
|||||||
@@ -44,3 +44,23 @@ async def test_callback_called_on_closed_candle():
|
|||||||
}
|
}
|
||||||
stream.handle_message(raw_msg)
|
stream.handle_message(raw_msg)
|
||||||
assert len(received) == 1
|
assert len(received) == 1
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_preload_history_fills_buffer():
|
||||||
|
stream = KlineStream(symbol="XRPUSDT", interval="1m", buffer_size=200)
|
||||||
|
|
||||||
|
# REST API 응답 형식: [open_time, open, high, low, close, volume, ...]
|
||||||
|
fake_klines = [
|
||||||
|
[1700000000000 + i * 60000, "0.5", "0.51", "0.49", "0.505", "100000",
|
||||||
|
0, "0", "0", "0", "0", "0"]
|
||||||
|
for i in range(201) # 201개 반환 → 마지막 1개 제외 → 200개 버퍼
|
||||||
|
]
|
||||||
|
|
||||||
|
mock_client = MagicMock()
|
||||||
|
mock_client.futures_klines.return_value = fake_klines
|
||||||
|
|
||||||
|
await stream._preload_history(mock_client, limit=200)
|
||||||
|
|
||||||
|
assert len(stream.buffer) == 200
|
||||||
|
assert stream.get_dataframe() is not None
|
||||||
|
|||||||
Reference in New Issue
Block a user