Die Vorhersage von Bitcoin-Kursbewegungen gehört zu den faszinierendsten Herausforderungen im quantitativen Trading. In diesem Tutorial zeige ich Ihnen, wie Sie Tardis-Daten – hochauflösende Kryptomarkt-Daten – nutzen, um ein LSTM-Netzwerk (Long Short-Term Memory) zur BTC-Kurzzeitprognose zu trainieren. Als Backend für die Datenverarbeitung und Modellschulung nutzen wir die leistungsstarke HolySheep AI API.

HolySheep vs. offizielle APIs vs. andere Relay-Dienste

Kriterium HolySheep AI Offizielle APIs (OpenAI/Anthropic) Andere Relay-Dienste
Preis pro 1M Tokens GPT-4.1: $8 | Claude 4.5: $15 | Gemini 2.5 Flash: $2.50 | DeepSeek V3.2: $0.42 GPT-4o: $15 | Claude 3.5: $18 $5-20 (variiert)
Währung ¥1 = $1 (85%+ günstiger für CNY-Nutzer) Nur USD Oft nur USD
Bezahlmethoden WeChat Pay, Alipay, Kreditkarte Nur Kreditkarte/USD Kreditkarte (eingeschränkt)
Latenz <50ms 100-300ms 80-200ms
Kostenlose Credits ✅ Ja, bei Registrierung ❌ Nein Selten
Tardis-Integration ✅ Nativ über API ❌ Nicht verfügbar ⚠️ Manuell
China-Verfügbarkeit ✅ 100% stabil ❌ Blockiert ⚠️ Instabil

Geeignet / Nicht geeignet für

✅ Perfekt geeignet für:

❌ Nicht geeignet für:

Voraussetzungen

Architektur: Tardis → HolySheep → LSTM → BTC-Prognose


┌─────────────────┐     ┌──────────────────┐     ┌─────────────────┐
│   Tardis.io     │     │   HolySheep AI   │     │   TensorFlow    │
│  (Marktdaten)   │ ──▶ │   (Text/LLM)     │ ──▶ │   (LSTM-Modell) │
│                 │     │                  │     │                 │
│ • Orderbooks    │     │ • Feature-       │     │ • Training      │
│ • Trades        │     │   Extraktion     │     │ • Validation    │
│ • OHLCV         │     │ • Anomalie-      │     │ • Inferenz      │
└─────────────────┘     │   Erkennung      │     └─────────────────┘
                        └──────────────────┘              │
                                                          ▼
                                              ┌─────────────────────┐
                                              │  BTC-Preisprognose  │
                                              │  (±0.5-2% Genauig-  │
                                              │   keit möglich)     │
                                              └─────────────────────┘

Schritt 1: Tardis-Daten abrufen

Tardis bietet historische und Echtzeit-Marktdaten von über 40 Kryptobörsen. Für BTC benötigen wir Trades und Orderbook-Daten.

# tardis_client.py
import requests
import pandas as pd
from datetime import datetime, timedelta

class TardisDataFetcher:
    def __init__(self, api_key: str):
        self.api_key = api_key
        self.base_url = "https://api.tardis.dev/v1"
    
    def get_btc_trades(self, exchange: str = "binance", 
                       since: datetime = None, 
                       until: datetime = None,
                       limit: int = 10000):
        """
        Ruft BTC-Trade-Daten von Tardis ab.
        
        Args:
            exchange: Börse (binance, coinbase, kraken, etc.)
            since: Startzeit
            until: Endzeit
            limit: Maximale Anzahl Trades
        
        Returns:
            DataFrame mit Trades
        """
        if since is None:
            since = datetime.now() - timedelta(hours=24)
        if until is None:
            until = datetime.now()
        
        # Tardis API-Endpunkt
        url = f"{self.base_url}/feeds/{exchange}:spot"
        
        params = {
            "api_key": self.api_key,
            "symbol": "BTC-USD",  # oder BTC-USDT für Binance
            "since": int(since.timestamp() * 1000),
            "until": int(until.timestamp() * 1000),
            "limit": limit,
            "format": "json"
        }
        
        response = requests.get(url, params=params)
        response.raise_for_status()
        
        trades = response.json()
        
        # In DataFrame konvertieren
        df = pd.DataFrame(trades)
        df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
        
        print(f"✅ {len(df)} Trades abgerufen von {exchange}")
        return df
    
    def get_orderbook_snapshot(self, exchange: str = "binance", 
                               symbol: str = "BTC-USDT") -> dict:
        """
        Ruft aktuellen Orderbook-Snapshot ab.
        """
        url = f"{self.base_url}/feeds/{exchange}:spot"
        
        params = {
            "api_key": self.api_key,
            "symbol": symbol,
            "type": "book_snapshot",
            "format": "json"
        }
        
        response = requests.get(url, params=params)
        return response.json()

