When I first built my crypto quant trading system in 2024, I spent three weeks debugging a ConnectionError: timeout error that was actually caused by incorrect historical data timestamp configuration. The Binance OHLCV endpoint kept returning empty datasets because my UTC offset was misconfigured. That single mistake cost me valuable backtesting time and taught me why proper historical data configuration is the foundation of any quantitative trading framework. In this guide, I will walk you through everything you need to know about configuring historical data for cryptocurrency quantitative backtesting, including working code examples, pricing comparisons, and solutions to the most common errors you will encounter.

Understanding Historical Data in Quantitative Backtesting

Quantitative backtesting requires historical market data that is accurate, complete, and properly formatted. The quality of your backtesting results is directly tied to the quality of your historical data configuration. Cryptocurrency markets operate 24/7, which means there are no market holidays or gaps to consider, but this also means you need robust data pipelines that can handle continuous market activity without interruption.

For quantitative strategies, you typically need several types of historical data: OHLCV (Open, High, Low, Close, Volume) price data for technical analysis, order book snapshots for market microstructure studies, funding rate history for perpetual futures strategies, and trade-level data for high-frequency analysis. Each data type has specific configuration requirements that we will explore in detail.

HolySheep AI Data Relay Integration

If you are building a sophisticated quant system, you will likely need AI-powered data analysis capabilities for strategy optimization. Sign up here for HolySheep AI, which offers crypto market data relay including trades, order book, liquidations, and funding rates for major exchanges like Binance, Bybit, OKX, and Deribit. The platform delivers sub-50ms latency with pricing at just $0.42 per million tokens for DeepSeek V3.2, compared to GPT-4.1 at $8 per million tokensβ€”a savings of over 85%.

Setting Up Your Data Configuration Environment

Before diving into code, you need to establish a proper environment for historical data configuration. Your Python environment should include essential libraries for data retrieval, manipulation, and storage. Install the following packages to get started:

pip install pandas numpy requests asyncio aiohttp ccxt pandas-ta
pip install pyarrow fastparquet sqlalchemy redis-cache python-dotenv

Create a configuration file to manage your data sources, API credentials, and backtesting parameters. This separation of configuration from logic makes your backtesting framework maintainable and adaptable to different market conditions.

# config/historical_data_config.py
import os
from dataclasses import dataclass
from typing import List, Optional
from datetime import datetime, timedelta

@dataclass
class DataSourceConfig:
    """Configuration for cryptocurrency data sources."""
    exchange: str = "binance"
    symbols: List[str] = None  # e.g., ["BTC/USDT", "ETH/USDT"]
    timeframes: List[str] = None  # e.g., ["1m", "5m", "1h", "1d"]
    start_date: datetime = None
    end_date: datetime = None
    include_orderbook: bool = False
    include_trades: bool = False
    include_funding: bool = True  # For perpetual futures
    
    def __post_init__(self):
        if self.symbols is None:
            self.symbols = ["BTC/USDT"]
        if self.timeframes is None:
            self.timeframes = ["1h"]
        if self.start_date is None:
            self.start_date = datetime.utcnow() - timedelta(days=365)
        if self.end_date is None:
            self.end_date = datetime.utcnow()

@dataclass
class StorageConfig:
    """Configuration for data storage backends."""
    use_parquet: bool = True
    use_redis_cache: bool = True
    data_directory: str = "./data/historical"
    cache_ttl_seconds: int = 3600

@dataclass
class BacktestConfig:
    """Configuration for backtesting engine."""
    initial_capital: float = 10000.0
    commission_rate: float = 0.001  # 0.1% per trade
    slippage_model: str = "fixed"  # "fixed", "volume", "volatility"
    slippage_bps: float = 5.0  # Basis points
    max_position_size: float = 0.2  # 20% of capital per position

