Files
Apex_AI_MT5_EA_Optimizer/app.py
T
LEGSTECH Optimizer cf8613eb49 fix: reports 500, NaN scores, hypothesis dedup blocking
Reports 500 (Internal Server Error):
- app.py: Replace NaN/Infinity->null before json.loads() in both
  /reports and /api/runs routes. Old summary.json files with invalid
  NaN are now handled transparently.
- reports/writer.py: Added _safe_val() helper using math.isfinite().
  All summary.json values now use safe rounding (NaN->0, Inf->0).

No new hypotheses (optimizer stops after 1 iter):
- optimizer_loop.py: Changed dedup from DB-wide history to
  session-scoped (self.session_tested_deltas). Previous runs from
  old sessions no longer block new hypothesis proposals.
- Each tested hypothesis is tracked within the session only.

Result: optimizer now runs multiple iterations, scores are valid
numbers, reports page loads without 500.
2026-04-13 03:29:33 +00:00

170 lines
5.7 KiB
Python

"""
app.py — MT5 EA Optimizer Web App
Double-click to launch. Browser opens automatically at http://localhost:5000
"""
import sys, os, threading, webbrowser, time
from pathlib import Path
# ── Make sure imports resolve from project root ───────────────────────────────
BASE_DIR = Path(__file__).parent
sys.path.insert(0, str(BASE_DIR))
from flask import Flask, render_template, jsonify, request, send_from_directory
from flask_socketio import SocketIO, emit
from optimizer_loop import OptimizerLoop
# ── App setup ─────────────────────────────────────────────────────────────────
app = Flask(__name__,
template_folder="ui/templates",
static_folder="ui/static")
app.config["SECRET_KEY"] = "mt5optimizer2024"
socketio = SocketIO(app, cors_allowed_origins="*", async_mode="threading")
REPORTS_DIR = BASE_DIR / "Reports"
REPORTS_DIR.mkdir(exist_ok=True)
# Global optimizer instance
optimizer: OptimizerLoop = None
optimizer_thread: threading.Thread = None
# ── Routes ────────────────────────────────────────────────────────────────────
@app.route("/")
def index():
return render_template("index.html")
@app.route("/api/status")
def status():
if optimizer is None:
return jsonify({"state": "idle", "iteration": 0, "best_score": 0})
return jsonify(optimizer.get_status())
@app.route("/api/start", methods=["POST"])
def start():
global optimizer, optimizer_thread
if optimizer and optimizer.running:
return jsonify({"ok": False, "msg": "Already running"})
data = request.get_json(silent=True) or {}
optimizer = OptimizerLoop(
config_path=str(BASE_DIR / "config.yaml"),
socketio=socketio,
reports_dir=REPORTS_DIR,
auto_mode=data.get("auto", True),
)
optimizer_thread = threading.Thread(target=optimizer.run, daemon=True)
optimizer_thread.start()
return jsonify({"ok": True})
@app.route("/api/pause", methods=["POST"])
def pause():
if optimizer:
optimizer.toggle_pause()
return jsonify({"ok": True, "paused": optimizer.paused})
return jsonify({"ok": False})
@app.route("/api/stop", methods=["POST"])
def stop():
if optimizer:
optimizer.stop()
return jsonify({"ok": True})
@app.route("/api/skip", methods=["POST"])
def skip():
if optimizer:
optimizer.skip_hypothesis()
return jsonify({"ok": True})
@app.route("/api/history")
def history():
if optimizer is None:
return jsonify([])
return jsonify(optimizer.score_history)
@app.route("/reports")
@app.route("/reports/")
def reports_index():
"""Reports browser page — fixes the 404 on the Reports button."""
import json, re
runs = []
for run_dir in sorted(REPORTS_DIR.iterdir(), reverse=True) if REPORTS_DIR.exists() else []:
if not run_dir.is_dir():
continue
summary = run_dir / "summary.json"
if summary.exists():
try:
txt = summary.read_text(encoding="utf-8")
# Fix legacy NaN values (invalid JSON) written before the fix
txt = re.sub(r'\bNaN\b', 'null', txt)
txt = re.sub(r'\bInfinity\b', 'null', txt)
data = json.loads(txt)
# Replace None scores with 0 for display
data["score"] = data.get("score") or 0
data["score_delta"] = data.get("score_delta") or 0
runs.append(data)
except Exception:
pass
return render_template("reports_index.html", runs=runs[:100])
@app.route("/reports/<path:filename>")
def reports_file(filename):
"""Serve individual report files (HTML, CSV, JSON)."""
return send_from_directory(REPORTS_DIR, filename)
@app.route("/api/runs")
def runs_list():
import json, re
runs = []
if REPORTS_DIR.exists():
for run_dir in sorted(REPORTS_DIR.iterdir(), reverse=True):
if not run_dir.is_dir():
continue
summary = run_dir / "summary.json"
if summary.exists():
try:
txt = summary.read_text(encoding="utf-8")
txt = re.sub(r'\bNaN\b', 'null', txt)
txt = re.sub(r'\bInfinity\b', 'null', txt)
data = json.loads(txt)
data["score"] = data.get("score") or 0
data["score_delta"] = data.get("score_delta") or 0
runs.append(data)
except Exception:
pass
return jsonify(runs[:50])
# ── SocketIO events ───────────────────────────────────────────────────────────
@socketio.on("connect")
def on_connect():
if optimizer:
emit("status_sync", optimizer.get_status())
# ── Launch ────────────────────────────────────────────────────────────────────
def open_browser():
time.sleep(1.5)
webbrowser.open("http://localhost:5000")
if __name__ == "__main__":
print("=" * 60)
print(" MT5 EA Optimizer — Starting...")
print(" Opening browser at http://localhost:5000")
print("=" * 60)
threading.Thread(target=open_browser, daemon=True).start()
socketio.run(app, host="0.0.0.0", port=5000, debug=False, use_reloader=False)