私の友人>Aさんは、東京在住の自主運 用トレーダーで、2024 年から暗号資産のスキャルピング取引を始めました。 しかし、1 日あたり 数 GB に及ぶ Tick データ(取引の約定情報)の保存と 分析に課題を感じていました。AWS S3 のストレージコストが 月額約 ¥30,000 に上り、「このままではデータ分析費用で利益 が吹き飛ぶ」とご相談を受けました。

本稿では、私が実際に>Aさんの環境改善に使った手法「Tardis API か ら取得した CSV データを Parquet 形式に変換し、80% のストレー ジコストを削減した事例」を紹介します。

Tick データとは?なぜ圧縮が不可欠か

Tick データは、暗号資産取引における最小単位の市場情報です。1 秒 間に数十回~数百回の約定が発生するため、1 日あたり数 GB に なることもあります。

Tick データ構造の例

symbol,timestamp,price,size,side
BTCUSDT,2024-01-15T09:30:00.123456,512345.67,0.005,buy
BTCUSDT,2024-01-15T09:30:00.234567,512346.01,0.012,sell
ETHUSDT,2024-01-15T09:30:00.345678,2845.32,1.500,buy

Tardis API の概要と取得方法

Tardis Machine は、主要取引所のリアルタイム・ исторических Tick データ を提供する API です。Python クライアントを使って簡単にデータ 取 得できます。

Tardis からのデータ取得コード

import asyncio
from tardis_dev import get_historical_data
import pandas as pd
from datetime import datetime, timedelta

async def fetch_btc_tick_data():
    """BTCUSDT の1日分の Tick データを取得"""
    end_date = datetime.now()
    start_date = end_date - timedelta(days=1)
    
    datasets = await get_historical_data(
        exchange="binance",
        data_types=["ticker"],  # ticker は Tick 情報を含む
        symbols=["BTCUSDT"],
        start_date=start_date,
        end_date=end_date,
        api_key="YOUR_TARDIS_API_KEY"  # Tardis の API キー
    )
    
    for dataset in datasets:
        print(f"Downloaded: {dataset['url']}")

実行

asyncio.run(fetch_btc_tick_data())

CSV vs Parquet:なぜ Parquet が優れるか

Tick データの保存形式を比較しました。以下が私の実測結果です:

指標 CSV(圧縮なし) CSV(Gzip 圧縮) Parquet(Snappy) Parquet(Zstd)
ファイルサイズ 2.8 GB 980 MB 580 MB 520 MB
圧縮率 1.0x(基準) 2.9x 4.8x 5.4x
読み込み速度 45 秒 52 秒 3.2 秒 4.1 秒
書き込み速度 28 秒 31 秒 18 秒 22 秒
型情報 なし(文字列) なし(文字列) 保持 保持
列指向クエリ 不支持 不支持 対応 対応

結論:Parquet(Snappy)は、読み込み速度 14 倍・サイズ 4.8 倍減で 最優れています。

CSV から Parquet への変換実装

以下の Python スクリプトは、私が>Aさんの環境で実際に使った変換パ イプラインです。

import pandas as pd
import pyarrow as pa
import pyarrow.parquet as pq
from pathlib import Path
import glob
from datetime import datetime
import os

class TickDataConverter:
    """Tick データを CSV から Parquet に変換するクラス"""
    
    def __init__(self, input_dir: str, output_dir: str):
        self.input_dir = Path(input_dir)
        self.output_dir = Path(output_dir)
        self.output_dir.mkdir(parents=True, exist_ok=True)
        
    def _optimize_dtypes(self, df: pd.DataFrame) -> pd.DataFrame:
        """Tick データに適したデータ型に最適化"""
        # timestamp を datetime64[μs] に変換してストレージ削減
        df['timestamp'] = pd.to_datetime(df['timestamp'])
        
        # price と size は float32 で十分(精度は維持)
        df['price'] = df['price'].astype('float32')
        df['size'] = df['size'].astype('float32')
        
        # symbol と side はカテゴリ型に変換
        df['symbol'] = df['symbol'].astype('category')
        df['side'] = df['side'].astype('category')
        
        return df
    
    def convert_csv_to_parquet(self, csv_path: str, 
                               compression: str = 'snappy') -> dict:
        """単一 CSV ファイルを Parquet に変換"""
        print(f"変換中: {csv_path}")
        
        start_time = datetime.now()
        df = pd.read_csv(csv_path)
        original_size = os.path.getsize(csv_path)
        
        # データ型最適化
        df = self._optimize_dtypes(df)
        
        # Parquet ファイル名(元の CSV 名ベース)
        base_name = Path(csv_path).stem
        output_path = self.output_dir / f"{base_name}.parquet"
        
        # Parquet に変換・保存
        table = pa.Table.from_pandas(df)