class HistoricalDataConfig:
    """Main configuration class for historical data in backtesting."""
    
    def __init__(
        self,
        api_key: str = None,
        data_source: str = "ccxt",
        holy_sheep_api_key: str = None
    ):
        self.api_key = api_key or os.getenv("EXCHANGE_API_KEY")
        self.holy_sheep_api_key = holy_sheep_api_key or os.getenv("HOLYSHEEP_API_KEY")
        self.data_source = data_source
        self.data_source_config = DataSourceConfig()
        self.storage_config = StorageConfig()
        self.backtest_config = BacktestConfig()
        self.base_url = "https://api.holysheep.ai/v1"  # HolySheep API endpoint
        
    def validate_configuration(self) -> List[str]:
        """Validate configuration and return list of issues."""
        issues = []
        
        if not self.data_source_config.symbols:
            issues.append("No symbols configured for data retrieval")
        
        if self.data_source_config.start_date >= self.data_source_config.end_date:
            issues.append("Start date must be before end date")
            
        if self.data_source == "holy_sheep" and not self.holy_sheep_api_key:
            issues.append("HolySheep API key required when using holy_sheep data source")
            
        # Check for data freshness requirements
        data_age = datetime.utcnow() - self.data_source_config.end_date
        if data_age.days > 30:
            issues.append("Warning: End date is more than 30 days in the past")
            
        return issues
    
    def to_dict(self) -> dict:
        """Export configuration as dictionary for logging."""
        return {
            "data_source": self.data_source,
            "symbols": self.data_source_config.symbols,
            "timeframes": self.data_source_config.timeframes,
            "date_range": {
                "start": self.data_source_config.start_date.isoformat(),
                "end": self.data_source_config.end_date.isoformat()
            },
            "storage": {
                "parquet": self.storage_config.use_parquet,
                "redis_cache": self.storage_config.use_redis_cache
            },
            "backtest": {
                "initial_capital": self.backtest_config.initial_capital,
                "commission_rate": f"{self.backtest_config.commission_rate * 100}%"
            }
        }

Fetching Historical OHLCV Data with CCXT

The CCXT library is the industry standard for accessing cryptocurrency exchange APIs. It provides a unified interface for over 100 exchanges, making it ideal for multi-exchange backtesting. The key to successful data retrieval is proper pagination handling and rate limit management.

# src/data_fetchers/ccxt_fetcher.py
import ccxt
import pandas as pd
from datetime import datetime, timedelta
from typing import Optional, List, Dict
import time
import asyncio

