// Dual-mode data source: static (bootstrap tag) vs server (fetch /api/*). window.BacktestDataSource = { isStatic() { return !!document.getElementById('run-data'); }, async loadBootstrap() { if (this.isStatic()) { const runData = JSON.parse(document.getElementById('run-data').textContent); // Static mode: version is frozen at report-generation time and lives // inside the embedded payload. Fall back to defaults for old reports. return { config: null, runs: [], currentRun: runData, version: runData.version || { semver: '0.4.1', build: 0, commit: 'dev', deployed_at: null }, }; } const [config, runs, version] = await Promise.all([ fetch('/api/config').then((r) => { if (!r.ok) throw new Error(`GET /api/config failed: ${r.status}`); return r.json(); }), fetch('/api/runs').then((r) => { if (!r.ok) throw new Error(`GET /api/runs failed: ${r.status}`); return r.json(); }), // Version is non-critical — never fail the whole bootstrap on a /api/version blip. fetch('/api/version') .then((r) => (r.ok ? r.json() : null)) .catch(() => null), ]); return { config, runs, currentRun: null, version }; }, async runBacktest(request) { const r = await fetch('/api/run', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(request), }); if (r.status === 429) throw new Error('A backtest is already running'); if (!r.ok) throw new Error(`Run failed: ${r.status} ${await r.text()}`); return r.json(); }, async getRun(id) { const r = await fetch(`/api/runs/${id}`); if (!r.ok) throw new Error(`Load run failed: ${r.status}`); return r.json(); }, async loadRuns() { const r = await fetch('/api/runs'); if (!r.ok) throw new Error(`GET /api/runs failed: ${r.status}`); return r.json(); }, async loadBars({ symbol, timeframe, start, end, indicators = [], params = {} }) { const qs = new URLSearchParams({ symbol, timeframe, start, end, }); if (indicators.length) qs.set('indicators', indicators.join(',')); for (const [k, v] of Object.entries(params)) { if (v !== undefined && v !== null && v !== '') qs.set(k, String(v)); } const r = await fetch(`/api/bars?${qs.toString()}`); if (!r.ok) { const body = await r.text(); throw new Error(`GET /api/bars failed: ${r.status} ${body}`); } return r.json(); }, };