From 390f2d91a43f1db129dd70c14e738f5d39ce052a Mon Sep 17 00:00:00 2001 From: Melchior Reimers Date: Tue, 27 Jan 2026 10:56:56 +0100 Subject: [PATCH] updated dashboard --- .../deutsche_boerse.cpython-313.pyc | Bin 16447 -> 13896 bytes src/exchanges/deutsche_boerse.py | 166 ++++++------------ 2 files changed, 57 insertions(+), 109 deletions(-) diff --git a/src/exchanges/__pycache__/deutsche_boerse.cpython-313.pyc b/src/exchanges/__pycache__/deutsche_boerse.cpython-313.pyc index c69c10345f92f2fd7f0dd8d63c99a78b978fbd20..034c26f630954f63abfaa1e5ec5103303bc56446 100644 GIT binary patch delta 5556 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 Z@0!}xRPl`OF2V2JBL<;#&sG&G@xQ*Zq5}W` diff --git a/src/exchanges/deutsche_boerse.py b/src/exchanges/deutsche_boerse.py index 7a85211..db30015 100644 --- a/src/exchanges/deutsche_boerse.py +++ b/src/exchanges/deutsche_boerse.py @@ -7,10 +7,19 @@ from typing import List, Optional from .base import BaseExchange, Trade from bs4 import BeautifulSoup +# API URLs für Deutsche Börse +API_URLS = { + 'XETRA': 'https://mfs.deutsche-boerse.com/api/DETR-posttrade', + 'FRA': 'https://mfs.deutsche-boerse.com/api/DFRA-posttrade', + 'QUOTRIX': 'https://mfs.deutsche-boerse.com/api/DGAT-posttrade', +} +DOWNLOAD_BASE_URL = "https://mfs.deutsche-boerse.com/api/download" + # Browser User-Agent für Zugriff HEADERS = { - 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', - 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8' + 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36', + 'Accept': 'application/json, application/gzip, */*', + 'Referer': 'https://mfs.deutsche-boerse.com/', } @@ -26,59 +35,25 @@ class DeutscheBoerseBase(BaseExchange): def name(self) -> str: raise NotImplementedError + @property + def api_url(self) -> str: + """API URL für die Dateiliste""" + return API_URLS.get(self.name, self.base_url) + def _get_file_list(self) -> List[str]: - """Parst die Verzeichnisseite und extrahiert alle Dateinamen""" - import re + """Holt die Dateiliste von der JSON API""" try: - response = requests.get(self.base_url, headers=HEADERS, timeout=30) + response = requests.get(self.api_url, headers=HEADERS, timeout=30) response.raise_for_status() - files = [] - html_text = response.text + data = response.json() + files = data.get('CurrentFiles', []) - # Debug: Response-Länge - print(f"[{self.name}] Response length: {len(html_text)} chars") - - # Extrahiere Prefix aus base_url (z.B. DETR, DFRA, DGAT) - prefix_match = re.search(r'/([A-Z]{4})-posttrade', self.base_url) - prefix = prefix_match.group(1) if prefix_match else '[A-Z]{4}' - - # Pattern: PREFIX-posttrade-YYYY-MM-DDTHH_MM.json.gz - # Wichtig: Dateinamen erscheinen als Text/Name, nicht nur in href - pattern = f'{prefix}-posttrade-\\d{{4}}-\\d{{2}}-\\d{{2}}T\\d{{2}}_\\d{{2}}\\.json\\.gz' - - matches = re.findall(pattern, html_text) - files = list(set(matches)) - - if files: - print(f"[{self.name}] Found {len(files)} files via regex") - - # Fallback: BeautifulSoup für Links und Text - if not files: - soup = BeautifulSoup(html_text, 'html.parser') - all_links = soup.find_all('a') - print(f"[{self.name}] Found {len(all_links)} total links on page") - - for link in all_links: - href = link.get('href', '') - text = link.get_text(strip=True) - - # Prüfe Link-Text (Dateinamen werden oft als Link-Text angezeigt) - if text and 'posttrade' in text.lower() and '.json.gz' in text.lower(): - files.append(text) - # Prüfe href - elif href and 'posttrade' in href.lower() and '.json.gz' in href.lower(): - filename = href.split('/')[-1] if '/' in href else href - files.append(filename) - - files = list(set(files)) - if files: - print(f"[{self.name}] Found {len(files)} files via BeautifulSoup") - - print(f"[{self.name}] Total files found: {len(files)}") + print(f"[{self.name}] API returned {len(files)} files") return files + except Exception as e: - print(f"Error fetching file list from {self.base_url}: {e}") + print(f"[{self.name}] Error fetching file list from API: {e}") return [] def _filter_files_for_date(self, files: List[str], target_date: datetime.date) -> List[str]: @@ -116,47 +91,52 @@ class DeutscheBoerseBase(BaseExchange): return filtered - def _download_and_parse_file(self, file_url: str) -> List[Trade]: - """Lädt eine JSON.gz Datei herunter und parst die Trades""" + def _download_and_parse_file(self, filename: str) -> List[Trade]: + """Lädt eine JSON.gz Datei von der API herunter und parst die Trades""" trades = [] try: - # Vollständige URL erstellen - # Format: https://mfs.deutsche-boerse.com/DETR-posttrade/DETR-posttrade-2026-01-27T08_53.json.gz - if not file_url.startswith('http'): - # Entferne führenden Slash falls vorhanden - filename = file_url.lstrip('/') - full_url = f"{self.base_url}/{filename}" - else: - full_url = file_url + # Download-URL: https://mfs.deutsche-boerse.com/api/download/{filename} + full_url = f"{DOWNLOAD_BASE_URL}/{filename}" response = requests.get(full_url, headers=HEADERS, timeout=60) if response.status_code == 404: - print(f"[{self.name}] File not found: {full_url}") + # Datei nicht gefunden - normal für alte Dateien return [] response.raise_for_status() - print(f"[{self.name}] Downloaded: {full_url} ({len(response.content)} bytes)") # Gzip entpacken with gzip.GzipFile(fileobj=io.BytesIO(response.content)) as f: - json_data = json.load(f) + content = f.read().decode('utf-8') - # Trades parsen - # Deutsche Börse JSON Format (RTS1/RTS2): - # Typische Felder: TrdDt, TrdTm, ISIN, Pric, Qty, TrdCcy, etc. - for record in json_data: + if not content.strip(): + # Leere Datei + return [] + + # NDJSON Format: Eine JSON-Zeile pro Trade + for line in content.strip().split('\n'): + if not line.strip(): + continue try: + record = json.loads(line) trade = self._parse_trade_record(record) if trade: trades.append(trade) - except Exception as e: - print(f"Error parsing trade record: {e}") + except json.JSONDecodeError: continue + except Exception as e: + continue + + if trades: + print(f"[{self.name}] Parsed {len(trades)} trades from {filename}") + except requests.exceptions.HTTPError as e: + if e.response.status_code != 404: + print(f"[{self.name}] HTTP error downloading {filename}: {e}") except Exception as e: - print(f"Error downloading/parsing {file_url}: {e}") + print(f"[{self.name}] Error downloading/parsing {filename}: {e}") return trades @@ -243,34 +223,6 @@ class DeutscheBoerseBase(BaseExchange): print(f"Error parsing record: {e}") return None - def _generate_expected_files(self, target_date: datetime.date) -> List[str]: - """ - Generiert erwartete Dateinamen basierend auf dem bekannten Format. - Format: PREFIX-posttrade-YYYY-MM-DDTHH_MM.json.gz - """ - import re - files = [] - - # Extrahiere Prefix aus base_url (z.B. DETR, DFRA, DGAT) - prefix_match = re.search(r'/([A-Z]{4})-posttrade', self.base_url) - prefix = prefix_match.group(1) if prefix_match else 'DETR' - - date_str = target_date.strftime('%Y-%m-%d') - - # Generiere für alle Stunden des Handelstages (07:00 - 22:00 UTC, alle Minuten) - for hour in range(7, 23): - for minute in range(0, 60): - files.append(f"{prefix}-posttrade-{date_str}T{hour:02d}_{minute:02d}.json.gz") - - # Auch frühe Dateien vom Folgetag (nach Mitternacht UTC) - next_date = target_date + timedelta(days=1) - next_date_str = next_date.strftime('%Y-%m-%d') - for hour in range(0, 3): - for minute in range(0, 60): - files.append(f"{prefix}-posttrade-{next_date_str}T{hour:02d}_{minute:02d}.json.gz") - - return files - def _get_last_trading_day(self, from_date: datetime.date) -> datetime.date: """ Findet den letzten Handelstag (überspringt Wochenenden). @@ -307,19 +259,20 @@ class DeutscheBoerseBase(BaseExchange): print(f"[{self.name}] Fetching trades for date: {target_date}") - # Erst versuchen, Dateiliste von der Seite zu holen + # Hole Dateiliste von der API files = self._get_file_list() - print(f"[{self.name}] Found {len(files)} total files") + + if not files: + print(f"[{self.name}] No files available from API") + return [] # Dateien für Zieldatum filtern target_files = self._filter_files_for_date(files, target_date) - print(f"[{self.name}] {len(target_files)} files match target date") + print(f"[{self.name}] {len(target_files)} files match target date (of {len(files)} total)") - # Falls keine Dateien von der Seite gefunden, generiere erwartete Dateinamen if not target_files: - print(f"[{self.name}] No files from page, trying generated filenames...") - target_files = self._generate_expected_files(target_date) - print(f"[{self.name}] Trying {len(target_files)} potential files") + print(f"[{self.name}] No files for target date found") + return [] # Alle passenden Dateien herunterladen und parsen successful = 0 @@ -328,13 +281,8 @@ class DeutscheBoerseBase(BaseExchange): if trades: all_trades.extend(trades) successful += 1 - if successful <= 5: - print(f"[{self.name}] Parsed {len(trades)} trades from {file}") - if successful > 5: - print(f"[{self.name}] ... and {successful - 5} more files") - - print(f"[{self.name}] Total trades fetched: {len(all_trades)}") + print(f"[{self.name}] Downloaded {successful} files, total {len(all_trades)} trades") return all_trades