こんにちは、HolySheep AI テクニカルリサーチャーの田尻です。本日は、暗号資産トレーディングやクオンツ分析において避けて通れない「L2 深度データ」の取得方法について、の実機検証を交えながら詳しく解説します。

私は2024年から約18ヶ月間にわたり、OKX を始めとする主要取引所の板情報(orderbook)取得と分析mundoを重ねてきました。この過程で Tardis(タルディス)の API を多用するようになりましたが、その処理・分析には HolySheep AI を活用しています。本ガイドがその方の参考になれば幸いです。

L2 深度データとは?なぜ重要か

L2 深度データとは、板情報(Order Book)における最良気配値(Best Bid/Ask)から数段階の奥行きを持つ価格・数量情報を指します。OKX の永続契約では、通常20段階の深度データが提供されます。

Tardis Historical API の概要

Tardis(https://tardis.dev)は、主要取引所の歴史的市場データを REST API および WebSocket で提供するSaaSです。OKX 以外にも Bybit、 Binance、Deribit など40以上の取引所に対応しています。

対応データ種別

データ種別粒度保持期間用途
L2 Orderbook.tick最大2年深度分析、スプレッド監視
Trades1 trade無制限約定履歴、 VWAP 計算
OHLCV1m-1D無制限チャート、分析
Liquidationsevent1年強制決済分析

環境構築と前提条件

まずは必要な環境をセットアップします。本ガイドでは Python 3.10+ を前提とします。

# 必要なライブラリのインストール
pip install requests pandas aiohttp asyncio nest-asyncio

プロジェクト構造

mkdir -p tardis_okx_project/{data,logs,analysis} cd tardis_okx_project

requirements.txt

cat > requirements.txt << 'EOF' requests==2.31.0 pandas==2.1.4 aiohttp==3.9.1 nest-asyncio==1.5.8 python-dotenv==1.0.0 EOF

OKX 永続契約 L2 深度データ取得の実装

方法1:REST API(バッチダウンロード)

# tardis_client.py
import requests
import pandas as pd
from datetime import datetime, timedelta
import time
import os
from dotenv import load_dotenv

load_dotenv()

class TardisClient:
    BASE_URL = "https://tardis.dev/api/v1"
    API_TOKEN = os.getenv("TARDIS_API_TOKEN")  # 環境変数から取得
    
    def __init__(self):
        self.session = requests.Session()
        self.session.headers.update({
            "Authorization": f"Bearer {self.API_TOKEN}",
            "Content-Type": "application/json"
        })
    
    def get_okx_perpetual_symbols(self):
        """OKX 永続契約のシンボル一覧取得"""
        url = f"{self.BASE_URL}/exchange-symbols/oku"
        response = self.session.get(url)
        response.raise_for_status()
        symbols = response.json()
        
        # 永続契約(USDT-M Futures)のみをフィルタリング
        perpetual_symbols = [
            s for s in symbols 
            if "USDT" in s.get("symbol", "") and "swap" in s.get("symbol", "").lower()
        ]
        return perpetual_symbols
    
    def download_l2_orderbook(
        self, 
        symbol: str, 
        start_date: datetime, 
        end_date: datetime,
        symbol_id: str = "okx"
    ):
        """
        OKX L2 深度データをダウンロード
        
        Parameters:
            symbol: 例 "BTC-USDT-SWAP"
            start_date: 開始日時
            end_date: 終了日時
            symbol_id: 取引所識別子
        """
        # ミリ秒タイムスタンプに変換
        from_ts = int(start_date.timestamp() * 1000)
        to_ts = int(end_date.timestamp() * 1000)
        
        url = f"{self.BASE_URL}/historical/{symbol_id}/{symbol}/orderbook-levels"
        params = {
            "from": from_ts,
            "to": to_ts,
            "limit": 10000,  # 1リクエストあたりの最大件数
            "format": "json"
        }
        
        all_data = []
        pagination_token = None
        
        print(f"📥 {symbol} L2深度データ取得開始: {start_date} → {end_date}")
        
        while True:
            if pagination_token:
                params["page_token"] = pagination_token
            
            response = self.session.get(url, params=params)
            
            if response.status_code == 429:
                # レートリミット回避
                retry_after = int(response.headers.get("Retry-After", 60))
                print(f"⏳ レート制限: {retry_after}秒待機")
                time.sleep(retry_after)
                continue
            
            response.raise_for_status()
            data = response.json()
            
            if "data" in data:
                all_data.extend(data["data"])
            
            # ページネーション処理
            pagination_token = data.get("meta", {}).get("next_page_token")
            if not pagination_token:
                break
            
            # 次のリクエスト前に待機(レートリミット対策)
            time.sleep(0.1)
        
        print(f"✅ 取得完了: {len(all_data):,}件のレコードを処理")
        return all_data
    
    def save_to_csv(self, data: list, filename: str):
        """JSON データを CSV に変換して保存"""
        if not data:
            print("⚠️ 保存するデータがありません")
            return None
        
        df = pd.DataFrame(data)
        
        # タイムスタンプをdatetimeに変換
        if "timestamp" in df.columns:
            df["datetime"] = pd.to_datetime(df["timestamp"], unit="ms")
        
        # 必須カラムの順序整列
        columns_order = ["datetime", "timestamp", "symbol", "side", "price", "size"]
        available_cols = [c for c in columns_order if c in df.columns]
        df = df[available_cols]
        
        filepath = f"data/{filename}"
        df.to_csv(filepath, index=False)
        print(f"💾 保存完了: {filepath} ({len(df):,}行)")
        return filepath

使用例

if __name__ == "__main__": client = TardisClient() # BTC-USDT-SWAP の過去1時間のデータを取得 end_time = datetime.utcnow() start_time = end_time - timedelta(hours=1) btc_data = client.download_l2_orderbook( symbol="BTC-USDT-SWAP", start_date=start_time, end_date=end_time ) if btc_data: client.save_to_csv(btc_data, "btc_usdt_swap_orderbook.csv")

方法2:WebSocket リアルタイム取得

# tardis_websocket.py
import asyncio
import aiohttp
import json
import nest_asyncio
from datetime import datetime
from typing import Callable, Optional
import pandas as pd

nest_asyncio.apply()  # Jupyter環境での互換性対応

class TardisWebSocketClient:
    WS_URL = "wss://tardis.dev/ws"
    
    def __init__(self, api_token: str):
        self.api_token = api_token
        self.websocket = None
        self.buffer = []
        self.max_buffer_size = 10000
        self.is_connected = False
    
    async def connect(self):
        """WebSocket 接続確立"""
        self.websocket = await aiohttp.ClientSession().ws_connect(
            self.WS_URL,
            headers={"Authorization": f"Bearer {self.api_token}"}
        )
        self.is_connected = True
        print("🔌 WebSocket 接続完了")
    
    async def subscribe_orderbook(
        self, 
        exchange: str = "okx",
        symbol: str = "BTC-USDT-SWAP"
    ):
        """
        L2 Orderbook チャンネルにサブスクライブ
        """
        subscribe_message = {
            "type": "subscribe",
            "channel": "orderbook",
            "exchange": exchange,
            "symbol": symbol
        }
        
        await self.websocket.send_json(subscribe_message)
        print(f"📡 購読開始: {exchange}/{symbol} orderbook")
        
        # 購読確認メッセージを受信
        response = await self.websocket.receive()
        ack = json.loads(response.data)
        print(f"📨 購読確認: {ack}")
        
        return ack
    
    async def stream_orderbook(self, duration_seconds: int = 300):
        """
        指定秒数分の板情報をストリーミング
        
        Parameters:
            duration_seconds: 取得時間(秒)
        """
        start_time = datetime.utcnow()
        message_count = 0
        
        async for msg in self.websocket:
            if msg.type == aiohttp.WSMsgType.CLOSED:
                print("❌ WebSocket 接続が切断されました")
                break
            
            if msg.type == aiohttp.WSMsgType.ERROR:
                print(f"⚠️ WebSocket エラー: {msg.data}")
                continue
            
            try:
                data = json.loads(msg.data)
                message_count += 1
                
                # 板情報をバッファに追加
                if data.get("type") == "data":
                    orderbook_snapshot = data.get("data", {})
                    self.buffer.append({
                        "timestamp": datetime.utcnow().isoformat(),
                        "symbol": data.get("symbol"),
                        "bids": orderbook_snapshot.get("bids", []),
                        "asks": orderbook_snapshot.get("asks", []),
                        "best_bid": orderbook_snapshot.get("bids", [[None]])[0][0],
                        "best_ask": orderbook_snapshot.get("asks", [[None]])[0][0]
                    })
                    
                    # バッファ上限に達したらflush
                    if len(self.buffer) >= self.max_buffer_size:
                        self._flush_buffer()
                
                # 経過時間チェック
                elapsed = (datetime.utcnow() - start_time).total_seconds()
                if elapsed >= duration_seconds:
                    print(f"⏹ 指定時間 ({duration_seconds}秒) 到達")
                    break
                    
            except json.JSONDecodeError as e:
                print(f"⚠️ JSON パースエラー: {e}")
                continue
        
        # 最終バッファをflush
        self._flush_buffer()
        print(f"✅ ストリーミング完了: {message_count:,}件のメッセージを受信")
        
        return self.buffer
    
    def _flush_buffer(self):
        """バッファをCSVに保存"""
        if not self.buffer:
            return
        
        df = pd.DataFrame(self.buffer)
        filename = f"data/orderbook_{datetime.utcnow().strftime('%Y%m%d_%H%M%S')}.csv"
        df.to_csv(filename, index=False)
        print(f"💾 中間保存: {filename} ({len(self.buffer):,}行)")
        self.buffer = []  # バッファクリア
    
    async def close(self):
        """接続切断"""
        if self.websocket:
            await self.websocket.close()
            self.is_connected = False
            print("🔌 WebSocket 切断完了")

非同期メイン処理

async def main(): import os from dotenv import load_dotenv load_dotenv() api_token = os.getenv("TARDIS_API_TOKEN") client = TardisWebSocketClient(api_token) try: await client.connect() await client.subscribe_orderbook("okx", "BTC-USDT-SWAP") await client.stream_orderbook(duration_seconds=60) # 60秒間取得 finally: await client.close()

実行

if __name__ == "__main__": asyncio.run(main())

HolySheheep AI でのデータ分析

取得した L2 深度データは、HolySheheep AI を活用することで高度な分析が可能です。HolySheheep AI は業界最安水準の料金耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿準耿