"""从Binance获取加密货币OHLCV数据""" import os from datetime import datetime, timedelta from pathlib import Path from typing import Optional, Literal import pandas as pd from binance.client import Client import pytz from dotenv import load_dotenv from data.database import DatabaseManager # 加载环境变量 load_dotenv() class BinanceDataFetcher: """从Binance公开API获取和缓存OHLCV数据""" INTERVALS = { '1h': Client.KLINE_INTERVAL_1HOUR, '15m': Client.KLINE_INTERVAL_15MINUTE, } def __init__(self, cache_dir: str = "./data/cache"): """初始化Binance客户端和数据库管理器 Args: cache_dir: 缓存/数据库目录 """ # 配置代理 proxies = None use_proxy = os.getenv('USE_PROXY', 'false').lower() == 'true' if use_proxy: socks5_proxy = os.getenv('SOCKS5_PROXY', 'socks5://127.0.0.1:7890') proxies = { 'http': socks5_proxy, 'https': socks5_proxy } print(f"✓ 使用SOCKS5代理: {socks5_proxy}") try: # 初始化Binance客户端(带代理) if proxies: self.client = Client(requests_params={'proxies': proxies}) else: self.client = Client() # 测试连接 self.client.ping() print("✓ 已成功连接到Binance API") except Exception as e: print(f"⚠ Binance API连接失败: {e}") print("提示:") print(" 1. 检查代理设置 (.env文件)") print(" 2. 确保代理服务正在运行") print(" 3. 或者使用已缓存的数据") self.client = None # 初始化数据库管理器 self.db_manager = DatabaseManager(os.path.join(cache_dir, "market_data.db")) def fetch_ohlcv( self, symbol: Literal['BTC', 'ETH', 'SOL'], timeframe: Literal['1h', '15m'], start_date: Optional[str] = None, end_date: Optional[str] = None, use_cache: bool = True ) -> pd.DataFrame: """获取加密货币对的OHLCV数据 Args: symbol: 加密货币符号 (BTC, ETH, SOL) timeframe: 时间框架间隔 (1h 或 15m) start_date: 开始日期,格式:'YYYY-MM-DD' (默认: 1年前) end_date: 结束日期,格式:'YYYY-MM-DD' (默认: 现在) use_cache: 是否使用缓存数据 Returns: 包含以下列的DataFrame: timestamp, open, high, low, close, volume """ # 设置日期范围 if end_date is None: end_dt = datetime.now(pytz.UTC) else: end_dt = datetime.strptime(end_date, '%Y-%m-%d').replace(tzinfo=pytz.UTC) if start_date is None: start_dt = end_dt - timedelta(days=365) # 1年前 else: start_dt = datetime.strptime(start_date, '%Y-%m-%d').replace(tzinfo=pytz.UTC) start_str = start_dt.strftime('%Y-%m-%d') end_str = end_dt.strftime('%Y-%m-%d') # 1. 尝试从数据库获取数据 df = pd.DataFrame() if use_cache: df = self.db_manager.get_ohlcv(symbol, timeframe, start_str, end_str) # 检查数据是否足够 if not df.empty: min_ts = df['timestamp'].min().replace(tzinfo=pytz.UTC) max_ts = df['timestamp'].max().replace(tzinfo=pytz.UTC) # 简单判断覆盖范围 (容忍少量缺失) if min_ts <= start_dt + timedelta(days=2) and max_ts >= end_dt - timedelta(days=2): print(f"从数据库加载数据:{symbol} {timeframe} ({len(df)} rows)") return df # 2. 从API获取数据 if self.client is None: if not df.empty: print(f"⚠ API不可用,返回数据库现有数据 ({len(df)} rows)") return df raise ConnectionError( "Binance客户端不可用。请使用VPN或确保数据库有数据。" ) # Binance使用USDT交易对 pair = f"{symbol}USDT" interval = self.INTERVALS[timeframe] print(f"获取 {symbol} {timeframe} 数据,时间范围:{start_dt.date()} 至 {end_dt.date()}...") # 获取K线数据 try: klines = self.client.get_historical_klines( symbol=pair, interval=interval, start_str=str(int(start_dt.timestamp() * 1000)), end_str=str(int(end_dt.timestamp() * 1000)) ) except Exception as e: if not df.empty: print(f"⚠ API请求失败 ({e}),返回数据库现有数据") return df raise e if not klines: if not df.empty: return df raise ValueError(f"未获取到 {symbol} {timeframe} 的数据") # 转换为DataFrame new_df = pd.DataFrame(klines, columns=[ 'timestamp', 'open', 'high', 'low', 'close', 'volume', 'close_time', 'quote_volume', 'trades', 'taker_buy_base', 'taker_buy_quote', 'ignore' ]) # 选择并转换列 new_df = new_df[['timestamp', 'open', 'high', 'low', 'close', 'volume']].copy() new_df['timestamp'] = pd.to_datetime(new_df['timestamp'], unit='ms') for col in ['open', 'high', 'low', 'close', 'volume']: new_df[col] = new_df[col].astype(float) # 按时间排序 new_df = new_df.sort_values('timestamp').reset_index(drop=True) print(f"已获取 {len(new_df)} 行数据") # 保存到数据库 self.db_manager.save_ohlcv(new_df, symbol, timeframe) return new_df def get_latest_price(self, symbol: Literal['BTC', 'ETH', 'SOL']) -> float: """获取符号的当前价格 Args: symbol: 加密货币符号 Returns: 当前USDT价格 """ pair = f"{symbol}USDT" ticker = self.client.get_symbol_ticker(symbol=pair) return float(ticker['price']) if __name__ == "__main__": # 测试数据获取器 fetcher = BinanceDataFetcher() # 获取BTC 1小时数据(1年) df = fetcher.fetch_ohlcv('BTC', '1h') print("\nBTC 1小时数据:") print(df.head()) print(f"\n形状:{df.shape}") print(f"日期范围:{df['timestamp'].min()} 至 {df['timestamp'].max()}") # 获取当前价格 try: current_price = fetcher.get_latest_price('BTC') print(f"\n当前BTC价格:${current_price:,.2f}") except: print("\n无法获取当前价格")