Verwendung

tardis = TardisDataFetcher(api_key="YOUR_TARDIS_API_KEY") trades_df = tardis.get_btc_trades(exchange="binance", limit=50000) print(trades_df.head())

Schritt 2: Feature-Engineering mit HolySheep AI

Der Clou: Wir nutzen HolySheep AI, um komplexe Marktindikatoren zu berechnen und Anomalien zu erkennen, die in rohen Daten schwer zu finden sind.

# feature_engineering.py
import pandas as pd
import numpy as np
from holy_sheep_client import HolySheepClient

class BTCFeatureEngineer:
    def __init__(self, holy_sheep_key: str):
        self.client = HolySheepClient(api_key=holy_sheep_key)
    
    def calculate_technical_indicators(self, df: pd.DataFrame) -> pd.DataFrame:
        """
        Berechnet technische Indikatoren für LSTM-Input.
        """
        # Basis-Indikatoren
        df['returns'] = df['price'].pct_change()
        df['log_returns'] = np.log(df['price'] / df['price'].shift(1))
        
        # Rolling Statistics
        for window in [5, 15, 30, 60]:
            df[f'sma_{window}'] = df['price'].rolling(window).mean()
            df[f'std_{window}'] = df['price'].rolling(window).std()
            df[f'volatility_{window}'] = df[f'std_{window}'] / df[f'sma_{window}']
        
        # RSI (Relative Strength Index)
        delta = df['price'].diff()
        gain = (delta.where(delta > 0, 0)).rolling(window=14).mean()
        loss = (-delta.where(delta < 0, 0)).rolling(window=14).mean()
        rs = gain / loss
        df['rsi'] = 100 - (100 / (1 + rs))
        
        # MACD
        exp1 = df['price'].ewm(span=12, adjust=False).mean()
        exp2 = df['price'].ewm(span=26, adjust=False).mean()
        df['macd'] = exp1 - exp2
        df['macd_signal'] = df['macd'].ewm(span=9, adjust=False).mean()
        df['macd_hist'] = df['macd'] - df['macd_signal']
        
        # Bollinger Bands
        df['bb_middle'] = df['price'].rolling(window=20).mean()
        bb_std = df['price'].rolling(window=20).std()
        df['bb_upper'] = df['bb_middle'] + (bb_std * 2)
        df['bb_lower'] = df['bb_middle'] - (bb_std * 2)
        df['bb_width'] = (df['bb_upper'] - df['bb_lower']) / df['bb_middle']
        
        # Volumen-Indikatoren
        if 'volume' in df.columns:
            df['volume_sma_20'] = df['volume'].rolling(window=20).mean()
            df['volume_ratio'] = df['volume'] / df['volume_sma_20']
        
        # Preis-Momentum
        df['momentum_5'] = df['price'] / df['price'].shift(5) - 1
        df['momentum_15'] = df['price'] / df['price'].shift(15) - 1
        
        return df.dropna()
    
    def analyze_market_regime(self, df: pd.DataFrame) -> pd.DataFrame:
        """
        Nutzt HolySheep AI, um Markt-Regime zu klassifizieren.
        """
        # Erstelle Text-Zusammenfassung der letzten Daten
        recent_data = df.tail(100).to_dict('records')
        summary = f"""
        BTC-Marktdaten der letzten Stunden:
        - Durchschnittspreis: {df['price'].mean():.2f}
        - Volatilität: {df['returns'].std():.4f}
        - RSI: {df['rsi'].iloc[-1]:.2f}
        - Trend: {'bullish' if df['sma_15'].iloc[-1] > df['sma_30'].iloc[-1] else 'bearish'}
        """
        
        prompt = f"""Analysiere folgende BTC-Marktdaten und klassifiziere das aktuelle Markt-Regime:
        {summary}
        
        Gib mir eine kurze Analyse (max. 100 Wörter) mit:
        1. Regime-Typ (Volatile/Range/Trending)
        2. Risiko-Level (Niedrig/Mittel/Hoch)
        3. Empfehlung für LSTM-Trainingsparameter
        """
        
        # API-Call über HolySheep (Latein-1 kodierung für Sonderzeichen)
        response = self.client.chat.completions.create(
            model="gpt-4.1",
            messages=[
                {"role": "system", "content": "Du bist ein Krypto-Marktanalyst."},
                {"role": "user", "content": prompt}
            ],
            max_tokens=200,
            temperature=0.3
        )
        
        analysis = response.choices[0].message.content
        print(f"📊 Markt-Regime-Analyse: {analysis}")
        
        # Regime-Feature hinzufügen basierend auf Analyse
        df['market_regime'] = self._parse_regime(analysis)
        
        return df
    
    def _parse_regime(self, analysis: str) -> int:
        """Parst Regime aus LLM-Antwort."""
        analysis_lower = analysis.lower()
        if 'volatile' in analysis_lower or 'hoch' in analysis_lower:
            return 2  # Hochvolatile
        elif 'range' in analysis_lower:
            return 1  # Range-bound
        else:
            return 0  # Trending

