SPB Git

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%
4.0 KB · 110 lines python
Raw Blame History
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