DEV Community

Cover image for Stock API Integration Practice: Python Access to Frankfurt Stock Exchange (FWB/Xetra) for Quantitative Analysis
San Si wu
San Si wu

Posted on

Stock API Integration Practice: Python Access to Frankfurt Stock Exchange (FWB/Xetra) for Quantitative Analysis

To achieve quantitative analysis, first obtaining real-time stock quotes, historical stock data, and market data is key to quantitative trading and analysis. Through reliable stock real-time quote interfaces, such as stock APIs, stock real-time quote APIs, and stock market APIs, developers can easily access global market data. This article will introduce how to use professional stock real-time quote APIs, financial APIs, and financial market data APIs to connect to German stock market data, particularly the Frankfurt Stock Exchange (FWB/Xetra), thereby enabling efficient quantitative analysis. These tools not only provide millisecond-level low-latency real-time data but also support historical backtesting, helping investors make data-driven decisions.

Frankfurt Stock Exchange - iTick API

API Integration Scheme Comparison

The Frankfurt Stock Exchange (FWB/Xetra) is one of Europe's largest stock exchanges, covering numerous German blue-chip stocks such as Adidas (ADS) and Deutsche Bank (DBK). It is renowned for its efficient electronic trading system and massive trading volume, making it suitable for the development of quantitative strategies. Through API integration, we can obtain real-time quotes, historical candlestick charts, and order book depth data, which form the foundation for building quantitative models such as moving average strategies and volatility analysis.

In the field of quantitative trading, selecting an appropriate stock data API is crucial to the success of strategies. For the German stock market, especially the Frankfurt Stock Exchange, developers typically face three core challenges: data timeliness, completeness, and compliance requirements.

There are several main API solutions available in the market:

iTick, as a financial data provider focused on the European market, has its API achieving full coverage of Frankfurt Stock Exchange instruments (including XETRA-traded varieties), supporting millisecond-level real-time stock quote pushes and 20 years of historical tick data retrieval, fully compliant with MiFID II regulatory requirements. It also provides Python SDK and complete quantitative tool integration solutions. Registration allows access to a free development package, suitable for medium-to-high-frequency strategies and in-depth quantitative analysis.

Alpha Vantage supports stock data from over 30 countries globally, including components of the German DAX index, with the free version allowing 500 calls per day. However, its main limitations for German stock real-time APIs include delays of up to 15 minutes (for non-paid users), and historical data only provides 10 years of daily-level data without Level 2 depth quotes.

IEX Cloud provides real-time stock quote APIs for the Frankfurt Stock Exchange with approximately 1-second delays and integrates financial statements and ESG data. However, its coverage of German stocks is limited to DAX30 components, and historical data extends only up to 5 years.

Tip: Regardless of the API chosen, you must first complete platform registration and authentication to obtain a dedicated API key (Key). This is the credential for interface calls and should be securely stored to prevent leakage.

Preparation: Obtaining API Token

This article references the iTick API, which is a financial data interface supporting multiple global markets, including Germany (region=DE). It provides both RESTful API and WebSocket methods, with data covering real-time quotes, historical candlesticks, and order book depth. Note: Before use, register an account and obtain a token. In the code examples in this article, replace "your_token" with the actual value.

First, visit the iTick official website to register an account and obtain the API Token. The API supports regions including DE (Germany), with codes as stock symbols (e.g., ADS for Adidas). During testing, ensure your subscription plan supports German market data.

Step 1: Obtaining Real-Time Quotes (Quote)

The real-time quote API provides core indicators such as the latest price, opening price, highest price, and lowest price. Interface path: GET /stock/quote?region={region}&code={code}

Python code example:

import requests

url = "https://api.itick.org/stock/quote?region=DE&code=ADS"

headers = {
    "accept": "application/json",
    "token": "your_token"
}

response = requests.get(url, headers=headers)
data = response.json()

if data["code"] == 0:
    quote = data["data"]
    print(f"Stock Code: {quote['s']}")
    print(f"Latest Price: {quote['ld']}")
    print(f"Opening Price: {quote['o']}")
    print(f"Highest Price: {quote['h']}")
    print(f"Lowest Price: {quote['l']}")
    print(f"Percentage Change: {quote['chp']}%")
else:
    print("Request Failed:", data["msg"])
Enter fullscreen mode Exit fullscreen mode

This interface returns a clear JSON data structure that is easy to parse. In quantitative analysis, you can use the latest price to calculate real-time returns.

Step 2: Obtaining Historical Candlestick Data (Kline)

Historical candlestick data is the core of quantitative backtesting, supporting intervals from minute-level to monthly-level. Interface path: GET /stock/kline?region={region}&code={code}&kType={kType}&limit={limit}