class CCXTHistoricalDataFetcher:
    """
    Fetches historical OHLCV data using CCXT library.
    Handles rate limiting, pagination, and data validation.
    """
    
    def __init__(self, exchange_id: str = "binance", api_key: str = None, 
                 api_secret: str = None, config = None):
        self.exchange_id = exchange_id
        
        # Initialize exchange with credentials
        exchange_class = getattr(ccxt, exchange_id)
        init_params = {
            'enableRateLimit': True,
            'options': {'defaultType': 'spot'}  # or 'future', 'margin'
        }
        
        if api_key and api_secret:
            init_params.update({'apiKey': api_key, 'secret': api_secret})
        
        self.exchange = exchange_class(init_params)
        self.config = config
        
        # Rate limit tracking
        self.request_count = 0
        self.last_request_time = time.time()
        
    def _rate_limit_check(self, calls_per_second: int = 10):
        """Implement rate limiting to avoid exchange bans."""
        elapsed = time.time() - self.last_request_time
        min_interval = 1.0 / calls_per_second
        
        if elapsed < min_interval:
            time.sleep(min_interval - elapsed)
        
        self.last_request_time = time.time()
        
    async def fetch_ohlcv_async(
        self,
        symbol: str,
        timeframe: str,
        start_time: datetime,
        end_time: Optional[datetime] = None,
        limit: int = 1000
    ) -> pd.DataFrame:
        """
        Fetch OHLCV data asynchronously using CCXT.
        
        Args:
            symbol: Trading pair (e.g., "BTC/USDT")
            timeframe: Candle timeframe ("1m", "5m", "1h", "1d")
            start_time: Start of data range
            end_time: End of data range (defaults to now)
            limit: Maximum candles per request (exchange-specific)
            
        Returns:
            DataFrame with OHLCV data and timestamp index
        """
        all_candles = []
        current_start = start_time
        
        # CCXT requires milliseconds
        since_ms = int(start_time.timestamp() * 1000)
        end_ms = int(end_time.timestamp() * 1000) if end_time else None
        
        print(f"Fetching {symbol} {timeframe} data from {start_time}")
        
        while True:
            self._rate_limit_check(calls_per_second=5)
            
            try:
                # Fetch candles
                candles = await asyncio.to_thread(
                    self.exchange.fetch_ohlcv,
                    symbol,
                    timeframe=timeframe,
                    since=since_ms,
                    limit=limit
                )
                
                if not candles:
                    break
                    
                all_candles.extend(candles)
                
                # Update start time for next request
                last_candle_time = candles[-1][0]
                since_ms = last_candle_time + 1
                
                # Check if we've reached the end
                if end_ms and since_ms >= end_ms:
                    break
                    
                # CCXT returns data in ascending order
                # Break if we're past the end time
                if candles[-1][0] >= (end_ms or float('inf')):
                    break
                    
                print(f"  Retrieved {len(candles)} candles, "
                      f"latest timestamp: {pd.Timestamp(last_candle_time, unit='ms')}")
                
            except ccxt.RateLimitExceeded:
                print("Rate limit hit, waiting 60 seconds...")
                await asyncio.sleep(60)
            except ccxt.NetworkError as e:
                print(f"Network error: {e}, retrying in 10 seconds...")
                await asyncio.sleep(10)
            except Exception as e:
                print(f"Error fetching data: {e}")
                break
        
        # Convert to DataFrame
        df = pd.DataFrame(
            all_candles,
            columns=['timestamp', 'open', 'high', 'low', 'close', 'volume']
        )
        df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
        df.set_index('timestamp', inplace=True)
        
        # Remove duplicates and sort
        df = df[~df.index.duplicated(keep='last')]
        df = df.sort_index()
        
        # Filter to exact date range
        if end_time:
            df = df[df.index <= end_time]
            
        print(f"Total candles retrieved: {len(df)}")
        
        return df
    
    def fetch_funding_rates(self, symbol: str, start_time: datetime,
                           end_time: datetime) -> pd.DataFrame:
        """
        Fetch funding rate history for perpetual futures.
        Critical for funding rate arbitrage strategies.
        """
        all_funding = []
        since_ms = int(start_time.timestamp() * 1000)
        end_ms = int(end_time.timestamp() * 1000)
        
        # Set exchange to use futures
        if hasattr(self.exchange, 'options'):
            self.exchange.options['defaultType'] = 'future'
        
        while since_ms < end_ms:
            self._rate_limit_check(calls_per_second=2)
            
            try:
                funding = self.exchange.fetch_funding_history(
                    symbol, since=since_ms, limit=200
                )
                
                if not funding:
                    break
                    
                all_funding.extend(funding)
                since_ms = funding[-1]['timestamp'] + 1
                
            except Exception as e:
                print(f"Error fetching funding rates: {e}")
                break
        
        df = pd.DataFrame(all_funding)
        if not df.empty:
            df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
            df.set_index('timestamp', inplace=True)
            
        return df
    
    def validate_data_quality(self, df: pd.DataFrame) -> Dict[str, any]:
        """
        Validate data quality and return quality report.
        Checks for gaps, anomalies, and completeness.
        """
        report = {
            "total_candles": len(df),
            "date_range": {
                "start": df.index.min(),
                "end": df.index.max()
            },
            "missing_candles": 0,
            "price_anomalies": [],
            "volume_anomalies": []
        }
        
        # Check for expected candle count
        if len(df) > 1:
            expected_interval = df.index[1] - df.index[0]
            full_range = df.index.max() - df.index.min()
            expected_candles = full_range / expected_interval + 1
            report["missing_candles"] = expected_candles - len(df)
            report["expected_vs_actual"] = f"{len(df)}/{int(expected_candles)}"
        
        # Check for price anomalies (zero or negative)
        anomalies = df[(df['close'] <= 0) | (df['high'] < df['low'])]
        report["price_anomalies"] = len(anomalies)
        
        # Check for volume anomalies
        volume_pct = df['volume'].quantile([0.01, 0.99])
        extreme_volume = df[
            (df['volume'] < volume_pct[0.01]) | 
            (df['volume'] > volume_pct[0.99])
        ]
        report["volume_anomalies"] = len(extreme_volume)
        
        return report

Usage example

async def main(): config = HistoricalDataConfig() fetcher = CCXTHistoricalDataFetcher( exchange_id="binance", config=config ) # Fetch one year of daily BTC data btc_daily = await fetcher.fetch_ohlcv_async( symbol="BTC/USDT", timeframe="1d", start_time=datetime(2024, 1, 1), end_time=datetime(2025, 1, 1) ) # Validate data quality quality_report = fetcher.validate_data_quality(btc_daily) print(f"Data quality report: {quality_report}") # Save to parquet for efficient storage btc_daily.to_parquet("./data/btc_daily_2024.parquet") if __name__ == "__main__": asyncio.run(main())