HolySheep Client-Klasse

class HolySheepClient: def __init__(self, api_key: str): self.api_key = api_key self.base_url = "https://api.holysheep.ai/v1" @property def chat(self): return ChatCompletions(self.api_key, self.base_url) class ChatCompletions: def __init__(self, api_key: str, base_url: str): self.api_key = api_key self.base_url = base_url def create(self, model: str, messages: list, max_tokens: int = 1000, temperature: float = 0.7) -> dict: import requests url = f"{self.base_url}/chat/completions" headers = { "Authorization": f"Bearer {self.api_key}", "Content-Type": "application/json" } payload = { "model": model, "messages": messages, "max_tokens": max_tokens, "temperature": temperature } response = requests.post(url, headers=headers, json=payload) response.raise_for_status() return response.json()

Verwendung

engineer = BTCFeatureEngineer(holy_sheep_key="YOUR_HOLYSHEEP_API_KEY") features_df = engineer.calculate_technical_indicators(trades_df) features_df = engineer.analyze_market_regime(features_df) print(f"✅ Feature-DataFrame erstellt: {features_df.shape}")

Schritt 3: LSTM-Modellarchitektur

# lstm_model.py
import numpy as np
import pandas as pd
from sklearn.preprocessing import MinMaxScaler
from sklearn.model_selection import train_test_split
import tensorflow as tf
from tensorflow.keras.models import Sequential
from tensorflow.keras.layers import LSTM, Dense, Dropout, Bidirectional
from tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau

