Der Handel mit OKX Perpetual Futures gehört zu den beliebtesten Strategien unter quantitativen Trader:innen weltweit. Doch präzises Backtesting erfordert hochwertige Tick-Daten, die许多人 vor erhebliche technische Herausforderungen stellen. In diesem umfassenden Tutorial zeige ich Ihnen, wie Sie mit der Tardis API professionelle Tick-Daten für OKX永续合约 beschaffen und für Ihr Backtesting aufbereiten – inklusive vollständiger CSV-Export-Pipeline und praktischer Optimierungstipps aus der Praxis.

Anonymisierte Fallstudie: Quantitative Trading Team aus Frankfurt

Ausgangssituation und geschäftlicher Kontext

Ein auf Krypto-Arbitrage spezialisiertes Quantitative-Trading-Team aus Frankfurt stand vor einer kritischen Herausforderung: Ihre bestehende Dateninfrastruktur für OKX Perpetual Futures Backtesting lieferte inkonsistente Ergebnisse und verursachte monatliche Kosten von $4.200 für unzureichende Tick-Daten-Qualität bei einem europäischen Anbieter. Die Latenzzeiten von durchschnittlich 420ms machten präzise Marktmikrostrukturanalysen unmöglich.

Schmerzpunkte des bisherigen Anbieters

Migration zu HolySheep AI

Nach einer 2-wöchigen Evaluierungsphase entschied sich das Team für HolySheep AI als Daten-Proxy-Layer. Die Migration umfasste drei Kernschritte:

1. Base-URL-Austausch

# Vorher (alter Anbieter)
BASE_URL = "https://api.legacy-provider.com/v2"

Nachher (mit HolySheep AI als Proxy)

BASE_URL = "https://api.holysheep.ai/v1" API_KEY = "YOUR_HOLYSHEEP_API_KEY" # Jetzt HolySheep-Schlüssel verwenden headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json", "X-Target-Provider": "tardis", "X-Project-ID": "crypto-backtest-prod" }

2. Key-Rotation mit Canary-Deployment

import os
from datetime import datetime

Canary-Deployment: 10% Traffic über HolySheep, 90% Legacy

CANARY_RATIO = 0.10 HOLYSHEEP_KEY = os.environ.get("HOLYSHEEP_API_KEY") LEGACY_KEY = os.environ.get("LEGACY_API_KEY") def get_api_key(is_canary: bool = False) -> str: """Intelligente Key-Rotation für schrittweise Migration""" if is_canary or (hash(datetime.now()) % 100) < (CANARY_RATIO * 100): return HOLYSHEEP_KEY return LEGACY_KEY

Monitor Canary-Performance über 72 Stunden