For example, obtaining the last 100 daily candlesticks for Adidas:

import requests
import pandas as pd
from datetime import datetime

def fetch_historical_data(symbol, region="DE", kType=8, limit=100):
    """
    Fetch Historical Candlestick Data

    Parameters:
    symbol: Stock code, e.g., "ADS"
    region: Market code, "DE" for Germany
    kType: Candlestick type, 1-minute, 2-5 minute, 8-daily, 9-weekly, 10-monthly
    limit: Number of data bars to retrieve
    """
    url = f"https://api.itick.org/stock/kline?region={region}&code={symbol}&kType={kType}&limit={limit}"

    headers = {
        "accept": "application/json",
        "token": "your_token"  # Replace with actual Token
    }

    try:
        response = requests.get(url, headers=headers)
        response.raise_for_status()  # Check if request is successful

        data = response.json()

        if data.get("code") == 0 and "data" in data:
            # Convert data to Pandas DataFrame
            df = pd.DataFrame(data["data"])

            # Convert timestamp to readable format
            df['datetime'] = pd.to_datetime(df['t'], unit='ms')

            # Rename columns to standard financial data format
            df.rename(columns={
                'o': 'Open',
                'h': 'High',
                'l': 'Low',
                'c': 'Close',
                'v': 'Volume',
                'tu': 'Turnover'
            }, inplace=True)

            # Select and sort columns
            df = df[['datetime', 'Open', 'High', 'Low', 'Close', 'Volume', 'Turnover']]
            df.set_index('datetime', inplace=True)

            return df
        else:
            print(f"Data Fetch Failed: {data.get('msg')}")
            return None

    except requests.exceptions.RequestException as e:
        print(f"Request Error: {e}")
        return None

def analyze_german_stocks():
    """Analyze Historical Performance of Multiple German Stocks"""
    symbols = ["ADS", "SAP", "VOW3", "ALV", "MRK"]

    all_data = {}

    for symbol in symbols:
        print(f"Fetching historical data for {symbol}...")
        df = fetch_historical_data(symbol, kType=8, limit=200)  # Fetch 200 daily bars

        if df is not None and len(df) > 0:
            all_data[symbol] = df

            # Calculate basic statistical indicators
            latest_close = df['Close'].iloc[-1]
            previous_close = df['Close'].iloc[-2] if len(df) > 1 else latest_close
            daily_change = ((latest_close - previous_close) / previous_close * 100) if len(df) > 1 else 0

            # Calculate 20-day moving average
            ma_20 = df['Close'].rolling(window=20).mean().iloc[-1]

            print(f"{symbol}:")
            print(f"  Latest Close Price: {latest_close:.2f} EUR")
            print(f"  Daily Change: {daily_change:+.2f}%")
            print(f"  20-Day Moving Average: {ma_20:.2f} EUR")
            print(f"  Data Time Range: {df.index[0].date()} to {df.index[-1].date()}")
            print()

    return all_data

if __name__ == "__main__":
    # Fetch and analyze German stock data
    stock_data = analyze_german_stocks()

    # If data is fetched, further analysis can be performed
    if stock_data:
        print("Data fetching complete, ready for quantitative strategy backtesting and analysis!")
Enter fullscreen mode Exit fullscreen mode

This helps identify trend reversal points.

Step 3: Obtaining Real-Time Order Book Depth (Depth)

The order book depth provides bid and ask data for five or ten levels, reflecting market order hanging situations. Interface path: GET /stock/depth?region={region}&code={code}

import requests

url = "https://api.itick.org/stock/depth?region=DE&code=ADS"

headers = {
    "accept": "application/json",
    "token": "your_token"
}

response = requests.get(url, headers=headers)
data = response.json()

if data["code"] == 0:
    depth = data["data"]
    print(f"Stock Code: {depth['s']}")
    print("Ask Side:")
    for ask in depth['a'][:5]:  # Display top 5 ask levels
        print(f"Level {ask['po']}: Price {ask['p']}, Volume {ask['v']}, Orders {ask['o']}")
    print("Bid Side:")
    for bid in depth['b'][:5]:  # Display top 5 bid levels
        print(f"Level {bid['po']}: Price {bid['p']}, Volume {bid['v']}, Orders {bid['o']}")
else:
    print("Request Failed:", data["msg"])
Enter fullscreen mode Exit fullscreen mode

In quantitative analysis, order book data can be used to calculate bid-ask pressure ratios, helping to gauge market sentiment.

Step 4: Implementing Real-Time Push via WebSocket

