-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathapi_server.py
More file actions
113 lines (99 loc) · 4 KB
/
Copy pathapi_server.py
File metadata and controls
113 lines (99 loc) · 4 KB
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
"""HFT-Engine API server — thin HTTP router over the api.handlers package.
Serves the dashboard/ front end and dispatches JSON endpoints to
api.handlers (backtest, portfolio, strategies, orderflow, live).
"""
import json
import sys
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
from pathlib import Path
from api import backtest, live, orderflow, portfolio, strategies
DASH = Path(__file__).parent / "dashboard"
class Handler(BaseHTTPRequestHandler):
def _json(self, code: int, payload: dict):
body = json.dumps(payload).encode()
self.send_response(code)
self.send_header("Content-Type", "application/json")
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def _read_body(self) -> dict:
n = int(self.headers.get("Content-Length", 0))
if n <= 0:
return {}
return json.loads(self.rfile.read(n))
def do_GET(self):
if self.path == "/api/health":
return self._json(200, {"ok": True, "engine": "hft-engine"})
if self.path.startswith("/api/live/stream"):
return self._stream_live()
# static files
rel = self.path.split("?")[0].lstrip("/")
if not rel:
rel = "index.html"
target = (DASH / rel).resolve()
if DASH not in target.parents and target != DASH:
return self._json(404, {"error": "not found"})
if not target.exists() or not target.is_file():
return self._json(404, {"error": "not found"})
ctype = {
".html": "text/html; charset=utf-8",
".js": "application/javascript",
".css": "text/css",
".json": "application/json",
}.get(target.suffix, "application/octet-stream")
body = target.read_bytes()
self.send_response(200)
self.send_header("Content-Type", ctype)
self.send_header("Content-Length", str(len(body)))
self.end_headers()
self.wfile.write(body)
def _stream_live(self):
"""SSE stream: push the live engine status every 1s."""
import time
from api.handlers import live as live_handler
self.send_response(200)
self.send_header("Content-Type", "text/event-stream")
self.send_header("Cache-Control", "no-cache")
self.send_header("Connection", "keep-alive")
self.end_headers()
try:
while True:
payload = live_handler({"action": "status"})
body = f"data: {json.dumps(payload)}\n\n".encode()
self.wfile.write(body)
self.wfile.flush()
time.sleep(1)
except (BrokenPipeError, ConnectionResetError):
pass
def do_POST(self):
try:
cfg = self._read_body()
except Exception as exc:
import traceback
traceback.print_exc()
return self._json(400, {"error": f"bad body: {exc}"})
try:
if self.path == "/api/backtest":
return self._json(200, backtest(cfg))
if self.path == "/api/portfolio":
return self._json(200, portfolio(cfg))
if self.path == "/api/strategies":
return self._json(200, strategies(cfg))
if self.path == "/api/orderflow":
return self._json(200, orderflow(cfg))
if self.path.startswith("/api/live"):
return self._json(200, live(cfg))
return self._json(404, {"error": f"unknown endpoint {self.path}"})
except Exception as exc:
import traceback
traceback.print_exc()
return self._json(500, {"error": str(exc)})
def log_message(self, *args):
pass
def main():
port = int(sys.argv[1]) if len(sys.argv) > 1 else 8765
srv = ThreadingHTTPServer(("127.0.0.1", port), Handler)
print(f"HFT-Engine API + dashboard on http://127.0.0.1:{port}", flush=True)
srv.serve_forever()
if __name__ == "__main__":
main()