143 lines
4.4 KiB
Python
143 lines
4.4 KiB
Python
"""
|
|
OHLCClient - High-level API for fetching OHLC data with smart caching
|
|
"""
|
|
|
|
import asyncio
|
|
import pandas as pd
|
|
from typing import Optional
|
|
from .iceberg_client import IcebergClient
|
|
from .history_client import HistoryClient
|
|
|
|
|
|
class OHLCClient:
|
|
"""
|
|
High-level client for fetching OHLC data.
|
|
|
|
Workflow:
|
|
1. Check Iceberg for existing data
|
|
2. Identify missing ranges
|
|
3. Request missing data via relay
|
|
4. Wait for notification
|
|
5. Query Iceberg for complete dataset
|
|
6. Return combined results
|
|
|
|
This provides transparent caching - clients don't need to know
|
|
whether data came from cache or was fetched on-demand.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
iceberg_catalog_uri: str,
|
|
relay_endpoint: str,
|
|
notification_endpoint: str,
|
|
namespace: str = "trading",
|
|
s3_endpoint: str = None,
|
|
s3_access_key: str = None,
|
|
s3_secret_key: str = None,
|
|
):
|
|
"""
|
|
Initialize OHLC client.
|
|
|
|
Args:
|
|
iceberg_catalog_uri: URI of Iceberg catalog
|
|
relay_endpoint: ZMQ endpoint for relay requests
|
|
notification_endpoint: ZMQ endpoint for notifications
|
|
namespace: Iceberg namespace (default: "trading")
|
|
s3_endpoint: S3/MinIO endpoint URL (e.g., "http://localhost:9000")
|
|
s3_access_key: S3/MinIO access key
|
|
s3_secret_key: S3/MinIO secret key
|
|
"""
|
|
self.iceberg = IcebergClient(
|
|
iceberg_catalog_uri, namespace,
|
|
s3_endpoint=s3_endpoint,
|
|
s3_access_key=s3_access_key,
|
|
s3_secret_key=s3_secret_key,
|
|
)
|
|
self.history = HistoryClient(relay_endpoint, notification_endpoint)
|
|
|
|
async def start(self):
|
|
"""
|
|
Start the client. Must be called before making requests.
|
|
Starts background notification listener.
|
|
"""
|
|
await self.history.connect()
|
|
|
|
async def stop(self):
|
|
"""
|
|
Stop the client and cleanup resources.
|
|
"""
|
|
await self.history.close()
|
|
|
|
async def fetch_ohlc(
|
|
self,
|
|
ticker: str,
|
|
period_seconds: int,
|
|
start_time: int,
|
|
end_time: int,
|
|
request_timeout: float = 30.0
|
|
) -> pd.DataFrame:
|
|
"""
|
|
Fetch OHLC data with smart caching.
|
|
|
|
Steps:
|
|
1. Query Iceberg for existing data
|
|
2. If complete, return immediately
|
|
3. If missing data, request via relay
|
|
4. Wait for completion notification
|
|
5. Query Iceberg again for complete dataset
|
|
6. Return results
|
|
|
|
Args:
|
|
ticker: Market identifier (e.g., "BINANCE:BTC/USDT")
|
|
period_seconds: OHLC period in seconds (60, 300, 3600, etc.)
|
|
start_time: Start timestamp in microseconds
|
|
end_time: End timestamp in microseconds
|
|
request_timeout: Timeout for historical data requests (default: 30s)
|
|
|
|
Returns:
|
|
DataFrame with OHLC data sorted by timestamp
|
|
|
|
Raises:
|
|
TimeoutError: If historical data request times out
|
|
ValueError: If request fails
|
|
"""
|
|
# Step 1: Check Iceberg for existing data
|
|
df = self.iceberg.query_ohlc(ticker, period_seconds, start_time, end_time)
|
|
|
|
# Step 2: Identify missing ranges
|
|
missing_ranges = self.iceberg.find_missing_ranges(
|
|
ticker, period_seconds, start_time, end_time
|
|
)
|
|
|
|
if not missing_ranges:
|
|
# All data exists in Iceberg
|
|
return df
|
|
|
|
# Step 3: Request missing data for each range
|
|
# For simplicity, request entire range (relay can merge adjacent requests)
|
|
result = await self.history.request_historical_ohlc(
|
|
ticker=ticker,
|
|
period_seconds=period_seconds,
|
|
start_time=start_time,
|
|
end_time=end_time,
|
|
timeout=request_timeout
|
|
)
|
|
|
|
# Step 4: Check result status
|
|
if result['status'] == 'ERROR':
|
|
raise ValueError(f"Historical data request failed: {result['error_message']}")
|
|
|
|
# Step 5: Query Iceberg again for complete dataset
|
|
df = self.iceberg.query_ohlc(ticker, period_seconds, start_time, end_time)
|
|
|
|
return df
|
|
|
|
async def __aenter__(self):
|
|
"""Support async context manager."""
|
|
await self.start()
|
|
return self
|
|
|
|
async def __aexit__(self, exc_type, exc_val, exc_tb):
|
|
"""Support async context manager."""
|
|
await self.stop()
|