class BTCLSTMModel:
    def __init__(self, sequence_length: int = 60):
        self.sequence_length = sequence_length
        self.scaler = MinMaxScaler(feature_range=(0, 1))
        self.model = None
        self.feature_columns = [
            'price', 'returns', 'log_returns',
            'sma_5', 'sma_15', 'sma_30', 'sma_60',
            'volatility_5', 'volatility_15', 'volatility_30',
            'rsi', 'macd', 'macd_signal', 'macd_hist',
            'bb_width', 'momentum_5', 'momentum_15',
            'volume_ratio', 'market_regime'
        ]
    
    def prepare_sequences(self, df: pd.DataFrame) -> tuple:
        """
        Erstellt Sequences für LSTM-Training.
        
        Args:
            df: DataFrame mit Features
        
        Returns:
            X, y: Trainingsdaten
        """
        # Features skalieren
        feature_data = df[self.feature_columns].values
        scaled_data = self.scaler.fit_transform(feature_data)
        
        X, y = [], []
        
        for i in range(self.sequence_length, len(scaled_data)):
            # Input: Alle Features der letzten sequence_length Zeitschritte
            X.append(scaled_data[i-self.sequence_length:i])
            
            # Target: Normalisierter Preis (erste Spalte)
            # Vorhersage: Nächster Preis oder Preisänderung
            target_idx = 0  # 'price' ist erste Spalte
            y.append(scaled_data[i, target_idx])
        
        return np.array(X), np.array(y)
    
    def build_model(self, input_shape: tuple) -> Sequential:
        """
        Erstellt Bidirectional LSTM-Architektur.
        """
        model = Sequential([
            # Bidirectional LSTM für bessere Pattern-Erkennung
            Bidirectional(LSTM(128, return_sequences=True), 
                         input_shape=input_shape),
            Dropout(0.3),
            
            Bidirectional(LSTM(64, return_sequences=True)),
            Dropout(0.3),
            
            LSTM(32, return_sequences=False),
            Dropout(0.2),
            
            Dense(32, activation='relu'),
            Dense(16, activation='relu'),
            Dense(1, activation='linear')  # Preis-Vorhersage
        ])
        
        model.compile(
            optimizer=tf.keras.optimizers.Adam(learning_rate=0.001),
            loss='mse',
            metrics=['mae']
        )
        
        print(model.summary())
        return model
    
    def train(self, X_train: np.ndarray, y_train: np.ndarray,
              X_val: np.ndarray, y_val: np.ndarray,
              epochs: int = 100,
              batch_size: int = 32) -> tf.keras.callbacks.History:
        """
        Trainiert das LSTM-Modell.
        """
        if self.model is None:
            self.model = self.build_model(input_shape=X_train.shape[1:])
        
        callbacks = [
            EarlyStopping(
                monitor='val_loss',
                patience=15,
                restore_best_weights=True,
                verbose=1
            ),
            ReduceLROnPlateau(
                monitor='val_loss',
                factor=0.5,
                patience=5,
                min_lr=1e-6,
                verbose=1
            )
        ]
        
        history = self.model.fit(
            X_train, y_train,
            validation_data=(X_val, y_val),
            epochs=epochs,
            batch_size=batch_size,
            callbacks=callbacks,
            verbose=1
        )
        
        return history
    
    def predict(self, X: np.ndarray) -> np.ndarray:
        """
        Macht Vorhersagen.
        """
        predictions = self.model.predict(X)
        
        # Rückskalierung
        dummy = np.zeros((len(predictions), len(self.feature_columns)))
        dummy[:, 0] = predictions.flatten()
        predictions_rescaled = self.scaler.inverse_transform(dummy)[:, 0]
        
        return predictions_rescaled
    
    def evaluate(self, X_test: np.ndarray, y_test: np.ndarray) -> dict:
        """
        Evaluiert Modell-Performance.
        """
        predictions = self.model.predict(X_test)
        
        # Rückskalierung
        dummy_pred = np.zeros((len(predictions), len(self.feature_columns)))
        dummy_pred[:, 0] = predictions.flatten()
        pred_rescaled = self.scaler.inverse_transform(dummy_pred)[:, 0]
        
        dummy_true = np.zeros((len(y_test), len(self.feature_columns)))
        dummy_true[:, 0] = y_test.flatten()
        true_rescaled = self.scaler.inverse_transform(dummy_true)[:, 0]
        
        # Metriken berechnen
        mae = np.mean(np.abs(pred_rescaled - true_rescaled))
        mape = np.mean(np.abs((true_rescaled - pred_rescaled) / true_rescaled)) * 100
        rmse = np.sqrt(np.mean((pred_rescaled - true_rescaled) ** 2))
        
        # Direction Accuracy
        pred_direction = np.sign(np.diff(pred_rescaled))
        true_direction = np.sign(np.diff(true_rescaled))
        direction_accuracy = np.mean(pred_direction == true_direction) * 100
        
        return {
            'MAE': mae,
            'MAPE': mape,
            'RMSE': rmse,
            'Direction_Accuracy': direction_accuracy
        }

Training-Pipeline