For high-frequency quantitative analysis, RESTful APIs may have delays; WebSocket is recommended. After connection, subscribe to data supporting tick, quote, and depth types.

Python example (using websocket library):

import websocket
import json
import threading
import time

# WebSocket connection URL and Token
WS_URL = "wss://api.itick.org/stock"
API_TOKEN = "your_token"  # Replace with actual Token

def on_message(ws, message):
    """Handle received messages"""
    data = json.loads(message)

    # Handle successful connection message
    if data.get("code") == 1 and data.get("msg") == "Connected Successfully":
        print("Connection successful, awaiting authentication...")

    # Handle authentication result
    elif data.get("resAc") == "auth":
        if data.get("code") == 1:
            print("Authentication successful")
            subscribe(ws)  # Subscribe after successful authentication
        else:
            print("Authentication failed")
            ws.close()

    # Handle subscription result
    elif data.get("resAc") == "subscribe":
        if data.get("code") == 1:
            print("Subscription successful")
        else:
            print("Subscription failed:", data.get("msg"))

    # Handle market data
    elif data.get("data"):
        market_data = data["data"]
        data_type = market_data.get("type")
        symbol = market_data.get("s")

        if data_type == "tick":
            print(f"Tick Data {symbol}: Latest Price={market_data['ld']}, Volume={market_data['v']}, Time={market_data['t']}")
        elif data_type == "quote":
            print(f"Quote Data {symbol}: Open={market_data['o']}, High={market_data['h']}, Low={market_data['l']}, Close={market_data['ld']}")
        elif data_type == "depth":
            print(f"Depth Data {symbol}: Bid1 Price={market_data['b'][0]['p'] if market_data['b'] else 'N/A'}, "
                  f"Ask1 Price={market_data['a'][0]['p'] if market_data['a'] else 'N/A'}")

def on_error(ws, error):
    """Handle errors"""
    print("Error:", error)

def on_close(ws, close_status_code, close_msg):
    """Connection close callback"""
    print("Connection closed")

def on_open(ws):
    """Callback after connection is established"""
    print("WebSocket connection opened")

def subscribe(ws):
    """Subscribe to market data"""
    subscribe_msg = {
        "ac": "subscribe",
        # Subscribe to real-time data for German Adidas, SAP, and Volkswagen
        "params": "ADS$DE,SAP$DE,VOW3$DE",
        "types": "tick,quote,depth"  # Subscribe to tick, quote, and depth data
    }
    ws.send(json.dumps(subscribe_msg))
    print("Subscription message sent")

def send_ping(ws):
    """Send heartbeat packets periodically to maintain connection"""
    while True:
        time.sleep(30)  # Send heartbeat every 30 seconds
        ping_msg = {
            "ac": "ping",
            "params": str(int(time.time() * 1000))
        }
        ws.send(json.dumps(ping_msg))
        print("Heartbeat packet sent")

if __name__ == "__main__":
    # Create WebSocket connection, pass Token via header
    ws = websocket.WebSocketApp(
        WS_URL,
        header={"token": API_TOKEN},
        on_open=on_open,
        on_message=on_message,
        on_error=on_error,
        on_close=on_close
    )

    # Start heartbeat mechanism in a separate thread
    ping_thread = threading.Thread(target=send_ping, args=(ws,))
    ping_thread.daemon = True
    ping_thread.start()

    # Start WebSocket connection
    ws.run_forever()
Enter fullscreen mode Exit fullscreen mode

This code establishes a connection to the iTick WebSocket server and subscribes to real-time data for three well-known German companies (Adidas, SAP, and Volkswagen). After the connection is established, the server will continuously push three types of data:

  • Tick Data: Includes the latest trade price, trade volume, and timestamp
  • Quote Data: Includes open, high, low, close (OHLC) data such as opening price, highest price, lowest price, and latest price
  • Depth Data: Includes bid and ask volumes and prices for each of the five levels

The advantages of obtaining real-time data via WebSocket lie in low latency and efficient data push mechanisms, particularly suitable for quantitative strategies that require real-time market monitoring and quick trading decisions.

Quantitative Analysis Example: Building a Simple Strategy

Obtaining data is just the first step; the real value lies in how to utilize this data for quantitative analysis. Below, we combine real-time and historical data to build a simple quantitative analysis example.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from datetime import datetime, timedelta