HolySheep AI Integration for Advanced Analysis

For sophisticated quant strategies, you may want to leverage AI for pattern recognition, strategy optimization, or market regime detection. HolySheep AI provides a cost-effective solution with $0.42 per million tokens for DeepSeek V3.2, compared to $8.00 for GPT-4.1 and $15.00 for Claude Sonnet 4.5. This pricing makes large-scale AI analysis economically viable for retail traders and small hedge funds.

# src/data_fetchers/holy_sheep_analyzer.py
import requests
import json
import pandas as pd
from typing import List, Dict, Optional
from datetime import datetime

class HolySheepQuantAnalyzer:
    """
    Integrates with HolySheep AI for quantitative analysis.
    Uses market data relay for trades, order book, liquidations, and funding rates.
    """
    
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://api.holysheep.ai/v1"
        self.headers = {
            "Authorization": f"Bearer {api_key}",
            "Content-Type": "application/json"
        }
        
    def _make_request(self, endpoint: str, method: str = "GET", 
                     data: dict = None) -> dict:
        """Make authenticated request to HolySheep API."""
        url = f"{self.base_url}/{endpoint}"
        
        try:
            if method == "GET":
                response = requests.get(url, headers=self.headers, params=data)
            else:
                response = requests.post(url, headers=self.headers, json=data)
                
            response.raise_for_status()
            return response.json()
            
        except requests.exceptions.HTTPError as e:
            if response.status_code == 401:
                raise Exception("Invalid API key. Check your HolySheep credentials.")
            elif response.status_code == 429:
                raise Exception("Rate limit exceeded. Wait before retrying.")
            else:
                raise Exception(f"HTTP error {response.status_code}: {e}")
        except requests.exceptions.ConnectionError:
            raise Exception("Connection error: Unable to reach HolySheep API. "
                          "Check your internet connection.")
    
    def analyze_market_regime(self, symbol: str, timeframe: str,
                               price_data: List[Dict]) -> Dict:
        """
        Use AI to analyze current market regime.
        Returns regime classification and recommended strategy parameters.
        """
        prompt = f"""Analyze this {symbol} {timeframe} price data and identify the current market regime.
        Price data is provided as a list of candles with 'open', 'high', 'low', 'close', 'volume'.

        Return a JSON response with:
        {{
            "regime": "trending_up|trending_down|ranging|volatile|unknown",
            "confidence": 0.0-1.0,
            "recommended_strategy": "mean_reversion|momentum|breakout|neutral",
            "risk_level": "low|medium|high",
            "key_support_levels": [price_levels],
            "key_resistance_levels": [price_levels],
            "volatility_assessment": "low|medium|high",
            "momentum_direction": "bullish|bearish|neutral",
            "market_sentiment": "fear|greed|neutral"
        }}

        Price data sample (last 20 candles):
        {json.dumps(price_data[-20:], indent=2)}
        """
        
        payload = {
            "model": "deepseek-chat",  # Cost-effective: $0.42/MTok vs $8.00 for GPT-4.1
            "messages": [
                {"role": "system", "content": "You are an expert quantitative analyst specializing in cryptocurrency markets."},
                {"role": "user", "content": prompt}
            ],
            "temperature": 0.3,
            "max_tokens": 500
        }
        
        result = self._make_request("chat/completions", method="POST", data=payload)
        
        # Parse AI response
        content = result['choices'][0]['message']['content']
        # Extract JSON from response
        try:
            # Find JSON in the response
            start = content.find('{')
            end = content.rfind('}') + 1
            analysis = json.loads(content[start:end])
            return analysis
        except:
            return {"error": "Failed to parse AI response", "raw": content}
    
    def optimize_strategy_parameters(self, strategy_type: str, 
                                     historical_results: Dict) -> Dict:
        """
        Use AI to optimize strategy parameters based on backtest results.
        Identifies parameter combinations that maximize Sharpe ratio.
        """
        prompt = f"""Optimize parameters for a {strategy_type} trading strategy based on backtest results.

        Backtest Results:
        - Total Return: {historical_results.get('total_return', 'N/A')}%
        - Sharpe Ratio: {historical_results.get('sharpe_ratio', 'N/A')}
        - Max Drawdown: {historical_results.get('max_drawdown', 'N/A')}%
        - Win Rate: {historical_results.get('win_rate', 'N/A')}%
        - Total Trades: {historical_results.get('total_trades', 'N/A')}
        - Average Trade Duration: {historical_results.get('avg_duration', 'N/A')}

        Parameter Space Tested:
        {json.dumps(historical_results.get('parameter_grid', {}), indent=2)}

        Return optimized parameters and explain why they perform better.
        """
        
        payload = {
            "model": "deepseek-chat",
            "messages": [
                {"role": "system", "content": "You are an expert algorithmic trading strategist."},
                {"role": "user", "content": prompt}
            ],
            "temperature": 0.2,
            "max_tokens": 800
        }
        
        result = self._make_request("chat/completions", method="POST", data=payload)
        return result['choices'][0]['message']['content']
    
    def detect_patterns(self, ohlcv_df: pd.DataFrame, 
                       symbol: str) -> List[Dict]:
        """
        Use AI to detect chart patterns and technical formations.
        """
        # Prepare data summary
        recent_data = ohlcv_df.tail(50).to_dict('records')
        
        prompt = f"""Analyze this {symbol} price chart and identify any notable chart patterns or technical formations.

        Recent price data (50 candles):
        {json.dumps(recent_data[:20], indent=2)}  # Send first 20 as sample

        Identify any of the following patterns:
        - Technical: double_top, double_bottom, head_shoulders, cup_handle, wedge
        - Candlestick: doji, hammer, engulfing, morning_star, evening_star
        - Trend: ascending_triangle, descending_triangle, symmetrical_triangle

        Return patterns found with confidence scores and price targets.
        """
        
        payload = {
            "model": "deepseek-chat",
            "messages": [{"role": "user", "content": prompt}],
            "temperature": 0.3,
            "max_tokens": 600
        }
        
        result = self._make_request("chat/completions", method="POST", data=payload)
        return {"patterns": result['choices'][0]['message']['content']}
    
    def generate_trading_signals(self, market_data: Dict) -> Dict:
        """
        Generate trading signals using HolySheep AI analysis.
        Combines technical analysis with market microstructure data.
        """
        prompt = f"""Generate a comprehensive trading signal analysis for {market_data.get('symbol', 'UNKNOWN')}.

        Market Data:
        {json.dumps(market_data, indent=2)}

        Provide:
        1. Overall signal: BUY, SELL, or NEUTRAL
        2. Entry price suggestion
        3. Stop loss level (with rationale)
        4. Take profit levels (multiple targets)
        5. Position sizing recommendation
        6. Risk/reward ratio
        7. Key risks to monitor
        8. Time horizon recommendation
        """
        
        payload = {
            "model": "deepseek-chat",
            "messages": [{"role": "user", "content": prompt}],
            "temperature": 0.4,
            "max_tokens": 700
        }
        
        result = self._make_request("chat/completions", method="POST", data=payload)
        return {"signal": result['choices'][0]['message']['content']}

