"""Flask backend for IB dashboard. Provides simple REST endpoints that the vanilla-JS frontend polls: - GET /api/quotes -> latest market quotes - GET /api/positions -> latest positions - POST /api/subscribe { symbols: ["AAPL","MSFT"] } -> start subscribing This app expects an IB Gateway or TWS running locally on port 7496. """ from flask import Flask, jsonify, request, send_from_directory import os import threading from ib_client import IBClient import time # Use an absolute path for the static folder so Flask can reliably serve files STATIC_FOLDER = os.path.abspath(os.path.join(os.path.dirname(__file__), "..", "frontend")) app = Flask(__name__, static_folder=STATIC_FOLDER, static_url_path="/static") # instantiate the IB client with a process-unique client id to avoid # collisions when Flask's reloader spawns multiple processes. ib = IBClient(client_id=os.getpid()) # Start connection only in the main process (Werkzeug sets WERKZEUG_RUN_MAIN) if os.environ.get("WERKZEUG_RUN_MAIN") == "true" or not os.environ.get("WERKZEUG_RUN_MAIN"): # attempt to start the connection; failures are logged by the client try: ib.connect_and_start() except Exception: pass @app.route("/") def index(): return send_from_directory(app.static_folder, "index.html") @app.route("/api/quotes") def api_quotes(): return jsonify(ib.get_quotes()) @app.route("/api/positions") def api_positions(): # request a fresh positions snapshot from IB and wait briefly for callbacks try: ib.request_positions() except Exception: pass # wait up to 2 seconds for positions to arrive deadline = time.time() + 2.0 while time.time() < deadline: positions = ib.get_positions() if positions: return jsonify(positions) time.sleep(0.1) # return whatever we have (possibly empty) return jsonify(ib.get_positions()) @app.route("/api/subscribe", methods=["POST"]) def api_subscribe(): data = request.get_json(force=True) symbols = data.get("symbols", []) if isinstance(data, dict) else [] if not isinstance(symbols, list): return jsonify({"error": "symbols must be a list"}), 400 for s in symbols: ib.subscribe_market_data(s) return jsonify({"subscribed": symbols}) if __name__ == "__main__": # simple dev server; disable reloader to avoid duplicate IB client instances app.run(debug=True, port=8000, use_reloader=False)