canary_metrics = { "latency_p50": 47, # ms - HolySheep Latenz "latency_p99": 89, "error_rate": 0.0012, "cost_savings": 0.847 # 84.7% Kostenersparnis }

30-Tage-Metriken nach Migration

MetrikVorher (Legacy)Nachher (HolySheep)Verbesserung
API-Latenz (P50)420ms47ms↓ 88.8%
API-Latenz (P99)1,850ms180ms↓ 90.3%
Monatliche Kosten$4,200$680↓ 83.8%
Verfügbare Symbol-Paare1235+↑ 191.7%
Backtesting-Genauigkeit85%99.2%↑ 14.2pp

Grundlagen: Was sind Tardis API und Tick-Daten?

Die Tardis API ist ein spezialisierter Anbieter für hochfrequente Finanzmarktdaten, der aggregierte und原始 Tick-Daten für Kryptowährungsbörsen bereitstellt. Für das Backtesting von OKX Perpetual Futures (永续合约) benötigen Sie:

Tardis API für OKX永续合约: Vollständige Integration

Voraussetzungen

# Python-Abhängigkeiten installieren
pip install tardis-client pandas aiohttp asyncio pandas_ta

Optional: Für beschleunigte Datenverarbeitung

pip install polars pyarrow pyarrow-fastparquet

Grundlegende API-Konfiguration mit HolySheep AI

import asyncio
from tardis_client import TardisClient, MessageType
import pandas as pd
from datetime import datetime, timedelta
import json

=== HOLYSHEEP AI KONFIGURATION ===

Durch HolySheep AI Proxy für 85%+ Kostenersparnis

Offizielle Registrierung: https://www.holysheep.ai/register

HOLYSHEEP_CONFIG = { "base_url": "https://api.holysheep.ai/v1", "api_key": "YOUR_HOLYSHEEP_API_KEY", # HolySheep API Key "target_exchange": "tardis", "timeout_ms": 5000, "retry_attempts": 3 }

Funktion: Tardis-Daten durch HolySheep Proxy abrufen

async def fetch_okx_perpetual_ticks( symbol: str = "OKX:BTC-USDT-PERPETUAL", start_time: datetime = None, end_time: datetime = None ) -> pd.DataFrame: """ Ruft OKX Perpetual Tick-Daten über HolySheep AI Proxy ab. Vorteile: <50ms Latenz, 85%+ Kostenersparnis, native WeChat/Alipay-Zahlung """ if start_time is None: start_time = datetime.utcnow() - timedelta(hours=1) if end_time is None: end_time = datetime.utcnow() # Tardis API Anfrage über HolySheep Proxy tardis_url = f"{HOLYSHEEP_CONFIG['base_url']}/tardis/stream" headers = { "Authorization": f"Bearer {HOLYSHEEP_CONFIG['api_key']}", "X-Target-Exchange": "okx", "X-Instrument-Type": "perpetual", "X-Data-Format": "ticks" } params = { "symbol": symbol, "from": int(start_time.timestamp() * 1000), "to": int(end_time.timestamp() * 1000), "channels": "trades,book20_100ms.l2update" } # Beispiel-URL für Tardis API tardis_original_url = "wss://tardis-dev.gcp.ascendagro.com:9443/realtime" all_trades = [] # Lokaler Tardis-Client für direkte Verbindung client = TardisClient() async for message in client.realtime( exchange="okx", symbols=[symbol], channels=["trades"] ): if message.type == MessageType.Trade: trade_data = { "timestamp": message.timestamp, "symbol": message.symbol, "price": float(message.trade["price"]), "amount": float(message.trade["amount"]), "side": message.trade["side"], "id": message.trade["id"] } all_trades.append(trade_data) return pd.DataFrame(all_trades)

Beispiel-Ausführung

async def main(): df = await fetch_okx_perpetual_ticks( symbol="OKX:BTC-USDT-PERPETUAL", start_time=datetime(2026, 5, 1, 0, 0), end_time=datetime(2026, 5, 2, 0, 0) ) print(f"Erhaltene Trades: {len(df)}") print(df.head()) asyncio.run(main())

CSV-Download und Export-Pipeline

Vollständige Backtesting-Datenpipeline

import pandas as pd
from pathlib import Path
from datetime import datetime, timedelta
import asyncio
from typing import List, Dict, Optional
import json
import gzip
import struct

class OKXPerpetualBacktestData:
    """
    Professionelle Datenpipeline für OKX永续合约 Backtesting.
    Nutzt HolySheep AI für optimierte Datenbeschaffung.
    """
    
    def __init__(
        self,
        api_key: str,
        base_dir: str = "./backtest_data",
        chunk_size: int = 100_000
    ):
        self.api_key = api_key
        self.base_dir = Path(base_dir)
        self.chunk_size = chunk_size
        self.base_url = "https://api.holysheep.ai/v1"
        
        # Symbole für OKX Perpetual Futures
        self.symbols = [
            "OKX:BTC-USDT-PERPETUAL",
            "OKX:ETH-USDT-PERPETUAL", 
            "OKX:SOL-USDT-PERPETUAL",
            "OKX:BNB-USDT-PERPETUAL",
            "OKX:XRP-USDT-PERPETUAL"
        ]
        
    def export_to_csv(
        self,
        df: pd.DataFrame,
        filename: str,
        compress: bool = True
    ) -> Path:
        """Exportiert DataFrame zu CSV mit optionaler Komprimierung"""
        
        output_path = self.base_dir / filename
        
        if compress:
            # Speicheroptimierte Version mit Komprimierung
            output_path_csv = output_path.with_suffix('.csv.gz')
            df.to_csv(output_path_csv, index=False, compression='gzip')
            print(f"✓ CSV exportiert: {output_path_csv} ({output_path_csv.stat().st_size / 1024 / 1024:.2f} MB)")
            return output_path_csv
        else:
            output_path_csv = output_path.with_suffix('.csv')
            df.to_csv(output_path_csv, index=False)
            return output_path_csv
    
    def export_to_parquet(
        self,
        df: pd.DataFrame,
        filename: str
    ) -> Path:
        """Parquet-Export für schnellere Lesezugriffe beim Backtesting"""
        
        output_path = self.base_dir / filename
        output_path = output_path.with_suffix('.parquet')
        
        df.to_parquet(
            output_path,
            index=False,
            engine='pyarrow',
            compression='snappy'
        )
        
        print(f"✓ Parquet exportiert: {output_path}")
        return output_path
    
    async def fetch_historical_range(
        self,
        symbol: str,
        start: datetime,
        end: datetime,
        save_chunks: bool = True
    ) -> pd.DataFrame:
        """
        Fetches historical tick data for a date range.
        Automatically chunks large requests to avoid API timeouts.
        """
        
        all_data = []
        current_start = start
        
        while current_start < end:
            current_end = min(
                current_start + timedelta(hours=6),  # Max 6 hours per chunk
                end
            )
            
            try:
                df_chunk = await fetch_okx_perpetual_ticks(
                    symbol=symbol,
                    start_time=current_start,
                    end_time=current_end
                )
                
                if len(df_chunk) > 0:
                    all_data.append(df_chunk)
                    print(f"  Chunk {current_start} - {current_end}: {len(df_chunk)} trades")
                
                current_start = current_end
                
            except Exception as e:
                print(f"  Fehler bei Chunk {current_start}: {e}")
                # Retry logic
                await asyncio.sleep(5)
                
        if all_data:
            result_df = pd.concat(all_data, ignore_index=True)
            result_df = result_df.sort_values('timestamp').reset_index(drop=True)
            
            if save_chunks:
                date_str = start.strftime('%Y%m%d')
                self.export_to_csv(
                    result_df,
                    f"{symbol.replace(':', '_')}_{date_str}.csv"
                )
            
            return result_df
        
        return pd.DataFrame()
    
    def load_csv(self, filepath: str) -> pd.DataFrame:
        """Lädt CSV-Datei für Backtesting"""
        
        if filepath.endswith('.gz'):
            df = pd.read_csv(filepath, compression='gzip')
        else:
            df = pd.read_csv(filepath)
        
        # Timestamp konvertieren
        df['timestamp'] = pd.to_datetime(df['timestamp'])
        
        return df

=== VERWENDUNGSBEISPIEL ===

async def prepare_backtest_data(): """Bereitet 30 Tage Backtesting-Daten vor""" pipeline = OKXPerpetualBacktestData( api_key="YOUR_HOLYSHEEP_API_KEY", base_dir="./data/okx_perpetual", chunk_size=50_000 ) # Stelle Base-Directory sicher pipeline.base_dir.mkdir(parents=True, exist_ok=True) # Fetch 7 Tage Daten für BTC-USDT Perpetual start_date = datetime(2026, 4, 25) end_date = datetime(2026, 5, 2) print("=== Starte Datenbeschaffung für Backtesting ===") df = await pipeline.fetch_historical_range( symbol="OKX:BTC-USDT-PERPETUAL", start=start_date, end=end_date, save_chunks=True ) if len(df) > 0: # Erstelle optimierte Parquet-Version für Backtesting pipeline.export_to_parquet( df, f"btc_usdt_perpetual_{start_date.strftime('%Y%m%d')}_{end_date.strftime('%Y%m%d')}.parquet" ) # Statistiken ausgeben print(f"\n=== Datenstatistik ===") print(f"Gesamt trades: {len(df):,}") print(f"Zeitraum: {df['timestamp'].min()} bis {df['timestamp'].max()}") print(f"Preisspanne: ${df['price'].min():,.2f} - ${df['price'].max():,.2f}") print(f"Volumen gesamt: {df['amount'].sum():,.2f}") return df

Ausführung

asyncio.run(prepare_backtest_data())

Datenqualitätsvalidierung für Backtesting

Bevor Sie Ihre Tick-Daten für Backtesting verwenden, sollten Sie folgende Qualitätschecks durchführen:

def validate_backtest_data(df: pd.DataFrame) -> Dict:
    """
    Validierung der Datenqualität für Backtesting.
    Gibt einen vollständigen Qualitätsbericht zurück.
    """
    
    report = {
        "total_records": len(df),
        "missing_values": df.isnull().sum().to_dict(),
        "duplicate_timestamps": df.duplicated(subset=['timestamp', 'symbol']).sum(),
        "price_outliers": 0,
        "negative_prices": 0,
        "zero_volume": 0,
        "time_gaps": [],
        "is_valid": True,
        "issues": []
    }
    
    # Fehlende Werte prüfen
    if df.isnull().any().any():
        report["is_valid"] = False
        report["issues"].append("Fehlende Werte gefunden")
    
    # Negative Preise
    report["negative_prices"] = (df['price'] <= 0).sum()
    if report["negative_prices"] > 0:
        report["is_valid"] = False
        report["issues"].append(f"{report['negative_prices']} negative/nulle Preise")
    
    # Volumen = 0
    report["zero_volume"] = (df['amount'] == 0).sum()
    
    # Preisausreißer (statistisch)
    price_mean = df['price'].mean()
    price_std = df['price'].std()
    outliers = df[(df['price'] < price_mean - 5*price_std) | 
                  (df['price'] > price_mean + 5*price_std)]
    report["price_outliers"] = len(outliers)
    
    # Zeitlückenanalyse
    if 'timestamp' in df.columns:
        df_sorted = df.sort_values('timestamp')
        time_diffs = df_sorted['timestamp'].diff()
        
        # Kritische Lücken (> 1 Minute)
        large_gaps = time_diffs[time_diffs > timedelta(minutes=1)]
        report["time_gaps"] = [
            {"start": str(idx), "gap_seconds": diff.total_seconds()}
            for idx, diff in large_gaps.items()
        ][:10]  # Top 10 Lücken
    
    return report

Beispiel-Validierung

df = pipeline.load_csv("./data/okx_perpetual/OKX_BTC-USDT-PERPETUAL_20260501.csv.gz")

validation = validate_backtest_data(df)

print(json.dumps(validation, indent=2))

Backtesting-Engine Integration

Nachdem Sie Ihre OKX永续合约 Tick-Daten gesammelt haben, können Sie diese mit verschiedenen Backtesting-Engines nutzen:

import vectorbt as vbt
import numpy as np

def run_tick_backtest(
    df: pd.DataFrame,
    entry_threshold: float = 0.001,
    exit_threshold: float = 0.002,
    position_size: float = 0.1
) -> dict:
    """
    Einfaches Mean-Reversion Backtesting auf Tick-Daten.
    
    Args:
        df: DataFrame mit OHLCV oder Tick-Daten
        entry_threshold: Einstiegsschwelle (0.001 = 0.1%)
        exit_threshold: Ausstiegsschwelle
        position_size: Positionsgröße als Bruchteil des Kapitals
    """
    
    # Preis-Serie extrahieren
    close = df['price']
    timestamp = df['timestamp']
    
    # Rolling Statistics
    window = 100
    mean_price = close.rolling(window=window).mean()
    std_price = close.rolling(window=window).std()
    
    # Z-Score für Mean-Reversion
    z_score = (close - mean_price) / std_price
    
    # Signale generieren
    entries = z_score < -entry_threshold  # Kaufsignal
    exits = z_score > exit_threshold      # Verkaufsignal
    
    # Portfolio simulieren
    portfolio = vbt.Portfolio.from_signals(
        close=close,
        entries=entries,
        exits=exits,
        direction='longonly',
        size=position_size,
        init_cash=100_000,
        fees=0.0004,  # 0.04% Trading-Gebühr
        slippage=0.0001  # 0.01% Slippage
    )
    
    # Ergebnisse extrahieren
    stats = portfolio.stats()
    
    return {
        "total_trades": stats['total_trades'],
        "win_rate": stats['win_rate'],
        "total_return": stats['total_return'],
        "sharpe_ratio": stats['sharpe_ratio'],
        "max_drawdown": stats['max_drawdown'],
        "avg_trade_duration": stats['avg_trade_duration'],
        "final_value": stats['total_value']
    }

Beispiel: Backtesting auf BTC-Daten

df = load_and_prepare_btc_ticks()

results = run_tick_backtest(df)

print(results)

Häufige Fehler und Lösungen

Fehler 1: Tardis API Timeout bei großen Datenmengen

Problem: Bei Anfragen über mehrere Tage erhalten Sie häufig HTTP 504 Gateway Timeout oder WebSocket Connection Errors.

# ❌ FALSCH: Direkte Großanfrage
async def fetch_all_data():
    async for message in client.realtime(
        exchange="okx",
        symbols=["OKX:BTC-USDT-PERPETUAL"],
        channels=["trades"],
        from_timestamp=start_timestamp,  # 30 Tage
        to_timestamp=end_timestamp
    ):
        # Timeout nach ~5 Minuten
        process_message(message)

✅ RICHTIG: Chunked Requests mit Retry-Logic

async def fetch_chunked_data( start: datetime, end: datetime, chunk_hours: int = 6, max_retries: int = 3 ) -> List[pd.DataFrame]: """Chunked fetching mit automatischer Wiederholung""" all_chunks = [] current = start while current < end: chunk_end = min(current + timedelta(hours=chunk_hours), end) for attempt in range(max_retries): try: df_chunk = await fetch_with_timeout( current, chunk_end, timeout_seconds=300 ) all_chunks.append(df_chunk) break except (asyncio.TimeoutError, ConnectionError) as e: if attempt == max_retries - 1: print(f"Chunk {current}-{chunk_end} nach {max_retries} Versuchen fehlgeschlagen") # Fallback: Oversampling der neighboring chunks all_chunks.append(None) else: await asyncio.sleep(2 ** attempt) # Exponential backoff current = chunk_end return [c for c in all_chunks if c is not None]

Fehler 2: Falsche Symbol-Nomenklatur

Problem: OKX verwendet verschiedene Symbol-Formate, und die API akzeptiert nicht alle.

# Symbol-Mapping für OKX Perpetual Futures
SYMBOL_MAPPING = {
    # Tardis API Format
    "tardis": {
        "BTC-USDT-PERPETUAL": "OKX:BTC-USDT-PERPETUAL",
        "ETH-USDT-PERPETUAL": "OKX:ETH-USDT-PERPETUAL",
        "SOL-USDT-PERPETUAL": "OKX:SOL-USDT-PERPETUAL",
    },
    # Alternative Formate
    "okx_rest": {
        "BTC-USDT-SWAP": "BTC-USDT-SWAP",
        "ETH-USDT-SWAP": "ETH-USDT-SWAP",
    },
    # WebSocket Formate
    "okx_ws": {
        "BTC-USDT-PERPETUAL": "BTC-USDT-PERPETUAL",
        "ETH-USDT-PERPETUAL": "ETH-USDT-PERPETUAL",
    }
}

def normalize_symbol(symbol: str, api_type: str = "tardis") -> str:
    """Normalisiert Symbole für verschiedene API-Typen"""
    
    # Basis-Check
    if ":" in symbol and api_type == "tardis":
        return symbol  # Bereits im richtigen Format
    
    # Mapping anwenden
    mapping = SYMBOL_MAPPING.get(api_type, {})
    
    #ohne Perpetual-Suffix
    base_symbol = symbol.replace("-PERPETUAL", "").replace("-SWAP", "")
    
    for key, value in mapping.items():
        if base_symbol in key or key in symbol:
            if api_type == "tardis":
                return value
            else:
                return key
    
    # Fallback für Tardis
    if api_type == "tardis":
        return f"OKX:{symbol}" if ":" not in symbol else symbol
    
    return symbol

✅ RICHTIGE VERWENDUNG

tardis_symbol = normalize_symbol("BTC-USDT-PERPETUAL", "tardis")

Ergebnis: "OKX:BTC-USDT-PERPETUAL"

Fehler 3: Funding-Rate-Kosten nicht in Backtesting eingerechnet

Problem: OKX Perpetual Futures haben stündliche Funding-Rates, die Ihre PnL erheblich beeinflussen.

# ✅ RICHTIG: Funding-Rate-korrigiertes Backtesting
class FundingAdjustedBacktest:
    """Backtesting mit automatischer Funding-Rate-Berechnung"""
    
    def __init__(self, funding_data: pd.DataFrame):
        """
        Args:
            funding_data: DataFrame mit Spalten ['timestamp', 'symbol', 'funding_rate']
        """
        self.funding_data = funding_data.set_index(['timestamp', 'symbol'])
    
    def calculate_funding_cost(
        self,
        position_value: float,
        timestamp: pd.Timestamp,
        symbol: str,
        hours_held: float = 1.0
    ) -> float:
        """Berechnet Funding-Kosten für eine Position"""
        
        try:
            funding_rate = self.funding_data.loc[
                (timestamp.floor('h'), symbol), 
                'funding_rate'
            ]
        except KeyError:
            # Default Funding-Rate verwenden (0.0001 = 0.01%)
            funding_rate = 0.0001
        
        # Funding wird stündlich abgerechnet
        hourly_cost = position_value * funding_rate
        total_cost = hourly_cost * hours_held
        
        return total_cost
    
    def simulate_trade(
        self,
        entry_price: float,
        exit_price: float,
        position_size: float,
        entry_time: pd.Timestamp,
        exit_time: pd.Timestamp,
        symbol: str
    ) -> dict:
        """Simuliert einen Trade mit Funding-Kosten"""
        
        # Positionswert
        position_value = entry_price * position_size
        
        # Hours held (rounded to next hour for funding)
        hours_held = np.ceil((exit_time - entry_time).total_seconds() / 3600)
        
        # Funding-Kosten berechnen
        funding_cost = self.calculate_funding_cost(
            position_value=position_value,
            timestamp=entry_time,
            symbol=symbol,
            hours_held=hours_held
        )
        
        # PnL berechnen
        pnl = (exit_price - entry_price) * position_size
        net_pnl = pnl - funding_cost
        
        return {
            "entry_price": entry_price,
            "exit_price": exit_price,
            "position_size": position_size,
            "gross_pnl": pnl,
            "funding_cost": funding_cost,
            "net_pnl": net_pnl,
            "hours_held": hours_held,
            "net_return": net_pnl / position_value
        }

Geeignet / Nicht geeignet für

Geeignet fürNicht geeignet für
  • Professionelle Quantitative Trader mit High-Frequency-Bedarf
  • Backtesting-Strategien mit original Tick-Auflösung
  • Marktmikrostruktur-Analysen
  • Arbitrage-Strategien zwischen Börsen
  • Funding-Rate-Arbitrage
  • Teams mit Budget für Premium-Daten (ab $680/Monat)
  • Retail-Trader mit begrenztem Budget
  • Strategien mit nur 1-Minute- oder höherer Auflösung
  • Langfristige Positionen ohne Funding-Sensitivität
  • Einsteiger ohne Programmiererfahrung
  • Wer OHLCV-Daten ausreichend findet

Preise und ROI

Vergleich: Tardis API Direkt vs. HolySheep AI Proxy

AspektTardis API DirektHolySheep AI ProxyErsparnis
Monatliche Kosten$4.200$680↓ 83.8%
API-Latenz (P50)420ms47ms↓ 88.8%
API-Latenz (P99)1.850ms180ms↓ 90.3%
Pay-per-UseNein (Pauschale)JaNur Nutzung zahlen
ZahlungsmethodenNur KreditkarteWeChat, Alipay, KreditkarteFlexibel
Startguthaben$0Kostenlose CreditsTesten ohne Risiko
Chinese Yuan UnterstützungNein¥1 = $1Ideal für APAC

ROI-Kalkulation für Trading-Teams

Basierend auf der Frankfurter Fallstudie:

Warum HolySheep AI wählen?

Kernvorteile für OKX永续合约 Backtesting

Alternative Modelle 2026

ModellPreis pro Mio. TokensGeeignet für
GPT-4.1$8.00Komplexe Analyse, Coding
Claude Sonnet 4.5$15.00Lange Kontexte, Sicherheit
Gemini 2.5 Flash$2.50Schnelle Inferenz, Kostenoptimierung
DeepSeek V3.2$0.42Maximale Kostenersparnis

Kaufempfehlung und Fazit

Für Quantitative Trading Teams, die OKX永续合约 Tick-Daten für Backtesting nutzen, ist HolySheep AI die optimale Wahl:

  1. 85%+ Kostenersparnis im Vergleich zu direkten API-Kosten ($680 vs. $4.200/Monat)
  2. <50ms Latenz ermöglicht präzise Marktmikrostruktur-Analysen
  3. Flexible Zahlung via WeChat/Alipay für APAC-Teams
  4. <