Example usage with cost tracking

def main(): # Initialize analyzer analyzer = HolySheepQuantAnalyzer(api_key="YOUR_HOLYSHEEP_API_KEY") # Create sample price data sample_prices = [ {"open": 42000, "high": 42500, "low": 41800, "close": 42300, "volume": 25000}, {"open": 42300, "high": 42800, "low": 42100, "close": 42650, "volume": 28000}, # ... more data ] * 20 # Replicate to get 20 candles # Analyze market regime try: regime_analysis = analyzer.analyze_market_regime( symbol="BTC/USDT", timeframe="1h", price_data=sample_prices ) print(f"Market Regime: {regime_analysis.get('regime', 'unknown')}") print(f"Confidence: {regime_analysis.get('confidence', 0):.2%}") print(f"Recommended Strategy: {regime_analysis.get('recommended_strategy', 'N/A')}") except Exception as e: print(f"Analysis error: {e}") if __name__ == "__main__": main()

Data Storage and Retrieval Optimization

Efficient data storage is crucial for backtesting performance. Parquet format provides excellent compression and fast read/write speeds, making it ideal for large historical datasets. A typical one-year dataset for 10 trading pairs across 5 timeframes can consume several gigabytes in CSV format but only 200-500MB in Parquet.

# src/data_storage/optimized_storage.py
import pandas as pd
import pyarrow as pa
import pyarrow.parquet as pq
import os
from pathlib import Path
from typing import List, Dict, Optional
from datetime import datetime
import hashlib

