Files
ai/client-py/dexorder/ohlc_client.py
2026-03-11 18:47:11 -04:00

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()