class GermanStockAnalyzer:
    """German Stock Analyzer"""

    def __init__(self, historical_data):
        self.data = historical_data

    def calculate_technical_indicators(self):
        """Calculate Common Technical Indicators"""
        df = self.data.copy()

        # Calculate Moving Averages
        df['MA_5'] = df['Close'].rolling(window=5).mean()
        df['MA_20'] = df['Close'].rolling(window=20).mean()
        df['MA_60'] = df['Close'].rolling(window=60).mean()

        # Calculate Relative Strength Index (RSI)
        delta = df['Close'].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))

        # Calculate Bollinger Bands
        df['BB_middle'] = df['Close'].rolling(window=20).mean()
        bb_std = df['Close'].rolling(window=20).std()
        df['BB_upper'] = df['BB_middle'] + 2 * bb_std
        df['BB_lower'] = df['BB_middle'] - 2 * bb_std

        # Calculate Volume Weighted Average Price (VWAP) - Intraday Indicator
        df['VWAP'] = (df['Turnover'] / df['Volume']).rolling(window=20).mean()

        return df

    def generate_signals(self, df):
        """Generate Trading Signals Based on Technical Indicators"""
        signals = pd.DataFrame(index=df.index)
        signals['price'] = df['Close']
        signals['signal'] = 0

        # Dual Moving Average Crossover Strategy
        signals['ma_signal'] = 0
        signals.loc[df['MA_5'] > df['MA_20'], 'ma_signal'] = 1  # Golden Cross
        signals.loc[df['MA_5'] < df['MA_20'], 'ma_signal'] = -1  # Death Cross

        # RSI Overbought/Oversold Signals
        signals['rsi_signal'] = 0
        signals.loc[df['RSI'] < 30, 'rsi_signal'] = 1  # Oversold, Buy Signal
        signals.loc[df['RSI'] > 70, 'rsi_signal'] = -1  # Overbought, Sell Signal

        # Bollinger Bands Breakout Signals
        signals['bb_signal'] = 0
        signals.loc[df['Close'] < df['BB_lower'], 'bb_signal'] = 1  # Break Below Lower Band, Buy Signal
        signals.loc[df['Close'] > df['BB_upper'], 'bb_signal'] = -1  # Break Above Upper Band, Sell Signal

        # Combined Signal
        signals['combined_signal'] = signals[['ma_signal', 'rsi_signal', 'bb_signal']].mean(axis=1)

        # Generate Final Trading Signals
        signals.loc[signals['combined_signal'] > 0.3, 'signal'] = 1  # Strong Buy
        signals.loc[signals['combined_signal'] < -0.3, 'signal'] = -1  # Strong Sell

        return signals

    def plot_analysis(self, df, signals):
        """Visualize Analysis Results"""
        fig, axes = plt.subplots(3, 1, figsize=(15, 12))

        # Price and Moving Averages
        ax1 = axes[0]
        ax1.plot(df.index, df['Close'], label='Close Price', linewidth=1)
        ax1.plot(df.index, df['MA_5'], label='5-Day MA', linewidth=1, alpha=0.7)
        ax1.plot(df.index, df['MA_20'], label='20-Day MA', linewidth=1, alpha=0.7)
        ax1.plot(df.index, df['MA_60'], label='60-Day MA', linewidth=1, alpha=0.7)

        # Mark Trading Signals
        buy_signals = signals[signals['signal'] == 1]
        sell_signals = signals[signals['signal'] == -1]

        ax1.scatter(buy_signals.index, df.loc[buy_signals.index, 'Close'],
                   color='green', marker='^', s=100, label='Buy Signal')
        ax1.scatter(sell_signals.index, df.loc[sell_signals.index, 'Close'],
                   color='red', marker='v', s=100, label='Sell Signal')

        ax1.set_title('German Stock Price and Moving Averages')
        ax1.set_ylabel('Price (EUR)')
        ax1.legend()
        ax1.grid(True, alpha=0.3)

        # RSI Indicator
        ax2 = axes[1]
        ax2.plot(df.index, df['RSI'], label='RSI', linewidth=1, color='purple')
        ax2.axhline(y=70, color='red', linestyle='--', alpha=0.5, label='Overbought Line')
        ax2.axhline(y=30, color='green', linestyle='--', alpha=0.5, label='Oversold Line')
        ax2.fill_between(df.index, 30, 70, alpha=0.1, color='gray')
        ax2.set_title('Relative Strength Index (RSI)')
        ax2.set_ylabel('RSI Value')
        ax2.legend()
        ax2.grid(True, alpha=0.3)

        # Bollinger Bands
        ax3 = axes[2]
        ax3.plot(df.index, df['Close'], label='Close Price', linewidth=1)
        ax3.plot(df.index, df['BB_middle'], label='Middle Band', linewidth=1, alpha=0.7)
        ax3.plot(df.index, df['BB_upper'], label='Upper Band', linewidth=1, alpha=0.7, linestyle='--')
        ax3.plot(df.index, df['BB_lower'], label='Lower Band', linewidth=1, alpha=0.7, linestyle='--')
        ax3.fill_between(df.index, df['BB_lower'], df['BB_upper'], alpha=0.1)
        ax3.set_title('Bollinger Bands')
        ax3.set_ylabel('Price (EUR)')
        ax3.set_xlabel('Date')
        ax3.legend()
        ax3.grid(True, alpha=0.3)

        plt.tight_layout()
        plt.show()

    def backtest_strategy(self, signals, initial_capital=10000):
        """Simple Strategy Backtest"""
        capital = initial_capital
        position = 0
        trades = []

        for i in range(1, len(signals)):
            current_price = signals['price'].iloc[i]
            signal = signals['signal'].iloc[i]

            if signal == 1 and position == 0:  # Buy signal, no current position
                position = capital / current_price
                capital = 0
                trades.append({
                    'date': signals.index[i],
                    'action': 'BUY',
                    'price': current_price,
                    'position': position
                })
            elif signal == -1 and position > 0:  # Sell signal, with current position
                capital = position * current_price
                position = 0
                trades.append({
                    'date': signals.index[i],
                    'action': 'SELL',
                    'price': current_price,
                    'capital': capital
                })

        # Calculate final returns
        if position > 0:
            final_capital = position * signals['price'].iloc[-1]
        else:
            final_capital = capital

        total_return = (final_capital - initial_capital) / initial_capital * 100

        return {
            'initial_capital': initial_capital,
            'final_capital': final_capital,
            'total_return': total_return,
            'trades': trades
        }

