initial release: GizerBridge v1.0

This commit is contained in:
2026-05-22 12:27:27 +02:00
commit b2e4a8b46a
23 changed files with 2773 additions and 0 deletions
+284
View File
@@ -0,0 +1,284 @@
"""Diagnose-Script für DTC HISTORICAL_ORDER_FILL_RESPONSE Paket-Struktur.
Startet SC, verbindet via DTC, empfängt Fill-Pakete und analysiert
die Byte-Struktur um den Preis-Offset zu finden.
Aufruf: python3 test_dtc_diag.py [account] [num_days]
"""
import socket
import struct
import sys
import time
from datetime import datetime
# ── DTC Konstanten ──────────────────────────────────────────────────────────
_LOGON_REQUEST = 1
_LOGON_RESPONSE = 2
_HEARTBEAT = 3
_HIST_FILLS_REQ = 303
_HIST_FILL_RESP = 304
_PROTOCOL_VER = 8
def _s(text, n):
b = (text or "").encode("ascii", errors="replace")[:n]
return b.ljust(n, b"\x00")
def _build_logon():
fmt = "<HHi32s32s64siiii32s64s32si"
size = struct.calcsize(fmt)
return struct.pack(fmt,
size, _LOGON_REQUEST, _PROTOCOL_VER,
_s("", 32), _s("", 32), _s("GizerBridge-Diag", 64),
0, 0, 10, 0, _s("", 32), _s("", 64), _s("diag", 32), 0)
def _build_hist_req(req_id, account, num_days):
fmt = "<HHi32si32sB7xq"
size = struct.calcsize(fmt)
return struct.pack(fmt,
size, _HIST_FILLS_REQ,
req_id, _s("", 32), num_days, _s(account, 32), 1, 0)
def _recv_msg(sock, buf):
while True:
if len(buf) >= 4:
size = struct.unpack_from("<H", buf, 0)[0]
if size >= 4 and len(buf) >= size:
msg = bytes(buf[:size])
return msg, buf[size:]
try:
chunk = sock.recv(65536)
if not chunk:
raise ConnectionError("Verbindung geschlossen")
buf += chunk
except socket.timeout:
return None, buf
def _unpack_str(data, offset, n):
return data[offset:offset+n].rstrip(b"\x00").decode("ascii", errors="replace")
def _ts(raw):
try:
return datetime.fromtimestamp(raw).strftime("%Y-%m-%d %H:%M:%S") if 0 < raw < 4_102_444_800 else f"invalid({raw})"
except Exception:
return f"error({raw})"
def hex_dump(data, label=""):
"""Formatierter Hex-Dump mit ASCII-Darstellung."""
print(f"\n{'='*60}")
if label:
print(f" {label}")
print(f" Länge: {len(data)} Bytes")
print(f"{'='*60}")
for i in range(0, len(data), 16):
chunk = data[i:i+16]
hex_part = " ".join(f"{b:02X}" for b in chunk)
ascii_part = "".join(chr(b) if 32 <= b < 127 else "." for b in chunk)
print(f" {i:4d} ({i:04X}): {hex_part:<48} |{ascii_part}|")
def analyze_fill_packet(data, fill_num):
"""Umfassende Analyse eines HIST_FILL_RESP Pakets."""
n = len(data)
print(f"\n{'#'*60}")
print(f" FILL-PAKET #{fill_num} ({n} Bytes)")
print(f"{'#'*60}")
# Bekannte Felder ausgeben
print("\n--- Bestätigte Felder ---")
print(f" [0] Size (uint16) = {struct.unpack_from('<H', data, 0)[0]}")
print(f" [2] Type (uint16) = {struct.unpack_from('<H', data, 2)[0]}")
print(f" [4] ReqID (int32) = {struct.unpack_from('<i', data, 4)[0]}")
if n >= 16:
print(f" [8] TotalMsg(int32) = {struct.unpack_from('<i', data, 8)[0]}")
print(f" [12] MsgNum (int32) = {struct.unpack_from('<i', data, 12)[0]}")
if n >= 80:
sym = _unpack_str(data, 16, 64)
print(f" [16] Symbol (ch[64]) = '{sym}'")
if n >= 96:
exch = _unpack_str(data, 80, 16)
print(f" [80] Exchange(ch[16]) = '{exch}'")
if n >= 132:
bs = struct.unpack_from("<I", data, 128)[0]
print(f" [128] BuySell (uint32) = {bs} ({'BUY' if bs==1 else 'SELL' if bs==2 else '???'})")
if n >= 152:
dt_raw = struct.unpack_from("<q", data, 144)[0]
print(f" [144] DateTime(int64) = {dt_raw} = {_ts(dt_raw)}")
if n >= 160:
qty = struct.unpack_from("<d", data, 152)[0]
print(f" [152] Volume (double) = {qty}")
if n >= 192:
tid = _unpack_str(data, 160, 32)
print(f" [160] TradeID (ch[32]) = '{tid}'")
if n >= 256:
acc = _unpack_str(data, 224, 32)
print(f" [224] Account (ch[32]) = '{acc}'")
# Unbekannte Zone 96-128 analysieren
print("\n--- Zone 96-128 (UNBEKANNT, möglicherweise Preis) ---")
for off in range(96, min(128, n-3), 4):
if off + 4 <= n:
as_uint32 = struct.unpack_from("<I", data, off)[0]
as_int32 = struct.unpack_from("<i", data, off)[0]
as_float32 = struct.unpack_from("<f", data, off)[0]
as_ascii = data[off:off+4].decode("ascii", errors=".")
print(f" [{off:3d}] uint32={as_uint32:10d} int32={as_int32:10d} float32={as_float32:12.4f} ascii='{as_ascii}'")
# Zone 128-160 analysieren (zwischen BuySell und TradeID)
print("\n--- Zone 128-160 (zwischen BuySell und DateTime/Volume) ---")
for off in range(128, min(160, n-3), 4):
if off + 4 <= n:
as_uint32 = struct.unpack_from("<I", data, off)[0]
as_int32 = struct.unpack_from("<i", data, off)[0]
as_float32 = struct.unpack_from("<f", data, off)[0]
as_ascii = data[off:off+4].decode("ascii", errors=".")
label = ""
if off == 128: label = " ← BuySell (bestätigt)"
if off == 144: label = " ← DateTime (bestätigt)"
if off == 152: label = " ← Volume (bestätigt)"
print(f" [{off:3d}] uint32={as_uint32:10d} int32={as_int32:10d} float32={as_float32:12.4f} ascii='{as_ascii}'{label}")
# Doubles-Scan über gesamtes Paket
print("\n--- Doubles (alle 8-Byte-ausgerichteten Offsets) ---")
for off in range(0, n-7, 8):
val = struct.unpack_from("<d", data, off)[0]
tag = ""
if 1.0 <= val <= 10.0: tag = " ← Lots?"
if 5000 <= val <= 30000: tag = " ← PREIS? (Futures-Range)"
if 1_600_000_000 <= val <= 2_000_000_000: tag = " ← Timestamp als double?"
if tag or (off in (96, 104, 112, 120, 136, 152)):
print(f" [{off:3d}] double = {val:20.6f}{tag}")
# Alle uint32 die MNQ-Preis in Ticks sein könnten (80000-100000)
print("\n--- uint32 im Bereich 50000-120000 (Preis in Ticks à 0.25?) ---")
for off in range(0, n-3, 4):
val = struct.unpack_from("<I", data, off)[0]
if 50000 <= val <= 120000:
print(f" [{off:3d}] uint32 = {val} → Preis = {val * 0.25:.2f}")
# uint32 im Bereich für MNQ-Preis * 100
print("\n--- uint32 im Bereich 1500000-3000000 (Preis * 100?) ---")
for off in range(0, n-3, 4):
val = struct.unpack_from("<I", data, off)[0]
if 1_500_000 <= val <= 3_000_000:
print(f" [{off:3d}] uint32 = {val} → Preis/100 = {val / 100:.2f}")
# int64 Scan (könnte Preis als Festkomma sein)
print("\n--- int64 im Bereich 50000-5000000 (Preis-Integer?) ---")
for off in range(0, n-7, 8):
val = struct.unpack_from("<q", data, off)[0]
if 50_000 <= val <= 5_000_000:
print(f" [{off:3d}] int64 = {val} → /4={val/4:.2f} /100={val/100:.2f} /400={val/400:.4f}")
def main():
account = sys.argv[1] if len(sys.argv) > 1 else ""
num_days = int(sys.argv[2]) if len(sys.argv) > 2 else 30
print(f"DTC Diagnose-Tool")
print(f"Account: '{account}', Tage: {num_days}")
print(f"Verbinde zu localhost:11099 ...")
try:
sock = socket.socket()
sock.settimeout(10)
sock.connect(("localhost", 11099))
sock.settimeout(5)
print("Verbunden!")
except Exception as e:
print(f"FEHLER: {e}")
return
buf = b""
# Logon
sock.sendall(_build_logon())
for _ in range(20):
msg, buf = _recv_msg(sock, buf)
if msg is None:
continue
mtype = struct.unpack_from("<H", msg, 2)[0]
if mtype == _LOGON_RESPONSE:
result = struct.unpack_from("<i", msg, 8)[0]
text = _unpack_str(msg, 12, 96)
print(f"Logon-Antwort: result={result}, text='{text}'")
if result != 1:
print("FEHLER: Logon abgelehnt")
return
break
# Historical fills request
req_id = int(time.time() * 1000) & 0x7FFFFFFF
sock.sendall(_build_hist_req(req_id, account, num_days))
print(f"\nHistorical-Fills-Anfrage gesendet (req_id={req_id})")
fill_count = 0
raw_packets = []
deadline = time.time() + 20
while time.time() < deadline:
msg, buf = _recv_msg(sock, buf)
if msg is None:
continue
mtype = struct.unpack_from("<H", msg, 2)[0]
if mtype != _HIST_FILL_RESP:
continue
fill_count += 1
raw_packets.append(msg)
# Zeige die ersten 3 und letzten 2 Pakete im Detail
if fill_count <= 3 or (fill_count <= 5 and len(msg) < 100):
analyze_fill_packet(msg, fill_count)
# Prüfe NoOrderFills an verschiedenen Offsets
for check_off in [256, 257, 260, 264, 188, 180]:
if len(msg) > check_off:
val = struct.unpack_from("<B", msg, check_off)[0]
if val == 1:
print(f"\n>>> NoOrderFills=1 gefunden bei Offset {check_off}! (fill_count={fill_count})")
# Letztes Paket immer anzeigen (NoOrderFills-Paket)
if fill_count == 1:
print(f"\n=== ERSTES PAKET (hex dump) ===")
hex_dump(msg)
print(f"\n\nGesamt empfangene Fill-Pakete: {fill_count}")
if raw_packets:
print("\n=== LETZTES PAKET (hex dump) ===")
hex_dump(raw_packets[-1], f"Paket #{fill_count}")
if len(raw_packets) >= 2:
print("\n=== VERGLEICH: Preis-relevante Bytes in allen Paketen ===")
print(f" {'Offset':<8} ", end="")
for i in range(min(len(raw_packets), 12)):
print(f"{'Paket'+str(i+1):>12}", end="")
print()
# Zeige uint32-Werte für Offsets 96-200
for off in range(88, 200, 4):
row = f" [{off:3d}] "
interesting = False
vals = []
for pkt in raw_packets[:12]:
if len(pkt) > off + 3:
v = struct.unpack_from("<I", pkt, off)[0]
vals.append(v)
row += f"{v:>12}"
else:
row += f"{'N/A':>12}"
# Interessant wenn Werte variieren (könnten Preise sein)
if vals and max(vals) != min(vals):
interesting = True
if off in (128, 144, 152, 160):
interesting = True
if interesting:
print(row + " ← variiert!" if max(vals) != min(vals) else row)
sock.close()
print("\nFertig.")
if __name__ == "__main__":
main()