File size: 5,470 Bytes
c10af81
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#!/usr/bin/env python3
"""
patch_websocket_hub.py
──────────────────────
Injects TradeLogParser (from hub_dashboard_service.py) and four API routes
into /app/websocket_hub.py so that port 7860 serves:

  GET /api/trades          β†’ full open + closed state + stats
  GET /api/trades/open     β†’ open trades only
  GET /api/trades/closed   β†’ recent closed trades + stats  (?limit=N, default 50)
  GET /api/health          β†’ service health including trade counts

Usage:
    python3 patch_websocket_hub.py [--target /app/websocket_hub.py] [--dry-run]
"""

import argparse
import shutil
import sys
from datetime import datetime
from pathlib import Path

# ── Snippet 1: Parser instantiation block ─────────────────────────────────────
# Inserted BEFORE  `_START_TIME = time.time()`
PARSER_BLOCK = '''
# ── Trade log parser β€” injected so /api/trades is served on port 7860 ────────
import sys as _sys, os as _os
_sys.path.insert(0, '/app')
from hub_dashboard_service import TradeLogParser as _TradeLogParser
_trade_parser = _TradeLogParser(log_dir=_os.environ.get("RANKER_LOG_DIR", "/app/ranker_logs"))
_trade_parser.start_background()
'''

# ── Snippet 2: FastAPI route definitions ──────────────────────────────────────
# Appended AFTER  `_START_TIME = time.time()`
TRADE_ROUTES = '''

# ── /api/trades routes β€” injected by patch_websocket_hub.py ──────────────────
@app.get("/api/trades")
async def api_trades():
    """Full trade state: open trades, recent closed trades, summary stats."""
    return JSONResponse(_trade_parser.get_state())


@app.get("/api/trades/open")
async def api_trades_open():
    """Open trades only."""
    state = _trade_parser.get_state()
    return JSONResponse({"open": state["open"]})


@app.get("/api/trades/closed")
async def api_trades_closed(limit: int = 50):
    """Recent closed trades (newest first) + cumulative stats."""
    state = _trade_parser.get_state()
    return JSONResponse({
        "closed": state["closed"][:limit],
        "stats":  state["stats"],
    })


@app.get("/api/health")
async def api_health():
    """Service health check β€” includes live trade counts and log-file inventory."""
    import glob as _glob
    return JSONResponse({
        "service":      "websocket_hub",
        "version":      "v2.0",
        "status":       "running",
        "log_files":    len(_glob.glob("/app/ranker_logs/*.log")),
        "trade_open":   len(_trade_parser.get_state()["open"]),
        "trade_closed": len(_trade_parser.get_state()["closed"]),
    })
'''

ANCHOR = "_START_TIME = time.time()"


def patch(source: str) -> str:
    """Apply both substitutions and return the patched source."""
    if ANCHOR not in source:
        raise ValueError(
            f"Anchor '{ANCHOR}' not found in source β€” "
            "is this the right file?"
        )

    # Count occurrences so we can give a clear warning if there are multiples.
    count = source.count(ANCHOR)
    if count > 1:
        print(
            f"[WARNING] Anchor appears {count} times; "
            "only the first occurrence will be modified.",
            file=sys.stderr,
        )

    # ── Step 1: prepend the parser block before the anchor ────────────────────
    src = source.replace(ANCHOR, PARSER_BLOCK + ANCHOR, 1)

    # ── Step 2: append trade routes after the (now-unique) anchor ────────────
    #   After step 1, ANCHOR still appears exactly once (at the end of the
    #   inserted block), so a second replace(…, 1) is safe.
    src = src.replace(ANCHOR, ANCHOR + TRADE_ROUTES, 1)

    return src


def main() -> None:
    ap = argparse.ArgumentParser(description="Patch websocket_hub.py with trade routes.")
    ap.add_argument(
        "--target",
        default="/app/websocket_hub.py",
        help="Path to websocket_hub.py (default: /app/websocket_hub.py)",
    )
    ap.add_argument(
        "--dry-run",
        action="store_true",
        help="Print the patched source to stdout instead of writing to disk.",
    )
    args = ap.parse_args()

    target = Path(args.target)

    if not target.exists():
        print(f"[ERROR] Target file not found: {target}", file=sys.stderr)
        sys.exit(1)

    original = target.read_text(encoding="utf-8")

    # Guard: don't double-patch
    if "_trade_parser" in original:
        print(
            "[SKIP] Patch already applied (_trade_parser already present in file).",
            file=sys.stderr,
        )
        sys.exit(0)

    patched = patch(original)

    if args.dry_run:
        print(patched)
        return

    # ── Backup the original ───────────────────────────────────────────────────
    ts = datetime.now().strftime("%Y%m%d_%H%M%S")
    backup = target.with_suffix(f".bak_{ts}.py")
    shutil.copy2(target, backup)
    print(f"[INFO] Backup written β†’ {backup}")

    target.write_text(patched, encoding="utf-8")
    print(f"[OK] Patch applied β†’ {target}")
    print(f"     Routes added:  /api/trades  /api/trades/open  /api/trades/closed  /api/health")


if __name__ == "__main__":
    main()