Конвертация HDF5 в Parquet с партиционированием по датам в Pandas

Конвертация HDF5 в Parquet с партиционированием по датам в Pandas Анализ данных и Бэктесты
Пошаговое руководство от Senior Quant Developer по конвертации тиковых данных из формата HDF5 в партиционированный Parquet с помощью pandas и pyarrow.
Суть: Эффективный Python-скрипт для конвертации тяжелых HDF5-файлов с тиковыми данными в оптимизированный формат Parquet с физическим партиционированием по датам. Решение использует pandas и pyarrow для обработки данных чанками, предотвращая переполнение оперативной памяти.

Почему кванты переходят с HDF5 на Parquet?

В количественном анализе (Quantitative Finance) скорость доступа к историческим тикам определяет общую производительность бэктестера. Исторически формат HDF5 (.h5) пользовался популярностью благодаря высокой скорости последовательного чтения на локальных машинах. Однако при работе с распределенными хранилищами или облачной инфраструктурой HDF5 показывает себя не с лучшей стороны: он не поддерживает эффективное колоночное сжатие на уровне метаданных и не позволяет выполнять фильтрацию на стороне хранилища (predicate pushdown).

Формат Parquet с партиционированием по датам решает эти проблемы. Он позволяет считывать с диска только те дни и те колонки, которые необходимы для текущего расчета. Это критически важно, когда вы проводите ресурсоемкие вычисления, такие как Оптимизация торговых стратегий с помощью генетических алгоритмов DEAP на Python, или когда вам нужно рассчитать сложные производные параметры, выполняя Расчет и Визуализация Поверхности Подразумеваемой Волатильности (IV Surface) Опционов в Python Matplotlib.

Исходный код

Ниже представлен готовый к использованию скрипт. Он считывает данные из HDF5 порциями (чанками), извлекает дату из временной метки, преобразует данные в формат PyArrow и записывает их в структурированную директорию Parquet.

import os
import pandas as pd
import pyarrow as pa
import pyarrow.parquet as pq

def convert_hdf5_to_parquet(hdf_file_path, hdf_key, output_dir, chunksize=1000000, compression='snappy'):
    '''
    Конвертирует HDF5 файл с тиками в партиционированный Parquet.
    
    :param hdf_file_path: Путь к исходному файлу HDF5.
    :param hdf_key: Ключ (датасет) внутри HDF5.
    :param output_dir: Директория для сохранения Parquet партиций.
    :param chunksize: Размер чанка для итеративного чтения (избегает OOM).
    :param compression: Алгоритм сжатия (snappy, gzip, zstd).
    '''
    if not os.path.exists(output_dir):
        os.makedirs(output_dir)
        
    print(f'Начало конвертации {hdf_file_path} (ключ: {hdf_key}) -> {output_dir}')
    
    # Открываем HDF5 в режиме чтения
    with pd.HDFStore(hdf_file_path, mode='r') as store:
        if hdf_key not in store:
            raise KeyError(f'Ключ {hdf_key} не найден в файле {hdf_file_path}')
            
        # Получаем общее количество строк для мониторинга прогресса
        nrows = store.get_storer(hdf_key).nrows
        print(f'Всего строк для обработки: {nrows}')
        
        processed_rows = 0
        
        # Читаем HDF5 чанками для экономии RAM
        for chunk in store.select(hdf_key, chunksize=chunksize):
            # Проверяем структуру индекса
            if not isinstance(chunk.index, pd.DatetimeIndex):
                if 'timestamp' in chunk.columns:
                    chunk['timestamp'] = pd.to_datetime(chunk['timestamp'])
                    chunk.set_index('timestamp', inplace=True)
                else:
                    raise ValueError('Данные должны содержать DatetimeIndex или колонку timestamp')
            
            # Сбрасываем индекс, чтобы сохранить timestamp как колонку в Parquet
            df_flat = chunk.reset_index()
            
            # Создаем строковую колонку даты для партиционирования
            df_flat['date'] = df_flat['timestamp'].dt.date.astype(str)
            
            # Конвертируем pandas DataFrame в таблицу PyArrow
            table = pa.Table.from_pandas(df_flat)
            
            # Записываем чанк в партиционированную директорию
            pq.write_to_dataset(
                table,
                root_path=output_dir,
                partition_cols=['date'],
                compression=compression,
                use_dictionary=True
            )
            
            processed_rows += len(chunk)
            print(f'Обработано строк: {processed_rows}/{nrows} ({processed_rows/nrows*100:.2f}%)')
            
    print('Конвертация успешно завершена!')

if __name__ == '__main__':
    # Конфигурация путей
    HDF5_FILE = 'ticks.h5'
    HDF5_KEY = '/BTCUSDT'
    OUTPUT_DIRECTORY = 'parquet_dataset'
    
    # Генерация тестового HDF5 файла, если он отсутствует
    if not os.path.exists(HDF5_FILE):
        print('Генерация тестового HDF5 файла...')
        date_range = pd.date_range(start='2023-10-01', end='2023-10-05', freq='ms')
        test_df = pd.DataFrame({
            'bid': [27000.0 + i * 0.1 for i in range(len(date_range))],
            'ask': [27000.5 + i * 0.1 for i in range(len(date_range))],
            'volume': [1.0] * len(date_range)
        }, index=date_range)
        test_df.index.name = 'timestamp'
        test_df.to_hdf(HDF5_FILE, key=HDF5_KEY, format='table')
        print('Тестовый файл создан.')

    # Запуск конвертации
    convert_hdf5_to_parquet(
        hdf_file_path=HDF5_FILE,
        hdf_key=HDF5_KEY,
        output_dir=OUTPUT_DIRECTORY,
        chunksize=500000
    )

Разбор параметров

  • hdf_file_path: Путь к исходному файлу HDF5 на вашем диске. Поддерживаются форматы .h5 и .hdf5.
  • hdf_key: Внутренний путь к таблице внутри HDF5-файла (например, идентификатор торговой пары или тикера).
  • output_dir: Корневая директория, куда будет записана структура партиций Parquet.
  • chunksize: Размер порции данных, считываемой за один шаг. Настройка этого параметра позволяет обрабатывать файлы, размер которых превышает объем доступной оперативной памяти.
  • compression: Метод сжатия данных. Опция snappy обеспечивает высокую скорость декомпрессии при умеренном сжатии, что идеально подходит для локального бэктестинга.

Как запустить

Для работы скрипта вам понадобятся библиотеки для работы с таблицами HDF5 и движок PyArrow. Установите их с помощью пакетного менеджера pip:

pip install pandas tables pyarrow

Сохраните приведенный выше код в файл convert_ticks.py и выполните его в терминале:

python convert_ticks.py

После завершения работы скрипта в целевой директории parquet_dataset будет создана структура папок, разделенная по датам:

parquet_dataset/
├── date=2023-10-01/
│   └── xxxxxxxx.parquet
├── date=2023-10-02/
│   └── xxxxxxxx.parquet
└── date=2023-10-03/
    └── xxxxxxxx.parquet

Полученный датасет готов к интеграции в любые современные бэктест-платформы. Вы можете использовать эти структурированные данные для симуляции торгов, предварительно изучив материал по теме Настройка комиссий и проскальзывания в Zipline-Reloaded для крипты, чтобы ваши тесты максимально соответствовали реальным рыночным условиям.

Оцените статью
FinFluct