fix(hyperliquid): page+trim get_historical past 5000-candle cap
candleSnapshot caps each response at ~5000 candles and keeps the newest slice, silently dropping older candles when the range needs more — so a wide start_date was not honored. Mirror Deribit's approach: page the range in windows of interval_ms * 5000, trim each page back to [start_ms, end_ms], dedupe by timestamp, with a 500-page hard stop. Resolutions without a known interval fall back to the original single-call path. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -1,11 +1,18 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
import re
|
||||
|
||||
import pytest
|
||||
from cerbero_mcp.exchanges.hyperliquid.client import HyperliquidClient
|
||||
from pytest_httpx import HTTPXMock
|
||||
|
||||
from cerbero_mcp.exchanges.hyperliquid.client import (
|
||||
_MAX_CANDLES_PER_REQUEST,
|
||||
_RESOLUTION_MS,
|
||||
HyperliquidClient,
|
||||
_to_ms,
|
||||
)
|
||||
|
||||
# Chiave privata fissa: rende deterministica la firma EIP-712 per i test write.
|
||||
DUMMY_PRIVATE_KEY = "0x" + "01" * 32
|
||||
DUMMY_WALLET = "0x1a642f0E3c3aF545E7AcBD38b07251B3990914F1" # derived from key above
|
||||
@@ -201,10 +208,11 @@ async def test_get_open_orders(httpx_mock: HTTPXMock, client: HyperliquidClient)
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_historical(httpx_mock: HTTPXMock, client: HyperliquidClient):
|
||||
ts = _to_ms("2024-01-01") + _RESOLUTION_MS["1h"] # one candle inside the range
|
||||
httpx_mock.add_response(
|
||||
url=re.compile(r"https://api\.hyperliquid-testnet\.xyz/info"),
|
||||
json=[
|
||||
{"t": 1000000, "o": "49000", "h": "51000", "l": "48500", "c": "50000", "v": "100"},
|
||||
{"t": ts, "o": "49000", "h": "51000", "l": "48500", "c": "50000", "v": "100"},
|
||||
],
|
||||
)
|
||||
result = await client.get_historical("BTC", "2024-01-01", "2024-01-02", "1h")
|
||||
@@ -212,6 +220,57 @@ async def test_get_historical(httpx_mock: HTTPXMock, client: HyperliquidClient):
|
||||
assert result["candles"][0]["close"] == 50000.0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_historical_trims_out_of_range(
|
||||
httpx_mock: HTTPXMock, client: HyperliquidClient
|
||||
):
|
||||
# candleSnapshot can return candles outside the requested window; trim them.
|
||||
in_range = _to_ms("2024-01-01") + _RESOLUTION_MS["1h"]
|
||||
before = _to_ms("2024-01-01") - _RESOLUTION_MS["1h"]
|
||||
httpx_mock.add_response(
|
||||
url=re.compile(r"https://api\.hyperliquid-testnet\.xyz/info"),
|
||||
json=[
|
||||
{"t": before, "o": "1", "h": "1", "l": "1", "c": "1", "v": "1"},
|
||||
{"t": in_range, "o": "49000", "h": "51000", "l": "48500", "c": "50000",
|
||||
"v": "100"},
|
||||
],
|
||||
)
|
||||
result = await client.get_historical("BTC", "2024-01-01", "2024-01-02", "1h")
|
||||
assert [c["timestamp"] for c in result["candles"]] == [in_range]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_get_historical_pages_wide_range(
|
||||
httpx_mock: HTTPXMock, client: HyperliquidClient
|
||||
):
|
||||
# A span wider than the per-call cap must be paged across multiple requests.
|
||||
interval_ms = _RESOLUTION_MS["1h"]
|
||||
window_span = interval_ms * _MAX_CANDLES_PER_REQUEST
|
||||
start_ms = _to_ms("2024-01-01")
|
||||
page1_ts = start_ms + interval_ms
|
||||
page2_ts = start_ms + window_span # start of the second window
|
||||
httpx_mock.add_response(
|
||||
url=re.compile(r"https://api\.hyperliquid-testnet\.xyz/info"),
|
||||
json=[
|
||||
{"t": page1_ts, "o": "100", "h": "110", "l": "90", "c": "105", "v": "1"},
|
||||
],
|
||||
)
|
||||
httpx_mock.add_response(
|
||||
url=re.compile(r"https://api\.hyperliquid-testnet\.xyz/info"),
|
||||
json=[
|
||||
{"t": page2_ts, "o": "200", "h": "210", "l": "190", "c": "205", "v": "1"},
|
||||
],
|
||||
)
|
||||
# 2024 is a leap year (366 d = 8784 h > 5000), so the range spans 2 windows.
|
||||
result = await client.get_historical("BTC", "2024-01-01", "2025-01-01", "1h")
|
||||
|
||||
requests = httpx_mock.get_requests()
|
||||
assert len(requests) == 2
|
||||
starts = [json.loads(r.read())["req"]["startTime"] for r in requests]
|
||||
assert starts[1] > starts[0] # paging advanced the cursor
|
||||
assert [c["timestamp"] for c in result["candles"]] == [page1_ts, page2_ts]
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_health_ok(httpx_mock: HTTPXMock, client: HyperliquidClient):
|
||||
httpx_mock.add_response(
|
||||
|
||||
Reference in New Issue
Block a user