spb/hfchart Public
HFChart — la référence des charts haute fréquence : 14 types rendus canvas from scratch (zéro dépendance), données HF Market Data — www.hfchart.io
JavaScript 82.1%
CSS 10%
HTML 5.8%
Python 2.1%
1#!/usr/bin/env python32# -*- coding: utf-8 -*-3"""4================================================================================5 HFChart — plateforme de référence des charts haute fréquence6 Hôte statique minimal (stdlib uniquement).78 Les données des graphs proviennent de l'API HF Market Data :9 https://www.hfmarketdata.io (interrogée directement par le navigateur)1011 Author : Simon-Pierre Boucher12 Contact : contact@spboucher.ai13================================================================================1415 python3 server.py [port] # défaut: 878716"""17import os18import sys19from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer20from urllib.parse import urlparse2122ROOT = os.path.dirname(os.path.abspath(__file__))23WEB = os.path.join(ROOT, "web")2425MIME = {26 ".html": "text/html; charset=utf-8",27 ".js": "application/javascript; charset=utf-8",28 ".css": "text/css; charset=utf-8",29 ".json": "application/json; charset=utf-8",30 ".svg": "image/svg+xml",31 ".png": "image/png",32 ".ico": "image/x-icon",33 ".webmanifest": "application/manifest+json",34}353637# /embed.js = moteur complet concaténé (transforms + indicateurs + renderers +38# engine + bootstrapper d'embed) — un seul <script> pour les snippets exportés.39EMBED_PARTS = ["js/transforms.js", "js/indicators.js", "js/renderers.js",40 "js/engine.js", "js/embed-boot.js"]41_embed_cache = {"key": None, "body": b""}424344def embed_js():45 paths = [os.path.join(WEB, p) for p in EMBED_PARTS]46 key = tuple(os.path.getmtime(p) for p in paths)47 if _embed_cache["key"] != key:48 chunks = [b"/* HFChart embed bundle - Simon-Pierre Boucher - "49 b"contact@spboucher.ai - https://www.hfchart.io */\n"]50 for p in paths:51 with open(p, "rb") as fh:52 chunks.append(fh.read())53 chunks.append(b"\n;\n")54 _embed_cache["key"] = key55 _embed_cache["body"] = b"".join(chunks)56 return _embed_cache["body"]575859class Handler(BaseHTTPRequestHandler):60 protocol_version = "HTTP/1.1"6162 def log_message(self, fmt, *args):63 sys.stderr.write("[hfchart] %s\n" % (fmt % args))6465 def _send(self, code, body, mime="text/plain; charset=utf-8", cache="no-cache"):66 self.send_response(code)67 self.send_header("Content-Type", mime)68 self.send_header("Content-Length", str(len(body)))69 self.send_header("Cache-Control", cache)70 self.end_headers()71 self.wfile.write(body)7273 def do_GET(self):74 try:75 path = urlparse(self.path).path76 if path == "/health":77 return self._send(200, b'{"status":"ok","service":"hfchart"}',78 "application/json; charset=utf-8")79 if path == "/embed.js":80 self.send_response(200)81 self.send_header("Content-Type", MIME[".js"])82 body = embed_js()83 self.send_header("Content-Length", str(len(body)))84 self.send_header("Cache-Control", "public, max-age=300")85 self.send_header("Access-Control-Allow-Origin", "*")86 self.end_headers()87 self.wfile.write(body)88 return None89 if path == "/":90 path = "/index.html"91 fp = os.path.normpath(os.path.join(WEB, path.lstrip("/")))92 if not fp.startswith(WEB) or not os.path.isfile(fp):93 return self._send(404, b"not found")94 with open(fp, "rb") as fh:95 body = fh.read()96 return self._send(200, body, MIME.get(os.path.splitext(fp)[1],97 "application/octet-stream"))98 except BrokenPipeError:99 pass100101102def main():103 port = int(sys.argv[1]) if len(sys.argv) > 1 else 8787104 print(f"HFChart — http://localhost:{port} (données : https://www.hfmarketdata.io)")105 ThreadingHTTPServer(("0.0.0.0", port), Handler).serve_forever()106107108if __name__ == "__main__":109 main()110