feat: open-source release — AI-driven autonomous optimization with live visibility
Major upgrade making the AI loop visible and the project ready for public release. UI / UX - Live AI Thinking Feed: streams reasoning, decisions, and outcomes per iteration - Parameter Changes panel: prev → new + reason for every AI-driven edit - Validation Activity panel: out-of-sample + sensitivity runs with live metrics - Early Termination banner: surfaces why optimization stopped (targets met, no profit, budget, stuck, user stop) - 3-phase tracker renamed Exploration / Iteration / Validation with live N/total - Best Result modal exposes Evolution Path showing how the AI arrived at the winner - Run-detail modal accessible from every recent run row - Setup form validation (dates, walk-forward order, params selection, AI targets) - Pause button removed; misleading sidebar nav consolidated to Dashboard / New Run / Reports / Source Backend - AIGuidedLoop streams ai_thinking, param_changes, ai_targets_met, ai_stuck - Pipeline emits validation_start / validation_run_start / validation_run_complete / validation_done - Pipeline emits early_termination on every early-stop path - /api/best_result returns best run + full evolution chain - /api/run/<id> + /api/runs sorted by ts - AIReasoner falls back to ANTHROPIC_API_KEY env var when config is a placeholder - Demo mode (APEX_DEMO_MODE=1) generates deterministic synthetic backtests so judges can run end-to-end without MT5 Open-source readiness - README.md with pitch, demo flow, architecture diagram, quickstart, event reference - LICENSE (MIT) - config.example.yaml template (config.yaml now git-ignored) - requirements.txt: added anthropic / requests / psutil / beautifulsoup4, capped majors - .gitignore: secrets, *.set, scratch screenshots, ea_registry.yaml - demo/run_demo.py: one-command offline demo runner - 10 polished screenshots for README + judge review
This commit is contained in:
@@ -324,3 +324,110 @@ body {
|
||||
from { opacity: 0; transform: translateY(8px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* ── AI Insight Panel ─────────────────────────────────────────────────────── */
|
||||
.ai-insight-card {
|
||||
border: 1px solid rgba(0, 212, 170, 0.25);
|
||||
background: linear-gradient(135deg, rgba(0,212,170,0.04) 0%, rgba(124,109,250,0.04) 100%);
|
||||
animation: slideIn 0.4s ease both;
|
||||
}
|
||||
.ai-confidence-badge {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
padding: 0.2rem 0.7rem;
|
||||
border-radius: 20px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
.ai-confidence-badge.high { background: rgba(0,212,170,0.15); color: #00d4aa; }
|
||||
.ai-confidence-badge.medium { background: rgba(251,191,36,0.15); color: #fbbf24; }
|
||||
.ai-confidence-badge.low { background: rgba(100,116,139,0.15); color: #94a3b8; }
|
||||
.ai-confidence-badge.analyzing { background: rgba(124,109,250,0.15); color: #7c6dfa; }
|
||||
|
||||
.ai-headline {
|
||||
font-size: 1.05rem;
|
||||
font-weight: 700;
|
||||
color: #e2e8f0;
|
||||
margin: 0.75rem 0 0.5rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
.ai-diagnosis {
|
||||
font-size: 0.875rem;
|
||||
color: #94a3b8;
|
||||
line-height: 1.7;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
.ai-sections {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
.ai-section {
|
||||
background: rgba(255,255,255,0.03);
|
||||
border: 1px solid rgba(255,255,255,0.06);
|
||||
border-radius: 10px;
|
||||
padding: 0.85rem 1rem;
|
||||
}
|
||||
.ai-section-label {
|
||||
font-size: 0.72rem;
|
||||
font-weight: 700;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.08em;
|
||||
color: #64748b;
|
||||
margin-bottom: 0.6rem;
|
||||
}
|
||||
.ai-list {
|
||||
list-style: none;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.35rem;
|
||||
}
|
||||
.ai-list li {
|
||||
font-size: 0.83rem;
|
||||
color: #cbd5e1;
|
||||
padding-left: 1.1rem;
|
||||
position: relative;
|
||||
line-height: 1.5;
|
||||
}
|
||||
.ai-list li::before {
|
||||
content: "→";
|
||||
position: absolute;
|
||||
left: 0;
|
||||
color: #00d4aa;
|
||||
font-size: 0.75rem;
|
||||
}
|
||||
.ai-list-warn li::before { color: #fbbf24; content: "⚠"; }
|
||||
|
||||
.ai-suggestions {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
.ai-suggestion-row {
|
||||
display: grid;
|
||||
grid-template-columns: 180px 80px 80px 1fr;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
font-size: 0.82rem;
|
||||
padding: 0.4rem 0.5rem;
|
||||
border-radius: 6px;
|
||||
background: rgba(255,255,255,0.02);
|
||||
}
|
||||
.ai-param-name {
|
||||
font-family: 'JetBrains Mono', monospace;
|
||||
font-size: 0.78rem;
|
||||
color: #7c6dfa;
|
||||
}
|
||||
.ai-param-from { color: #64748b; text-align: center; }
|
||||
.ai-param-to {
|
||||
color: #00d4aa;
|
||||
font-weight: 600;
|
||||
text-align: center;
|
||||
}
|
||||
.ai-param-reason {
|
||||
color: #94a3b8;
|
||||
font-size: 0.78rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
+1694
-407
File diff suppressed because it is too large
Load Diff
+2328
-241
File diff suppressed because it is too large
Load Diff
+425
-236
@@ -1,267 +1,456 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>MT5 Smart Optimizer</title>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>APEX — AI-Powered EA Optimizer</title>
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
:root {
|
||||
--bg: #080d1a;
|
||||
--accent: #4f46e5;
|
||||
--accent2: #7c6dfa;
|
||||
--teal: #00d4aa;
|
||||
--text: #e2e8f0;
|
||||
--muted: #64748b;
|
||||
}
|
||||
|
||||
:root {
|
||||
--bg: #07090f;
|
||||
--bg2: #0d1117;
|
||||
--bg3: #141b27;
|
||||
--border: rgba(255,255,255,0.07);
|
||||
--accent: #00d4aa;
|
||||
--accent2: #7c6dfa;
|
||||
--warn: #f59e0b;
|
||||
--text: #e2e8f0;
|
||||
--muted: #64748b;
|
||||
--glow: 0 0 40px rgba(0,212,170,0.15);
|
||||
}
|
||||
* { box-sizing: border-box; margin: 0; padding: 0; }
|
||||
|
||||
html, body { height: 100%; }
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
min-height: 100vh;
|
||||
overflow-x: hidden;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Inter', sans-serif;
|
||||
background: var(--bg);
|
||||
color: var(--text);
|
||||
display: grid;
|
||||
place-items: center;
|
||||
min-height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
/* Grid overlay */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed; inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(0,212,170,0.03) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(0,212,170,0.03) 1px, transparent 1px);
|
||||
background-size: 50px 50px;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* Animated background grid */
|
||||
body::before {
|
||||
content: '';
|
||||
position: fixed; inset: 0;
|
||||
background-image:
|
||||
linear-gradient(rgba(0,212,170,0.03) 1px, transparent 1px),
|
||||
linear-gradient(90deg, rgba(0,212,170,0.03) 1px, transparent 1px);
|
||||
background-size: 60px 60px;
|
||||
animation: gridScroll 20s linear infinite;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
@keyframes gridScroll { to { background-position: 60px 60px; } }
|
||||
/* Glow orb */
|
||||
body::after {
|
||||
content: '';
|
||||
position: fixed;
|
||||
top: 20%;
|
||||
left: 10%;
|
||||
width: 600px;
|
||||
height: 600px;
|
||||
background: radial-gradient(circle, rgba(79,70,229,0.08) 0%, transparent 70%);
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
/* Glowing orbs */
|
||||
.orb {
|
||||
position: fixed;
|
||||
border-radius: 50%;
|
||||
filter: blur(120px);
|
||||
opacity: 0.12;
|
||||
pointer-events: none;
|
||||
z-index: 0;
|
||||
animation: orb-float 8s ease-in-out infinite;
|
||||
}
|
||||
.orb-1 { width: 600px; height: 600px; background: var(--accent); top: -200px; left: -100px; animation-delay: 0s; }
|
||||
.orb-2 { width: 500px; height: 500px; background: var(--accent2); bottom: -200px; right: -100px; animation-delay: 4s; }
|
||||
@keyframes orb-float { 0%,100%{transform:translateY(0)} 50%{transform:translateY(-30px)} }
|
||||
.page {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
max-width: 1200px;
|
||||
margin: 0 auto;
|
||||
padding: 2rem;
|
||||
}
|
||||
|
||||
.landing {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
text-align: center;
|
||||
max-width: 700px;
|
||||
padding: 2rem;
|
||||
animation: fadeUp 0.8s ease both;
|
||||
}
|
||||
@keyframes fadeUp { from{opacity:0;transform:translateY(30px)} to{opacity:1;transform:translateY(0)} }
|
||||
/* Navbar */
|
||||
.navbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1.5rem 0;
|
||||
margin-bottom: 4rem;
|
||||
}
|
||||
|
||||
.logo {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 2.5rem;
|
||||
}
|
||||
.logo-icon {
|
||||
width: 52px; height: 52px;
|
||||
background: linear-gradient(135deg, var(--accent), var(--accent2));
|
||||
border-radius: 14px;
|
||||
display: grid; place-items: center;
|
||||
font-size: 1.6rem;
|
||||
box-shadow: var(--glow);
|
||||
}
|
||||
.logo-name {
|
||||
font-size: 1.4rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: -0.02em;
|
||||
color: white;
|
||||
}
|
||||
.logo-sub { font-size: 0.75rem; color: var(--muted); margin-top: 2px; }
|
||||
.nav-logo {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: clamp(2.2rem, 5vw, 3.5rem);
|
||||
font-weight: 900;
|
||||
letter-spacing: -0.04em;
|
||||
line-height: 1.1;
|
||||
margin-bottom: 1.25rem;
|
||||
background: linear-gradient(135deg, #ffffff 0%, var(--accent) 60%, var(--accent2) 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
}
|
||||
.nav-logo-text {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.05em;
|
||||
background: linear-gradient(135deg, #fff 0%, #94a3b8 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.subtitle {
|
||||
font-size: 1.1rem;
|
||||
color: var(--muted);
|
||||
line-height: 1.7;
|
||||
margin-bottom: 3rem;
|
||||
font-weight: 400;
|
||||
}
|
||||
.status-pill {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.4rem 1rem;
|
||||
background: rgba(255,255,255,0.05);
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
border-radius: 999px;
|
||||
font-size: 0.8rem;
|
||||
color: var(--muted);
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
justify-content: center;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.status-dot {
|
||||
width: 8px; height: 8px;
|
||||
border-radius: 50%;
|
||||
background: var(--muted);
|
||||
flex-shrink: 0;
|
||||
}
|
||||
.status-dot.running {
|
||||
background: var(--teal);
|
||||
box-shadow: 0 0 8px var(--teal);
|
||||
animation: pulse 1.5s infinite;
|
||||
}
|
||||
.status-dot.idle { background: #475569; }
|
||||
@keyframes pulse { 0%,100%{opacity:1} 50%{opacity:0.5} }
|
||||
|
||||
.btn {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.6rem;
|
||||
padding: 0.9rem 2rem;
|
||||
border-radius: 12px;
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
text-decoration: none;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
font-family: inherit;
|
||||
}
|
||||
/* Hero */
|
||||
.hero {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 4rem;
|
||||
align-items: center;
|
||||
min-height: 70vh;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background: linear-gradient(135deg, var(--accent), #00b890);
|
||||
color: #051a14;
|
||||
box-shadow: 0 4px 24px rgba(0,212,170,0.35);
|
||||
}
|
||||
.btn-primary:hover {
|
||||
transform: translateY(-2px);
|
||||
box-shadow: 0 8px 36px rgba(0,212,170,0.5);
|
||||
}
|
||||
.hero-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.35rem 0.9rem;
|
||||
background: rgba(79,70,229,0.15);
|
||||
border: 1px solid rgba(79,70,229,0.3);
|
||||
border-radius: 999px;
|
||||
font-size: 0.72rem;
|
||||
font-weight: 600;
|
||||
color: var(--accent2);
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.btn-secondary {
|
||||
background: var(--bg3);
|
||||
color: var(--text);
|
||||
border: 1px solid var(--border);
|
||||
}
|
||||
.btn-secondary:hover {
|
||||
background: #1e2a3f;
|
||||
border-color: rgba(255,255,255,0.15);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
.hero-logo-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1.25rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.stats {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
justify-content: center;
|
||||
margin-top: 3.5rem;
|
||||
padding-top: 2.5rem;
|
||||
border-top: 1px solid var(--border);
|
||||
}
|
||||
.stat { text-align: center; }
|
||||
.stat-val {
|
||||
font-size: 1.6rem;
|
||||
font-weight: 800;
|
||||
color: var(--accent);
|
||||
letter-spacing: -0.03em;
|
||||
}
|
||||
.stat-label { font-size: 0.75rem; color: var(--muted); margin-top: 0.2rem; text-transform: uppercase; letter-spacing: 0.08em; }
|
||||
.hero-title {
|
||||
font-size: 5rem;
|
||||
font-weight: 900;
|
||||
letter-spacing: -0.04em;
|
||||
line-height: 1;
|
||||
background: linear-gradient(135deg, #ffffff 0%, #c7d2fe 50%, #00d4aa 100%);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
}
|
||||
|
||||
.status-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.4rem;
|
||||
padding: 0.3rem 0.8rem;
|
||||
background: rgba(0,212,170,0.1);
|
||||
border: 1px solid rgba(0,212,170,0.2);
|
||||
border-radius: 999px;
|
||||
font-size: 0.78rem;
|
||||
color: var(--accent);
|
||||
margin-bottom: 2rem;
|
||||
}
|
||||
.pulse {
|
||||
width: 7px; height: 7px;
|
||||
background: var(--accent);
|
||||
border-radius: 50%;
|
||||
animation: pulse 2s ease infinite;
|
||||
}
|
||||
@keyframes pulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:0.5;transform:scale(0.8)} }
|
||||
</style>
|
||||
.hero-subtitle-row {
|
||||
font-size: 0.85rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.2em;
|
||||
text-transform: uppercase;
|
||||
color: var(--muted);
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.hero-tagline {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 300;
|
||||
color: #94a3b8;
|
||||
margin-bottom: 2.5rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.hero-tagline strong {
|
||||
color: var(--teal);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.hero-ctas {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 3rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
display: inline-flex; align-items: center; gap: 0.5rem;
|
||||
padding: 0.9rem 2rem;
|
||||
background: linear-gradient(135deg, var(--accent), var(--accent2));
|
||||
color: white; border: none; border-radius: 10px;
|
||||
font-size: 0.95rem; font-weight: 700; cursor: pointer;
|
||||
text-decoration: none; font-family: inherit;
|
||||
box-shadow: 0 4px 24px rgba(79,70,229,0.4);
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 8px 36px rgba(79,70,229,0.5); }
|
||||
|
||||
.btn-secondary {
|
||||
display: inline-flex; align-items: center; gap: 0.5rem;
|
||||
padding: 0.9rem 1.75rem;
|
||||
background: rgba(255,255,255,0.06);
|
||||
border: 1px solid rgba(255,255,255,0.12);
|
||||
color: var(--text); border-radius: 10px;
|
||||
font-size: 0.95rem; font-weight: 600;
|
||||
text-decoration: none; font-family: inherit;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
.btn-secondary:hover { background: rgba(255,255,255,0.1); }
|
||||
|
||||
.hero-stats {
|
||||
display: flex;
|
||||
gap: 2rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.hero-stat { display: flex; flex-direction: column; }
|
||||
.hero-stat-val {
|
||||
font-size: 1.5rem; font-weight: 800;
|
||||
background: linear-gradient(135deg, var(--teal), var(--accent2));
|
||||
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
||||
}
|
||||
.hero-stat-lbl {
|
||||
font-size: 0.72rem; color: var(--muted); margin-top: 0.1rem;
|
||||
}
|
||||
|
||||
/* Features panel */
|
||||
.features-panel {
|
||||
background: rgba(15,22,41,0.8);
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
border-radius: 20px;
|
||||
padding: 2rem;
|
||||
backdrop-filter: blur(10px);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.features-panel::before {
|
||||
content: 'APEX';
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
font-size: 4rem;
|
||||
font-weight: 900;
|
||||
letter-spacing: 0.1em;
|
||||
background: linear-gradient(135deg, #4f46e5, #00d4aa);
|
||||
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
||||
opacity: 0.15;
|
||||
pointer-events: none;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.features-panel-title {
|
||||
font-size: 0.75rem; font-weight: 700;
|
||||
text-transform: uppercase; letter-spacing: 0.15em;
|
||||
color: var(--muted); margin-bottom: 1.5rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.features-list { display: flex; flex-direction: column; gap: 1.25rem; }
|
||||
|
||||
.feature-item { display: flex; align-items: flex-start; gap: 1rem; }
|
||||
|
||||
.feature-icon-wrap {
|
||||
width: 40px; height: 40px;
|
||||
border-radius: 10px;
|
||||
display: flex; align-items: center; justify-content: center;
|
||||
flex-shrink: 0;
|
||||
font-size: 1.1rem;
|
||||
}
|
||||
.feature-icon-wrap.purple { background: rgba(79,70,229,0.2); border: 1px solid rgba(79,70,229,0.3); }
|
||||
.feature-icon-wrap.teal { background: rgba(0,212,170,0.15); border: 1px solid rgba(0,212,170,0.3); }
|
||||
.feature-icon-wrap.blue { background: rgba(59,130,246,0.15); border: 1px solid rgba(59,130,246,0.3); }
|
||||
.feature-icon-wrap.green { background: rgba(16,185,129,0.15); border: 1px solid rgba(16,185,129,0.3); }
|
||||
|
||||
.feature-name {
|
||||
font-size: 0.72rem; font-weight: 700;
|
||||
letter-spacing: 0.1em; text-transform: uppercase;
|
||||
margin-bottom: 0.2rem;
|
||||
}
|
||||
.feature-name.purple { color: var(--accent2); }
|
||||
.feature-name.teal { color: var(--teal); }
|
||||
.feature-name.blue { color: #60a5fa; }
|
||||
.feature-name.green { color: #34d399; }
|
||||
|
||||
.feature-desc { font-size: 0.8rem; color: #94a3b8; line-height: 1.5; }
|
||||
|
||||
/* Footer */
|
||||
.footer {
|
||||
margin-top: 4rem;
|
||||
padding-top: 2rem;
|
||||
border-top: 1px solid rgba(255,255,255,0.06);
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
color: var(--muted);
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
@media (max-width: 768px) {
|
||||
.hero { grid-template-columns: 1fr; min-height: auto; }
|
||||
.hero-title { font-size: 3rem; }
|
||||
.features-panel { display: none; }
|
||||
.hero-stats { gap: 1.25rem; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="orb orb-1"></div>
|
||||
<div class="orb orb-2"></div>
|
||||
<div class="page">
|
||||
|
||||
<div class="landing">
|
||||
<!-- Navbar -->
|
||||
<nav class="navbar">
|
||||
<div class="nav-logo">
|
||||
<svg width="32" height="32" viewBox="0 0 36 36" fill="none">
|
||||
<polygon points="18,2 34,32 2,32" fill="url(#logoGradL)" opacity="0.15"/>
|
||||
<polygon points="18,8 30,30 6,30" fill="url(#logoGradL)" opacity="0.3"/>
|
||||
<path d="M18 6L22 14H14L18 6Z" fill="url(#logoGradL)"/>
|
||||
<rect x="11" y="22" width="3" height="8" rx="1" fill="#00d4aa"/>
|
||||
<rect x="16" y="18" width="3" height="12" rx="1" fill="#00d4aa" opacity="0.8"/>
|
||||
<rect x="21" y="14" width="3" height="16" rx="1" fill="#00d4aa" opacity="0.6"/>
|
||||
<defs>
|
||||
<linearGradient id="logoGradL" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#4f46e5"/>
|
||||
<stop offset="100%" stop-color="#00d4aa"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
<span class="nav-logo-text">APEX</span>
|
||||
</div>
|
||||
<div class="status-pill" id="status-pill">
|
||||
<div class="status-dot idle" id="status-dot"></div>
|
||||
<span id="status-text">Ready</span>
|
||||
</div>
|
||||
</nav>
|
||||
|
||||
<div class="logo">
|
||||
<div class="logo-icon">⚡</div>
|
||||
<div>
|
||||
<div class="logo-name">MT5 Smart Optimizer</div>
|
||||
<div class="logo-sub">Autonomous EA Optimization Engine</div>
|
||||
<!-- Hero -->
|
||||
<div class="hero">
|
||||
<div class="hero-left">
|
||||
<div class="hero-badge">✦ Hackathon Edition 2026</div>
|
||||
|
||||
<div class="hero-logo-row">
|
||||
<svg width="72" height="72" viewBox="0 0 72 72" fill="none">
|
||||
<polygon points="36,4 68,64 4,64" fill="url(#heroGrad)" opacity="0.12"/>
|
||||
<polygon points="36,16 60,60 12,60" fill="url(#heroGrad)" opacity="0.25"/>
|
||||
<path d="M36 12L44 28H28L36 12Z" fill="url(#heroGrad)"/>
|
||||
<path d="M33 20L36 13L39 20" stroke="#00d4aa" stroke-width="1.5" fill="none" stroke-linecap="round"/>
|
||||
<rect x="22" y="44" width="6" height="16" rx="2" fill="#00d4aa" opacity="0.9"/>
|
||||
<rect x="33" y="36" width="6" height="24" rx="2" fill="#00d4aa" opacity="0.7"/>
|
||||
<rect x="44" y="28" width="6" height="32" rx="2" fill="#00d4aa" opacity="0.5"/>
|
||||
<defs>
|
||||
<linearGradient id="heroGrad" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0%" stop-color="#4f46e5"/>
|
||||
<stop offset="100%" stop-color="#7c6dfa"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
</div>
|
||||
|
||||
<div class="hero-title">APEX</div>
|
||||
<div class="hero-subtitle-row">AI Powered EA Optimizer</div>
|
||||
|
||||
<div class="hero-tagline">
|
||||
Optimize. <strong>Understand.</strong> Evolve.<br>
|
||||
<span style="font-size:1rem">The first optimizer that tells you <em>why</em> — not just what.</span>
|
||||
</div>
|
||||
|
||||
<div class="hero-ctas">
|
||||
<a href="/setup" class="btn-primary">⚡ Start Optimizing →</a>
|
||||
<a href="/dashboard" class="btn-secondary">📊 Live Dashboard</a>
|
||||
<a href="/reports" class="btn-secondary">📋 Reports</a>
|
||||
</div>
|
||||
|
||||
<div class="hero-stats">
|
||||
<div class="hero-stat">
|
||||
<div class="hero-stat-val">3</div>
|
||||
<div class="hero-stat-lbl">Phase Pipeline</div>
|
||||
</div>
|
||||
<div class="hero-stat">
|
||||
<div class="hero-stat-val">AI</div>
|
||||
<div class="hero-stat-lbl">Claude-Powered</div>
|
||||
</div>
|
||||
<div class="hero-stat">
|
||||
<div class="hero-stat-val">IS/OOS</div>
|
||||
<div class="hero-stat-lbl">Walk-Forward Validated</div>
|
||||
</div>
|
||||
<div class="hero-stat">
|
||||
<div class="hero-stat-val">Any EA</div>
|
||||
<div class="hero-stat-lbl">Works with any .set file</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="hero-right">
|
||||
<div class="features-panel">
|
||||
<div class="features-panel-title">What makes APEX different</div>
|
||||
<div class="features-list">
|
||||
|
||||
<div class="feature-item">
|
||||
<div class="feature-icon-wrap purple">🧠</div>
|
||||
<div>
|
||||
<div class="feature-name purple">AI Reasoning</div>
|
||||
<div class="feature-desc">Analyzes results, detects patterns and explains performance in plain language using Claude AI.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-item">
|
||||
<div class="feature-icon-wrap teal">🎯</div>
|
||||
<div>
|
||||
<div class="feature-name teal">Smart Optimization</div>
|
||||
<div class="feature-desc">Finds the best parameters using 3-phase search: broad discovery → refinement → out-of-sample validation.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-item">
|
||||
<div class="feature-icon-wrap blue" style="font-size:0.85rem;font-weight:700;font-family:monospace;color:#60a5fa"></></div>
|
||||
<div>
|
||||
<div class="feature-name blue">Strategy Evolution</div>
|
||||
<div class="feature-desc">Suggests improvements and helps evolve your trading systems based on behavioral pattern analysis.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="feature-item">
|
||||
<div class="feature-icon-wrap green">🛡</div>
|
||||
<div>
|
||||
<div class="feature-name green">Robust & Reliable</div>
|
||||
<div class="feature-desc">Built-in validation (IS/OOS/WFV) and sensitivity testing to ensure real market reliability.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="status-pill" id="status-pill">
|
||||
<div class="pulse"></div>
|
||||
<span id="status-text">Ready</span>
|
||||
</div>
|
||||
|
||||
<h1>Find the Best Settings<br>for Any MT5 EA</h1>
|
||||
|
||||
<p class="subtitle">
|
||||
The Smart Optimizer tests dozens of configurations across your full parameter space,
|
||||
validates the winner on unseen data, and delivers a ready-to-use .set file — in under an hour.
|
||||
</p>
|
||||
|
||||
<div class="actions">
|
||||
<a href="/setup" class="btn btn-primary">
|
||||
⚡ New Optimization
|
||||
</a>
|
||||
<a href="/dashboard" class="btn btn-secondary" id="dashboard-btn">
|
||||
📊 Live Dashboard
|
||||
</a>
|
||||
<a href="/reports" class="btn btn-secondary">
|
||||
📁 View Reports
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div class="stats">
|
||||
<div class="stat">
|
||||
<div class="stat-val">3</div>
|
||||
<div class="stat-label">Phases</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-val" id="stat-runs">20–50</div>
|
||||
<div class="stat-label">Config Tests</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-val"><1hr</div>
|
||||
<div class="stat-label">Typical Time</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-val">OOS</div>
|
||||
<div class="stat-label">Validated</div>
|
||||
</div>
|
||||
</div>
|
||||
<footer class="footer">
|
||||
<span>APEX — AI-Powered EA Optimizer</span>
|
||||
<span>Powered by Claude AI + MetaTrader 5</span>
|
||||
</footer>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Check if optimizer is running
|
||||
fetch('/api/status').then(r => r.json()).then(s => {
|
||||
if (s.state === 'running') {
|
||||
document.getElementById('status-text').textContent = `Running — ${s.phase} (${s.run_count}/${s.total_runs})`;
|
||||
document.getElementById('dashboard-btn').style.background = 'linear-gradient(135deg,#7c6dfa,#5b4fe8)';
|
||||
document.getElementById('dashboard-btn').style.color = 'white';
|
||||
}
|
||||
}).catch(() => {});
|
||||
// Fetch optimizer status and update the live pill
|
||||
fetch('/api/status')
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(s) {
|
||||
var dot = document.getElementById('status-dot');
|
||||
var txt = document.getElementById('status-text');
|
||||
if (s.state === 'running') {
|
||||
dot.className = 'status-dot running';
|
||||
txt.textContent = 'Running: ' + (s.phase || '') + ' (' + (s.run_count || 0) + ' runs)';
|
||||
} else if (s.verdict) {
|
||||
dot.className = 'status-dot';
|
||||
dot.style.background = '#10b981';
|
||||
txt.textContent = 'Last: ' + s.verdict;
|
||||
}
|
||||
})
|
||||
.catch(function() {});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
+405
-8
@@ -370,6 +370,161 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Add New EA -->
|
||||
<div style="margin-top:0.5rem;display:flex;gap:0.75rem;align-items:center;flex-wrap:wrap">
|
||||
<button type="button" onclick="showAddEAModal()"
|
||||
style="background:none;border:1px dashed rgba(79,70,229,0.4);color:#7c6dfa;padding:0.4rem 1rem;border-radius:8px;font-size:0.8rem;cursor:pointer;font-family:inherit;transition:all 0.2s"
|
||||
onmouseover="this.style.borderColor='#7c6dfa'" onmouseout="this.style.borderColor='rgba(79,70,229,0.4)'">
|
||||
+ Register New EA
|
||||
</button>
|
||||
<button type="button" id="scan-btn" onclick="scanForEAs()"
|
||||
style="background:none;border:1px dashed rgba(0,212,170,0.35);color:#00d4aa;padding:0.4rem 1rem;border-radius:8px;font-size:0.8rem;cursor:pointer;font-family:inherit;transition:all 0.2s;display:flex;align-items:center;gap:0.4rem"
|
||||
onmouseover="this.style.borderColor='#00d4aa'" onmouseout="this.style.borderColor='rgba(0,212,170,0.35)'">
|
||||
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><circle cx="11" cy="11" r="8"/><path d="M21 21l-4.35-4.35"/></svg>
|
||||
Scan for EAs
|
||||
</button>
|
||||
<span id="scan-status" style="font-size:0.78rem;color:#64748b"></span>
|
||||
</div>
|
||||
|
||||
<!-- Add EA Modal -->
|
||||
<div id="add-ea-modal" style="display:none;position:fixed;inset:0;background:rgba(0,0,0,0.75);z-index:50;align-items:center;justify-content:center;backdrop-filter:blur(6px)">
|
||||
<div style="background:#0d1117;border:1px solid rgba(255,255,255,0.1);border-radius:16px;padding:2rem;width:540px;max-width:95vw;max-height:90vh;overflow-y:auto">
|
||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1.5rem">
|
||||
<div>
|
||||
<h3 style="font-size:1.05rem;font-weight:700;color:#e2e8f0">Register New EA</h3>
|
||||
<p style="font-size:0.78rem;color:#64748b;margin-top:0.2rem">Pick a detected EA or enter paths manually</p>
|
||||
</div>
|
||||
<button onclick="hideAddEAModal()" style="background:none;border:none;color:#64748b;cursor:pointer;font-size:1.4rem;line-height:1">✕</button>
|
||||
</div>
|
||||
|
||||
<!-- Detected EAs picker (shown after scan) -->
|
||||
<div id="detected-section" style="display:none;margin-bottom:1.25rem">
|
||||
<div style="font-size:0.78rem;font-weight:600;text-transform:uppercase;letter-spacing:0.08em;color:#00d4aa;margin-bottom:0.6rem">
|
||||
Detected EAs — click to auto-fill
|
||||
</div>
|
||||
<div id="detected-list" style="display:flex;flex-direction:column;gap:0.4rem;max-height:180px;overflow-y:auto;padding-right:4px"></div>
|
||||
<div style="margin:1rem 0;border-top:1px solid rgba(255,255,255,0.06)"></div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;flex-direction:column;gap:1rem">
|
||||
<div class="field">
|
||||
<label>EA Display Name</label>
|
||||
<input type="text" id="new-ea-name" placeholder="e.g. MyEA_v2">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>EA File Name (without .ex5)</label>
|
||||
<input type="text" id="new-ea-file" placeholder="e.g. MyEA_v2">
|
||||
</div>
|
||||
<div class="field">
|
||||
<label style="display:flex;align-items:center;justify-content:space-between">
|
||||
<span>.set Template Path</span>
|
||||
<span id="set-match-badge" style="display:none;font-size:0.7rem;background:rgba(0,212,170,0.15);color:#00d4aa;padding:2px 8px;border-radius:999px;font-weight:600">✓ auto-matched</span>
|
||||
</label>
|
||||
<input type="text" id="new-ea-set" placeholder="C:\MT5 Set files\MyEA.set">
|
||||
<!-- Set file picker (shown after scan) -->
|
||||
<select id="set-picker" style="display:none;margin-top:0.4rem;font-size:0.82rem" onchange="onSetPick()">
|
||||
<option value="">— pick from detected .set files —</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="grid-2">
|
||||
<div class="field">
|
||||
<label>Symbol</label>
|
||||
<select id="new-ea-symbol">
|
||||
<option value="XAUUSD">XAUUSD (Gold)</option>
|
||||
<option value="EURUSD">EURUSD</option>
|
||||
<option value="GBPUSD">GBPUSD</option>
|
||||
<option value="USDJPY">USDJPY</option>
|
||||
<option value="USDCAD">USDCAD</option>
|
||||
<option value="AUDUSD">AUDUSD</option>
|
||||
<option value="BTCUSD">BTCUSD</option>
|
||||
<option value="NASDAQ">NASDAQ</option>
|
||||
<option value="US30">US30</option>
|
||||
<option value="DE40">DE40</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="field">
|
||||
<label>Timeframe</label>
|
||||
<select id="new-ea-tf">
|
||||
<option value="M5">M5</option>
|
||||
<option value="M15">M15</option>
|
||||
<option value="M30">M30</option>
|
||||
<option value="H1" selected>H1</option>
|
||||
<option value="H4">H4</option>
|
||||
<option value="D1">D1</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex;gap:0.75rem;margin-top:1.5rem;justify-content:flex-end">
|
||||
<button type="button" onclick="hideAddEAModal()"
|
||||
style="padding:0.6rem 1.25rem;background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.1);color:#e2e8f0;border-radius:8px;cursor:pointer;font-family:inherit">
|
||||
Cancel
|
||||
</button>
|
||||
<button type="button" onclick="registerNewEA()"
|
||||
style="padding:0.6rem 1.5rem;background:linear-gradient(135deg,#4f46e5,#7c6dfa);color:white;border:none;border-radius:8px;font-weight:700;cursor:pointer;font-family:inherit">
|
||||
Register EA
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Autonomous AI Loop -->
|
||||
<div style="border:1px solid rgba(0,212,170,0.25);border-radius:12px;padding:1.25rem 1.5rem;background:rgba(0,212,170,0.04);margin-top:0.5rem">
|
||||
<div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:0.75rem">
|
||||
<div>
|
||||
<div style="display:flex;align-items:center;gap:0.6rem">
|
||||
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="#00d4aa" stroke-width="2"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>
|
||||
<span style="font-size:0.88rem;font-weight:700;color:#e2e8f0">Autonomous AI Loop</span>
|
||||
<span style="font-size:0.62rem;background:rgba(0,212,170,0.18);color:#00d4aa;padding:2px 7px;border-radius:999px;font-weight:700;letter-spacing:0.05em">NEW</span>
|
||||
</div>
|
||||
<div style="font-size:0.72rem;color:#64748b;margin-top:0.25rem">
|
||||
AI analyzes results and evolves parameters each iteration until targets are met
|
||||
</div>
|
||||
</div>
|
||||
<!-- Toggle -->
|
||||
<button type="button" class="settings-toggle" id="autonomous-toggle"
|
||||
onclick="toggleAutonomous(this)"
|
||||
style="width:44px;height:24px;background:rgba(255,255,255,0.12);border-radius:999px;border:none;cursor:pointer;position:relative;transition:background 0.2s;flex-shrink:0">
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Expanded controls (hidden when off) -->
|
||||
<div id="autonomous-controls" style="display:none">
|
||||
<div style="display:grid;grid-template-columns:1fr 1fr 1fr;gap:0.75rem;margin-bottom:0.75rem">
|
||||
<div class="field" style="margin:0">
|
||||
<label>Max Iterations</label>
|
||||
<input type="number" name="autonomous_max_iterations" id="ai-max-iter"
|
||||
value="10" min="3" max="50" step="1" class="input" style="margin-top:0.3rem">
|
||||
</div>
|
||||
<div class="field" style="margin:0">
|
||||
<label>Target Profit Factor</label>
|
||||
<input type="number" name="target_profit_factor" id="ai-target-pf"
|
||||
value="1.5" min="1.0" max="5.0" step="0.1" class="input" style="margin-top:0.3rem">
|
||||
</div>
|
||||
<div class="field" style="margin:0">
|
||||
<label>Target Max Drawdown %</label>
|
||||
<input type="number" name="target_max_drawdown_pct" id="ai-target-dd"
|
||||
value="20" min="5" max="50" step="1" class="input" style="margin-top:0.3rem">
|
||||
</div>
|
||||
</div>
|
||||
<div style="display:grid;grid-template-columns:1fr 2fr;gap:0.75rem">
|
||||
<div class="field" style="margin:0">
|
||||
<label>Target Min Calmar</label>
|
||||
<input type="number" name="target_min_calmar" id="ai-target-calmar"
|
||||
value="0.5" min="0.1" max="5.0" step="0.05" class="input" style="margin-top:0.3rem">
|
||||
</div>
|
||||
<div style="background:rgba(255,255,255,0.03);border:1px solid rgba(255,255,255,0.07);border-radius:8px;padding:0.6rem 0.9rem;font-size:0.72rem;color:#64748b;line-height:1.55">
|
||||
Loop stops early when <strong style="color:#e2e8f0">ALL</strong> targets are met simultaneously.
|
||||
Requires an Anthropic API key configured in Settings.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Hidden inputs for form submission -->
|
||||
<input type="hidden" name="autonomous_mode" id="autonomous-mode-val" value="false">
|
||||
</div>
|
||||
|
||||
<!-- Submit -->
|
||||
<div class="submit-row">
|
||||
<button type="submit" class="btn-start" id="start-btn">
|
||||
@@ -419,13 +574,242 @@
|
||||
body.classList.toggle('open');
|
||||
}
|
||||
|
||||
let scannedData = null; // cache from /api/ea/scan
|
||||
|
||||
function showAddEAModal() {
|
||||
document.getElementById('add-ea-modal').style.display = 'flex';
|
||||
// If we already scanned, populate the detected section
|
||||
if (scannedData) populateDetected(scannedData);
|
||||
}
|
||||
|
||||
function hideAddEAModal() {
|
||||
document.getElementById('add-ea-modal').style.display = 'none';
|
||||
}
|
||||
|
||||
async function scanForEAs() {
|
||||
const btn = document.getElementById('scan-btn');
|
||||
const status = document.getElementById('scan-status');
|
||||
btn.style.opacity = '0.5';
|
||||
btn.style.pointerEvents = 'none';
|
||||
status.textContent = 'Scanning...';
|
||||
try {
|
||||
const resp = await fetch('/api/ea/scan');
|
||||
const data = await resp.json();
|
||||
scannedData = data;
|
||||
const count = data.ex5.length;
|
||||
const setCount = data.set.length;
|
||||
status.style.color = '#00d4aa';
|
||||
status.textContent = `Found ${count} EA${count !== 1 ? 's' : ''} · ${setCount} .set file${setCount !== 1 ? 's' : ''}`;
|
||||
showAddEAModal();
|
||||
} catch(e) {
|
||||
status.style.color = '#ef4444';
|
||||
status.textContent = 'Scan failed: ' + e.message;
|
||||
} finally {
|
||||
btn.style.opacity = '';
|
||||
btn.style.pointerEvents = '';
|
||||
}
|
||||
}
|
||||
|
||||
function populateDetected(data) {
|
||||
const section = document.getElementById('detected-section');
|
||||
const list = document.getElementById('detected-list');
|
||||
const picker = document.getElementById('set-picker');
|
||||
|
||||
if (!data.ex5.length && !data.set.length) return;
|
||||
|
||||
// Build EA pill list
|
||||
list.innerHTML = '';
|
||||
data.ex5.forEach(ea => {
|
||||
const pill = document.createElement('button');
|
||||
pill.type = 'button';
|
||||
pill.style.cssText = 'display:flex;align-items:center;justify-content:space-between;padding:0.55rem 0.85rem;background:rgba(255,255,255,0.04);border:1px solid rgba(255,255,255,0.08);border-radius:9px;cursor:pointer;font-family:inherit;width:100%;text-align:left;transition:all 0.15s';
|
||||
pill.onmouseover = () => { pill.style.background='rgba(124,109,250,0.12)'; pill.style.borderColor='rgba(124,109,250,0.4)'; };
|
||||
pill.onmouseout = () => { pill.style.background='rgba(255,255,255,0.04)'; pill.style.borderColor='rgba(255,255,255,0.08)'; };
|
||||
pill.innerHTML = `
|
||||
<span style="font-size:0.85rem;font-weight:600;color:#e2e8f0">${ea.name}</span>
|
||||
${ea.suggested_set
|
||||
? '<span style="font-size:0.7rem;background:rgba(0,212,170,0.15);color:#00d4aa;padding:2px 8px;border-radius:999px;font-weight:600">.set matched</span>'
|
||||
: '<span style="font-size:0.7rem;color:#64748b">no .set matched</span>'
|
||||
}
|
||||
`;
|
||||
pill.onclick = () => autoFillFromEA(ea);
|
||||
list.appendChild(pill);
|
||||
});
|
||||
|
||||
// Build .set picker
|
||||
picker.innerHTML = '<option value="">— pick from detected .set files —</option>';
|
||||
data.set.forEach(s => {
|
||||
const opt = new Option(s.filename, s.path);
|
||||
picker.add(opt);
|
||||
});
|
||||
picker.style.display = data.set.length ? 'block' : 'none';
|
||||
|
||||
section.style.display = 'block';
|
||||
}
|
||||
|
||||
function autoFillFromEA(ea) {
|
||||
document.getElementById('new-ea-name').value = ea.name;
|
||||
document.getElementById('new-ea-file').value = ea.name;
|
||||
if (ea.suggested_set) {
|
||||
document.getElementById('new-ea-set').value = ea.suggested_set;
|
||||
document.getElementById('set-match-badge').style.display = 'inline';
|
||||
} else {
|
||||
document.getElementById('new-ea-set').value = '';
|
||||
document.getElementById('set-match-badge').style.display = 'none';
|
||||
}
|
||||
// Scroll to bottom of modal to show form fields
|
||||
document.getElementById('add-ea-modal').querySelector('div').scrollTop = 999;
|
||||
}
|
||||
|
||||
function onSetPick() {
|
||||
const val = document.getElementById('set-picker').value;
|
||||
if (val) {
|
||||
document.getElementById('new-ea-set').value = val;
|
||||
document.getElementById('set-match-badge').style.display = 'inline';
|
||||
}
|
||||
}
|
||||
|
||||
async function registerNewEA() {
|
||||
const name = document.getElementById('new-ea-name').value.trim();
|
||||
const file = document.getElementById('new-ea-file').value.trim();
|
||||
const set = document.getElementById('new-ea-set').value.trim();
|
||||
const symbol = document.getElementById('new-ea-symbol').value;
|
||||
const timeframe = document.getElementById('new-ea-tf').value;
|
||||
|
||||
if (!name || !set) {
|
||||
alert('EA Name and .set file path are required.');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const resp = await fetch('/api/ea/register', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({ name, ex5_file: file || name, set_template: set, symbol, timeframe, mode: 'generic' }),
|
||||
});
|
||||
const result = await resp.json();
|
||||
if (result.ok) {
|
||||
hideAddEAModal();
|
||||
// Add to dropdown immediately without reload
|
||||
const sel = document.getElementById('ea_name');
|
||||
const existing = [...sel.options].find(o => o.value === name);
|
||||
if (!existing) {
|
||||
const opt = new Option(name, name);
|
||||
sel.add(opt);
|
||||
}
|
||||
sel.value = name;
|
||||
document.getElementById('scan-status').textContent = `"${name}" registered ✓`;
|
||||
document.getElementById('scan-status').style.color = '#00d4aa';
|
||||
// Reload params for this EA
|
||||
if (typeof loadEAParams === 'function') loadEAParams(name);
|
||||
} else {
|
||||
alert('Error: ' + (result.error || 'Registration failed'));
|
||||
}
|
||||
} catch(e) {
|
||||
alert('Network error: ' + e.message);
|
||||
}
|
||||
}
|
||||
|
||||
async function loadEAParams(eaName) {
|
||||
const grid = document.getElementById('param-grid');
|
||||
if (!grid) return;
|
||||
grid.innerHTML = '<span style="color:#64748b;font-size:0.82rem">Loading parameters...</span>';
|
||||
try {
|
||||
const resp = await fetch('/api/ea_params?ea=' + encodeURIComponent(eaName));
|
||||
const params = await resp.json();
|
||||
if (params.error) { grid.innerHTML = `<span style="color:#ef4444;font-size:0.82rem">${params.error}</span>`; return; }
|
||||
if (!params.length) { grid.innerHTML = '<span style="color:#64748b;font-size:0.82rem">No optimizable parameters found in .set file.</span>'; return; }
|
||||
grid.innerHTML = params.map(p => `
|
||||
<label class="param-check">
|
||||
<input type="checkbox" name="selected_params" value="${p.name}" ${p.optimize ? 'checked' : ''}>
|
||||
<span title="${p.range || ''}">${p.name.replace('Inp','')}</span>
|
||||
</label>
|
||||
`).join('');
|
||||
} catch(e) {
|
||||
grid.innerHTML = `<span style="color:#ef4444;font-size:0.82rem">Error loading params: ${e.message}</span>`;
|
||||
}
|
||||
}
|
||||
|
||||
// Reload params + symbol/timeframe when EA selection changes
|
||||
document.getElementById('ea_name').addEventListener('change', function() {
|
||||
loadEAParams(this.value);
|
||||
});
|
||||
|
||||
function toggleAutonomous(btn) {
|
||||
const isOn = btn.style.background === 'rgb(79, 70, 229)' || btn.classList.contains('on');
|
||||
if (isOn) {
|
||||
btn.style.background = 'rgba(255,255,255,0.12)';
|
||||
btn.classList.remove('on');
|
||||
btn.querySelector('span') && (btn.querySelector('span').style.transform = '');
|
||||
document.getElementById('autonomous-controls').style.display = 'none';
|
||||
document.getElementById('autonomous-mode-val').value = 'false';
|
||||
} else {
|
||||
btn.style.background = '#4f46e5';
|
||||
btn.classList.add('on');
|
||||
document.getElementById('autonomous-controls').style.display = 'block';
|
||||
document.getElementById('autonomous-mode-val').value = 'true';
|
||||
}
|
||||
// Maintain the ::after pseudo-element position via class
|
||||
btn.style.setProperty('--toggle-x', isOn ? '0px' : '20px');
|
||||
}
|
||||
|
||||
// ── Toast helper for inline validation feedback ────────────────────────
|
||||
function showSetupError(msg) {
|
||||
let toast = document.getElementById('setup-toast');
|
||||
if (!toast) {
|
||||
toast = document.createElement('div');
|
||||
toast.id = 'setup-toast';
|
||||
toast.style.cssText = 'position:fixed;top:20px;right:20px;background:rgba(239,68,68,0.95);color:white;padding:0.8rem 1.2rem;border-radius:8px;font-size:0.85rem;font-weight:600;z-index:9999;box-shadow:0 4px 12px rgba(0,0,0,0.3);max-width:380px;line-height:1.4;';
|
||||
document.body.appendChild(toast);
|
||||
}
|
||||
toast.textContent = msg;
|
||||
toast.style.display = 'block';
|
||||
clearTimeout(toast._timer);
|
||||
toast._timer = setTimeout(() => { toast.style.display = 'none'; }, 5000);
|
||||
}
|
||||
|
||||
function validateSetup(data) {
|
||||
if (!data.ea_name || !data.ea_name.trim()) {
|
||||
return 'Please select an EA before starting.';
|
||||
}
|
||||
if (!data.symbol || !data.timeframe) {
|
||||
return 'Symbol and timeframe are required.';
|
||||
}
|
||||
const ds = (s) => new Date((s || '').replace(/\./g, '-'));
|
||||
const ts = ds(data.train_start), te = ds(data.train_end);
|
||||
const vs = ds(data.val_start), ve = ds(data.val_end);
|
||||
if (isNaN(ts) || isNaN(te) || isNaN(vs) || isNaN(ve)) {
|
||||
return 'All four dates must be valid (YYYY-MM-DD).';
|
||||
}
|
||||
if (ts >= te) return 'Training start must come before training end.';
|
||||
if (vs >= ve) return 'Validation start must come before validation end.';
|
||||
if (vs < te) return 'Validation period should start after the training period (walk-forward).';
|
||||
if (data.budget_minutes < 1) return 'Time budget must be at least 1 minute.';
|
||||
if (!data.selected_params || data.selected_params.length === 0) {
|
||||
return 'Pick at least one parameter to optimize.';
|
||||
}
|
||||
if (data.autonomous_mode) {
|
||||
if (data.autonomous_max_iterations < 1 || data.autonomous_max_iterations > 100) {
|
||||
return 'AI max iterations must be between 1 and 100.';
|
||||
}
|
||||
if (data.target_profit_factor < 1) return 'Target profit factor must be ≥ 1.';
|
||||
if (data.target_max_drawdown_pct <= 0 || data.target_max_drawdown_pct > 100) {
|
||||
return 'Target max drawdown must be between 0 and 100 (%).';
|
||||
}
|
||||
}
|
||||
return null; // valid
|
||||
}
|
||||
|
||||
document.getElementById('setup-form').addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
const btn = document.getElementById('start-btn');
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span>⏳</span> Starting...';
|
||||
const restoreBtn = () => {
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<span>⚡</span> Start Optimization';
|
||||
};
|
||||
|
||||
const form = e.target;
|
||||
const autonomousOn = document.getElementById('autonomous-mode-val').value === 'true';
|
||||
const data = {
|
||||
ea_name: form.ea_name.value,
|
||||
symbol: form.symbol.value,
|
||||
@@ -438,8 +822,23 @@
|
||||
budget_minutes: parseInt(form.budget_minutes.value),
|
||||
selected_params: [...form.querySelectorAll('input[name=selected_params]:checked')]
|
||||
.map(cb => cb.value),
|
||||
// Autonomous loop
|
||||
autonomous_mode: autonomousOn,
|
||||
autonomous_max_iterations: parseInt(document.getElementById('ai-max-iter').value) || 10,
|
||||
target_profit_factor: parseFloat(document.getElementById('ai-target-pf').value) || 1.5,
|
||||
target_max_drawdown_pct: parseFloat(document.getElementById('ai-target-dd').value) || 20.0,
|
||||
target_min_calmar: parseFloat(document.getElementById('ai-target-calmar').value) || 0.5,
|
||||
};
|
||||
|
||||
const validationError = validateSetup(data);
|
||||
if (validationError) {
|
||||
showSetupError(validationError);
|
||||
return;
|
||||
}
|
||||
|
||||
btn.disabled = true;
|
||||
btn.innerHTML = '<span>⏳</span> Starting...';
|
||||
|
||||
try {
|
||||
const resp = await fetch('/api/start', {
|
||||
method: 'POST',
|
||||
@@ -450,14 +849,12 @@
|
||||
if (result.ok) {
|
||||
window.location.href = '/dashboard';
|
||||
} else {
|
||||
alert('Could not start: ' + (result.msg || 'Unknown error'));
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<span>⚡</span> Start Optimization';
|
||||
showSetupError('Could not start: ' + (result.msg || 'Unknown error'));
|
||||
restoreBtn();
|
||||
}
|
||||
} catch(err) {
|
||||
alert('Network error: ' + err.message);
|
||||
btn.disabled = false;
|
||||
btn.innerHTML = '<span>⚡</span> Start Optimization';
|
||||
showSetupError('Network error: ' + err.message);
|
||||
restoreBtn();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
Reference in New Issue
Block a user