87 lines
3.3 KiB
Python
87 lines
3.3 KiB
Python
#!/usr/bin/env python3
| |||
# -*- coding: utf-8 -*-
| |||
"""
| |||
ingest_telemetry.py — File-based telemetry fallback ingester (Track B).
| |||
| |||
Reads MQL5\\Files\\telemetry.jsonl (written by CentaurQuant.mq5 when the
| |||
TCP transport is unavailable — see AppendTelemetryFile in the EA) and
| |||
imports SDP Trade_Opened / Trade_Closed frames into centaur_telemetry.db
| |||
via TelemetryDB — the same schema/logic the router uses.
| |||
| |||
Safe to run repeatedly: TelemetryDB dedupes on the PRIMARY KEY ticket
| |||
(INSERT OR IGNORE for opens, UPDATE for closes).
| |||
| |||
Usage:
| |||
python ingest_telemetry.py [--jsonl PATH] [--db PATH] [--keep]
| |||
"""
| |||
| |||
import argparse
| |||
import json
| |||
import os
| |||
import sys
| |||
| |||
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
| |||
from database.telemetry import TelemetryDB # noqa: E402
| |||
| |||
DEFAULT_JSONL = r"D:\TradingTerminal\MetaTrader 5\MQL5\Files\telemetry.jsonl"
| |||
DEFAULT_DB = os.path.join(os.path.dirname(os.path.abspath(__file__)),
| |||
"centaur_telemetry.db")
| |||
| |||
| |||
def main() -> None:
| |||
ap = argparse.ArgumentParser(description="Centaur file-telemetry ingester")
| |||
ap.add_argument("--jsonl", default=DEFAULT_JSONL,
| |||
help="path to telemetry.jsonl (default: %(default)s)")
| |||
ap.add_argument("--db", default=DEFAULT_DB,
| |||
help="SQLite telemetry DB path (default: %(default)s)")
| |||
ap.add_argument("--keep", action="store_true",
| |||
help="do NOT truncate the JSONL after a successful ingest")
| |||
args = ap.parse_args()
| |||
| |||
if not os.path.exists(args.jsonl):
| |||
print(f"ingest_telemetry: no telemetry file at {args.jsonl} — nothing to do.")
| |||
return
| |||
| |||
db = TelemetryDB(args.db)
| |||
opened = closed = skipped = 0
| |||
try:
| |||
with open(args.jsonl, "r", encoding="utf-8", errors="replace") as f:
| |||
for line in f:
| |||
line = line.strip()
| |||
if not line:
| |||
continue
| |||
try:
| |||
msg = json.loads(line)
| |||
except json.JSONDecodeError:
| |||
skipped += 1
| |||
continue
| |||
action = msg.get("action_type")
| |||
try:
| |||
if action == "Trade_Opened":
| |||
if db.log_trade_opened(msg):
| |||
opened += 1
| |||
elif action == "Trade_Closed":
| |||
if db.log_trade_closed(msg):
| |||
closed += 1
| |||
else:
| |||
skipped += 1 # heartbeat/tick frames are not persisted
| |||
except Exception as exc: # a bad frame must not abort the batch
| |||
skipped += 1
| |||
print(f"ingest_telemetry: frame error: {exc!r}")
| |||
finally:
| |||
db.close()
| |||
| |||
print(f"ingest_telemetry: opened={opened} closed={closed} skipped={skipped}")
| |||
| |||
# Consume the file only when something was processed; keeps the EA's
| |||
# concurrent appends from being lost in a race with an empty truncate.
| |||
if not args.keep and (opened + closed + skipped) > 0:
| |||
try:
| |||
open(args.jsonl, "w", encoding="utf-8").close()
| |||
print(f"ingest_telemetry: {args.jsonl} truncated (consumed).")
| |||
except OSError as exc:
| |||
print(f"ingest_telemetry: WARNING could not truncate: {exc}")
| |||
| |||
| |||
if __name__ == "__main__":
| |||
main()
|