add CHANGELOG, icon_full, update gitignore

This commit is contained in:
2026-05-22 18:42:25 +02:00
parent 408f36135d
commit c474b82bf0
7 changed files with 158 additions and 125 deletions
+38 -36
View File
@@ -112,46 +112,50 @@ def _build_open_orders_request():
# ── Message parsers ───────────────────────────────────────────────────────────
# HISTORICAL_ORDER_FILL_RESPONSE empirisch ermittelte Offsets (480-Byte-Pakete):
# HISTORICAL_ORDER_FILL_RESPONSE Offsets empirisch bestätigt (480-Byte-Pakete, Rithmic):
# 0 uint16 Size
# 2 uint16 Type
# 4 int32 RequestID
# 8 int32 TotalNumberMessages
# 12 int32 MessageNumber
# 16 char[64] Symbol
# 80 char[16] Exchange
# 96 char[32] ??? (ASCII-Feld, Inhalt noch unbekannt)
# 128 uint32 BuySell (1=BUY, 2=SELL)
# 132 uint32 ??? (4 Bytes Padding/unbekannt)
# 136 double Price (136 % 8 == 0, 8-Byte-aligned)
# 144 int64 DateTime (Unix-Sekunden)
# 152 double Volume/Qty
# 160 char[32] FillIdentifier/TradeID
# 192 char[32] ???
# 224 char[32] TradeAccount
# 256 uint8 NoOrderFills
# 257 char[96] InfoText
# 353 char[64] UniqueExecutionID
# 417 char[32] ExchangeOrderID
# 16 char[64] Symbol (z.B. "MNQM6.CME")
# 80 char[16] Exchange (leer bei Rithmic, Exchange steckt im Symbol)
# 96 char[32] FillIdentifier (laufende Fill-Nr. als String, z.B. "592")
# 128 uint32 BuySell (1=BUY, 2=SELL)
# 132 uint32 unbekannt (immer 0)
# 136 double Price × 100 (z.B. 2902225.0 → 29022.25 Punkte)
# 144 int64 DateTime (Unix-Sekunden)
# 152 double Volume/Qty (Anzahl Kontrakte, z.B. 1.0)
# 160 char[32] TradeID (Rithmic Order-ID, z.B. "1031001")
# 192 char[32] unbekannt
# 224 char[32] TradeAccount (z.B. "4PRO-RQTPUT")
# 256 uint8 FillStatus (1=partial, 2=final KEIN NoOrderFills-Flag)
# 257 char[96] InfoText (z.B. "Rithmic Direct - DTC (Filled)[final]")
# 353 char[64] unbekannt
# 416 char[64] UniqueExecutionID (z.B. "2537652419")
def _parse_fill_resp(data):
if len(data) < 260:
return None
try:
no_fills = bool(struct.unpack_from("<B", data, 256)[0]) if len(data) > 256 else False
price = struct.unpack_from("<d", data, 136)[0]
qty = struct.unpack_from("<d", data, 152)[0]
buysell = struct.unpack_from("<I", data, 128)[0]
raw_price = struct.unpack_from("<d", data, 136)[0]
price = raw_price / 100.0 # Rithmic liefert price × 100
qty = struct.unpack_from("<d", data, 152)[0]
buysell = struct.unpack_from("<I", data, 128)[0]
total = struct.unpack_from("<i", data, 8)[0]
msgno = struct.unpack_from("<i", data, 12)[0]
return {
"req_id": struct.unpack_from("<i", data, 4)[0],
"total": total,
"msgno": msgno,
"symbol": _unpack_str(data, 16, 64),
"price": price,
"qty": qty,
"datetime": _unpack_ts(data, 144),
"buysell": buysell,
"account": _unpack_str(data, 224, 32),
"no_fills": no_fills,
"unique_id": _unpack_str(data, 353, 64) if len(data) >= 417 else "",
"no_fills": total > 0 and msgno >= total,
"unique_id": _unpack_str(data, 416, 64) if len(data) >= 480 else "",
}
except struct.error:
return None
@@ -193,7 +197,7 @@ def _parse_order_update(data):
return None
try:
qty = struct.unpack_from("<d", data, 320)[0]
price = struct.unpack_from("<d", data, 304)[0]
price = struct.unpack_from("<d", data, 304)[0] / 100.0
if qty <= 0 or price <= 0:
return None # not a fill event
uid = _unpack_str(data, 328, 64) if len(data) >= 392 else ""
@@ -258,22 +262,14 @@ class DTCClient:
for msg_type, data in self._read_iter(timeout=15):
if msg_type != _HIST_FILL_RESP:
continue
if len(data) < 16:
continue
if struct.unpack_from("<i", data, 4)[0] != req_id:
continue
total = struct.unpack_from("<i", data, 8)[0]
msgno = struct.unpack_from("<i", data, 12)[0]
f = _parse_fill_resp(data)
if not f:
if not f or f["req_id"] != req_id:
continue
if f["no_fills"] or (total > 0 and msgno >= total):
break
# Gültige Fills: BuySell muss 1 (BUY) oder 2 (SELL) sein, Qty > 0
if f["buysell"] in (_BUY, _SELL) and f["qty"] > 0:
if f["buysell"] in (_BUY, _SELL) and f["qty"] > 0 and f["price"] > 0:
fills.append(f)
if f["no_fills"]:
break
return fills
def subscribe_order_updates(self):
@@ -400,8 +396,14 @@ def _pending_to_trade(fills, account, symbol, instruments):
else:
execs = f"Sell {lots} @ {ep:.4f} / {entry_str},Buy {lots} @ {xp:.4f} / {exit_str}"
# Trade-ID uses only actual fill datetimes — never datetime.now().
# This makes IDs identical whether generated from the historical-fill path
# or from the real-time ORDER_UPDATE path for the same trade.
_id_ts = entry_ts or exit_ts
_id_str = _id_ts.strftime(_fmt) if _id_ts else ""
return {
"trade_id": f"{account}_{symbol}_{ep:.4f}_{xp:.4f}_{entry_str}",
"trade_id": f"{account}_{symbol}_{ep:.4f}_{xp:.4f}_{_id_str}",
"instrument": symbol,
"direction": direction,
"lots": lots,