154 lines
4.5 KiB
Python
Executable File
154 lines
4.5 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
"""Utility script to fetch and store historical OHLCV data from Binance public API.
|
|
|
|
This script uses the PublicDataAdapter to fetch historical market data without
|
|
requiring API keys. Perfect for populating your database with historical data
|
|
for backtesting and analysis.
|
|
|
|
Usage:
|
|
python scripts/fetch_historical_data.py --symbol BTC/USDT --timeframe 1h --days 30
|
|
python scripts/fetch_historical_data.py --symbol ETH/USDT --timeframe 1d --days 365
|
|
"""
|
|
|
|
import sys
|
|
import argparse
|
|
from datetime import datetime, timedelta
|
|
from pathlib import Path
|
|
|
|
# Add src to path
|
|
sys.path.insert(0, str(Path(__file__).parent.parent))
|
|
|
|
from src.exchanges.public_data import PublicDataAdapter
|
|
from src.data.collector import get_data_collector
|
|
from src.core.logger import setup_logging, get_logger
|
|
|
|
setup_logging()
|
|
logger = get_logger(__name__)
|
|
|
|
|
|
def fetch_historical_data(
|
|
symbol: str,
|
|
timeframe: str,
|
|
days: int,
|
|
exchange_name: str = "Binance Public"
|
|
) -> int:
|
|
"""Fetch and store historical OHLCV data.
|
|
|
|
Args:
|
|
symbol: Trading symbol (e.g., 'BTC/USDT')
|
|
timeframe: Timeframe (e.g., '1h', '1d', '4h')
|
|
days: Number of days of historical data to fetch
|
|
exchange_name: Exchange name for storage
|
|
|
|
Returns:
|
|
Number of candles stored
|
|
"""
|
|
logger.info(f"Fetching {days} days of {timeframe} data for {symbol}")
|
|
|
|
# Create public data adapter
|
|
adapter = PublicDataAdapter()
|
|
if not adapter.connect():
|
|
logger.error("Failed to connect to Binance public API")
|
|
return 0
|
|
|
|
# Calculate start date
|
|
end_date = datetime.utcnow()
|
|
start_date = end_date - timedelta(days=days)
|
|
|
|
# Fetch data in chunks (Binance limit is 1000 candles per request)
|
|
collector = get_data_collector()
|
|
total_candles = 0
|
|
|
|
current_date = start_date
|
|
chunk_days = 30 # Fetch 30 days at a time to stay under 1000 candle limit
|
|
|
|
while current_date < end_date:
|
|
chunk_end = min(current_date + timedelta(days=chunk_days), end_date)
|
|
|
|
logger.info(f"Fetching data from {current_date.date()} to {chunk_end.date()}")
|
|
|
|
# Fetch OHLCV data
|
|
ohlcv = adapter.get_ohlcv(
|
|
symbol=symbol,
|
|
timeframe=timeframe,
|
|
since=current_date,
|
|
limit=1000
|
|
)
|
|
|
|
if ohlcv:
|
|
# Store in database
|
|
collector.store_ohlcv(exchange_name, symbol, timeframe, ohlcv)
|
|
total_candles += len(ohlcv)
|
|
logger.info(f"Stored {len(ohlcv)} candles (total: {total_candles})")
|
|
else:
|
|
logger.warning(f"No data returned for period {current_date} to {chunk_end}")
|
|
|
|
# Move to next chunk
|
|
current_date = chunk_end
|
|
|
|
# Small delay to respect rate limits
|
|
import time
|
|
time.sleep(1)
|
|
|
|
adapter.disconnect()
|
|
logger.info(f"Completed! Total candles stored: {total_candles}")
|
|
return total_candles
|
|
|
|
|
|
def main():
|
|
"""Main entry point."""
|
|
parser = argparse.ArgumentParser(
|
|
description="Fetch historical OHLCV data from Binance public API"
|
|
)
|
|
parser.add_argument(
|
|
'--symbol',
|
|
type=str,
|
|
default='BTC/USDT',
|
|
help='Trading symbol (e.g., BTC/USDT, ETH/USDT)'
|
|
)
|
|
parser.add_argument(
|
|
'--timeframe',
|
|
type=str,
|
|
default='1h',
|
|
choices=['1m', '5m', '15m', '30m', '1h', '4h', '1d', '1w'],
|
|
help='Timeframe for candles'
|
|
)
|
|
parser.add_argument(
|
|
'--days',
|
|
type=int,
|
|
default=30,
|
|
help='Number of days of historical data to fetch'
|
|
)
|
|
parser.add_argument(
|
|
'--exchange',
|
|
type=str,
|
|
default='Binance Public',
|
|
help='Exchange name for storage (default: Binance Public)'
|
|
)
|
|
|
|
args = parser.parse_args()
|
|
|
|
try:
|
|
count = fetch_historical_data(
|
|
symbol=args.symbol,
|
|
timeframe=args.timeframe,
|
|
days=args.days,
|
|
exchange_name=args.exchange
|
|
)
|
|
print(f"\n✓ Successfully fetched and stored {count} candles")
|
|
print(f" Symbol: {args.symbol}")
|
|
print(f" Timeframe: {args.timeframe}")
|
|
print(f" Period: {args.days} days")
|
|
return 0
|
|
except KeyboardInterrupt:
|
|
print("\n\nInterrupted by user")
|
|
return 1
|
|
except Exception as e:
|
|
logger.error(f"Error: {e}", exc_info=True)
|
|
print(f"\n✗ Error: {e}")
|
|
return 1
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|