# Usage Example
if __name__ == "__main__":
    # Assume we have fetched historical data
    # Use simulated data for demonstration here
    dates = pd.date_range(start='2024-01-01', end='2024-12-01', freq='D')
    np.random.seed(42)
    prices = 100 + np.cumsum(np.random.randn(len(dates)) * 0.5)
    volumes = np.random.randint(100000, 1000000, len(dates))

    historical_data = pd.DataFrame({
        'Close': prices,
        'Volume': volumes,
        'Turnover': prices * volumes
    }, index=dates)

    # Create analyzer instance
    analyzer = GermanStockAnalyzer(historical_data)

    # Calculate technical indicators
    df_with_indicators = analyzer.calculate_technical_indicators()

    # Generate trading signals
    signals = analyzer.generate_signals(df_with_indicators)

    # Visualize analysis
    analyzer.plot_analysis(df_with_indicators, signals)

    # Backtest strategy
    backtest_result = analyzer.backtest_strategy(signals)

    print("Strategy Backtest Results:")
    print(f"Initial Capital: {backtest_result['initial_capital']:.2f} EUR")
    print(f"Final Capital: {backtest_result['final_capital']:.2f} EUR")
    print(f"Total Return: {backtest_result['total_return']:.2f}%")
    print(f"Number of Trades: {len(backtest_result['trades'])}")
Enter fullscreen mode Exit fullscreen mode

This quantitative analysis example demonstrates how to apply data obtained from the iTick API to practical quantitative strategies. By calculating technical indicators, generating trading signals, and conducting strategy backtesting, we can systematically evaluate the effectiveness of trading strategies.

API Integration and Quantitative Analysis Considerations

  • Rate Limits and Subscriptions: APIs have call limits; monitor them in production environments.
  • Data Accuracy: After obtaining data, perform completeness and accuracy checks, such as detecting missing values or abnormal prices (e.g., 0 or prices far beyond normal ranges). Use pandas methods like dropna() and replace() to handle dirty data.
  • Real-Time Optimization: For high-frequency quantitative strategies, select API providers with local Frankfurt deployments (such as iTick) to reduce network latency; also set up reasonable data caching to minimize repeated requests.
  • Extensions: iTick supports more markets and can be extended to multi-asset strategies.

Summary

By integrating the Frankfurt Stock Exchange API for real-time stock quotes and historical data via Python, we have built the core data pipeline for quantitative analysis. This is not only a technical implementation but also the beginning of data-driven decision-making—a stable and reliable data flow makes strategy backtesting more accurate and signal generation more timely, laying a solid foundation for exploring alpha opportunities in the rigorous European market. Now, you possess the capability to connect to important global financial markets; it's time to transform this data into your strategic advantage.

Warm Reminder: This article is for reference only and does not constitute any investment advice. Markets involve risks; invest with caution.

Reference Document: https://itick.org/blog/stock-api/free-german-stock-api-comparison

GitHub: https://github.com/itick-org/

Top comments (0)