def train_model_pipeline(features_df: pd.DataFrame): """ Komplette Training-Pipeline. """ lstm = BTCLSTMModel(sequence_length=60) # Sequences erstellen X, y = lstm.prepare_sequences(features_df) print(f"📊 Dataset: X shape={X.shape}, y shape={y.shape}") # Train/Test Split (80/20) X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, shuffle=False # Zeit-Series: nicht shuffeln! ) print(f"🔄 Training: {X_train.shape[0]} Samples") print(f"🔍 Test: {X_test.shape[0]} Samples") # Training history = lstm.train(X_train, y_train, X_test, y_test, epochs=100, batch_size=64) # Evaluation metrics = lstm.evaluate(X_test, y_test) print("\n📈 Modell-Performance:") print(f" MAE: ${metrics['MAE']:.2f}") print(f" MAPE: {metrics['MAPE']:.2f}%") print(f" RMSE: ${metrics['RMSE']:.2f}") print(f" Direction Accuracy: {metrics['Direction_Accuracy']:.1f}%") return lstm, history

Schritt 4: Echtzeit-Vorhersage mit HolySheep Integration

# real_time_prediction.py
import time
import pandas as pd
from datetime import datetime
from lstm_model import BTCLSTMModel
from feature_engineering import BTCFeatureEngineer
from tardis_client import TardisDataFetcher

class BTCPredictionService:
    def __init__(self, holy_sheep_key: str, tardis_key: str):
        self.lstm = BTCLSTMModel()
        self.lstm.model = self._load_trained_model()
        
        self.engineer = BTCFeatureEngineer(holy_sheep_key)
        self.tardis = TardisDataFetcher(tardis_key)
        
        self.last_prediction = None
        self.prediction_history = []
    
    def _load_trained_model(self):
        """
        Lädt trainiertes Modell.
        """
        import tensorflow as tf
        return tf.keras.models.load_model('btc_lstm_model.h5')
    
    def get_latest_prediction(self) -> dict:
        """
        Holt neueste Daten und macht Vorhersage.
        """
        # 1. Tardis-Daten aktualisieren
        trades = self.tardis.get_btc_trades(limit=500)
        features = self.engineer.calculate_technical_indicators(trades)
        features = self.engineer.analyze_market_regime(features)
        
        # 2. Sequenz für Vorhersage vorbereiten
        last_sequence = features[self.lstm.feature_columns].values[-60:]
        scaled_sequence = self.lstm.scaler.transform(last_sequence)
        X = scaled_sequence.reshape(1, 60, -1)
        
        # 3. Vorhersage
        current_price = features['price'].iloc[-1]
        predicted_price = self.lstm.predict(X)[0]
        
        # 4. Änderung berechnen
        price_change = ((predicted_price - current_price) / current_price) * 100
        
        # 5. Konfidenz basierend auf Marktvolatilität
        recent_volatility = features['volatility_5'].iloc[-1]
        confidence = max(0, min(100, 100 - (recent_volatility * 1000)))
        
        result = {
            'timestamp': datetime.now(),
            'current_price': current_price,
            'predicted_price': predicted_price,
            'predicted_change_pct': price_change,
            'direction': 'UP' if price_change > 0 else 'DOWN',
            'confidence': confidence,
            'market_regime': features['market_regime'].iloc[-1]
        }
        
        self.last_prediction = result
        self.prediction_history.append(result)
        
        return result
    
    def run_prediction_loop(self, interval_seconds: int = 60):
        """
        Kontinuierliche Vorhersage-Schleife.
        """
        print(f"🚀 Starte BTC-Vorhersage-Service (Intervall: {interval_seconds}s)")
        print("Drücke Ctrl+C zum Stoppen\n")
        
        try:
            while True:
                prediction = self.get_latest_prediction()
                
                print(f"⏰ {prediction['timestamp'].strftime('%Y-%m-%d %H:%M:%S')}")
                print(f"   💰 Aktuell: ${prediction['current_price']:,.2f}")
                print(f"   🔮 Prognose: ${prediction['predicted_price']:,.2f}")
                print(f"   📈 Änderung: {prediction['predicted_change_pct']:+.2f}%")
                print(f"   🎯 Richtung: {prediction['direction']}")
                print(f"   📊 Konfidenz: {prediction['confidence']:.1f}%")
                print("-" * 50)
                
                time.sleep(interval_seconds)
                
        except KeyboardInterrupt:
            print("\n⛔ Service gestoppt")
            self._save_prediction_history()
    
    def _save_prediction_history(self):
        """Speichert Vorhersage-Historie."""
        if self.prediction_history:
            df = pd.DataFrame(self.prediction_history)
            filename = f"predictions_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
            df.to_csv(filename, index=False)
            print(f"💾 Vorhersagen gespeichert: {filename}")