class HistoricalDataStore:
    """
    Optimized storage system for historical market data.
    Uses Parquet format with partitioning for efficient querying.
    """
    
    def __init__(self, base_path: str = "./data/historical"):
        self.base_path = Path(base_path)
        self.base_path.mkdir(parents=True, exist_ok=True)
        
    def _get_partition_path(self, exchange: str, symbol: str, 
                           timeframe: str) -> Path:
        """Generate partition path for data organization."""
        # Path format: base_path/exchange/symbol/timeframe/
        symbol_clean = symbol.replace('/', '_')
        return self.base_path / exchange / symbol_clean / timeframe
    
    def save_ohlcv(self, df: pd.DataFrame, exchange: str, symbol: str,
                   timeframe: str, partition: str = "daily"):
        """
        Save OHLCV data with intelligent partitioning.
        
        Partition strategies:
        - "daily": One file per day (best for large datasets, frequent updates)
        - "monthly": One file per month (balanced)
        - "static": Single file for entire dataset (fastest reads)
        """
        partition_path = self._get_partition_path(exchange, symbol, timeframe)
        
        if partition == "daily":
            # Partition by date
            df['date'] = df.index.date
            for date, group_df in df.groupby('date'):
                date_path = partition_path / f"date={date}"
                date_path.mkdir(parents=True, exist_ok=True)
                
                file_path = date_path / "data.parquet"
                group_df.drop(columns=['date']).to_parquet(
                    file_path, 
                    engine='pyarrow',
                    compression='snappy'  # Good balance of speed and compression
                )
        else:
            # Single file storage
            partition_path.mkdir(parents=True, exist_ok=True)
            file_path = partition_path / "data.parquet"
            df.to_parquet(file_path, engine='pyarrow', compression='snappy')
            
        print(f"Saved {len(df)} candles to {partition_path}")
    
    def load_ohlcv(self, exchange: str, symbol: str, timeframe: str,
                   start_date: Optional[datetime] = None,
                   end_date: Optional[datetime] = None) -> pd.DataFrame:
        """
        Load OHLCV data with date range filtering.
        Uses predicate pushdown for efficient Parquet queries.
        """
        partition_path = self._get_partition_path(exchange, symbol, timeframe)
        
        if not partition_path.exists():
            raise FileNotFoundError(f"No data found at {partition_path}")
        
        # Check if partitioned data exists
        daily_files = list(partition_path.glob("date=*/data.parquet"))
        
        if daily_files:
            # Load partitioned data
            tables = []
            for file_path in daily_files:
                date_str = file_path.parent.name.replace("date=", "")
                file_date = datetime.strptime(date_str, "%Y-%m-%d").date()
                
                # Filter by date range
                if start_date and file_date < start_date.date():
                    continue
                if end_date and file_date > end_date.date():
                    continue
                    
                df = pd.read_parquet(file_path)
                tables.append(df)
            
            if tables:
                result = pd.concat(tables, ignore_index=False)
            else:
                result = pd.DataFrame()
        else:
            # Load single file
            file_path = partition_path / "data.parquet"
            result = pd.read_parquet(file_path)
            
            # Apply date filter
            if start_date:
                result = result[result.index >= start_date]
            if end_date:
                result = result[result.index <= end_date]
        
        return result.sort_index()
    
    def get_data_info(self, exchange: str, symbol: str, 
                     timeframe: str) -> Dict:
        """Get information about stored data without loading it."""
        partition_path = self._get_partition_path(exchange, symbol, timeframe)
        
        if not partition_path.exists():
            return {"exists": False}
        
        daily_files = list(partition_path.glob("date=*/data.parquet"))
        
        if daily_files:
            # Get metadata from first and last files
            first_df = pd.read_parquet(daily_files[0])
            last_df = pd.read_parquet(daily_files[-1])
            
            return {
                "exists": True,
                "partition_type": "daily",
                "file_count": len(daily_files),
                "date_range": {
                    "start": daily_files[0].parent.name.replace("date=", ""),
                    "end": daily_files[-1].parent.name.replace("date=", "")
                },
                "row_count": len(first_df) + len(last_df),  # Approximate
                "sample_columns": list(first_df.columns)
            }
        else:
            file_path = partition_path / "data.parquet"
            if file_path.exists():
                pf = pq.ParquetFile(file_path)
                schema = pf.schema_arrow
                
                return {
                    "exists": True,
                    "partition_type": "static",
                    "file_size_mb": file_path.stat().st_size / (1024 * 1024),
                    "row_count": pf.metadata.num_rows,
                    "columns": [s.name for s in schema]
                }
            
        return {"exists": False}
    
    def compress_existing_data(self, exchange: str, symbol: str,
                               timeframe: str):
        """Compress existing CSV data to Parquet format."""
        symbol_clean = symbol.replace('/', '_')
        csv_path = self.base_path / f"{exchange}_{symbol_clean}_{timeframe}.csv"
        
        if csv_path.exists():
            df = pd.read_csv(csv_path, parse_dates=True, index_col=0)
            partition_path = self._get_partition_path(exchange, symbol, timeframe)
            partition_path.mkdir(parents=True, exist_ok=True)
            
            output_path = partition_path / "data.parquet"
            df.to_parquet(output_path, compression='snappy')
            
            csv_size = csv_path.stat().st_size / (1024 * 1024)
            parquet_size = output_path.stat().st_size / (1024 * 1024)
            
            print(f"Compressed {csv_path} ({csv_size:.2f}MB) to "
                  f"{output_path} ({parquet_size:.2f}MB) - "
                  f"{100 * (1 - parquet_size/csv_size):.1f}% reduction")

