diff --git a/backend/app.py b/backend/app.py index 624cc2f..5989b49 100644 --- a/backend/app.py +++ b/backend/app.py @@ -1,12 +1,12 @@ -"""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. -""" +"""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 @@ -132,49 +132,54 @@ def start_flex_startup_sync_if_needed() -> None: # 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 +# 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 start_flex_startup_sync_if_needed() - - -@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("/") +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/lots") def api_lots(): + api_flex_sync() flex_status = get_flex_status() if flex_status.get("configured") and flex_status.get("data_dir"): latest_file, flex_lots = load_latest_flex_lots(flex_status["data_dir"]) + print( + f"[D]Latest file{latest_file} " + ) + if latest_file and flex_lots: return jsonify(flex_lots) @@ -341,19 +346,19 @@ def api_flex_sync(): _flex_sync_state["last_result"] = "error" _flex_sync_state["last_error"] = str(exc) return jsonify({"ok": False, "error": str(exc)}), 500 - - -@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) + + +@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)