diff --git a/README.md b/README.md index 45bacd1..8dca9cb 100644 --- a/README.md +++ b/README.md @@ -117,16 +117,30 @@ hit **Start**, and watch the AI think. ### Demo mode (no MT5 required) -Don't have MT5 installed? Run the offline demo that feeds synthetic backtest results -through the same AI loop and dashboard: +Don't have MT5 installed? The offline demo feeds synthetic backtest results +through the same AI loop and dashboard. **It auto-starts** — open the link +the script prints, sit back, and watch APEX think: ```bash python -m demo.run_demo ``` -This is the path to use if you're a hackathon judge — you'll see the full thinking feed, -parameter‑change panel, validation phase, and verdict screen without needing a Windows -machine with MT5. +That's it. The browser opens to the live dashboard, an optimization kicks off +automatically, and you'll see all three phases play out over ~3-4 minutes: +exploration → AI iteration with reasoning streaming live → out-of-sample + +sensitivity validation → final verdict. Every panel populates so you can see +exactly what the system does. + +If you'd rather drive it yourself (configure your own EA, dates, targets), use: + +```bash +python -m demo.run_demo --quick # boots the server, you click "New Run" +python -m demo.run_demo --loop # auto-restart between runs (unattended recording) +``` + +This is the path to use if you're a hackathon judge — you'll see the full +thinking feed, parameter-change panel, validation phase, and verdict screen +without needing a Windows machine with MT5. --- diff --git a/SUBMISSION.md b/SUBMISSION.md index a8c9e4c..0caf38a 100644 --- a/SUBMISSION.md +++ b/SUBMISSION.md @@ -81,8 +81,9 @@ https://github.com/tonnylegacy/MT5_Optimizer No hosted demo (the app runs locally to drive a local MT5 install). For judges: - **Static**: open `screenshots/apex_demo.gif` in the repo (6‑frame timelapse of one autonomous run) -- **Run it**: `git clone … && pip install -r requirements.txt && python -m demo.run_demo` → opens at `http://localhost:5000` with synthetic backtests, no MT5 required -- **With API key**: set `ANTHROPIC_API_KEY` env var to see live Claude reasoning stream into the Thinking Feed +- **Run it**: `git clone … && pip install -r requirements.txt && python -m demo.run_demo` → the browser opens to the dashboard, a ~3-4 minute optimization auto-starts, and every phase (exploration → AI iteration → validation → verdict) plays out without you touching anything +- **With API key**: set `ANTHROPIC_API_KEY` env var (or fill `ai.anthropic_api_key` in `config.yaml`) to see live Claude reasoning stream into the Thinking Feed +- **Faster / your-own-config**: `python -m demo.run_demo --quick` to skip the auto-run and drive the demo from `/setup` yourself --- diff --git a/demo/run_demo.py b/demo/run_demo.py index a32f29f..c34f503 100644 --- a/demo/run_demo.py +++ b/demo/run_demo.py @@ -7,16 +7,20 @@ generates synthetic backtest results instead of calling MT5. Lets judges without a Windows + MT5 install see the full live AI loop, validation, and verdict flow. +By default, the demo **auto-runs**: it opens the dashboard in your browser +and immediately starts a ~3-4 minute optimization showcasing every phase +(exploration → AI iteration → validation → verdict). No manual setup needed. + Usage: - python -m demo.run_demo - # or - python demo/run_demo.py + python -m demo.run_demo # auto-running showcase (default, ~3-4 min) + python -m demo.run_demo --quick # original fast/manual demo (you click "New Run") + python -m demo.run_demo --loop # auto-restart on completion (for unattended recording) """ from __future__ import annotations +import argparse import os import sys -import textwrap from pathlib import Path import yaml @@ -83,24 +87,159 @@ def ensure_config() -> None: print(f" [!] No config.yaml or config.example.yaml — app may fail to start") -def banner() -> None: +def banner(quick: bool = False, loop: bool = False) -> None: bar = "=" * 72 - print(textwrap.dedent(f""" - {bar} - APEX -- DEMO MODE (offline / no MT5) - {bar} - * Backtests are synthetic (deterministic from params + jitter) - * The AI loop, validation, and verdict flow are 100% real - * Set ANTHROPIC_API_KEY to see live AI reasoning + if quick: + mode_line = "APEX -- DEMO MODE (quick / manual)" + body = ( + " * Backtests are synthetic; AI reasoning is real if API key is set\n" + " * Per-run latency is short for fast UI testing\n\n" + ' Open http://localhost:5000 in your browser, hit "New Run", and\n' + " configure your own optimization." + ) + else: + suffix = " [LOOP]" if loop else "" + mode_line = f"APEX -- DEMO MODE (auto-running showcase){suffix}" + body = ( + " * Auto-starts a ~3-4 min optimization showing every phase\n" + " * AI reasoning streams live (API key loaded from env or config.yaml)\n" + " * The dashboard opens itself; just sit back or hit your recorder hotkey\n" + " * Heads up: AI API calls add ~12-15s each — actual run can stretch\n" + " to ~5 min depending on Claude latency.\n" + " * To skip the auto-start and configure your own run: --quick" + ) + print(f"\n{bar}\n{mode_line.center(72)}\n{bar}\n{body}\n{bar}\n") - Open http://localhost:5000 in your browser, hit "New Run", and - watch the AI think. - {bar} - """)) + +# ── Showcase auto-start config ────────────────────────────────────────────── +# Tuned for ~2.5-3 min runtime that actually shows the AI loop iterating: +# * Phase 1 (10 LHS samples × ~2.5s, no AI) ≈ 25-30s +# * Phase 2 (5 AI iterations × ~15-18s incl. analyze + suggest_next_params) +# ≈ 75-90s — targets are deliberately STIFF so the loop can't early-exit +# at iteration 0; Phase 1's best typically lands around PF≈1.9 / Calmar≈1 +# which is below these targets, so the AI gets to actually do its job. +# * Phase 3 (1 OOS + 3 sensitivity, each with AI analyze) ≈ 50-60s +# AI call latency dominates the math; the synthetic backtest delay is short. +CINEMATIC_PAYLOAD = { + "ea_name": "APEX_DEMO_EA", + "symbol": "XAUUSD", + "timeframe": "H1", + "train_start": "2022.01.01", + "train_end": "2023.12.31", + "val_start": "2024.01.01", + "val_end": "2024.06.30", + "objective": "balanced", + "budget_minutes": 15, + "autonomous_mode": True, + "autonomous_max_iterations": 5, + "target_profit_factor": 2.5, + "target_max_drawdown_pct": 5.0, + "target_min_calmar": 1.5, + "selected_params": [], +} + + +def _autostart_cinematic_run(loop: bool = False) -> None: + """ + Background thread: waits for the server to be up, then POSTs /api/start + with cinematic settings. If --loop, polls /api/status and re-triggers + when the pipeline goes idle (so an unattended recording keeps producing + fresh footage). + """ + import json + import time + import urllib.error + import urllib.request + + base = "http://127.0.0.1:5000" + + def _server_ready() -> bool: + try: + with urllib.request.urlopen(f"{base}/api/status", timeout=2) as r: + return r.status == 200 + except Exception: + return False + + def _is_running() -> bool: + try: + with urllib.request.urlopen(f"{base}/api/status", timeout=2) as r: + data = json.loads(r.read()) + state = (data.get("state") or "").lower() + return state in ("running", "starting") + except Exception: + return False + + def _post_start() -> bool: + body = json.dumps(CINEMATIC_PAYLOAD).encode("utf-8") + req = urllib.request.Request( + f"{base}/api/start", data=body, + headers={"Content-Type": "application/json"}, method="POST", + ) + try: + with urllib.request.urlopen(req, timeout=5) as r: + resp = json.loads(r.read()) + return bool(resp.get("ok")) + except Exception as e: + print(f" [cinematic] /api/start failed: {e}") + return False + + # Wait for server to come up (~10 seconds max) + for _ in range(40): + if _server_ready(): + break + time.sleep(0.25) + else: + print(" [cinematic] server didn't become ready — aborting auto-start") + return + + # Small grace period so the dashboard tab finishes connecting via SocketIO + time.sleep(2.0) + + while True: + print(" [cinematic] starting optimization run …") + if not _post_start(): + print(" [cinematic] failed to start — retrying in 10s") + time.sleep(10) + continue + + # Wait for run to complete (poll until idle for 3 consecutive checks) + idle_streak = 0 + while idle_streak < 3: + time.sleep(4) + if _is_running(): + idle_streak = 0 + else: + idle_streak += 1 + + if not loop: + print(" [cinematic] run complete — staying on verdict screen") + return + print(" [cinematic] run complete — restarting in 12s for next loop iteration") + time.sleep(12) def main() -> int: - banner() + parser = argparse.ArgumentParser( + prog="demo.run_demo", + description="APEX offline demo runner.", + ) + parser.add_argument( + "--quick", action="store_true", + help="Skip the auto-start showcase. Server boots with fast per-run latency " + "and you drive the demo manually from /setup.", + ) + parser.add_argument( + "--loop", action="store_true", + help="Auto-restart a fresh showcase run when each one completes (for unattended " + "screen recording). Ignored with --quick.", + ) + parser.add_argument( + "--per-run-seconds", type=float, default=None, + help="Override APEX_DEMO_RUN_SECONDS (default: 5.0 showcase / 1.2 --quick).", + ) + args = parser.parse_args() + + banner(quick=args.quick, loop=args.loop) print("Bootstrapping demo environment...") ensure_config() @@ -108,14 +247,35 @@ def main() -> int: # Set the demo flag — the pipeline checks this in _execute_run. os.environ["APEX_DEMO_MODE"] = "1" - # Per-run latency tunable — keep small so demo feels snappy. - os.environ.setdefault("APEX_DEMO_RUN_SECONDS", "1.2") + # Per-run latency tunable. Default (auto-running showcase) is slower so + # each phase is visible long enough to film and narrate over. + if args.per_run_seconds is not None: + os.environ["APEX_DEMO_RUN_SECONDS"] = str(args.per_run_seconds) + elif args.quick: + os.environ.setdefault("APEX_DEMO_RUN_SECONDS", "1.2") + else: + # 2.5s synthetic delay; AI call latency dominates total runtime anyway + os.environ.setdefault("APEX_DEMO_RUN_SECONDS", "2.5") + # Skip per-run AI analysis during Phase 1 exploration so the demo + # finishes in ~3-4 min. Phase 2's autonomous AI loop (the headline + # feature) still streams reasoning live. + os.environ.setdefault("APEX_DEMO_SKIP_PHASE1_AI", "1") - if "ANTHROPIC_API_KEY" not in os.environ: - print(" [!] ANTHROPIC_API_KEY not set — AI reasoning will be skipped (synthetic metrics still flow).") + # AI reasoner pulls from env var OR config.yaml ai.anthropic_api_key + cfg_has_key = False + try: + cfg = yaml.safe_load(CONFIG.read_text()) if CONFIG.exists() else {} + cfg_has_key = bool(((cfg or {}).get("ai") or {}).get("anthropic_api_key", "").strip()) + except Exception: + pass + if not (os.environ.get("ANTHROPIC_API_KEY", "").strip() or cfg_has_key): + print(" [!] No API key in env (ANTHROPIC_API_KEY) or config.yaml — AI reasoning will be skipped.") + else: + src = "config.yaml" if cfg_has_key else "env var" + print(f" [ok] API key found in {src} — AI reasoning enabled.") print() - print("Launching APEX server at http://localhost:5000 ...") + print(f"Launching APEX server at http://localhost:5000 (per-run: {os.environ['APEX_DEMO_RUN_SECONDS']}s) ...") sys.path.insert(0, str(ROOT)) # Import after env vars are set so the pipeline picks them up. import threading @@ -126,11 +286,22 @@ def main() -> int: import time as _t _t.sleep(1.5) try: - webbrowser.open("http://localhost:5000") + # In auto-running mode jump straight to the dashboard so the user sees + # the live run unfold; in --quick mode land on the landing page. + url = "http://localhost:5000" if args.quick else "http://localhost:5000/dashboard" + webbrowser.open(url) except Exception: pass threading.Thread(target=_open_browser, daemon=True).start() + + if not args.quick: + threading.Thread( + target=_autostart_cinematic_run, + kwargs={"loop": args.loop}, + daemon=True, + ).start() + socketio.run( flask_app, host="0.0.0.0", port=5000, debug=False, use_reloader=False, allow_unsafe_werkzeug=True, diff --git a/optimizer/pipeline.py b/optimizer/pipeline.py index f8d5d7e..f645a7b 100644 --- a/optimizer/pipeline.py +++ b/optimizer/pipeline.py @@ -898,11 +898,17 @@ class OptimizationPipeline: delay = float(os.environ.get("APEX_DEMO_RUN_SECONDS", "1.5")) time.sleep(max(0.05, delay)) - # Run AI reasoning if enabled — same flow as live mode - try: - self._reason_about_run(run_id, metrics, [], params) - except Exception: - pass + # Run AI reasoning if enabled — same flow as live mode. + # In showcase mode (APEX_DEMO_SKIP_PHASE1_AI=1) we skip Phase 1's + # per-run AI analysis to keep total runtime to ~3-4 min. The AI loop + # in Phase 2 — where reasoning actually drives parameter changes — is + # untouched, so the headline feature is still visible. + skip_p1_ai = os.environ.get("APEX_DEMO_SKIP_PHASE1_AI", "").strip() in ("1", "true", "yes") + if not (skip_p1_ai and phase.startswith("phase1")): + try: + self._reason_about_run(run_id, metrics, [], params) + except Exception: + pass return ranker.make_result(run_id, params, phase, metrics) diff --git a/ui/static/img/apex-logo-wide.png b/ui/static/img/apex-logo-wide.png new file mode 100644 index 0000000..c9a452f Binary files /dev/null and b/ui/static/img/apex-logo-wide.png differ diff --git a/ui/static/img/apex-logo.png b/ui/static/img/apex-logo.png new file mode 100644 index 0000000..9ef3749 Binary files /dev/null and b/ui/static/img/apex-logo.png differ diff --git a/ui/static/img/apex-mark.png b/ui/static/img/apex-mark.png new file mode 100644 index 0000000..84aefe8 Binary files /dev/null and b/ui/static/img/apex-mark.png differ diff --git a/ui/static/img/favicon.png b/ui/static/img/favicon.png new file mode 100644 index 0000000..2d6ff6c Binary files /dev/null and b/ui/static/img/favicon.png differ diff --git a/ui/templates/dashboard.html b/ui/templates/dashboard.html index 277867c..af713a8 100644 --- a/ui/templates/dashboard.html +++ b/ui/templates/dashboard.html @@ -4,6 +4,7 @@