Backtesting data pipeline

class BacktestDataPipeline: """ Complete pipeline for preparing data for backtesting. Handles data fetching, cleaning, feature engineering, and storage. """ def __init__(self, store: HistoricalDataStore, fetcher = None): self.store = store self.fetcher = fetcher def prepare_backtest_data( self, symbols: List[str], timeframes: List[str], start_date: datetime, end_date: datetime, exchanges: List[str] = None, compute_features: bool = True ) -> Dict[str, Dict[str, pd.DataFrame]]: """ Prepare complete dataset for backtesting. Returns nested dictionary: {exchange: {symbol_timeframe: DataFrame}} """ if exchanges is None: exchanges = ["binance"] all_data = {} for exchange in exchanges: exchange_data = {} for symbol in symbols: for timeframe in timeframes: key = f"{symbol}_{timeframe}" try: # Try to load from cache df = self.store.load_ohlcv( exchange, symbol, timeframe, start_date, end_date ) print(f"Loaded cached data for {exchange}/{symbol}/{timeframe}") except FileNotFoundError: # Fetch from exchange if self.fetcher: print(f"Fetching data for {exchange}/{symbol}/{timeframe}") df = self.fetcher.fetch_ohlcv( symbol=symbol, timeframe=timeframe, start_time=start_date, end_time=end_date ) # Cache the data self.store.save_ohlcv( df, exchange, symbol, timeframe ) else: raise Exception(f"No data and no fetcher configured") if compute_features: df = self._add_technical_features(df) exchange_data[key] = df all_data[exchange] = exchange_data return all_data def _add_technical_features(self, df: pd.DataFrame) -> pd.DataFrame: """Add common technical indicators as features.""" df = df.copy() # Price-based features df['returns'] = df['close'].pct_change() df['log_returns'] = np.log(df['close'] / df['close'].shift(1)) # Moving averages for window in [5, 10, 20, 50]: df[f'sma_{window}'] = df['close'].rolling(window).mean() df[f'ema_{window}'] = df['close'].ewm(span=window).mean() # Volatility df['volatility_20'] = df['returns'].rolling(20).std() df['volatility_50'] = df['returns'].rolling(50).std() # Volume features df['volume_sma_20'] = df['volume'].rolling(20).mean() df['volume_ratio'] = df['volume'] / df['volume_sma_20'] # High-Low range df['hl_range'] = (df['high'] - df['low']) / df['close'] # Drop NaN rows created by rolling calculations df = df.dropna() return df

Usage

import numpy as np store = HistoricalDataStore("./data/historical") pipeline = BacktestDataPipeline(store) data = pipeline.prepare_backtest_data( symbols=["BTC/USDT",