initial release: GizerBridge v1.0
This commit is contained in:
@@ -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()
|
||||
Reference in New Issue
Block a user