Hauptprogramm

if __name__ == "__main__": SERVICE = BTCPredictionService( holy_sheep_key="YOUR_HOLYSHEEP_API_KEY", tardis_key="YOUR_TARDIS_API_KEY" ) SERVICE.run_prediction_loop(interval_seconds=60)

Preise und ROI

Modell HolySheep-Preis Offizieller Preis Ersparnis
GPT-4.1 $8.00/MTok $15.00/MTok 47% günstiger
Claude Sonnet 4.5 $15.00/MTok $18.00/MTok 17% günstiger
Gemini 2.5 Flash $2.50/MTok $5.00/MTok 50% günstiger
DeepSeek V3.2 $0.42/MTok $0.50/MTok 16% günstiger

ROI-Analyse für BTC-Prognoseprojekt

Warum HolySheep wählen?

  1. 85%+ Ersparnis für CNY-Nutzer: Mit ¥1 = $1 Wechselkurs sind alle Modelle extrem günstig
  2. <50ms Latenz: Kritisch für Echtzeit-Trading-Vorhersagen
  3. WeChat/Alipay Support: Keine ausländischen Kreditkarten nötig
  4. Stabile Verfügbarkeit in China: Keine VPN-Probleme, keine Ausfälle
  5. Kostenlose Credits: Sofort loslegen ohne upfront investment
  6. DeepSeek V3.2 für $0.42: Perfekt für große Datenmengen im Batch-Training

Häufige Fehler und Lösungen

Fehler 1: "Connection Error" bei HolySheep API

# ❌ FALSCH - Alte Domain verwenden
url = "https://api.openai.com/v1/chat/completions"  # BLOCKIERT in China!

✅ RICHTIG - HolySheep Endpoint

url = "https://api.holysheep.ai/v1/chat/completions"

Lösung: Stellen Sie sicher, dass Sie immer https://api.holysheep.ai/v1 als Base-URL verwenden. Bei Verbindungsproblemen prüfen Sie:

Fehler 2: "UnicodeEncodeError" bei Umlauten

# ❌ FALSCH - Unicode-Sonderzeichen
response = requests.post(url, headers=headers, json=payload)

Manchmal Probleme mit ä, ö, ü in Responses

✅ RICHTIG - Explizite Encoding

response = requests.post( url, headers=headers, json=payload, timeout=30 )

Response mit Latin-1 oder UTF-8 handhaben

text = response.text.encode('latin-1').decode('utf-8', errors='replace')

Fehler 3: Tardis "Rate Limit Exceeded"

# ❌ FALSCH - Zu viele Requests
for i in range(1000):
    trades = tardis.get_btc_trades()  # Rate Limit erreicht

✅ RICHTIG - Rate Limiting implementieren

import time from ratelimit import limits, sleep_and_retry @sleep_and_retry @limits(calls=30, period=60) # Max 30 Calls pro Minute def get_trades_with_backoff(): try: return tardis.get_btc_trades() except RateLimitError: # Exponential Backoff for wait in [1, 2, 4, 8, 16]: time.sleep(wait) try: return tardis.get_btc_trades() except RateLimitError: continue return None

Fehler 4: LSTM "NaN Loss" beim Training

# ❌ FALSCH - Keine NaN-Handling
X = scaled_data[sequence_length:]
y = prices[sequence_length:]

✅ RICHTIG - Vollständige NaN-Behandlung

def prepare_data(df, feature_cols, target_col='price'): # 1. NaN auffüllen df = df.fillna(method='ffill').fillna(method='bfill') # 2. Infinite Werte ersetzen df = df.replace([np.inf, -np.inf], np.nan) df = df.fillna(df.median()) # 3. Nochmals prüfen assert not df.isnull().any().any(), "Noch NaN vorhanden!" # 4. Outlier clippen for col in feature_cols: q1 = df[col].quantile(0.01) q99 = df[col].quantile(0.99) df[col] = df[col].clip(q1, q99) return df

5. Scale und Split

scaler = MinMaxScaler() scaled_data = scaler.fit_transform(df[feature_cols])

Fehler 5: Train/Test Leakage bei Zeitreihen

# ❌ FALSCH - Random Split bei Zeitreihen
X_train, X_test =