from __future__ import annotations from pathlib import Path import os import threading import time import uuid from flask import Flask, jsonify, render_template, request from exchange import ExchangeGuard from trading_engine import ConfigStore, TradingAnalyzer from storage import TradeStore BASE_DIR = Path(__file__).resolve().parent app = Flask(__name__) config_store = ConfigStore(BASE_DIR / "config.json") store = TradeStore(BASE_DIR / "data" / "trade_web.sqlite3") analyzer = TradingAnalyzer() exchange_guard = ExchangeGuard() optimizer_jobs: dict[str, dict] = {} optimizer_lock = threading.Lock() @app.get("/") def index(): return render_template("index.html") @app.get("/api/config") def get_config(): return jsonify(config_store.load()) @app.post("/api/config") def update_config(): config = config_store.load() payload = request.get_json(silent=True) or {} symbol = str(payload.get("symbol", config.get("symbol", "BTCUSDT"))).upper().strip() signal_mode = str(payload.get("signal_mode", config.get("signal_mode", "balanced"))).strip() if symbol: config["symbol"] = symbol if symbol not in config.get("available_symbols", []): config.setdefault("available_symbols", []).append(symbol) if signal_mode in config.get("available_signal_modes", ["balanced", "high_precision", "lux_style"]): config["signal_mode"] = signal_mode config_store.save(config) return jsonify(config) @app.get("/api/analyze") def analyze(): config = config_store.load() demo = request.args.get("demo", "").lower() in {"1", "true", "yes"} result = analyzer.analyze(config, use_demo_data=demo) result["risk_plan"] = analyzer.risk_plan(config, result["signal"]) return jsonify(result) @app.get("/api/backtest") def backtest(): config = config_store.load() demo = request.args.get("demo", "").lower() in {"1", "true", "yes"} symbols = request.args.get("symbols") modes = request.args.get("modes") result = analyzer.backtest( config, symbols=[item.strip().upper() for item in symbols.split(",") if item.strip()] if symbols else None, modes=[item.strip() for item in modes.split(",") if item.strip()] if modes else None, candles=int(request.args.get("candles", "360")), horizon=int(request.args.get("horizon", "12")), use_demo_data=demo, ) run_id = store.save_run("backtest", result, label=",".join(result["settings"]["symbols"])) result["run_id"] = run_id return jsonify(result) @app.get("/api/forecast") def forecast(): config = config_store.load() demo = request.args.get("demo", "").lower() in {"1", "true", "yes"} return jsonify( analyzer.forecast( config, candles=int(request.args.get("candles", "220")), horizon=int(request.args.get("horizon", "24")), use_demo_data=demo, ) ) @app.get("/api/optimize") def optimize(): config = config_store.load() demo = request.args.get("demo", "").lower() in {"1", "true", "yes"} symbols = request.args.get("symbols") result = analyzer.optimize( config, symbols=[item.strip().upper() for item in symbols.split(",") if item.strip()] if symbols else None, candles=int(request.args.get("candles", "320")), horizon=int(request.args.get("horizon", "12")), use_demo_data=demo, ) return jsonify(result) @app.post("/api/optimize/start") def optimize_start(): config = config_store.load() payload = request.get_json(silent=True) or {} symbols = payload.get("symbols") demo = bool(payload.get("demo")) job_id = uuid.uuid4().hex with optimizer_lock: optimizer_jobs[job_id] = { "id": job_id, "status": "running", "done": 0, "total": 0, "best": None, "candidates": [], "best_history": [], "result": None, "error": None, "cancel": False, "started_at": int(time.time()), } def progress(update: dict) -> None: with optimizer_lock: job = optimizer_jobs.get(job_id) if not job: return job["done"] = update.get("done", job["done"]) job["total"] = update.get("total", job["total"]) job["best"] = update.get("best") if update.get("candidate"): job["candidates"].append(update["candidate"]) job["candidates"] = sorted(job["candidates"], key=lambda item: item["score"], reverse=True)[:8] if update.get("convergence"): job["best_history"].append(update["convergence"]) def should_cancel() -> bool: with optimizer_lock: return bool(optimizer_jobs.get(job_id, {}).get("cancel")) def run_job() -> None: try: result = analyzer.optimize( config, symbols=[item.strip().upper() for item in symbols.split(",") if item.strip()] if isinstance(symbols, str) else None, candles=int(payload.get("candles", 320)), horizon=int(payload.get("horizon", 12)), use_demo_data=demo, progress=progress, should_cancel=should_cancel, ) with optimizer_lock: job = optimizer_jobs[job_id] run_id = store.save_run("optimizer", result, label=",".join(result["settings"]["symbols"])) result["run_id"] = run_id job["result"] = result job["best"] = result.get("best") job["status"] = "cancelled" if job.get("cancel") else "done" job["finished_at"] = int(time.time()) except Exception as exc: with optimizer_lock: job = optimizer_jobs[job_id] job["status"] = "error" job["error"] = str(exc) job["finished_at"] = int(time.time()) threading.Thread(target=run_job, daemon=True).start() return jsonify({"job_id": job_id}) @app.get("/api/optimize/status/") def optimize_status(job_id: str): with optimizer_lock: job = optimizer_jobs.get(job_id) if not job: return jsonify({"error": "job nicht gefunden"}), 404 return jsonify({key: value for key, value in job.items() if key != "cancel"}) @app.post("/api/optimize/cancel/") def optimize_cancel(job_id: str): with optimizer_lock: job = optimizer_jobs.get(job_id) if not job: return jsonify({"error": "job nicht gefunden"}), 404 job["cancel"] = True return jsonify({"status": "cancel_requested"}) @app.post("/api/optimize/apply") def apply_optimization(): config = config_store.load() payload = request.get_json(silent=True) or {} params = payload.get("signal_params") if not isinstance(params, dict): return jsonify({"error": "signal_params fehlt"}), 400 config["signal_params"] = params symbol_params = payload.get("symbol_params") if isinstance(symbol_params, dict): config["symbol_params"] = symbol_params config["optimizer_suggestion"] = { "applied_at": int(__import__("time").time()), "params": params, "symbol_params": config.get("symbol_params", {}), "source": "api/optimize", } config_store.save(config) return jsonify(config) @app.get("/api/history") def history(): kind = request.args.get("kind") limit = int(request.args.get("limit", "20")) return jsonify({"runs": store.recent_runs(kind=kind, limit=limit)}) @app.get("/api/health") def health(): config = config_store.load() return jsonify( { "status": "ok", "updated_at": int(time.time()), "database": str(store.path), "execution": config.get("execution", {}), "risk_management": config.get("risk_management", {}), "exchange_guard": exchange_guard.status(config), "optimizer_jobs": { "running": sum(1 for job in optimizer_jobs.values() if job.get("status") == "running"), "total": len(optimizer_jobs), }, } ) @app.get("/api/paper/orders") def paper_orders(): return jsonify({"orders": store.recent_paper_orders(limit=int(request.args.get("limit", "25")))}) @app.post("/api/paper/order") def paper_order(): config = config_store.load() payload = request.get_json(silent=True) or {} signal = payload.get("signal") if not isinstance(signal, dict): analysis = analyzer.analyze(config, use_demo_data=bool(payload.get("demo"))) signal = analysis["signal"] plan = analyzer.risk_plan(config, signal) if not plan["paper_allowed"]: return jsonify({"error": "paper order blockiert", "risk_plan": plan}), 400 order = { "symbol": config.get("symbol", "BTCUSDT"), "side": plan.get("side", signal.get("side", "BUY")), "status": "paper_open", "quantity": plan["quantity"], "entry": plan["entry"], "stop": plan["stop"], "target": plan["target"], "risk_amount": plan["risk_amount"], "risk_plan": plan, "signal": signal, } order["id"] = store.save_paper_order(order) return jsonify(order) @app.get("/api/exchange/status") def exchange_status(): return jsonify(exchange_guard.status(config_store.load())) @app.post("/api/exchange/order") def exchange_order(): config = config_store.load() payload = request.get_json(silent=True) or {} return jsonify(exchange_guard.place_order(config, payload)) if __name__ == "__main__": host = os.environ.get("FLASK_HOST", "127.0.0.1") port = int(os.environ.get("PORT", "5050")) debug = os.environ.get("FLASK_DEBUG", "1") == "1" app.run(host=host, port=port, debug=debug)