From 80c6d792f4355adb402daaba4a5e6738f71d9ea7 Mon Sep 17 00:00:00 2001 From: Kunthawat Greethong Date: Thu, 27 Aug 2026 12:09:01 +0700 Subject: [PATCH] feat(ui): real forward-test lifecycle panel (not cosmetic) Forward mode in the simulation tab now calls the durable /api/v1/forward lifecycle instead of the single-pass /api/v1/simulation (which only differed by a mode string). runSimulation branches to /api/v1/forward for forward mode, loads runs on mount, and renders a forward panel listing each paper run with its status (frozen/executed/marked/matured), non-PIT badge, holdings and a Mark / Mature action per run. Frontend build passes (bundle index-INuvPOCF.js). --- frontend/src/App.vue | 81 +++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 76 insertions(+), 5 deletions(-) diff --git a/frontend/src/App.vue b/frontend/src/App.vue index b602c5e..f83b3c1 100644 --- a/frontend/src/App.vue +++ b/frontend/src/App.vue @@ -259,11 +259,21 @@ async function runSimulation() { simLoading.value = true simResult.value = null try { - simResult.value = await fetchJson('/api/v1/simulation', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ capital: Number(simCapital.value), mode: simMode.value }), - }) + if (simMode.value === 'forward') { + // REAL forward lifecycle (not cosmetic): freeze + execute a paper run. + simResult.value = await fetchJson('/api/v1/forward', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ capital: Number(simCapital.value), use_pit: false }), + }) + await loadForward() + } else { + simResult.value = await fetchJson('/api/v1/simulation', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ capital: Number(simCapital.value), mode: 'backtest' }), + }) + } } catch (caught) { notice.value = caught.message } finally { @@ -271,6 +281,38 @@ async function runSimulation() { } } +// ---- real forward-test lifecycle (durable paper runs) ---- +const fwdRuns = ref([]) +const fwdLoading = ref(false) + +async function loadForward() { + fwdLoading.value = true + try { + const body = await fetchJson('/api/v1/forward') + fwdRuns.value = body.runs ?? [] + } catch (caught) { + notice.value = caught.message + } finally { + fwdLoading.value = false + } +} +async function markForward(id) { + try { + await fetchJson(`/api/v1/forward/${id}/mark`, { method: 'POST' }) + await loadForward() + } catch (caught) { + notice.value = caught.message + } +} +async function matureForward(id) { + try { + await fetchJson(`/api/v1/forward/${id}/mature`, { method: 'POST' }) + await loadForward() + } catch (caught) { + notice.value = caught.message + } +} + async function loadDashboard() { loading.value = true error.value = '' @@ -300,6 +342,7 @@ async function loadDashboard() { paperAuthWarning.value = sessionBody.warning || '' backtest.value = backtestBody researchRun.value = researchBody + await loadForward() // load durable forward-test runs (not cosmetic) } catch (caught) { error.value = caught.message } finally { @@ -679,6 +722,34 @@ onMounted(async () => { await loadDashboard(); await loadBacktestRuns() })
กด 'คำนวณการจัดสรร' เพื่อดูว่า 50/20/30 จัดสรรทุนของคุณไปที่หุ้นไหนบ้าง
+ + +
+
Forward Test (Paper) — สัญญาณถูกตรึง ณ เวลาสร้าง
+

สร้าง forward run → สัญญาณ (คะแนน) ถูก freeze ทันทีที่สร้าง แล้ว execute ด้วยราคาหลัง freeze. กด Mark ตามราคาล่าสุด, Mature เพื่อปิด run. เป็น Paper เท่านั้น.

+
ยังไม่มี forward run — กด 'คำนวณการจัดสรร' ข้างบนเพื่อสร้าง
+ + + + + + + + + + + + + +
#สถานะทุนลงทุนถือผลตอบแทนตรวจ
{{ run.id.slice(0, 12) }} + {{ run.status }} + non-PIT + {{ formatNumber(run.capital, 0) }}{{ formatNumber(run.invested, 0) }}{{ Object.keys(run.holdings || {}).join(', ') || '—' }}{{ run.net_return != null ? (run.net_return * 100).toFixed(2) + '%' : '—' }} + + + ปิดแล้ว +
+