From e124f38ac9ac35771a4522b215620dc8a5940178 Mon Sep 17 00:00:00 2001 From: Melchior Reimers Date: Tue, 27 Jan 2026 10:38:29 +0100 Subject: [PATCH] updated dashboard --- .../deutsche_boerse.cpython-313.pyc | Bin 13187 -> 15752 bytes .../__pycache__/gettex.cpython-313.pyc | Bin 13978 -> 17082 bytes src/exchanges/deutsche_boerse.py | 69 ++++++++- src/exchanges/gettex.py | 142 ++++++++++++++---- 4 files changed, 173 insertions(+), 38 deletions(-) diff --git a/src/exchanges/__pycache__/deutsche_boerse.cpython-313.pyc b/src/exchanges/__pycache__/deutsche_boerse.cpython-313.pyc index 76f5aa242cba23620f8c32aa27be97dceb9c573f..8cb1cc7cd5fdac96416a2d5d20a46f566274da63 100644 GIT binary patch delta 4654 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 G$Mr8_nr$fn delta 2398 zcmah~Z%kX)6~EWd|DOG9F#ZEJrkLU+I2c2SAr5~-mxK&T)7g7!6~mYlKZ6tVjIPa^ zf~vKh(xOh9vYkmulTM-8+9pk-Q89HNrYPBmG}Wd~piFIK<-@d1le+Z-QP4I`Yqxuz zabVM=-4DNa@44syx#zsah3`z+AKGjd!q@GWPG@h1-?v9}y5AER(B0hSOjM0K6V>C^ zgfK3S+YOv4YD_qM-Nym?dewvQ}TcmrhYWv;_oF`wokjeS>h!Mf?f3uYU4 zVe2IJ|C~$wZ@t!SdVfWTl>|s~N5yc$DUY{AWMhGs~=&`2^gR0k=`F**Z2aWO3?;mK}oR_(EER1(% zrW9uLYC$O(GTEF`fP=R7CVeieo?=)d7S)*WN=?luC8`wcoB}JhQRy;&RWFi1m>ui7 z7O>buFlj#$`)S+Nw!5C#;_!V}-5bvH&s_B1b?sa{^j9NsdGFT-m!5rR;?0TWvAeZT zoYmj+2QH1hK6a(~6MqE0uwO8?Z`2W64g9$(-rd2iymH7hY$mq@HHh7aA4qBxQhwKk|s4N6u)-BLhnD;h@+wdxaz+XNoSqsHfajr0V zBA-^6CWeV;7zFEw+uqTL=Eru!<0xk=vQ%0ruwguyGjqAzGS%KO2K zI;aYF>sECu!{I;D2p=m?@}4m9PI@$uxX|X3O_he=BT3*RsnPKmTotYGWxFq8*_v>& zxx&X=$ts(8D;X;kJ`tYcoeS&jErOJE^EP!sdxwMCV5s9O#xZV*SLd~k$LVgk-qGO| zCjw8WSV2h#=52v;iUXLK0G!%W|80B8G@UA>ibcjQprn>KLCrtMa73~1A?Ojd3i(w$ z_9GlaU>r(D-fD`P4vFQ5bPg}h1OsZue;rj+7j#))ml_(UZv$Dtr{ zfZd&=#?SFI1Ph(3@rOp@ZdyEe&+Yxx-MQxOT#nv##}*IYlRTeF4Qo=v`OI&n9rq>2 z*`c%PbJ_1_&nB0|_nNO{uC-oiSf06c@K*O~&B!Ox=mQhivtc2k9a^IiK0J;^C!$&~ z7<4&&Eh7B?X<@O~aA!i$3a>w&&*#_@tViqVWsti3^kWEh4OF)pJWKke#GB#<;oJ3N z@LX3i@*tyo%-~!8`a0pCkEXTH>ha9#RBr9~%-@ixkr=)bW{2_EbhYhSg{O+orsVF5ZL%EYXYPcU7LUlFR(~)Kv@nF>3q?!dnP$ zBfNuf1>q_}ErQmT*KmiDWgQ;eU9W%*9I+94ojbQcy>Kt~EzJWjOz*E3{Wu1eIVT+5 zx63FTjt?<()de5#_s0U;-E(bokNpgF|ChZFkcQxerk)Y{0pxm~r#s+JJ!dxUo$2+8 zE!*wEZ+gYeY<%A9i-otFtNVZEZlF1Y9Wc;0Qj5Egi)m9#!K*C76kO^X+=~N^;gB!q rL~~RV|IY|Y=DwMHdM>97un)n}KT!3&Kz--^8w9@_r%mvU{@wou8)GhE diff --git a/src/exchanges/__pycache__/gettex.cpython-313.pyc b/src/exchanges/__pycache__/gettex.cpython-313.pyc index d81427af22f78e0828b6293ac61011092f05fcf9..947d40263dc4f7a138047ae7e2c292b44a407f9e 100644 GIT binary patch delta 6786 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 V_|~1Q;hKW9%$)uo0()`g{{rtp1=Rom delta 3975 zcma)9Yiv`=6`t$s>u2oXx1Bi7B?$!QK@viU^8yU9NuZ$Mn@cEOpx)RCUJ^S@OnGR# z*>Z_fFH;aM zq87A;+~iYp>6j-ct)^wDPu0ZaMg+B>X;CE9qS}JG>JUn3JV->L^{ef3yr_|V5?WEa zY1trRK}b}D%Ga>hQu{)x;L(1VdVd2#dh(%d{oVtK4z?5xFzZpcLUvT6hzpk2g)8L| zK`ZKvs8Jy=%cD{xs1iEAZhsUlb7ZI8VUuH#9ib0Zks-T_)rmS%-)1G(?WJQY^Xf## zhy=YfyYl7PMMcH(>;udT@wvYfU%~6oGk`R?PxczfE{9mtcALp+qp_j*a6A>m=iT8w zf!^N0;iG&2<0R%sB6u{O802yCOe;^)&NB8}MJu`OEM*+zOXo?_<#Lk|mrH@0$xmE7 zqbIjqRV6AMi$r5M%Ys*yOJOQ(hg^*6fe=m6VdML-A@8E6CQDkisN_ka=duL6)N((;>tEPa(Wg$Xq`Q6_@)|}1I!;y zozIu-$hrO*QkY7fSX~Ql-;8O7T_|dQWbOE~)%|>t1I6Nb^5s3Qe$T$H(M)d8#y}S zW2v^y6KrJXT-8AxJFhbUKkqOFH?#AbIpA>$*6UXQ$u)AMM6Vfu+wu|eAW~y+8bu}q z8Ic?5F<3oK%k~L{sF2T96pRLy)R^!V>*g1|WN$$wsAXrlPp~3OBD{4>&luH_AQ5mE|lRMbT=2inYTpTyafkUSYFNFy*J!iW<1VW>E|Hz|uu%mfeCy zu;xwFpjNj*E+b}GRx&sw*aY*6L0BcHFQk#VmA(v|@`(W)2}#hqqNiMc2~4e{Ym;kc zk5#a@FaTgC*$8mdOwN@TAJPeq7F7iTxUb04rCsmvc};zNtMr>?db7-t^Q!7)i6>ue^o(VdaX`Z?HzF;mIVI1oO~r>} zqp8U7$a(%10J>W^lmk(30j{9|s&Q;&C<2jl14GG33eyXcH4Nb7aC|g5AY~1w>F3Zp znq^O?`Y}0PY21pNL54}_c1gg3FHi%m6w`4?5WYxd^{Lc3N%{~ZaSA9*L;^|a72(Qx5Mxe4I$=)2T6@%>y33-$iHTQZx18QY$8@N0|f zDTL^_>X>v)m1SJDmH=a}c3EeSlO)JP7C0citl0GtZD8YJA81^*R1O@;> zL3(O!CIzh=8dudGoE5Yo|CXI<-IfHJsab*KWtpn?3Qw20w} zzj=K@7!WSkLRNC1*+D|(7P4s8YE>UlQgdme3SJ}lk%5p$VJK{t z{nxA(!)9{A=jmBhG-?Ii8bu?;hwrriQ#7(2g5f)gK4x zSo`Ce*1i6W`8UNx-fpY5{R~1sUwD-7OrA*qLQ>F%67df;9{;*Vi4Td^mWO2y6w&XQvt?z&Pq zIir67k}sP}wSERLY1}^^y%N6^9}iC!PP0>|7tBqM3bv8)j<;U7q*l?AddW;~Y_D#q zTeG5`@J~c<#IMFD!V8-^W+F4fLXrQGb=Ruex&UhHi&COK@T-Cd-P8LO(A_uio$c)X z+HJr;sPG01idjP~@U!i@fKEN9QUgC{&;>fUIY)88$IWe_&T~GFj&!iV&m&KlnVt9e z_PE*4-3H)4_j=y 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 fetch_latest_trades(self, include_yesterday: bool = True, since_date: datetime = None) -> List[Trade]: """ Holt alle Trades vom Vortag (oder seit since_date). @@ -248,7 +292,7 @@ class DeutscheBoerseBase(BaseExchange): print(f"[{self.name}] Fetching trades for date: {target_date}") - # Dateiliste holen + # Erst versuchen, Dateiliste von der Seite zu holen files = self._get_file_list() print(f"[{self.name}] Found {len(files)} total files") @@ -256,11 +300,24 @@ class DeutscheBoerseBase(BaseExchange): target_files = self._filter_files_for_date(files, target_date) print(f"[{self.name}] {len(target_files)} files match target date") + # 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") + # Alle passenden Dateien herunterladen und parsen + successful = 0 for file in target_files: trades = self._download_and_parse_file(file) - all_trades.extend(trades) - print(f"[{self.name}] Parsed {len(trades)} trades from {file}") + 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)}") return all_trades diff --git a/src/exchanges/gettex.py b/src/exchanges/gettex.py index 84ac844..7a15e69 100644 --- a/src/exchanges/gettex.py +++ b/src/exchanges/gettex.py @@ -133,17 +133,32 @@ class GettexExchange(BaseExchange): with gzip.GzipFile(fileobj=io.BytesIO(response.content)) as f: csv_text = f.read().decode('utf-8') - # CSV parsen - reader = csv.DictReader(io.StringIO(csv_text), delimiter=';') + # Debug: Zeige erste Zeilen und Spalten + lines = csv_text.strip().split('\n') + if lines: + print(f"[GETTEX] CSV has {len(lines)} lines, first line (headers): {lines[0][:200]}") + if len(lines) > 1: + print(f"[GETTEX] Sample data row: {lines[1][:200]}") + # CSV parsen - versuche verschiedene Delimiter + delimiter = ';' if ';' in lines[0] else ',' + reader = csv.DictReader(io.StringIO(csv_text), delimiter=delimiter) + + row_count = 0 for row in reader: + row_count += 1 + if row_count == 1: + print(f"[GETTEX] CSV columns: {list(row.keys())}") try: trade = self._parse_csv_row(row) if trade: trades.append(trade) except Exception as e: - print(f"[GETTEX] Error parsing row: {e}") + if row_count <= 3: + print(f"[GETTEX] Error parsing row {row_count}: {e}, row keys: {list(row.keys())}") continue + + print(f"[GETTEX] Processed {row_count} rows, found {len(trades)} valid trades") except requests.exceptions.HTTPError as e: if e.response.status_code != 404: @@ -157,48 +172,102 @@ class GettexExchange(BaseExchange): """ Parst eine CSV-Zeile zu einem Trade. - Erwartete Spalten (RTS Format): - - TrdDtTm: Trading Date/Time - - ISIN: Instrument Identifier - - Pric: Preis - - Qty: Menge - - Ccy: Währung + Unterstützte Spalten (RTS1/RTS2 Format, verschiedene Varianten): + - ISIN / FinInstrmId / Isin: Instrument Identifier + - Pric / Price / pric: Preis + - Qty / Quantity / qty: Menge + - TrdDtTm / TradingDateTime / TrdgDtTm: Trading Date/Time + - TrdDt / TradingDate: Trading Date + - TrdTm / TradingTime: Trading Time """ try: - # ISIN - isin = row.get('ISIN', row.get('FinInstrmId', '')) + # ISIN - versuche verschiedene Spaltennamen + isin = None + for key in ['ISIN', 'Isin', 'isin', 'FinInstrmId', 'FinInstrmId.Id', 'Id']: + if key in row and row[key]: + isin = str(row[key]).strip() + break + if not isin: return None - # Preis - price_str = row.get('Pric', row.get('Price', '0')) - price_str = price_str.replace(',', '.') - price = float(price_str) + # Preis - versuche verschiedene Spaltennamen + price = None + for key in ['Pric', 'Price', 'pric', 'price', 'Pric.Pric.MntryVal.Amt', 'TradPric']: + if key in row and row[key]: + price_str = str(row[key]).replace(',', '.').strip() + try: + price = float(price_str) + if price > 0: + break + except ValueError: + continue - if price <= 0: + if not price or price <= 0: return None - # Menge - qty_str = row.get('Qty', row.get('Quantity', '0')) - qty_str = qty_str.replace(',', '.') - quantity = float(qty_str) + # Menge - versuche verschiedene Spaltennamen + quantity = None + for key in ['Qty', 'Quantity', 'qty', 'quantity', 'TradQty', 'Qty.Unit']: + if key in row and row[key]: + qty_str = str(row[key]).replace(',', '.').strip() + try: + quantity = float(qty_str) + if quantity > 0: + break + except ValueError: + continue - if quantity <= 0: + if not quantity or quantity <= 0: return None - # Timestamp - ts_str = row.get('TrdDtTm', row.get('TradingDateTime', '')) + # Timestamp - versuche verschiedene Formate + ts_str = None + + # Erst kombiniertes Feld versuchen + for key in ['TrdDtTm', 'TradingDateTime', 'TrdgDtTm', 'Timestamp', 'timestamp']: + if key in row and row[key]: + ts_str = str(row[key]).strip() + break + + # Falls nicht gefunden, separate Felder kombinieren if not ts_str: - # Fallback: Separate Felder - trd_dt = row.get('TrdDt', '') - trd_tm = row.get('TrdTm', '00:00:00') - ts_str = f"{trd_dt}T{trd_tm}" + trd_dt = None + trd_tm = '00:00:00' + + for key in ['TrdDt', 'TradingDate', 'Date', 'date']: + if key in row and row[key]: + trd_dt = str(row[key]).strip() + break + + for key in ['TrdTm', 'TradingTime', 'Time', 'time']: + if key in row and row[key]: + trd_tm = str(row[key]).strip() + break + + if trd_dt: + ts_str = f"{trd_dt}T{trd_tm}" + + if not ts_str: + return None # Parse Timestamp (UTC) ts_str = ts_str.replace('Z', '+00:00') if 'T' not in ts_str: ts_str = ts_str.replace(' ', 'T') + # Entferne Mikrosekunden wenn zu lang + if '.' in ts_str: + parts = ts_str.split('.') + if len(parts) > 1: + ms_part = parts[1].split('+')[0].split('-')[0] + if len(ms_part) > 6: + ts_str = parts[0] + '.' + ms_part[:6] + if '+' in parts[1]: + ts_str += '+' + parts[1].split('+')[1] + elif '-' in parts[1][1:]: + ts_str += '-' + parts[1].split('-')[-1] + timestamp = datetime.fromisoformat(ts_str) if timestamp.tzinfo is None: timestamp = timestamp.replace(tzinfo=timezone.utc) @@ -213,7 +282,7 @@ class GettexExchange(BaseExchange): ) except Exception as e: - print(f"[GETTEX] Error parsing CSV row: {e}") + # Nur bei den ersten paar Fehlern loggen return None def fetch_latest_trades(self, include_yesterday: bool = True, since_date: datetime = None) -> List[Trade]: @@ -289,7 +358,6 @@ class GettexExchange(BaseExchange): trades = [] try: - print(f"[{self.name}] Downloading: {url}") response = requests.get(url, headers=HEADERS, timeout=60) if response.status_code == 404: @@ -301,16 +369,26 @@ class GettexExchange(BaseExchange): with gzip.GzipFile(fileobj=io.BytesIO(response.content)) as f: csv_text = f.read().decode('utf-8') - # CSV parsen - reader = csv.DictReader(io.StringIO(csv_text), delimiter=';') + # Debug: Zeige erste Zeilen + lines = csv_text.strip().split('\n') + if len(lines) <= 1: + # Datei ist leer oder nur Header + return [] + # CSV parsen - versuche verschiedene Delimiter + delimiter = ';' if ';' in lines[0] else (',' if ',' in lines[0] else '\t') + reader = csv.DictReader(io.StringIO(csv_text), delimiter=delimiter) + + row_count = 0 for row in reader: + row_count += 1 try: trade = self._parse_csv_row(row) if trade: trades.append(trade) except Exception as e: - print(f"[{self.name}] Error parsing row: {e}") + if row_count <= 2: + print(f"[{self.name}] Error parsing row: {e}, keys: {list(row.keys())[:5]}") continue print(f"[{self.name}] Parsed {len(trades)} trades from {filename}")